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