2018-05-12 15:29:12 +02:00
// 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 (
2021-01-22 16:05:32 +01:00
"fmt"
2022-02-15 13:55:11 +01:00
"path"
2021-01-22 16:05:04 +01:00
"strconv"
2018-05-12 15:29:12 +02:00
"strings"
"github.com/google/blueprint/proptools"
2020-05-15 03:05:32 +02:00
"android/soong/android"
2021-01-22 16:05:32 +01:00
"android/soong/genrule"
2018-05-12 15:29:12 +02:00
)
func init ( ) {
2019-12-19 12:25:19 +01:00
RegisterPrebuiltApisBuildComponents ( android . InitRegistrationContext )
}
2018-05-12 15:29:12 +02:00
2019-12-19 12:25:19 +01:00
func RegisterPrebuiltApisBuildComponents ( ctx android . RegistrationContext ) {
ctx . RegisterModuleType ( "prebuilt_apis" , PrebuiltApisFactory )
2018-05-12 15:29:12 +02:00
}
2018-06-21 06:47:17 +02:00
type prebuiltApisProperties struct {
// list of api version directories
Api_dirs [ ] string
2020-08-12 23:42:30 +02:00
2022-02-15 13:55:11 +01:00
// Directory containing finalized api txt files for extension versions.
// Extension versions higher than the base sdk extension version will
// be assumed to be finalized later than all Api_dirs.
Extensions_dir * string
2021-01-22 16:05:32 +01:00
// The next API directory can optionally point to a directory where
// files incompatibility-tracking files are stored for the current
// "in progress" API. Each module present in one of the api_dirs will have
// a <module>-incompatibilities.api.<scope>.latest module created.
Next_api_dir * string
2020-08-12 23:42:30 +02:00
// The sdk_version of java_import modules generated based on jar files.
// Defaults to "current"
Imports_sdk_version * string
2020-09-02 17:37:49 +02:00
// If set to true, compile dex for java_import modules. Defaults to false.
Imports_compile_dex * bool
2023-08-25 20:02:13 +02:00
// If set to true, allow incremental platform API of the form MM.m where MM is the major release
// version corresponding to the API level/SDK_INT and m is an incremental release version
// (e.g. API changes associated with QPR). Defaults to false.
Allow_incremental_platform_api * bool
2018-06-21 06:47:17 +02:00
}
2018-05-12 15:29:12 +02:00
type prebuiltApis struct {
android . ModuleBase
2018-06-21 06:47:17 +02:00
properties prebuiltApisProperties
2018-05-12 15:29:12 +02:00
}
func ( module * prebuiltApis ) GenerateAndroidBuildActions ( ctx android . ModuleContext ) {
// no need to implement
}
2022-02-15 13:55:11 +01:00
// parsePrebuiltPath parses the relevant variables out of a variety of paths, e.g.
// <version>/<scope>/<module>.jar
// <version>/<scope>/api/<module>.txt
2023-08-25 20:02:13 +02:00
// *Note when using incremental platform API, <version> may be of the form MM.m where MM is the
// API level and m is an incremental release, otherwise <version> is a single integer corresponding to the API level only.
2022-02-15 13:55:11 +01:00
// extensions/<version>/<scope>/<module>.jar
// extensions/<version>/<scope>/api/<module>.txt
func parsePrebuiltPath ( ctx android . LoadHookContext , p string ) ( module string , version string , scope string ) {
elements := strings . Split ( p , "/" )
scopeIdx := len ( elements ) - 2
if elements [ scopeIdx ] == "api" {
scopeIdx --
}
scope = elements [ scopeIdx ]
if scope != "core" && scope != "public" && scope != "system" && scope != "test" && scope != "module-lib" && scope != "system-server" {
ctx . ModuleErrorf ( "invalid scope %q found in path: %q" , scope , p )
return
}
version = elements [ scopeIdx - 1 ]
2018-06-07 14:42:16 +02:00
2022-02-15 13:55:11 +01:00
module = strings . TrimSuffix ( path . Base ( p ) , path . Ext ( p ) )
2018-06-07 14:42:16 +02:00
return
}
2022-02-15 13:55:11 +01:00
// parseFinalizedPrebuiltPath is like parsePrebuiltPath, but verifies the version is numeric (a finalized version).
2023-08-25 20:02:13 +02:00
func parseFinalizedPrebuiltPath ( ctx android . LoadHookContext , p string , allowIncremental bool ) ( module string , version int , release int , scope string ) {
2022-02-15 13:55:11 +01:00
module , v , scope := parsePrebuiltPath ( ctx , p )
2023-08-25 20:02:13 +02:00
if allowIncremental {
parts := strings . Split ( v , "." )
if len ( parts ) != 2 {
ctx . ModuleErrorf ( "Found unexpected version '%v' for incremental prebuilts - expect MM.m format for incremental API with both major (MM) an minor (m) revision." , v )
return
}
sdk , sdk_err := strconv . Atoi ( parts [ 0 ] )
qpr , qpr_err := strconv . Atoi ( parts [ 1 ] )
if sdk_err != nil || qpr_err != nil {
ctx . ModuleErrorf ( "Unable to read version number for incremental prebuilt api '%v'" , v )
return
}
version = sdk
release = qpr
return
}
release = 0
2022-02-15 13:55:11 +01:00
version , err := strconv . Atoi ( v )
if err != nil {
ctx . ModuleErrorf ( "Found finalized API files in non-numeric dir '%v'" , v )
2018-05-12 15:29:12 +02:00
return
}
return
}
2022-02-15 13:55:11 +01:00
func prebuiltApiModuleName ( mctx android . LoadHookContext , module , scope , version string ) string {
return fmt . Sprintf ( "%s_%s_%s_%s" , mctx . ModuleName ( ) , scope , version , module )
2020-05-15 03:05:32 +02:00
}
2023-05-04 16:16:26 +02:00
func hasBazelPrebuilt ( module string ) bool {
return module == "android" || module == "core-for-system-modules"
}
func bazelPrebuiltApiModuleName ( module , scope , version string ) string {
bazelModule := module
switch module {
case "android" :
bazelModule = "android_jar"
case "core-for-system-modules" :
bazelModule = "core_jar"
}
bazelVersion := version
if version == "current" {
bazelVersion = strconv . Itoa ( android . FutureApiLevelInt )
}
bazelScope := scope
switch scope {
case "module-lib" :
bazelScope = "module"
case "system-server" :
bazelScope = "system_server"
}
return fmt . Sprintf ( "//prebuilts/sdk:%s_%s_%s" , bazelScope , bazelVersion , bazelModule )
}
2022-02-15 13:55:11 +01:00
func createImport ( mctx android . LoadHookContext , module , scope , version , path , sdkVersion string , compileDex bool ) {
2018-06-07 14:42:16 +02:00
props := struct {
2023-05-04 16:16:26 +02:00
Name * string
Jars [ ] string
Sdk_version * string
Installable * bool
Compile_dex * bool
Bazel_module android . BazelModuleProperties
} {
Name : proptools . StringPtr ( prebuiltApiModuleName ( mctx , module , scope , version ) ) ,
Jars : [ ] string { path } ,
Sdk_version : proptools . StringPtr ( sdkVersion ) ,
Installable : proptools . BoolPtr ( false ) ,
Compile_dex : proptools . BoolPtr ( compileDex ) ,
}
if hasBazelPrebuilt ( module ) {
props . Bazel_module = android . BazelModuleProperties {
Label : proptools . StringPtr ( bazelPrebuiltApiModuleName ( module , scope , version ) ) }
}
2019-09-25 20:33:01 +02:00
mctx . CreateModule ( ImportFactory , & props )
2018-06-07 14:42:16 +02:00
}
2021-02-17 15:21:33 +01:00
func createApiModule ( mctx android . LoadHookContext , name string , path string ) {
genruleProps := struct {
2018-05-12 15:29:12 +02:00
Name * string
Srcs [ ] string
2021-02-17 15:21:33 +01:00
Out [ ] string
Cmd * string
2018-05-12 15:29:12 +02:00
} { }
2021-02-17 15:21:33 +01:00
genruleProps . Name = proptools . StringPtr ( name )
genruleProps . Srcs = [ ] string { path }
genruleProps . Out = [ ] string { name }
genruleProps . Cmd = proptools . StringPtr ( "cp $(in) $(out)" )
mctx . CreateModule ( genrule . GenRuleFactory , & genruleProps )
2018-05-12 15:29:12 +02:00
}
2023-04-21 18:30:03 +02:00
func createLatestApiModuleExtensionVersionFile ( mctx android . LoadHookContext , name string , version string ) {
genruleProps := struct {
Name * string
Srcs [ ] string
Out [ ] string
Cmd * string
} { }
genruleProps . Name = proptools . StringPtr ( name )
genruleProps . Out = [ ] string { name }
genruleProps . Cmd = proptools . StringPtr ( "echo " + version + " > $(out)" )
mctx . CreateModule ( genrule . GenRuleFactory , & genruleProps )
}
2021-01-22 16:05:32 +01:00
func createEmptyFile ( mctx android . LoadHookContext , name string ) {
props := struct {
Name * string
Cmd * string
Out [ ] string
} { }
props . Name = proptools . StringPtr ( name )
props . Out = [ ] string { name }
props . Cmd = proptools . StringPtr ( "touch $(genDir)/" + name )
mctx . CreateModule ( genrule . GenRuleFactory , & props )
}
2022-02-15 13:55:11 +01:00
// globApiDirs collects all the files in all api_dirs and all scopes that match the given glob, e.g. '*.jar' or 'api/*.txt'.
// <api-dir>/<scope>/<glob> for all api-dir and scope.
func globApiDirs ( mctx android . LoadHookContext , p * prebuiltApis , api_dir_glob string ) [ ] string {
2018-06-21 06:47:17 +02:00
var files [ ] string
2020-08-12 23:42:30 +02:00
for _ , apiver := range p . properties . Api_dirs {
2022-02-15 13:55:11 +01:00
files = append ( files , globScopeDir ( mctx , apiver , api_dir_glob ) ... )
2021-01-22 16:05:32 +01:00
}
return files
}
2022-02-15 13:55:11 +01:00
// globExtensionDirs collects all the files under the extension dir (for all versions and scopes) that match the given glob
// <extension-dir>/<version>/<scope>/<glob> for all version and scope.
func globExtensionDirs ( mctx android . LoadHookContext , p * prebuiltApis , extension_dir_glob string ) [ ] string {
// <extensions-dir>/<num>/<extension-dir-glob>
return globScopeDir ( mctx , * p . properties . Extensions_dir + "/*" , extension_dir_glob )
}
2022-02-15 13:55:11 +01:00
// globScopeDir collects all the files in the given subdir across all scopes that match the given glob, e.g. '*.jar' or 'api/*.txt'.
// <subdir>/<scope>/<glob> for all scope.
func globScopeDir ( mctx android . LoadHookContext , subdir string , subdir_glob string ) [ ] string {
2021-01-22 16:05:32 +01:00
var files [ ] string
dir := mctx . ModuleDir ( ) + "/" + subdir
for _ , scope := range [ ] string { "public" , "system" , "test" , "core" , "module-lib" , "system-server" } {
2022-02-15 13:55:11 +01:00
glob := fmt . Sprintf ( "%s/%s/%s" , dir , scope , subdir_glob )
2021-01-22 16:05:32 +01:00
vfiles , err := mctx . GlobWithDeps ( glob , nil )
if err != nil {
2022-02-15 13:55:11 +01:00
mctx . ModuleErrorf ( "failed to glob %s files under %q: %s" , subdir_glob , dir + "/" + scope , err )
2018-06-21 06:47:17 +02:00
}
2021-01-22 16:05:32 +01:00
files = append ( files , vfiles ... )
2018-06-07 14:42:16 +02:00
}
2022-02-15 13:55:11 +01:00
for i , f := range files {
files [ i ] = strings . TrimPrefix ( f , mctx . ModuleDir ( ) + "/" )
}
2019-02-14 03:49:24 +01:00
return files
}
2020-08-12 23:42:30 +02:00
func prebuiltSdkStubs ( mctx android . LoadHookContext , p * prebuiltApis ) {
2019-02-14 03:49:24 +01:00
// <apiver>/<scope>/<module>.jar
2022-02-15 13:55:11 +01:00
files := globApiDirs ( mctx , p , "*.jar" )
2020-08-12 23:42:30 +02:00
2020-09-02 17:37:49 +02:00
sdkVersion := proptools . StringDefault ( p . properties . Imports_sdk_version , "current" )
compileDex := proptools . BoolDefault ( p . properties . Imports_compile_dex , false )
2018-05-12 15:29:12 +02:00
2018-06-07 14:42:16 +02:00
for _ , f := range files {
// create a Import module for each jar file
2022-02-15 13:55:11 +01:00
module , version , scope := parsePrebuiltPath ( mctx , f )
createImport ( mctx , module , scope , version , f , sdkVersion , compileDex )
2021-10-28 14:27:37 +02:00
if module == "core-for-system-modules" {
2022-02-15 13:55:11 +01:00
createSystemModules ( mctx , version , scope )
2021-10-28 14:27:37 +02:00
}
2018-06-07 14:42:16 +02:00
}
}
2022-02-15 13:55:11 +01:00
func createSystemModules ( mctx android . LoadHookContext , version , scope string ) {
2020-05-15 03:05:32 +02:00
props := struct {
Name * string
Libs [ ] string
} { }
2022-02-15 13:55:11 +01:00
props . Name = proptools . StringPtr ( prebuiltApiModuleName ( mctx , "system_modules" , scope , version ) )
props . Libs = append ( props . Libs , prebuiltApiModuleName ( mctx , "core-for-system-modules" , scope , version ) )
2020-05-15 03:05:32 +02:00
2021-03-11 08:56:22 +01:00
mctx . CreateModule ( systemModulesImportFactory , & props )
2020-05-15 03:05:32 +02:00
}
2022-05-16 15:10:47 +02:00
func PrebuiltApiModuleName ( module , scope , version string ) string {
return module + ".api." + scope + "." + version
}
2020-08-12 23:42:30 +02:00
func prebuiltApiFiles ( mctx android . LoadHookContext , p * prebuiltApis ) {
2018-06-07 14:42:16 +02:00
// <apiver>/<scope>/api/<module>.txt
2022-02-15 13:55:11 +01:00
apiLevelFiles := globApiDirs ( mctx , p , "api/*.txt" )
if len ( apiLevelFiles ) == 0 {
mctx . ModuleErrorf ( "no api file found under %q" , mctx . ModuleDir ( ) )
2018-06-07 14:42:16 +02:00
}
2021-02-17 15:21:33 +01:00
// Create modules for all (<module>, <scope, <version>) triplets,
2023-08-25 20:02:13 +02:00
allowIncremental := proptools . BoolDefault ( p . properties . Allow_incremental_platform_api , false )
2022-02-15 13:55:11 +01:00
for _ , f := range apiLevelFiles {
2023-08-25 20:02:13 +02:00
module , version , release , scope := parseFinalizedPrebuiltPath ( mctx , f , allowIncremental )
if allowIncremental {
incrementalVersion := strconv . Itoa ( version ) + "." + strconv . Itoa ( release )
createApiModule ( mctx , PrebuiltApiModuleName ( module , scope , incrementalVersion ) , f )
} else {
createApiModule ( mctx , PrebuiltApiModuleName ( module , scope , strconv . Itoa ( version ) ) , f )
}
2022-02-15 13:55:11 +01:00
}
2018-06-07 14:42:16 +02:00
2022-02-15 13:55:11 +01:00
// Figure out the latest version of each module/scope
type latestApiInfo struct {
module , scope , path string
2023-08-25 20:02:13 +02:00
version , release int
2023-04-21 18:30:03 +02:00
isExtensionApiFile bool
2022-02-15 13:55:11 +01:00
}
2023-04-21 18:30:03 +02:00
getLatest := func ( files [ ] string , isExtensionApiFile bool ) map [ string ] latestApiInfo {
2022-02-15 13:55:11 +01:00
m := make ( map [ string ] latestApiInfo )
for _ , f := range files {
2023-08-25 20:02:13 +02:00
module , version , release , scope := parseFinalizedPrebuiltPath ( mctx , f , allowIncremental )
2022-02-15 13:55:11 +01:00
if strings . HasSuffix ( module , "incompatibilities" ) {
continue
}
key := module + "." + scope
info , exists := m [ key ]
2023-08-25 20:02:13 +02:00
if ! exists || version > info . version || ( version == info . version && release > info . release ) {
m [ key ] = latestApiInfo { module , scope , f , version , release , isExtensionApiFile }
2022-02-15 13:55:11 +01:00
}
2021-01-22 16:05:04 +01:00
}
2022-02-15 13:55:11 +01:00
return m
}
2021-01-22 16:05:04 +01:00
2023-04-21 18:30:03 +02:00
latest := getLatest ( apiLevelFiles , false )
2022-02-15 13:55:11 +01:00
if p . properties . Extensions_dir != nil {
extensionApiFiles := globExtensionDirs ( mctx , p , "api/*.txt" )
2023-04-21 18:30:03 +02:00
for k , v := range getLatest ( extensionApiFiles , true ) {
2022-09-20 19:08:49 +02:00
if _ , exists := latest [ k ] ; ! exists {
mctx . ModuleErrorf ( "Module %v finalized for extension %d but never during an API level; likely error" , v . module , v . version )
2022-02-15 13:55:11 +01:00
}
2022-09-20 19:08:49 +02:00
// The extension version is always at least as new as the last sdk int version (potentially identical)
latest [ k ] = v
2018-05-12 15:29:12 +02:00
}
}
2021-01-22 16:05:32 +01:00
2021-01-22 16:05:04 +01:00
// Sort the keys in order to make build.ninja stable
2023-03-01 01:02:16 +01:00
for _ , k := range android . SortedKeys ( latest ) {
2022-02-15 13:55:11 +01:00
info := latest [ k ]
2022-05-16 15:10:47 +02:00
name := PrebuiltApiModuleName ( info . module , info . scope , "latest" )
2023-04-21 18:30:03 +02:00
latestExtensionVersionModuleName := PrebuiltApiModuleName ( info . module , info . scope , "latest.extension_version" )
if info . isExtensionApiFile {
createLatestApiModuleExtensionVersionFile ( mctx , latestExtensionVersionModuleName , strconv . Itoa ( info . version ) )
} else {
createLatestApiModuleExtensionVersionFile ( mctx , latestExtensionVersionModuleName , "-1" )
}
2021-02-17 15:21:33 +01:00
createApiModule ( mctx , name , info . path )
2021-01-22 16:05:32 +01:00
}
// Create incompatibilities tracking files for all modules, if we have a "next" api.
2021-03-09 22:25:02 +01:00
incompatibilities := make ( map [ string ] bool )
2021-01-22 16:05:32 +01:00
if nextApiDir := String ( p . properties . Next_api_dir ) ; nextApiDir != "" {
2022-02-15 13:55:11 +01:00
files := globScopeDir ( mctx , nextApiDir , "api/*incompatibilities.txt" )
2021-01-22 16:05:32 +01:00
for _ , f := range files {
2022-02-15 13:55:11 +01:00
filename , _ , scope := parsePrebuiltPath ( mctx , f )
2021-04-13 20:09:48 +02:00
referencedModule := strings . TrimSuffix ( filename , "-incompatibilities" )
2021-01-22 16:05:32 +01:00
2022-05-16 15:10:47 +02:00
createApiModule ( mctx , PrebuiltApiModuleName ( referencedModule + "-incompatibilities" , scope , "latest" ) , f )
2021-01-22 16:05:32 +01:00
incompatibilities [ referencedModule + "." + scope ] = true
}
2021-03-09 22:25:02 +01:00
}
// Create empty incompatibilities files for remaining modules
2023-03-01 01:02:16 +01:00
for _ , k := range android . SortedKeys ( latest ) {
2021-03-09 22:25:02 +01:00
if _ , ok := incompatibilities [ k ] ; ! ok {
2022-05-16 15:10:47 +02:00
createEmptyFile ( mctx , PrebuiltApiModuleName ( latest [ k ] . module + "-incompatibilities" , latest [ k ] . scope , "latest" ) )
2021-01-22 16:05:32 +01:00
}
2018-06-07 14:42:16 +02:00
}
}
2020-04-09 18:15:44 +02:00
func createPrebuiltApiModules ( mctx android . LoadHookContext ) {
2020-08-12 23:42:30 +02:00
if p , ok := mctx . Module ( ) . ( * prebuiltApis ) ; ok {
prebuiltApiFiles ( mctx , p )
prebuiltSdkStubs ( mctx , p )
2018-06-07 14:42:16 +02:00
}
2018-05-12 15:29:12 +02:00
}
2021-02-17 15:21:33 +01:00
// prebuilt_apis is a meta-module that generates modules for all API txt files
// found under the directory where the Android.bp is located.
2019-03-21 18:48:25 +01:00
// Specifically, an API file located at ./<ver>/<scope>/api/<module>.txt
2021-02-17 15:21:33 +01:00
// generates a module named <module>-api.<scope>.<ver>.
2019-03-21 18:48:25 +01:00
//
// It also creates <module>-api.<scope>.latest for the latest <ver>.
2020-04-09 18:15:44 +02:00
//
// Similarly, it generates a java_import for all API .jar files found under the
// directory where the Android.bp is located. Specifically, an API file located
// at ./<ver>/<scope>/api/<module>.jar generates a java_import module named
2020-05-15 03:05:32 +02:00
// <prebuilt-api-module>_<scope>_<ver>_<module>, and for SDK versions >= 30
// a java_system_modules module named
// <prebuilt-api-module>_public_<ver>_system_modules
2019-02-08 13:00:45 +01:00
func PrebuiltApisFactory ( ) android . Module {
2018-05-12 15:29:12 +02:00
module := & prebuiltApis { }
2018-06-21 06:47:17 +02:00
module . AddProperties ( & module . properties )
2018-05-12 15:29:12 +02:00
android . InitAndroidModule ( module )
2020-04-09 18:15:44 +02:00
android . AddLoadHook ( module , createPrebuiltApiModules )
2018-05-12 15:29:12 +02:00
return module
}