github.phpd.cn/thought-machine/please@v12.2.0+incompatible/src/build/build_step.go (about)

     1  // Package build houses the core functionality for actually building targets.
     2  package build
     3  
     4  import (
     5  	"bytes"
     6  	"crypto/sha1"
     7  	"encoding/hex"
     8  	"fmt"
     9  	"io"
    10  	"net/http"
    11  	"os"
    12  	"path"
    13  	"runtime"
    14  	"strconv"
    15  	"strings"
    16  	"sync"
    17  	"time"
    18  
    19  	"github.com/hashicorp/go-multierror"
    20  	"gopkg.in/op/go-logging.v1"
    21  
    22  	"core"
    23  	"fs"
    24  	"metrics"
    25  )
    26  
    27  var log = logging.MustGetLogger("build")
    28  
    29  // Type that indicates that we're stopping the build of a target in a nonfatal way.
    30  var errStop = fmt.Errorf("stopping build")
    31  
    32  // goDirOnce guards the creation of plz-out/go, which we only attempt once per process.
    33  var goDirOnce sync.Once
    34  
    35  // httpClient is the shared http client that we use for fetching remote files.
    36  var httpClient http.Client
    37  
    38  // Build implements the core logic for building a single target.
    39  func Build(tid int, state *core.BuildState, label core.BuildLabel) {
    40  	start := time.Now()
    41  	target := state.Graph.TargetOrDie(label)
    42  	state = state.ForTarget(target)
    43  	target.SetState(core.Building)
    44  	if err := buildTarget(tid, state, target); err != nil {
    45  		if err == errStop {
    46  			target.SetState(core.Stopped)
    47  			state.LogBuildResult(tid, target.Label, core.TargetBuildStopped, "Build stopped")
    48  			return
    49  		}
    50  		state.LogBuildError(tid, label, core.TargetBuildFailed, err, "Build failed: %s", err)
    51  		if err := RemoveOutputs(target); err != nil {
    52  			log.Errorf("Failed to remove outputs for %s: %s", target.Label, err)
    53  		}
    54  		target.SetState(core.Failed)
    55  		return
    56  	}
    57  	metrics.Record(target, time.Since(start))
    58  
    59  	// Add any of the reverse deps that are now fully built to the queue.
    60  	for _, reverseDep := range state.Graph.ReverseDependencies(target) {
    61  		if reverseDep.State() == core.Active && state.Graph.AllDepsBuilt(reverseDep) && reverseDep.SyncUpdateState(core.Active, core.Pending) {
    62  			state.AddPendingBuild(reverseDep.Label, false)
    63  		}
    64  	}
    65  	if target.IsTest && state.NeedTests {
    66  		state.AddPendingTest(target.Label)
    67  	}
    68  	state.Parser.UndeferAnyParses(state, target)
    69  }
    70  
    71  // Builds a single target
    72  func buildTarget(tid int, state *core.BuildState, target *core.BuildTarget) (err error) {
    73  	defer func() {
    74  		if r := recover(); r != nil {
    75  			if e, ok := r.(error); ok {
    76  				err = e
    77  			} else {
    78  				err = fmt.Errorf("%s", r)
    79  			}
    80  		}
    81  	}()
    82  
    83  	if err := target.CheckDependencyVisibility(state); err != nil {
    84  		return err
    85  	}
    86  	// We can't do this check until build time, until then we don't know what all the outputs
    87  	// will be (eg. for filegroups that collect outputs of other rules).
    88  	if err := target.CheckDuplicateOutputs(); err != nil {
    89  		return err
    90  	}
    91  	// This must run before we can leave this function successfully by any path.
    92  	if target.PreBuildFunction != nil {
    93  		log.Debug("Running pre-build function for %s", target.Label)
    94  		if err := state.Parser.RunPreBuildFunction(tid, state, target); err != nil {
    95  			return err
    96  		}
    97  		log.Debug("Finished pre-build function for %s", target.Label)
    98  	}
    99  	state.LogBuildResult(tid, target.Label, core.TargetBuilding, "Preparing...")
   100  	var postBuildOutput string
   101  	if state.PrepareOnly && state.IsOriginalTarget(target.Label) {
   102  		if target.IsFilegroup {
   103  			return fmt.Errorf("Filegroup targets don't have temporary directories")
   104  		}
   105  		if err := prepareDirectories(target); err != nil {
   106  			return err
   107  		}
   108  		if err := prepareSources(state.Graph, target); err != nil {
   109  			return err
   110  		}
   111  		return errStop
   112  	}
   113  	if target.IsHashFilegroup {
   114  		updateHashFilegroupPaths(state, target)
   115  	}
   116  	if !needsBuilding(state, target, false) {
   117  		log.Debug("Not rebuilding %s, nothing's changed", target.Label)
   118  		if postBuildOutput, err = runPostBuildFunctionIfNeeded(tid, state, target, ""); err != nil {
   119  			log.Warning("Missing post-build output for %s; will rebuild.", target.Label)
   120  		} else {
   121  			// If a post-build function ran it may modify the rule definition. In that case we
   122  			// need to check again whether the rule needs building.
   123  			if target.PostBuildFunction == nil || !needsBuilding(state, target, true) {
   124  				if target.IsFilegroup {
   125  					// Small optimisation to ensure we don't need to rehash things unnecessarily.
   126  					copyFilegroupHashes(state, target)
   127  				}
   128  				target.SetState(core.Reused)
   129  				state.LogBuildResult(tid, target.Label, core.TargetCached, "Unchanged")
   130  				return nil // Nothing needs to be done.
   131  			}
   132  			log.Debug("Rebuilding %s after post-build function", target.Label)
   133  		}
   134  	}
   135  	oldOutputHash, outputHashErr := OutputHash(target)
   136  	if target.IsFilegroup {
   137  		log.Debug("Building %s...", target.Label)
   138  		if err := buildFilegroup(tid, state, target); err != nil {
   139  			return err
   140  		} else if newOutputHash, err := calculateAndCheckRuleHash(state, target); err != nil {
   141  			return err
   142  		} else if !bytes.Equal(newOutputHash, oldOutputHash) {
   143  			target.SetState(core.Built)
   144  			state.LogBuildResult(tid, target.Label, core.TargetBuilt, "Built")
   145  		} else {
   146  			target.SetState(core.Unchanged)
   147  			state.LogBuildResult(tid, target.Label, core.TargetCached, "Unchanged")
   148  		}
   149  		return nil
   150  	}
   151  	if err := prepareDirectories(target); err != nil {
   152  		return fmt.Errorf("Error preparing directories for %s: %s", target.Label, err)
   153  	}
   154  
   155  	// Similarly to the createInitPy special-casing, this is not very nice, but makes it
   156  	// rather easier to have a consistent GOPATH setup.
   157  	if target.HasLabel("go") {
   158  		goDirOnce.Do(createPlzOutGo)
   159  	}
   160  
   161  	retrieveArtifacts := func() bool {
   162  		state.LogBuildResult(tid, target.Label, core.TargetBuilding, "Checking cache...")
   163  		if _, retrieved := retrieveFromCache(state, target); retrieved {
   164  			log.Debug("Retrieved artifacts for %s from cache", target.Label)
   165  			checkLicences(state, target)
   166  			newOutputHash, err := calculateAndCheckRuleHash(state, target)
   167  			if err != nil { // Most likely hash verification failure
   168  				log.Warning("Error retrieving cached artifacts for %s: %s", target.Label, err)
   169  				RemoveOutputs(target)
   170  				return false
   171  			} else if outputHashErr != nil || !bytes.Equal(oldOutputHash, newOutputHash) {
   172  				target.SetState(core.Cached)
   173  				state.LogBuildResult(tid, target.Label, core.TargetCached, "Cached")
   174  			} else {
   175  				target.SetState(core.Unchanged)
   176  				state.LogBuildResult(tid, target.Label, core.TargetCached, "Cached (unchanged)")
   177  			}
   178  			return true // got from cache
   179  		}
   180  		return false
   181  	}
   182  	cacheKey := mustShortTargetHash(state, target)
   183  	if state.Cache != nil {
   184  		// Note that ordering here is quite sensitive since the post-build function can modify
   185  		// what we would retrieve from the cache.
   186  		if target.PostBuildFunction != nil {
   187  			log.Debug("Checking for post-build output file for %s in cache...", target.Label)
   188  			if state.Cache.RetrieveExtra(target, cacheKey, target.PostBuildOutputFileName()) {
   189  				if postBuildOutput, err = runPostBuildFunctionIfNeeded(tid, state, target, postBuildOutput); err != nil {
   190  					panic(err)
   191  				}
   192  				if retrieveArtifacts() {
   193  					return nil
   194  				}
   195  			}
   196  		} else if retrieveArtifacts() {
   197  			return nil
   198  		}
   199  	}
   200  	if err := target.CheckSecrets(); err != nil {
   201  		return err
   202  	}
   203  	if err := prepareSources(state.Graph, target); err != nil {
   204  		return fmt.Errorf("Error preparing sources for %s: %s", target.Label, err)
   205  	}
   206  
   207  	state.LogBuildResult(tid, target.Label, core.TargetBuilding, target.BuildingDescription)
   208  	out, err := buildMaybeRemotely(state, target, cacheKey)
   209  	if err != nil {
   210  		return err
   211  	}
   212  	if target.PostBuildFunction != nil {
   213  		out = bytes.TrimSpace(out)
   214  		if err := runPostBuildFunction(tid, state, target, string(out), postBuildOutput); err != nil {
   215  			return err
   216  		}
   217  		storePostBuildOutput(state, target, out)
   218  	}
   219  	checkLicences(state, target)
   220  	state.LogBuildResult(tid, target.Label, core.TargetBuilding, "Collecting outputs...")
   221  	extraOuts, outputsChanged, err := moveOutputs(state, target)
   222  	if err != nil {
   223  		return fmt.Errorf("Error moving outputs for target %s: %s", target.Label, err)
   224  	}
   225  	if _, err = calculateAndCheckRuleHash(state, target); err != nil {
   226  		return err
   227  	}
   228  	if outputsChanged {
   229  		target.SetState(core.Built)
   230  	} else {
   231  		target.SetState(core.Unchanged)
   232  	}
   233  	if state.Cache != nil {
   234  		state.LogBuildResult(tid, target.Label, core.TargetBuilding, "Storing...")
   235  		newCacheKey := mustShortTargetHash(state, target)
   236  		if target.PostBuildFunction != nil {
   237  			if !bytes.Equal(newCacheKey, cacheKey) {
   238  				// NB. Important this is stored with the earlier hash - if we calculate the hash
   239  				//     now, it might be different, and we could of course never retrieve it again.
   240  				state.Cache.StoreExtra(target, cacheKey, target.PostBuildOutputFileName())
   241  			} else {
   242  				extraOuts = append(extraOuts, target.PostBuildOutputFileName())
   243  			}
   244  		}
   245  		state.Cache.Store(target, newCacheKey, extraOuts...)
   246  	}
   247  	// Clean up the temporary directory once it's done.
   248  	if state.CleanWorkdirs {
   249  		if err := os.RemoveAll(target.TmpDir()); err != nil {
   250  			log.Warning("Failed to remove temporary directory for %s: %s", target.Label, err)
   251  		}
   252  	}
   253  	if outputsChanged {
   254  		state.LogBuildResult(tid, target.Label, core.TargetBuilt, "Built")
   255  	} else {
   256  		state.LogBuildResult(tid, target.Label, core.TargetBuilt, "Built (unchanged)")
   257  	}
   258  	return nil
   259  }
   260  
   261  // runBuildCommand runs the actual command to build a target.
   262  // On success it returns the stdout of the target, otherwise an error.
   263  func runBuildCommand(state *core.BuildState, target *core.BuildTarget, command string, inputHash []byte) ([]byte, error) {
   264  	if target.IsRemoteFile {
   265  		return nil, fetchRemoteFile(state, target)
   266  	}
   267  	env := core.StampedBuildEnvironment(state, target, false, inputHash)
   268  	log.Debug("Building target %s\nENVIRONMENT:\n%s\n%s", target.Label, env, command)
   269  	out, combined, err := core.ExecWithTimeoutShell(state, target, target.TmpDir(), env, target.BuildTimeout, state.Config.Build.Timeout, state.ShowAllOutput, command, target.Sandbox)
   270  	if err != nil {
   271  		if state.Verbosity >= 4 {
   272  			return nil, fmt.Errorf("Error building target %s: %s\nENVIRONMENT:\n%s\n%s\n%s",
   273  				target.Label, err, env, target.GetCommand(state), combined)
   274  		}
   275  		return nil, fmt.Errorf("Error building target %s: %s\n%s", target.Label, err, combined)
   276  	}
   277  	return out, nil
   278  }
   279  
   280  // Prepares the output directories for a target
   281  func prepareDirectories(target *core.BuildTarget) error {
   282  	if err := prepareDirectory(target.TmpDir(), true); err != nil {
   283  		return err
   284  	}
   285  	if err := prepareDirectory(target.OutDir(), false); err != nil {
   286  		return err
   287  	}
   288  	// Nicety for the build rules: create any directories that it's
   289  	// declared it'll create files in.
   290  	for _, out := range target.Outputs() {
   291  		if dir := path.Dir(out); dir != "." {
   292  			outPath := path.Join(target.TmpDir(), dir)
   293  			if !core.PathExists(outPath) {
   294  				if err := os.MkdirAll(outPath, core.DirPermissions); err != nil {
   295  					return err
   296  				}
   297  			}
   298  		}
   299  	}
   300  	return nil
   301  }
   302  
   303  func prepareDirectory(directory string, remove bool) error {
   304  	if remove && core.PathExists(directory) {
   305  		if err := os.RemoveAll(directory); err != nil {
   306  			return err
   307  		}
   308  	}
   309  	err := os.MkdirAll(directory, core.DirPermissions)
   310  	if err != nil && checkForStaleOutput(directory, err) {
   311  		err = os.MkdirAll(directory, core.DirPermissions)
   312  	}
   313  	return err
   314  }
   315  
   316  // Symlinks the source files of this rule into its temp directory.
   317  func prepareSources(graph *core.BuildGraph, target *core.BuildTarget) error {
   318  	for source := range core.IterSources(graph, target) {
   319  		if err := core.PrepareSourcePair(source); err != nil {
   320  			return err
   321  		}
   322  	}
   323  	return nil
   324  }
   325  
   326  func moveOutputs(state *core.BuildState, target *core.BuildTarget) ([]string, bool, error) {
   327  	// Before we write any outputs, we must remove the old hash file to avoid it being
   328  	// left in an inconsistent state.
   329  	if err := os.RemoveAll(ruleHashFileName(target)); err != nil {
   330  		return nil, true, err
   331  	}
   332  	changed := false
   333  	tmpDir := target.TmpDir()
   334  	outDir := target.OutDir()
   335  	for _, output := range target.Outputs() {
   336  		tmpOutput := path.Join(tmpDir, output)
   337  		realOutput := path.Join(outDir, output)
   338  		if !core.PathExists(tmpOutput) {
   339  			return nil, true, fmt.Errorf("Rule %s failed to create output %s", target.Label, tmpOutput)
   340  		}
   341  		outputChanged, err := moveOutput(target, tmpOutput, realOutput)
   342  		if err != nil {
   343  			return nil, true, err
   344  		}
   345  		changed = changed || outputChanged
   346  	}
   347  	if changed {
   348  		log.Debug("Outputs for %s have changed", target.Label)
   349  	} else {
   350  		log.Debug("Outputs for %s are unchanged", target.Label)
   351  	}
   352  	// Optional outputs get moved but don't contribute to the hash or for incrementality.
   353  	// Glob patterns are supported on these.
   354  	extraOuts := []string{}
   355  	for _, output := range fs.Glob(state.Config.Parse.BuildFileName, tmpDir, target.OptionalOutputs, nil, nil, true) {
   356  		log.Debug("Discovered optional output %s", output)
   357  		tmpOutput := path.Join(tmpDir, output)
   358  		realOutput := path.Join(outDir, output)
   359  		if _, err := moveOutput(target, tmpOutput, realOutput); err != nil {
   360  			return nil, changed, err
   361  		}
   362  		extraOuts = append(extraOuts, output)
   363  	}
   364  	return extraOuts, changed, nil
   365  }
   366  
   367  func moveOutput(target *core.BuildTarget, tmpOutput, realOutput string) (bool, error) {
   368  	// hash the file
   369  	newHash, err := pathHash(tmpOutput, false)
   370  	if err != nil {
   371  		return true, err
   372  	}
   373  	if fs.PathExists(realOutput) {
   374  		if oldHash, err := pathHash(realOutput, false); err != nil {
   375  			return true, err
   376  		} else if bytes.Equal(oldHash, newHash) {
   377  			// We already have the same file in the current location. Don't bother moving it.
   378  			log.Debug("Checking %s vs. %s, hashes match", tmpOutput, realOutput)
   379  			return false, nil
   380  		}
   381  		if err := os.RemoveAll(realOutput); err != nil {
   382  			return true, err
   383  		}
   384  	}
   385  	movePathHash(tmpOutput, realOutput, false)
   386  	// Check if we need a directory for this output.
   387  	dir := path.Dir(realOutput)
   388  	if !core.PathExists(dir) {
   389  		if err := os.MkdirAll(dir, core.DirPermissions); err != nil {
   390  			return true, err
   391  		}
   392  	}
   393  	// If the output file is in plz-out/tmp we can just move it to save time, otherwise we need
   394  	// to copy so we don't move files from other directories.
   395  	if strings.HasPrefix(tmpOutput, target.TmpDir()) {
   396  		if err := os.Rename(tmpOutput, realOutput); err != nil {
   397  			return true, err
   398  		}
   399  	} else {
   400  		if err := core.RecursiveCopyFile(tmpOutput, realOutput, target.OutMode(), false, false); err != nil {
   401  			return true, err
   402  		}
   403  	}
   404  	if target.IsBinary {
   405  		if err := os.Chmod(realOutput, target.OutMode()); err != nil {
   406  			return true, err
   407  		}
   408  	}
   409  	return true, nil
   410  }
   411  
   412  // RemoveOutputs removes all generated outputs for a rule.
   413  func RemoveOutputs(target *core.BuildTarget) error {
   414  	if err := os.Remove(ruleHashFileName(target)); err != nil && !os.IsNotExist(err) {
   415  		if checkForStaleOutput(ruleHashFileName(target), err) {
   416  			return RemoveOutputs(target) // try again
   417  		}
   418  		return err
   419  	}
   420  	for _, output := range target.Outputs() {
   421  		if err := os.RemoveAll(path.Join(target.OutDir(), output)); err != nil {
   422  			return err
   423  		}
   424  	}
   425  	return nil
   426  }
   427  
   428  // checkForStaleOutput removes any parents of a file that are files themselves.
   429  // This is a fix for a specific case where there are old file outputs in plz-out which
   430  // have the same name as part of a package path.
   431  // It returns true if something was removed.
   432  func checkForStaleOutput(filename string, err error) bool {
   433  	if perr, ok := err.(*os.PathError); ok && perr.Err.Error() == "not a directory" {
   434  		for dir := path.Dir(filename); dir != "." && dir != "/" && path.Base(dir) != "plz-out"; dir = path.Dir(filename) {
   435  			if fs.FileExists(dir) {
   436  				log.Warning("Removing %s which appears to be a stale output file", dir)
   437  				os.Remove(dir)
   438  				return true
   439  			}
   440  		}
   441  	}
   442  	return false
   443  }
   444  
   445  // calculateAndCheckRuleHash checks the output hash for a rule.
   446  func calculateAndCheckRuleHash(state *core.BuildState, target *core.BuildTarget) ([]byte, error) {
   447  	hash, err := OutputHash(target)
   448  	if err != nil {
   449  		return nil, err
   450  	}
   451  	if err = checkRuleHashes(target, hash); err != nil {
   452  		if state.NeedHashesOnly && (state.IsOriginalTarget(target.Label) || state.IsOriginalTarget(target.Label.Parent())) {
   453  			return nil, errStop
   454  		} else if state.VerifyHashes {
   455  			return nil, err
   456  		} else {
   457  			log.Warning("%s", err)
   458  		}
   459  	}
   460  	if err := writeRuleHashFile(state, target); err != nil {
   461  		return nil, fmt.Errorf("Attempting to create hash file: %s", err)
   462  	}
   463  	return hash, nil
   464  }
   465  
   466  // OutputHash calculates the hash of a target's outputs.
   467  func OutputHash(target *core.BuildTarget) ([]byte, error) {
   468  	h := sha1.New()
   469  	for _, output := range target.Outputs() {
   470  		// NB. Always force a recalculation of the output hashes here. Memoisation is not
   471  		//     useful because by definition we are rebuilding a target, and can actively hurt
   472  		//     in cases where we compare the retrieved cache artifacts with what was there before.
   473  		filename := path.Join(target.OutDir(), output)
   474  		h2, err := pathHash(filename, true)
   475  		if err != nil {
   476  			return nil, err
   477  		}
   478  		h.Write(h2)
   479  		// Record the name of the file too, but not if the rule has hash verification
   480  		// (because this will change the hashes, and the cases it fixes are relatively rare
   481  		// and generally involve things like hash_filegroup that doesn't have hashes set).
   482  		// TODO(pebers): Find some more elegant way of unifying this behaviour.
   483  		if len(target.Hashes) == 0 {
   484  			h.Write([]byte(filename))
   485  		}
   486  	}
   487  	return h.Sum(nil), nil
   488  }
   489  
   490  // mustOutputHash calculates the hash of a target's outputs. It panics on any errors.
   491  func mustOutputHash(target *core.BuildTarget) []byte {
   492  	hash, err := OutputHash(target)
   493  	if err != nil {
   494  		panic(err)
   495  	}
   496  	return hash
   497  }
   498  
   499  // Verify the hash of output files for a rule match the ones set on it.
   500  func checkRuleHashes(target *core.BuildTarget, hash []byte) error {
   501  	if len(target.Hashes) == 0 {
   502  		return nil // nothing to check
   503  	}
   504  	hashStr := hex.EncodeToString(hash)
   505  	for _, okHash := range target.Hashes {
   506  		// Hashes can have an arbitrary label prefix. Strip it off if present.
   507  		if index := strings.LastIndexByte(okHash, ':'); index != -1 {
   508  			okHash = strings.TrimSpace(okHash[index+1:])
   509  		}
   510  		if okHash == hashStr {
   511  			return nil
   512  		}
   513  	}
   514  	if len(target.Hashes) == 1 {
   515  		return fmt.Errorf("Bad output hash for rule %s: was %s but expected %s",
   516  			target.Label, hashStr, target.Hashes[0])
   517  	}
   518  	return fmt.Errorf("Bad output hash for rule %s: was %s but expected one of [%s]",
   519  		target.Label, hashStr, strings.Join(target.Hashes, ", "))
   520  }
   521  
   522  func retrieveFromCache(state *core.BuildState, target *core.BuildTarget) ([]byte, bool) {
   523  	hash := mustShortTargetHash(state, target)
   524  	return hash, state.Cache.Retrieve(target, hash)
   525  }
   526  
   527  // Runs the post-build function for a target if it's got one.
   528  func runPostBuildFunctionIfNeeded(tid int, state *core.BuildState, target *core.BuildTarget, prevOutput string) (string, error) {
   529  	if target.PostBuildFunction != nil {
   530  		out, err := loadPostBuildOutput(state, target)
   531  		if err != nil {
   532  			return "", err
   533  		}
   534  		return out, runPostBuildFunction(tid, state, target, out, prevOutput)
   535  	}
   536  	return "", nil
   537  }
   538  
   539  // Runs the post-build function for a target.
   540  // In some cases it may have already run; if so we compare the previous output and warn
   541  // if the two differ (they must be deterministic to ensure it's a pure function, since there
   542  // are a few different paths through here and we guarantee to only run them once).
   543  func runPostBuildFunction(tid int, state *core.BuildState, target *core.BuildTarget, output, prevOutput string) error {
   544  	if prevOutput != "" {
   545  		if output != prevOutput {
   546  			log.Warning("The build output for %s differs from what we got back from the cache earlier.\n"+
   547  				"This implies your target's output is nondeterministic; Please won't re-run the\n"+
   548  				"post-build function, which will *probably* be okay, but Please can't be sure.\n"+
   549  				"See https://github.com/thought-machine/please/issues/113 for more information.", target.Label)
   550  			log.Debug("Cached build output for %s: %s\n\nNew build output: %s", target.Label, prevOutput, output)
   551  		}
   552  		return nil
   553  	}
   554  	return state.Parser.RunPostBuildFunction(tid, state, target, output)
   555  }
   556  
   557  // checkLicences checks the licences for the target match what we've accepted / rejected in the config
   558  // and panics if they don't match.
   559  func checkLicences(state *core.BuildState, target *core.BuildTarget) {
   560  	for _, licence := range target.Licences {
   561  		for _, reject := range state.Config.Licences.Reject {
   562  			if strings.EqualFold(reject, licence) {
   563  				panic(fmt.Sprintf("Target %s is licensed %s, which is explicitly rejected for this repository", target.Label, licence))
   564  			}
   565  		}
   566  		for _, accept := range state.Config.Licences.Accept {
   567  			if strings.EqualFold(accept, licence) {
   568  				log.Info("Licence %s is accepted in this repository", licence)
   569  				return // Note licences are assumed to be an 'or', ie. any one of them can be accepted.
   570  			}
   571  		}
   572  	}
   573  	if len(target.Licences) > 0 && len(state.Config.Licences.Accept) > 0 {
   574  		panic(fmt.Sprintf("None of the licences for %s are accepted in this repository: %s", target.Label, strings.Join(target.Licences, ", ")))
   575  	}
   576  }
   577  
   578  // createPlzOutGo creates a directory plz-out/go that contains src / pkg links which
   579  // make it easier to set up one's GOPATH appropriately.
   580  func createPlzOutGo() {
   581  	dir := path.Join(core.RepoRoot, core.OutDir, "go")
   582  	genDir := path.Join(core.RepoRoot, core.GenDir)
   583  	srcDir := path.Join(dir, "src")
   584  	pkgDir := path.Join(dir, "pkg")
   585  	archDir := path.Join(pkgDir, runtime.GOOS+"_"+runtime.GOARCH)
   586  	if err := os.MkdirAll(pkgDir, core.DirPermissions); err != nil {
   587  		log.Warning("Failed to create %s: %s", pkgDir, err)
   588  		return
   589  	}
   590  	symlinkIfNotExists(genDir, srcDir)
   591  	symlinkIfNotExists(genDir, archDir)
   592  }
   593  
   594  // symlinkIfNotExists creates newDir as a link to oldDir if it doesn't already exist.
   595  func symlinkIfNotExists(oldDir, newDir string) {
   596  	if !core.PathExists(newDir) {
   597  		if err := os.Symlink(oldDir, newDir); err != nil && !os.IsExist(err) {
   598  			log.Warning("Failed to create %s: %s", newDir, err)
   599  		}
   600  	}
   601  }
   602  
   603  // fetchRemoteFile fetches a remote file from a URL.
   604  // This is a builtin for better efficiency and more control over the whole process.
   605  func fetchRemoteFile(state *core.BuildState, target *core.BuildTarget) error {
   606  	if err := prepareDirectory(target.OutDir(), false); err != nil {
   607  		return err
   608  	} else if err := prepareDirectory(target.TmpDir(), false); err != nil {
   609  		return err
   610  	} else if err := os.RemoveAll(ruleHashFileName(target)); err != nil {
   611  		return err
   612  	}
   613  	httpClient.Timeout = time.Duration(state.Config.Build.Timeout) // Can't set this when we init the client because config isn't loaded then.
   614  	var err error
   615  	for _, src := range target.Sources {
   616  		if e := fetchOneRemoteFile(state, target, string(src.(core.URLLabel))); e != nil {
   617  			err = multierror.Append(err, e)
   618  		} else {
   619  			return nil
   620  		}
   621  	}
   622  	return err
   623  }
   624  
   625  func fetchOneRemoteFile(state *core.BuildState, target *core.BuildTarget, url string) error {
   626  	env := core.BuildEnvironment(state, target, false)
   627  	url = os.Expand(url, env.ReplaceEnvironment)
   628  	tmpPath := path.Join(target.TmpDir(), target.Outputs()[0])
   629  	f, err := os.Create(tmpPath)
   630  	if err != nil {
   631  		return err
   632  	}
   633  	resp, err := httpClient.Get(url)
   634  	if err != nil {
   635  		return err
   636  	}
   637  	defer resp.Body.Close()
   638  	if resp.StatusCode < 200 || resp.StatusCode > 299 {
   639  		return fmt.Errorf("Error retrieving %s: %s", url, resp.Status)
   640  	}
   641  	var r io.Reader = resp.Body
   642  	if length := resp.Header.Get("Content-Length"); length != "" {
   643  		if i, err := strconv.Atoi(length); err == nil {
   644  			r = &progressReader{Reader: resp.Body, Target: target, Total: float32(i)}
   645  		}
   646  	}
   647  	target.ShowProgress = true // Required for it to actually display
   648  	h := sha1.New()
   649  	if _, err := io.Copy(io.MultiWriter(f, h), r); err != nil {
   650  		return err
   651  	}
   652  	setPathHash(tmpPath, h.Sum(nil))
   653  	return f.Close()
   654  }
   655  
   656  // A progressReader tracks progress from a HTTP response and marks it on the given target.
   657  type progressReader struct {
   658  	Reader      io.Reader
   659  	Target      *core.BuildTarget
   660  	Done, Total float32
   661  }
   662  
   663  // Read implements the io.Reader interface
   664  func (r *progressReader) Read(b []byte) (int, error) {
   665  	n, err := r.Reader.Read(b)
   666  	r.Done += float32(n)
   667  	r.Target.Progress = 100.0 * r.Done / r.Total
   668  	return n, err
   669  }