native shared libs in an SDK can be snapshotted

The snapshot script can now handle native shared libs in an SDK.

Bug: 138182343
Test: create following sdk module:
sdk {
    name: "mysdk",
    native_shared_libs: ["libc", "libdl"],
}
, then execute `m mysdk` and execute the update_prebuilt-1.sh as
prompted. Following directories are generated under the directory where
mysdk is defined at:

1
├── aidl
├── Android.bp
├── arm64
│   ├── include
│   ├── include_gen
│   └── lib
│       ├── libc.so
│       └── libdl.so
├── include
│   └── bionic
│       └── libc
│           └── include
│               ├── alloca.h
│               ├── android
│               │   ├── api-level.h
<omitted>

Change-Id: Ia1dcc5564c1cd17c6ccf441d06d5995af55db9ee
diff --git a/sdk/update.go b/sdk/update.go
index 5235c9e..ce60827 100644
--- a/sdk/update.go
+++ b/sdk/update.go
@@ -24,6 +24,7 @@
 	"github.com/google/blueprint/proptools"
 
 	"android/soong/android"
+	"android/soong/cc"
 	"android/soong/java"
 )
 
@@ -32,21 +33,31 @@
 // generatedFile abstracts operations for writing contents into a file and emit a build rule
 // for the file.
 type generatedFile struct {
-	path    android.OutputPath
-	content strings.Builder
+	path        android.OutputPath
+	content     strings.Builder
+	indentLevel int
 }
 
 func newGeneratedFile(ctx android.ModuleContext, name string) *generatedFile {
 	return &generatedFile{
-		path: android.PathForModuleOut(ctx, name).OutputPath,
+		path:        android.PathForModuleOut(ctx, name).OutputPath,
+		indentLevel: 0,
 	}
 }
 
+func (gf *generatedFile) indent() {
+	gf.indentLevel++
+}
+
+func (gf *generatedFile) dedent() {
+	gf.indentLevel--
+}
+
 func (gf *generatedFile) printfln(format string, args ...interface{}) {
 	// ninja consumes newline characters in rspfile_content. Prevent it by
 	// escaping the backslash in the newline character. The extra backshash
 	// is removed when the rspfile is written to the actual script file
-	fmt.Fprintf(&(gf.content), format+"\\n", args...)
+	fmt.Fprintf(&(gf.content), strings.Repeat("    ", gf.indentLevel)+format+"\\n", args...)
 }
 
 func (gf *generatedFile) build(pctx android.PackageContext, ctx android.BuilderContext, implicits android.Paths) {
@@ -61,34 +72,187 @@
 	rb.Build(pctx, ctx, gf.path.Base(), "Build "+gf.path.Base())
 }
 
-func (s *sdk) javaMemberNames(ctx android.ModuleContext) []string {
-	result := []string{}
+func (s *sdk) javaLibs(ctx android.ModuleContext) []*java.Library {
+	result := []*java.Library{}
 	ctx.VisitDirectDeps(func(m android.Module) {
-		if _, ok := m.(*java.Library); ok {
-			result = append(result, m.Name())
+		if j, ok := m.(*java.Library); ok {
+			result = append(result, j)
 		}
 	})
 	return result
 }
 
-// buildAndroidBp creates the blueprint file that defines prebuilt modules for each of
-// the SDK members, and the sdk_snapshot module for the specified version
-func (s *sdk) buildAndroidBp(ctx android.ModuleContext, version string) android.OutputPath {
-	bp := newGeneratedFile(ctx, "blueprint-"+version+".sh")
+// archSpecificNativeLibInfo represents an arch-specific variant of a native lib
+type archSpecificNativeLibInfo struct {
+	name                      string
+	archType                  string
+	exportedIncludeDirs       android.Paths
+	exportedSystemIncludeDirs android.Paths
+	exportedFlags             []string
+	outputFile                android.Path
+}
 
-	makePrebuiltName := func(name string) string {
-		return ctx.ModuleName() + "_" + name + string(android.SdkVersionSeparator) + version
+func (lib *archSpecificNativeLibInfo) signature() string {
+	return fmt.Sprintf("%v %v %v %v",
+		lib.name,
+		lib.exportedIncludeDirs.Strings(),
+		lib.exportedSystemIncludeDirs.Strings(),
+		lib.exportedFlags)
+}
+
+// nativeLibInfo represents a collection of arch-specific modules having the same name
+type nativeLibInfo struct {
+	name         string
+	archVariants []archSpecificNativeLibInfo
+	// hasArchSpecificFlags is set to true if modules for each architecture all have the same
+	// include dirs, flags, etc, in which case only those of the first arch is selected.
+	hasArchSpecificFlags bool
+}
+
+// nativeMemberInfos collects all cc.Modules that are member of an SDK.
+func (s *sdk) nativeMemberInfos(ctx android.ModuleContext) []*nativeLibInfo {
+	infoMap := make(map[string]*nativeLibInfo)
+
+	// Collect cc.Modules
+	ctx.VisitDirectDeps(func(m android.Module) {
+		ccModule, ok := m.(*cc.Module)
+		if !ok {
+			return
+		}
+		depName := ctx.OtherModuleName(m)
+
+		if _, ok := infoMap[depName]; !ok {
+			infoMap[depName] = &nativeLibInfo{name: depName}
+		}
+
+		info := infoMap[depName]
+		info.archVariants = append(info.archVariants, archSpecificNativeLibInfo{
+			name:                      ccModule.BaseModuleName(),
+			archType:                  ccModule.Target().Arch.ArchType.String(),
+			exportedIncludeDirs:       ccModule.ExportedIncludeDirs(),
+			exportedSystemIncludeDirs: ccModule.ExportedSystemIncludeDirs(),
+			exportedFlags:             ccModule.ExportedFlags(),
+			outputFile:                ccModule.OutputFile().Path(),
+		})
+	})
+
+	// Determine if include dirs and flags for each module are different across arch-specific
+	// modules or not. And set hasArchSpecificFlags accordingly
+	for _, info := range infoMap {
+		// by default, include paths and flags are assumed to be the same across arches
+		info.hasArchSpecificFlags = false
+		oldSignature := ""
+		for _, av := range info.archVariants {
+			newSignature := av.signature()
+			if oldSignature == "" {
+				oldSignature = newSignature
+			}
+			if oldSignature != newSignature {
+				info.hasArchSpecificFlags = true
+				break
+			}
+		}
 	}
 
-	javaLibs := s.javaMemberNames(ctx)
-	for _, name := range javaLibs {
-		prebuiltName := makePrebuiltName(name)
-		jar := filepath.Join("java", name, "stub.jar")
+	var list []*nativeLibInfo
+	for _, v := range infoMap {
+		list = append(list, v)
+	}
+	return list
+}
 
+// SDK directory structure
+// <sdk_root>/
+//     Android.bp   : definition of a 'sdk' module is here. This is a hand-made one.
+//     <api_ver>/   : below this directory are all auto-generated
+//         Android.bp   : definition of 'sdk_snapshot' module is here
+//         aidl/
+//            frameworks/base/core/..../IFoo.aidl   : an exported AIDL file
+//         java/
+//            java/<module_name>/stub.jar    : a stub jar for a java library 'module_name'
+//         include/
+//            bionic/libc/include/stdlib.h   : an exported header file
+//         include_gen/
+//            com/android/.../IFoo.h : a generated header file
+//         <arch>/include/   : arch-specific exported headers
+//         <arch>/include_gen/   : arch-specific generated headers
+//         <arch>/lib/
+//            libFoo.so   : a stub library
+
+const (
+	aidlIncludeDir            = "aidl"
+	javaStubDir               = "java"
+	javaStubFile              = "stub.jar"
+	nativeIncludeDir          = "include"
+	nativeGeneratedIncludeDir = "include_gen"
+	nativeStubDir             = "lib"
+	nativeStubFileSuffix      = ".so"
+)
+
+// path to the stub file of a java library. Relative to <sdk_root>/<api_dir>
+func javaStubFilePathFor(javaLib *java.Library) string {
+	return filepath.Join(javaStubDir, javaLib.Name(), javaStubFile)
+}
+
+// path to the stub file of a native shared library. Relative to <sdk_root>/<api_dir>
+func nativeStubFilePathFor(lib archSpecificNativeLibInfo) string {
+	return filepath.Join(lib.archType,
+		nativeStubDir, lib.name+nativeStubFileSuffix)
+}
+
+// paths to the include dirs of a native shared library. Relative to <sdk_root>/<api_dir>
+func nativeIncludeDirPathsFor(ctx android.ModuleContext, lib archSpecificNativeLibInfo,
+	systemInclude bool, archSpecific bool) []string {
+	var result []string
+	buildDir := ctx.Config().BuildDir()
+	var includeDirs []android.Path
+	if !systemInclude {
+		includeDirs = lib.exportedIncludeDirs
+	} else {
+		includeDirs = lib.exportedSystemIncludeDirs
+	}
+	for _, dir := range includeDirs {
+		var path string
+		if gen := strings.HasPrefix(dir.String(), buildDir); gen {
+			path = filepath.Join(nativeGeneratedIncludeDir, dir.Rel())
+		} else {
+			path = filepath.Join(nativeIncludeDir, dir.String())
+		}
+		if archSpecific {
+			path = filepath.Join(lib.archType, path)
+		}
+		result = append(result, path)
+	}
+	return result
+}
+
+// A name that uniquely identifies an prebuilt SDK member for a version of SDK snapshot
+// This isn't visible to users, so could be changed in future.
+func versionedSdkMemberName(ctx android.ModuleContext, memberName string, version string) string {
+	return ctx.ModuleName() + "_" + memberName + string(android.SdkVersionSeparator) + version
+}
+
+// arm64, arm, x86, x86_64, etc.
+func archTypeOf(module android.Module) string {
+	return module.Target().Arch.ArchType.String()
+}
+
+// buildAndroidBp creates the blueprint file that defines prebuilt modules for each of
+// the SDK members, and the entire sdk_snapshot module for the specified version
+func (s *sdk) buildAndroidBp(ctx android.ModuleContext, version string) android.OutputPath {
+	bp := newGeneratedFile(ctx, "blueprint-"+version+".bp")
+	bp.printfln("// This is auto-generated. DO NOT EDIT.")
+	bp.printfln("")
+
+	javaLibModules := s.javaLibs(ctx)
+	for _, m := range javaLibModules {
+		name := m.Name()
 		bp.printfln("java_import {")
-		bp.printfln("    name: %q,", prebuiltName)
-		bp.printfln("    jars: [%q],", jar)
-		bp.printfln("    sdk_member_name: %q,", name)
+		bp.indent()
+		bp.printfln("name: %q,", versionedSdkMemberName(ctx, name, version))
+		bp.printfln("sdk_member_name: %q,", name)
+		bp.printfln("jars: [%q],", javaStubFilePathFor(m))
+		bp.dedent()
 		bp.printfln("}")
 		bp.printfln("")
 
@@ -96,25 +260,92 @@
 		// doesn't exist (i.e. building in an unbundled tree). "prefer:" is set to false
 		// so that this module does not eclipse the unversioned module if it exists.
 		bp.printfln("java_import {")
-		bp.printfln("    name: %q,", name)
-		bp.printfln("    jars: [%q],", jar)
-		bp.printfln("    prefer: false,")
+		bp.indent()
+		bp.printfln("name: %q,", name)
+		bp.printfln("jars: [%q],", javaStubFilePathFor(m))
+		bp.printfln("prefer: false,")
+		bp.dedent()
 		bp.printfln("}")
 		bp.printfln("")
-
 	}
 
-	// TODO(jiyong): emit cc_prebuilt_library_shared for the native libs
+	nativeLibInfos := s.nativeMemberInfos(ctx)
+	for _, info := range nativeLibInfos {
+		bp.printfln("cc_prebuilt_library_shared {")
+		bp.indent()
+		bp.printfln("name: %q,", versionedSdkMemberName(ctx, info.name, version))
+		bp.printfln("sdk_member_name: %q,", info.name)
+
+		// a function for emitting include dirs
+		printExportedDirsForNativeLibs := func(lib archSpecificNativeLibInfo, systemInclude bool) {
+			includeDirs := nativeIncludeDirPathsFor(ctx, lib, systemInclude, info.hasArchSpecificFlags)
+			if len(includeDirs) == 0 {
+				return
+			}
+			if !systemInclude {
+				bp.printfln("export_include_dirs: [")
+			} else {
+				bp.printfln("export_system_include_dirs: [")
+			}
+			bp.indent()
+			for _, dir := range includeDirs {
+				bp.printfln("%q,", dir)
+			}
+			bp.dedent()
+			bp.printfln("],")
+		}
+
+		if !info.hasArchSpecificFlags {
+			printExportedDirsForNativeLibs(info.archVariants[0], false /*systemInclude*/)
+			printExportedDirsForNativeLibs(info.archVariants[0], true /*systemInclude*/)
+		}
+
+		bp.printfln("arch: {")
+		bp.indent()
+		for _, av := range info.archVariants {
+			bp.printfln("%s: {", av.archType)
+			bp.indent()
+			bp.printfln("srcs: [%q],", nativeStubFilePathFor(av))
+			if info.hasArchSpecificFlags {
+				// export_* properties are added inside the arch: {<arch>: {...}} block
+				printExportedDirsForNativeLibs(av, false /*systemInclude*/)
+				printExportedDirsForNativeLibs(av, true /*systemInclude*/)
+			}
+			bp.dedent()
+			bp.printfln("},") // <arch>
+		}
+		bp.dedent()
+		bp.printfln("},") // arch
+		bp.printfln("stl: \"none\",")
+		bp.printfln("system_shared_libs: [],")
+		bp.dedent()
+		bp.printfln("}") // cc_prebuilt_library_shared
+		bp.printfln("")
+	}
 
 	bp.printfln("sdk_snapshot {")
-	bp.printfln("    name: %q,", ctx.ModuleName()+string(android.SdkVersionSeparator)+version)
-	bp.printfln("    java_libs: [")
-	for _, n := range javaLibs {
-		bp.printfln("        %q,", makePrebuiltName(n))
+	bp.indent()
+	bp.printfln("name: %q,", ctx.ModuleName()+string(android.SdkVersionSeparator)+version)
+	if len(javaLibModules) > 0 {
+		bp.printfln("java_libs: [")
+		bp.indent()
+		for _, m := range javaLibModules {
+			bp.printfln("%q,", versionedSdkMemberName(ctx, m.Name(), version))
+		}
+		bp.dedent()
+		bp.printfln("],") // java_libs
 	}
-	bp.printfln("    ],")
-	// TODO(jiyong): emit native_shared_libs
-	bp.printfln("}")
+	if len(nativeLibInfos) > 0 {
+		bp.printfln("native_shared_libs: [")
+		bp.indent()
+		for _, info := range nativeLibInfos {
+			bp.printfln("%q,", versionedSdkMemberName(ctx, info.name, version))
+		}
+		bp.dedent()
+		bp.printfln("],") // native_shared_libs
+	}
+	bp.dedent()
+	bp.printfln("}") // sdk_snapshot
 	bp.printfln("")
 
 	bp.build(pctx, ctx, nil)
@@ -123,46 +354,104 @@
 
 func (s *sdk) buildScript(ctx android.ModuleContext, version string) android.OutputPath {
 	sh := newGeneratedFile(ctx, "update_prebuilt-"+version+".sh")
+	buildDir := ctx.Config().BuildDir()
 
-	snapshotRoot := filepath.Join(ctx.ModuleDir(), version)
-	aidlIncludeDir := filepath.Join(snapshotRoot, "aidl")
-	javaStubsDir := filepath.Join(snapshotRoot, "java")
+	snapshotPath := func(paths ...string) string {
+		return filepath.Join(ctx.ModuleDir(), version, filepath.Join(paths...))
+	}
 
+	// TODO(jiyong) instead of creating script, create a zip file having the Android.bp, the headers,
+	// and the stubs and put it to the dist directory. The dist'ed zip file then would be downloaded,
+	// unzipped and then uploaded to gerrit again.
 	sh.printfln("#!/bin/bash")
-	sh.printfln("echo Updating snapshot of %s in %s", ctx.ModuleName(), snapshotRoot)
+	sh.printfln("echo Updating snapshot of %s in %s", ctx.ModuleName(), snapshotPath())
 	sh.printfln("pushd $ANDROID_BUILD_TOP > /dev/null")
-	sh.printfln("rm -rf %s", snapshotRoot)
-	sh.printfln("mkdir -p %s", aidlIncludeDir)
-	sh.printfln("mkdir -p %s", javaStubsDir)
-	// TODO(jiyong): mkdir the 'native' dir
+	sh.printfln("mkdir -p %s", snapshotPath(aidlIncludeDir))
+	sh.printfln("mkdir -p %s", snapshotPath(javaStubDir))
+	sh.printfln("mkdir -p %s", snapshotPath(nativeIncludeDir))
+	sh.printfln("mkdir -p %s", snapshotPath(nativeGeneratedIncludeDir))
+	for _, target := range ctx.MultiTargets() {
+		arch := target.Arch.ArchType.String()
+		sh.printfln("mkdir -p %s", snapshotPath(arch, nativeStubDir))
+		sh.printfln("mkdir -p %s", snapshotPath(arch, nativeIncludeDir))
+		sh.printfln("mkdir -p %s", snapshotPath(arch, nativeGeneratedIncludeDir))
+	}
 
 	var implicits android.Paths
-	ctx.VisitDirectDeps(func(m android.Module) {
-		if javaLib, ok := m.(*java.Library); ok {
-			headerJars := javaLib.HeaderJars()
-			if len(headerJars) != 1 {
-				panic(fmt.Errorf("there must be only one header jar from %q", m.Name()))
-			}
-			implicits = append(implicits, headerJars...)
-
-			exportedAidlIncludeDirs := javaLib.AidlIncludeDirs()
-			for _, dir := range exportedAidlIncludeDirs {
-				// Using tar to copy with the directory structure
-				// TODO(jiyong): copy parcelable declarations only
-				sh.printfln("find %s -name \"*.aidl\" | tar cf - -T - | (cd %s; tar xf -)",
-					dir.String(), aidlIncludeDir)
-			}
-
-			copiedHeaderJar := filepath.Join(javaStubsDir, m.Name(), "stub.jar")
-			sh.printfln("mkdir -p $(dirname %s) && cp %s %s",
-				copiedHeaderJar, headerJars[0].String(), copiedHeaderJar)
+	for _, m := range s.javaLibs(ctx) {
+		headerJars := m.HeaderJars()
+		if len(headerJars) != 1 {
+			panic(fmt.Errorf("there must be only one header jar from %q", m.Name()))
 		}
-		// TODO(jiyong): emit the commands for copying the headers and stub libraries for native libs
-	})
+		implicits = append(implicits, headerJars...)
+
+		exportedAidlIncludeDirs := m.AidlIncludeDirs()
+		for _, dir := range exportedAidlIncludeDirs {
+			// Using tar to copy with the directory structure
+			// TODO(jiyong): copy parcelable declarations only
+			sh.printfln("find %s -name \"*.aidl\" | tar cf - -T - | (cd %s; tar xf -)",
+				dir.String(), snapshotPath(aidlIncludeDir))
+		}
+
+		copyTarget := snapshotPath(javaStubFilePathFor(m))
+		sh.printfln("mkdir -p %s && cp %s %s",
+			filepath.Dir(copyTarget), headerJars[0].String(), copyTarget)
+	}
+
+	nativeLibInfos := s.nativeMemberInfos(ctx)
+	for _, info := range nativeLibInfos {
+
+		// a function for emitting include dirs
+		printExportedDirCopyCommandsForNativeLibs := func(lib archSpecificNativeLibInfo) {
+			includeDirs := lib.exportedIncludeDirs
+			includeDirs = append(includeDirs, lib.exportedSystemIncludeDirs...)
+			if len(includeDirs) == 0 {
+				return
+			}
+			for _, dir := range includeDirs {
+				gen := strings.HasPrefix(dir.String(), buildDir)
+				targetDir := nativeIncludeDir
+				if gen {
+					targetDir = nativeGeneratedIncludeDir
+				}
+				if info.hasArchSpecificFlags {
+					targetDir = filepath.Join(lib.archType, targetDir)
+				}
+				targetDir = snapshotPath(targetDir)
+
+				sourceDirRoot := "."
+				sourceDirRel := dir.String()
+				if gen {
+					// ex) out/soong/.intermediate/foo/bar/gen/aidl
+					sourceDirRoot = strings.TrimSuffix(dir.String(), dir.Rel())
+					sourceDirRel = dir.Rel()
+				}
+				// TODO(jiyong) copy headers having other suffixes
+				sh.printfln("(cd %s; find %s -name \"*.h\" | tar cf - -T - ) | (cd %s; tar xf -)",
+					sourceDirRoot, sourceDirRel, targetDir)
+			}
+		}
+
+		if !info.hasArchSpecificFlags {
+			printExportedDirCopyCommandsForNativeLibs(info.archVariants[0])
+		}
+
+		// for each architecture
+		for _, av := range info.archVariants {
+			stub := av.outputFile
+			implicits = append(implicits, stub)
+			copiedStub := snapshotPath(nativeStubFilePathFor(av))
+			sh.printfln("cp %s %s", stub.String(), copiedStub)
+
+			if info.hasArchSpecificFlags {
+				printExportedDirCopyCommandsForNativeLibs(av)
+			}
+		}
+	}
 
 	bp := s.buildAndroidBp(ctx, version)
 	implicits = append(implicits, bp)
-	sh.printfln("cp %s %s", bp.String(), filepath.Join(snapshotRoot, "Android.bp"))
+	sh.printfln("cp %s %s", bp.String(), snapshotPath("Android.bp"))
 
 	sh.printfln("popd > /dev/null")
 	sh.printfln("rm -- \"$0\"") // self deleting so that stale script is not used
@@ -218,8 +507,8 @@
 				fmt.Fprintln(w, "$(LOCAL_BUILT_MODULE): $(LOCAL_ADDITIONAL_DEPENDENCIES)")
 				fmt.Fprintln(w, "	touch $@")
 				fmt.Fprintln(w, "	echo ##################################################")
-				fmt.Fprintln(w, "	echo To update current SDK: execute", s.updateScript.String())
-				fmt.Fprintln(w, "	echo To freeze current SDK: execute", s.freezeScript.String())
+				fmt.Fprintln(w, "	echo To update current SDK: execute", filepath.Join("\\$$ANDROID_BUILD_TOP", s.updateScript.String()))
+				fmt.Fprintln(w, "	echo To freeze current SDK: execute", filepath.Join("\\$$ANDROID_BUILD_TOP", s.freezeScript.String()))
 				fmt.Fprintln(w, "	echo ##################################################")
 			},
 		},