Home | History | Annotate | Download | only in choosestage
      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 
     32 package choosestage
     33 
     34 import (
     35 	"bytes"
     36 	"flag"
     37 	"fmt"
     38 	"io/ioutil"
     39 	"os"
     40 	"path/filepath"
     41 )
     42 
     43 var (
     44 	outputFile    string
     45 	currentFile   string
     46 	bootstrapFile string
     47 	verbose       bool
     48 )
     49 
     50 func init() {
     51 	flag.StringVar(&outputFile, "o", "", "Output file")
     52 	flag.StringVar(&currentFile, "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 
     57 func 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.
     74 func 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 
    114 func 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