java_sdk_library does the apicheck by default

droiddoc now supports apicheck. java_sdk_library uses it to
automatically perform apichecks against the not-yet-release API and
the latest-released API.

A module type prebuilt_apis is added. It finds api txt files and creates
filegroup modules so that it can be referenced from java_sdk_library
across the module boundary.

Bug: 77575606
Test: m -j
Test: m -j checkapi
Test: m -j update-api
Change-Id: I0ba859972eac060296e1df2e71c4e047392d4877
diff --git a/java/java_test.go b/java/java_test.go
index 4a0229e..fe1c3d7 100644
--- a/java/java_test.go
+++ b/java/java_test.go
@@ -84,10 +84,12 @@
 	ctx.RegisterModuleType("droiddoc_host", android.ModuleFactoryAdaptor(DroiddocHostFactory))
 	ctx.RegisterModuleType("droiddoc_template", android.ModuleFactoryAdaptor(DroiddocTemplateFactory))
 	ctx.RegisterModuleType("java_sdk_library", android.ModuleFactoryAdaptor(sdkLibraryFactory))
+	ctx.RegisterModuleType("prebuilt_apis", android.ModuleFactoryAdaptor(prebuiltApisFactory))
 	ctx.PreArchMutators(android.RegisterPrebuiltsPreArchMutators)
 	ctx.PreArchMutators(android.RegisterPrebuiltsPostDepsMutators)
 	ctx.PreArchMutators(android.RegisterDefaultsPreArchMutators)
 	ctx.PreArchMutators(func(ctx android.RegisterMutatorsContext) {
+		ctx.TopDown("prebuilt_apis", prebuiltApisMutator).Parallel()
 		ctx.TopDown("java_sdk_library", sdkLibraryMutator).Parallel()
 	})
 	ctx.RegisterPreSingletonType("overlay", android.SingletonFactoryAdaptor(OverlaySingletonFactory))
@@ -141,19 +143,25 @@
 	}
 
 	mockFS := map[string][]byte{
-		"Android.bp":     []byte(bp),
-		"a.java":         nil,
-		"b.java":         nil,
-		"c.java":         nil,
-		"b.kt":           nil,
-		"a.jar":          nil,
-		"b.jar":          nil,
-		"java-res/a/a":   nil,
-		"java-res/b/b":   nil,
-		"java-res2/a":    nil,
-		"java-fg/a.java": nil,
-		"java-fg/b.java": nil,
-		"java-fg/c.java": nil,
+		"Android.bp":             []byte(bp),
+		"a.java":                 nil,
+		"b.java":                 nil,
+		"c.java":                 nil,
+		"b.kt":                   nil,
+		"a.jar":                  nil,
+		"b.jar":                  nil,
+		"java-res/a/a":           nil,
+		"java-res/b/b":           nil,
+		"java-res2/a":            nil,
+		"java-fg/a.java":         nil,
+		"java-fg/b.java":         nil,
+		"java-fg/c.java":         nil,
+		"api/current.txt":        nil,
+		"api/removed.txt":        nil,
+		"api/system-current.txt": nil,
+		"api/system-removed.txt": nil,
+		"api/test-current.txt":   nil,
+		"api/test-removed.txt":   nil,
 
 		"prebuilts/sdk/14/public/android.jar":         nil,
 		"prebuilts/sdk/14/public/framework.aidl":      nil,
@@ -163,6 +171,19 @@
 		"prebuilts/sdk/current/public/core.jar":       nil,
 		"prebuilts/sdk/current/system/android.jar":    nil,
 		"prebuilts/sdk/current/test/android.jar":      nil,
+		"prebuilts/sdk/28/public/api/foo.txt":         nil,
+		"prebuilts/sdk/28/system/api/foo.txt":         nil,
+		"prebuilts/sdk/28/test/api/foo.txt":           nil,
+		"prebuilts/sdk/28/public/api/foo-removed.txt": nil,
+		"prebuilts/sdk/28/system/api/foo-removed.txt": nil,
+		"prebuilts/sdk/28/test/api/foo-removed.txt":   nil,
+		"prebuilts/sdk/28/public/api/bar.txt":         nil,
+		"prebuilts/sdk/28/system/api/bar.txt":         nil,
+		"prebuilts/sdk/28/test/api/bar.txt":           nil,
+		"prebuilts/sdk/28/public/api/bar-removed.txt": nil,
+		"prebuilts/sdk/28/system/api/bar-removed.txt": nil,
+		"prebuilts/sdk/28/test/api/bar-removed.txt":   nil,
+		"prebuilts/sdk/Android.bp":                    []byte(`prebuilt_apis { name: "prebuilt_apis",}`),
 
 		// For framework-res, which is an implicit dependency for framework
 		"AndroidManifest.xml":                   nil,
@@ -196,7 +217,7 @@
 
 func run(t *testing.T, ctx *android.TestContext, config android.Config) {
 	t.Helper()
-	_, errs := ctx.ParseFileList(".", []string{"Android.bp"})
+	_, errs := ctx.ParseFileList(".", []string{"Android.bp", "prebuilts/sdk/Android.bp"})
 	android.FailIfErrored(t, errs)
 	_, errs = ctx.PrepareBuildActions(config)
 	android.FailIfErrored(t, errs)
@@ -1038,10 +1059,15 @@
 	ctx.ModuleForTests("foo", "android_common")
 	ctx.ModuleForTests("foo"+sdkStubsLibrarySuffix, "android_common")
 	ctx.ModuleForTests("foo"+sdkStubsLibrarySuffix+sdkSystemApiSuffix, "android_common")
+	ctx.ModuleForTests("foo"+sdkStubsLibrarySuffix+sdkTestApiSuffix, "android_common")
 	ctx.ModuleForTests("foo"+sdkDocsSuffix, "android_common")
 	ctx.ModuleForTests("foo"+sdkDocsSuffix+sdkSystemApiSuffix, "android_common")
+	ctx.ModuleForTests("foo"+sdkDocsSuffix+sdkTestApiSuffix, "android_common")
 	ctx.ModuleForTests("foo"+sdkImplLibrarySuffix, "android_common")
 	ctx.ModuleForTests("foo"+sdkXmlFileSuffix, "android_common")
+	ctx.ModuleForTests("foo.api.public.28", "")
+	ctx.ModuleForTests("foo.api.system.28", "")
+	ctx.ModuleForTests("foo.api.test.28", "")
 
 	bazJavac := ctx.ModuleForTests("baz", "android_common").Rule("javac")
 	// tests if baz is actually linked to the stubs lib
diff --git a/java/prebuilt_apis.go b/java/prebuilt_apis.go
new file mode 100644
index 0000000..50318bb
--- /dev/null
+++ b/java/prebuilt_apis.go
@@ -0,0 +1,140 @@
+// Copyright 2018 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package java
+
+import (
+	"android/soong/android"
+	"sort"
+	"strconv"
+	"strings"
+
+	"github.com/google/blueprint/proptools"
+)
+
+// prebuilt_apis is a meta-module that generates filegroup modules for all
+// API txt files found under the directory where the Android.bp is located.
+// Specificaly, an API file located at ./<ver>/<scope>/api/<module>.txt
+// generates a filegroup module named <module>-api.<scope>.<ver>.
+//
+// It also creates <module>-api.<scope>.latest for the lastest <ver>.
+//
+func init() {
+	android.RegisterModuleType("prebuilt_apis", prebuiltApisFactory)
+
+	android.PreArchMutators(func(ctx android.RegisterMutatorsContext) {
+		ctx.TopDown("prebuilt_apis", prebuiltApisMutator).Parallel()
+	})
+}
+
+type prebuiltApis struct {
+	android.ModuleBase
+}
+
+func (module *prebuiltApis) DepsMutator(ctx android.BottomUpMutatorContext) {
+	// no need to implement
+}
+
+func (module *prebuiltApis) GenerateAndroidBuildActions(ctx android.ModuleContext) {
+	// no need to implement
+}
+
+func parseApiFilePath(ctx android.BaseModuleContext, path string) (module string, apiver int, scope string) {
+	elements := strings.Split(path, "/")
+	ver, err := strconv.Atoi(elements[0])
+	if err != nil {
+		ctx.ModuleErrorf("invalid version %q found in path: %q", elements[0], path)
+		return
+	}
+	apiver = ver
+
+	scope = elements[1]
+	if scope != "public" && scope != "system" && scope != "test" {
+		ctx.ModuleErrorf("invalid scope %q found in path: %q", scope, path)
+		return
+	}
+
+	// elements[2] is string literal "api". skipping.
+	module = strings.TrimSuffix(elements[3], ".txt")
+	return
+}
+
+func createFilegroup(mctx android.TopDownMutatorContext, module string, scope string, apiver string, path string) {
+	fgName := module + ".api." + scope + "." + apiver
+	filegroupProps := struct {
+		Name *string
+		Srcs []string
+	}{}
+	filegroupProps.Name = proptools.StringPtr(fgName)
+	filegroupProps.Srcs = []string{path}
+	mctx.CreateModule(android.ModuleFactoryAdaptor(android.FileGroupFactory), &filegroupProps)
+}
+
+func prebuiltApisMutator(mctx android.TopDownMutatorContext) {
+	if _, ok := mctx.Module().(*prebuiltApis); ok {
+		mydir := mctx.ModuleDir() + "/"
+		// <apiver>/<scope>/api/<module>.txt
+		files, err := mctx.GlobWithDeps(mydir+"*/*/api/*.txt", nil)
+		if err != nil {
+			mctx.ModuleErrorf("failed to glob api txt files under %q: %s", mydir, err)
+		}
+		if len(files) == 0 {
+			mctx.ModuleErrorf("no api file found under %q", mydir)
+		}
+
+		// construct a map to find out the latest api file path
+		// for each (<module>, <scope>) pair.
+		type latestApiInfo struct {
+			module string
+			scope  string
+			apiver int
+			path   string
+		}
+		m := make(map[string]latestApiInfo)
+
+		for _, f := range files {
+			// create a filegroup for each api txt file
+			localPath := strings.TrimPrefix(f, mydir)
+			module, apiver, scope := parseApiFilePath(mctx, localPath)
+			createFilegroup(mctx, module, scope, strconv.Itoa(apiver), localPath)
+
+			// find the latest apiver
+			key := module + "." + scope
+			info, ok := m[key]
+			if !ok {
+				m[key] = latestApiInfo{module, scope, apiver, localPath}
+			} else if apiver > info.apiver {
+				info.apiver = apiver
+				info.path = localPath
+			}
+		}
+		// create filegroups for the latest version of (<module>, <scope>) pairs
+		// sort the keys in order to make build.ninja stable
+		keys := make([]string, 0, len(m))
+		for k := range m {
+			keys = append(keys, k)
+		}
+		sort.Strings(keys)
+		for _, k := range keys {
+			info := m[k]
+			createFilegroup(mctx, info.module, info.scope, "latest", info.path)
+		}
+	}
+}
+
+func prebuiltApisFactory() android.Module {
+	module := &prebuiltApis{}
+	android.InitAndroidModule(module)
+	return module
+}
diff --git a/java/sdk_library.go b/java/sdk_library.go
index ee6998c..ba121ab 100644
--- a/java/sdk_library.go
+++ b/java/sdk_library.go
@@ -67,10 +67,9 @@
 // classpath at runtime if requested via <uses-library>.
 //
 // TODO: these are big features that are currently missing
-// 1) check for API consistency
-// 2) ensuring that apps have appropriate <uses-library> tag
-// 3) disallowing linking to the runtime shared lib
-// 4) HTML generation
+// 1) ensuring that apps have appropriate <uses-library> tag
+// 2) disallowing linking to the runtime shared lib
+// 3) HTML generation
 
 func init() {
 	android.RegisterModuleType("java_sdk_library", sdkLibraryFactory)
@@ -260,21 +259,32 @@
 	return apiTagName
 }
 
-// returns the path (relative to this module) to the API txt file. Files are located
-// ./<api_dir>/<api_level>.txt where <api_level> is either current, system-current, removed,
-// or system-removed.
-func (module *sdkLibrary) apiFilePath(apiLevel string, apiScope apiScope) string {
-	apiDir := "api"
-	apiFile := apiLevel
+func (module *sdkLibrary) latestApiFilegroupName(apiScope apiScope) string {
+	name := ":" + module.BaseModuleName() + ".api."
 	switch apiScope {
+	case apiScopePublic:
+		name = name + "public"
 	case apiScopeSystem:
-		apiFile = "system-" + apiFile
+		name = name + "system"
 	case apiScopeTest:
-		apiFile = "test-" + apiFile
+		name = name + "test"
 	}
-	apiFile = apiFile + ".txt"
+	name = name + ".latest"
+	return name
+}
 
-	return path.Join(apiDir, apiFile)
+func (module *sdkLibrary) latestRemovedApiFilegroupName(apiScope apiScope) string {
+	name := ":" + module.BaseModuleName() + "-removed.api."
+	switch apiScope {
+	case apiScopePublic:
+		name = name + "public"
+	case apiScopeSystem:
+		name = name + "system"
+	case apiScopeTest:
+		name = name + "test"
+	}
+	name = name + ".latest"
+	return name
 }
 
 // Creates a static java library that has API stubs
@@ -331,6 +341,10 @@
 		Api_tag_name            *string
 		Api_filename            *string
 		Removed_api_filename    *string
+		Check_api               struct {
+			Current       ApiToCheck
+			Last_released ApiToCheck
+		}
 	}{}
 
 	props.Name = proptools.StringPtr(module.docsName(apiScope))
@@ -355,7 +369,6 @@
 	// List of APIs identified from the provided source files are created. They are later
 	// compared against to the not-yet-released (a.k.a current) list of APIs and to the
 	// last-released (a.k.a numbered) list of API.
-	// TODO: If any incompatible change is detected, break the build
 	currentApiFileName := "current.txt"
 	removedApiFileName := "removed.txt"
 	switch apiScope {
@@ -368,12 +381,31 @@
 	}
 	currentApiFileName = path.Join("api", currentApiFileName)
 	removedApiFileName = path.Join("api", removedApiFileName)
+	// TODO(jiyong): remove these three props
 	props.Api_tag_name = proptools.StringPtr(module.apiTagName(apiScope))
-	// Note: the exact names of these two are not important because they are always
-	// referenced by the make variable $(INTERNAL_PLATFORM_<TAG_NAME>_API_FILE)
 	props.Api_filename = proptools.StringPtr(currentApiFileName)
 	props.Removed_api_filename = proptools.StringPtr(removedApiFileName)
 
+	// check against the not-yet-release API
+	props.Check_api.Current.Api_file = proptools.StringPtr(currentApiFileName)
+	props.Check_api.Current.Removed_api_file = proptools.StringPtr(removedApiFileName)
+	// any change is reported as error
+	props.Check_api.Current.Args = proptools.StringPtr("-error 2 -error 3 -error 4 -error 5 " +
+		"-error 6 -error 7 -error 8 -error 9 -error 10 -error 11 -error 12 -error 13 " +
+		"-error 14 -error 15 -error 16 -error 17 -error 18 -error 19 -error 20 " +
+		"-error 21 -error 23 -error 24 -error 25 -error 26 -error 27")
+
+	// check against the latest released API
+	props.Check_api.Last_released.Api_file = proptools.StringPtr(
+		module.latestApiFilegroupName(apiScope))
+	props.Check_api.Last_released.Removed_api_file = proptools.StringPtr(
+		module.latestRemovedApiFilegroupName(apiScope))
+	// backward incompatible changes are reported as error
+	props.Check_api.Last_released.Args = proptools.StringPtr("-hide 2 -hide 3 -hide 4 -hide 5 " +
+		"-hide 6 -hide 24 -hide 25 -hide 26 -hide 27 " +
+		"-error 7 -error 8 -error 9 -error 10 -error 11 -error 12 -error 13 -error 14 " +
+		"-error 15 -error 16 -error 17 -error 18")
+
 	// Include the part of the framework source. This is required for the case when
 	// API class is extending from the framework class. In that case, doclava needs
 	// to know whether the base class is hidden or not. Since that information is