github.com/distbuild/reclient@v0.0.0-20240401075343-3de72e395564/cmd/rewrapper/main.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 // Main package for the rewrapper binary. 16 // 17 // The rewrapper is used as a prefix to a build/test command that should be remoted to RBE. The 18 // rewrapper works as follows: 19 // 20 // rewrapper --labels=type=compile,lang=cpp --command_id=12345 --exec_strategy=remote -- \ 21 // 22 // clang -c test.c -o test.o 23 // 24 // In the above example, the clang command is packaged as a request to a long running proxy server 25 // which receives the command and returns its result. The result could be obtained via remote 26 // execution, local execution, or cache. 27 package main 28 29 import ( 30 "context" 31 "flag" 32 "fmt" 33 "os" 34 "path" 35 "path/filepath" 36 "strings" 37 "time" 38 39 "github.com/bazelbuild/reclient/internal/pkg/ipc" 40 "github.com/bazelbuild/reclient/internal/pkg/protoencoding" 41 "github.com/bazelbuild/reclient/internal/pkg/rbeflag" 42 "github.com/bazelbuild/reclient/internal/pkg/rewrapper" 43 "github.com/bazelbuild/reclient/pkg/version" 44 45 "github.com/bazelbuild/remote-apis-sdks/go/pkg/moreflag" 46 47 pb "github.com/bazelbuild/reclient/api/proxy" 48 49 log "github.com/golang/glog" 50 ) 51 52 var ( 53 cOpts = &rewrapper.CommandOptions{StartTime: time.Now()} 54 serverAddr = "127.0.0.1:8000" 55 dialTimeout *time.Duration 56 57 execStrategies = []string{"local", "remote", "remote_local_fallback", "racing"} 58 ) 59 60 func initFlags() { 61 flag.StringVar(&serverAddr, "server_address", "127.0.0.1:8000", "The server address in the format of host:port for network, or unix:///file for unix domain sockets.") 62 flag.StringVar(&cOpts.CommandID, "command_id", "", "An identifier for the command for use in future debugging") 63 flag.StringVar(&cOpts.InvocationID, "invocation_id", "", "An identifier for a group of commands for use in future debugging") 64 flag.StringVar(&cOpts.ToolName, "tool_name", "", "The name of the tool to associate with executed commands") 65 flag.Var((*moreflag.StringMapValue)(&cOpts.Labels), "labels", "Comma-separated key value pairs in the form key=value. This is used to identify the type of command to help the proxy make decisions regarding remote execution. Defaults to type=tool.") 66 flag.StringVar(&cOpts.ExecRoot, "exec_root", "", "The exec root of the command. The path from which all inputs and outputs are defined relatively. Defaults to current working directory.") 67 flag.DurationVar(&cOpts.ExecTimeout, "exec_timeout", time.Hour, "Timeout for the command on RBE. Default is 1 hour.") 68 flag.DurationVar(&cOpts.ReclientTimeout, "reclient_timeout", time.Hour, "Timeout for remotely executed actions to wait for a response from RBE. Default is 1 hour.") 69 flag.Var((*moreflag.StringMapValue)(&cOpts.Platform), "platform", "Comma-separated key value pairs in the form key=value. This is used to identify remote platform settings like the docker image to use to run the command.") 70 flag.Var((*moreflag.StringListValue)(&cOpts.EnvVarAllowlist), "env_var_allowlist", "List of environment variables allowed to pass to the proxy.") 71 flag.Var((*moreflag.StringListValue)(&cOpts.Inputs), "inputs", "Comma-separated command input paths, relative to exec root. Each path may be either a file or a directory.") 72 flag.Var((*moreflag.StringListValue)(&cOpts.ToolchainInputs), "toolchain_inputs", "Comma-separated command toolchain inputs relative to the exec root, which are paths to binaries needed to execute the action. Each binary can have a <binary>_remote_toolchain_inputs file next to it to refer to all dependencies of the toolchain binary. Paths in the <binary>_remote_toolchain_inputs file should be normalized.") 73 flag.Var((*moreflag.StringListValue)(&cOpts.InputListPaths), "input_list_paths", "Comma-separated paths to files containing lists of inputs (rsp files). Used when inputs are too long to add to the command line. Paths contained in this file should be relative to the exec_root.") 74 flag.Var((*moreflag.StringListValue)(&cOpts.OutputListPaths), "output_list_paths", "Comma-separated paths to files containing lists of outputs (rsp files). Used when outputs are too long to add to the command line. Paths contained in this file should be relative to the exec_root.") 75 flag.Var((*moreflag.StringListValue)(&cOpts.OutputFiles), "output_files", "Comma-separated command output file paths, relative to exec root.") 76 flag.Var((*moreflag.StringListValue)(&cOpts.OutputDirectories), "output_directories", "Comma-separated command output directory paths, relative to exec root.") 77 flag.StringVar(&cOpts.ExecStrategy, "exec_strategy", "remote", fmt.Sprintf("one of %s. Defaults to remote.", execStrategies)) 78 flag.BoolVar(&cOpts.Compare, "compare", false, "Boolean indicating whether to compare chosen exec strategy with local execution. Default is false.") 79 flag.IntVar(&cOpts.NumRetriesIfMismatched, "num_retries_if_mismatched", 0, "Deprecated: Number of times the action should be remotely executed to identify determinism. Used only when compare is set to true.") 80 flag.IntVar(&cOpts.NumLocalReruns, "num_local_reruns", 0, "Number of times the action should be rerun locally.") 81 flag.IntVar(&cOpts.NumRemoteReruns, "num_remote_reruns", 0, "Number of times the action should be rerun remotely.") 82 flag.BoolVar(&cOpts.RemoteAcceptCache, "remote_accept_cache", true, "Boolean indicating whether to accept remote cache hits. Default is true.") 83 flag.BoolVar(&cOpts.RemoteUpdateCache, "remote_update_cache", true, "Boolean indicating whether to cache the command result remotely. Default is true.") 84 flag.BoolVar(&cOpts.DownloadOutputs, "download_outputs", true, "Boolean indicating whether to download outputs after the command is executed. Default is true.") 85 flag.BoolVar(&cOpts.EnableAtomicDownloads, "enable_atomic_downloads", true, "Boolean indicating whether to download outputs atomically in remote/remote_local_fallback modes. Default is true.") 86 flag.BoolVar(&cOpts.LogEnvironment, "log_env", false, "Boolean indicating whether to pass the entire environment of the rewrapper to the reproxy for logging. Default is false.") 87 flag.BoolVar(&cOpts.PreserveUnchangedOutputMtime, "preserve_unchanged_output_mtime", false, "Boolean indicating whether or not to preserve mtimes of unchanged outputs when they are downloaded. Default is false.") 88 flag.StringVar(&cOpts.LocalWrapper, "local_wrapper", "", "Wrapper path to execute locally only. Relative to the current working directory of rewrapper.") 89 flag.StringVar(&cOpts.RemoteWrapper, "remote_wrapper", "", "Wrapper path to execute on remote worker. Relative to the current working directory of rewrapper.") 90 dialTimeout = flag.Duration("dial_timeout", 3*time.Minute, "Timeout for dialing reproxy. Default is 3 minutes.") 91 flag.BoolVar(&cOpts.PreserveSymlink, "preserve_symlink", false, "Boolean indicating whether to preserve symlinks in input tree. Default is false.") 92 flag.BoolVar(&cOpts.CanonicalizeWorkingDir, "canonicalize_working_dir", false, "Replaces local working directory with a canonical value when running on RE server. The feature makes actions working-dir agnostic and enables to cache them across various same depth (e.g. out/default and out/rbe-build) local working directories (default: false)") 93 flag.StringVar(&cOpts.ActionLog, "action_log", "", "If set, write a reproxy log entry for this remote action to the named file.") 94 } 95 96 func execStrategyValid() bool { 97 for _, s := range execStrategies { 98 if cOpts.ExecStrategy == s { 99 return true 100 } 101 } 102 return false 103 } 104 105 func main() { 106 initFlags() 107 version.PrintAndExitOnVersionFlag(false) 108 flag.Usage = func() { 109 fmt.Fprintf(flag.CommandLine.Output(), "Usage: %v [-flags] -- command ...\n", path.Base(os.Args[0])) 110 flag.PrintDefaults() 111 } 112 rbeflag.Parse() 113 rbeflag.LogAllFlags(1) 114 cmd := flag.Args() 115 if len(cmd) == 0 { 116 flag.Usage() 117 log.Exitf("No command provided") 118 } 119 if !execStrategyValid() { 120 flag.Usage() 121 log.Exitf("No exec_strategy provided, must be one of %v", execStrategies) 122 } 123 if serverAddr == "" { 124 log.Exitf("Invalid server address (%q), must be non empty.", serverAddr) 125 } 126 127 ctx := context.Background() 128 conn, err := ipc.DialContext(ctx, serverAddr) 129 if err != nil { 130 log.Exitf("Fail to dial %s: %v", serverAddr, err) 131 } 132 defer conn.Close() 133 134 proxy := pb.NewCommandsClient(conn) 135 wd, err := os.Getwd() 136 if err != nil { 137 log.Exitf("Failed to get current working directory: %v", err) 138 } 139 if wd == "/proc/self/cwd" { 140 wd, err = os.Readlink(wd) 141 if err != nil { 142 log.Exitf("Failed to get current true directory: %v", err) 143 } 144 } 145 if cOpts.ExecRoot == "" { 146 cOpts.ExecRoot = wd 147 } 148 if !filepath.IsAbs(cOpts.ExecRoot) { 149 cOpts.ExecRoot, err = filepath.Abs(cOpts.ExecRoot) 150 if err != nil { 151 log.Exitf("Failed to get abs path for exec_root: %v", err) 152 } 153 } 154 if len(cOpts.Labels) == 0 { 155 cOpts.Labels = map[string]string{"type": "tool"} 156 } 157 cOpts.WorkDir, err = filepath.Rel(cOpts.ExecRoot, wd) 158 if err != nil { 159 log.Exitf("Failed to compute working directory path relative to the exec root: %v", err) 160 } 161 if strings.HasPrefix(cOpts.WorkDir, "..") { 162 log.Exitf("Current working directory (%q) is not under the exec root (%q), relative working dir = %q", wd, cOpts.ExecRoot, cOpts.WorkDir) 163 } 164 165 if cOpts.NumRemoteReruns < 0 { 166 log.Exitf("Expected num_remote_reruns to be at least 0, got %v.", cOpts.NumRemoteReruns) 167 } 168 if cOpts.NumLocalReruns < 0 { 169 log.Exitf("Expected num_local_reruns to be at least 0, got %v.", cOpts.NumLocalReruns) 170 } 171 172 // TODO (b/296409009): Add support for preserve true and download outputs false for downloading stubs. 173 174 resp, err := rewrapper.RunCommand(ctx, *dialTimeout, proxy, cmd, cOpts) 175 if err != nil { 176 // Don't use log.Fatalf to avoid printing a stack trace. 177 log.Exitf("Command failed: %v", err) 178 } 179 if cOpts.ActionLog != "" && resp.ActionLog != nil { 180 if err := os.WriteFile(cOpts.ActionLog, []byte(protoencoding.TextWithIndent.Format(resp.ActionLog)), 0644); err != nil { 181 log.Errorf("Failed to write reproxy action log %v", cOpts.ActionLog) 182 } 183 } 184 os.Stdout.Write(resp.GetStdout()) 185 os.Stderr.Write(resp.GetStderr()) 186 log.Flush() 187 os.Exit(int(resp.GetResult().GetExitCode())) 188 }