| // Copyright 2021 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| package codifier |
| |
| import ( |
| "errors" |
| "fmt" |
| "io/fs" |
| "log" |
| "os" |
| "path/filepath" |
| "regexp" |
| "strings" |
| ) |
| |
| const ( |
| groupTestsTemplate = ` |
| |
| group("tests") { |
| testonly = true |
| deps = [ "%s" ] |
| } |
| |
| ` |
| groupTestsFileHeader = ` |
| # Copyright 2021 The Fuchsia Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| import("//build/test.gni") |
| |
| ` |
| ) |
| |
| // Do not add tests to these directories or above. |
| var defaultStopList = []string{"//zircon/system", "//zircon/third_party"} |
| |
| // AddTestsGroup finds a group("tests") clause and adds the given testTarget to |
| // its dependencies. |
| func (p *Proc) AddTestsGroup(testTarget string) *Proc { |
| if p == nil { |
| log.Println(" AddTestsGroup() error: nil receiver") |
| return nil |
| } |
| log.Printf("AddTestsGroup(): checking %q is in test group for file %q", testTarget, p.filename) |
| |
| // Look for sections like group("tests"). |
| groupTestsScope := p.Scope("group", "tests") |
| if groupTestsScope == nil { |
| // No scope found, so no child added to p, so no ApplyChanges() needed. |
| log.Println(" did not find 'group(\"tests\")'; adding...") |
| p = p.Append(fmt.Sprintf(groupTestsTemplate, testTarget)) |
| p.changedFiles.Add(p.filename) |
| return p |
| } |
| |
| // Found a scope, adding a child, so ApplyChanges() is needed before exit. |
| p = groupTestsScope |
| p = p.ExtractList("deps") |
| if p == nil { |
| log.Print(" AddTestsGroup() error: couldn't find deps in tests group") |
| return nil |
| } |
| // Don't add if already present. |
| // TODO: The change to orderedStrings broke this check: ListContains() casts |
| // to orderedStrings, which fails because these are stored as just |
| // []strings. Same for the "tests:tests: check below. Fix. |
| if p.ListContains("deps", testTarget) { |
| log.Printf(" AddTestsGroup() error: %q already present in test group deps", testTarget) |
| return p |
| } |
| // There's a default where a dependency like "tests" will point to a |
| // directory named tests and a group named "tests". |
| if testTarget == "tests:tests" && p.ListContains("deps", "tests") { |
| return p |
| } |
| |
| // Insert the new dependency by inserting at the front of the list. The |
| // indentation may not match, but the gn format step will correct it. Gn |
| // format will also correct the alphabetical ordering of the dep list. |
| // TODO(gboone@): Consider replacing this line with a function that will do |
| // a line insertion with matching indentation. |
| |
| // Regular expression: the deps keywork, some space, an equals sign, some |
| // space an opening bracket. |
| re := regexp.MustCompile(`deps\s*=\s*\[`) |
| newStr := fmt.Sprintf("deps = [ \"%s\",\n", testTarget) |
| p.replacement = re.ReplaceAllString(p.replacement, newStr) |
| p.changedFiles.Add(p.filename) |
| p = p.ApplyChanges() |
| return p |
| } |
| |
| // AddParentTests ensures that the group("tests") in the current file is linked |
| // to parent tests in all of the parent build files. |
| func (p *Proc) AddParentTests(buildFile string) *Proc { |
| if p == nil { |
| log.Println(" AddParentTests() error: nil receiver") |
| return nil |
| } |
| log.Printf(" AddParentTests() checking group tests in %q", buildFile) |
| |
| dirParts := strings.Split(buildFile, string(os.PathSeparator)) |
| if len(dirParts) <= 2 { |
| return p |
| } |
| stopDirs, err := stopDirs(p.fuchsiaDir) |
| if err != nil { |
| return nil |
| } |
| |
| // The last path item is the BUILD.gn file, so start one up from that. The |
| // last is len - 1, so that would be len - 2. |
| for i := len(dirParts) - 2; i > 0; i-- { |
| dir := string(os.PathSeparator) + filepath.Join(filepath.Join(dirParts[:i]...)) |
| if stopDirs.contains(dir) { |
| break |
| } |
| filename := filepath.Join(dir, "BUILD.gn") |
| testToAdd := dirParts[i] + ":tests" |
| var q *Proc |
| if fileDoesNotExist(filename) { |
| q = NewProcFromString(groupTestsFileHeader).setFilename(filename) |
| } else { |
| q = NewProcFromFile(filename) |
| } |
| q = q.AddTestsGroup(testToAdd).WriteFile() |
| if q == nil { |
| return nil |
| } |
| p.changedFiles.Add(q.changedFiles...) |
| } |
| return p |
| } |
| |
| // Returns true if the given file does not exist. |
| func fileDoesNotExist(filename string) bool { |
| _, err := os.Stat(filename) |
| return errors.Is(err, fs.ErrNotExist) |
| } |
| |
| // Returns a list of directories that should stop path climbing. That is, if the |
| // stop directory is "b", then path traversals up "a/b/c/d/e/f", which would be |
| // "a/b/c/d/e/f", "a/b/c/d/e", "a/b/c/d" and so on, would not include "a/b". The |
| // stop directories include the given Fuchsia directory. All stop directories |
| // are fully resolved to their absolute filepaths. |
| func stopDirs(fuchsiaDir string) (orderedStrings, error) { |
| stopDir, err := resolveGnPath("", fuchsiaDir) |
| if err != nil { |
| log.Println(" stopDirs() error: couldn't resolve fuchsia base directory") |
| return nil, err |
| } |
| stopDir = strings.TrimSuffix(stopDir, string(os.PathSeparator)) |
| if stopDir == "" { |
| stopDir = "/" |
| } |
| stopDirs := []string{stopDir} |
| // Add the others. |
| for _, f := range defaultStopList { |
| d, err := resolveGnPath(fuchsiaDir, f) |
| if err != nil { |
| log.Printf(" stopDirs() error: couldn't resolve stop directory %q", d) |
| return nil, err |
| } |
| stopDirs = append(stopDirs, d) |
| } |
| return stopDirs, nil |
| } |