| // Copyright 2015 Google Inc. All rights reserved. |
| // |
| // 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. |
| |
| // Choose which ninja file (stage) to run next |
| // |
| // In the common case, this program takes a list of ninja files, compares their |
| // mtimes against their $file.timestamp mtimes, and picks the last up to date |
| // ninja file to output. That stage is expected to rebuild the next file in the |
| // list and call this program again. If none of the ninja files are considered |
| // dirty, the last stage is output. |
| // |
| // One exception is if the current stage's ninja file was rewritten, it will be |
| // run again. |
| // |
| // Another exception is if the source bootstrap file has been updated more |
| // recently than the first stage, the source file will be copied to the first |
| // stage, and output. This would be expected with a new source drop via git. |
| // The timestamp of the first file is not updated so that it can be regenerated |
| // with any local changes. |
| |
| package main |
| |
| import ( |
| "bytes" |
| "flag" |
| "fmt" |
| "io/ioutil" |
| "os" |
| "path/filepath" |
| ) |
| |
| var ( |
| outputFile string |
| currentFile string |
| bootstrapFile string |
| verbose bool |
| ) |
| |
| func init() { |
| flag.StringVar(&outputFile, "o", "", "Output file") |
| flag.StringVar(¤tFile, "current", "", "Current stage's file") |
| flag.StringVar(&bootstrapFile, "bootstrap", "", "Bootstrap file checked into source") |
| flag.BoolVar(&verbose, "v", false, "Verbose mode") |
| } |
| |
| func compareFiles(a, b string) (bool, error) { |
| aData, err := ioutil.ReadFile(a) |
| if err != nil { |
| return false, err |
| } |
| |
| bData, err := ioutil.ReadFile(b) |
| if err != nil { |
| return false, err |
| } |
| |
| return bytes.Equal(aData, bData), nil |
| } |
| |
| // If the source bootstrap reference file is newer, then we may have gotten |
| // other source updates too. So we need to restart everything with the file |
| // that was checked in instead of the bootstrap that we last built. |
| func copyBootstrapIfNecessary(bootstrapFile, filename string) (bool, error) { |
| if bootstrapFile == "" { |
| return false, nil |
| } |
| |
| bootstrapStat, err := os.Stat(bootstrapFile) |
| if err != nil { |
| return false, err |
| } |
| |
| fileStat, err := os.Stat(filename) |
| if err != nil { |
| return false, err |
| } |
| |
| time := fileStat.ModTime() |
| if !bootstrapStat.ModTime().After(time) { |
| return false, nil |
| } |
| |
| fmt.Printf("Newer source version of %s. Copying to %s\n", filepath.Base(bootstrapFile), filepath.Base(filename)) |
| if verbose { |
| fmt.Printf("Source: %s\nBuilt: %s\n", bootstrapStat.ModTime(), time) |
| } |
| |
| data, err := ioutil.ReadFile(bootstrapFile) |
| if err != nil { |
| return false, err |
| } |
| |
| err = ioutil.WriteFile(filename, data, 0666) |
| if err != nil { |
| return false, err |
| } |
| |
| // Restore timestamp to force regeneration of the bootstrap.ninja.in |
| err = os.Chtimes(filename, time, time) |
| return true, err |
| } |
| |
| func main() { |
| flag.Parse() |
| |
| if flag.NArg() == 0 { |
| fmt.Fprintf(os.Stderr, "Must specify at least one ninja file\n") |
| os.Exit(1) |
| } |
| |
| if outputFile == "" { |
| fmt.Fprintf(os.Stderr, "Must specify an output file\n") |
| os.Exit(1) |
| } |
| |
| gotoFile := flag.Arg(0) |
| if copied, err := copyBootstrapIfNecessary(bootstrapFile, flag.Arg(0)); err != nil { |
| fmt.Fprintf(os.Stderr, "Failed to copy bootstrap ninja file: %s\n", err) |
| os.Exit(1) |
| } else if !copied { |
| for _, fileName := range flag.Args() { |
| timestampName := fileName + ".timestamp" |
| |
| // If we're currently running this stage, and the build.ninja.in |
| // file differs from the current stage file, then it has been rebuilt. |
| // Restart the stage. |
| if filepath.Clean(currentFile) == filepath.Clean(fileName) { |
| if _, err := os.Stat(outputFile); !os.IsNotExist(err) { |
| if ok, err := compareFiles(fileName, outputFile); err != nil { |
| fmt.Fprintf(os.Stderr, "Failure when comparing files: %s\n", err) |
| os.Exit(1) |
| } else if !ok { |
| fmt.Printf("Stage %s has changed, restarting\n", filepath.Base(fileName)) |
| gotoFile = fileName |
| break |
| } |
| } |
| } |
| |
| fileStat, err := os.Stat(fileName) |
| if err != nil { |
| // Regenerate this stage on error |
| break |
| } |
| |
| timestampStat, err := os.Stat(timestampName) |
| if err != nil { |
| // This file may not exist. There's no point for |
| // the first stage to have one, as it should be |
| // a subset of the second stage dependencies, |
| // and both will return to the first stage. |
| continue |
| } |
| |
| if verbose { |
| fmt.Printf("For %s:\n file: %s\n time: %s\n", fileName, fileStat.ModTime(), timestampStat.ModTime()) |
| } |
| |
| // If the timestamp file has a later modification time, that |
| // means that this stage needs to be regenerated. Break, so |
| // that we run the last found stage. |
| if timestampStat.ModTime().After(fileStat.ModTime()) { |
| break |
| } |
| |
| gotoFile = fileName |
| } |
| } |
| |
| fmt.Printf("Choosing %s for next stage\n", filepath.Base(gotoFile)) |
| |
| data, err := ioutil.ReadFile(gotoFile) |
| if err != nil { |
| fmt.Fprintf(os.Stderr, "Can't read file: %s", err) |
| os.Exit(1) |
| } |
| |
| err = ioutil.WriteFile(outputFile, data, 0666) |
| if err != nil { |
| fmt.Fprintf(os.Stderr, "Can't write file: %s", err) |
| os.Exit(1) |
| } |
| } |