1// Copyright 2015 Google Inc. All rights reserved. 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// Choose which ninja file (stage) to run next 16// 17// In the common case, this program takes a list of ninja files, compares their 18// mtimes against their $file.timestamp mtimes, and picks the last up to date 19// ninja file to output. That stage is expected to rebuild the next file in the 20// list and call this program again. If none of the ninja files are considered 21// dirty, the last stage is output. 22// 23// One exception is if the current stage's ninja file was rewritten, it will be 24// run again. 25// 26// Another exception is if the source bootstrap file has been updated more 27// recently than the first stage, the source file will be copied to the first 28// stage, and output. This would be expected with a new source drop via git. 29// The timestamp of the first file is not updated so that it can be regenerated 30// with any local changes. 31 32package choosestage 33 34import ( 35 "bytes" 36 "flag" 37 "fmt" 38 "io/ioutil" 39 "os" 40 "path/filepath" 41) 42 43var ( 44 outputFile string 45 currentFile string 46 bootstrapFile string 47 verbose bool 48) 49 50func init() { 51 flag.StringVar(&outputFile, "o", "", "Output file") 52 flag.StringVar(¤tFile, "current", "", "Current stage's file") 53 flag.StringVar(&bootstrapFile, "bootstrap", "", "Bootstrap file checked into source") 54 flag.BoolVar(&verbose, "v", false, "Verbose mode") 55} 56 57func compareFiles(a, b string) (bool, error) { 58 aData, err := ioutil.ReadFile(a) 59 if err != nil { 60 return false, err 61 } 62 63 bData, err := ioutil.ReadFile(b) 64 if err != nil { 65 return false, err 66 } 67 68 return bytes.Equal(aData, bData), nil 69} 70 71// If the source bootstrap reference file is newer, then we may have gotten 72// other source updates too. So we need to restart everything with the file 73// that was checked in instead of the bootstrap that we last built. 74func copyBootstrapIfNecessary(bootstrapFile, filename string) (bool, error) { 75 if bootstrapFile == "" { 76 return false, nil 77 } 78 79 bootstrapStat, err := os.Stat(bootstrapFile) 80 if err != nil { 81 return false, err 82 } 83 84 fileStat, err := os.Stat(filename) 85 if err != nil { 86 return false, err 87 } 88 89 time := fileStat.ModTime() 90 if !bootstrapStat.ModTime().After(time) { 91 return false, nil 92 } 93 94 fmt.Printf("Newer source version of %s. Copying to %s\n", filepath.Base(bootstrapFile), filepath.Base(filename)) 95 if verbose { 96 fmt.Printf("Source: %s\nBuilt: %s\n", bootstrapStat.ModTime(), time) 97 } 98 99 data, err := ioutil.ReadFile(bootstrapFile) 100 if err != nil { 101 return false, err 102 } 103 104 err = ioutil.WriteFile(filename, data, 0666) 105 if err != nil { 106 return false, err 107 } 108 109 // Restore timestamp to force regeneration of the bootstrap.ninja.in 110 err = os.Chtimes(filename, time, time) 111 return true, err 112} 113 114func main() { 115 flag.Parse() 116 117 if flag.NArg() == 0 { 118 fmt.Fprintf(os.Stderr, "Must specify at least one ninja file\n") 119 os.Exit(1) 120 } 121 122 if outputFile == "" { 123 fmt.Fprintf(os.Stderr, "Must specify an output file\n") 124 os.Exit(1) 125 } 126 127 gotoFile := flag.Arg(0) 128 if copied, err := copyBootstrapIfNecessary(bootstrapFile, flag.Arg(0)); err != nil { 129 fmt.Fprintf(os.Stderr, "Failed to copy bootstrap ninja file: %s\n", err) 130 os.Exit(1) 131 } else if !copied { 132 for _, fileName := range flag.Args() { 133 timestampName := fileName + ".timestamp" 134 135 // If we're currently running this stage, and the build.ninja.in 136 // file differs from the current stage file, then it has been rebuilt. 137 // Restart the stage. 138 if filepath.Clean(currentFile) == filepath.Clean(fileName) { 139 if _, err := os.Stat(outputFile); !os.IsNotExist(err) { 140 if ok, err := compareFiles(fileName, outputFile); err != nil { 141 fmt.Fprintf(os.Stderr, "Failure when comparing files: %s\n", err) 142 os.Exit(1) 143 } else if !ok { 144 fmt.Printf("Stage %s has changed, restarting\n", filepath.Base(fileName)) 145 gotoFile = fileName 146 break 147 } 148 } 149 } 150 151 fileStat, err := os.Stat(fileName) 152 if err != nil { 153 // Regenerate this stage on error 154 break 155 } 156 157 timestampStat, err := os.Stat(timestampName) 158 if err != nil { 159 // This file may not exist. There's no point for 160 // the first stage to have one, as it should be 161 // a subset of the second stage dependencies, 162 // and both will return to the first stage. 163 continue 164 } 165 166 if verbose { 167 fmt.Printf("For %s:\n file: %s\n time: %s\n", fileName, fileStat.ModTime(), timestampStat.ModTime()) 168 } 169 170 // If the timestamp file has a later modification time, that 171 // means that this stage needs to be regenerated. Break, so 172 // that we run the last found stage. 173 if timestampStat.ModTime().After(fileStat.ModTime()) { 174 break 175 } 176 177 gotoFile = fileName 178 } 179 } 180 181 fmt.Printf("Choosing %s for next stage\n", filepath.Base(gotoFile)) 182 183 data, err := ioutil.ReadFile(gotoFile) 184 if err != nil { 185 fmt.Fprintf(os.Stderr, "Can't read file: %s", err) 186 os.Exit(1) 187 } 188 189 err = ioutil.WriteFile(outputFile, data, 0666) 190 if err != nil { 191 fmt.Fprintf(os.Stderr, "Can't write file: %s", err) 192 os.Exit(1) 193 } 194} 195