Merge remote-tracking branch 'aosp/upstream' into master

* aosp/upstream:
  Add missing dependencies when linking tests
  Allow mutated property structs to contain slices of non-strings
  Enable restat for go binaries

Bug: 80095087
Test: m checkbuild
Change-Id: I804970378ea31fbab1a33c34849c2183caa0133c
diff --git a/bootstrap/bootstrap.go b/bootstrap/bootstrap.go
index 03aead9..be156f9 100644
--- a/bootstrap/bootstrap.go
+++ b/bootstrap/bootstrap.go
@@ -57,18 +57,22 @@
 
 	compile = pctx.StaticRule("compile",
 		blueprint.RuleParams{
-			Command: "GOROOT='$goRoot' $compileCmd $parallelCompile -o $out " +
-				"-p $pkgPath -complete $incFlags -pack $in",
+			Command: "GOROOT='$goRoot' $compileCmd $parallelCompile -o $out.tmp " +
+				"-p $pkgPath -complete $incFlags -pack $in && " +
+				"if cmp --quiet $out.tmp $out; then rm $out.tmp; else mv -f $out.tmp $out; fi",
 			CommandDeps: []string{"$compileCmd"},
 			Description: "compile $out",
+			Restat:      true,
 		},
 		"pkgPath", "incFlags")
 
 	link = pctx.StaticRule("link",
 		blueprint.RuleParams{
-			Command:     "GOROOT='$goRoot' $linkCmd -o $out $libDirFlags $in",
+			Command: "GOROOT='$goRoot' $linkCmd -o $out.tmp $libDirFlags $in && " +
+				"if cmp --quiet $out.tmp $out; then rm $out.tmp; else mv -f $out.tmp $out; fi",
 			CommandDeps: []string{"$linkCmd"},
 			Description: "link $out",
+			Restat:      true,
 		},
 		"libDirFlags")
 
@@ -435,10 +439,12 @@
 
 	buildGoPackage(ctx, objDir, name, archiveFile, srcs, genSrcs)
 
+	var linkDeps []string
 	var libDirFlags []string
 	ctx.VisitDepsDepthFirstIf(isGoPackageProducer,
 		func(module blueprint.Module) {
 			dep := module.(goPackageProducer)
+			linkDeps = append(linkDeps, dep.GoPackageTarget())
 			libDir := dep.GoPkgRoot()
 			libDirFlags = append(libDirFlags, "-L "+libDir)
 			deps = append(deps, dep.GoTestTargets()...)
@@ -450,11 +456,12 @@
 	}
 
 	ctx.Build(pctx, blueprint.BuildParams{
-		Rule:     link,
-		Outputs:  []string{aoutFile},
-		Inputs:   []string{archiveFile},
-		Args:     linkArgs,
-		Optional: true,
+		Rule:      link,
+		Outputs:   []string{aoutFile},
+		Inputs:    []string{archiveFile},
+		Implicits: linkDeps,
+		Args:      linkArgs,
+		Optional:  true,
 	})
 
 	ctx.Build(pctx, blueprint.BuildParams{
@@ -554,11 +561,13 @@
 		Optional: true,
 	})
 
+	var linkDeps []string
 	libDirFlags := []string{"-L " + testRoot}
 	testDeps := []string{}
 	ctx.VisitDepsDepthFirstIf(isGoPackageProducer,
 		func(module blueprint.Module) {
 			dep := module.(goPackageProducer)
+			linkDeps = append(linkDeps, dep.GoPackageTarget())
 			libDir := dep.GoPkgRoot()
 			libDirFlags = append(libDirFlags, "-L "+libDir)
 			testDeps = append(testDeps, dep.GoTestTargets()...)
@@ -577,9 +586,10 @@
 	})
 
 	ctx.Build(pctx, blueprint.BuildParams{
-		Rule:    link,
-		Outputs: []string{testFile},
-		Inputs:  []string{testArchive},
+		Rule:      link,
+		Outputs:   []string{testFile},
+		Inputs:    []string{testArchive},
+		Implicits: linkDeps,
 		Args: map[string]string{
 			"libDirFlags": strings.Join(libDirFlags, " "),
 		},
diff --git a/proptools/clone.go b/proptools/clone.go
index 9948b9a..dbd72b4 100644
--- a/proptools/clone.go
+++ b/proptools/clone.go
@@ -52,9 +52,6 @@
 			CopyProperties(dstFieldValue, srcFieldValue)
 		case reflect.Slice:
 			if !srcFieldValue.IsNil() {
-				if field.Type.Elem().Kind() != reflect.String {
-					panic(fmt.Errorf("can't copy field %q: slice elements are not strings", field.Name))
-				}
 				if srcFieldValue != dstFieldValue {
 					newSlice := reflect.MakeSlice(field.Type, srcFieldValue.Len(),
 						srcFieldValue.Len())
diff --git a/proptools/clone_test.go b/proptools/clone_test.go
index b6f1bf6..660f1c0 100644
--- a/proptools/clone_test.go
+++ b/proptools/clone_test.go
@@ -71,6 +71,19 @@
 		out: &struct{ S []string }{},
 	},
 	{
+		// Clone slice of structs
+		in: &struct{ S []struct{ T string } }{
+			S: []struct{ T string }{
+				{"string1"}, {"string2"},
+			},
+		},
+		out: &struct{ S []struct{ T string } }{
+			S: []struct{ T string }{
+				{"string1"}, {"string2"},
+			},
+		},
+	},
+	{
 		// Clone pointer to bool
 		in: &struct{ B1, B2 *bool }{
 			B1: BoolPtr(true),
@@ -317,6 +330,17 @@
 		out: &struct{ S []string }{},
 	},
 	{
+		// Clone slice of structs
+		in: &struct{ S []struct{ T string } }{
+			S: []struct{ T string }{
+				{"string1"}, {"string2"},
+			},
+		},
+		out: &struct{ S []struct{ T string } }{
+			S: []struct{ T string }(nil),
+		},
+	},
+	{
 		// Clone pointer to bool
 		in: &struct{ B1, B2 *bool }{
 			B1: BoolPtr(true),
diff --git a/unpack.go b/unpack.go
index cd165da..3156599 100644
--- a/unpack.go
+++ b/unpack.go
@@ -163,7 +163,9 @@
 		case reflect.Slice:
 			elemType := field.Type.Elem()
 			if elemType.Kind() != reflect.String {
-				panic(fmt.Errorf("field %s is a non-string slice", propertyName))
+				if !proptools.HasTag(field, "blueprint", "mutated") {
+					panic(fmt.Errorf("field %s is a non-string slice", propertyName))
+				}
 			}
 		case reflect.Interface:
 			if fieldValue.IsNil() {
diff --git a/unpack_test.go b/unpack_test.go
index b65fa3f..d6b88ab 100644
--- a/unpack_test.go
+++ b/unpack_test.go
@@ -111,13 +111,15 @@
 		`,
 		output: []interface{}{
 			struct {
-				Stuff []string
-				Empty []string
-				Nil   []string
+				Stuff     []string
+				Empty     []string
+				Nil       []string
+				NonString []struct{ S string } `blueprint:"mutated"`
 			}{
-				Stuff: []string{"asdf", "jkl;", "qwert", "uiop", "bnm,"},
-				Empty: []string{},
-				Nil:   nil,
+				Stuff:     []string{"asdf", "jkl;", "qwert", "uiop", "bnm,"},
+				Empty:     []string{},
+				Nil:       nil,
+				NonString: nil,
 			},
 		},
 	},