diff --git a/.github/workflows/validate_changed_files.sh b/.github/workflows/validate_changed_files.sh deleted file mode 100755 index d6edd6fa6..000000000 --- a/.github/workflows/validate_changed_files.sh +++ /dev/null @@ -1,31 +0,0 @@ -#!/usr/bin/env bash - -set -o errexit -o nounset -o pipefail - -INVALID_FILE_PATHS=('pkg/plugin/sdk') # Array of filepaths that are not allowed -VALID_FILE_PATHS=('pkg/plugin/sdk/v1alpha3') # Array of filepaths that are allowed - -# by default the only local branch will be pull/PR#/merge -# fetch only the latest commit from the 2 branches in question to avoid fetching the entire repo which could be costly - -git fetch --depth 1 origin "${GITHUB_BASE_REF}" -git fetch --depth 1 origin "${GITHUB_REF}" - -git diff --name-only "origin/${GITHUB_BASE_REF}..${GITHUB_SHA}" | while read -r file; do - - # check if filepath matches a valid path. If so move to the next change - for valid_path in "${VALID_FILE_PATHS[@]}"; do - if [[ "${file}" == "${valid_path}"* ]]; then - continue 2 - fi - done - - # check if filepath matches an invalid path - for invalid_path in "${INVALID_FILE_PATHS[@]}"; do - if [[ "${file}" == "${invalid_path}"* ]]; then - echo "Branch contains changes to versioned SDK files." - echo "Add the 'allow sdk change' tag to the PR and re-trigger CI to bypass this check." - exit 1 - fi - done -done diff --git a/.github/workflows/validation.yml b/.github/workflows/validation.yml deleted file mode 100644 index 274c5522d..000000000 --- a/.github/workflows/validation.yml +++ /dev/null @@ -1,33 +0,0 @@ -name: Validation -on: - push: - branches: [main] - pull_request: - branches: [main] - # Allows you to run this workflow manually from the Actions tab. - workflow_dispatch: -concurrency: - # Cancel previous actions from the same PR or branch except 'main' branch. - # See https://docs.github.com/en/actions/using-jobs/using-concurrency and https://docs.github.com/en/actions/learn-github-actions/contexts for more info. - group: concurrency-group::${{ github.workflow }}::${{ github.event.pull_request.number > 0 && format('pr-{0}', github.event.pull_request.number) || github.ref_name }}${{ github.ref_name == 'main' && format('::{0}', github.run_id) || ''}} - cancel-in-progress: ${{ github.ref_name != 'main' }} -jobs: - validation: - name: Validation - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - name: Validate changed files - if: github.event_name == 'pull_request' && !contains(github.event.pull_request.labels.*.name, 'allow sdk change') - run: ./.github/workflows/validate_changed_files.sh - - uses: bazelbuild/setup-bazelisk@v1 - - name: Go cache - uses: actions/cache@v2 - with: - path: | - ~/.cache/go-build - ~/go/pkg/mod - key: ${{ runner.os }}-go-${{ hashFiles('**/go.mod', '**/go.sum') }} - restore-keys: ${{ runner.os }}-go- - - name: Tidy up repository - run: ./.github/workflows/tidy_up_repository.sh diff --git a/WORKSPACE b/WORKSPACE index cfd61d03e..cdc68022d 100644 --- a/WORKSPACE +++ b/WORKSPACE @@ -220,6 +220,7 @@ http_archive( "//:patches/bazelbuild_bazel-gazelle_aspect-cli.patch", "//:patches/bazelbuild_bazel-gazelle_aspect-walk-subdir.patch", "//:patches/bazelbuild_bazel-gazelle_aspect-gitignore.patch", + "//:patches/bazelbuild_bazel-gazelle_aspect-fs-direntry.patch", ], sha256 = "872f1532567cdc53dc8e9f4681cd45021cd6787e2bde8a022bcec24a5867ce4c", # Ensure this version always matches the go.mod version. diff --git a/cmd/aspect/root/root.go b/cmd/aspect/root/root.go index 2fb40be6d..a75274cc9 100644 --- a/cmd/aspect/root/root.go +++ b/cmd/aspect/root/root.go @@ -102,7 +102,7 @@ func HandleVersionFlags(streams ioutils.Streams, args []string, bzl bazel.Bazel) if err != nil { aspecterrors.HandleError(err) } - fmt.Fprintf(streams.Stdout, version) + fmt.Fprint(streams.Stdout, version) os.Exit(0) } } diff --git a/gazelle/common/git/gitignore.go b/gazelle/common/git/gitignore.go index eec2f1ddb..a5d2055fd 100644 --- a/gazelle/common/git/gitignore.go +++ b/gazelle/common/git/gitignore.go @@ -2,7 +2,9 @@ package git import ( "bufio" + "fmt" "io" + "io/fs" "os" "path" "strings" @@ -36,6 +38,11 @@ func collectIgnoreFiles(c *config.Config, rel string) { } c.Exts[lastConfiguredExt] = rel + ents := c.Exts[common.ASPECT_DIR_ENTRIES].(map[string]fs.DirEntry) + if _, hasIgnore := ents[".gitignore"]; !hasIgnore { + return + } + // Find and add .gitignore files from this directory ignoreFilePath := path.Join(c.RepoRoot, rel, ".gitignore") ignoreReader, ignoreErr := os.Open(ignoreFilePath) @@ -43,8 +50,10 @@ func collectIgnoreFiles(c *config.Config, rel string) { BazelLog.Tracef("Add ignore file %s/.gitignore", rel) defer ignoreReader.Close() addIgnore(c, rel, ignoreReader) - } else if !os.IsNotExist(ignoreErr) { - BazelLog.Errorf("Failed to open %s/.gitignore: %v", rel, ignoreErr) + } else { + msg := fmt.Sprintf("Failed to open %s/.gitignore: %v", rel, ignoreErr) + BazelLog.Error(msg) + fmt.Printf("%s\n", msg) } } diff --git a/gazelle/common/regex.go b/gazelle/common/regex.go index 585b1ecdd..4fc7b229f 100644 --- a/gazelle/common/regex.go +++ b/gazelle/common/regex.go @@ -9,18 +9,13 @@ import ( var regexCache = make(map[string]*regexp.Regexp) var regexMutex sync.Mutex -func ParseRegex(regexStr string) (*regexp.Regexp, error) { +func ParseRegex(regexStr string) *regexp.Regexp { regexMutex.Lock() defer regexMutex.Unlock() if regexCache[regexStr] == nil { - re, err := regexp.Compile(regexStr) - if err != nil { - return nil, err - } - - regexCache[regexStr] = re + regexCache[regexStr] = regexp.MustCompile(regexStr) } - return regexCache[regexStr], nil + return regexCache[regexStr] } diff --git a/gazelle/common/set.go b/gazelle/common/set.go index c86e9af4c..616c4ac8c 100644 --- a/gazelle/common/set.go +++ b/gazelle/common/set.go @@ -14,7 +14,26 @@ type LabelSet struct { } func LabelComparator(a, b interface{}) int { - return utils.StringComparator(a.(label.Label).String(), b.(label.Label).String()) + al := a.(label.Label) + bl := b.(label.Label) + + if al.Relative && !bl.Relative { + return -1 + } else if !al.Relative && bl.Relative { + return +1 + } + + c := utils.StringComparator(al.Repo, bl.Repo) + if c != 0 { + return c + } + + c = utils.StringComparator(al.Pkg, bl.Pkg) + if c != 0 { + return c + } + + return utils.StringComparator(al.Name, bl.Name) } func NewLabelSet(from label.Label) *LabelSet { diff --git a/gazelle/common/treesitter/BUILD.bazel b/gazelle/common/treesitter/BUILD.bazel index e536e3241..f1ba9d7c8 100644 --- a/gazelle/common/treesitter/BUILD.bazel +++ b/gazelle/common/treesitter/BUILD.bazel @@ -6,11 +6,13 @@ go_library( "filters.go", "parser.go", "queries.go", + "query.go", "traversal.go", ], importpath = "aspect.build/cli/gazelle/common/treesitter", visibility = ["//visibility:public"], deps = [ + "//gazelle/common", "//gazelle/common/treesitter/grammars/json", "//gazelle/common/treesitter/grammars/kotlin", "//gazelle/common/treesitter/grammars/starlark", diff --git a/gazelle/common/treesitter/filters.go b/gazelle/common/treesitter/filters.go index e94376013..bfe52eb82 100644 --- a/gazelle/common/treesitter/filters.go +++ b/gazelle/common/treesitter/filters.go @@ -1,8 +1,7 @@ package treesitter import ( - "regexp" - + common "aspect.build/cli/gazelle/common" sitter "github.com/smacker/go-tree-sitter" ) @@ -17,7 +16,7 @@ import ( // Predicates implemented here: // - eq? // - match? -func matchesAllPredicates(q *sitter.Query, m *sitter.QueryMatch, qc *sitter.QueryCursor, input []byte) bool { +func matchesAllPredicates(q *sitterQuery, m *sitter.QueryMatch, qc *sitter.QueryCursor, input []byte) bool { qm := &sitter.QueryMatch{ ID: m.ID, PatternIndex: m.PatternIndex, @@ -79,7 +78,7 @@ func matchesAllPredicates(q *sitter.Query, m *sitter.QueryMatch, qc *sitter.Quer isPositive := operator == "match?" expectedCaptureName := q.CaptureNameForId(steps[1].ValueId) - regex := regexp.MustCompile(q.StringValueForId(steps[2].ValueId)) + regex := common.ParseRegex(q.StringValueForId(steps[2].ValueId)) for _, c := range m.Captures { captureName := q.CaptureNameForId(c.Index) @@ -87,7 +86,7 @@ func matchesAllPredicates(q *sitter.Query, m *sitter.QueryMatch, qc *sitter.Quer continue } - if regex.Match([]byte(c.Node.Content(input))) != isPositive { + if regex.MatchString(c.Node.Content(input)) != isPositive { return false } } diff --git a/gazelle/common/treesitter/queries.go b/gazelle/common/treesitter/queries.go index f47f6c620..f5824d7cd 100644 --- a/gazelle/common/treesitter/queries.go +++ b/gazelle/common/treesitter/queries.go @@ -13,15 +13,15 @@ import ( var ErrorsQuery = `(ERROR) @error` // A cache of parsed queries per language -var queryCache = make(map[LanguageGrammar]map[string]*sitter.Query) +var queryCache = make(map[LanguageGrammar]map[string]*sitterQuery) var queryMutex sync.Mutex -func parseQuery(lang LanguageGrammar, queryStr string) *sitter.Query { +func parseQuery(lang LanguageGrammar, queryStr string) *sitterQuery { queryMutex.Lock() defer queryMutex.Unlock() if queryCache[lang] == nil { - queryCache[lang] = make(map[string]*sitter.Query) + queryCache[lang] = make(map[string]*sitterQuery) } if queryCache[lang][queryStr] == nil { queryCache[lang][queryStr] = mustNewQuery(lang, queryStr) @@ -39,7 +39,7 @@ func (tree TreeAst) QueryStrings(query, returnVar string) []string { // Execute the query. qc := sitter.NewQueryCursor() - qc.Exec(sitterQuery, rootNode) + qc.Exec(sitterQuery.q, rootNode) // Collect string from the query results. for { @@ -82,7 +82,7 @@ func (tree TreeAst) Query(query string) <-chan ASTQueryResult { // Execute the query. go func() { qc := sitter.NewQueryCursor() - qc.Exec(q, rootNode) + qc.Exec(q.q, rootNode) for { m, ok := qc.NextMatch() @@ -104,7 +104,7 @@ func (tree TreeAst) Query(query string) <-chan ASTQueryResult { return out } -func (tree TreeAst) mapQueryMatchCaptures(m *sitter.QueryMatch, q *sitter.Query) map[string]string { +func (tree TreeAst) mapQueryMatchCaptures(m *sitter.QueryMatch, q *sitterQuery) map[string]string { captures := make(map[string]string, len(m.Captures)) for _, c := range m.Captures { name := q.CaptureNameForId(c.Index) @@ -120,11 +120,11 @@ func (tree TreeAst) mapQueryMatchCaptures(m *sitter.QueryMatch, q *sitter.Query) // Find and read the `from` QueryCapture from a QueryMatch. // Filter matches based on captures value using "equals-{name}" vars. -func fetchQueryMatch(query *sitter.Query, name string, m *sitter.QueryMatch, sourceCode []byte) *sitter.QueryCapture { +func fetchQueryMatch(query *sitterQuery, name string, m *sitter.QueryMatch, sourceCode []byte) *sitter.QueryCapture { var result *sitter.QueryCapture - for ci, c := range m.Captures { - cn := query.CaptureNameForId(uint32(ci)) + for _, c := range m.Captures { + cn := query.CaptureNameForId(c.Index) // Filters where a capture must equal a specific value. if strings.HasPrefix(cn, "equals-") { @@ -145,10 +145,10 @@ func fetchQueryMatch(query *sitter.Query, name string, m *sitter.QueryMatch, sou return result } -func mustNewQuery(lang LanguageGrammar, queryStr string) *sitter.Query { - treeQ, err := sitter.NewQuery([]byte(queryStr), toSitterLanguage(lang)) +func mustNewTreeQuery(lang LanguageGrammar, query string) *sitter.Query { + treeQ, err := sitter.NewQuery([]byte(query), toSitterLanguage(lang)) if err != nil { - BazelLog.Fatalf("Failed to create query for %q: %v", queryStr, err) + BazelLog.Fatalf("Failed to create query for %q: %v", query, err) } return treeQ } @@ -166,7 +166,7 @@ func (tree TreeAst) QueryErrors() []error { // Execute the import query qc := sitter.NewQueryCursor() - qc.Exec(query, node) + qc.Exec(query.q, node) // Collect import statements from the query results for { @@ -201,7 +201,7 @@ func (tree TreeAst) QueryErrors() []error { msg := pre + line arw := strings.Repeat(" ", len(pre)+colI) + "^" - errors = append(errors, fmt.Errorf(msg+"\n"+arw)) + errors = append(errors, fmt.Errorf("%s\n%s", msg, arw)) } } diff --git a/gazelle/common/treesitter/query.go b/gazelle/common/treesitter/query.go new file mode 100644 index 000000000..fadb4fe10 --- /dev/null +++ b/gazelle/common/treesitter/query.go @@ -0,0 +1,53 @@ +package treesitter + +import sitter "github.com/smacker/go-tree-sitter" + +// Basic wrapper around sitter.Query to cache tree-sitter cgo calls. +type sitterQuery struct { + q *sitter.Query + + // Pre-computed and cached query data + stringValues []string + captureNames []string + predicatePatterns [][][]sitter.QueryPredicateStep +} + +func mustNewQuery(lang LanguageGrammar, query string) *sitterQuery { + q := mustNewTreeQuery(lang, query) + + captureNames := make([]string, q.CaptureCount()) + for i := uint32(0); i < q.CaptureCount(); i++ { + captureNames[i] = q.CaptureNameForId(i) + } + + stringValues := make([]string, q.StringCount()) + for i := uint32(0); i < q.StringCount(); i++ { + stringValues[i] = q.StringValueForId(i) + } + + predicatePatterns := make([][][]sitter.QueryPredicateStep, q.PatternCount()) + for i := uint32(0); i < q.PatternCount(); i++ { + predicatePatterns[i] = q.PredicatesForPattern(i) + } + + return &sitterQuery{ + q: q, + stringValues: stringValues, + captureNames: captureNames, + predicatePatterns: predicatePatterns, + } +} + +// Cached query data accessors mirroring the tree-sitter Query signatures. + +func (q *sitterQuery) StringValueForId(id uint32) string { + return q.stringValues[id] +} + +func (q *sitterQuery) CaptureNameForId(id uint32) string { + return q.captureNames[id] +} + +func (q *sitterQuery) PredicatesForPattern(patternIndex uint32) [][]sitter.QueryPredicateStep { + return q.predicatePatterns[patternIndex] +} diff --git a/gazelle/common/walk.go b/gazelle/common/walk.go index 98205633e..ff5a51045 100644 --- a/gazelle/common/walk.go +++ b/gazelle/common/walk.go @@ -14,6 +14,7 @@ type GazelleWalkFunc func(path string) error // Must align with patched bazel-gazelle const ASPECT_WALKSUBDIR = "__aspect:walksubdir" +const ASPECT_DIR_ENTRIES = "__aspect:direntries" // Read any configuration regarding walk options. func ReadWalkConfig(c *config.Config, rel string, f *rule.File) bool { diff --git a/gazelle/js/config.go b/gazelle/js/config.go index bef33babc..8b3a5b7b8 100644 --- a/gazelle/js/config.go +++ b/gazelle/js/config.go @@ -305,7 +305,7 @@ func (c *JsGazelleConfig) GetNpmPackageGenerationMode() NpmPackageMode { // Set the pnpm-workspace.yaml file path. func (c *JsGazelleConfig) SetPnpmLockfile(pnpmLockPath string) { - c.pnpmLockPath = pnpmLockPath + c.pnpmLockPath = path.Clean(pnpmLockPath) } func (c *JsGazelleConfig) PnpmLockfile() string { return c.pnpmLockPath diff --git a/gazelle/js/configure.go b/gazelle/js/configure.go index 30adda66c..f6b7b093b 100644 --- a/gazelle/js/configure.go +++ b/gazelle/js/configure.go @@ -3,6 +3,7 @@ package gazelle import ( "flag" "fmt" + "io/fs" "log" "os" "path" @@ -89,11 +90,10 @@ func (ts *typeScriptLang) Configure(c *config.Config, rel string, f *rule.File) if f != nil { ts.readDirectives(c, rel, f) - - // Read configurations relative to the current BUILD file. - ts.readConfigurations(c, rel) } + ts.readConfigurations(c, rel) + common.ReadWalkConfig(c, rel, f) git.ReadGitConfig(c, rel, f) @@ -102,16 +102,29 @@ func (ts *typeScriptLang) Configure(c *config.Config, rel string, f *rule.File) func (ts *typeScriptLang) readConfigurations(c *config.Config, rel string) { config := c.Exts[LanguageName].(*JsGazelleConfig) + ents := c.Exts[common.ASPECT_DIR_ENTRIES].(map[string]fs.DirEntry) + // pnpm - lockfilePath := path.Join(c.RepoRoot, rel, config.PnpmLockfile()) - if _, err := os.Stat(lockfilePath); err == nil { - ts.addPnpmLockfile(config, c.RepoName, c.RepoRoot, path.Join(rel, config.PnpmLockfile())) + pnpmLockPath := config.PnpmLockfile() + pnpmLockDir := path.Dir(pnpmLockPath) + if pnpmLockDir == "." { + // Common case of the lockfile not being within a subdirectory. + // Can use the fs.DirEntry list of this directory to check if the lockfile exists. + if ents[pnpmLockPath] != nil { + ts.addPnpmLockfile(config, c.RepoName, c.RepoRoot, path.Join(rel, pnpmLockPath)) + } + } else if rootDirEntry := strings.Split(pnpmLockDir, "/")[0]; ents[rootDirEntry] != nil { + // If the lockfile is in a subdirectory then check the fs.DirEntry of the subdirectory. + // If the subdirectory exists then perform the expensive os.Stat to check if the file exists. + lockfilePath := path.Join(c.RepoRoot, rel, pnpmLockPath) + if _, err := os.Stat(lockfilePath); err == nil { + ts.addPnpmLockfile(config, c.RepoName, c.RepoRoot, path.Join(rel, pnpmLockPath)) + } } // tsconfig // TODO: add support for alternate tsconfig names - configPath := path.Join(c.RepoRoot, rel, config.defaultTsconfigName) - if _, err := os.Stat(configPath); err == nil { + if _, hasTsconfig := ents[config.defaultTsconfigName]; hasTsconfig { ts.tsconfig.AddTsConfigFile(c.RepoRoot, rel, config.defaultTsconfigName) } } diff --git a/gazelle/js/generate.go b/gazelle/js/generate.go index e091ac585..d76807931 100644 --- a/gazelle/js/generate.go +++ b/gazelle/js/generate.go @@ -399,11 +399,12 @@ func (ts *typeScriptLang) addProjectRule(cfg *JsGazelleConfig, tsconfigRel strin // Check for name-collisions with the rule being generated. colError := gazelle.CheckCollisionErrors(targetName, TsProjectKind, sourceRuleKinds, args) if colError != nil { - return nil, fmt.Errorf(colError.Error()+" "+ + return nil, fmt.Errorf("%v "+ "Use the '# aspect:%s' directive to change the naming convention.\n\n"+ "For example:\n"+ "\t# aspect:%s {dirname}_js\n"+ "\t# aspect:%s {dirname}_js_tests", + colError.Error(), Directive_LibraryNamingConvention, Directive_LibraryNamingConvention, Directive_TestsNamingConvention, @@ -540,6 +541,13 @@ func (ts *typeScriptLang) addProjectRule(cfg *JsGazelleConfig, tsconfigRel strin existing.DelAttr("resolve_json_module") } + // Reflect the tsconfig resolve_json_module in the ts_project rule + if tsconfig.Jsx != typescript.JsxNone { + sourceRule.SetAttr("preserve_jsx", tsconfig.Jsx == typescript.JsxPreserve) + } else if existing != nil { + existing.DelAttr("preserve_jsx") + } + // Reflect the tsconfig out_dir in the ts_project rule if tsconfig.OutDir != "" && tsconfig.OutDir != "." { sourceRule.SetAttr("out_dir", tsconfig.OutDir) @@ -560,6 +568,7 @@ func (ts *typeScriptLang) addProjectRule(cfg *JsGazelleConfig, tsconfigRel strin existing.DelAttr("declaration") existing.DelAttr("declaration_map") existing.DelAttr("out_dir") + existing.DelAttr("preserve_jsx") existing.DelAttr("resolve_json_module") existing.DelAttr("source_map") existing.DelAttr("root_dir") @@ -695,7 +704,7 @@ func parseSourceFile(rootDir, filePath string) (parser.ParseResult, []error) { return parser.ParseResult{}, []error{err} } - return parser.ParseSource(filePath, string(content)) + return parser.ParseSource(filePath, content) } func (ts *typeScriptLang) addFileLabel(importPath string, label *label.Label) { diff --git a/gazelle/js/parser/parser.go b/gazelle/js/parser/parser.go index 5874c363c..2b73cfce2 100644 --- a/gazelle/js/parser/parser.go +++ b/gazelle/js/parser/parser.go @@ -55,12 +55,11 @@ var importQueries = map[string]string{ var tripleSlashRe = regexp.MustCompile(`^///\s*[^"]+)"`) -func ParseSource(filePath, sourceCodeStr string) (ParseResult, []error) { +func ParseSource(filePath string, sourceCode []byte) (ParseResult, []error) { imports := make([]string, 0, 5) modules := make([]string, 0) errs := make([]error, 0) - sourceCode := []byte(sourceCodeStr) lang := filenameToLanguage(filePath) // Parse the source code @@ -109,9 +108,9 @@ func ParseSource(filePath, sourceCodeStr string) (ParseResult, []error) { } } } - } else if isTripleSlashDirective(node, sourceCode) { - typesImport := getTripleSlashDirectiveModule(node, sourceCode) - if typesImport != "" { + } else if isPotentialTripleSlashDirective(node, sourceCode) { + typesImport, isTripleSlash := getTripleSlashDirectiveModule(node, sourceCode) + if isTripleSlash { imports = append(imports, typesImport) } } @@ -146,9 +145,10 @@ func ParseSource(filePath, sourceCodeStr string) (ParseResult, []error) { return result, errs } -// Determine if a node is a triple slash directive. +// Determine if a node is potentially a triple-slash directive. +// To be used before running a more expensive check and triple-slash directive extraction. // See: https://www.typescriptlang.org/docs/handbook/triple-slash-directives.html -func isTripleSlashDirective(node *sitter.Node, sourceCode []byte) bool { +func isPotentialTripleSlashDirective(node *sitter.Node, sourceCode []byte) bool { nodeType := node.Type() if nodeType != "comment" { return false @@ -162,16 +162,15 @@ func isTripleSlashDirective(node *sitter.Node, sourceCode []byte) bool { // Extract a /// from a comment node // Note: could also potentially use a treesitter query such as: // / `(program (comment) @result (#match? @c "^///\\s* 0 } // Determine if a node is an import/export statement that may contain a `from` value. diff --git a/gazelle/js/parser/parser_test.go b/gazelle/js/parser/parser_test.go index 8455c1d3f..698106456 100644 --- a/gazelle/js/parser/parser_test.go +++ b/gazelle/js/parser/parser_test.go @@ -328,7 +328,7 @@ func equal[T comparable](a, b []T) bool { func TestTreesitterParser(t *testing.T) { for _, tc := range testCases { t.Run(tc.desc, func(t *testing.T) { - res, _ := ParseSource(tc.filename, tc.ts) + res, _ := ParseSource(tc.filename, []byte(tc.ts)) if !equal(res.Imports, tc.expectedImports) { t.Errorf("Unexpected import results\nactual: %#v;\nexpected: %#v\ntypescript code:\n%v", res.Imports, tc.expectedImports, tc.ts) diff --git a/gazelle/js/pnpm/parser.go b/gazelle/js/pnpm/parser.go index 2fca2a54e..13784be42 100644 --- a/gazelle/js/pnpm/parser.go +++ b/gazelle/js/pnpm/parser.go @@ -29,13 +29,13 @@ func ParsePnpmLockFileDependencies(lockfilePath string) WorkspacePackageVersionM var lockVersionRegex = regexp.MustCompile(`^\s*lockfileVersion: '?(?P\d\.\d)'?`) func parsePnpmLockVersion(yamlFileContent []byte) (string, error) { - match := lockVersionRegex.FindStringSubmatch(string(yamlFileContent)) + match := lockVersionRegex.FindSubmatch(yamlFileContent) if len(match) != 2 { return "", fmt.Errorf("failed to find lockfile version in: %q", string(yamlFileContent)) } - return match[1], nil + return string(match[1]), nil } func parsePnpmLockDependencies(yamlFileContent []byte) (WorkspacePackageVersionMap, error) { diff --git a/gazelle/js/resolve.go b/gazelle/js/resolve.go index 528bbe551..752997355 100644 --- a/gazelle/js/resolve.go +++ b/gazelle/js/resolve.go @@ -228,14 +228,17 @@ func (ts *typeScriptLang) Resolve( // Support this target representing a project or a package var imports *treeset.Set - if packageInfo, isProjectInfo := importData.(*TsPackageInfo); isProjectInfo { + if packageInfo, isPackageInfo := importData.(*TsPackageInfo); isPackageInfo { imports = packageInfo.imports if packageInfo.source != nil { deps.Add(packageInfo.source) } + } else if projectInfo, isProjectInfo := importData.(*TsProjectInfo); isProjectInfo { + imports = projectInfo.imports } else { - imports = importData.(*TsProjectInfo).imports + BazelLog.Infof("%s //%s:%s with no/unknown package info", r.Kind(), from.Pkg, r.Name()) + break } err := ts.resolveImports(c, ix, deps, imports, from) @@ -252,7 +255,12 @@ func (ts *typeScriptLang) Resolve( r.SetAttr("deps", deps.Labels()) } case NpmPackageKind: - packageInfo := importData.(*TsPackageInfo) + packageInfo, isPackageInfo := importData.(*TsPackageInfo) + if !isPackageInfo { + BazelLog.Infof("%s //%s:%s with no/unknown package info", r.Kind(), from.Pkg, r.Name()) + break + } + srcs := packageInfo.sources.Values() deps := common.NewLabelSet(from) diff --git a/gazelle/js/tests/tsconfig_jsx/preserve/BUILD.out b/gazelle/js/tests/tsconfig_jsx/preserve/BUILD.out index a88539be9..78ca6e3e1 100644 --- a/gazelle/js/tests/tsconfig_jsx/preserve/BUILD.out +++ b/gazelle/js/tests/tsconfig_jsx/preserve/BUILD.out @@ -3,6 +3,7 @@ load("@aspect_rules_ts//ts:defs.bzl", "ts_config", "ts_project") ts_project( name = "preserve", srcs = ["lib.tsx"], + preserve_jsx = True, tsconfig = ":tsconfig", ) diff --git a/gazelle/js/tests/tsconfig_jsx/react-jsx/BUILD.out b/gazelle/js/tests/tsconfig_jsx/react-jsx/BUILD.out index 9d0df53ed..6eb5dc4c9 100644 --- a/gazelle/js/tests/tsconfig_jsx/react-jsx/BUILD.out +++ b/gazelle/js/tests/tsconfig_jsx/react-jsx/BUILD.out @@ -3,6 +3,7 @@ load("@aspect_rules_ts//ts:defs.bzl", "ts_config", "ts_project") ts_project( name = "react-jsx", srcs = ["rjsx.tsx"], + preserve_jsx = False, tsconfig = ":tsconfig", deps = ["//:node_modules/react"], ) diff --git a/gazelle/js/tests/tsconfig_jsx/react/BUILD.out b/gazelle/js/tests/tsconfig_jsx/react/BUILD.out index c54763a25..5f5c0a0b8 100644 --- a/gazelle/js/tests/tsconfig_jsx/react/BUILD.out +++ b/gazelle/js/tests/tsconfig_jsx/react/BUILD.out @@ -3,6 +3,7 @@ load("@aspect_rules_ts//ts:defs.bzl", "ts_config", "ts_project") ts_project( name = "react", srcs = ["r.tsx"], + preserve_jsx = False, tsconfig = ":tsconfig", deps = ["//:node_modules/react"], ) diff --git a/gazelle/js/tests/tsconfig_jsx/react/no-jsx/BUILD.out b/gazelle/js/tests/tsconfig_jsx/react/no-jsx/BUILD.out index 4c22af78f..65b56f56e 100644 --- a/gazelle/js/tests/tsconfig_jsx/react/no-jsx/BUILD.out +++ b/gazelle/js/tests/tsconfig_jsx/react/no-jsx/BUILD.out @@ -3,5 +3,6 @@ load("@aspect_rules_ts//ts:defs.bzl", "ts_project") ts_project( name = "no-jsx", srcs = ["s.ts"], + preserve_jsx = False, tsconfig = "//react:tsconfig", ) diff --git a/gazelle/js/tests/tsconfig_jsx/react/sub/BUILD.out b/gazelle/js/tests/tsconfig_jsx/react/sub/BUILD.out index 30890a4ae..b9e9b666f 100644 --- a/gazelle/js/tests/tsconfig_jsx/react/sub/BUILD.out +++ b/gazelle/js/tests/tsconfig_jsx/react/sub/BUILD.out @@ -3,6 +3,7 @@ load("@aspect_rules_ts//ts:defs.bzl", "ts_project") ts_project( name = "sub", srcs = ["s.tsx"], + preserve_jsx = False, tsconfig = "//react:tsconfig", deps = ["//:node_modules/react"], ) diff --git a/gazelle/js/typescript/config.go b/gazelle/js/typescript/config.go index 8ec392b55..2ed27e095 100644 --- a/gazelle/js/typescript/config.go +++ b/gazelle/js/typescript/config.go @@ -7,6 +7,7 @@ import ( node "aspect.build/cli/gazelle/js/node" pnpm "aspect.build/cli/gazelle/js/pnpm" + BazelLog "aspect.build/cli/pkg/logger" ) type workspacePath struct { @@ -46,6 +47,8 @@ func (tc *TsWorkspace) AddTsConfigFile(root, rel, fileName string) { return } + BazelLog.Debugf("Adding tsconfig file %s/%s", rel, fileName) + tc.cm.configFiles[rel] = &workspacePath{ root: root, rel: rel, @@ -78,13 +81,19 @@ func (tc *TsWorkspace) GetTsConfigFile(rel string) *TsConfig { return nil } + BazelLog.Debugf("Parsed tsconfig file %s/%s", p.rel, p.fileName) + return c } // A `TsConfigResolver` to resolve imports from *within* tsconfig files // to real paths such as resolved the tsconfig `extends`. func (tc *TsWorkspace) tsConfigResolver(dir, rel string) []string { - possible := []string{path.Join(dir, rel)} + possible := []string{} + + if isRelativePath(rel) { + possible = append(possible, path.Join(dir, rel)) + } if p := tc.cm.pnpmProjects.GetProject(dir); p != nil { pkg, subFile := node.ParseImportPath(rel) diff --git a/gazelle/js/typescript/tsconfig.go b/gazelle/js/typescript/tsconfig.go index 7d4cd0bf5..e07bf68af 100644 --- a/gazelle/js/typescript/tsconfig.go +++ b/gazelle/js/typescript/tsconfig.go @@ -149,9 +149,6 @@ func parseTsConfigJSONFile(parsed map[string]*TsConfig, resolver TsConfigResolve content, err := os.ReadFile(path.Join(root, tsconfig)) if err != nil { - if os.IsNotExist(err) { - err = nil - } return nil, err } diff --git a/gazelle/kotlin/generate.go b/gazelle/kotlin/generate.go index b05a5e6ad..5e8b45b85 100644 --- a/gazelle/kotlin/generate.go +++ b/gazelle/kotlin/generate.go @@ -210,7 +210,7 @@ func parseFile(rootDir, filePath string) (*parser.ParseResult, []error) { } p := parser.NewParser() - return p.Parse(filePath, string(content)) + return p.Parse(filePath, content) } func (kt *kotlinLang) collectSourceFiles(cfg *kotlinconfig.KotlinConfig, args language.GenerateArgs) *treeset.Set { diff --git a/gazelle/kotlin/parser/parser.go b/gazelle/kotlin/parser/parser.go index 98721158a..9ba0797a6 100644 --- a/gazelle/kotlin/parser/parser.go +++ b/gazelle/kotlin/parser/parser.go @@ -18,7 +18,7 @@ type ParseResult struct { } type Parser interface { - Parse(filePath, source string) (*ParseResult, []error) + Parse(filePath string, source []byte) (*ParseResult, []error) } type treeSitterParser struct { @@ -31,7 +31,7 @@ func NewParser() Parser { return &p } -func (p *treeSitterParser) Parse(filePath, source string) (*ParseResult, []error) { +func (p *treeSitterParser) Parse(filePath string, sourceCode []byte) (*ParseResult, []error) { var result = &ParseResult{ File: filePath, Imports: make([]string, 0), @@ -39,8 +39,6 @@ func (p *treeSitterParser) Parse(filePath, source string) (*ParseResult, []error errs := make([]error, 0) - sourceCode := []byte(source) - tree, err := treeutils.ParseSourceCode(treeutils.Kotlin, filePath, sourceCode) if err != nil { errs = append(errs, err) diff --git a/gazelle/kotlin/parser/parser_test.go b/gazelle/kotlin/parser/parser_test.go index 63a6f86d1..53f11ba5d 100644 --- a/gazelle/kotlin/parser/parser_test.go +++ b/gazelle/kotlin/parser/parser_test.go @@ -69,7 +69,7 @@ func TestTreesitterParser(t *testing.T) { for _, tc := range testCases { t.Run(tc.desc, func(t *testing.T) { - res, _ := NewParser().Parse(tc.filename, tc.kt) + res, _ := NewParser().Parse(tc.filename, []byte(tc.kt)) if !equal(res.Imports, tc.imports) { t.Errorf("Imports...\nactual: %#v;\nexpected: %#v\nkotlin code:\n%v", res.Imports, tc.imports, tc.kt) @@ -82,24 +82,24 @@ func TestTreesitterParser(t *testing.T) { } t.Run("main detection", func(t *testing.T) { - res, _ := NewParser().Parse("main.kt", "fun main() {}") + res, _ := NewParser().Parse("main.kt", []byte("fun main() {}")) if !res.HasMain { t.Errorf("main method should be detected") } - res, _ = NewParser().Parse("x.kt", ` + res, _ = NewParser().Parse("x.kt", []byte(` package my.demo fun main() {} - `) + `)) if !res.HasMain { t.Errorf("main method should be detected with package") } - res, _ = NewParser().Parse("x.kt", ` + res, _ = NewParser().Parse("x.kt", []byte(` package my.demo import kotlin.text.* fun main() {} - `) + `)) if !res.HasMain { t.Errorf("main method should be detected with imports") } diff --git a/go.bzl b/go.bzl index 21165acef..8bdd02470 100644 --- a/go.bzl +++ b/go.bzl @@ -2308,15 +2308,15 @@ def deps(): name = "org_golang_google_genproto_googleapis_api", build_file_proto_mode = "disable_global", importpath = "google.golang.org/genproto/googleapis/api", - sum = "h1:SkdGTrROJl2jRGT/Fxv5QUf9jtdKCQh4KQJXbXVLAi0=", - version = "v0.0.0-20240521202816-d264139d666e", + sum = "h1:+rdxYoE3E5htTEWIe15GlN6IfvbURM//Jt0mmkmm6ZU=", + version = "v0.0.0-20240604185151-ef581f913117", ) go_repository( name = "org_golang_google_genproto_googleapis_rpc", build_file_proto_mode = "disable_global", importpath = "google.golang.org/genproto/googleapis/rpc", - sum = "h1:Elxv5MwEkCI9f5SkoL6afed6NTdxaGoAo39eANBwHL8=", - version = "v0.0.0-20240521202816-d264139d666e", + sum = "h1:Zy9XzmMEflZ/MAaA7vNcoebnRAld7FsPW1EeBB7V0m8=", + version = "v0.0.0-20240528184218-531527333157", ) go_repository( name = "org_golang_google_grpc", @@ -2344,29 +2344,29 @@ def deps(): name = "org_golang_x_crypto", build_file_proto_mode = "disable_global", importpath = "golang.org/x/crypto", - sum = "h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30=", - version = "v0.25.0", + sum = "h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw=", + version = "v0.28.0", ) go_repository( name = "org_golang_x_exp", build_file_proto_mode = "disable_global", importpath = "golang.org/x/exp", - sum = "h1:wDLEX9a7YQoKdKNQt88rtydkqDxeGaBUTnIYc3iG/mA=", - version = "v0.0.0-20240716175740-e3f259677ff7", + sum = "h1:7dEasQXItcW1xKJ2+gg5VOiBnqWrJc+rq0DPKyvvdbY=", + version = "v0.0.0-20241009180824-f66d83c29e7c", ) go_repository( name = "org_golang_x_mod", build_file_proto_mode = "disable_global", importpath = "golang.org/x/mod", - sum = "h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0=", - version = "v0.20.0", + sum = "h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0=", + version = "v0.21.0", ) go_repository( name = "org_golang_x_net", build_file_proto_mode = "disable_global", importpath = "golang.org/x/net", - sum = "h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys=", - version = "v0.27.0", + sum = "h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4=", + version = "v0.30.0", ) go_repository( name = "org_golang_x_oauth2", @@ -2386,22 +2386,22 @@ def deps(): name = "org_golang_x_sys", build_file_proto_mode = "disable_global", importpath = "golang.org/x/sys", - sum = "h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34=", - version = "v0.25.0", + sum = "h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=", + version = "v0.26.0", ) go_repository( name = "org_golang_x_term", build_file_proto_mode = "disable_global", importpath = "golang.org/x/term", - sum = "h1:BbsgPEJULsl2fV/AT3v15Mjva5yXKQDyKf+TbDz7QJk=", - version = "v0.22.0", + sum = "h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24=", + version = "v0.25.0", ) go_repository( name = "org_golang_x_text", build_file_proto_mode = "disable_global", importpath = "golang.org/x/text", - sum = "h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=", - version = "v0.16.0", + sum = "h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM=", + version = "v0.19.0", ) go_repository( name = "org_golang_x_time", @@ -2415,8 +2415,8 @@ def deps(): build_directives = ["gazelle:exclude **/testdata/**/*"], build_file_proto_mode = "disable_global", importpath = "golang.org/x/tools", - sum = "h1:SGsXPZ+2l4JsgaCKkx+FQ9YZ5XEtA1GZYuoDjenLjvg=", - version = "v0.23.0", + sum = "h1:v/60pFQmzmT9ExmjDv2gGIfi3OqfKoEP6I5+umXlbnQ=", + version = "v0.26.0", ) go_repository( name = "org_golang_x_tools_go_vcs", diff --git a/go.mod b/go.mod index 1fdc907ff..feb5e58b8 100644 --- a/go.mod +++ b/go.mod @@ -36,7 +36,7 @@ require ( github.com/twmb/murmur3 v1.1.8 github.com/yargevad/filepathx v1.0.0 go.starlark.net v0.0.0-20240725214946-42030a7cedce - golang.org/x/exp v0.0.0-20240716175740-e3f259677ff7 // indirect + golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c // indirect golang.org/x/sync v0.8.0 google.golang.org/genproto v0.0.0-20240528184218-531527333157 google.golang.org/grpc v1.64.1 @@ -133,15 +133,15 @@ require ( github.com/yuin/goldmark-emoji v1.0.3 // indirect go.uber.org/atomic v1.9.0 // indirect go.uber.org/multierr v1.9.0 // indirect - golang.org/x/crypto v0.25.0 // indirect - golang.org/x/mod v0.20.0 // indirect - golang.org/x/net v0.27.0 // indirect - golang.org/x/sys v0.25.0 // indirect - golang.org/x/term v0.22.0 // indirect - golang.org/x/text v0.16.0 // indirect + golang.org/x/crypto v0.28.0 // indirect + golang.org/x/mod v0.21.0 // indirect + golang.org/x/net v0.30.0 // indirect + golang.org/x/sys v0.26.0 // indirect + golang.org/x/term v0.25.0 // indirect + golang.org/x/text v0.19.0 // indirect golang.org/x/tools/go/vcs v0.1.0-deprecated // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20240521202816-d264139d666e // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240521202816-d264139d666e // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240604185151-ef581f913117 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240528184218-531527333157 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect diff --git a/go.sum b/go.sum index 0c4c693b2..a9c9fec26 100644 --- a/go.sum +++ b/go.sum @@ -342,15 +342,15 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= -golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30= -golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= -golang.org/x/exp v0.0.0-20240716175740-e3f259677ff7 h1:wDLEX9a7YQoKdKNQt88rtydkqDxeGaBUTnIYc3iG/mA= -golang.org/x/exp v0.0.0-20240716175740-e3f259677ff7/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= +golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= +golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= +golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c h1:7dEasQXItcW1xKJ2+gg5VOiBnqWrJc+rq0DPKyvvdbY= +golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c/go.mod h1:NQtJDoLvd6faHhE7m4T/1IY708gDefGGjR/iUW8yQQ8= golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0= -golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0= +golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= @@ -360,8 +360,8 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= -golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys= -golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= +golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4= +golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -394,15 +394,15 @@ golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= -golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= +golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= -golang.org/x/term v0.22.0 h1:BbsgPEJULsl2fV/AT3v15Mjva5yXKQDyKf+TbDz7QJk= -golang.org/x/term v0.22.0/go.mod h1:F3qCibpT5AMpCRfhfT53vVJwhLtIVHhB9XDjfFvnMI4= +golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24= +golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= @@ -410,15 +410,15 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= -golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= -golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= +golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= +golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.8/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/tools v0.23.0 h1:SGsXPZ+2l4JsgaCKkx+FQ9YZ5XEtA1GZYuoDjenLjvg= -golang.org/x/tools v0.23.0/go.mod h1:pnu6ufv6vQkll6szChhK3C3L/ruaIv5eBeztNG8wtsI= +golang.org/x/tools v0.26.0 h1:v/60pFQmzmT9ExmjDv2gGIfi3OqfKoEP6I5+umXlbnQ= +golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0= golang.org/x/tools/go/vcs v0.1.0-deprecated h1:cOIJqWBl99H1dH5LWizPa+0ImeeJq3t3cJjaeOWUAL4= golang.org/x/tools/go/vcs v0.1.0-deprecated/go.mod h1:zUrvATBAvEI9535oC0yWYsLsHIV4Z7g63sNPVMtuBy8= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -427,10 +427,10 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/genproto v0.0.0-20240528184218-531527333157 h1:u7WMYrIrVvs0TF5yaKwKNbcJyySYf+HAIFXxWltJOXE= google.golang.org/genproto v0.0.0-20240528184218-531527333157/go.mod h1:ubQlAQnzejB8uZzszhrTCU2Fyp6Vi7ZE5nn0c3W8+qQ= -google.golang.org/genproto/googleapis/api v0.0.0-20240521202816-d264139d666e h1:SkdGTrROJl2jRGT/Fxv5QUf9jtdKCQh4KQJXbXVLAi0= -google.golang.org/genproto/googleapis/api v0.0.0-20240521202816-d264139d666e/go.mod h1:LweJcLbyVij6rCex8YunD8DYR5VDonap/jYl3ZRxcIU= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240521202816-d264139d666e h1:Elxv5MwEkCI9f5SkoL6afed6NTdxaGoAo39eANBwHL8= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240521202816-d264139d666e/go.mod h1:EfXuqaE1J41VCDicxHzUDm+8rk+7ZdXzHV0IhO/I6s0= +google.golang.org/genproto/googleapis/api v0.0.0-20240604185151-ef581f913117 h1:+rdxYoE3E5htTEWIe15GlN6IfvbURM//Jt0mmkmm6ZU= +google.golang.org/genproto/googleapis/api v0.0.0-20240604185151-ef581f913117/go.mod h1:OimBR/bc1wPO9iV4NC2bpyjy3VnAwZh5EBPQdtaE5oo= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240528184218-531527333157 h1:Zy9XzmMEflZ/MAaA7vNcoebnRAld7FsPW1EeBB7V0m8= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240528184218-531527333157/go.mod h1:EfXuqaE1J41VCDicxHzUDm+8rk+7ZdXzHV0IhO/I6s0= google.golang.org/grpc v1.64.1 h1:LKtvyfbX3UGVPFcGqJ9ItpVWW6oN/2XqTxfAnwRRXiA= google.golang.org/grpc v1.64.1/go.mod h1:hiQF4LFZelK2WKaP6W0L92zGHtiQdZxk8CrSdvyjeP0= google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg= diff --git a/patches/bazelbuild_bazel-gazelle_aspect-fs-direntry.patch b/patches/bazelbuild_bazel-gazelle_aspect-fs-direntry.patch new file mode 100644 index 000000000..4b8d6b98f --- /dev/null +++ b/patches/bazelbuild_bazel-gazelle_aspect-fs-direntry.patch @@ -0,0 +1,69 @@ +diff --git a/config/config.go b/config/config.go +index 1841650..f34ad5e 100644 +--- a/config/config.go ++++ b/config/config.go +@@ -122,6 +122,7 @@ type MappedKind struct { + + const ASPECT_WALKSUBDIR = "__aspect:walksubdir" + const ASPECT_GITIGNORE = "__aspect:gitignore" ++const ASPECT_DIR_ENTRIES = "__aspect:direntries" + + func New() *Config { + return &Config{ +diff --git a/walk/walk.go b/walk/walk.go +index 1ba1b47..19e99ef 100644 +--- a/walk/walk.go ++++ b/walk/walk.go +@@ -144,6 +144,14 @@ func visit(c *config.Config, cexts []config.Configurer, knownDirectives map[stri + return ents[i].Name() < ents[j].Name() + }) + ++ // PATCH(fs.DirEntry map) --- ++ entsMap := make(map[string]fs.DirEntry, len(trie.children)) ++ for _, node := range trie.children { ++ entsMap[(*node.entry).Name()] = *node.entry ++ } ++ c.Exts[config.ASPECT_DIR_ENTRIES] = entsMap ++ // END PATCH(fs.DirEntry map) --- ++ + // Absolute path to the directory being visited + dir := filepath.Join(c.RepoRoot, rel) + +@@ -158,7 +166,7 @@ func visit(c *config.Config, cexts []config.Configurer, knownDirectives map[stri + haveError = true + } + +- c = configure(cexts, knownDirectives, c, rel, f) ++ configure(cexts, knownDirectives, c, rel, f) + wc := getWalkConfig(c) + + if wc.isExcluded(rel) { +@@ -203,7 +211,7 @@ func visit(c *config.Config, cexts []config.Configurer, knownDirectives map[stri + if subRel := path.Join(rel, sub); updateRels.shouldVisit(subRel, shouldUpdate) { + // PATCH --- + // Merge the returned 'subFiles' if 'mergeFiles' is true +- subFiles, mergeFiles := visit(c, cexts, knownDirectives, updateRels, trie.children[sub], wf, subRel, shouldUpdate) ++ subFiles, mergeFiles := visit(c.Clone(), cexts, knownDirectives, updateRels, trie.children[sub], wf, subRel, shouldUpdate) + if mergeFiles { + for _, f := range subFiles { + regularFiles = append(regularFiles, path.Join(sub, f)) +@@ -330,10 +338,7 @@ func loadBuildFile(c *config.Config, pkg, dir string, ents []fs.DirEntry) (*rule + return rule.LoadFile(path, pkg) + } + +-func configure(cexts []config.Configurer, knownDirectives map[string]bool, c *config.Config, rel string, f *rule.File) *config.Config { +- if rel != "" { +- c = c.Clone() +- } ++func configure(cexts []config.Configurer, knownDirectives map[string]bool, c *config.Config, rel string, f *rule.File) { + if f != nil { + for _, d := range f.Directives { + if !knownDirectives[d.Key] { +@@ -349,7 +354,6 @@ func configure(cexts []config.Configurer, knownDirectives map[string]bool, c *co + for _, cext := range cexts { + cext.Configure(c, rel, f) + } +- return c + } + + func findGenFiles(wc *walkConfig, f *rule.File) []string { diff --git a/pkg/aspect/configure/BUILD.bazel b/pkg/aspect/configure/BUILD.bazel index ed3dcbdee..15ad5357a 100644 --- a/pkg/aspect/configure/BUILD.bazel +++ b/pkg/aspect/configure/BUILD.bazel @@ -8,6 +8,7 @@ go_library( "fix.go", "fix-update.go", "gazelle.go", + "go_deps.go", "metaresolver.go", "print.go", "profiler.go", @@ -21,7 +22,9 @@ go_library( "//gazelle/python", "//pkg/aspect/configure/internal/wspace", "//pkg/aspecterrors", + "//pkg/bazel", "//pkg/ioutils", + "//pkg/logger", "@bazel_gazelle//config:go_default_library", "@bazel_gazelle//flag:go_default_library", "@bazel_gazelle//label:go_default_library", diff --git a/pkg/aspect/configure/configure.go b/pkg/aspect/configure/configure.go index e374698c8..e30193095 100644 --- a/pkg/aspect/configure/configure.go +++ b/pkg/aspect/configure/configure.go @@ -75,6 +75,17 @@ func (c *Configure) addDefaultLanguages() { viper.SetDefault("configure.languages.go", false) if viper.GetBool("configure.languages.go") { + if os.Getenv(GO_REPOSITORY_CONFIG_ENV) == "" { + goConfigPath, err := determineGoRepositoryConfigPath() + if err != nil { + log.Fatalf("ERROR: unable to determine go_repository config path: %v", err) + } + + if goConfigPath != "" { + os.Setenv(GO_REPOSITORY_CONFIG_ENV, goConfigPath) + } + } + c.AddLanguage("go", golang.NewLanguage) } @@ -140,6 +151,11 @@ configure: fixArgs = append(fixArgs, "--memprofile="+memprofile) } + go_repo_config := os.Getenv(GO_REPOSITORY_CONFIG_ENV) + if go_repo_config != "" { + fixArgs = append(fixArgs, "--repo_config="+go_repo_config) + } + // Append additional args including specific directories to fix. fixArgs = append(fixArgs, args...) diff --git a/pkg/aspect/configure/diff.go b/pkg/aspect/configure/diff.go index f89e1935d..3db457f3b 100644 --- a/pkg/aspect/configure/diff.go +++ b/pkg/aspect/configure/diff.go @@ -76,6 +76,19 @@ func diffFile(c *config.Config, f *rule.File) error { } else { diff.ToFile = outPath } + // Trim off trailing newline from the end of the file. Modern diff tools typically + // handle this, however difflib does not and is no longer maintained, so we need to + // handle it here. + if len(diff.A) > 0 { + if diff.A[len(diff.A)-1] == "\n" { + diff.A = diff.A[:len(diff.A)-1] + } + } + if len(diff.B) > 0 { + if diff.B[len(diff.B)-1] == "\n" { + diff.B = diff.B[:len(diff.B)-1] + } + } uc := getUpdateConfig(c) var out io.Writer = os.Stdout diff --git a/pkg/aspect/configure/go_deps.go b/pkg/aspect/configure/go_deps.go new file mode 100644 index 000000000..001a69f40 --- /dev/null +++ b/pkg/aspect/configure/go_deps.go @@ -0,0 +1,82 @@ +/* + * Copyright 2024 Aspect Build Systems, Inc. + * + * 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 configure + +import ( + "fmt" + "os" + "path" + "strings" + + "aspect.build/cli/pkg/bazel" + "aspect.build/cli/pkg/ioutils" + BazelLog "aspect.build/cli/pkg/logger" +) + +// The @gazelle go_deps extension name. +// https://github.com/bazel-contrib/bazel-gazelle/blob/v0.39.1/internal/bzlmod/go_deps.bzl#L827 +const GO_DEPS_EXTENSION_NAME = "go_deps" + +// The repository name for the gazelle repo_config. +// https://github.com/bazel-contrib/bazel-gazelle/blob/v0.39.1/internal/bzlmod/go_deps.bzl#L648-L654 +const GO_REPOSITORY_CONFIG_REPO_NAME = "bazel_gazelle_go_repository_config" + +// An environment variable to set the full path to the gazelle repo_config +const GO_REPOSITORY_CONFIG_ENV = GO_REPOSITORY_CONFIG_REPO_NAME + +// bazel 8 switches the bzlmod separator to "+" +// See https://github.com/bazelbuild/bazel/issues/23127 +var BZLMOD_REPO_SEPARATORS = []string{"~", "+"} + +func determineGoRepositoryConfigPath() (string, error) { + // TODO(jason): look into a store of previous invocations for relevant logs + bzl := bazel.WorkspaceFromWd + + var out strings.Builder + streams := ioutils.Streams{Stdout: &out, Stderr: nil} + if err := bzl.RunCommand(streams, nil, "info", "output_base"); err != nil { + return "", fmt.Errorf("unable to locate output_base: %w", err) + } + + outputBase := strings.TrimSpace(out.String()) + if outputBase == "" { + return "", fmt.Errorf("unable to locate output_base on path") + } + + var goDepsRepoName string + for _, sep := range BZLMOD_REPO_SEPARATORS { + repoName := fmt.Sprintf("gazelle%s%s%s%s%s/WORKSPACE", sep, sep, GO_DEPS_EXTENSION_NAME, sep, GO_REPOSITORY_CONFIG_REPO_NAME) + repoPath := path.Join(outputBase, "external", repoName) + + _, err := os.Stat(repoPath) + if err == nil { + goDepsRepoName = repoPath + break + } + } + + if goDepsRepoName == "" { + // Assume no matches means rules_go is not being used in bzlmod + // or the gazelle `go_deps` extension is not being used + BazelLog.Infof("No %s found in output_base: %s", GO_REPOSITORY_CONFIG_REPO_NAME, outputBase) + return "", nil + } + + BazelLog.Infof("Found %s(s): %v", GO_REPOSITORY_CONFIG_REPO_NAME, goDepsRepoName) + + return goDepsRepoName, nil +} diff --git a/pkg/bazel/BUILD.bazel b/pkg/bazel/BUILD.bazel index 17f805875..0512b3c3c 100644 --- a/pkg/bazel/BUILD.bazel +++ b/pkg/bazel/BUILD.bazel @@ -6,6 +6,7 @@ go_library( "bazel.go", "bazel_flags.go", "bazelisk.go", + "bazelisk-core.go", ], importpath = "aspect.build/cli/pkg/bazel", visibility = ["//visibility:public"], diff --git a/pkg/bazel/bazelisk-core.go b/pkg/bazel/bazelisk-core.go new file mode 100644 index 000000000..ec1cf44e2 --- /dev/null +++ b/pkg/bazel/bazelisk-core.go @@ -0,0 +1,260 @@ +// VENDORED https://github.com/bazelbuild/bazelisk/blob/v1.17.0/core/core.go +// +// Minor changes made to align with the ./bazelisk.go API, which is a mix of custom code +// and vendored code significantly different then the origin bazelisk/core/core.go. +// +// DO NOT MODIFY ... without diffing with the upstream file. + +// Package core contains the core Bazelisk logic, as well as abstractions for Bazel repositories. +package bazel + +// TODO: split this file into multiple smaller ones in dedicated packages (e.g. execution, incompatible, ...). + +import ( + "crypto/sha256" + "fmt" + "io" + "log" + "os" + "os/exec" + "os/signal" + "path/filepath" + "regexp" + "strings" + "sync" + "syscall" + + "aspect.build/cli/buildinfo" + "aspect.build/cli/pkg/ioutils" + "github.com/bazelbuild/bazelisk/platforms" + "github.com/bazelbuild/bazelisk/versions" +) + +const ( + bazelReal = "BAZEL_REAL" + skipWrapperEnv = "BAZELISK_SKIP_WRAPPER" + wrapperPath = "./tools/bazel" + rcFileName = ".bazeliskrc" + maxDirLength = 255 +) + +var ( + fileConfig map[string]string + fileConfigOnce sync.Once +) + +func (bazelisk *Bazelisk) getUserAgent() string { + agent := bazelisk.GetEnvOrConfig("BAZELISK_USER_AGENT") + if len(agent) > 0 { + return agent + } + return fmt.Sprintf("Aspect/%s", buildinfo.Current().Version()) +} + +// GetEnvOrConfig reads a configuration value from the environment, but fall back to reading it from .bazeliskrc. +func (bazelisk *Bazelisk) GetEnvOrConfig(name string) string { + if val, found := os.LookupEnv(name); found { + return val + } + + fileConfigOnce.Do(bazelisk.loadFileConfig) + + return fileConfig[name] +} + +// loadFileConfig locates available .bazeliskrc configuration files, parses them with a precedence order preference, +// and updates a global configuration map with their contents. This routine should be executed exactly once. +func (bazelisk *Bazelisk) loadFileConfig() { + var rcFilePaths []string + + if userRC, err := locateUserConfigFile(); err == nil { + rcFilePaths = append(rcFilePaths, userRC) + } + if workspaceRC, err := bazelisk.locateWorkspaceConfigFile(); err == nil { + rcFilePaths = append(rcFilePaths, workspaceRC) + } + + fileConfig = make(map[string]string) + for _, rcPath := range rcFilePaths { + config, err := parseFileConfig(rcPath) + if err != nil { + log.Fatal(err) + } + + for key, value := range config { + fileConfig[key] = value + } + } +} + +// MODIFIED to use the Bazelisk struct +// locateWorkspaceConfigFile locates a .bazeliskrc file in the current workspace root. +func (bazelisk *Bazelisk) locateWorkspaceConfigFile() (string, error) { + return filepath.Join(bazelisk.workspaceRoot, rcFileName), nil +} + +// locateUserConfigFile locates a .bazeliskrc file in the user's home directory. +func locateUserConfigFile() (string, error) { + home, err := os.UserHomeDir() + if err != nil { + return "", err + } + return filepath.Join(home, rcFileName), nil +} + +// parseFileConfig parses a .bazeliskrc file as a map of key-value configuration values. +func parseFileConfig(rcFilePath string) (map[string]string, error) { + config := make(map[string]string) + + contents, err := os.ReadFile(rcFilePath) + if err != nil { + if os.IsNotExist(err) { + // Non-critical error. + return config, nil + } + return nil, err + } + + for _, line := range strings.Split(string(contents), "\n") { + if strings.HasPrefix(line, "#") { + // comments + continue + } + parts := strings.SplitN(line, "=", 2) + if len(parts) < 2 { + continue + } + key := strings.TrimSpace(parts[0]) + config[key] = strings.TrimSpace(parts[1]) + } + + return config, nil +} + +// isValidWorkspace returns true iff the supplied path is the workspace root, defined by the presence of +// a file named WORKSPACE or WORKSPACE.bazel +// see https://github.com/bazelbuild/bazel/blob/8346ea4cfdd9fbd170d51a528fee26f912dad2d5/src/main/cpp/workspace_layout.cc#L37 +func isValidWorkspace(path string) bool { + info, err := os.Stat(path) + if err != nil { + return false + } + + return !info.IsDir() +} + +func parseBazelForkAndVersion(bazelForkAndVersion string) (string, string, error) { + var bazelFork, bazelVersion string + + versionInfo := strings.Split(bazelForkAndVersion, "/") + + if len(versionInfo) == 1 { + bazelFork, bazelVersion = versions.BazelUpstream, versionInfo[0] + } else if len(versionInfo) == 2 { + bazelFork, bazelVersion = versionInfo[0], versionInfo[1] + } else { + return "", "", fmt.Errorf("invalid version \"%s\", could not parse version with more than one slash", bazelForkAndVersion) + } + + return bazelFork, bazelVersion, nil +} + +func copyFile(src, dst string, perm os.FileMode) error { + srcFile, err := os.Open(src) + if err != nil { + return err + } + defer srcFile.Close() + + dstFile, err := os.OpenFile(dst, os.O_WRONLY|os.O_CREATE, perm) + if err != nil { + return err + } + defer dstFile.Close() + + _, err = io.Copy(dstFile, srcFile) + + return err +} + +func linkLocalBazel(baseDirectory string, bazelPath string) (string, error) { + normalizedBazelPath := dirForURL(bazelPath) + destinationDir := filepath.Join(baseDirectory, normalizedBazelPath, "bin") + err := os.MkdirAll(destinationDir, 0755) + if err != nil { + return "", fmt.Errorf("could not create directory %s: %v", destinationDir, err) + } + destinationPath := filepath.Join(destinationDir, "bazel"+platforms.DetermineExecutableFilenameSuffix()) + if _, err := os.Stat(destinationPath); err != nil { + err = os.Symlink(bazelPath, destinationPath) + // If can't create Symlink, fallback to copy + if err != nil { + err = copyFile(bazelPath, destinationPath, 0755) + if err != nil { + return "", fmt.Errorf("could not copy file from %s to %s: %v", bazelPath, destinationPath, err) + } + } + } + return destinationPath, nil +} + +func prependDirToPathList(cmd *exec.Cmd, dir string) { + found := false + for idx, val := range cmd.Env { + splits := strings.Split(val, "=") + if len(splits) != 2 { + continue + } + if strings.EqualFold(splits[0], "PATH") { + found = true + cmd.Env[idx] = fmt.Sprintf("PATH=%s%s%s", dir, string(os.PathListSeparator), splits[1]) + break + } + } + + if !found { + cmd.Env = append(cmd.Env, fmt.Sprintf("PATH=%s", dir)) + } +} + +func (bazelisk *Bazelisk) runBazel(bazel string, args []string, streams ioutils.Streams, env []string, wd *string) (int, error) { + cmd := bazelisk.makeBazelCmd(bazel, args, streams, env, wd) + + err := cmd.Start() + if err != nil { + return 1, fmt.Errorf("could not start Bazel: %v", err) + } + + c := make(chan os.Signal, 1) + signal.Notify(c, os.Interrupt, syscall.SIGTERM) + go func() { + s := <-c + + // Only forward SIGTERM to our child process. + if s != os.Interrupt { + cmd.Process.Kill() + } + }() + + err = cmd.Wait() + if err != nil { + if exitError, ok := err.(*exec.ExitError); ok { + waitStatus := exitError.Sys().(syscall.WaitStatus) + return waitStatus.ExitStatus(), nil + } + return 1, fmt.Errorf("could not launch Bazel: %v", err) + } + return 0, nil +} + +func dirForURL(url string) string { + // Replace all characters that might not be allowed in filenames with "-". + dir := regexp.MustCompile("[[:^alnum:]]").ReplaceAllString(url, "-") + // Work around length limit on some systems by truncating and then appending + // a sha256 hash of the URL. + if len(dir) > maxDirLength { + suffix := fmt.Sprintf("...%x", sha256.Sum256([]byte(url))) + dir = dir[:maxDirLength-len(suffix)] + suffix + } + return dir +} diff --git a/pkg/bazel/bazelisk.go b/pkg/bazel/bazelisk.go index 18cc1dab0..bdf35c61e 100644 --- a/pkg/bazel/bazelisk.go +++ b/pkg/bazel/bazelisk.go @@ -19,21 +19,15 @@ package bazel import ( "bufio" "fmt" - "io" "log" "os" "os/exec" - "os/signal" "path/filepath" - "regexp" "strings" - "sync" - "syscall" "github.com/bazelbuild/bazelisk/core" "github.com/bazelbuild/bazelisk/httputil" "github.com/bazelbuild/bazelisk/platforms" - "github.com/bazelbuild/bazelisk/versions" "github.com/mitchellh/go-homedir" "aspect.build/cli/buildinfo" @@ -43,17 +37,8 @@ import ( ) const ( - bazelReal = "BAZEL_REAL" - skipWrapperEnv = "BAZELISK_SKIP_WRAPPER" aspectReentrantEnv = "ASPECT_REENTRANT" useBazelVersionEnv = "USE_BAZEL_VERSION" - wrapperPath = "./tools/bazel" - rcFileName = ".bazeliskrc" -) - -var ( - fileConfig map[string]string - fileConfigOnce sync.Once ) type Bazelisk struct { @@ -107,31 +92,10 @@ func (bazelisk *Bazelisk) GetBazelPath(repos *core.Repositories) (string, error) return "", fmt.Errorf("could not expand home directory in path: %v", err) } - // If the Bazel version is an absolute path to a Bazel binary in the filesystem, we can - // use it directly. In that case, we don't know which exact version it is, though. - resolvedBazelVersion := "unknown" - // If we aren't using a local Bazel binary, we'll have to parse the version string and // download the version that the user wants. if !filepath.IsAbs(bazelPath) { - bazelFork, bazelVersion, err := parseBazelForkAndVersion(bazelVersionString) - if err != nil { - return "", fmt.Errorf("could not parse Bazel fork and version: %v", err) - } - - var downloader core.DownloadFunc - resolvedBazelVersion, downloader, err = repos.ResolveVersion(bazeliskHome, bazelFork, bazelVersion) - if err != nil { - return "", fmt.Errorf("could not resolve the version '%s' to an actual version number: %v", bazelVersion, err) - } - - bazelForkOrURL := dirForURL(baseUrl) - if len(bazelForkOrURL) == 0 { - bazelForkOrURL = bazelFork - } - - baseDirectory := filepath.Join(bazeliskHome, "downloads", bazelForkOrURL) - bazelPath, err = bazelisk.downloadBazel(resolvedBazelVersion, baseDirectory, repos, baseUrl, downloader) + bazelPath, err = downloadBazel(bazelVersionString, baseUrl, bazeliskHome, repos) if err != nil { return "", fmt.Errorf("could not download Bazel: %v", err) } @@ -169,100 +133,6 @@ func (bazelisk *Bazelisk) Run(args []string, repos *core.Repositories, streams i return nil } -func (bazelisk *Bazelisk) getUserAgent() string { - agent := bazelisk.GetEnvOrConfig("BAZELISK_USER_AGENT") - if len(agent) > 0 { - return agent - } - return fmt.Sprintf("Aspect/%s", buildinfo.Current().Version()) -} - -// GetConfig reads a configuration value from .bazeliskrc. -func (bazelisk *Bazelisk) GetConfig(name string) string { - fileConfigOnce.Do(bazelisk.loadFileConfig) - - return fileConfig[name] -} - -// GetEnvOrConfig reads a configuration value from the environment, but fall back to reading it from .bazeliskrc. -func (bazelisk *Bazelisk) GetEnvOrConfig(name string) string { - if val, found := os.LookupEnv(name); found { - return val - } - - fileConfigOnce.Do(bazelisk.loadFileConfig) - - return fileConfig[name] -} - -// loadFileConfig locates available .bazeliskrc configuration files, parses them with a precedence order preference, -// and updates a global configuration map with their contents. This routine should be executed exactly once. -func (bazelisk *Bazelisk) loadFileConfig() { - var rcFilePaths []string - - if userRC, err := locateUserConfigFile(); err == nil { - rcFilePaths = append(rcFilePaths, userRC) - } - if workspaceRC, err := bazelisk.locateWorkspaceConfigFile(); err == nil { - rcFilePaths = append(rcFilePaths, workspaceRC) - } - - fileConfig = make(map[string]string) - for _, rcPath := range rcFilePaths { - config, err := parseFileConfig(rcPath) - if err != nil { - log.Fatal(err) - } - - for key, value := range config { - fileConfig[key] = value - } - } -} - -// locateWorkspaceConfigFile locates a .bazeliskrc file in the current workspace root. -func (bazelisk *Bazelisk) locateWorkspaceConfigFile() (string, error) { - return filepath.Join(bazelisk.workspaceRoot, rcFileName), nil -} - -// locateUserConfigFile locates a .bazeliskrc file in the user's home directory. -func locateUserConfigFile() (string, error) { - home, err := os.UserHomeDir() - if err != nil { - return "", err - } - return filepath.Join(home, rcFileName), nil -} - -// parseFileConfig parses a .bazeliskrc file as a map of key-value configuration values. -func parseFileConfig(rcFilePath string) (map[string]string, error) { - config := make(map[string]string) - - contents, err := os.ReadFile(rcFilePath) - if err != nil { - if os.IsNotExist(err) { - // Non-critical error. - return config, nil - } - return nil, err - } - - for _, line := range strings.Split(string(contents), "\n") { - if strings.HasPrefix(line, "#") { - // comments - continue - } - parts := strings.SplitN(line, "=", 2) - if len(parts) < 2 { - continue - } - key := strings.TrimSpace(parts[0]) - config[key] = strings.TrimSpace(parts[1]) - } - - return config, nil -} - type aspectRuntimeInfo struct { Reentrant bool Version string @@ -350,9 +220,11 @@ func (bazelisk *Bazelisk) getBazelVersion() (string, string, error) { // Same as upstream bazelisk at this point: // https://github.com/bazelbuild/bazelisk/blob/c9081741bc1420d601140a4232b5c48872370fdc/core/core.go#L344 + workspaceRoot := bazelisk.workspaceRoot + // Load the version from the .bazelversion file if we know the workspace root and it exists - if len(bazelisk.workspaceRoot) != 0 { - bazelVersionPath := filepath.Join(bazelisk.workspaceRoot, ".bazelversion") + if len(workspaceRoot) != 0 { + bazelVersionPath := filepath.Join(workspaceRoot, ".bazelversion") if _, err := os.Stat(bazelVersionPath); err == nil { f, err := os.Open(bazelVersionPath) if err != nil { @@ -395,75 +267,41 @@ func (bazelisk *Bazelisk) getBazelVersion() (string, string, error) { return "", "", fmt.Errorf("invalid fallback version format %q (effectively %q)", fallbackVersionFormat, fmt.Sprintf("%s:%s", fallbackVersionMode, fallbackVersion)) } -func parseBazelForkAndVersion(bazelForkAndVersion string) (string, string, error) { - var bazelFork, bazelVersion string +func downloadBazel(bazelVersionString, baseURL string, bazeliskHome string, repos *core.Repositories) (string, error) { + bazelFork, bazelVersion, err := parseBazelForkAndVersion(bazelVersionString) + if err != nil { + return "", fmt.Errorf("could not parse Bazel fork and version: %v", err) + } - versionInfo := strings.Split(bazelForkAndVersion, "/") + resolvedBazelVersion, downloader, err := repos.ResolveVersion(bazeliskHome, bazelFork, bazelVersion) + if err != nil { + return "", fmt.Errorf("could not resolve the version '%s' to an actual version number: %v", bazelVersion, err) + } - if len(versionInfo) == 1 { - bazelFork, bazelVersion = versions.BazelUpstream, versionInfo[0] - } else if len(versionInfo) == 2 { - bazelFork, bazelVersion = versionInfo[0], versionInfo[1] - } else { - return "", "", fmt.Errorf("invalid version \"%s\", could not parse version with more than one slash", bazelForkAndVersion) + bazelForkOrURL := dirForURL(baseURL) + if len(bazelForkOrURL) == 0 { + bazelForkOrURL = bazelFork } - return bazelFork, bazelVersion, nil + baseDirectory := filepath.Join(bazeliskHome, "downloads", bazelForkOrURL) + bazelPath, err := downloadBazelIfNecessary(resolvedBazelVersion, baseDirectory, repos, baseURL, downloader) + return bazelPath, err } -func (bazelisk *Bazelisk) downloadBazel(version string, baseDirectory string, repos *core.Repositories, baseUrl string, downloader core.DownloadFunc) (string, error) { +func downloadBazelIfNecessary(version string, baseDirectory string, repos *core.Repositories, baseURL string, downloader core.DownloadFunc) (string, error) { pathSegment, err := platforms.DetermineBazelFilename(version, false) if err != nil { return "", fmt.Errorf("could not determine path segment to use for Bazel binary: %v", err) } + destDir := filepath.Join(baseDirectory, pathSegment, "bin") destFile := "bazel" + platforms.DetermineExecutableFilenameSuffix() - destinationDir := filepath.Join(baseDirectory, pathSegment, "bin") - if baseUrl != "" { - return repos.DownloadFromBaseURL(baseUrl, version, destinationDir, destFile) + if baseURL != "" { + return repos.DownloadFromBaseURL(baseURL, version, destDir, destFile) } - return downloader(destinationDir, destFile) -} - -func copyFile(src, dst string, perm os.FileMode) error { - srcFile, err := os.Open(src) - if err != nil { - return err - } - defer srcFile.Close() - - dstFile, err := os.OpenFile(dst, os.O_WRONLY|os.O_CREATE, perm) - if err != nil { - return err - } - defer dstFile.Close() - - _, err = io.Copy(dstFile, srcFile) - - return err -} - -func linkLocalBazel(baseDirectory string, bazelPath string) (string, error) { - normalizedBazelPath := dirForURL(bazelPath) - destinationDir := filepath.Join(baseDirectory, normalizedBazelPath, "bin") - err := os.MkdirAll(destinationDir, 0755) - if err != nil { - return "", fmt.Errorf("could not create directory %s: %v", destinationDir, err) - } - destinationPath := filepath.Join(destinationDir, "bazel"+platforms.DetermineExecutableFilenameSuffix()) - if _, err := os.Stat(destinationPath); err != nil { - err = os.Symlink(bazelPath, destinationPath) - // If can't create Symlink, fallback to copy - if err != nil { - err = copyFile(bazelPath, destinationPath, 0755) - if err != nil { - return "", fmt.Errorf("could not copy file from %s to %s: %v", bazelPath, destinationPath, err) - } - } - } - return destinationPath, nil + return downloader(destDir, destFile) } func (bazelisk *Bazelisk) maybeDelegateToWrapper(bazel string) string { @@ -479,25 +317,6 @@ func (bazelisk *Bazelisk) maybeDelegateToWrapper(bazel string) string { return wrapper } -func prependDirToPathList(cmd *exec.Cmd, dir string) { - found := false - for idx, val := range cmd.Env { - splits := strings.Split(val, "=") - if len(splits) != 2 { - continue - } - if strings.EqualFold(splits[0], "PATH") { - found = true - cmd.Env[idx] = fmt.Sprintf("PATH=%s%s%s", dir, string(os.PathListSeparator), splits[1]) - break - } - } - - if !found { - cmd.Env = append(cmd.Env, fmt.Sprintf("PATH=%s", dir)) - } -} - func (bazelisk *Bazelisk) makeBazelCmd(bazel string, args []string, streams ioutils.Streams, env []string, wd *string) *exec.Cmd { execPath := bazelisk.maybeDelegateToWrapper(bazel) @@ -519,58 +338,3 @@ func (bazelisk *Bazelisk) makeBazelCmd(bazel string, args []string, streams iout cmd.Stderr = streams.Stderr return cmd } - -func (bazelisk *Bazelisk) runBazel(bazel string, args []string, streams ioutils.Streams, env []string, wd *string) (int, error) { - cmd := bazelisk.makeBazelCmd(bazel, args, streams, env, wd) - - err := cmd.Start() - if err != nil { - return 1, fmt.Errorf("could not start Bazel: %v", err) - } - - c := make(chan os.Signal, 1) - signal.Notify(c, os.Interrupt, syscall.SIGTERM) - go func() { - s := <-c - - // Only forward SIGTERM to our child process. - if s != os.Interrupt { - cmd.Process.Kill() - } - }() - - err = cmd.Wait() - if err != nil { - if exitError, ok := err.(*exec.ExitError); ok { - waitStatus := exitError.Sys().(syscall.WaitStatus) - return waitStatus.ExitStatus(), nil - } - return 1, fmt.Errorf("could not launch Bazel: %v", err) - } - return 0, nil -} - -// insertArgs will insert newArgs in baseArgs. If baseArgs contains the -// "--" argument, newArgs will be inserted before that. Otherwise, newArgs -// is appended. -func insertArgs(baseArgs []string, newArgs []string) []string { - var result []string - inserted := false - for _, arg := range baseArgs { - if !inserted && arg == "--" { - result = append(result, newArgs...) - inserted = true - } - result = append(result, arg) - } - - if !inserted { - result = append(result, newArgs...) - } - return result -} - -func dirForURL(url string) string { - // Replace all characters that might not be allowed in filenames with "-". - return regexp.MustCompile("[[:^alnum:]]").ReplaceAllString(url, "-") -} diff --git a/pkg/logger/logger.go b/pkg/logger/logger.go index fb114b857..1ec8be440 100644 --- a/pkg/logger/logger.go +++ b/pkg/logger/logger.go @@ -112,6 +112,12 @@ func Warnf(format string, args ...interface{}) { logger.Printf(format, args...) } +func Error(msg string) { + if level > ErrorLevel { + return + } + logger.Print(msg) +} func Errorf(format string, args ...interface{}) { if level > ErrorLevel { return @@ -122,6 +128,9 @@ func Errorf(format string, args ...interface{}) { func Fatalf(format string, args ...interface{}) { logger.Fatalf(format+"\n", args...) } +func Fatal(msg string) { + logger.Fatal(msg + "\n") +} func IsLevelEnabled(l Level) bool { return level > l diff --git a/pkg/plugin/sdk/v1alpha1/plugin/grpc.go b/pkg/plugin/sdk/v1alpha1/plugin/grpc.go index ceefbbe1f..650d06e5e 100644 --- a/pkg/plugin/sdk/v1alpha1/plugin/grpc.go +++ b/pkg/plugin/sdk/v1alpha1/plugin/grpc.go @@ -21,6 +21,7 @@ package plugin import ( "context" + "errors" "fmt" goplugin "github.com/hashicorp/go-plugin" @@ -180,7 +181,7 @@ func (p *PrompterGRPCClient) Run(prompt promptui.Prompt) (string, error) { return "", err } if res.Error != nil && res.Error.Happened { - return "", fmt.Errorf(res.Error.Message) + return "", errors.New(res.Error.Message) } return res.Result, nil } diff --git a/pkg/plugin/sdk/v1alpha2/plugin/grpc.go b/pkg/plugin/sdk/v1alpha2/plugin/grpc.go index 8d4790f18..ec80d54d0 100644 --- a/pkg/plugin/sdk/v1alpha2/plugin/grpc.go +++ b/pkg/plugin/sdk/v1alpha2/plugin/grpc.go @@ -21,6 +21,7 @@ package plugin import ( "context" + "errors" "fmt" goplugin "github.com/hashicorp/go-plugin" @@ -352,7 +353,7 @@ func (p *PrompterGRPCClient) Run(prompt promptui.Prompt) (string, error) { return "", err } if res.Error != nil && res.Error.Happened { - return "", fmt.Errorf(res.Error.Message) + return "", errors.New(res.Error.Message) } return res.Result, nil } diff --git a/pkg/plugin/sdk/v1alpha3/plugin/grpc.go b/pkg/plugin/sdk/v1alpha3/plugin/grpc.go index b343b7577..18011e9f8 100644 --- a/pkg/plugin/sdk/v1alpha3/plugin/grpc.go +++ b/pkg/plugin/sdk/v1alpha3/plugin/grpc.go @@ -21,6 +21,7 @@ package plugin import ( "context" + "errors" "fmt" "sync" @@ -347,7 +348,7 @@ func (p *PrompterGRPCClient) Run(prompt promptui.Prompt) (string, error) { return "", err } if res.Error != nil && res.Error.Happened { - return "", fmt.Errorf(res.Error.Message) + return "", errors.New(res.Error.Message) } return res.Result, nil } diff --git a/pkg/plugin/sdk/v1alpha4/plugin/grpc.go b/pkg/plugin/sdk/v1alpha4/plugin/grpc.go index 497114052..ed2b9a997 100644 --- a/pkg/plugin/sdk/v1alpha4/plugin/grpc.go +++ b/pkg/plugin/sdk/v1alpha4/plugin/grpc.go @@ -21,6 +21,7 @@ package plugin import ( "context" + "errors" "fmt" "sync" @@ -348,7 +349,7 @@ func (p *PrompterGRPCClient) Run(prompt promptui.Prompt) (string, error) { return "", err } if res.Error != nil && res.Error.Happened { - return "", fmt.Errorf(res.Error.Message) + return "", errors.New(res.Error.Message) } return res.Result, nil }