/* Copyright 2016 Google LLC 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 https://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 differ determines how to invoke diff in the given environment. package differ import ( "fmt" "os" "os/exec" "runtime" "strings" ) // Invocation of different diff commands, according to environment variables. // A Differ describes how to invoke diff. type Differ struct { Cmd string // command MultiDiff bool // diff accepts list of multiple pairs Args []string // accumulated arguments } // run runs the given command with args. func (d *Differ) run(command string, args ...string) error { // The special diff command ":" means don't run anything. if d.Cmd == ":" { return nil } // Pass args to bash and reference with $@ to avoid shell injection in args. var cmd *exec.Cmd if command == "FC" { cmd = exec.Command(command, "/T") } else { cmd = exec.Command("/usr/bin/env", "bash", "-c", command+` "$@"`, "--") } cmd.Args = append(cmd.Args, args...) cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr if err := cmd.Start(); err != nil { // Couldn't even start bash. Worth reporting. return fmt.Errorf("buildifier: %s: %v", command, err) } // Assume bash reported anything else worth reporting. // As long as the program started (above), we don't care about the // exact exit status. In the most common case, the diff command // will exit 1, because there are diffs, causing bash to exit 1. return cmd.Wait() } // Show diffs old and new. // For a single-pair diff program, Show runs the diff program before returning. // For a multi-pair diff program, Show records the pair for later use by Run. func (d *Differ) Show(old, new string) error { if !d.MultiDiff { return d.run(d.Cmd, old, new) } d.Args = append(d.Args, ":", old, new) return nil } // Run runs any pending diffs. // For a single-pair diff program, Show already ran diff; Run is a no-op. // For a multi-pair diff program, Run displays the diffs queued by Show. func (d *Differ) Run() error { if !d.MultiDiff { return nil } if len(d.Args) == 0 { return nil } return d.run(d.Cmd, d.Args...) } // Find returns the differ to use, using various environment variables. func Find() (*Differ, bool) { d := &Differ{} deprecationWarning := false if cmd := os.Getenv("BUILDIFIER_DIFF"); cmd != "" { deprecationWarning = true d.Cmd = cmd } // Load MultiDiff setting from environment. knowMultiDiff := false if md := os.Getenv("BUILDIFIER_MULTIDIFF"); md == "0" || md == "1" { deprecationWarning = true d.MultiDiff = md == "1" knowMultiDiff = true } if d.Cmd != "" { if !knowMultiDiff { lower := strings.ToLower(d.Cmd) d.MultiDiff = strings.Contains(lower, "tkdiff") && isatty(1) && os.Getenv("DISPLAY") != "" } } else { if !knowMultiDiff { d.MultiDiff = isatty(1) && os.Getenv("DISPLAY") != "" if d.MultiDiff { deprecationWarning = true } } if d.MultiDiff { d.Cmd = "tkdiff" } else { if runtime.GOOS == "windows" { deprecationWarning = true d.Cmd = "FC" } else { d.Cmd = "diff --unified" } } } return d, deprecationWarning }