// Copyright 2019 The Chromium OS Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. package main import ( "bytes" "fmt" "io" "os" "strings" ) type env interface { getenv(key string) (string, bool) environ() []string getwd() string stdin() io.Reader stdout() io.Writer stderr() io.Writer run(cmd *command, stdin io.Reader, stdout io.Writer, stderr io.Writer) error exec(cmd *command) error } type processEnv struct { wd string } func newProcessEnv() (env, error) { // Note: We are not using os.getwd() as this sometimes uses the value of the PWD // env variable. This has the following problems: // - if PWD=/proc/self/cwd, os.getwd() will return "/proc/self/cwd", // and we need to read the link to get the actual wd. However, we can't always // do this as we are calculating // the path to clang, and following a symlinked cwd first would make // this calculation invalid. // - the old python wrapper doesn't respect the PWD env variable either, so if we // did we would fail the comparison to the old wrapper. wd, err := os.Readlink("/proc/self/cwd") if err != nil { return nil, wrapErrorwithSourceLocf(err, "failed to read working directory") } return &processEnv{wd: wd}, nil } var _ env = (*processEnv)(nil) func (env *processEnv) getenv(key string) (string, bool) { return os.LookupEnv(key) } func (env *processEnv) environ() []string { return os.Environ() } func (env *processEnv) getwd() string { return env.wd } func (env *processEnv) stdin() io.Reader { return os.Stdin } func (env *processEnv) stdout() io.Writer { return os.Stdout } func (env *processEnv) stderr() io.Writer { return os.Stderr } func (env *processEnv) exec(cmd *command) error { return libcExec(env, cmd) } func (env *processEnv) run(cmd *command, stdin io.Reader, stdout io.Writer, stderr io.Writer) error { return runCmd(env, cmd, stdin, stdout, stderr) } type commandRecordingEnv struct { env stdinReader io.Reader cmdResults []*commandResult } type commandResult struct { Cmd *command `json:"cmd"` Stdout string `json:"stdout,omitempty"` Stderr string `json:"stderr,omitempty"` ExitCode int `json:"exitcode,omitempty"` } var _ env = (*commandRecordingEnv)(nil) func (env *commandRecordingEnv) stdin() io.Reader { return env.stdinReader } func (env *commandRecordingEnv) exec(cmd *command) error { // Note: We treat exec the same as run so that we can do work // after the call. return env.run(cmd, env.stdin(), env.stdout(), env.stderr()) } func (env *commandRecordingEnv) run(cmd *command, stdin io.Reader, stdout io.Writer, stderr io.Writer) error { stdoutBuffer := &bytes.Buffer{} stderrBuffer := &bytes.Buffer{} err := env.env.run(cmd, stdin, io.MultiWriter(stdout, stdoutBuffer), io.MultiWriter(stderr, stderrBuffer)) if exitCode, ok := getExitCode(err); ok { env.cmdResults = append(env.cmdResults, &commandResult{ Cmd: cmd, Stdout: stdoutBuffer.String(), Stderr: stderrBuffer.String(), ExitCode: exitCode, }) } return err } type printingEnv struct { env } var _env = (*printingEnv)(nil) func (env *printingEnv) exec(cmd *command) error { printCmd(env, cmd) return env.env.exec(cmd) } func (env *printingEnv) run(cmd *command, stdin io.Reader, stdout io.Writer, stderr io.Writer) error { printCmd(env, cmd) return env.env.run(cmd, stdin, stdout, stderr) } func printCmd(env env, cmd *command) { fmt.Fprintf(env.stderr(), "cd '%s' &&", env.getwd()) if len(cmd.EnvUpdates) > 0 { fmt.Fprintf(env.stderr(), " env '%s'", strings.Join(cmd.EnvUpdates, "' '")) } fmt.Fprintf(env.stderr(), " '%s'", getAbsCmdPath(env, cmd)) if len(cmd.Args) > 0 { fmt.Fprintf(env.stderr(), " '%s'", strings.Join(cmd.Args, "' '")) } io.WriteString(env.stderr(), "\n") }