Skip to content

Commit

Permalink
Fix taskcluster breakage from #6524
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
bendk committed Dec 20, 2024
1 parent 10b9ce5 commit 8f9691d
Show file tree
Hide file tree
Showing 4 changed files with 111 additions and 47 deletions.
2 changes: 1 addition & 1 deletion .buildconfig-android.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
36 changes: 4 additions & 32 deletions build-scripts/component-common.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)
}
}
}
Expand All @@ -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) {
Expand All @@ -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
Expand Down
9 changes: 5 additions & 4 deletions docs/design/megazords.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
111 changes: 101 additions & 10 deletions megazords/full/android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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 = '[email protected]'
}
}

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
}
}
}
}

0 comments on commit 8f9691d

Please sign in to comment.