name: Build Binaries # Build multi-platform binaries on release or manual trigger # Produces distributable packages for Linux, macOS, and Windows on: release: types: [published] workflow_dispatch: # Environment variables for build configuration env: GO_VERSION: "1.25.7" NODE_VERSION: "22" PNPM_VERSION: "10" ARTIFACT_RETENTION_DAYS: 60 # Artifact naming: {ARTIFACT_PREFIX}_{version}_{os}_{arch}.tar.gz|zip ARTIFACT_PREFIX: memos jobs: # Job 1: Extract version information # - For git tags: use tag version (e.g., v0.28.1 -> 0.28.1) # - For branches: use branch-name-shortSHA format prepare: name: Extract Version runs-on: ubuntu-latest outputs: version: ${{ steps.version.outputs.version }} steps: - name: Checkout code uses: actions/checkout@v6 with: fetch-depth: 0 # Full history for git describe - name: Extract version id: version run: | # Try to get version from git tag TAG=$(git describe --tags --exact-match 2>/dev/null || echo "") if [ -n "$TAG" ]; then echo "version=${TAG#v}" >> $GITHUB_OUTPUT echo "Version from tag: ${TAG#v}" else # Use branch name + short SHA BRANCH="${GITHUB_REF_NAME//\//-}" SHORT_SHA="${GITHUB_SHA:0:7}" echo "version=${BRANCH}-${SHORT_SHA}" >> $GITHUB_OUTPUT echo "Version from branch: ${BRANCH}-${SHORT_SHA}" fi # Job 2: Build frontend assets # - Builds React frontend with Vite # - Produces static files that will be embedded in Go binary # - Shared across all platform builds build-frontend: name: Build Frontend needs: prepare runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v6 - name: Setup pnpm uses: pnpm/action-setup@v4.2.0 with: version: ${{ env.PNPM_VERSION }} - name: Setup Node.js uses: actions/setup-node@v6 with: node-version: ${{ env.NODE_VERSION }} cache: pnpm cache-dependency-path: web/pnpm-lock.yaml - name: Get pnpm store directory id: pnpm-cache shell: bash run: echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT - name: Setup pnpm cache uses: actions/cache@v5 with: path: ${{ steps.pnpm-cache.outputs.STORE_PATH }} key: ${{ runner.os }}-pnpm-store-${{ hashFiles('web/pnpm-lock.yaml') }} restore-keys: ${{ runner.os }}-pnpm-store- - name: Install dependencies working-directory: web run: pnpm install --frozen-lockfile - name: Build frontend working-directory: web run: pnpm release - name: Upload frontend artifacts uses: actions/upload-artifact@v6 with: name: frontend-dist path: server/router/frontend/dist retention-days: ${{ env.ARTIFACT_RETENTION_DAYS }} # Job 3: Build Go binaries for multiple platforms # - Cross-compiles using native Go toolchain # - Embeds frontend assets built in previous job # - Produces static binaries with no external dependencies # - Packages as tar.gz (Unix) or zip (Windows) build-binaries: name: Build ${{ matrix.goos }}-${{ matrix.goarch }}${{ matrix.goarm && format('v{0}', matrix.goarm) || '' }} needs: [prepare, build-frontend] runs-on: ubuntu-latest strategy: fail-fast: false matrix: include: # Linux targets - goos: linux goarch: amd64 - goos: linux goarch: arm64 - goos: linux goarch: arm goarm: "7" # macOS targets - goos: darwin goarch: amd64 # Intel Macs - goos: darwin goarch: arm64 # Apple Silicon # Windows targets - goos: windows goarch: amd64 steps: - name: Checkout code uses: actions/checkout@v6 - name: Setup Go uses: actions/setup-go@v6 with: go-version: ${{ env.GO_VERSION }} cache: true - name: Download frontend artifacts uses: actions/download-artifact@v7 with: name: frontend-dist path: server/router/frontend/dist - name: Build binary env: GOOS: ${{ matrix.goos }} GOARCH: ${{ matrix.goarch }} GOARM: ${{ matrix.goarm }} CGO_ENABLED: "0" run: | # Determine output binary name OUTPUT_NAME="memos" if [ "$GOOS" = "windows" ]; then OUTPUT_NAME="memos.exe" fi mkdir -p build # Build static binary with optimizations go build \ -trimpath \ -ldflags="-s -w -extldflags '-static'" \ -tags netgo,osusergo \ -o "build/${OUTPUT_NAME}" \ ./cmd/memos echo "✓ Built: build/${OUTPUT_NAME}" ls -lh build/ - name: Package binary id: package env: VERSION: ${{ needs.prepare.outputs.version }} GOOS: ${{ matrix.goos }} GOARCH: ${{ matrix.goarch }} GOARM: ${{ matrix.goarm }} run: | cd build # Construct package name: {prefix}_{version}_{os}_{arch}[v{arm_version}] PACKAGE_NAME="${ARTIFACT_PREFIX}_${VERSION}_${GOOS}_${GOARCH}" if [ -n "$GOARM" ]; then PACKAGE_NAME="${PACKAGE_NAME}v${GOARM}" fi # Package based on platform if [ "$GOOS" = "windows" ]; then ARTIFACT_NAME="${PACKAGE_NAME}.zip" zip -q "${ARTIFACT_NAME}" memos.exe else ARTIFACT_NAME="${PACKAGE_NAME}.tar.gz" tar czf "${ARTIFACT_NAME}" memos fi # Output for next step echo "ARTIFACT_NAME=${ARTIFACT_NAME}" >> $GITHUB_ENV echo "✓ Package created: ${ARTIFACT_NAME} ($(du -h "${ARTIFACT_NAME}" | cut -f1))" - name: Upload binary artifact uses: actions/upload-artifact@v6 with: name: ${{ env.ARTIFACT_NAME }} path: build/${{ env.ARTIFACT_NAME }} retention-days: ${{ env.ARTIFACT_RETENTION_DAYS }} # Job 4: Upload artifacts to GitHub Release # - Only runs when triggered by a release publish event # - Downloads all built artifacts and attaches them to the release release: name: Upload Release Assets needs: build-binaries if: github.event_name == 'release' runs-on: ubuntu-latest permissions: contents: write steps: - name: Download all artifacts uses: actions/download-artifact@v7 with: path: artifacts pattern: ${{ env.ARTIFACT_PREFIX }}_* merge-multiple: true - name: Upload to GitHub Release uses: softprops/action-gh-release@v2 with: files: artifacts/*