diff --git a/.buildconfig-android.yml b/.buildconfig-android.yml index a113ee3d3d..fbae09c2dd 100644 --- a/.buildconfig-android.yml +++ b/.buildconfig-android.yml @@ -127,7 +127,7 @@ projects: publications: - name: full-megazord type: aar - - name: full-megazord-forUnitTests + - name: full-megazord-libsForTests type: jar description: Megazord containing all features tooling-nimbus-gradle: diff --git a/build-scripts/component-common.gradle b/build-scripts/component-common.gradle index 72468127af..ea8b8f44b2 100644 --- a/build-scripts/component-common.gradle +++ b/build-scripts/component-common.gradle @@ -60,21 +60,11 @@ ext.dependsOnTheMegazord = { api project(":full-megazord") // 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) { artifact { type = "aar" } } - testImplementation(libs.jna) { - artifact { - type = "jar" - } - } } // 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 { - megazordNative(project("path": ":full-megazord", "configuration": "megazordNative")) + megazordNative project("path": ":full-megazord", "configuration": "megazordNative") + implementation project("path": ":full-megazord", "configuration": "libsForTests") } afterEvaluate { android.libraryVariants.all { variant -> def variantName = variant.name.capitalize(); 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 def generateUniffiBindings = tasks.register("generateUniffiBindings") { def megazordNative = configurations.getByName("megazordNative") - doFirst { def libraryPath = megazordNative.asFileTree.matching { - include "**/libmegazord.*" + include "${nativeRustTarget}/libmegazord.*" }.singleFile if (libraryPath == null) { @@ -143,6 +114,7 @@ ext.configureUniFFIBindgen = { crateName -> workingDir project.rootDir commandLine '/usr/bin/env', 'cargo', 'uniffi-bindgen', 'generate', '--library', libraryPath, "--crate", crateName, '--language', 'kotlin', '--out-dir', uniffiOutDir.get() } + } outputs.dir uniffiOutDir // Re-generate when the native megazord library is rebuilt diff --git a/docs/design/megazords.md b/docs/design/megazords.md index 5da41b1c8d..de27c653d6 100644 --- a/docs/design/megazords.md +++ b/docs/design/megazords.md @@ -57,11 +57,12 @@ the name of the component and the expected version number of the shared library. ## 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 -suites such as robolectric, each megazord has a corresponding Java ARchive (JAR) distribution named e.g. -`full-megazord-forUnitTests.jar`. This contains the rust code compiled for various Desktop architectures, -and consumers can add it to their classpath when running tests on a Desktop machine. +suites such as robolectric, each Megazord has a corresponding Java ARchive (JAR) distribution named +e.g. `full-megazord-libsForTests.jar`. This contains the compiled Megazord code as well as +libjnidispatch, targetted at various Desktop architectures. Consumers can add it to their classpath +when running tests on a Desktop machine. ## Gotchas and Rough Edges diff --git a/megazords/full/android/build.gradle b/megazords/full/android/build.gradle index 8ac728e8c0..d761394d25 100644 --- a/megazords/full/android/build.gradle +++ b/megazords/full/android/build.gradle @@ -35,25 +35,68 @@ kotlin { // 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. 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 - // to run unit tests. + // to run uniffi-bindgen against consumable("megazordNative") } -// Wrap the cargoBuild task to copy the native library to an output dir -// -// This allows it to be piped in to a Gradle configuration. -def cargoBuildNativeArtifacts = tasks.register("copyNativeMegazord", Copy) { +dependencies { + // Needed so we can copy the libraries into libsForTests. + jna(libs.jna) { + artifact { + type = "jar" + } + } +} + +// 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") - into layout.buildDirectory.dir("nativeMegazord") + dependsOn tasks["cargoBuild${rootProject.ext.nativeRustTarget.capitalize()}"] +} - def nativeTarget = rootProject.ext.nativeRustTarget - dependsOn tasks["cargoBuild${nativeTarget.capitalize()}"] +def copyMegazordNative = tasks.register("copyMegazordNative", Copy) { + from layout.buildDirectory.dir("rustJniLibs/desktop") + into layout.buildDirectory.dir("megazordNative") } + artifacts { - // Connect task output to configurations - megazordNative(cargoBuildNativeArtifacts) + // Connect task output to the configurations + libsForTests(packageLibsForTest) + megazordNative(copyMegazordNative) } cargo { @@ -99,3 +142,51 @@ afterEvaluate { apply from: "$rootDir/publish.gradle" 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 + } + } + } +}