From 4374b907e9f166da6bd23a8ef94399872b00afd6 Mon Sep 17 00:00:00 2001 From: Cody Oss <6331106+codyoss@users.noreply.github.com> Date: Thu, 5 Aug 2021 13:54:20 -0600 Subject: [PATCH] feat(.github): support dynamic submodule detection (#4537) Today the sub-modules are are coded into a couple of places to work with release-please. This change is the first of three to make release-please automatically support newly added modules. --- .github/workflows/release-submodule.yaml | 42 +----- internal/actions/cmd/changefinder/main.go | 152 ++++++++++++++++++++++ internal/actions/go.mod | 3 + 3 files changed, 160 insertions(+), 37 deletions(-) create mode 100644 internal/actions/cmd/changefinder/main.go create mode 100644 internal/actions/go.mod diff --git a/.github/workflows/release-submodule.yaml b/.github/workflows/release-submodule.yaml index 4e4f5d93d0c..f4ad84be4cc 100644 --- a/.github/workflows/release-submodule.yaml +++ b/.github/workflows/release-submodule.yaml @@ -19,44 +19,12 @@ jobs: submodules: ${{ steps.interrogate.outputs.submodules }} steps: - uses: actions/checkout@v2 - - id: interrogate - uses: actions/github-script@v4 + - name: Setup Go + uses: actions/setup-go@v2 with: - github-token: ${{secrets.GITHUB_TOKEN}} - script: | - const {execSync} = require('child_process'); - const SUB_MODULES = [ - 'bigtable', - 'bigquery', - 'datastore', - 'firestore', - 'logging', - 'pubsub', - 'pubsublite', - 'spanner', - 'storage', - ]; - const [owner, repo] = process.env.GITHUB_REPOSITORY.split('/'); - const latestRelease = await github.repos.getLatestRelease({ - owner, - repo - }); - console.info(`latest release: ${latestRelease.data.tag_name}`); - // pull all tags, so we can get diff between HEAD and last release: - execSync('git pull --tags'); - execSync(`git reset --hard ${latestRelease.data.tag_name}`); - const status = execSync(`git diff --name-only origin/master`, { encoding: 'utf-8'}); - console.info(status); - const changes = status.split('\n'); - const submodules = new Set(); - for (const change of changes) { - const library = change.split('/')[0]; - console.info(`update to path ${library}`); - if (SUB_MODULES.includes(library)) { - submodules.add(library); - } - } - console.log(`::set-output name=submodules::${JSON.stringify(Array.from(submodules))}`); + go-version: '^1.16' + - id: interrogate + run: go run internal/actions/cmd/changefinder/main.go release-pr: # Create the release PR based on commit history: runs-on: ubuntu-latest needs: changeFinder diff --git a/internal/actions/cmd/changefinder/main.go b/internal/actions/cmd/changefinder/main.go new file mode 100644 index 00000000000..61a232a1978 --- /dev/null +++ b/internal/actions/cmd/changefinder/main.go @@ -0,0 +1,152 @@ +// Copyright 2021 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package main + +import ( + "bytes" + "encoding/json" + "fmt" + "io/fs" + "log" + "os" + "os/exec" + "path/filepath" + "strings" +) + +func main() { + rootDir, err := os.Getwd() + if err != nil { + log.Fatal(err) + } + if len(os.Args) > 1 { + rootDir = os.Args[1] + } + log.Printf("Root dir: %q", rootDir) + var modDirs []string + // Find all external modules + filepath.WalkDir(rootDir, func(path string, d fs.DirEntry, err error) error { + if err != nil { + return err + } + if d.Name() == "internal" { + return filepath.SkipDir + } + if d.Name() == "go.mod" { + modDirs = append(modDirs, filepath.Dir(path)) + } + return nil + }) + + // Find relative sub-module + submodules := map[string]bool{} + for _, dir := range modDirs { + name, err := modName(dir) + if err != nil { + log.Fatalf("unable to lookup mod dir") + } + // Skip non-submodule + if name == "cloud.google.com/go" { + continue + } + name = strings.TrimPrefix(name, "cloud.google.com/go/") + submodules[name] = true + } + + c := exec.Command("git", "pull", "--tags") + c.Dir = rootDir + if err := c.Run(); err != nil { + log.Fatalf("unable to pull tags: %v", err) + } + + tag, err := latestTag(rootDir) + if err != nil { + log.Fatalf("unable to find tag: %v", err) + } + log.Printf("Latest release: %s", tag) + + c = exec.Command("git", "reset", "--hard", tag) + c.Dir = rootDir + if err := c.Run(); err != nil { + log.Fatalf("unable to reset to tag: %v", err) + } + + changes, err := gitFilesChanges(rootDir) + if err != nil { + log.Fatalf("unable to get files changed: %v", err) + } + + updatedSubmodulesSet := map[string]bool{} + for _, change := range changes { + //TODO(codyoss): This will not work with nested sub-modules. If we add + // those this needs to be updated. + pkg := strings.Split(change, "/")[0] + log.Printf("update to path: %s", pkg) + if submodules[pkg] { + updatedSubmodulesSet[pkg] = true + } + } + + updatedSubmodule := []string{} + for mod := range updatedSubmodulesSet { + updatedSubmodule = append(updatedSubmodule, mod) + } + b, err := json.Marshal(updatedSubmodule) + if err != nil { + log.Fatalf("unable to marshal submodules: %v", err) + } + fmt.Printf("::set-output name=submodules::%s", b) +} + +func modName(dir string) (string, error) { + c := exec.Command("go", "list", "-m") + c.Dir = dir + b, err := c.Output() + if err != nil { + return "", err + } + b = bytes.TrimSpace(b) + return string(b), nil +} + +func latestTag(dir string) (string, error) { + c := exec.Command("git", "rev-list", "--tags", "--max-count=1") + c.Dir = dir + b, err := c.Output() + if err != nil { + return "", err + } + commit := string(bytes.TrimSpace(b)) + c = exec.Command("git", "describe", "--tags", commit) + c.Dir = dir + b, err = c.Output() + if err != nil { + return "", err + } + b = bytes.TrimSpace(b) + return string(b), nil +} + +func gitFilesChanges(dir string) ([]string, error) { + c := exec.Command("git", "diff", "--name-only", "origin/master") + c.Dir = dir + b, err := c.Output() + if err != nil { + return nil, err + } + b = bytes.TrimSpace(b) + log.Printf("Files changed:\n%s", b) + return strings.Split(string(b), "\n"), nil +} diff --git a/internal/actions/go.mod b/internal/actions/go.mod new file mode 100644 index 00000000000..5d48ba600ef --- /dev/null +++ b/internal/actions/go.mod @@ -0,0 +1,3 @@ +module cloud.google.com/go/internal/actions + +go 1.16