diff --git a/.github/workflows/cli_go_release.yml b/.github/workflows/cli_go_release.yml deleted file mode 100644 index d0d93d52d..000000000 --- a/.github/workflows/cli_go_release.yml +++ /dev/null @@ -1,284 +0,0 @@ -name: "CLI: Build Go binaries and platform wheels" -run-name: "CLI Go release ${{ inputs.version || 'dev' }} by @${{ github.actor }}" - -on: - workflow_dispatch: - inputs: - version: - description: "Version to build (reads from __init__.py if empty)" - required: false - type: string - publish: - description: "Publish wheels to PyPI" - required: false - type: boolean - default: false - - # Also triggered by the main release workflow for libs/cli - workflow_call: - inputs: - version: - required: false - type: string - publish: - required: false - type: boolean - -permissions: - contents: read - -env: - GO_VERSION: "1.23" - PYTHON_VERSION: "3.11" - WORKING_DIR: "libs/cli" - -jobs: - # ----------------------------------------------------------------------- - # Stage 1: Cross-compile Go binaries for all platforms - # ----------------------------------------------------------------------- - build-go: - runs-on: ubuntu-latest - strategy: - matrix: - include: - - goos: linux - goarch: amd64 - plat: manylinux_2_17_x86_64.manylinux2014_x86_64 - - goos: linux - goarch: arm64 - plat: manylinux_2_17_aarch64.manylinux2014_aarch64 - - goos: darwin - goarch: amd64 - plat: macosx_11_0_x86_64 - - goos: darwin - goarch: arm64 - plat: macosx_11_0_arm64 - - goos: windows - goarch: amd64 - plat: win_amd64 - - steps: - - uses: actions/checkout@v6 - - - name: Set up Go - uses: actions/setup-go@v5 - with: - go-version: ${{ env.GO_VERSION }} - - - name: Determine version - id: version - working-directory: ${{ env.WORKING_DIR }} - run: | - if [ -n "${{ inputs.version }}" ]; then - echo "version=${{ inputs.version }}" >> "$GITHUB_OUTPUT" - else - VERSION=$(grep -m 1 '^__version__' langgraph_cli/__init__.py | cut -d '"' -f 2) - echo "version=$VERSION" >> "$GITHUB_OUTPUT" - fi - COMMIT=$(git rev-parse --short HEAD) - echo "commit=$COMMIT" >> "$GITHUB_OUTPUT" - - - name: Build Go binary - working-directory: ${{ env.WORKING_DIR }} - env: - GOOS: ${{ matrix.goos }} - GOARCH: ${{ matrix.goarch }} - CGO_ENABLED: "0" - run: | - EXT="" - if [ "${{ matrix.goos }}" = "windows" ]; then EXT=".exe"; fi - OUTNAME="langgraph-${{ matrix.goos }}-${{ matrix.goarch }}${EXT}" - - go build \ - -ldflags "-s -w \ - -X 'github.com/langchain-ai/langgraph/libs/cli/internal/version.Version=${{ steps.version.outputs.version }}' \ - -X 'github.com/langchain-ai/langgraph/libs/cli/internal/version.Commit=${{ steps.version.outputs.commit }}' \ - -X 'github.com/langchain-ai/langgraph/libs/cli/internal/version.Date=$(date -u +%Y-%m-%dT%H:%M:%SZ)'" \ - -o "$OUTNAME" \ - cmd/langgraph/main.go - - echo "binary=$OUTNAME" >> "$GITHUB_OUTPUT" - echo "Built $OUTNAME ($(stat -c%s "$OUTNAME" 2>/dev/null || stat -f%z "$OUTNAME") bytes)" - - - name: Upload binary artifact - uses: actions/upload-artifact@v7 - with: - name: go-binary-${{ matrix.goos }}-${{ matrix.goarch }} - path: ${{ env.WORKING_DIR }}/langgraph-${{ matrix.goos }}-${{ matrix.goarch }}* - retention-days: 7 - - # ----------------------------------------------------------------------- - # Stage 2: Run Go tests - # ----------------------------------------------------------------------- - test-go: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v6 - - name: Set up Go - uses: actions/setup-go@v5 - with: - go-version: ${{ env.GO_VERSION }} - - name: Run tests - working-directory: ${{ env.WORKING_DIR }} - run: go test -count=1 -race ./... - - # ----------------------------------------------------------------------- - # Stage 3: Build platform-specific wheels (one per Go target) - # ----------------------------------------------------------------------- - build-wheels: - needs: [build-go, test-go] - runs-on: ubuntu-latest - strategy: - matrix: - include: - - goos: linux - goarch: amd64 - plat: manylinux_2_17_x86_64.manylinux2014_x86_64 - - goos: linux - goarch: arm64 - plat: manylinux_2_17_aarch64.manylinux2014_aarch64 - - goos: darwin - goarch: amd64 - plat: macosx_11_0_x86_64 - - goos: darwin - goarch: arm64 - plat: macosx_11_0_arm64 - - goos: windows - goarch: amd64 - plat: win_amd64 - - steps: - - uses: actions/checkout@v6 - - - name: Set up Python - uses: ./.github/actions/uv_setup - with: - python-version: ${{ env.PYTHON_VERSION }} - cache-suffix: "cli-wheel" - working-directory: ${{ env.WORKING_DIR }} - - - name: Download Go binary - uses: actions/download-artifact@v7 - with: - name: go-binary-${{ matrix.goos }}-${{ matrix.goarch }} - path: ${{ env.WORKING_DIR }}/ - - - name: Build platform wheel - working-directory: ${{ env.WORKING_DIR }} - env: - LANGGRAPH_GO_BINARY: langgraph-${{ matrix.goos }}-${{ matrix.goarch }}${{ matrix.goos == 'windows' && '.exe' || '' }} - LANGGRAPH_WHEEL_PLAT: ${{ matrix.plat }} - run: | - chmod +x "$LANGGRAPH_GO_BINARY" - uv build --wheel - echo "Built wheel:" - ls -lh dist/*.whl - - - name: Upload wheel - uses: actions/upload-artifact@v7 - with: - name: wheel-${{ matrix.goos }}-${{ matrix.goarch }} - path: ${{ env.WORKING_DIR }}/dist/*.whl - retention-days: 7 - - # ----------------------------------------------------------------------- - # Stage 4: Build pure-Python fallback wheel (no Go binary) - # ----------------------------------------------------------------------- - build-sdist: - needs: [test-go] - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v6 - - - name: Set up Python - uses: ./.github/actions/uv_setup - with: - python-version: ${{ env.PYTHON_VERSION }} - cache-suffix: "cli-sdist" - working-directory: ${{ env.WORKING_DIR }} - - - name: Build sdist and fallback wheel - working-directory: ${{ env.WORKING_DIR }} - run: | - uv build - echo "Built:" - ls -lh dist/ - - - name: Upload sdist and fallback wheel - uses: actions/upload-artifact@v7 - with: - name: sdist-and-fallback - path: ${{ env.WORKING_DIR }}/dist/ - retention-days: 7 - - # ----------------------------------------------------------------------- - # Stage 5: Validate wheels install correctly - # ----------------------------------------------------------------------- - validate-wheels: - needs: [build-wheels, build-sdist] - strategy: - matrix: - os: [ubuntu-latest, macos-latest, windows-latest] - python-version: ["3.11", "3.12"] - runs-on: ${{ matrix.os }} - steps: - - uses: actions/checkout@v6 - - - name: Set up Python - uses: actions/setup-python@v5 - with: - python-version: ${{ matrix.python-version }} - - - name: Download all wheels - uses: actions/download-artifact@v7 - with: - pattern: wheel-* - path: wheels/ - merge-multiple: true - - - name: Download fallback - uses: actions/download-artifact@v7 - with: - name: sdist-and-fallback - path: wheels/ - - - name: Install and test - shell: bash - run: | - pip install wheels/*.whl 2>/dev/null || pip install wheels/langgraph_cli-*.tar.gz - langgraph --help - langgraph --version - echo "Wheel installs and runs correctly on ${{ matrix.os }} / Python ${{ matrix.python-version }}" - - # ----------------------------------------------------------------------- - # Stage 6: Publish to PyPI (only when publish=true) - # ----------------------------------------------------------------------- - publish: - if: ${{ inputs.publish }} - needs: [validate-wheels] - runs-on: ubuntu-latest - environment: release - permissions: - id-token: write # required for trusted publishing - steps: - - name: Download platform wheels - uses: actions/download-artifact@v7 - with: - pattern: wheel-* - path: dist/ - merge-multiple: true - - - name: Download sdist - uses: actions/download-artifact@v7 - with: - name: sdist-and-fallback - path: dist/ - - - name: List all artifacts - run: ls -lh dist/ - - - name: Publish to PyPI - uses: pypa/gh-action-pypi-publish@release/v1 - with: - packages-dir: dist/ - skip-existing: true diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 96e982ae5..6046b16e9 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -13,6 +13,7 @@ permissions: env: PYTHON_VERSION: "3.11" + GO_VERSION: "1.23" jobs: build: @@ -34,6 +35,70 @@ jobs: cache-suffix: "release" working-directory: ${{ inputs.working-directory }} + # -- Go cross-compilation (libs/cli only) -- + # When releasing the CLI, we cross-compile the Go binary for each + # platform and build platform-specific wheels that bundle it. + - name: Set up Go + if: inputs.working-directory == 'libs/cli' + uses: actions/setup-go@v5 + with: + go-version: ${{ env.GO_VERSION }} + + - name: Cross-compile Go binaries + if: inputs.working-directory == 'libs/cli' + working-directory: ${{ inputs.working-directory }} + run: make build-go-all + + - name: Run Go tests + if: inputs.working-directory == 'libs/cli' + working-directory: ${{ inputs.working-directory }} + run: go test -count=1 -race ./... + + - name: Build CLI platform wheels + if: inputs.working-directory == 'libs/cli' + working-directory: ${{ inputs.working-directory }} + run: | + # Each entry: GO_BINARY_KEY WHEEL_PLATFORM_TAG + # + # Since Go builds are statically linked (CGO_ENABLED=0), the same + # linux binary works on both glibc and musl. We produce separate + # manylinux and musllinux wheels so pip installs the right one. + TARGETS=( + # Linux glibc + "linux-amd64 manylinux_2_17_x86_64.manylinux2014_x86_64" + "linux-arm64 manylinux_2_17_aarch64.manylinux2014_aarch64" + "linux-arm manylinux_2_17_armv7l.manylinux2014_armv7l" + "linux-386 manylinux_2_17_i686.manylinux2014_i686" + "linux-ppc64le manylinux_2_17_ppc64le.manylinux2014_ppc64le" + "linux-s390x manylinux_2_17_s390x.manylinux2014_s390x" + # Linux musl (same Go binaries, different wheel tag) + "linux-amd64 musllinux_1_2_x86_64" + "linux-arm64 musllinux_1_2_aarch64" + "linux-arm musllinux_1_2_armv7l" + "linux-386 musllinux_1_2_i686" + # macOS + "darwin-amd64 macosx_11_0_x86_64" + "darwin-arm64 macosx_11_0_arm64" + # Windows + "windows-amd64 win_amd64" + "windows-arm64 win_arm64" + "windows-386 win32" + ) + for entry in "${TARGETS[@]}"; do + key=$(echo "$entry" | awk '{print $1}') + plat=$(echo "$entry" | awk '{print $2}') + ext="" + if [[ "$key" == windows-* ]]; then ext=".exe"; fi + binary="langgraph_cli/bin/langgraph-${key}${ext}" + echo "Building wheel for ${key} -> ${plat}..." + LANGGRAPH_GO_BINARY="$binary" LANGGRAPH_WHEEL_PLAT="$plat" uv build --wheel + done + # Also build the sdist and pure-Python fallback wheel + uv build + echo "All wheels built:" + ls -lh dist/ + + # -- Standard build (all other packages) -- # We want to keep this build stage *separate* from the release stage, # so that there's no sharing of permissions between them. # The release stage has trusted publishing and GitHub repo contents write access, @@ -46,6 +111,7 @@ jobs: # > from the publish job. # https://github.com/pypa/gh-action-pypi-publish#non-goals - name: Build project for distribution + if: inputs.working-directory != 'libs/cli' run: uv build working-directory: ${{ inputs.working-directory }} diff --git a/libs/cli/Makefile b/libs/cli/Makefile index 97d2a105e..b18f20931 100644 --- a/libs/cli/Makefile +++ b/libs/cli/Makefile @@ -64,13 +64,22 @@ GO_LDFLAGS=-s -w \ -X '$(GO_VERSION_PKG).Commit=$(GO_COMMIT)' \ -X '$(GO_VERSION_PKG).Date=$(GO_DATE)' -# Platform matrix — matches orjson support as proxy for practical CLI targets. +# Platform matrix — covers every platform orjson ships for. Since the Go +# binary is statically linked (CGO_ENABLED=0), the same linux binary works +# on both glibc and musl. Musllinux wheels reuse the linux binary but are +# tagged differently so pip installs them on Alpine/musl systems. GO_PLATFORMS = \ linux/amd64 \ linux/arm64 \ + linux/arm \ + linux/386 \ + linux/ppc64le \ + linux/s390x \ darwin/amd64 \ darwin/arm64 \ - windows/amd64 + windows/amd64 \ + windows/arm64 \ + windows/386 # Build for the current platform (development). build-go: