blob: 0625cfcfe4d8f18813a20b08ed9c88853fc655d5 [file] [log] [blame]
// Copyright 2024 The LUCI Authors.
//
// 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 host
import (
"os"
"os/exec"
"path/filepath"
"runtime"
"strings"
bbpb "go.chromium.org/luci/buildbucket/proto"
"go.chromium.org/luci/common/errors"
)
// ResolveExeCmd resolves the given host options and returns the command for
// luciexe host to invoke as a luciexe.
//
// This includes resolving paths relative to the current working directory, or
// the directory contains agent inputs if DownloadAgentInputs enabled.
func ResolveExeCmd(opts *Options, defaultPayloadPath string) ([]string, error) {
exeArgs := make([]string, 0, len(opts.BaseBuild.Exe.Wrapper)+len(opts.BaseBuild.Exe.Cmd)+1)
if len(opts.BaseBuild.Exe.Wrapper) != 0 {
exeArgs = append(exeArgs, opts.BaseBuild.Exe.Wrapper...)
exeArgs = append(exeArgs, "--")
if strings.Contains(exeArgs[0], "/") || strings.Contains(exeArgs[0], "\\") {
absPath, err := filepath.Abs(exeArgs[0])
if err != nil {
return nil, errors.Annotate(err, "absoluting wrapper path: %q", exeArgs[0]).Err()
}
exeArgs[0] = absPath
}
cmdPath, err := exec.LookPath(exeArgs[0])
if err != nil {
return nil, errors.Annotate(err, "wrapper not found: %q", exeArgs[0]).Err()
}
exeArgs[0] = cmdPath
}
exeCmd := opts.BaseBuild.Exe.Cmd[0]
payloadPath := defaultPayloadPath
for p, purpose := range opts.BaseBuild.GetInfra().GetBuildbucket().GetAgent().GetPurposes() {
if purpose == bbpb.BuildInfra_Buildbucket_Agent_PURPOSE_EXE_PAYLOAD {
payloadPath = p
break
}
}
if !filepath.IsAbs(payloadPath) && opts.DownloadAgentInputs {
payloadPath = filepath.Join(opts.agentInputsDir, payloadPath)
}
exePath, err := processCmd(payloadPath, exeCmd)
if err != nil {
return nil, err
}
exeArgs = append(exeArgs, exePath)
exeArgs = append(exeArgs, opts.BaseBuild.Exe.Cmd[1:]...)
return exeArgs, nil
}
func resolveExe(path string) (string, error) {
if filepath.Ext(path) != "" {
return path, nil
}
lme := errors.NewLazyMultiError(2)
for i, ext := range []string{".exe", ".bat"} {
candidate := path + ext
if _, err := os.Stat(candidate); !lme.Assign(i, err) {
return candidate, nil
}
}
me := lme.Get().(errors.MultiError)
return path, errors.Reason("cannot find .exe (%q) or .bat (%q)", me[0], me[1]).Err()
}
// processCmd resolves the cmd by constructing the absolute path and resolving
// the exe suffix.
func processCmd(path, cmd string) (string, error) {
relPath := filepath.Join(path, cmd)
absPath, err := filepath.Abs(relPath)
if err != nil {
return "", errors.Annotate(err, "absoluting %q", relPath).Err()
}
if runtime.GOOS == "windows" {
absPath, err = resolveExe(absPath)
if err != nil {
return "", errors.Annotate(err, "resolving %q", absPath).Err()
}
}
return absPath, nil
}