diff --git a/src/build/build_step.go b/src/build/build_step.go index b2c8e4c05..1468dc1c8 100644 --- a/src/build/build_step.go +++ b/src/build/build_step.go @@ -185,6 +185,17 @@ func buildTarget(state *core.BuildState, target *core.BuildTarget, runRemotely b return err } log.Debug("Finished pre-build function for %s", target.Label) + + // Wait for any new dependencies added by pre-build commands before continuing. + for _, dep := range target.Dependencies() { + dep.WaitForBuild(target.Label) + if dep.State() >= core.DependencyFailed { // Either the target failed or its dependencies failed + // Give up and set the original target as dependency failed + target.SetState(core.DependencyFailed) + return fmt.Errorf("error in pre-rule dependency for %s: %s", target.Label, dep.Label) + } + } + log.Debug("Finished waiting for dependencies for %s", target.Label) } state.LogBuildResult(target, core.TargetBuilding, "Preparing...") @@ -343,6 +354,16 @@ func buildTarget(state *core.BuildState, target *core.BuildTarget, runRemotely b return err } + // Wait for any new dependencies added by post-build commands before continuing. + for _, dep := range target.Dependencies() { + dep.WaitForBuild(target.Label) + if dep.State() >= core.DependencyFailed { // Either the target failed or its dependencies failed + // Give up and set the original target as dependency failed + target.SetState(core.DependencyFailed) + return fmt.Errorf("error in post-rule dependency for %s: %s", target.Label, dep.Label) + } + } + if runRemotely && len(outs) != len(target.Outputs()) { // postBuildFunction has changed the target - must rebuild it log.Info("Rebuilding %s after post-build function", target) diff --git a/src/core/build_input.go b/src/core/build_input.go index 010e047b0..d066338fc 100644 --- a/src/core/build_input.go +++ b/src/core/build_input.go @@ -220,6 +220,51 @@ func (label SystemPathLabel) String() string { return label.Name } +// SingleOutputLabel represents a build label for a specific output of a rule. +// This can be used to target a specific output of this rule when depended on or an entry point when used in +// the context of tools. +type SingleOutputLabel struct { + BuildLabel + Output string +} + +// MarshalText implements the encoding.TextMarshaler interface, which makes AnnotatedOutputLabel +// usable as map keys in JSON. +func (label SingleOutputLabel) MarshalText() ([]byte, error) { + return []byte(label.String()), nil +} + +// Paths returns a slice of paths to the files of this input. +func (label SingleOutputLabel) Paths(graph *BuildGraph) []string { + target := graph.TargetOrDie(label.BuildLabel) + return addPathPrefix([]string{label.Output}, target.PackageDir()) +} + +// FullPaths is like Paths but includes the leading plz-out/gen directory. +func (label SingleOutputLabel) FullPaths(graph *BuildGraph) []string { + target := graph.TargetOrDie(label.BuildLabel) + return addPathPrefix([]string{label.Output}, target.OutDir()) +} + +// LocalPaths returns paths within the local package +func (label SingleOutputLabel) LocalPaths(graph *BuildGraph) []string { + return []string{label.Output} +} + +// Label returns the build rule associated with this input. For a SingleOutputLabel it's always non-nil. +func (label SingleOutputLabel) Label() (BuildLabel, bool) { + return label.BuildLabel, true +} + +func (label SingleOutputLabel) nonOutputLabel() (BuildLabel, bool) { + return BuildLabel{}, false +} + +// String returns a string representation of this input. +func (label SingleOutputLabel) String() string { + return label.BuildLabel.String() + "+" + label.Output +} + // AnnotatedOutputLabel represents a build label with an annotation e.g. //foo:bar|baz where baz constitutes the // annotation. This can be used to target a named output of this rule when depended on or an entry point when used in // the context of tools. diff --git a/src/core/build_target.go b/src/core/build_target.go index 5e523be46..8fc7284f2 100644 --- a/src/core/build_target.go +++ b/src/core/build_target.go @@ -1664,6 +1664,16 @@ func (target *BuildTarget) AddMaybeExportedDependency(dep BuildLabel, exported, } } +// RegisterDependencyTarget registers a build target to be used for a dependency label on the given target. +func (target *BuildTarget) RegisterDependencyTarget(dep BuildLabel, deptarget *BuildTarget) { + info := target.dependencyInfo(dep) + if info == nil { + log.Fatalf("Target %s doesn't contain dependency %s.\n", target.Label, dep) + } else { + info.deps = append(info.deps, deptarget) + } +} + // IsTool returns true if the given build label is a tool used by this target. func (target *BuildTarget) IsTool(tool BuildLabel) bool { if target.isTool(tool, target.Tools, target.namedTools) { diff --git a/src/parse/asp/builtins.go b/src/parse/asp/builtins.go index 7562efd60..4231ab8fa 100644 --- a/src/parse/asp/builtins.go +++ b/src/parse/asp/builtins.go @@ -1151,6 +1151,7 @@ func addDep(s *scope, args []pyObject) pyObject { dep := s.parseLabelInPackage(string(args[1].(pyString)), s.pkg) exported := args[2].IsTruthy() target.AddMaybeExportedDependency(dep, exported, false, false) + target.RegisterDependencyTarget(dep, s.state.Graph.Target(dep)) // Queue this dependency if it'll be needed. if target.State() > core.Inactive { err := s.state.QueueTarget(dep, target.Label, false, core.ParseModeNormal) diff --git a/src/parse/asp/interpreter.go b/src/parse/asp/interpreter.go index 218815615..c09cb4ac4 100644 --- a/src/parse/asp/interpreter.go +++ b/src/parse/asp/interpreter.go @@ -310,6 +310,16 @@ type scope struct { // parseAnnotatedLabelInPackage similarly to parseLabelInPackage, parses the label contextualising it to the provided // package. It may return an AnnotatedOutputLabel or a BuildLabel depending on if the label is annotated. func (s *scope) parseAnnotatedLabelInPackage(label string, pkg *core.Package) core.BuildInput { + if strings.Contains(label, "+") { + parts := strings.Split(label, "+") + if len(parts) == 2 { + return core.SingleOutputLabel{ + BuildLabel: s.parseLabelInPackage(parts[0], pkg), + Output: parts[1], + } + } + } + label, annotation := core.SplitLabelAnnotation(label) if annotation != "" { return core.AnnotatedOutputLabel{