diff --git a/support/android/apk/build.gradle b/support/android/apk/build.gradle deleted file mode 100644 index 4ddff3a3b04..00000000000 --- a/support/android/apk/build.gradle +++ /dev/null @@ -1,101 +0,0 @@ -import java.text.SimpleDateFormat - -// Top-level build file where you can add configuration options common to all sub-projects/modules. -plugins { - id 'com.android.application' version '8.7.0' apply false - id 'com.android.library' version '8.7.0' apply false -} - -// Utility methods -ext.getTargetDir = { boolean debug, String arch -> - def basePath = project.rootDir.getParentFile().getParentFile().getParentFile().absolutePath - return basePath + '/target/android/' + getSubTargetDir(debug, arch) -} - -ext.getNativeTargetDir = { boolean debug, String arch -> - def basePath = project.rootDir.getParentFile().getParentFile().getParentFile().absolutePath - return basePath + '/target/' + getSubTargetDir(debug, arch) -} - -ext.getSubTargetDir = { boolean debug, String arch -> - return getRustTarget(arch) + '/' + (debug ? 'debug' : 'release') -} - -ext.getJniLibsPath = { boolean debug, String arch -> - return getTargetDir(debug, arch) + '/jniLibs' -} - -ext.getRustTarget = { String arch -> - switch (arch.toLowerCase()) { - case 'armv7': return 'armv7-linux-androideabi' - case 'arm64': return 'aarch64-linux-android' - case 'x86': return 'i686-linux-android' - case 'x64': return 'x86_64-linux-android' - default: throw new GradleException("Invalid target architecture " + arch) - } -} - -ext.getNDKAbi = { String arch -> - switch (arch.toLowerCase()) { - case 'armv7': return 'armeabi-v7a' - case 'arm64': return 'arm64-v8a' - case 'x86': return 'x86' - case 'x64': return 'x86_64' - default: throw new GradleException("Invalid target architecture " + arch) - } -} - -ext.getNdkDir = { -> - // Read environment variable used in rust build system - String ndkRoot = System.getenv('ANDROID_NDK_ROOT') - if (ndkRoot == null) { - // Fallback to ndkDir in local.properties - def rootDir = project.rootDir - def localProperties = new File(rootDir, "local.properties") - Properties properties = new Properties() - localProperties.withInputStream { instr -> - properties.load(instr) - } - - ndkRoot = properties.getProperty('ndk.dir') - } - - def ndkDir = ndkRoot != null ? new File(ndkRoot) : null - if (!ndkDir || !ndkDir.exists()) { - throw new GradleException("Please set a valid ANDROID_NDK_ROOT environment variable " + - "or ndk.dir path in local.properties file") - } - return ndkDir.absolutePath -} - -ext.getSigningKeyInfo = { -> - def storeFilePath = System.getenv("APK_SIGNING_KEY_STORE_PATH") - if (storeFilePath != null) { - return [ - storeFile: new File(storeFilePath), - storePassword: System.getenv("APK_SIGNING_KEY_STORE_PASS"), - keyAlias: System.getenv("APK_SIGNING_KEY_ALIAS"), - keyPassword: System.getenv("APK_SIGNING_KEY_PASS"), - ] - } else { - return null - } -} - -ext { - // Generate unique version code based on the build date and time to support nightly - // builds. - // - // The version scheme is currently: yyyyMMddXX - // where - // yyyy is the 4 digit year (e.g 2024) - // MMdd is the 2 digit month followed by 2 digit day (e.g 0915 for September 15) - // XX is currently hardcoded to 00, but can be used to distinguish multiple builds within - // the same day. - // - // TODO: check if this interferes with caching of local builds and add option to use - // a static version. - def today = new Date() - def versionCodeString = new SimpleDateFormat("yyyyMMdd00").format(today) - generatedVersionCode = versionCodeString as int -} diff --git a/support/android/apk/build.gradle.kts b/support/android/apk/build.gradle.kts new file mode 100644 index 00000000000..6b2ac10f486 --- /dev/null +++ b/support/android/apk/build.gradle.kts @@ -0,0 +1,5 @@ +// Top-level build file where you can add configuration options common to all sub-projects/modules. +plugins { + id("com.android.application") version "8.7.0" apply false + id("com.android.library") version "8.7.0" apply false +} \ No newline at end of file diff --git a/support/android/apk/buildSrc/.gitignore b/support/android/apk/buildSrc/.gitignore new file mode 100644 index 00000000000..192221b47d1 --- /dev/null +++ b/support/android/apk/buildSrc/.gitignore @@ -0,0 +1,2 @@ +.gradle/ +build/ \ No newline at end of file diff --git a/support/android/apk/buildSrc/build.gradle.kts b/support/android/apk/buildSrc/build.gradle.kts new file mode 100644 index 00000000000..b22ed732fde --- /dev/null +++ b/support/android/apk/buildSrc/build.gradle.kts @@ -0,0 +1,7 @@ +plugins { + `kotlin-dsl` +} + +repositories { + mavenCentral() +} \ No newline at end of file diff --git a/support/android/apk/buildSrc/src/main/kotlin/Android.kt b/support/android/apk/buildSrc/src/main/kotlin/Android.kt new file mode 100644 index 00000000000..4b7f9d36fad --- /dev/null +++ b/support/android/apk/buildSrc/src/main/kotlin/Android.kt @@ -0,0 +1,15 @@ +import java.io.File + +fun getSigningKeyInfo(): Map? { + val storeFilePath = System.getenv("APK_SIGNING_KEY_STORE_PATH") + return if (storeFilePath != null) { + mapOf( + "storeFile" to File(storeFilePath), + "storePassword" to System.getenv("APK_SIGNING_KEY_STORE_PASS"), + "keyAlias" to System.getenv("APK_SIGNING_KEY_ALIAS"), + "keyPassword" to System.getenv("APK_SIGNING_KEY_PASS"), + ) + } else { + null + } +} \ No newline at end of file diff --git a/support/android/apk/buildSrc/src/main/kotlin/Interop.kt b/support/android/apk/buildSrc/src/main/kotlin/Interop.kt new file mode 100644 index 00000000000..79b9694058b --- /dev/null +++ b/support/android/apk/buildSrc/src/main/kotlin/Interop.kt @@ -0,0 +1,71 @@ +import org.gradle.api.GradleException +import org.gradle.api.Project +import java.io.File +import java.util.Locale +import java.util.Properties + +/* +Some functions are extensions to the Project class, as to allow access to its public members. + */ + +fun Project.getTargetDir(debug: Boolean, arch: String): String { + val basePath = project.rootDir.parentFile.parentFile.parentFile.absolutePath + return basePath + "/target/android/" + getSubTargetDir(debug, arch) +} + +fun Project.getNativeTargetDir(debug: Boolean, arch: String): String { + val basePath = project.rootDir.parentFile.parentFile.parentFile.absolutePath + return basePath + "/target/" + getSubTargetDir(debug, arch) +} + +fun getSubTargetDir(debug: Boolean, arch: String): String { + return getRustTarget(arch) + "/" + if (debug) "debug" else "release" +} + +fun Project.getJniLibsPath(debug: Boolean, arch: String): String = + getTargetDir(debug, arch) + "/jniLibs" + +fun getRustTarget(arch: String): String { + return when (arch.lowercase(Locale.getDefault())) { + "armv7" -> "armv7-linux-androideabi" + "arm64" -> "aarch64-linux-android" + "x86" -> "i686-linux-android" + "x64" -> "x86_64-linux-android" + else -> throw GradleException("Invalid target architecture $arch") + } +} + +fun getNDKAbi(arch: String): String { + return when (arch.lowercase(Locale.getDefault())) { + "armv7" -> "armeabi-v7a" + "arm64" -> "arm64-v8a" + "x86" -> "x86" + "x64" -> "x86_64" + else -> throw GradleException("Invalid target architecture $arch") + } +} + +fun Project.getNdkDir(): String { + // Read environment variable used in rust build system + var ndkRoot = System.getenv("ANDROID_NDK_ROOT") + if (ndkRoot == null) { + // Fallback to ndkDir in local.properties + val rootDir = project.rootDir + val localProperties = File(rootDir, "local.properties") + val properties = Properties() + localProperties.inputStream().use { instr -> + properties.load(instr) + } + + ndkRoot = properties.getProperty("ndk.dir") + } + + val ndkDir = if (ndkRoot != null) File(ndkRoot) else null + if (ndkDir == null || !ndkDir.exists()) { + throw GradleException( + "Please set a valid ANDROID_NDK_ROOT environment variable " + + "or ndk.dir path in local.properties file" + ) + } + return ndkDir.absolutePath +} \ No newline at end of file diff --git a/support/android/apk/buildSrc/src/main/kotlin/VersionCode.kt b/support/android/apk/buildSrc/src/main/kotlin/VersionCode.kt new file mode 100644 index 00000000000..fbeaeffdee8 --- /dev/null +++ b/support/android/apk/buildSrc/src/main/kotlin/VersionCode.kt @@ -0,0 +1,20 @@ +import java.text.SimpleDateFormat +import java.util.Date + +// Generate unique version code based on the build date and time to support nightly +// builds. +// +// The version scheme is currently: yyyyMMddXX +// where +// yyyy is the 4 digit year (e.g 2024) +// MMdd is the 2 digit month followed by 2 digit day (e.g 0915 for September 15) +// XX is currently hardcoded to 00, but can be used to distinguish multiple builds within +// the same day. +// +// TODO: check if this interferes with caching of local builds and add option to use +// a static version. +val today = Date() + +val versionCodeString: String = SimpleDateFormat("yyyyMMdd00").format(today) + +val generatedVersionCode = versionCodeString.toInt() \ No newline at end of file diff --git a/support/android/apk/servoapp/build.gradle b/support/android/apk/servoapp/build.gradle deleted file mode 100644 index c4168ff979d..00000000000 --- a/support/android/apk/servoapp/build.gradle +++ /dev/null @@ -1,164 +0,0 @@ -plugins { - id 'com.android.application' -} - -import java.util.regex.Matcher -import java.util.regex.Pattern - -android { - compileSdk 33 - buildToolsVersion = "34.0.0" - - namespace 'org.servo.servoshell' - - buildDir = rootDir.absolutePath + "/../../../target/android/gradle/servoapp" - - defaultConfig { - applicationId "org.servo.servoshell" - minSdk 30 - targetSdk 30 - versionCode generatedVersionCode - versionName "0.0.1" // TODO: Parse Servo's TOML and add git SHA. - } - - compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 - } - - // Share all of that with servoview - flavorDimensions = ["default"] - - productFlavors { - basic { - } - } - - splits { - density { - enable false - } - abi { - enable false - } - } - - sourceSets { - main { - java.srcDirs = ['src/main/java'] - } - } - - - def signingKeyInfo = getSigningKeyInfo() - - if (signingKeyInfo) { - signingConfigs { - release { - storeFile signingKeyInfo.storeFile - storePassword signingKeyInfo.storePassword - keyAlias signingKeyInfo.keyAlias - keyPassword signingKeyInfo.keyPassword - } - } - } - - buildTypes { - debug { - } - - release { - signingConfig signingKeyInfo ? signingConfigs.release : signingConfigs.debug - minifyEnabled false - proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' - } - - // Custom build types - armv7Debug { - initWith(debug) - ndk { - abiFilters getNDKAbi('armv7') - } - } - armv7Release { - initWith(release) - ndk { - abiFilters getNDKAbi('armv7') - } - } - arm64Debug { - initWith(debug) - ndk { - abiFilters getNDKAbi('arm64') - } - } - arm64Release { - initWith(release) - ndk { - abiFilters getNDKAbi('arm64') - } - } - x86Debug { - initWith(debug) - ndk { - abiFilters getNDKAbi('x86') - } - } - x86Release { - initWith(release) - ndk { - abiFilters getNDKAbi('x86') - } - } - x64Debug { - initWith(debug) - ndk { - abiFilters getNDKAbi('x64') - } - } - x64Release { - initWith(release) - ndk { - abiFilters getNDKAbi('x64') - } - } - } - - // Ignore default 'debug' and 'release' build types - variantFilter { variant -> - if(variant.buildType.name == 'release' || variant.buildType.name == 'debug') { - variant.setIgnore(true) - } - } - - project.afterEvaluate { - android.applicationVariants.all { variant -> - Pattern pattern = Pattern.compile(/^[\w\d]+([A-Z][\w\d]+)(Debug|Release)/) - Matcher matcher = pattern.matcher(variant.name) - if (!matcher.find()) { - throw new GradleException("Invalid variant name for output: " + variant.name) - } - def arch = matcher.group(1) - def debug = variant.name.contains("Debug") - def finalFolder = getTargetDir(debug, arch) - def finalFile = new File(finalFolder, "servoapp.apk") - variant.outputs.all { output -> - Task copyAndRenameAPKTask = project.task("copyAndRename${variant.name.capitalize()}APK", type: Copy) { - from output.outputFile.getParent() - into finalFolder - include output.outputFileName - rename(output.outputFileName, finalFile.getName()) - } - variant.assemble.finalizedBy(copyAndRenameAPKTask) - } - } - } -} - -dependencies { - if (findProject(':servoview-local')) { - implementation project(':servoview-local') - } else { - implementation project(':servoview') - } -} diff --git a/support/android/apk/servoapp/build.gradle.kts b/support/android/apk/servoapp/build.gradle.kts new file mode 100644 index 00000000000..269225dfa86 --- /dev/null +++ b/support/android/apk/servoapp/build.gradle.kts @@ -0,0 +1,175 @@ +import java.util.regex.Pattern + +plugins { + id("com.android.application") +} + +android { + compileSdk = 33 + buildToolsVersion = "34.0.0" + + namespace = "org.servo.servoshell" + + layout.buildDirectory = File(rootDir.absolutePath, "/../../../target/android/gradle/servoapp") + + defaultConfig { + applicationId = "org.servo.servoshell" + minSdk = 30 + targetSdk = 33 + versionCode = generatedVersionCode + versionName = "0.0.1" // TODO: Parse Servo"s TOML and add git SHA. + } + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + } + + // Share all of that with servoview + flavorDimensions.add("default") + + productFlavors { + register("basic") { + } + } + + splits { + density { + isEnable = false + } + abi { + isEnable = false + } + } + + sourceSets { + named("main") { + java.srcDirs("src/main/java") + } + } + + val signingKeyInfo = getSigningKeyInfo() + + if (signingKeyInfo != null) { + signingConfigs { + register("release") { + storeFile = signingKeyInfo["storeFile"] as File + storePassword = signingKeyInfo["storePassword"] as String + keyAlias = signingKeyInfo["keyAlias"] as String + keyPassword = signingKeyInfo["keyPassword"] as String + } + } + } + + buildTypes { + debug { + } + + release { + signingConfig = + signingConfigs.getByName(if (signingKeyInfo != null) "release" else "debug") + isMinifyEnabled = false + proguardFiles(getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro") + } + + // Custom build types + + val debug = getByName("debug") + val release = getByName("release") + + + register("armv7Debug") { + initWith(debug) + ndk { + abiFilters.add(getNDKAbi("armv7")) + } + } + register("armv7Release") { + initWith(release) + ndk { + abiFilters.add(getNDKAbi("armv7")) + } + } + register("arm64Debug") { + initWith(debug) + ndk { + abiFilters.add(getNDKAbi("arm64")) + } + } + register("arm64Release") { + initWith(release) + ndk { + abiFilters.add(getNDKAbi("arm64")) + } + } + register("x86Debug") { + initWith(debug) + ndk { + abiFilters.add(getNDKAbi("x86")) + } + } + register("x86Release") { + initWith(release) + ndk { + abiFilters.add(getNDKAbi("x86")) + } + } + register("x64Debug") { + initWith(debug) + ndk { + abiFilters.add(getNDKAbi("x64")) + } + } + register("x64Release") { + initWith(release) + ndk { + abiFilters.add(getNDKAbi("x64")) + } + } + } + + // Ignore default "debug" and "release" build types + androidComponents { + beforeVariants { + if (it.buildType == "release" || it.buildType == "debug") { + it.enable = false + } + } + } + + project.afterEvaluate { + android.applicationVariants.forEach { variant -> + val pattern = Pattern.compile("^[\\w\\d]+([A-Z][\\w\\d]+)(Debug|Release)") + val matcher = pattern.matcher(variant.name) + if (!matcher.find()) { + throw GradleException("Invalid variant name for output: " + variant.name) + } + val arch = matcher.group(1) + val debug = variant.name.contains("Debug") + val finalFolder = getTargetDir(debug, arch) + val finalFile = File(finalFolder, "servoapp.apk") + variant.outputs.forEach { output -> + val copyAndRenameAPKTask = + project.task("copyAndRename${variant.name.capitalize()}APK") { + from(output.outputFile.parent) + into(finalFolder) + include(output.outputFile.name) + rename(output.outputFile.name, finalFile.name) + } + variant.assembleProvider.get().finalizedBy(copyAndRenameAPKTask) + } + } + } +} + +dependencies { + if (findProject(":servoview-local") != null) { + implementation(project(":servoview-local")) + } else { + implementation(project(":servoview")) + } + + implementation("androidx.appcompat:appcompat:1.6.1") + implementation("com.google.android.material:material:1.9.0") + implementation("androidx.constraintlayout:constraintlayout:2.1.3") +} diff --git a/support/android/apk/servoapp/src/main/AndroidManifest.xml b/support/android/apk/servoapp/src/main/AndroidManifest.xml index 7c0b914b421..2264ea4f7a3 100644 --- a/support/android/apk/servoapp/src/main/AndroidManifest.xml +++ b/support/android/apk/servoapp/src/main/AndroidManifest.xml @@ -6,6 +6,7 @@ + diff --git a/support/android/apk/servoapp/src/main/java/org/servo/servoshell/MediaSession.java b/support/android/apk/servoapp/src/main/java/org/servo/servoshell/MediaSession.java index e76a0227fca..82f9103cb4c 100644 --- a/support/android/apk/servoapp/src/main/java/org/servo/servoshell/MediaSession.java +++ b/support/android/apk/servoapp/src/main/java/org/servo/servoshell/MediaSession.java @@ -149,7 +149,7 @@ public class MediaSession { Intent playIntent = new Intent(KEY_MEDIA_PLAY); Notification.Action playAction = new Notification.Action(R.drawable.media_session_play, "Play", - PendingIntent.getBroadcast(mContext, 0, playIntent, 0)); + PendingIntent.getBroadcast(mContext, 0, playIntent, PendingIntent.FLAG_IMMUTABLE)); builder.addAction(playAction); } @@ -157,7 +157,7 @@ public class MediaSession { Intent pauseIntent = new Intent(KEY_MEDIA_PAUSE); Notification.Action pauseAction = new Notification.Action(R.drawable.media_session_pause, "Pause", - PendingIntent.getBroadcast(mContext, 0, pauseIntent, 0)); + PendingIntent.getBroadcast(mContext, 0, pauseIntent, PendingIntent.FLAG_IMMUTABLE)); builder.addAction(pauseAction); } diff --git a/support/android/apk/servoview-local/build.gradle b/support/android/apk/servoview-local/build.gradle deleted file mode 100644 index f00a8239e23..00000000000 --- a/support/android/apk/servoview-local/build.gradle +++ /dev/null @@ -1,12 +0,0 @@ -configurations.maybeCreate("default") - -if (gradle.hasProperty('servoViewLocal')) { - def aar = new File(gradle.servoViewLocal) - if (aar.exists()) { - artifacts.add('default', aar) - } else { - throw new GradleException('Failed to find ServoView AAR at: ' + gradle.servoViewLocal) - } -} else { - throw new GradleException('Local ServoView AAR path not defined') -} diff --git a/support/android/apk/servoview-local/build.gradle.kts b/support/android/apk/servoview-local/build.gradle.kts new file mode 100644 index 00000000000..57d3b162b57 --- /dev/null +++ b/support/android/apk/servoview-local/build.gradle.kts @@ -0,0 +1,14 @@ +configurations.maybeCreate("default") + +val servoViewLocal: String? by gradle.extra + +if (servoViewLocal != null) { + val aar = File(servoViewLocal!!) + if (aar.exists()) { + artifacts.add("default", aar) + } else { + throw GradleException("Failed to find ServoView AAR at: $servoViewLocal") + } +} else { + throw GradleException("Local ServoView AAR path not defined") +} diff --git a/support/android/apk/servoview/build.gradle b/support/android/apk/servoview/build.gradle deleted file mode 100644 index bd76ab0793c..00000000000 --- a/support/android/apk/servoview/build.gradle +++ /dev/null @@ -1,250 +0,0 @@ -plugins { - id 'com.android.library' -} - -import groovy.io.FileType -import java.util.regex.Matcher -import java.util.regex.Pattern - -android { - compileSdk 33 - buildToolsVersion = "34.0.0" - - namespace 'org.servo.servoview' - - buildDir = rootDir.absolutePath + "/../../../target/android/gradle/servoview" - - ndkPath = getNdkDir() - - defaultConfig { - minSdk 30 - targetSdk 30 - versionCode generatedVersionCode - versionName "0.0.1" // TODO: Parse Servo's TOML and add git SHA. - } - - compileOptions { - incremental false - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 - } - - flavorDimensions = ["default"] - - productFlavors { - basic { - } - } - - splits { - density { - enable false - } - abi { - enable false - } - } - - - buildTypes { - // Default debug and release build types are used as templates - debug { - jniDebuggable true - } - - release { - signingConfig signingConfigs.debug // Change this to sign with a production key - minifyEnabled false - proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' - } - - // Custom build types - armv7Debug { - initWith(debug) - } - armv7Release { - initWith(release) - } - arm64Debug { - initWith(debug) - } - arm64Release { - initWith(release) - } - x86Debug { - initWith(debug) - } - x86Release { - initWith(release) - } - x64Debug { - initWith(debug) - } - x64Release { - initWith(release) - } - } - - sourceSets { - main { - } - armv7Debug { - jniLibs.srcDirs = [getJniLibsPath(true, 'armv7')] - } - armv7Release { - jniLibs.srcDirs = [getJniLibsPath(false, 'armv7')] - } - arm64Debug { - jniLibs.srcDirs = [getJniLibsPath(true, 'arm64')] - } - arm64Release { - jniLibs.srcDirs = [getJniLibsPath(false, 'arm64')] - } - x86Debug { - jniLibs.srcDirs = [getJniLibsPath(true, 'x86')] - } - x86Release { - jniLibs.srcDirs = [getJniLibsPath(false, 'x86')] - } - x64Debug { - jniLibs.srcDirs = [getJniLibsPath(true, 'x64')] - } - x64Release { - jniLibs.srcDirs = [getJniLibsPath(false, 'x64')] - } - } - - // Ignore default 'debug' and 'release' build types - variantFilter { variant -> - if(variant.buildType.name == 'release' || variant.buildType.name == 'debug') { - variant.setIgnore(true) - } - } - - // Call our custom NDK Build task using flavor parameters. - // This step is needed because the Android Gradle Plugin system's - // integration with native C/C++ shared objects (based on the - // `android.externalNativeBuild` dsl object) assumes that we - // actually execute compiler commands to produced the shared - // objects. We already have the libsimpleservo.so produced by rustc. - // We could simply copy the .so to the `sourceSet.jniLibs` folder - // to make AGP bundle it with the APK, but this doesn't copy the STL - // (libc++_shared.so) as well. So we use ndk-build as a glorified - // `cp` command to copy the libsimpleservo.so from target/ - // to target/android and crucially also include libc++_shared.so - // as well. - // - // FIXME(mukilan): According to the AGP docs, we should not be - // relying on task names used by the plugin system to hook into - // the build process, but instead we should use officially supported - // extension points such as `androidComponents.beforeVariants` - tasks.all { - compileTask -> - // This matches the task `mergeBasicArmv7DebugJniLibFolders`. - Pattern pattern = Pattern.compile(/^merge[A-Z]\w+([A-Z]\w+)(Debug|Release)JniLibFolders/) - Matcher matcher = pattern.matcher(compileTask.name) - if (!matcher.find()) { - return - } - - def taskName = "ndkbuild" + compileTask.name - tasks.create(name: taskName, type: Exec) { - def debug = compileTask.name.contains("Debug") - def arch = matcher.group(1) - commandLine getNdkDir() + "/ndk-build", - 'APP_BUILD_SCRIPT=../jni/Android.mk', - 'NDK_APPLICATION_MK=../jni/Application.mk', - 'NDK_LIBS_OUT=' + getJniLibsPath(debug, arch), - 'NDK_DEBUG=' + (debug ? '1' : '0'), - 'APP_ABI=' + getNDKAbi(arch), - 'NDK_LOG=1', - 'SERVO_TARGET_DIR=' + getNativeTargetDir(debug, arch) - } - - compileTask.dependsOn taskName - } - - project.afterEvaluate { - android.libraryVariants.all { variant -> - Pattern pattern = Pattern.compile(/^[\w\d]+([A-Z][\w\d]+)(Debug|Release)/) - Matcher matcher = pattern.matcher(variant.name) - if (!matcher.find()) { - throw new GradleException("Invalid variant name for output: " + variant.name) - } - def arch = matcher.group(1) - def debug = variant.name.contains("Debug") - def finalFolder = getTargetDir(debug, arch) - def finalFile = new File(finalFolder, "servoview.aar") - variant.outputs.all { output -> - Task copyAndRenameAARTask = project.task("copyAndRename${variant.name.capitalize()}AAR", type: Copy) { - from output.outputFile.getParent() - into finalFolder - include output.outputFileName - rename(output.outputFileName, finalFile.getName()) - } - variant.assemble.finalizedBy(copyAndRenameAARTask) - } - } - } -} - -dependencies { - - //Dependency list - def deps = [ - new ServoDependency("blurdroid.jar", "blurdroid") - ] - // Iterate all build types and dependencies - // For each dependency call the proper implementation command and set the correct dependency path - def list = ['armv7', 'arm64', 'x86', 'x64'] - for (arch in list) { - for (debug in [true, false]) { - String basePath = getTargetDir(debug, arch) + "/build" - String cmd = arch + (debug ? "Debug" : "Release") + "Implementation" - - for (ServoDependency dep : deps) { - String path = findDependencyPath(basePath, dep.fileName, dep.folderFilter) - if (path) { - "${cmd}" files(path) - } - } - } - } - - implementation 'androidx.appcompat:appcompat:1.6.1' - implementation 'com.google.android.material:material:1.9.0' - implementation 'androidx.constraintlayout:constraintlayout:2.1.3' -} - -// folderFilter can be used to improve search performance -static String findDependencyPath(String basePath, String filename, String folderFilter) { - File path = new File(basePath) - if (!path.exists()) { - return '' - } - - if (folderFilter) { - path.eachDir { - if (it.name.contains(folderFilter)) { - path = new File(it.absolutePath) - } - } - } - def result = '' - path.eachFileRecurse(FileType.FILES) { - if(it.name == filename) { - result = it.absolutePath - } - } - - return result -} - -class ServoDependency { - ServoDependency(String fileName, String folderFilter = null) { - this.fileName = fileName - this.folderFilter = folderFilter - } - public String fileName - public String folderFilter -} diff --git a/support/android/apk/servoview/build.gradle.kts b/support/android/apk/servoview/build.gradle.kts new file mode 100644 index 00000000000..aa63b6be375 --- /dev/null +++ b/support/android/apk/servoview/build.gradle.kts @@ -0,0 +1,248 @@ +import java.io.FileFilter +import java.util.regex.Pattern + +plugins { + id("com.android.library") +} + +android { + compileSdk = 33 + buildToolsVersion = "34.0.0" + + namespace = "org.servo.servoview" + + layout.buildDirectory = File(rootDir.absolutePath, "/../../../target/android/gradle/servoview") + + ndkPath = getNdkDir() + + defaultConfig { + minSdk = 30 + lint.targetSdk = 33 + defaultConfig.versionCode = generatedVersionCode + defaultConfig.versionName = "0.0.1" // TODO: Parse Servo"s TOML and add git SHA. + } + + compileOptions { + compileOptions.incremental = false + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + } + + flavorDimensions.add("default") + + productFlavors { + register("basic") { + } + } + + splits { + density { + isEnable = false + } + abi { + isEnable = false + } + } + + + buildTypes { + // Default debug and release build types are used as templates + debug { + isJniDebuggable = true + } + + release { + signingConfig = + signingConfigs.getByName("debug") // Change this to sign with a production key + isMinifyEnabled = false + proguardFiles(getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro") + } + + val debug = getByName("debug") + val release = getByName("release") + + // Custom build types + register("armv7Debug") { + initWith(debug) + } + register("armv7Release") { + initWith(release) + } + register("arm64Debug") { + initWith(debug) + } + register("arm64Release") { + initWith(release) + } + register("x86Debug") { + initWith(debug) + } + register("x86Release") { + initWith(release) + } + register("x64Debug") { + initWith(debug) + } + register("x64Release") { + initWith(release) + } + } + + sourceSets { + named("main") { + } + named("armv7Debug") { + jniLibs.srcDirs(getJniLibsPath(true, "armv7")) + } + named("armv7Release") { + jniLibs.srcDirs(getJniLibsPath(false, "armv7")) + } + named("arm64Debug") { + jniLibs.srcDirs(getJniLibsPath(true, "arm64")) + } + named("arm64Release") { + jniLibs.srcDirs(getJniLibsPath(false, "arm64")) + } + named("x86Debug") { + jniLibs.srcDirs(getJniLibsPath(true, "x86")) + } + named("x86Release") { + jniLibs.srcDirs(getJniLibsPath(false, "x86")) + } + named("x64Debug") { + jniLibs.srcDirs(getJniLibsPath(true, "x64")) + } + named("x64Release") { + jniLibs.srcDirs(getJniLibsPath(false, "x64")) + } + } + + // Ignore default "debug" and "release" build types + androidComponents { + beforeVariants { + if (it.buildType == "release" || it.buildType == "debug") { + it.enable = false + } + } + } + + // Call our custom NDK Build task using flavor parameters. + // This step is needed because the Android Gradle Plugin system"s + // integration with native C/C++ shared objects (based on the + // `android.externalNativeBuild` dsl object) assumes that we + // actually execute compiler commands to produced the shared + // objects. We already have the libsimpleservo.so produced by rustc. + // We could simply copy the .so to the `sourceSet.jniLibs` folder + // to make AGP bundle it with the APK, but this doesn"t copy the STL + // (libc++_shared.so) as well. So we use ndk-build as a glorified + // `cp` command to copy the libsimpleservo.so from target/ + // to target/android and crucially also include libc++_shared.so + // as well. + // + // FIXME(mukilan): According to the AGP docs, we should not be + // relying on task names used by the plugin system to hook into + // the build process, but instead we should use officially supported + // extension points such as `androidComponents.beforeVariants` + project.afterEvaluate { + // we filter entries first in order to abstract to a new list + // as to prevent concurrent modification exceptions due to creating a new task + // while iterating + tasks.mapNotNull { compileTask -> // mapNotNull acts as our filter, null results are dropped + // This matches the task `mergeBasicArmv7DebugJniLibFolders`. + val pattern = Pattern.compile("^merge[A-Z]\\w+([A-Z]\\w+)(Debug|Release)JniLibFolders") + val matcher = pattern.matcher(compileTask.name) + if (matcher.find()) + compileTask to matcher.group(1) + else null + }.forEach { (compileTask, arch) -> + val ndkBuildTask = tasks.create("ndkbuild" + compileTask.name) { + val debug = compileTask.name.contains("Debug") + commandLine( + getNdkDir() + "/ndk-build", + "APP_BUILD_SCRIPT=../jni/Android.mk", + "NDK_APPLICATION_MK=../jni/Application.mk", + "NDK_LIBS_OUT=" + getJniLibsPath(debug, arch), + "NDK_DEBUG=" + if (debug) "1" else "0", + "APP_ABI=" + getNDKAbi(arch), + "NDK_LOG=1", + "SERVO_TARGET_DIR=" + getNativeTargetDir(debug, arch) + ) + } + + compileTask.dependsOn(ndkBuildTask) + } + + android.libraryVariants.forEach { variant -> + val pattern = Pattern.compile("^[\\w\\d]+([A-Z][\\w\\d]+)(Debug|Release)") + val matcher = pattern.matcher(variant.name) + if (!matcher.find()) { + throw GradleException("Invalid variant name for output: " + variant.name) + } + val arch = matcher.group(1) + val debug = variant.name.contains("Debug") + val finalFolder = getTargetDir(debug, arch) + val finalFile = File(finalFolder, "servoview.aar") + variant.outputs.forEach { output -> + val copyAndRenameAARTask = + project.task("copyAndRename${variant.name.capitalize()}AAR") { + from(output.outputFile.parent) + into(finalFolder) + include(output.outputFile.name) + rename(output.outputFile.name, finalFile.name) + } + variant.assembleProvider.get().finalizedBy(copyAndRenameAARTask) + } + } + } +} + +dependencies { + + //Dependency list + val deps = listOf(ServoDependency("blurdroid.jar", "blurdroid")) + // Iterate all build types and dependencies + // For each dependency call the proper implementation command and set the correct dependency path + val list = listOf("armv7", "arm64", "x86", "x64") + for (arch in list) { + for (debug in listOf(true, false)) { + val basePath = getTargetDir(debug, arch) + "/build" + val cmd = arch + (if (debug) "Debug" else "Release") + "Implementation" + + for (dep in deps) { + val path = findDependencyPath(basePath, dep.fileName, dep.folderFilter) + if (path.isNotBlank()) + cmd(files(path)) // this is how custom flavors are called. + } + } + } +} + +// folderFilter can be used to improve search performance +fun findDependencyPath(basePath: String, filename: String, folderFilter: String?): String { + var path = File(basePath) + if (!path.exists()) { + return "" + } + + + if (folderFilter != null) { + path.listFiles(FileFilter { it.isDirectory })!!.forEach { + if (it.name.contains(folderFilter)) { + path = File(it.absolutePath) + } + } + } + + var result = "" + + for (file in path.walkTopDown()) { + if (file.isFile && file.name == filename) { + result = file.absolutePath + break // no more walking + } + } + + return result +} + +data class ServoDependency(val fileName: String, val folderFilter: String? = null) diff --git a/support/android/apk/servoview/proguard-rules.pro b/support/android/apk/servoview/proguard-rules.pro index f1b424510da..2f9dc5a47ed 100644 --- a/support/android/apk/servoview/proguard-rules.pro +++ b/support/android/apk/servoview/proguard-rules.pro @@ -1,6 +1,6 @@ # Add project specific ProGuard rules here. # You can control the set of applied configuration files using the -# proguardFiles setting in build.gradle. +# proguardFiles setting in build.gradle.kts. # # For more details, see # http://developer.android.com/guide/developing/tools/proguard.html diff --git a/support/android/apk/settings.gradle b/support/android/apk/settings.gradle deleted file mode 100644 index d8826861853..00000000000 --- a/support/android/apk/settings.gradle +++ /dev/null @@ -1,39 +0,0 @@ -pluginManagement { - repositories { - google() - mavenCentral() - gradlePluginPortal() - } -} - -dependencyResolutionManagement { - repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) - repositories { - google() - mavenCentral() - } -} - -include ':servoapp' - -def userPropertiesFile = new File('user.properties') -if (userPropertiesFile.exists()) { - println("Loading user.properties") - def props = new Properties() - userPropertiesFile.withInputStream { - props.load(it) - } - props.each { prop -> - println(prop.key + " = " + prop.value) - gradle.ext.set(prop.key, prop.value) - } - if (gradle.hasProperty('servoViewLocal')) { - println("Using local build of servoview") - include ':servoview-local' - project(':servoview-local').projectDir = new File('servoview-local') - } else { - include ':servoview' - } -} else { - include ':servoview' -} diff --git a/support/android/apk/settings.gradle.kts b/support/android/apk/settings.gradle.kts new file mode 100644 index 00000000000..77b9db78157 --- /dev/null +++ b/support/android/apk/settings.gradle.kts @@ -0,0 +1,41 @@ +import java.util.Properties + +pluginManagement { + repositories { + google() + mavenCentral() + gradlePluginPortal() + } +} + +dependencyResolutionManagement { + repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) + repositories { + google() + mavenCentral() + } +} + +include(":servoapp") + +val userPropertiesFile = File("user.properties") +if (userPropertiesFile.exists()) { + println("Loading user.properties") + val props = Properties() + userPropertiesFile.inputStream().use { + props.load(it) + } + props.forEach { (key, value) -> + println("$key = $value") + gradle.extra.set(key!!.toString(), value); + } + if (props.containsKey("servoViewLocal")) { + println("Using local build of servoview") + include (":servoview-local") + project(":servoview-local").projectDir = File("servoview-local") + } else { + include(":servoview") + } +} else { + include(":servoview") +} \ No newline at end of file