From 7341963ca036f0de3209554d462dfc90693aae9b Mon Sep 17 00:00:00 2001 From: Rick Guo Date: Thu, 30 Jul 2026 23:08:33 +0800 Subject: [PATCH 1/4] ci: validate changed formula directories --- .github/scripts/find_changed_modules.gsh | 97 ++++++++++++++++++++++++ .github/workflows/test.yml | 37 +++------ 2 files changed, 108 insertions(+), 26 deletions(-) create mode 100644 .github/scripts/find_changed_modules.gsh diff --git a/.github/scripts/find_changed_modules.gsh b/.github/scripts/find_changed_modules.gsh new file mode 100644 index 0000000..e03bef7 --- /dev/null +++ b/.github/scripts/find_changed_modules.gsh @@ -0,0 +1,97 @@ +import ( + "encoding/json" + "fmt" + "os" + "path/filepath" + "sort" + "strings" +) + +func formulaDir(module, changedPath string) string { + dir := filepath.dir(changedPath) + for strings.hasPrefix(dir, module+"/") { + matches := filepath.glob(filepath.join(dir, "*_llar.gox"))! + if matches.len > 0 { + return dir + } + dir = filepath.dir(dir) + } + return "" +} + +baseSHA := $BASE_SHA +defaultBranch := $DEFAULT_BRANCH +eventName := $EVENT_NAME +headSHA := $HEAD_SHA +refName := $REF_NAME + +var diffBase string +if eventName == "pull_request" { + capout => { git "merge-base", baseSHA, headSHA } + lastErr! + diffBase = strings.trimSpace(output) +} else if refName == defaultBranch { + diffBase = baseSHA +} else { + capout => { git "merge-base", "origin/"+defaultBranch, headSHA } + lastErr! + diffBase = strings.trimSpace(output) +} + +capout => { + git "diff", "--name-only", "--diff-filter=ACMRT", "-z", diffBase, headSHA +} +lastErr! + +var modules map[string]bool = {} +var formulaDirs map[string]map[string]bool = {} +for changedPath in output.split("\x00") { + parts := strings.splitN(changedPath, "/", 3) + if parts.len < 3 { + continue + } + module := parts[0] + "/" + parts[1] + _, err := os.stat(filepath.join(module, "versions.json")) + if os.isNotExist(err) { + continue + } + if err != nil { + panic err + } + modules[module] = true + + dir := formulaDir(module, changedPath) + if dir != "" { + dirs := formulaDirs[module] + if dirs == nil { + dirs = {} + formulaDirs[module] = dirs + } + dirs[dir] = true + } +} + +var changedModules []string = [] +changedModules <- [module for module, _ in modules]... +sort.strings changedModules + +for module in changedModules { + dirs := formulaDirs[module] + if len(dirs) > 1 { + changedDirs := [dir for dir, _ in dirs] + sort.strings changedDirs + panic fmt.Sprintf("module %s changes multiple Formula directories: %s; llar test cannot validate multiple fromVer ranges yet", module, strings.join(changedDirs, ", ")) + } +} + +// TODO: When changed modules depend on each other, test only the leaf modules +// in their dependency graph. +modulesJSON := json.marshal(changedModules)! + +outputFile := os.openFile($GITHUB_OUTPUT, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)! +defer outputFile.close() +fmt.fprintf! outputFile, "modules=%s\nhas_modules=%t\n", modulesJSON, changedModules.len > 0 + +summaryFile := os.openFile($GITHUB_STEP_SUMMARY, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)! +defer summaryFile.close() +fmt.fprintf! summaryFile, "Changed modules: %s\n", modulesJSON diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index dad4618..7b1f1fc 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -25,38 +25,23 @@ jobs: with: fetch-depth: 0 + - name: Set up Go + uses: actions/setup-go@v7 + with: + go-version: 1.24.0 + cache: false + - name: Find changed modules id: modules - shell: bash env: BASE_SHA: ${{ github.event.pull_request.base.sha || github.event.before }} + DEFAULT_BRANCH: ${{ github.event.repository.default_branch }} + EVENT_NAME: ${{ github.event_name }} HEAD_SHA: ${{ github.event.pull_request.head.sha || github.event.after }} + REF_NAME: ${{ github.ref_name }} run: | - changed_modules=() - - changed_paths=(git diff --name-only --diff-filter=ACMRT -z "$BASE_SHA" "$HEAD_SHA") - if ! git cat-file -e "${BASE_SHA}^{commit}" 2>/dev/null; then - # A first branch push has no previous commit. For example, GitHub - # supplies an all-zero `before` SHA when creating a new branch. - changed_paths=(git ls-tree -r -z --name-only "$HEAD_SHA") - fi - - while IFS= read -r -d '' path; do - IFS=/ read -r owner repo _ <<< "$path" - module="$owner/$repo" - if [[ -f "$module/versions.json" ]]; then - changed_modules+=("$module") - fi - done < <("${changed_paths[@]}") - - modules_json="$({ printf '%s\n' "${changed_modules[@]}" | sort -u; } | jq -Rsc 'split("\n") | map(select(length > 0))')" - echo "modules=$modules_json" >> "$GITHUB_OUTPUT" - if [[ "$modules_json" == "[]" ]]; then - echo "has_modules=false" >> "$GITHUB_OUTPUT" - else - echo "has_modules=true" >> "$GITHUB_OUTPUT" - fi - echo "Changed modules: $modules_json" >> "$GITHUB_STEP_SUMMARY" + go install -tags=linknamefix -ldflags="-checklinkname=0" github.com/goplus/ixgo/cmd/ixgo@v1.1.1 + "$(go env GOPATH)/bin/ixgo" run .github/scripts/find_changed_modules.gsh test: name: ${{ matrix.module }} (${{ matrix.goos }}-${{ matrix.goarch }}) From be51a6640c567646188b902969bef804d154badf Mon Sep 17 00:00:00 2001 From: Rick Guo Date: Fri, 31 Jul 2026 10:27:04 +0800 Subject: [PATCH 2/4] ci: reject modules without versions metadata --- .github/scripts/find_changed_modules.gsh | 31 ++++++++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/.github/scripts/find_changed_modules.gsh b/.github/scripts/find_changed_modules.gsh index e03bef7..3d548a6 100644 --- a/.github/scripts/find_changed_modules.gsh +++ b/.github/scripts/find_changed_modules.gsh @@ -1,6 +1,7 @@ import ( "encoding/json" "fmt" + "io/fs" "os" "path/filepath" "sort" @@ -19,6 +20,21 @@ func formulaDir(module, changedPath string) string { return "" } +func hasFormula(module string) (bool, error) { + found := false + err := filepath.walkDir(module, func(_ string, entry fs.DirEntry, err error) error { + if err != nil { + return err + } + if !entry.isDir() && strings.hasSuffix(entry.name(), "_llar.gox") { + found = true + return fs.SkipAll + } + return nil + }) + return found, err +} + baseSHA := $BASE_SHA defaultBranch := $DEFAULT_BRANCH eventName := $EVENT_NAME @@ -39,7 +55,7 @@ if eventName == "pull_request" { } capout => { - git "diff", "--name-only", "--diff-filter=ACMRT", "-z", diffBase, headSHA + git "diff", "--name-only", "--diff-filter=ACDMRT", "-z", diffBase, headSHA } lastErr! @@ -51,8 +67,19 @@ for changedPath in output.split("\x00") { continue } module := parts[0] + "/" + parts[1] - _, err := os.stat(filepath.join(module, "versions.json")) + versionsPath := filepath.join(module, "versions.json") + _, err := os.stat(versionsPath) if os.isNotExist(err) { + has, walkErr := hasFormula(module) + if os.isNotExist(walkErr) { + continue + } + if walkErr != nil { + panic walkErr + } + if has { + panic fmt.Sprintf("module %s is missing versions.json", module) + } continue } if err != nil { From 17908809dba6d119ceaed750481d2b01bbde8b0f Mon Sep 17 00:00:00 2001 From: Rick Guo Date: Fri, 31 Jul 2026 11:20:23 +0800 Subject: [PATCH 3/4] ci: simplify formula discovery script --- .github/scripts/find_changed_modules.gsh | 53 +++++++++++++----------- 1 file changed, 28 insertions(+), 25 deletions(-) diff --git a/.github/scripts/find_changed_modules.gsh b/.github/scripts/find_changed_modules.gsh index 3d548a6..2865d09 100644 --- a/.github/scripts/find_changed_modules.gsh +++ b/.github/scripts/find_changed_modules.gsh @@ -5,12 +5,11 @@ import ( "os" "path/filepath" "sort" - "strings" ) func formulaDir(module, changedPath string) string { dir := filepath.dir(changedPath) - for strings.hasPrefix(dir, module+"/") { + for dir.hasPrefix(module + "/") { matches := filepath.glob(filepath.join(dir, "*_llar.gox"))! if matches.len > 0 { return dir @@ -22,19 +21,37 @@ func formulaDir(module, changedPath string) string { func hasFormula(module string) (bool, error) { found := false - err := filepath.walkDir(module, func(_ string, entry fs.DirEntry, err error) error { + err := filepath.walkDir(module, (_, entry, err) => { if err != nil { return err } - if !entry.isDir() && strings.hasSuffix(entry.name(), "_llar.gox") { + if !entry.isDir && entry.name.hasSuffix("_llar.gox") { found = true return fs.SkipAll } return nil }) + if os.isNotExist(err) { + return false, nil + } return found, err } +func isModule(module string) (bool, error) { + _, err := os.stat(filepath.join(module, "versions.json")) + if err == nil { + return true, nil + } + if !os.isNotExist(err) { + return false, err + } + has := hasFormula(module)? + if has { + return false, fmt.errorf("module %s is missing versions.json", module) + } + return false, nil +} + baseSHA := $BASE_SHA defaultBranch := $DEFAULT_BRANCH eventName := $EVENT_NAME @@ -45,13 +62,13 @@ var diffBase string if eventName == "pull_request" { capout => { git "merge-base", baseSHA, headSHA } lastErr! - diffBase = strings.trimSpace(output) + diffBase = output.trimSpace } else if refName == defaultBranch { diffBase = baseSHA } else { capout => { git "merge-base", "origin/"+defaultBranch, headSHA } lastErr! - diffBase = strings.trimSpace(output) + diffBase = output.trimSpace } capout => { @@ -62,29 +79,15 @@ lastErr! var modules map[string]bool = {} var formulaDirs map[string]map[string]bool = {} for changedPath in output.split("\x00") { - parts := strings.splitN(changedPath, "/", 3) + parts := changedPath.splitN("/", 3) if parts.len < 3 { continue } module := parts[0] + "/" + parts[1] - versionsPath := filepath.join(module, "versions.json") - _, err := os.stat(versionsPath) - if os.isNotExist(err) { - has, walkErr := hasFormula(module) - if os.isNotExist(walkErr) { - continue - } - if walkErr != nil { - panic walkErr - } - if has { - panic fmt.Sprintf("module %s is missing versions.json", module) - } + ok := isModule(module)! + if !ok { continue } - if err != nil { - panic err - } modules[module] = true dir := formulaDir(module, changedPath) @@ -98,7 +101,7 @@ for changedPath in output.split("\x00") { } } -var changedModules []string = [] +changedModules := []string([]) changedModules <- [module for module, _ in modules]... sort.strings changedModules @@ -107,7 +110,7 @@ for module in changedModules { if len(dirs) > 1 { changedDirs := [dir for dir, _ in dirs] sort.strings changedDirs - panic fmt.Sprintf("module %s changes multiple Formula directories: %s; llar test cannot validate multiple fromVer ranges yet", module, strings.join(changedDirs, ", ")) + panic fmt.Sprintf("module %s changes multiple Formula directories: %s; llar test cannot validate multiple fromVer ranges yet", module, changedDirs.join(", ")) } } From 440a0160aa5d6522a0416877ed1f81a5755b24d9 Mon Sep 17 00:00:00 2001 From: Rick Guo Date: Fri, 31 Jul 2026 11:47:22 +0800 Subject: [PATCH 4/4] ci: inline module validation --- .github/scripts/find_changed_modules.gsh | 26 ++++++++---------------- 1 file changed, 9 insertions(+), 17 deletions(-) diff --git a/.github/scripts/find_changed_modules.gsh b/.github/scripts/find_changed_modules.gsh index 2865d09..fe9762a 100644 --- a/.github/scripts/find_changed_modules.gsh +++ b/.github/scripts/find_changed_modules.gsh @@ -37,21 +37,6 @@ func hasFormula(module string) (bool, error) { return found, err } -func isModule(module string) (bool, error) { - _, err := os.stat(filepath.join(module, "versions.json")) - if err == nil { - return true, nil - } - if !os.isNotExist(err) { - return false, err - } - has := hasFormula(module)? - if has { - return false, fmt.errorf("module %s is missing versions.json", module) - } - return false, nil -} - baseSHA := $BASE_SHA defaultBranch := $DEFAULT_BRANCH eventName := $EVENT_NAME @@ -84,8 +69,15 @@ for changedPath in output.split("\x00") { continue } module := parts[0] + "/" + parts[1] - ok := isModule(module)! - if !ok { + _, err := os.stat(filepath.join(module, "versions.json")) + if err != nil { + if !os.isNotExist(err) { + panic err + } + has := hasFormula(module)! + if has { + panic fmt.Sprintf("module %s is missing versions.json", module) + } continue } modules[module] = true