github.com/distbuild/reclient@v0.0.0-20240401075343-3de72e395564/internal/pkg/inputprocessor/toolchain/toolchain.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 toolchain is responsible for determining the toolchain inputs for a specific command. 16 package toolchain 17 18 import ( 19 "context" 20 "fmt" 21 "path/filepath" 22 "regexp" 23 "runtime" 24 "strings" 25 26 "github.com/bazelbuild/reclient/internal/pkg/pathtranslator" 27 28 "github.com/bazelbuild/remote-apis-sdks/go/pkg/cache" 29 "github.com/bazelbuild/remote-apis-sdks/go/pkg/command" 30 "github.com/bazelbuild/remote-apis-sdks/go/pkg/filemetadata" 31 log "github.com/golang/glog" 32 ) 33 34 var ( 35 // defaultPath is the default content of the PATH variable on the bot. 36 // TODO(b/149753814): make this configurable on a per action basis. 37 defaultPath = []string{"/usr/local/sbin", "/usr/local/bin", "/usr/sbin", "/usr/bin", "/sbin", "/bin"} 38 39 // metalavaRe is a regular expression to find the version number of metalava. 40 metalavaRe = regexp.MustCompile(`^[\w\s]+:\s*(.+)`) 41 42 toolchainBinCache cache.SingleFlight 43 ) 44 45 // InputProcessor determines the toolchain inputs of a command. 46 type InputProcessor struct{} 47 48 // ProcessToolchainInputs returns the toolchain inputs of the given executable path, 49 // and toolchains inputs. 50 func (p *InputProcessor) ProcessToolchainInputs(ctx context.Context, execRoot, workingDir, execPath string, toolchains []string, fmc filemetadata.Cache) (*command.InputSpec, error) { 51 execPath = pathtranslator.RelToExecRoot(execRoot, workingDir, execPath) 52 inp, err := p.processToolchainInputFiles(execRoot, append(toolchains, execPath), fmc) 53 if err != nil { 54 return nil, err 55 } 56 // Process the execPath separately since it is expected to be accessible to the user without 57 // PATH manipulations. This is in contrast with toolchains that could require explicit addition 58 // to the PATH variable to be usable remotely. 59 if err := markExtantFileAsExec(filepath.Join(execRoot, execPath), fmc); err != nil { 60 log.Errorf("Failed to store %v as an executable in file metadata cache: %v", execPath, err) 61 } 62 63 dirs := make([]string, 0) 64 dirMap := make(map[string]bool) 65 for _, tc := range toolchains { 66 fp := filepath.Join(execRoot, tc) 67 if err := markExtantFileAsExec(fp, fmc); err != nil { 68 log.Errorf("Failed to store %v as an executable in file metadata cache: %v", tc, err) 69 } 70 tcDir := filepath.Dir(tc) 71 tcDir = pathtranslator.RelToWorkingDir(execRoot, workingDir, tcDir) 72 if tcDir == "" { 73 return nil, fmt.Errorf("failed to make toolchain directory [%s] relative to the working directory [%s]: %w", tcDir, workingDir, err) 74 } 75 if _, ok := dirMap[tcDir]; !ok { 76 dirs = append(dirs, tcDir) 77 dirMap[tcDir] = true 78 } 79 } 80 if len(dirs) > 0 { 81 dirs = append(dirs, defaultPath...) 82 inp.EnvironmentVariables = map[string]string{"PATH": strings.Join(dirs, ":")} 83 } 84 return inp, nil 85 } 86 87 func markExtantFileAsExec(fp string, fmc filemetadata.Cache) error { 88 if fmc == nil { 89 return nil 90 } 91 if runtime.GOOS != "windows" { 92 return nil 93 } 94 if md := fmc.Get(fp); md.Err != nil { 95 return nil 96 } 97 _, err := toolchainBinCache.LoadOrStore(fp, func() (interface{}, error) { 98 md := fmc.Get(fp) 99 if md.IsExecutable { 100 return nil, nil 101 } 102 md.IsExecutable = true 103 return nil, fmc.Update(fp, md) 104 }) 105 return err 106 }