Files
thefeed/.github/workflows/build.yml
T

312 lines
14 KiB
YAML

name: Build
on:
push:
tags: ['v*']
permissions:
contents: write
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: '1.26'
cache: true
- name: Cache Go build
uses: actions/cache@v4
with:
path: ~/.cache/go-build
key: gobuild-test-${{ runner.os }}-${{ hashFiles('**/go.sum') }}
restore-keys: gobuild-test-${{ runner.os }}-
- name: Test + vet
run: |
go vet ./... &
go test -race -count=1 ./...
wait
build:
needs: test
runs-on: ubuntu-latest
strategy:
matrix:
include:
- goos: linux
goarch: amd64
- goos: linux
goarch: arm64
- goos: darwin
goarch: amd64
- goos: darwin
goarch: arm64
- goos: freebsd
goarch: amd64
- goos: freebsd
goarch: arm64
- goos: windows
goarch: amd64
- goos: android
goarch: arm64
- goos: android
goarch: arm
goarm: '7'
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: '1.26'
cache: true
- name: Cache Go build
uses: actions/cache@v4
with:
path: ~/.cache/go-build
key: gobuild-${{ matrix.goos }}-${{ matrix.goarch }}-${{ hashFiles('**/go.sum') }}
restore-keys: gobuild-${{ matrix.goos }}-${{ matrix.goarch }}-
- name: Install UPX
if: matrix.goos == 'linux' || matrix.goos == 'windows'
run: sudo apt-get install -y upx-ucl
- name: Build Server
if: matrix.goos != 'android'
env:
GOOS: ${{ matrix.goos }}
GOARCH: ${{ matrix.goarch }}
CGO_ENABLED: '0'
run: |
VERSION=${GITHUB_REF_NAME:-dev}
COMMIT=$(git rev-parse --short HEAD)
DATE=$(date -u +%Y-%m-%dT%H:%M:%SZ)
LDFLAGS="-s -w -X github.com/sartoopjj/thefeed/internal/version.Version=${VERSION} -X github.com/sartoopjj/thefeed/internal/version.Commit=${COMMIT} -X github.com/sartoopjj/thefeed/internal/version.Date=${DATE}"
ext=""
if [ "${{ matrix.goos }}" = "windows" ]; then ext=".exe"; fi
go build -trimpath -ldflags="${LDFLAGS}" -o build/thefeed-server-${{ matrix.goos }}-${{ matrix.goarch }}${ext} ./cmd/server
- name: Build Client
env:
GOOS: ${{ matrix.goos }}
GOARCH: ${{ matrix.goarch }}
GOARM: ${{ matrix.goarm || '' }}
CGO_ENABLED: '0'
run: |
VERSION=${GITHUB_REF_NAME:-dev}
COMMIT=$(git rev-parse --short HEAD)
DATE=$(date -u +%Y-%m-%dT%H:%M:%SZ)
ext=""
BUILD_MODE=""
if [ "${{ matrix.goos }}" = "windows" ]; then ext=".exe"; fi
# AssetTemplate matches the published filename in the
# thefeed-files repo so the in-app update prompt links straight
# to the right binary. {V} is replaced at runtime with the
# version string read from the public VERSION file.
if [ "${{ matrix.goos }}" = "android" ]; then
ASSET_TEMPLATE="thefeed-client-android-${{ matrix.goarch }}"
else
ASSET_TEMPLATE="thefeed-client-{V}-${{ matrix.goos }}-${{ matrix.goarch }}${ext}"
fi
LDFLAGS="-s -w -X github.com/sartoopjj/thefeed/internal/version.Version=${VERSION} -X github.com/sartoopjj/thefeed/internal/version.Commit=${COMMIT} -X github.com/sartoopjj/thefeed/internal/version.Date=${DATE} -X github.com/sartoopjj/thefeed/internal/version.AssetTemplate=${ASSET_TEMPLATE}"
# Modern Android requires PIE for executables launched via exec(),
# and several heuristic AV engines (Kaspersky Boogr.gsh,
# several VT vendors) flag non-PIE bundled binaries as suspicious.
# Force PIE for the binaries that ship inside the APK.
if [ "${{ matrix.goos }}" = "android" ]; then
BUILD_MODE="-buildmode=pie"
fi
# Android: build with cgo so DNS resolution goes through bionic
# libc / netd instead of Go's pure-Go resolver, which on Android
# finds /etc/resolv.conf empty and dies with "[::1]:53 connection
# refused". Wire the right NDK clang per architecture.
if [ "${{ matrix.goos }}" = "android" ]; then
export CGO_ENABLED=1
case "${{ matrix.goarch }}" in
arm)
export CC="$ANDROID_NDK_LATEST_HOME/toolchains/llvm/prebuilt/linux-x86_64/bin/armv7a-linux-androideabi24-clang"
;;
arm64)
export CC="$ANDROID_NDK_LATEST_HOME/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android24-clang"
;;
esac
test -x "$CC" || { echo "NDK clang not found at $CC"; exit 1; }
fi
if [ "${{ matrix.goos }}" = "android" ] && [ "${{ matrix.goarch }}" = "arm64" ]; then
out="build/thefeed-client-android-arm64"
elif [ "${{ matrix.goos }}" = "android" ] && [ "${{ matrix.goarch }}" = "arm" ]; then
out="build/thefeed-client-android-arm"
else
out="build/thefeed-client-${VERSION}-${{ matrix.goos }}-${{ matrix.goarch }}${ext}"
fi
go build -trimpath -buildvcs=false $BUILD_MODE -ldflags="${LDFLAGS}" -o "$out" ./cmd/client
- name: Compress with UPX
if: matrix.goos == 'linux' || matrix.goos == 'windows'
run: |
# Keep best/lzma for small binaries; xargs -P does them
# in parallel across CPUs so wall time stays low.
find build -maxdepth 1 -type f -print0 \
| xargs -0 -n1 -P "$(nproc)" -I{} sh -c 'upx --best --lzma "$1" || true' _ {}
- name: Upload artifacts
uses: actions/upload-artifact@v4
with:
name: binaries-${{ matrix.goos }}-${{ matrix.goarch }}
path: build/
android-apk:
needs: build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Download all artifacts
uses: actions/download-artifact@v4
with:
path: artifacts
merge-multiple: true
- name: Stage Android client binary as JNI library
run: |
mkdir -p android/app/src/main/jniLibs/arm64-v8a
mkdir -p android/app/src/main/jniLibs/armeabi-v7a
test -f artifacts/thefeed-client-android-arm64
test -f artifacts/thefeed-client-android-arm
cp artifacts/thefeed-client-android-arm64 android/app/src/main/jniLibs/arm64-v8a/libthefeed.so
cp artifacts/thefeed-client-android-arm android/app/src/main/jniLibs/armeabi-v7a/libthefeed.so
- name: Decode signing keystore
env:
KEYSTORE_BASE64: ${{ secrets.KEYSTORE_BASE64 }}
run: |
if [ -n "$KEYSTORE_BASE64" ]; then
echo "$KEYSTORE_BASE64" | base64 -d > android/app/keystore.jks
echo "Keystore decoded successfully"
else
echo "No KEYSTORE_BASE64 secret set — will use debug signing"
fi
- name: Set up Java
uses: actions/setup-java@v4
with:
distribution: temurin
java-version: '17'
- name: Set up Gradle
uses: gradle/actions/setup-gradle@v4
- name: Build Android APK
working-directory: android
env:
KEYSTORE_PASSWORD: ${{ secrets.KEYSTORE_PASSWORD }}
KEY_ALIAS: ${{ secrets.KEY_ALIAS }}
KEY_PASSWORD: ${{ secrets.KEY_PASSWORD }}
run: |
VERSION=${GITHUB_REF_NAME:-dev}
if [ ! -x ./gradlew ]; then gradle wrapper --gradle-version 8.10.2; fi
if [ -f app/keystore.jks ]; then BT=release; TASK=assembleRelease; else BT=debug; TASK=assembleDebug; fi
# No `clean` — fresh checkout is already clean and the
# Gradle build cache can reuse work between runs.
./gradlew --no-daemon --build-cache $TASK
APK_DIR=app/build/outputs/apk/$BT
cp "$APK_DIR"/app-arm64-v8a-${BT}.apk ../artifacts/thefeed-android-${VERSION}-arm64-v8a.apk
cp "$APK_DIR"/app-armeabi-v7a-${BT}.apk ../artifacts/thefeed-android-${VERSION}-armeabi-v7a.apk
- name: Upload Android APK artifacts
uses: actions/upload-artifact@v4
with:
name: thefeed-android-apk
path: |
artifacts/thefeed-android-*-arm64-v8a.apk
artifacts/thefeed-android-*-armeabi-v7a.apk
ios-ipa:
needs: test
runs-on: macos-14
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: '1.26'
cache: true
- name: Select Xcode
run: sudo xcode-select -s /Applications/Xcode.app
- name: Install gomobile + gobind
run: |
go install golang.org/x/mobile/cmd/gomobile@latest
go install golang.org/x/mobile/cmd/gobind@latest
gomobile init
go get golang.org/x/mobile/bind golang.org/x/mobile/bind/objc
go mod tidy
- name: Build Mobile.xcframework
run: gomobile bind -iosversion=14.0 -target=ios,iossimulator -o ios/Mobile.xcframework ./mobile
- name: Archive (unsigned)
run: |
xcodebuild \
-project ios/Thefeed.xcodeproj \
-scheme Thefeed \
-configuration Release \
-destination 'generic/platform=iOS' \
-archivePath build/Thefeed.xcarchive \
archive \
CODE_SIGN_IDENTITY="" \
CODE_SIGNING_REQUIRED=NO \
CODE_SIGNING_ALLOWED=NO \
DEVELOPMENT_TEAM=""
- name: Pack unsigned IPA
run: |
VERSION=${GITHUB_REF_NAME:-dev}
mkdir -p build/Payload
cp -r build/Thefeed.xcarchive/Products/Applications/Thefeed.app build/Payload/
(cd build && zip -qry "thefeed-ios-${VERSION}-unsigned.ipa" Payload)
ls -lh build/*.ipa
- name: Upload iOS IPA artifact
uses: actions/upload-artifact@v4
with:
name: thefeed-ios-ipa
path: build/thefeed-ios-*-unsigned.ipa
release:
needs: [build, android-apk, ios-ipa]
if: startsWith(github.ref, 'refs/tags/v')
runs-on: ubuntu-latest
steps:
- name: Download all artifacts
uses: actions/download-artifact@v4
with:
path: artifacts
merge-multiple: true
- name: Create Release
uses: softprops/action-gh-release@v2
with:
files: artifacts/*
generate_release_notes: true
prerelease: ${{ contains(github.ref_name, '-') }}
append_body: true
body: |
## Downloads
| Platform | Architecture | Download |
|----------|-------------|----------|
| Linux | amd64 | [server-سرور](https://github.com/${{ github.repository }}/releases/download/${{ github.ref_name }}/thefeed-server-linux-amd64) / [client-کلاینت](https://github.com/${{ github.repository }}/releases/download/${{ github.ref_name }}/thefeed-client-${{ github.ref_name }}-linux-amd64) |
| Linux | arm64 | [server-سرور](https://github.com/${{ github.repository }}/releases/download/${{ github.ref_name }}/thefeed-server-linux-arm64) / [client-کلاینت](https://github.com/${{ github.repository }}/releases/download/${{ github.ref_name }}/thefeed-client-${{ github.ref_name }}-linux-arm64) |
| macOS | amd64 (Intel) | [server-سرور](https://github.com/${{ github.repository }}/releases/download/${{ github.ref_name }}/thefeed-server-darwin-amd64) / [client-کلاینت](https://github.com/${{ github.repository }}/releases/download/${{ github.ref_name }}/thefeed-client-${{ github.ref_name }}-darwin-amd64) |
| macOS | arm64 (Apple Silicon) | [server-سرور](https://github.com/${{ github.repository }}/releases/download/${{ github.ref_name }}/thefeed-server-darwin-arm64) / [client-کلاینت](https://github.com/${{ github.repository }}/releases/download/${{ github.ref_name }}/thefeed-client-${{ github.ref_name }}-darwin-arm64) |
| FreeBSD | amd64 | [server-سرور](https://github.com/${{ github.repository }}/releases/download/${{ github.ref_name }}/thefeed-server-freebsd-amd64) / [client-کلاینت](https://github.com/${{ github.repository }}/releases/download/${{ github.ref_name }}/thefeed-client-${{ github.ref_name }}-freebsd-amd64) |
| FreeBSD | arm64 | [server-سرور](https://github.com/${{ github.repository }}/releases/download/${{ github.ref_name }}/thefeed-server-freebsd-arm64) / [client-کلاینت](https://github.com/${{ github.repository }}/releases/download/${{ github.ref_name }}/thefeed-client-${{ github.ref_name }}-freebsd-arm64) |
| Windows | amd64 | [server-سرور](https://github.com/${{ github.repository }}/releases/download/${{ github.ref_name }}/thefeed-server-windows-amd64.exe) / [client-کلاینت](https://github.com/${{ github.repository }}/releases/download/${{ github.ref_name }}/thefeed-client-${{ github.ref_name }}-windows-amd64.exe) |
| Android | arm64-v8a (most modern phones) | [thefeed-android-${{ github.ref_name }}-arm64-v8a.apk - اندروید (گوشی‌های جدید)](https://github.com/${{ github.repository }}/releases/download/${{ github.ref_name }}/thefeed-android-${{ github.ref_name }}-arm64-v8a.apk) |
| Android | armeabi-v7a (older 32-bit phones) | [thefeed-android-${{ github.ref_name }}-armeabi-v7a.apk - اندروید (دستگاه‌های قدیمی‌تر)](https://github.com/${{ github.repository }}/releases/download/${{ github.ref_name }}/thefeed-android-${{ github.ref_name }}-armeabi-v7a.apk) |
| iOS (unsigned) | universal | [thefeed-ios-${{ github.ref_name }}-unsigned.ipa](https://github.com/${{ github.repository }}/releases/download/${{ github.ref_name }}/thefeed-ios-${{ github.ref_name }}-unsigned.ipa) |
**iOS / iPadOS (preview, iOS 14+):** the `.ipa` is unsigned. Re-sign it with your own Apple ID and provisioning profile (AltStore, Sideloadly, or `xcrun altool` upload to your own TestFlight). It will not install directly from a download.