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