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  }