Using locally published components in Fenix

It's often important to test work-in-progress changes to Application Services components against a real-world consumer project. The most reliable method of performing such testing is to publish your components to a local Maven repository, and adjust the consuming project to install them from there.

With support from the upstream project, it's possible to do this in a single step using our auto-publishing workflow.

rust.targets

Both the auto-publishing and manual workflows can be sped up significantly by using the rust.targets property which limits which architectures the Rust code gets build against. You can set this property by creating/editing the local.properties file in the repository root and adding a line like rust.targets=x86,linux-x86-64. The trick is knowing which targets to put in that comma separated list:

  • Use x86 for running the app on most emulators (in rare cases, when you have a 64-bit emulator, you'll want x86_64)
  • If you're running the android-components or fenix unit tests, then you'll need the architecture of your machine:
    • OSX running Intel chips: darwin-x86-64
    • OSX running M1 chips: darwin-aarch64
    • Linux: linux-x86-64

Using the auto-publishing workflow

Some consumers (notably Fenix) have support for automatically publishing and including a local development version of application-services in their build. The workflow is:

  1. Check out the firefox-android mono-repo.

  2. Edit (or create) the file fenix/local.properties and tell it where to find your local checkout of application-services, by adding a line like:

    autoPublish.application-services.dir=relative/path/to/your/checkout/of/application-services

    Note that the path should be relative from local.properties. For example, if application-services and firefox-android are at the same level, the relative path would be ../../application-services

  3. Do the same for android-components/local.properties - so yes, your local checkout of firefox-android requires 2 copies of local.properties, both with identical values for autoPublish.application-services.dir (and probably identical in every other way too)

  4. Build the consuming project following its usual build procedure, e.g. via ./gradlew assembleDebug or ./gradlew test.

If all goes well, this should automatically build your checkout of application-services, publish it to a local maven repository, and configure the consuming project to install it from there instead of from our published releases.

Using Windows/WSL

If you are using Windows, there's a good chance you do most application-services work in WSL, but want to run Android Studio on native Windows. In that scenario you must:

  • From the app-services root, in WSL, execute ./automation/publish_to_maven_local_if_modified.py
  • In native Windows, just work as normal - that build process knows to not even attempt to execute the above command automatically.

Using a manual workflow

Note: This is a bit tedious, and you should first try the auto-publishing workflow described above. But if the auto-publishing workflow fails then it's important to know how to do the publishing process manually. Since most consuming apps get their copy of application-services via a dependency on android-components, this procedure involves three separate repos:

  1. Inside the application-services repository root:

    1. In version.txt, change the version to end in -TESTING$N 1, where $N is some number that you haven't used for this before.

      Example: 0.27.0-TESTING3

    2. Run ./gradlew publishToMavenLocal. This may take between 5 and 10 minutes.

  2. Inside the consuming project repository root (eg, firefox-android/fenix):

    1. Inside build.gradle, add mavenLocal() inside allprojects { repositories { <here> } }.

    2. Ensure that local.properties does not contain any configuration to related to auto-publishing the application-services repo.

    3. Inside buildSrc/src/main/java/AndroidComponents.kt, change the version numbers for android-components to match the new versions you defined above.

      Example: const val VERSION = "0.51.0-TESTING3"

You should now be able to build and run the consuming application (assuming you could do so before all this).

Caveats

  1. This assumes you have followed the build instructions for Fenix
  2. Make sure you're fully up to date in all repos, unless you know you need to not be.
  3. This omits the steps if changes needed because, e.g. application-services made a breaking change to an API used in android-components. These should be understandable to fix, you usually should be able to find a PR with the fixes somewhere in the android-component's list of pending PRs (or, failing that, a description of what to do in the application-services changelog).
  4. Contact us if you get stuck.

Adding support for the auto-publish workflow

If you had to use the manual workflow above and found it incredibly tedious, you might like to try adding support for the auto-publish workflow to the consuming project! The details will differ depending on the specifics of the project's build setup, but at a high level you will need to:

  1. In your settings.gradle, locate (or add) the code for parsing the local.properties file, and add support for loading a directory path from the property autoPublish.application-services.dir.

    If this property is present, spawn a subprocess to run ./gradlew autoPublishForLocalDevelopment in the specified directory. This automates step (1) of the manual workflow above, publishing your changes to application-services into a local maven repository under a unique version number.

  2. In your build.gradle, if the autoPublish.application-services.dir property is present, have each project apply the build script from ./build-scripts/substitute-local-appservices.gradle in the specified directory.

    This automates steps (2) and (3) of the manual workflow above, using gradle's dependency substitution capabilities to override the verion requirements for application-services components. It may be necessary to experiment with the ordering of this relative to other build configuration steps, in order for the dependency substitution to work correctly.

    For a single-project build this would look something like:

    if (gradle.hasProperty('localProperties.autoPublish.application-services.dir')) {
       ext.appServicesSrcDir = gradle."localProperties.autoPublish.application-services.dir"
       apply from: "${appServicesSrcDir}/build-scripts/substitute-local-appservices.gradle"
    }
    

    For a multi-project build it should be applied to all subprojects, like:

    subprojects {
       if (gradle.hasProperty('localProperties.autoPublish.application-services.dir')) {
          ext.appServicesSrcDir = gradle."localProperties.autoPublish.application-services.dir"
          apply from: "${rootProject.projectDir}/${appServicesSrcDir}/build-scripts/substitute-local-appservices.gradle"
       }
    }
    
  3. Confirm that the setup is working, by adding autoPublish.application-services.dir to your local.properties file and running ./gradlew dependencies for the project.

    You should be able to see gradle checking the build status of the various application-services dependencies as part of its setup phase. When the command completes, it should print the resolved versions of all dependencies, and you should see that application-services components have a version number in the format 0.0.1-SNAPSHOT-{TIMESTAMP}.


[1]: It doesn't have to start with -TESTING, it only needs to have the format -someidentifier. -SNAPSHOT$N is also very common to use, however without the numeric suffix, this has specific meaning to gradle, so we avoid it. Additionally, while the $N we have used in our running example has matched (e.g. all of the identifiers ended in -TESTING3, this is not required, so long as you match everything up correctly at the end. This can be tricky, so I always try to use the same number).