• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
<lambda>null2  * 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.json.Json
23 import kotlinx.serialization.stringify
24 import org.apache.maven.model.Dependency
25 import org.apache.maven.model.Developer
26 import org.apache.maven.model.License
27 import org.apache.maven.model.Scm
28 import org.apache.maven.model.io.DefaultModelWriter
29 import org.apache.maven.project.MavenProject
30 import org.redundent.kotlin.xml.xml
31 import java.io.File
32 
33 class PrefabPackageBuilder(
34     private val port: Port,
35     private val directory: File,
36     private val sourceDirectory: File,
37     private val publishToMavenLocal: Boolean,
38     private val ndk: Ndk,
39     private val abiToApiMap: Map<Abi, Int>,
40     private val portsByName: Map<String, Port>
41 ) {
42     private val packageDirectory = directory.resolve("aar")
43     private val prefabDirectory = packageDirectory.resolve("prefab")
44     private val modulesDirectory = prefabDirectory.resolve("modules")
45 
46     private val packageComponents = listOf(
47         "com",
48         "android",
49         "ndk",
50         "thirdparty",
51         port.name
52     )
53 
54     private val packageName = packageComponents.joinToString(".")
55     private val groupComponents = packageComponents.dropLast(1)
56     private val groupId = groupComponents.joinToString(".")
57     private val artifactId = packageComponents.last()
58 
59     private val mavenProject = MavenProject().also {
60         it.name = port.name
61         it.description = "The ndkports AAR for ${port.name}."
62         it.url = "https://android.googlesource.com/platform/tools/ndkports"
63         it.groupId = groupId
64         it.artifactId = artifactId
65         it.version = port.mavenVersion
66         it.packaging = "aar"
67         it.licenses = listOf(
68             License().also { license ->
69                 license.name = port.license.name
70                 license.url = port.license.url
71                 license.distribution = "repo"
72             }
73         )
74         it.developers = listOf(
75             Developer().also { developer ->
76                 developer.name = "The Android Open Source Project"
77             }
78         )
79         it.scm = Scm().also { scm ->
80             scm.url = "https://android.googlesource.com/platform/tools/ndkports"
81             scm.connection = "scm:git:https://android.googlesource.com/platform/tools/ndkports"
82         }
83         it.dependencies = port.dependencies.map { depName ->
84             val depPort = portsByName[depName] ?: throw RuntimeException(
85                 "${port.name} depends on unknown port: $depName"
86             )
87             Dependency().also { dep ->
88                 dep.artifactId = depPort.name
89                 dep.groupId = groupId
90                 dep.version = depPort.mavenVersion
91                 dep.type = "aar"
92                 // TODO: Make this an option in the Port.
93                 // We currently only have one dependency from curl to OpenSSL,
94                 // and that's (from the perspective of the AAR consumer), a
95                 // runtime dependency. If we ever have compile dependencies,
96                 // we'll want to make it possible for each port to customize its
97                 // scope.
98                 dep.scope = "runtime"
99             }
100         }
101         // TODO: Issue management?
102     }
103 
104     private fun preparePackageDirectory() {
105         if (packageDirectory.exists()) {
106             packageDirectory.deleteRecursively()
107         }
108         modulesDirectory.mkdirs()
109     }
110 
111     private fun makePackageMetadata() {
112         prefabDirectory.resolve("prefab.json").writeText(
113             Json.stringify(
114                 PackageMetadataV1(
115                     port.name,
116                     schemaVersion = 1,
117                     dependencies = port.dependencies,
118                     version = port.prefabVersion.toString()
119                 )
120             )
121         )
122     }
123 
124     private fun makeModuleMetadata(module: Module, moduleDirectory: File) {
125         moduleDirectory.resolve("module.json").writeText(
126             Json.stringify(
127                 ModuleMetadataV1(
128                     exportLibraries = module.dependencies
129                 )
130             )
131         )
132     }
133 
134     private fun installLibForAbi(module: Module, abi: Abi, libsDir: File) {
135         val libName = "lib${module.name}.so"
136         val installDirectory = libsDir.resolve("android.${abi.abiName}").apply {
137             mkdirs()
138         }
139 
140         directory.resolve("install/$abi/lib/$libName")
141             .copyTo(installDirectory.resolve(libName))
142 
143         val api = abiToApiMap.getOrElse(abi) {
144             throw RuntimeException(
145                 "No API level specified for ${abi.abiName}"
146             )
147         }
148 
149         installDirectory.resolve("abi.json").writeText(
150             Json.stringify(
151                 AndroidAbiMetadata(
152                     abi = abi.abiName,
153                     api = api,
154                     ndk = ndk.version.major,
155                     stl = "c++_shared"
156                 )
157             )
158         )
159     }
160 
161     private fun installLicense() {
162         val src = sourceDirectory.resolve(port.licensePath)
163         val dest = packageDirectory.resolve("META-INF")
164             .resolve(File(port.licensePath).name)
165         src.copyTo(dest)
166     }
167 
168     private fun createAndroidManifest() {
169         packageDirectory.resolve("AndroidManifest.xml")
170             .writeText(xml("manifest") {
171                 attributes(
172                     "xmlns:android" to "http://schemas.android.com/apk/res/android",
173                     "package" to packageName,
174                     "android:versionCode" to 1,
175                     "android:versionName" to "1.0"
176                 )
177 
178                 "uses-sdk" {
179                     attributes(
180                         "android:minSdkVersion" to 16,
181                         "android:targetSdkVersion" to 29
182                     )
183                 }
184             }.toString())
185     }
186 
187     private fun createPom(pomFile: File) {
188         DefaultModelWriter().write(pomFile, null, mavenProject.model)
189     }
190 
191     private fun installToLocalMaven(archive: File, pomFile: File) {
192         val pb = ProcessBuilder(
193             listOf(
194                 "mvn",
195                 "install:install-file",
196                 "-Dfile=$archive",
197                 "-DpomFile=$pomFile"
198             )
199         )
200             .redirectOutput(ProcessBuilder.Redirect.INHERIT)
201             .redirectError(ProcessBuilder.Redirect.INHERIT)
202 
203         return pb.start()
204             .waitFor().let {
205                 if (it != 0) {
206                     throw RuntimeException(
207                         "Failed to install archive to local maven " +
208                                 "repository: $archive $pomFile"
209                     )
210                 }
211             }
212     }
213 
214     private fun createArchive() {
215         val archive = directory.resolve("${port.name}-${port.mavenVersion}.aar")
216         val pomFile = directory.resolve("${port.name}-${port.mavenVersion}.pom")
217         createZipFromDirectory(archive, packageDirectory)
218         createPom(pomFile)
219         if (publishToMavenLocal) {
220             installToLocalMaven(archive, pomFile)
221         }
222     }
223 
224     fun build() {
225         preparePackageDirectory()
226         makePackageMetadata()
227         for (module in port.modules) {
228             val moduleDirectory = modulesDirectory.resolve(module.name).apply {
229                 mkdirs()
230             }
231 
232             makeModuleMetadata(module, moduleDirectory)
233 
234             if (module.includesPerAbi) {
235                 TODO()
236             } else {
237                 // TODO: Perform sanity check.
238                 directory.resolve("install/${Abi.Arm}/include")
239                     .copyRecursively(moduleDirectory.resolve("include"))
240             }
241 
242             val libsDir = moduleDirectory.resolve("libs").apply { mkdirs() }
243             for (abi in Abi.values()) {
244                 installLibForAbi(module, abi, libsDir)
245             }
246         }
247 
248         installLicense()
249 
250         createAndroidManifest()
251         createArchive()
252     }
253 }
254