Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,11 @@ commands:
- setup-rust-toolchain:
rust-version: stable
- install-android-ndk
- run:
name: Install required Python dependencies
command: |
sudo apt update
sudo apt install --yes --no-install-recommends python3-venv
- run:
name: Restrict to Linux builds only
command: |
Expand Down
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,17 @@

[Full changelog](https://github.com/mozilla/glean/compare/v70.0.0...main)

* Android
* Updated to Gradle 9.5.0 ([#3612](https://github.com/mozilla/glean/pull/3612))
* BREAKING CHANGE: The Glean Gradle plugin no longer uses JetBrains' Python envs plugin to
download a bundled Miniconda, as that plugin is unmaintained and incompatible with Gradle 9.
The plugin now always creates a virtual environment using the Python standard library `venv`
module, so a Python 3.9 or later interpreter must be available on the `PATH`, or pointed at
with the `GLEAN_PYTHON` environment variable. Consumers that set `ext.gleanPythonEnvDir` are
unaffected. Applications applying the Glean Gradle plugin no longer need to apply the
`com.jetbrains.python.envs` plugin themselves and can remove it from their build
([#3612](https://github.com/mozilla/glean/pull/3612))

# v70.0.0 (2026-08-20)

[Full changelog](https://github.com/mozilla/glean/compare/v69.0.0...v70.0.0)
Expand Down
11 changes: 6 additions & 5 deletions bin/check-artifact.sh
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,8 @@ check_files() {
echo "Artifact: $artifact"

if [[ -z "$artifact" ]]; then
echo "No artifact found. Skipping."
return
echo "No artifact found to check. Abort." >&2
exit 1
fi

shift
Expand Down Expand Up @@ -82,8 +82,8 @@ check_symbol() {
echo "Symbol: $symbol"

if [[ -z "$artifact" ]]; then
echo "No artifact found. Skipping."
return
echo "No artifact found to check. Abort." >&2
exit 1
fi

nm=$(find "$(rustc --print sysroot)" -name "llvm-nm")
Expand Down Expand Up @@ -125,6 +125,7 @@ case "$ARTIFACT_ID" in
check_symbol "$ARTIFACT" "glean_core_uniffi_contract_version" "${REQUIRED_FILES_TEST[@]}"
;;
*)
echo "Unknown Artifact ID"
echo "No check defined for artifact '${ARTIFACT_ID}'. Abort." >&2
exit 1
;;
esac
10 changes: 3 additions & 7 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -131,10 +131,6 @@ subprojects {
withSourcesJar()
}
}

lint {
baseline = file("lint-baseline.xml")
}
}
}
tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all {
Expand All @@ -159,11 +155,11 @@ subprojects {
}

detekt {
input = files("${projectDir}/glean-core", "${projectDir}/samples/android", "buildSrc")
config = files("${projectDir}/.detekt.yml")
source.from(files("${projectDir}/glean-core", "${projectDir}/samples/android", "buildSrc"))
config.from(files("${projectDir}/.detekt.yml"))
buildUponDefaultConfig = true
reports {
xml.enabled = false
xml.required = false
}
}

Expand Down
25 changes: 5 additions & 20 deletions docs/user/user/adding-glean-to-your-project/kotlin.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,10 +60,12 @@ This package is standalone and its version will be exported from the main Glean

## Setting up metrics and pings code generation

In order for the Glean Kotlin SDK to generate an API for your metrics, two Gradle plugins must be included in your build:
In order for the Glean Kotlin SDK to generate an API for your metrics, the
[Glean Gradle plugin](https://github.com/mozilla/glean/tree/main/gradle-plugin/) must be included in your build.

- The [Glean Gradle plugin](https://github.com/mozilla/glean/tree/main/gradle-plugin/)
- JetBrains' [Python envs plugin](https://github.com/JetBrains/gradle-python-envs/)
The plugin creates its own Python virtual environment to run `glean_parser` in, so a Python 3.9 or
later interpreter needs to be available on the `PATH`. Set the `GLEAN_PYTHON` environment variable to
point at a specific interpreter instead.

The Glean Gradle plugin is distributed through Mozilla's Maven, so we need to tell your build where to look for it by adding the following to the top of your `build.gradle`:

Expand All @@ -87,14 +89,6 @@ buildscript {

> As above, the `{latest-components-version}` placeholder in the above link should be replaced with the version number of the Glean SDK used in your project.

The JetBrains Python plugin is distributed in the Gradle plugin repository, so it can be included with:

```Groovy
plugins {
id "com.jetbrains.python.envs" version "0.0.26"
}
```

Right before the end of the same file, we need to apply the Glean Gradle plugin.
Set any [additional parameters](../../language-bindings/android/android-build-configuration-options.md) to control the behavior of the Glean Gradle plugin before calling `apply plugin`.

Expand All @@ -107,15 +101,6 @@ apply plugin: "org.mozilla.telemetry.glean-gradle-plugin"

{{#include ../../../shared/blockquote-info.html}}

##### Rosetta 2 required on Apple Silicon

> On Apple Silicon machines (M1/M2/M3 MacBooks and iMacs) Rosetta 2 is required for the bundled Python.
> See the [Apple documentation about Rosetta 2](https://support.apple.com/en-us/HT211861)
> and [Bug 1775420](https://bugzilla.mozilla.org/show_bug.cgi?id=1775420) for details.
> You can install it with `softwareupdate --install-rosetta`

{{#include ../../../shared/blockquote-info.html}}

##### Offline builds

> The Glean Gradle plugin has limited support for [offline builds](../../language-bindings/android/android-offline-builds.md) of applications that use the Glean SDK.
31 changes: 17 additions & 14 deletions glean-core/android-native/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,11 @@ android {
namespace = "mozilla.telemetry.glean_native"

ndkVersion = rootProject.ext.build.ndkVersion
compileSdkVersion = rootProject.ext.build.compileSdkVersion
compileSdk = rootProject.ext.build.compileSdkVersion

defaultConfig {
minSdkVersion rootProject.ext.build['minSdkVersion']
targetSdkVersion rootProject.ext.build['targetSdkVersion']
minSdk = rootProject.ext.build['minSdkVersion']
targetSdk = rootProject.ext.build['targetSdkVersion']
}

buildTypes {
Expand All @@ -34,7 +34,7 @@ android {

release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
consumerProguardFiles "$projectDir/proguard-rules-consumer.pro"
}

Expand Down Expand Up @@ -108,22 +108,24 @@ dependencies {
}
}

afterEvaluate {
// The `cargoBuild` task isn't available until after evaluation.
android.libraryVariants.all { variant ->
def productFlavor = ""
variant.productFlavors.each {
productFlavor += "${it.name.capitalize()}"
}
def buildType = "${variant.buildType.name.capitalize()}"
tasks["merge${productFlavor}${buildType}JniLibFolders"].dependsOn(tasks["cargoBuild"])
// Referenced by name so `cargoBuild` does not have to exist yet.
tasks.configureEach { task ->
if (task.name.startsWith("merge") && task.name.endsWith("JniLibFolders")) {
task.dependsOn("cargoBuild")
}
}

// Extract JNI dispatch libraries from the JAR into a directory, so that we can then package them
// into our own glean-native-desktopLibraries JAR.
def jnaJar = configurations.jna.elements.map {
if (it.size() != 1) {
throw new GradleException("Expected exactly one JNA jar, found ${it.size()}")
}
it.first().asFile
}

def extractLibJniDispatch = tasks.register("extractLibJniDispatch", Copy) {
from zipTree(configurations.jna.singleFile).matching {
from(zipTree(jnaJar)) {
include "**/libjnidispatch.*"
}
into layout.buildDirectory.dir("libjnidispatch").get()
Expand All @@ -140,6 +142,7 @@ def packageLibsForTest = tasks.register("packageLibsForTest", Jar) {
def copyGleanNative = tasks.register("copyGleanNative", Copy) {
from layout.buildDirectory.dir("rustJniLibs/desktop")
into layout.buildDirectory.dir("gleanNative")
dependsOn("cargoBuild${rootProject.ext.nativeRustTarget.capitalize()}")
}

artifacts {
Expand Down
81 changes: 39 additions & 42 deletions glean-core/android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,8 @@
// https://github.com/mozilla/application-services/blob/c40e2ccb422cf4af9ffdf095149cec34de1d4bef/components/fxa-client/android/build.gradle

import groovy.json.JsonOutput

plugins {
alias libs.plugins.gradle.python.envs
}
import javax.inject.Inject
import org.gradle.process.ExecOperations

apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'
Expand All @@ -24,9 +22,6 @@ apply plugin: 'kotlinx-serialization'
*/
File GLEAN_PING_SCHEMA_PATH = file("$rootDir/glean.1.schema.json")

// This will store the uniffi-bindgen generated files for our component
def UNIFFI_OUT_DIR = layout.buildDirectory.dir("generated/uniffi/")

// Set configuration for the glean_parser
ext.allowGleanInternal = true
ext.gleanNamespace = "mozilla.telemetry.glean"
Expand All @@ -38,18 +33,18 @@ kotlin {
android {
namespace = "mozilla.telemetry.glean"

ndkVersion rootProject.ext.build.ndkVersion
compileSdkVersion rootProject.ext.build.compileSdkVersion
ndkVersion = rootProject.ext.build.ndkVersion
compileSdk = rootProject.ext.build.compileSdkVersion

defaultConfig {
minSdkVersion rootProject.ext.build['minSdkVersion']
targetSdkVersion rootProject.ext.build['targetSdkVersion']
minSdk = rootProject.ext.build['minSdkVersion']
targetSdk = rootProject.ext.build['targetSdkVersion']

// Carefully escape the string here so it will support `\` in
// Windows paths correctly.
buildConfigField("String", "GLEAN_PING_SCHEMA_PATH", JsonOutput.toJson(GLEAN_PING_SCHEMA_PATH.path))

testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}

buildTypes {
Expand All @@ -60,7 +55,7 @@ android {

release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
consumerProguardFiles "$projectDir/proguard-rules-consumer.pro"
}

Expand All @@ -69,8 +64,6 @@ android {
}
}

sourceSets.main.kotlin.srcDirs += UNIFFI_OUT_DIR

// Uncomment to include debug symbols in native library builds.
// packagingOptions { doNotStrip "**/*.so" }

Expand All @@ -89,25 +82,8 @@ android {
}
}

kotlin {
jvmToolchain(rootProject.ext.build.jvmTargetCompatibility)
}

afterEvaluate {
android.libraryVariants.all { variant ->
def variantName = variant.name.capitalize();
def testTask = tasks["test${variantName}UnitTest"]
}

def gleanNative = configurations.getByName("gleanNative")
android.libraryVariants.all { variant ->
def variantName = variant.name.capitalize();
def compileTask = tasks["compile${variantName}Kotlin"]

compileTask.dependsOn(generateUniffiBindings)
variant.registerJavaGeneratingTask(generateUniffiBindings, gleanNative.singleFile)
}

if (project.hasProperty("coverage")) {
jacoco {
toolVersion = libs.versions.jacoco
Expand Down Expand Up @@ -194,29 +170,46 @@ dependencies {
apply from: "$rootDir/publish.gradle"
ext.configurePublish()

def generateUniffiBindings = tasks.register("generateUniffiBindings") {
// Gradle 9 removed Project.exec().
interface InjectedExecOps {
@Inject ExecOperations getExecOps()
}
def execOps = objects.newInstance(InjectedExecOps).execOps

// AGP owns the output location, so the task exposes it as a wired property.
abstract class GenerateUniffiBindingsTask extends DefaultTask {
@OutputDirectory
abstract DirectoryProperty getOutputDir()
}

// Resolved up front: the task action must not reach back into the project.
def nativeLibPattern = "${rootProject.ext.nativeRustTarget}/libxul.*"
def cargoWorkingDir = rootDir
def uniffiConfigFile = file("${projectDir}/../uniffi.toml")

def generateUniffiBindings = tasks.register("generateUniffiBindings", GenerateUniffiBindingsTask) {
def gleanNative = configurations.getByName("gleanNative")
doFirst {
def libraryPath = gleanNative.asFileTree.matching {
include "${nativeRustTarget}/libxul.*"
}.singleFile
def libraryFiles = gleanNative.asFileTree.matching {
include nativeLibPattern
}

if (libraryPath == null) {
if (libraryFiles.isEmpty()) {
throw new GradleException("libglean_ffi dynamic library path not found")
}
def libraryPath = libraryFiles.singleFile

exec {
workingDir project.rootDir
execOps.exec {
workingDir cargoWorkingDir
commandLine 'cargo', 'uniffi-bindgen', 'generate',
'--no-format',
'--library', libraryPath,
'--language', 'kotlin',
'--config', "${project.projectDir}/../uniffi.toml",
'--out-dir', UNIFFI_OUT_DIR.get()
'--config', uniffiConfigFile,
'--out-dir', outputDir.get()
}
}

outputs.dir UNIFFI_OUT_DIR
// Re-generate if native changes
inputs.files gleanNative
// Re-generate if the interface definition changes.
Expand All @@ -229,6 +222,10 @@ def generateUniffiBindings = tasks.register("generateUniffiBindings") {
inputs.file "${project.rootDir}/Cargo.lock"
}

androidComponents.onVariants(androidComponents.selector().all(), { variant ->
variant.sources.java.addGeneratedSourceDirectory(generateUniffiBindings) { task -> task.outputDir }
})

// Generate markdown docs for the collected metrics.
ext.gleanDocsDirectory = "$rootDir/docs/user/user/collected-metrics"
ext.gleanYamlFiles = [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ object GleanBuildInfo {
* Checks ping content against the Glean ping schema.
*
* This uses the Python utility, glean_parser, to perform the actual checking.
* This is installed in its own Miniconda environment as part of the build
* This is installed in its own virtual environment as part of the build
* configuration by glean-gradle-plugin.
*
* @param content The JSON content of the ping
Expand All @@ -62,7 +62,7 @@ internal fun checkPingSchema(content: JSONObject) {
val os = System.getProperty("os.name")?.lowercase()
val pythonExecutable =
if (os?.indexOf("win")?.compareTo(0) == 0) {
"${BuildConfig.GLEAN_MINICONDA_DIR}/python"
"${BuildConfig.GLEAN_MINICONDA_DIR}/Scripts/python"
} else {
"${BuildConfig.GLEAN_MINICONDA_DIR}/bin/python"
}
Expand Down
Loading
Loading