Build a JAR file with libjnidispatch and libmegazord, targetted at
Desktop arches so that devs can run unit tests with it.  Export this as
a configuration and Maven package.  This allows android-components code
to run the unit tests and also simplifies the gradle code for our own
components.

Named this full-megazord-libsForTest, which I think is a bit more clear
than full-megazord-forUnitTests.
This commit is contained in:
Ben Dean-Kawamura 2024-12-19 10:55:21 -05:00 коммит произвёл bendk
Родитель 83ad69b756
Коммит 176f82f69d
4 изменённых файлов: 113 добавлений и 49 удалений

Просмотреть файл

@ -127,7 +127,7 @@ projects:
publications: publications:
- name: full-megazord - name: full-megazord
type: aar type: aar
- name: full-megazord-forUnitTests - name: full-megazord-libsForTests
type: jar type: jar
description: Megazord containing all features description: Megazord containing all features
tooling-nimbus-gradle: tooling-nimbus-gradle:

Просмотреть файл

@ -60,21 +60,11 @@ ext.dependsOnTheMegazord = {
api project(":full-megazord") api project(":full-megazord")
// Add a JNA dependency, which is required by UniFFI. // Add a JNA dependency, which is required by UniFFI.
//
// The tricky thing is getting the correct libjnidispatch library. Use the AAR version for
// normal builds, since that ensures the various Android libraries get packaged correctly when
// we build our own AAR. Use the JAR version for tests, since that ensures the tests can load
// the native version.
implementation(libs.jna) { implementation(libs.jna) {
artifact { artifact {
type = "aar" type = "aar"
} }
} }
testImplementation(libs.jna) {
artifact {
type = "jar"
}
}
} }
// Configurations are a somewhat mysterious Gradle concept. For our purposes, we can treat them // Configurations are a somewhat mysterious Gradle concept. For our purposes, we can treat them
@ -85,33 +75,15 @@ ext.dependsOnTheMegazord = {
} }
} }
// Wire up the megazordNative configuration to the output produced from the `full-megazord` project.
dependencies { dependencies {
megazordNative(project("path": ":full-megazord", "configuration": "megazordNative")) megazordNative project("path": ":full-megazord", "configuration": "megazordNative")
implementation project("path": ":full-megazord", "configuration": "libsForTests")
} }
afterEvaluate { afterEvaluate {
android.libraryVariants.all { variant -> android.libraryVariants.all { variant ->
def variantName = variant.name.capitalize(); def variantName = variant.name.capitalize();
def testTask = tasks["test${variantName}UnitTest"] def testTask = tasks["test${variantName}UnitTest"]
def processTestResTask = tasks["process${variantName}UnitTestJavaRes"]
// Copy the native libmegazord to the resource dir so that it can be loaded by JNA.
// Note: we have to manually copy the library to the output dir ourselves. If we simply
// added the megazordNative directory to sourceSets.test.resources.srcDirs, then the
// android gradle plugin will refuse to copy it. For details see:
//
// * https://github.com/mozilla/application-services/pull/6476#issuecomment-2537227576
// * https://github.com/mozilla/glean/pull/2680#issuecomment-2056627683
def copyNativeMegazord = tasks.register("copy${variantName}NativeMegazord", Copy) {
from configurations.getByName("megazordNative")
into processTestResTask.destinationDir
// Make sure to run after the process java res task, otherwise that one may
// overwrite our work.
dependsOn(processTestResTask)
}
testTask.dependsOn(copyNativeMegazord)
} }
} }
} }
@ -130,10 +102,9 @@ ext.configureUniFFIBindgen = { crateName ->
// Call `uniffi-bindgen` to generate the Kotlin bindings // Call `uniffi-bindgen` to generate the Kotlin bindings
def generateUniffiBindings = tasks.register("generateUniffiBindings") { def generateUniffiBindings = tasks.register("generateUniffiBindings") {
def megazordNative = configurations.getByName("megazordNative") def megazordNative = configurations.getByName("megazordNative")
doFirst { doFirst {
def libraryPath = megazordNative.asFileTree.matching { def libraryPath = megazordNative.asFileTree.matching {
include "**/libmegazord.*" include "${nativeRustTarget}/libmegazord.*"
}.singleFile }.singleFile
if (libraryPath == null) { if (libraryPath == null) {
@ -143,6 +114,7 @@ ext.configureUniFFIBindgen = { crateName ->
workingDir project.rootDir workingDir project.rootDir
commandLine '/usr/bin/env', 'cargo', 'uniffi-bindgen', 'generate', '--library', libraryPath, "--crate", crateName, '--language', 'kotlin', '--out-dir', uniffiOutDir.get() commandLine '/usr/bin/env', 'cargo', 'uniffi-bindgen', 'generate', '--library', libraryPath, "--crate", crateName, '--language', 'kotlin', '--out-dir', uniffiOutDir.get()
} }
} }
outputs.dir uniffiOutDir outputs.dir uniffiOutDir
// Re-generate when the native megazord library is rebuilt // Re-generate when the native megazord library is rebuilt

Просмотреть файл

@ -57,11 +57,12 @@ the name of the component and the expected version number of the shared library.
## Unit Tests ## Unit Tests
The full-megazord AAR contains compiled rust code that targets various Android platforms, and is not The full-megazord AAR contains compiled Rust code that targets various Android platforms, and is not
suitable for running on a Desktop development machine. In order to support integration with unittest suitable for running on a Desktop development machine. In order to support integration with unittest
suites such as robolectric, each megazord has a corresponding Java ARchive (JAR) distribution named e.g. suites such as robolectric, each Megazord has a corresponding Java ARchive (JAR) distribution named
`full-megazord-forUnitTests.jar`. This contains the rust code compiled for various Desktop architectures, e.g. `full-megazord-libsForTests.jar`. This contains the compiled Megazord code as well as
and consumers can add it to their classpath when running tests on a Desktop machine. libjnidispatch, targetted at various Desktop architectures. Consumers can add it to their classpath
when running tests on a Desktop machine.
## Gotchas and Rough Edges ## Gotchas and Rough Edges

Просмотреть файл

@ -35,25 +35,68 @@ kotlin {
// Configurations are a somewhat mysterious Gradle concept. For our purposes, we can treat them // Configurations are a somewhat mysterious Gradle concept. For our purposes, we can treat them
// sets of files produced by one component and consumed by another. // sets of files produced by one component and consumed by another.
configurations { configurations {
// Libraries for unit tests
//
// This is a JAR file that contains libmegazord and libjnidispatch built for desktop platforms
// -- i.e. non-Android. These include linux-x86-64, darwin-x86-64, and darwin-aarch64. These
// libraries are needed to run unit tests, since the AAR packages only contain libraries for
// Android.
//
// For libmegazord, we copy the desktop libs from the
// [rust-android-gradle plugin](https://github.com/mozilla/rust-android-gradle), which is
// configurable via `local.properties`. The official packages are built in taskcluster include
// `linux-x86-64` and `darwin-x86-64` and the list is controlled by
// taskcluster/kinds/module-build/kind.yml
//
// For libjnidispatch, we include all libraries included in the official JAR file.
consumable("libsForTests")
// Stores the JNA jar file
jna {
canBeConsumed = false
canBeResolved = true
canBeDeclared = true
}
// Native megazord library, this is the one compatible with the user's local machine. We use it // Native megazord library, this is the one compatible with the user's local machine. We use it
// to run unit tests. // to run uniffi-bindgen against.
consumable("megazordNative") consumable("megazordNative")
} }
// Wrap the cargoBuild task to copy the native library to an output dir dependencies {
// // Needed so we can copy the libraries into libsForTests.
// This allows it to be piped in to a Gradle configuration. jna(libs.jna) {
def cargoBuildNativeArtifacts = tasks.register("copyNativeMegazord", Copy) { artifact {
from layout.buildDirectory.dir("rustJniLibs/desktop") type = "jar"
into layout.buildDirectory.dir("nativeMegazord") }
}
def nativeTarget = rootProject.ext.nativeRustTarget
dependsOn tasks["cargoBuild${nativeTarget.capitalize()}"]
} }
// Extract JNI dispatch libraries from the JAR into a directory, so that we can then package them
// into our own megazord-desktopLibraries JAR.
def extractLibJniDispatch = tasks.register("extractLibJniDispatch", Copy) {
from zipTree(configurations.jna.singleFile).matching {
include "**/libjnidispatch.*"
}
into layout.buildDirectory.dir("libjnidispatch").get()
}
def packageLibsForTest = tasks.register("packageLibsForTest", Jar) {
archiveBaseName = "full-megazord-libsForTests"
from extractLibJniDispatch
from layout.buildDirectory.dir("rustJniLibs/desktop")
dependsOn tasks["cargoBuild${rootProject.ext.nativeRustTarget.capitalize()}"]
}
def copyMegazordNative = tasks.register("copyMegazordNative", Copy) {
from layout.buildDirectory.dir("rustJniLibs/desktop")
into layout.buildDirectory.dir("megazordNative")
}
artifacts { artifacts {
// Connect task output to configurations // Connect task output to the configurations
megazordNative(cargoBuildNativeArtifacts) libsForTests(packageLibsForTest)
megazordNative(copyMegazordNative)
} }
cargo { cargo {
@ -99,3 +142,51 @@ afterEvaluate {
apply from: "$rootDir/publish.gradle" apply from: "$rootDir/publish.gradle"
ext.configurePublish() ext.configurePublish()
afterEvaluate {
publishing {
publications {
// Publish a second package named `full-megazord-libsForTests` to Maven with the
// `libsForTests` output. This contains the same content as our `libsForTests`
// configuration. Publishing it allows the android-components code to depend on it.
libsForTests(MavenPublication) {
artifact tasks['packageLibsForTest']
artifact file("${projectDir}/../DEPENDENCIES.md"), {
extension "LICENSES.md"
}
pom {
groupId = rootProject.ext.library.groupId
artifactId = "${project.ext.artifactId}-libsForTests"
description = project.ext.description
// For mavenLocal publishing workflow, increment the version number every publish.
version = rootProject.ext.library.version + (rootProject.hasProperty('local') ? '-' + rootProject.property('local') : '')
packaging = "jar"
licenses {
license {
name = libLicense
url = libLicenseUrl
}
}
developers {
developer {
name = 'Sync Team'
email = 'sync-team@mozilla.com'
}
}
scm {
connection = libVcsUrl
developerConnection = libVcsUrl
url = libUrl
}
}
// This is never the publication we want to use when publishing a
// parent project with us as a child `project()` dependency.
alias = true
}
}
}
}