Skip to content

Commit

Permalink
Add support for Android Gradle plugin 8.2 (#1003)
Browse files Browse the repository at this point in the history
  • Loading branch information
mhsmith committed Nov 16, 2023
1 parent 63850e2 commit 6f0dd1b
Show file tree
Hide file tree
Showing 45 changed files with 149 additions and 29 deletions.
5 changes: 3 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -57,13 +57,14 @@ jobs:
!**/*.sha512
if-no-files-found: error

# This should match the version discovery logic in gradle-plugin/build.gradle.kts.
- name: List Android Gradle plugin versions
id: agp-versions
run: |
cd product/gradle-plugin/src/test/integration/data
cd product/gradle-plugin/src/test/integration/data/base
(
echo -n versions=
echo base-* | sed 's/base-//g' | jq -cR 'split(" ")'
echo *.* | jq -cR 'split(" ")'
) >> $GITHUB_OUTPUT
outputs:
Expand Down
12 changes: 11 additions & 1 deletion demo/app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,17 @@ android {
minSdk = 21
targetSdk = 33

versionName = System.getProperty("chaquopyVersion") // Set in the root project
val plugins = buildscript.configurations.getByName("classpath")
.resolvedConfiguration.resolvedArtifacts.map {
it.moduleVersion.id
}.filter {
it.group == "com.chaquo.python" && it.name == "gradle"
}
if (plugins.size != 1) {
throw GradleException("found ${plugins.size} Chaquopy plugins")
}
versionName = plugins[0].version

val verParsed = versionName!!.split(".").map { it.toInt() }
versionCode = verParsed[0] * 1000000 +
verParsed[1] * 1000 +
Expand Down
11 changes: 4 additions & 7 deletions demo/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
// Top-level build file where you can add configuration options common to all
// sub-projects/modules.
buildscript {
// The version file can't be read within the plugins block, because there's no way
// to determine the project directory there.
System.setProperty("chaquopyVersion", file("../VERSION.txt").readText().trim())
}

plugins {
id("com.android.application") version "8.1.3" apply false
id("org.jetbrains.kotlin.android") version "1.8.10" apply false
id("com.chaquo.python") version System.getProperty("chaquopyVersion") apply false

// com.chaquo.python is declared in settings.gradle.kts, because dynamic versions
// are not possible here. See
// https://docs.gradle.org/current/userguide/plugins.html#sec:plugin_version_management
}
3 changes: 3 additions & 0 deletions demo/settings.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ pluginManagement {
mavenCentral()
gradlePluginPortal()
}
plugins {
id("com.chaquo.python") version file("../VERSION.txt").readText().trim()
}
}

dependencyResolutionManagement {
Expand Down
2 changes: 1 addition & 1 deletion product/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ The integration tests are run by the Gradle task `gradle-plugin:testIntegration-
`X.Y` is the Android Gradle plugin version to test against (e.g. `7.0`).

Each Android Gradle plugin version has a corresponding JDK version specified in
test/integration/data/base-X.Y/gradle.properties. The location of this JDK must be
test/integration/data/base/X.Y/gradle.properties. The location of this JDK must be
set in `product/local.properties` as described above.

The full set of tests will take a long time. To run only some of them, add `-P
Expand Down
8 changes: 4 additions & 4 deletions product/gradle-plugin/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,15 @@ After first release candidate:
* Check the bundled JDK version, and update product/local.properties to point at it.
* Use the new project wizard to create an "Empty Activity" project, with "Minimum SDK"
set to Chaquopy's current minimum.
* Create a directory integration/data/base-X.Y, where X.Y is the Android Gradle plugin
* Create a directory integration/data/base/X.Y, where X.Y is the Android Gradle plugin
version.
* Copy the contents from the previous base-X.Y directory, then update them with the
* Copy the contents from the previous base/X.Y directory, then update them with the
settings from the "Empty Activity" project.
* Run tests on all platforms.

After stable release:

* As above, update the integration/data/base-X.Y directory with the settings from the
* As above, update the integration/data/base/X.Y directory with the settings from the
new project wizard.
* Update the demo and pkgtest apps as follows. Leave the public apps alone for now: they
will be dealt with during the next release (see release/README.md).
Expand Down Expand Up @@ -43,7 +43,7 @@ After stable release:
* Check if there's any version-dependent code in the plugin or the tests which can now
be removed.
* Integration tests:
* Remove AndroidPlugin/old, then move the old base-X.Y directory to replace it.
* Remove AndroidPlugin/old, then move the old base/X.Y directory to replace it.
* Update test_old expected message, then run the test.
* Update android.rst and versions.rst.
* Consider increasing the Gradle version of the "product" project (see
Expand Down
18 changes: 12 additions & 6 deletions product/gradle-plugin/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -93,11 +93,17 @@ abstract class TestPythonTask : DefaultTask() {
val args = project.findProperty("testPythonArgs")
if (args != null) {
command += "-v"
command += args.toString().split(Regex("""\s+"""))
command += args.toString().split(Regex("""\s+""")).filter { !it.isEmpty() }
} else {
command += listOf("discover", "-v")
}
pb.command(command)

// Lower levels mean more logging.
if (project.gradle.startParameter.logLevel <= LogLevel.INFO) {
println("TestPythonTask: $command")
}

pb.redirectErrorStream(true) // Merge stdout and stderr.
val process = pb.start()

Expand All @@ -106,7 +112,7 @@ abstract class TestPythonTask : DefaultTask() {
// daemon's *native* stdout, which isn't connected to anything. So we
// capture the output manually and send it to System.out, which is connected
// to the Gradle client.
val stdout = process.getInputStream() // sic
val stdout = process.inputStream // It's an input from our point of view.
val buffer = ByteArray(1024 * 1024)
while (true) {
val available = stdout.available()
Expand Down Expand Up @@ -140,11 +146,11 @@ tasks.check {
dependsOn("testPython", "testIntegration")
}

// This should match the version discovery logic in ci.yml.
val INTEGRATION_DIR = "$projectDir/src/test/integration"
val PATTERN = Regex("^base-(.+)$")
for (f in file("$INTEGRATION_DIR/data").listFiles()!!) {
PATTERN.find(f.name)?.let {
val version = it.groupValues[1]
for (f in file("$INTEGRATION_DIR/data/base").listFiles()!!) {
val version = f.name
if (version.contains(".")) {
val task = tasks.register<TestPythonTask>("testIntegration-$version") {
pythonVersion = Common.DEFAULT_PYTHON_VERSION
if (System.getenv("CHAQUOPY_NO_BUILD") == null) { // Used in CI
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
plugins {
// Plugin versions are configured in settings.gradle.kts, because dynamic versions
// are not possible here. See
// https://docs.gradle.org/current/userguide/plugins.html#sec:plugin_version_management
//
// Listing the plugins in settings.gradle.kts is enough to enable the `plugins`
// syntax in subprojects, but the `apply` syntax requires them to be listed here as
// well.
id("com.android.application") apply false
id("com.android.library") apply false
id("com.android.dynamic-feature") apply false
id("org.jetbrains.kotlin.android") apply false
id("com.chaquo.python") apply false
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# The test script uses this to set JAVA_HOME.
chaquopy.java.version=17

agpVersion=8.2.0-rc03

# These variables will be filled in by the test script.
chaquopyRepository=
chaquopyVersion=

# Gradle Daemon will terminate itself after specified number of idle milliseconds. Default is
# 10800000 (3 hours), but that can overload the machine when running integration tests on many
# versions. Reduce to 30 minutes.
org.gradle.daemon.idletimeout=1800000

# Default settings generated by the new project wizard:

# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true
# AndroidX package structure to make it clearer which packages are bundled with the
# Android operating system, and which are packaged with your app's APK
# https://developer.android.com/topic/libraries/support-library/androidx-rn
android.useAndroidX=true
# Kotlin code style for this project: "official" or "obsolete":
kotlin.code.style=official
# Enables namespacing of each library's R class so that its R class includes only the
# resources declared in the library itself and none from the library's dependencies,
# thereby reducing the size of the R class for that library
android.nonTransitiveRClass=true
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
pluginManagement {
// These are defined in gradle.properties.
val chaquopyRepository: String by settings
val chaquopyVersion: String by settings
val agpVersion: String by settings

repositories {
google()
mavenCentral()
gradlePluginPortal()
maven { url = uri(chaquopyRepository) }
}
plugins {
id("com.android.application") version agpVersion
id("com.android.library") version agpVersion
id("com.android.dynamic-feature") version agpVersion
id("org.jetbrains.kotlin.android") version "1.9.0"
id("com.chaquo.python") version chaquopyVersion
}
}

dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
google()
mavenCentral()
}
}

rootProject.name = "My Application"

for (f in rootDir.listFiles()!!) {
if (f.isDirectory) {
for (buildGradle in listOf("build.gradle", "build.gradle.kts")) {
if (File(f, buildGradle).exists()) {
include(f.name)
}
}
}
}
26 changes: 18 additions & 8 deletions product/gradle-plugin/src/test/integration/test_gradle_plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -256,7 +256,14 @@ def test_apply(self):
# Make sure we still work if the plugin is applied in the app module buildscript rather
# than the root project.
def test_apply_buildscript(self):
self.RunGradle("base", "ChaquopyPlugin/apply_buildscript")
# The version-numbered "base" layers differ in whether their top-level
# build.gradle and settings.gradle files are in Kotlin or Groovy. This test
# provides its own Groovy files.
run = self.RunGradle("base", run=False)
if agp_version_info >= (8, 2):
os.remove(f"{run.project_dir}/build.gradle.kts")
os.remove(f"{run.project_dir}/settings.gradle.kts")
run.rerun("ChaquopyPlugin/apply_buildscript")


class AndroidPlugin(GradleTestCase):
Expand All @@ -279,7 +286,8 @@ def test_missing(self):
def test_old(self): # Also tests making a change
MESSAGE = ("This version of Chaquopy requires Android Gradle plugin version "
"7.0.0 or later")
run = self.RunGradle("base", "AndroidPlugin/old", succeed=False)
# This test doesn't use the version-numbered "base" layers.
run = self.RunGradle("base/0", "AndroidPlugin/old", succeed=False)
self.assertInLong(f"{MESSAGE}. {self.ADVICE}", run.stderr)

run.apply_layers("base")
Expand Down Expand Up @@ -1524,13 +1532,15 @@ def __init__(self, test, *layers, run=True, **kwargs):

def apply_layers(self, *layers):
for layer in layers:
# We use dir_utils.copy_tree because shutil.copytree can't merge into a destination
# that already exists.
dir_util._path_created.clear() # https://bugs.python.org/issue10948
dir_util.copy_tree(join(data_dir, layer), self.project_dir,
preserve_times=False) # https://github.com/gradle/gradle/issues/2301
if layer == "base":
self.apply_layers("base-" + agp_version)
self.apply_layers("base/0", f"base/{agp_version}")
else:
# We use dir_util.copy_tree, because shutil.copytree can't merge into a
# destination that already exists.
dir_util._path_created.clear() # https://bugs.python.org/issue10948
dir_util.copy_tree(
join(data_dir, layer), self.project_dir,
preserve_times=False) # https://github.com/gradle/gradle/issues/2301

def rerun(self, *layers, succeed=True, variants=["debug"], env=None, add_path=None,
**kwargs):
Expand Down

0 comments on commit 6f0dd1b

Please sign in to comment.