• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2019 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.ndkports
18 
19 import com.google.prefab.api.AndroidAbiMetadata
20 import com.google.prefab.api.ModuleMetadataV1
21 import com.google.prefab.api.PackageMetadataV1
22 import kotlinx.serialization.encodeToString
23 import kotlinx.serialization.json.Json
24 import org.redundent.kotlin.xml.xml
25 import java.io.File
26 import java.io.Serializable
27 
28 data class PackageData(
29     val name: String,
30     val mavenVersion: String,
31     val prefabVersion: CMakeCompatibleVersion,
32     val minSdkVersion: Int,
33     val licensePath: String,
34     val modules: List<ModuleDescription>,
35     val dependencies: Map<String, String>,
36 )
37 
38 /**
39  * A module exported by the package.
40  *
41  * As currently implemented by ndkports, one module is exactly one library.
42  * Prefab supports header-only libraries, but ndkports does not support these
43  * yet.
44  *
45  * Static libraries are not currently supported by ndkports.
46  *
47  * @property[name] The name of the module. Note that currently the name of the
48  * installed library file must be exactly `lib$name.so`.
49  * @property[includesPerAbi] Set to true if a different set of headers should be
50  * exposed per-ABI. Not currently implemented.
51  * @property[dependencies] A list of other modules required by this module, in
52  * the format described by https://google.github.io/prefab/.
53  */
54 data class ModuleDescription(
55     val name: String,
56     val static: Boolean,
57     val headerOnly: Boolean,
58     val includesPerAbi: Boolean,
59     val dependencies: List<String>,
60 ) : Serializable
61 
62 class PrefabPackageBuilder(
63     private val packageData: PackageData,
64     private val packageDirectory: File,
65     private val directory: File,
66     private val sourceDirectory: File,
67     private val ndk: Ndk,
68 ) {
69     private val prefabDirectory = packageDirectory.resolve("prefab")
70     private val modulesDirectory = prefabDirectory.resolve("modules")
71 
72     // TODO: Get from gradle.
73     private val packageName = "com.android.ndk.thirdparty.${packageData.name}"
74 
preparePackageDirectorynull75     private fun preparePackageDirectory() {
76         if (packageDirectory.exists()) {
77             packageDirectory.deleteRecursively()
78         }
79         modulesDirectory.mkdirs()
80     }
81 
makePackageMetadatanull82     private fun makePackageMetadata() {
83         prefabDirectory.resolve("prefab.json").writeText(
84             Json.encodeToString(
85                 PackageMetadataV1(
86                     packageData.name,
87                     schemaVersion = 1,
88                     dependencies = packageData.dependencies.keys.toList(),
89                     version = packageData.prefabVersion.toString()
90                 )
91             )
92         )
93     }
94 
makeModuleMetadatanull95     private fun makeModuleMetadata(module: ModuleDescription, moduleDirectory: File) {
96         moduleDirectory.resolve("module.json").writeText(
97             Json.encodeToString(
98                 ModuleMetadataV1(
99                     exportLibraries = module.dependencies
100                 )
101             )
102         )
103     }
104 
installLibForAbinull105     private fun installLibForAbi(module: ModuleDescription, abi: Abi, libsDir: File) {
106         val extension = if (module.static) "a" else "so"
107         val libName = "lib${module.name}.${extension}"
108         val installDirectory = libsDir.resolve("android.${abi.abiName}").apply {
109             mkdirs()
110         }
111 
112         directory.resolve("$abi/lib/$libName")
113             .copyTo(installDirectory.resolve(libName))
114 
115         installDirectory.resolve("abi.json").writeText(
116             Json.encodeToString(
117                 AndroidAbiMetadata(
118                     abi = abi.abiName,
119                     api = abi.adjustMinSdkVersion(packageData.minSdkVersion),
120                     ndk = ndk.version.major,
121                     stl = "c++_shared"
122                 )
123             )
124         )
125     }
126 
installLicensenull127     private fun installLicense() {
128         val src = sourceDirectory.resolve(packageData.licensePath)
129         val dest = packageDirectory.resolve("META-INF")
130             .resolve(File(packageData.licensePath).name)
131         src.copyTo(dest)
132     }
133 
createAndroidManifestnull134     private fun createAndroidManifest() {
135         packageDirectory.resolve("AndroidManifest.xml")
136             .writeText(xml("manifest") {
137                 attributes(
138                     "xmlns:android" to "http://schemas.android.com/apk/res/android",
139                     "package" to packageName,
140                     "android:versionCode" to 1,
141                     "android:versionName" to "1.0"
142                 )
143 
144                 "uses-sdk" {
145                     attributes(
146                         "android:minSdkVersion" to packageData.minSdkVersion,
147                         "android:targetSdkVersion" to 29
148                     )
149                 }
150             }.toString())
151     }
152 
buildnull153     fun build() {
154         preparePackageDirectory()
155         makePackageMetadata()
156         for (module in packageData.modules) {
157             val moduleDirectory = modulesDirectory.resolve(module.name).apply {
158                 mkdirs()
159             }
160 
161             makeModuleMetadata(module, moduleDirectory)
162 
163             if (module.includesPerAbi) {
164                 TODO()
165             } else {
166                 // TODO: Check that headers are actually identical across ABIs.
167                 directory.resolve("${Abi.Arm}/include")
168                     .copyRecursively(moduleDirectory.resolve("include"))
169             }
170 
171             if (!module.headerOnly) {
172                 val libsDir = moduleDirectory.resolve("libs").apply { mkdirs() }
173                 for (abi in Abi.values()) {
174                     installLibForAbi(module, abi, libsDir)
175                 }
176             }
177         }
178 
179         installLicense()
180 
181         createAndroidManifest()
182     }
183 }
184