diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index 8b05637c1..7118c9d19 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -19,33 +19,87 @@ concurrency: cancel-in-progress: true jobs: + discover: + runs-on: ubuntu-latest + outputs: + modules: ${{ steps.set-modules.outputs.modules }} + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - id: set-modules + run: | + MODULES=$(find . -not -path '*/\.git/*' -name go.mod -exec dirname {} \; | sort | jq -R -s -c 'split("\n")[:-1]') + echo "modules=$MODULES" >> $GITHUB_OUTPUT + test: + needs: discover runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + module: ${{ fromJson(needs.discover.outputs.modules) }} steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Setup uses: ./.github/actions/setup - - name: Tidy + - name: Initialize workspace + run: go work init && go work use -r . + + - name: Tidy Check + working-directory: ${{ matrix.module }} run: go mod tidy -diff - name: Build + working-directory: ${{ matrix.module }} run: go build -mod=readonly -v ./... - name: Test + working-directory: ${{ matrix.module }} run: go test -race -mod=readonly -v -count=1 -shuffle=on ./... lint: + needs: discover runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + module: ${{ fromJson(needs.discover.outputs.modules) }} steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Setup uses: ./.github/actions/setup + + - name: Initialize workspace + run: go work init && go work use -r . - name: Lint uses: golangci/golangci-lint-action@1e7e51e771db61008b38414a730f564565cf7c20 # v9.2.0 with: + working-directory: ${{ matrix.module }} install-mode: goinstall version: 5256574b81bcedfbcae9099f745f6aee9335da10 # v2.3.1 + + guardrail: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + + - name: Setup + uses: ./.github/actions/setup + + - name: Check root go.mod against internal modules + run: | + ROOT_REQS=$(go mod edit -json | jq -r '.Require // [] | .[] | .Path') + + FAIL=0 + while IFS= read -r -d '' dir; do + MOD_PATH=$(cd "$dir" && go mod edit -json | jq -r '.Module.Path') + if [ -n "$MOD_PATH" ] && echo "$ROOT_REQS" | grep -F -x -q "$MOD_PATH"; then + echo "ERROR: Root go.mod requires internal submodule $MOD_PATH" + FAIL=1 + fi + done < <(find . -mindepth 2 -not -path '*/\.git/*' -name go.mod -printf '%h\0') + + exit $FAIL diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index 78c641520..b814a567a 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -7,19 +7,42 @@ on: workflow_dispatch: jobs: + discover: + runs-on: ubuntu-latest + outputs: + modules: ${{ steps.set-modules.outputs.modules }} + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - id: set-modules + run: | + MODULES=$(find . -not -path '*/\.git/*' -name go.mod -exec dirname {} \; | sort | jq -R -s -c 'split("\n")[:-1]') + echo "modules=$MODULES" >> $GITHUB_OUTPUT + test: + needs: discover runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + module: ${{ fromJson(needs.discover.outputs.modules) }} steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Setup uses: ./.github/actions/setup + - name: Nightly Test + working-directory: ${{ matrix.module }} run: go test -race -mod=readonly -v -count=1 -shuffle=on ./... vulncheck: + needs: discover runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + module: ${{ fromJson(needs.discover.outputs.modules) }} steps: - name: Run govulncheck # NOTE: pinned to master HEAD instead of a tag. The latest release (v1.0.4) @@ -28,5 +51,6 @@ jobs: # TODO: re-pin to a tagged release once upstream tags one past v1.0.4. uses: golang/govulncheck-action@31f7c5463448f83528bd771c2d978d940080c9fd # master, post-v1.0.4 (Node 24) with: - go-version-file: go.mod + go-version-file: ${{ matrix.module }}/go.mod + work-dir: ${{ matrix.module }} repo-checkout: true \ No newline at end of file diff --git a/AGENTS.md b/AGENTS.md index df5830ff1..befcf02c6 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -23,6 +23,8 @@ Run from the repo root. These match what CI enforces (CI also passes `-v`): - Tidy check: `go mod tidy -diff` (must print nothing) - Format: `golangci-lint fmt` (applies gofumpt + goimports per config) +**Local Development**: Contributors should use `go work init && go work use -r .` to set up their local workspaces. + ## Definition of done A change is complete only when all of these pass locally: @@ -104,6 +106,10 @@ See `examples/quickstart` for a full runnable program. - **Add cross-cutting behavior:** register a `plugin.New(plugin.Config{...})` hook (`Before*`/`After*` for run/agent/model/tool) instead of editing the loop. +## Multi-Module Development + +See the [Multi-Module Development](CONTRIBUTING.md#multi-module-development) section in `CONTRIBUTING.md` for policy, steps to add a new module, and release tagging information. + ## Testing - Tests run **offline by default**: LLM HTTP traffic is replayed from diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index f979f22c1..0b26aae6e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -4,6 +4,7 @@ We'd love to accept your patches and contributions to this project. - [How to contribute](#how-to-contribute) - [Branches](#branches) +- [Multi-Module Development](#multi-module-development) - [Before you begin](#before-you-begin) - [Sign our Contributor License Agreement](#sign-our-contributor-license-agreement) - [Review our community guidelines](#review-our-community-guidelines) @@ -43,6 +44,31 @@ To work on a 1.x fix, base your branch on `v1`: git switch -c my-fix origin/v1 ``` +## Multi-Module Development + +**Policy**: New integrations with heavy or optional dependencies must be created as separate Go modules. + +**Local Development**: Contributors should use `go work init && go work use -r .` to set up their local workspaces. + +**Steps to Add a New Module (e.g., `plugin/myplugin`)**: +1. Navigate into the directory: `cd ` +2. Initialize the module: `go mod init google.golang.org/adk/` +3. Add your Go code, dependencies, and tests. +4. Tidy the module: `go mod tidy` +5. Return to the repo root. +6. Tidy the root module: `go mod tidy` +7. Add the module to your workspace: `go work use ./` +8. Verify everything builds and tests from the root: `go build work && go test work`. The CI will automatically pick up the new module on the PR. + +**Release Tagging**: +- **Core Module**: Tags remain `vX.Y.Z` (e.g., `v2.1.0`). +- **Submodules**: Tags are prefixed with the full module path directory, e.g., `plugin/agentanalytics/v0.1.0`. This is the standard Go way to version modules not at the repo root. +- **go get / go install**: Consumers will use: + - `go get google.golang.org/adk/v2@v2.1.0` + - `go get google.golang.org/adk/plugin/agentanalytics@v0.1.0` +- **Version Coupling**: Each submodule's `go.mod` will specify the minimum version of `google.golang.org/adk/v2` it depends on. Submodules can be released independently of the core module and each other. +- **go.work Impact**: `go.work` is for local development only and does not affect how modules are versioned, tagged, or fetched by consumers. + ## Before you begin ### Sign our Contributor License Agreement