Merge "Add a testdata property in goPackage and goBinary"
diff --git a/module_ctx.go b/module_ctx.go
index a43deed..ed03789 100644
--- a/module_ctx.go
+++ b/module_ctx.go
@@ -139,6 +139,14 @@
 	// Context.RegisterModuleType().
 	ModuleType() string
 
+	// ModuleTags returns the tags for this module that should be passed to
+	// ninja for analysis. For example:
+	// [
+	//   "module_name": "libfoo",
+	//   "module_type": "cc_library",
+	// ]
+	ModuleTags() map[string]string
+
 	// BlueprintsFile returns the name of the blueprint file that contains the definition of this
 	// module.
 	BlueprintsFile() string
@@ -406,6 +414,13 @@
 	return d.module.typeName
 }
 
+func (d *baseModuleContext) ModuleTags() map[string]string {
+	return map[string]string{
+		"module_name": d.ModuleName(),
+		"module_type": d.ModuleType(),
+	}
+}
+
 func (d *baseModuleContext) ContainsProperty(name string) bool {
 	_, ok := d.module.propertyPos[name]
 	return ok
@@ -796,7 +811,7 @@
 func (m *moduleContext) Build(pctx PackageContext, params BuildParams) {
 	m.scope.ReparentTo(pctx)
 
-	def, err := parseBuildParams(m.scope, &params)
+	def, err := parseBuildParams(m.scope, &params, m.ModuleTags())
 	if err != nil {
 		panic(err)
 	}
diff --git a/ninja_defs.go b/ninja_defs.go
index 4915fdf..482c80c 100644
--- a/ninja_defs.go
+++ b/ninja_defs.go
@@ -275,8 +275,25 @@
 	Optional        bool
 }
 
-func parseBuildParams(scope scope, params *BuildParams) (*buildDef,
-	error) {
+func formatTags(tags map[string]string, rule Rule) string {
+	// Maps in golang do not have a guaranteed iteration order, nor is there an
+	// ordered map type in the stdlib, but we need to deterministically generate
+	// the ninja file.
+	keys := make([]string, 0, len(tags))
+	for k := range tags {
+		keys = append(keys, k)
+	}
+	sort.Strings(keys)
+	pairs := make([]string, 0, len(keys))
+	for _, k := range keys {
+		pairs = append(pairs, k+"="+tags[k])
+	}
+	pairs = append(pairs, "rule_name="+rule.name())
+	return strings.Join(pairs, ";")
+}
+
+func parseBuildParams(scope scope, params *BuildParams,
+	tags map[string]string) (*buildDef, error) {
 
 	comment := params.Comment
 	rule := params.Rule
@@ -360,6 +377,10 @@
 			simpleNinjaString(strings.Join(params.SymlinkOutputs, " ")))
 	}
 
+	if len(tags) > 0 {
+		setVariable("tags", simpleNinjaString(formatTags(tags, rule)))
+	}
+
 	argNameScope := rule.scope()
 
 	if len(params.Args) > 0 {
diff --git a/ninja_strings.go b/ninja_strings.go
index c3b070e..0e565e2 100644
--- a/ninja_strings.go
+++ b/ninja_strings.go
@@ -394,6 +394,10 @@
 		return fmt.Errorf("%q contains a '.' character", argName)
 	}
 
+	if argName == "tags" {
+		return fmt.Errorf("\"tags\" is a reserved argument name")
+	}
+
 	for _, builtin := range builtinRuleArgs {
 		if argName == builtin {
 			return fmt.Errorf("%q conflicts with Ninja built-in", argName)
diff --git a/singleton_ctx.go b/singleton_ctx.go
index d579b8e..95c29c4 100644
--- a/singleton_ctx.go
+++ b/singleton_ctx.go
@@ -265,7 +265,10 @@
 func (s *singletonContext) Build(pctx PackageContext, params BuildParams) {
 	s.scope.ReparentTo(pctx)
 
-	def, err := parseBuildParams(s.scope, &params)
+	def, err := parseBuildParams(s.scope, &params, map[string]string{
+		"module_name": s.name,
+		"module_type": "singleton",
+	})
 	if err != nil {
 		panic(err)
 	}