github.com/distbuild/reclient@v0.0.0-20240401075343-3de72e395564/internal/pkg/inputprocessor/action/metalava/preprocessor.go (about) 1 // Copyright 2023 Google LLC 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package metalava 16 17 import ( 18 "fmt" 19 "path/filepath" 20 "regexp" 21 "strings" 22 23 "github.com/bazelbuild/reclient/internal/pkg/inputprocessor" 24 25 "github.com/bazelbuild/remote-apis-sdks/go/pkg/cache" 26 "github.com/bazelbuild/remote-apis-sdks/go/pkg/command" 27 ) 28 29 var ( 30 // metalavaRE is a regular expression to find the version number of metalava. 31 metalavaRE = regexp.MustCompile(`^[\w\s]+:\s*(.+)`) 32 versionCache = cache.SingleFlight{} 33 ) 34 35 // Preprocessor is the context for processing metalava actions. 36 type Preprocessor struct { 37 *inputprocessor.BasePreprocessor 38 } 39 40 // ParseFlags parses the commands flags and populates the ActionSpec object with inferred 41 // information. 42 func (p *Preprocessor) ParseFlags() error { 43 f, err := parseFlags(p.Ctx, p.Options.Cmd, p.Options.WorkingDir, p.Options.ExecRoot) 44 if err != nil { 45 p.Err = fmt.Errorf("flag parsing failed. %v", err) 46 return p.Err 47 } 48 p.Flags = f 49 p.FlagsToActionSpec() 50 return nil 51 } 52 53 // ComputeSpec computes ActionSpec for the options passed to the context. 54 func (p *Preprocessor) ComputeSpec() error { 55 err := p.verifyMetalavaVersion("1.3.0") 56 if err != nil { 57 p.Err = err 58 return err 59 } 60 return nil 61 } 62 63 // verifyMetalavaVersion checks the version of a binary using the --version flag. 64 func (p *Preprocessor) verifyMetalavaVersion(want string) error { 65 compute := func() (interface{}, error) { 66 if p.Executor == nil { 67 return "", fmt.Errorf("No executor passed to the toolchain input processor") 68 } 69 s, err := p.Spec() 70 if err != nil { 71 return "", err 72 } 73 stdout, _, err := p.Executor.Execute(p.Ctx, &command.Command{ 74 Args: []string{p.Flags.ExecutablePath, "--version"}, 75 WorkingDir: filepath.Join(p.Options.ExecRoot, p.Options.WorkingDir), 76 InputSpec: &command.InputSpec{ 77 EnvironmentVariables: s.InputSpec.EnvironmentVariables, 78 }, 79 }) 80 if err != nil { 81 return "", err 82 } 83 lines := strings.FieldsFunc(stdout, func(r rune) bool { 84 return r == '\n' || r == '\r' 85 }) 86 for _, l := range lines { 87 matches := metalavaRE.FindSubmatch([]byte(l)) 88 if len(matches) > 1 { 89 return string(matches[1]), nil 90 } 91 } 92 return "", fmt.Errorf("Version check of %v produced unexpected std out: %v", p.Flags.ExecutablePath, stdout) 93 } 94 version, err := versionCache.LoadOrStore(p.Flags.ExecutablePath, compute) 95 if err != nil { 96 return err 97 } 98 if v, ok := version.(string); !ok || v != want { 99 return fmt.Errorf("unexpected metalava version, want 1.3.0, got %v", version) 100 } 101 return nil 102 }