Merge pull request #100 from danw/blueprint_tools

Add blueprint_go_binary for user-run tools
diff --git a/Blueprints b/Blueprints
index 84345a3..74db5c1 100644
--- a/Blueprints
+++ b/Blueprints
@@ -117,13 +117,13 @@
     srcs = ["bootstrap/minibp/main.go"],
 )
 
-bootstrap_go_binary(
+blueprint_go_binary(
     name = "bpfmt",
     deps = ["blueprint-parser"],
     srcs = ["bpfmt/bpfmt.go"],
 )
 
-bootstrap_go_binary(
+blueprint_go_binary(
     name = "bpmodify",
     deps = ["blueprint-parser"],
     srcs = ["bpmodify/bpmodify.go"],
diff --git a/bootstrap/bootstrap.go b/bootstrap/bootstrap.go
index c05f799..c4ec3cf 100644
--- a/bootstrap/bootstrap.go
+++ b/bootstrap/bootstrap.go
@@ -118,6 +118,12 @@
 	minibpFile = filepath.Join("$BinDir", "minibp")
 
 	docsDir = filepath.Join(bootstrapDir, "docs")
+	toolDir = pctx.VariableFunc("ToolDir", func(config interface{}) (string, error) {
+		if c, ok := config.(ConfigBlueprintToolLocation); ok {
+			return c.BlueprintToolLocation(), nil
+		}
+		return filepath.Join("$buildDir", "bin"), nil
+	})
 
 	bootstrapDir     = filepath.Join("$buildDir", bootstrapSubDir)
 	miniBootstrapDir = filepath.Join("$buildDir", miniBootstrapSubDir)
@@ -129,15 +135,15 @@
 }
 
 func propagateStageBootstrap(mctx blueprint.TopDownMutatorContext) {
-	if mod, ok := mctx.Module().(bootstrapGoCore); !ok || mod.BuildStage() != StageBootstrap {
-		return
-	}
+	if mod, ok := mctx.Module().(bootstrapGoCore); ok {
+		stage := mod.BuildStage()
 
-	mctx.VisitDirectDeps(func(mod blueprint.Module) {
-		if m, ok := mod.(bootstrapGoCore); ok {
-			m.SetBuildStage(StageBootstrap)
-		}
-	})
+		mctx.VisitDirectDeps(func(mod blueprint.Module) {
+			if m, ok := mod.(bootstrapGoCore); ok && m.BuildStage() > stage {
+				m.SetBuildStage(stage)
+			}
+		})
+	}
 }
 
 func pluginDeps(ctx blueprint.BottomUpMutatorContext) {
@@ -226,7 +232,7 @@
 		module := &goPackage{
 			config: config,
 		}
-		module.properties.BuildStage = StagePrimary
+		module.properties.BuildStage = StageMain
 		return module, []interface{}{&module.properties}
 	}
 }
@@ -312,7 +318,7 @@
 
 		buildGoPackage(ctx, g.pkgRoot, g.properties.PkgPath, g.archiveFile,
 			g.properties.Srcs, genSrcs, deps)
-	} else if g.config.stage != StageBootstrap {
+	} else if g.config.stage > g.BuildStage() {
 		if len(g.properties.TestSrcs) > 0 && g.config.runGoTests {
 			phonyGoTarget(ctx, g.testArchiveFile, g.properties.TestSrcs, nil, nil)
 		}
@@ -360,13 +366,20 @@
 	g.properties.BuildStage = buildStage
 }
 
+func (g *goBinary) InstallPath() string {
+	if g.BuildStage() == StageMain {
+		return "$ToolDir"
+	}
+	return "$BinDir"
+}
+
 func (g *goBinary) GenerateBuildActions(ctx blueprint.ModuleContext) {
 	var (
 		name        = ctx.ModuleName()
 		objDir      = moduleObjDir(ctx)
 		archiveFile = filepath.Join(objDir, name+".a")
 		aoutFile    = filepath.Join(objDir, "a.out")
-		binaryFile  = filepath.Join("$BinDir", name)
+		binaryFile  = filepath.Join(g.InstallPath(), name)
 		hasPlugins  = false
 		pluginSrc   = ""
 		genSrcs     = []string{}
@@ -427,7 +440,7 @@
 			Outputs: []string{binaryFile},
 			Inputs:  []string{aoutFile},
 		})
-	} else if g.config.stage != StageBootstrap {
+	} else if g.config.stage > g.BuildStage() {
 		if len(g.properties.TestSrcs) > 0 && g.config.runGoTests {
 			phonyGoTarget(ctx, g.testArchiveFile, g.properties.TestSrcs, nil, nil)
 		}
@@ -637,16 +650,21 @@
 	var rebootstrapDeps []string
 	// primaryRebootstrapDeps contains modules that will be built in StagePrimary
 	var primaryRebootstrapDeps []string
+	// blueprintTools contains blueprint go binaries that will be built in StageMain
+	var blueprintTools []string
 	ctx.VisitAllModulesIf(isBootstrapBinaryModule,
 		func(module blueprint.Module) {
 			binaryModule := module.(*goBinary)
 			binaryModuleName := ctx.ModuleName(binaryModule)
-			binaryModulePath := filepath.Join("$BinDir", binaryModuleName)
+			installPath := filepath.Join(binaryModule.InstallPath(), binaryModuleName)
 
-			if binaryModule.BuildStage() == StageBootstrap {
-				rebootstrapDeps = append(rebootstrapDeps, binaryModulePath)
-			} else {
-				primaryRebootstrapDeps = append(primaryRebootstrapDeps, binaryModulePath)
+			switch binaryModule.BuildStage() {
+			case StageBootstrap:
+				rebootstrapDeps = append(rebootstrapDeps, installPath)
+			case StagePrimary:
+				primaryRebootstrapDeps = append(primaryRebootstrapDeps, installPath)
+			case StageMain:
+				blueprintTools = append(blueprintTools, installPath)
 			}
 			if binaryModule.properties.PrimaryBuilder {
 				primaryBuilders = append(primaryBuilders, binaryModule)
@@ -707,10 +725,13 @@
 			testModule := module.(goTestProducer)
 			target := testModule.GoTestTarget()
 			if target != "" {
-				if testModule.BuildStage() == StageBootstrap {
+				switch testModule.BuildStage() {
+				case StageBootstrap:
 					rebootstrapDeps = append(rebootstrapDeps, target)
-				} else {
+				case StagePrimary:
 					primaryRebootstrapDeps = append(primaryRebootstrapDeps, target)
+				case StageMain:
+					blueprintTools = append(blueprintTools, target)
 				}
 			}
 		})
@@ -961,6 +982,12 @@
 				Outputs: []string{finalMinibp},
 			})
 		}
+
+		ctx.Build(pctx, blueprint.BuildParams{
+			Rule:    blueprint.Phony,
+			Outputs: []string{"blueprint_tools"},
+			Inputs:  blueprintTools,
+		})
 	}
 
 	ctx.Build(pctx, blueprint.BuildParams{
diff --git a/bootstrap/command.go b/bootstrap/command.go
index 69b2271..2689a42 100644
--- a/bootstrap/command.go
+++ b/bootstrap/command.go
@@ -94,6 +94,7 @@
 	ctx.RegisterModuleType("bootstrap_go_package", newGoPackageModuleFactory(bootstrapConfig))
 	ctx.RegisterModuleType("bootstrap_core_go_binary", newGoBinaryModuleFactory(bootstrapConfig, StageBootstrap))
 	ctx.RegisterModuleType("bootstrap_go_binary", newGoBinaryModuleFactory(bootstrapConfig, StagePrimary))
+	ctx.RegisterModuleType("blueprint_go_binary", newGoBinaryModuleFactory(bootstrapConfig, StageMain))
 	ctx.RegisterTopDownMutator("bootstrap_stage", propagateStageBootstrap)
 	ctx.RegisterSingletonType("bootstrap", newSingletonFactory(bootstrapConfig))
 
diff --git a/bootstrap/config.go b/bootstrap/config.go
index 7730ce9..2e30718 100644
--- a/bootstrap/config.go
+++ b/bootstrap/config.go
@@ -44,6 +44,13 @@
 	RemoveAbandonedFiles() bool
 }
 
+type ConfigBlueprintToolLocation interface {
+	// BlueprintToolLocation can return a path name to install blueprint tools
+	// designed for end users (bpfmt, bpmodify, and anything else using
+	// blueprint_go_binary).
+	BlueprintToolLocation() string
+}
+
 type Stage int
 
 const (