prebuilt_apis.go 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371
  1. // Copyright 2018 Google Inc. All rights reserved.
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. package java
  15. import (
  16. "fmt"
  17. "path"
  18. "strconv"
  19. "strings"
  20. "github.com/google/blueprint/proptools"
  21. "android/soong/android"
  22. "android/soong/genrule"
  23. )
  24. func init() {
  25. RegisterPrebuiltApisBuildComponents(android.InitRegistrationContext)
  26. }
  27. func RegisterPrebuiltApisBuildComponents(ctx android.RegistrationContext) {
  28. ctx.RegisterModuleType("prebuilt_apis", PrebuiltApisFactory)
  29. }
  30. type prebuiltApisProperties struct {
  31. // list of api version directories
  32. Api_dirs []string
  33. // Directory containing finalized api txt files for extension versions.
  34. // Extension versions higher than the base sdk extension version will
  35. // be assumed to be finalized later than all Api_dirs.
  36. Extensions_dir *string
  37. // The next API directory can optionally point to a directory where
  38. // files incompatibility-tracking files are stored for the current
  39. // "in progress" API. Each module present in one of the api_dirs will have
  40. // a <module>-incompatibilities.api.<scope>.latest module created.
  41. Next_api_dir *string
  42. // The sdk_version of java_import modules generated based on jar files.
  43. // Defaults to "current"
  44. Imports_sdk_version *string
  45. // If set to true, compile dex for java_import modules. Defaults to false.
  46. Imports_compile_dex *bool
  47. }
  48. type prebuiltApis struct {
  49. android.ModuleBase
  50. properties prebuiltApisProperties
  51. }
  52. func (module *prebuiltApis) GenerateAndroidBuildActions(ctx android.ModuleContext) {
  53. // no need to implement
  54. }
  55. // parsePrebuiltPath parses the relevant variables out of a variety of paths, e.g.
  56. // <version>/<scope>/<module>.jar
  57. // <version>/<scope>/api/<module>.txt
  58. // extensions/<version>/<scope>/<module>.jar
  59. // extensions/<version>/<scope>/api/<module>.txt
  60. func parsePrebuiltPath(ctx android.LoadHookContext, p string) (module string, version string, scope string) {
  61. elements := strings.Split(p, "/")
  62. scopeIdx := len(elements) - 2
  63. if elements[scopeIdx] == "api" {
  64. scopeIdx--
  65. }
  66. scope = elements[scopeIdx]
  67. if scope != "core" && scope != "public" && scope != "system" && scope != "test" && scope != "module-lib" && scope != "system-server" {
  68. ctx.ModuleErrorf("invalid scope %q found in path: %q", scope, p)
  69. return
  70. }
  71. version = elements[scopeIdx-1]
  72. module = strings.TrimSuffix(path.Base(p), path.Ext(p))
  73. return
  74. }
  75. // parseFinalizedPrebuiltPath is like parsePrebuiltPath, but verifies the version is numeric (a finalized version).
  76. func parseFinalizedPrebuiltPath(ctx android.LoadHookContext, p string) (module string, version int, scope string) {
  77. module, v, scope := parsePrebuiltPath(ctx, p)
  78. version, err := strconv.Atoi(v)
  79. if err != nil {
  80. ctx.ModuleErrorf("Found finalized API files in non-numeric dir '%v'", v)
  81. return
  82. }
  83. return
  84. }
  85. func prebuiltApiModuleName(mctx android.LoadHookContext, module, scope, version string) string {
  86. return fmt.Sprintf("%s_%s_%s_%s", mctx.ModuleName(), scope, version, module)
  87. }
  88. func hasBazelPrebuilt(module string) bool {
  89. return module == "android" || module == "core-for-system-modules"
  90. }
  91. func bazelPrebuiltApiModuleName(module, scope, version string) string {
  92. bazelModule := module
  93. switch module {
  94. case "android":
  95. bazelModule = "android_jar"
  96. case "core-for-system-modules":
  97. bazelModule = "core_jar"
  98. }
  99. bazelVersion := version
  100. if version == "current" {
  101. bazelVersion = strconv.Itoa(android.FutureApiLevelInt)
  102. }
  103. bazelScope := scope
  104. switch scope {
  105. case "module-lib":
  106. bazelScope = "module"
  107. case "system-server":
  108. bazelScope = "system_server"
  109. }
  110. return fmt.Sprintf("//prebuilts/sdk:%s_%s_%s", bazelScope, bazelVersion, bazelModule)
  111. }
  112. func createImport(mctx android.LoadHookContext, module, scope, version, path, sdkVersion string, compileDex bool) {
  113. props := struct {
  114. Name *string
  115. Jars []string
  116. Sdk_version *string
  117. Installable *bool
  118. Compile_dex *bool
  119. Bazel_module android.BazelModuleProperties
  120. }{
  121. Name: proptools.StringPtr(prebuiltApiModuleName(mctx, module, scope, version)),
  122. Jars: []string{path},
  123. Sdk_version: proptools.StringPtr(sdkVersion),
  124. Installable: proptools.BoolPtr(false),
  125. Compile_dex: proptools.BoolPtr(compileDex),
  126. }
  127. if hasBazelPrebuilt(module) {
  128. props.Bazel_module = android.BazelModuleProperties{
  129. Label: proptools.StringPtr(bazelPrebuiltApiModuleName(module, scope, version))}
  130. }
  131. mctx.CreateModule(ImportFactory, &props)
  132. }
  133. func createApiModule(mctx android.LoadHookContext, name string, path string) {
  134. genruleProps := struct {
  135. Name *string
  136. Srcs []string
  137. Out []string
  138. Cmd *string
  139. }{}
  140. genruleProps.Name = proptools.StringPtr(name)
  141. genruleProps.Srcs = []string{path}
  142. genruleProps.Out = []string{name}
  143. genruleProps.Cmd = proptools.StringPtr("cp $(in) $(out)")
  144. mctx.CreateModule(genrule.GenRuleFactory, &genruleProps)
  145. }
  146. func createLatestApiModuleExtensionVersionFile(mctx android.LoadHookContext, name string, version string) {
  147. genruleProps := struct {
  148. Name *string
  149. Srcs []string
  150. Out []string
  151. Cmd *string
  152. }{}
  153. genruleProps.Name = proptools.StringPtr(name)
  154. genruleProps.Out = []string{name}
  155. genruleProps.Cmd = proptools.StringPtr("echo " + version + " > $(out)")
  156. mctx.CreateModule(genrule.GenRuleFactory, &genruleProps)
  157. }
  158. func createEmptyFile(mctx android.LoadHookContext, name string) {
  159. props := struct {
  160. Name *string
  161. Cmd *string
  162. Out []string
  163. }{}
  164. props.Name = proptools.StringPtr(name)
  165. props.Out = []string{name}
  166. props.Cmd = proptools.StringPtr("touch $(genDir)/" + name)
  167. mctx.CreateModule(genrule.GenRuleFactory, &props)
  168. }
  169. // globApiDirs collects all the files in all api_dirs and all scopes that match the given glob, e.g. '*.jar' or 'api/*.txt'.
  170. // <api-dir>/<scope>/<glob> for all api-dir and scope.
  171. func globApiDirs(mctx android.LoadHookContext, p *prebuiltApis, api_dir_glob string) []string {
  172. var files []string
  173. for _, apiver := range p.properties.Api_dirs {
  174. files = append(files, globScopeDir(mctx, apiver, api_dir_glob)...)
  175. }
  176. return files
  177. }
  178. // globExtensionDirs collects all the files under the extension dir (for all versions and scopes) that match the given glob
  179. // <extension-dir>/<version>/<scope>/<glob> for all version and scope.
  180. func globExtensionDirs(mctx android.LoadHookContext, p *prebuiltApis, extension_dir_glob string) []string {
  181. // <extensions-dir>/<num>/<extension-dir-glob>
  182. return globScopeDir(mctx, *p.properties.Extensions_dir+"/*", extension_dir_glob)
  183. }
  184. // globScopeDir collects all the files in the given subdir across all scopes that match the given glob, e.g. '*.jar' or 'api/*.txt'.
  185. // <subdir>/<scope>/<glob> for all scope.
  186. func globScopeDir(mctx android.LoadHookContext, subdir string, subdir_glob string) []string {
  187. var files []string
  188. dir := mctx.ModuleDir() + "/" + subdir
  189. for _, scope := range []string{"public", "system", "test", "core", "module-lib", "system-server"} {
  190. glob := fmt.Sprintf("%s/%s/%s", dir, scope, subdir_glob)
  191. vfiles, err := mctx.GlobWithDeps(glob, nil)
  192. if err != nil {
  193. mctx.ModuleErrorf("failed to glob %s files under %q: %s", subdir_glob, dir+"/"+scope, err)
  194. }
  195. files = append(files, vfiles...)
  196. }
  197. for i, f := range files {
  198. files[i] = strings.TrimPrefix(f, mctx.ModuleDir()+"/")
  199. }
  200. return files
  201. }
  202. func prebuiltSdkStubs(mctx android.LoadHookContext, p *prebuiltApis) {
  203. // <apiver>/<scope>/<module>.jar
  204. files := globApiDirs(mctx, p, "*.jar")
  205. sdkVersion := proptools.StringDefault(p.properties.Imports_sdk_version, "current")
  206. compileDex := proptools.BoolDefault(p.properties.Imports_compile_dex, false)
  207. for _, f := range files {
  208. // create a Import module for each jar file
  209. module, version, scope := parsePrebuiltPath(mctx, f)
  210. createImport(mctx, module, scope, version, f, sdkVersion, compileDex)
  211. if module == "core-for-system-modules" {
  212. createSystemModules(mctx, version, scope)
  213. }
  214. }
  215. }
  216. func createSystemModules(mctx android.LoadHookContext, version, scope string) {
  217. props := struct {
  218. Name *string
  219. Libs []string
  220. }{}
  221. props.Name = proptools.StringPtr(prebuiltApiModuleName(mctx, "system_modules", scope, version))
  222. props.Libs = append(props.Libs, prebuiltApiModuleName(mctx, "core-for-system-modules", scope, version))
  223. mctx.CreateModule(systemModulesImportFactory, &props)
  224. }
  225. func PrebuiltApiModuleName(module, scope, version string) string {
  226. return module + ".api." + scope + "." + version
  227. }
  228. func prebuiltApiFiles(mctx android.LoadHookContext, p *prebuiltApis) {
  229. // <apiver>/<scope>/api/<module>.txt
  230. apiLevelFiles := globApiDirs(mctx, p, "api/*.txt")
  231. if len(apiLevelFiles) == 0 {
  232. mctx.ModuleErrorf("no api file found under %q", mctx.ModuleDir())
  233. }
  234. // Create modules for all (<module>, <scope, <version>) triplets,
  235. for _, f := range apiLevelFiles {
  236. module, version, scope := parseFinalizedPrebuiltPath(mctx, f)
  237. createApiModule(mctx, PrebuiltApiModuleName(module, scope, strconv.Itoa(version)), f)
  238. }
  239. // Figure out the latest version of each module/scope
  240. type latestApiInfo struct {
  241. module, scope, path string
  242. version int
  243. isExtensionApiFile bool
  244. }
  245. getLatest := func(files []string, isExtensionApiFile bool) map[string]latestApiInfo {
  246. m := make(map[string]latestApiInfo)
  247. for _, f := range files {
  248. module, version, scope := parseFinalizedPrebuiltPath(mctx, f)
  249. if strings.HasSuffix(module, "incompatibilities") {
  250. continue
  251. }
  252. key := module + "." + scope
  253. info, exists := m[key]
  254. if !exists || version > info.version {
  255. m[key] = latestApiInfo{module, scope, f, version, isExtensionApiFile}
  256. }
  257. }
  258. return m
  259. }
  260. latest := getLatest(apiLevelFiles, false)
  261. if p.properties.Extensions_dir != nil {
  262. extensionApiFiles := globExtensionDirs(mctx, p, "api/*.txt")
  263. for k, v := range getLatest(extensionApiFiles, true) {
  264. if _, exists := latest[k]; !exists {
  265. mctx.ModuleErrorf("Module %v finalized for extension %d but never during an API level; likely error", v.module, v.version)
  266. }
  267. // The extension version is always at least as new as the last sdk int version (potentially identical)
  268. latest[k] = v
  269. }
  270. }
  271. // Sort the keys in order to make build.ninja stable
  272. for _, k := range android.SortedKeys(latest) {
  273. info := latest[k]
  274. name := PrebuiltApiModuleName(info.module, info.scope, "latest")
  275. latestExtensionVersionModuleName := PrebuiltApiModuleName(info.module, info.scope, "latest.extension_version")
  276. if info.isExtensionApiFile {
  277. createLatestApiModuleExtensionVersionFile(mctx, latestExtensionVersionModuleName, strconv.Itoa(info.version))
  278. } else {
  279. createLatestApiModuleExtensionVersionFile(mctx, latestExtensionVersionModuleName, "-1")
  280. }
  281. createApiModule(mctx, name, info.path)
  282. }
  283. // Create incompatibilities tracking files for all modules, if we have a "next" api.
  284. incompatibilities := make(map[string]bool)
  285. if nextApiDir := String(p.properties.Next_api_dir); nextApiDir != "" {
  286. files := globScopeDir(mctx, nextApiDir, "api/*incompatibilities.txt")
  287. for _, f := range files {
  288. filename, _, scope := parsePrebuiltPath(mctx, f)
  289. referencedModule := strings.TrimSuffix(filename, "-incompatibilities")
  290. createApiModule(mctx, PrebuiltApiModuleName(referencedModule+"-incompatibilities", scope, "latest"), f)
  291. incompatibilities[referencedModule+"."+scope] = true
  292. }
  293. }
  294. // Create empty incompatibilities files for remaining modules
  295. for _, k := range android.SortedKeys(latest) {
  296. if _, ok := incompatibilities[k]; !ok {
  297. createEmptyFile(mctx, PrebuiltApiModuleName(latest[k].module+"-incompatibilities", latest[k].scope, "latest"))
  298. }
  299. }
  300. }
  301. func createPrebuiltApiModules(mctx android.LoadHookContext) {
  302. if p, ok := mctx.Module().(*prebuiltApis); ok {
  303. prebuiltApiFiles(mctx, p)
  304. prebuiltSdkStubs(mctx, p)
  305. }
  306. }
  307. // prebuilt_apis is a meta-module that generates modules for all API txt files
  308. // found under the directory where the Android.bp is located.
  309. // Specifically, an API file located at ./<ver>/<scope>/api/<module>.txt
  310. // generates a module named <module>-api.<scope>.<ver>.
  311. //
  312. // It also creates <module>-api.<scope>.latest for the latest <ver>.
  313. //
  314. // Similarly, it generates a java_import for all API .jar files found under the
  315. // directory where the Android.bp is located. Specifically, an API file located
  316. // at ./<ver>/<scope>/api/<module>.jar generates a java_import module named
  317. // <prebuilt-api-module>_<scope>_<ver>_<module>, and for SDK versions >= 30
  318. // a java_system_modules module named
  319. // <prebuilt-api-module>_public_<ver>_system_modules
  320. func PrebuiltApisFactory() android.Module {
  321. module := &prebuiltApis{}
  322. module.AddProperties(&module.properties)
  323. android.InitAndroidModule(module)
  324. android.AddLoadHook(module, createPrebuiltApiModules)
  325. return module
  326. }