Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

KT-64377 - automatically download and setup Android SDK in Gradle Integration Test #3419

Closed
wants to merge 17 commits into from

Conversation

adam-enko
Copy link
Member

Automatically download and setup Android SDK so that the tests can be run on local machines without additional setup.

Downloading the Android SDK requires committing the license files, passing the directory via the ANDROID_SDK env var, and then AGP magic does the rest.

Depends on

Comment on lines 26 to 30
val integrationTestPreparation by tasks.registering {
description =
"lifecycle task for preparing the project for integration tests (for example, publishing to the test Maven repo)"
group = VERIFICATION_GROUP
}
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll be using this task more in later PRs to help manage local Maven publishing of the included-builds.

Copy link
Collaborator

@whyoleg whyoleg left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you please clarify a bit: this is needed for cases, where android SDK is not installed locally, right?

I mean, Android SDK can be installed already on users machines. Even so, if someone will want to run Android integration tests locally they are or Dokka developers or doing something with Android - and so most likely they will have Android SDK installed :)
On CI - it's the same, we don't want to download Android SDK on every run - so it will be preinstalled somewhere already.

I'm not against this change, just want to better understand the reasoning, may be f.e. it's better to add a flag which will cause using this SDK, if it's really needed.
With current approach, I think it's just a matter of time when some day (after some changes to tests/build-logic) we will understand, that our CI (or even local machine) downloads Android SDK on every run.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Im not sure, just asking - is this ok to store such files in .git? :)
I mean, if I understand correctly after this any user will automatically agree to license terms even if they doesn't know about it
May be Im a bit overreacting, just mentioning some thoughts

build.gradle.kts Outdated Show resolved Hide resolved
environment("ANDROID_HOME", androidSdkDir.asFile.invariantSeparatorsPath)
}

val updateProjectsAndroidLocalProperties by tasks.registering {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

May be Im missing something, but as we already provide ANDROID_HOME above as env variable, why do we need to additionally copy local.properties file to test project?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ANDROID_HOME is required by the integration tests - possibly it could be removed.

private val isAndroidSdkInstalled: Boolean = System.getenv("ANDROID_SDK_ROOT") != null ||
System.getenv("ANDROID_HOME") != null

local.properties is useful for if you want to manually open a project (in a temp dir) that's failed a test, and you want to check it manually.

/**
* Create an `local.properties` file so that Android projects will automatically work in integration tests.
*/
abstract class CreateAndroidLocalPropertiesFile : DefaultTask() {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need this task, as in the end we will just copy the text from this file? Can't we just have a property androidSdkDirPath which we will then use?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point, I've updated the task to do both in one. (I originally adapted it from a Dokkatoo task which needed the separate tasks.)

val createAndroidLocalPropertiesFile by tasks.registering(CreateAndroidLocalPropertiesFile::class) {
localPropertiesFile.convention(layout.file(provider { temporaryDir.resolve("local.properties") }))
androidSdkDirPath.convention(providers
// first try getting the SDK installed on via GitHub step setup-android
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it possible to somehow easily add out-of-the-box support for reading this value from local.properties inside project? As I already have Android SDK installed at my side.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a good idea, I'd be happy to do it. I've done something similar in MockK before. However I'm a little bit reluctant because the Android SDK is only needed in integration tests, and scanning/parsing local.properties files would add some complexity & maintenance.

Since it's possible to set the Android SDK using an environment variable, I'd prefer not to. But what do you think?

@adam-enko
Copy link
Member Author

So, just to summarise, the reason for this is so that AGP will automatically download the Android SDK when running integration tests. This means that anyone can run the Android tests without needing to manually setup and install the SDK, which is more convenient.

Adding the local.properties file is useful for opening projects manually. If a test fails then it's convenient to open the copied project and be able to run it as a normal project, again, without needing to manually setup Android SDK.

Committing these license files isn't common, but Mozilla and Google have done it. The android-actions/setup-android GitHub action will automatically accept the licenses too.

With current approach, I think it's just a matter of time when some day (after some changes to tests/build-logic) we will understand, that our CI (or even local machine) downloads Android SDK on every run.

Good point! I'm pretty sure that the AGP auto-downloader will pick up existing Android SDK dirs, since the Dokkatoo tests on GitHub download the Android SDK which the tests should use. I'm pretty sure it doesn't download the Android SDK every time. But it's been a while since I looked at it... Perhaps there's some logic in Dokkatoo that will defer to the pre-installed SDK.

@adam-enko adam-enko requested a review from Tapchicoma December 19, 2023 12:19
@whyoleg
Copy link
Collaborator

whyoleg commented Dec 19, 2023

On current moment when running android tests from this branch I see this error:
image
BTW, locally I have both ANDROID_SDK_ROOT and ANDROID_HOME defined, so I would expect no usage of projects/ANDROID_SDK

… the same Android SDK dir in tests and local.properties
@adam-enko
Copy link
Member Author

Thanks for checking @whyoleg 👍, I've updated the logic so the Android SDK dir will first use ANDROID_SDK_ROOT and ANDROID_HOME in both the local.properties file

https://github.com/Kotlin/dokka/pull/3419/files#diff-9cd6ff3290240988a2e812310b5c963500b89af00734469944b9e1a4dff06790R44-R49

and also pass it in as ANDROID_HOME in the tests https://github.com/Kotlin/dokka/pull/3419/files#diff-9cd6ff3290240988a2e812310b5c963500b89af00734469944b9e1a4dff06790R52 and the local.properties generation task https://github.com/Kotlin/dokka/pull/3419/files#diff-9cd6ff3290240988a2e812310b5c963500b89af00734469944b9e1a4dff06790R66.

Could you try checking it out and trying again please?

…64377/auto-android-sdk

# Conflicts:
#	dokka-integration-tests/gradle/.gitignore
@whyoleg
Copy link
Collaborator

whyoleg commented Dec 20, 2023

So, now everything works when ANDROID_HOME and ANDROID_SDK_ROOT are set at my side (pointing to ~/Library/Android/sdk). But, if I will remove them (and restart IDEA, gradle, to refetch envs) - it will not work and fail with the same error :)
Does it work at your side? Do you have android SDK installed at your side?

image

Also, I see that createAndroidLocalPropertiesFiles is not executed at all now, is it correct? You need to manually run it.
And even after running it I see that there is no local.properties file created in android-0 folder.

@adam-enko
Copy link
Member Author

So, now everything works when ANDROID_HOME and ANDROID_SDK_ROOT are set at my side (pointing to ~/Library/Android/sdk). But, if I will remove them (and restart IDEA, gradle, to refetch envs) - it will not work and fail with the same error :) Does it work at your side? Do you have android SDK installed at your side?

Ah okay, there was a bug with the createAndroidLocalPropertiesFiles task (see below) so it should work now. (I don't have Android SDK variables set up, but probably the local.properties file was still created from before I refactored the createAndroidLocalPropertiesFiles task, and I didn't delete them after refactoring to verify.)

Also, I see that createAndroidLocalPropertiesFiles is not executed at all now, is it correct? You need to manually run it. And even after running it I see that there is no local.properties file created in android-0 folder.

createAndroidLocalPropertiesFiles should run before the integration tests run

https://github.com/Kotlin/dokka/pull/3419/files#diff-9cd6ff3290240988a2e812310b5c963500b89af00734469944b9e1a4dff06790R69-R71

However, I learned today that a FileTree cannot contain directories

gradle/gradle#27567

I've updated the logic to use walk() to filter the Android project dirs.

Copy link
Contributor

@Tapchicoma Tapchicoma left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't like the approach to writing something into the repository state and would like to propose the following changes to use build directory for temporary things:

  • configure Android SDK for all integration tests in the build/android-sdk directory (if the user does not have it installed)
  • Generate local.properties file into the build/android-prepare/local.properties file and pass via system property the path to the test runner
  • Add to this logic additionally coping local.properties file into test project directory. Maybe this logic could be generalized to be used in other android IT tests.

build-logic/src/main/kotlin/dokkabuild.base.gradle.kts Outdated Show resolved Hide resolved
*/
abstract class CreateAndroidLocalPropertiesFiles : DefaultTask() {
@get:OutputFiles
val localPropertiesFilesDestinations: List<File>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: could be converted to SetProperty<File>:

    @get:OutputFiles
    val localPropertiesFilesDestinations: SetProperty<File> = objects.setProperty<File>()
        .value(
                androidProjectsDirectories
                    .elements
                    .map {
                        it.map { it.asFile.resolve("local.properties") }.toSet()
                    }
        )
        .apply { disallowChanges() }

We should use provider API in our tasks/build configuration to make our transition to Gradle 9 smoother.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure thing, done. I've updated it to a Provider<Set<File>> (which SetProperty<File> implements) because I think using Provider<> instead of Property<> makes it more clear that this is a read-only value.

.environmentVariable("ANDROID_SDK_ROOT").map(::File)
.orElse(providers.environmentVariable("ANDROID_HOME").map(::File))
// else get the project-local SDK
.orElse(templateProjectsDir.dir("ANDROID_SDK").asFile)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: could also add here .map { it.invariantSeparatorsPath } to avoid calling it in other places

Comment on lines 61 to 63
templateProjectsDir.asFile.walk()
.filter { it.isDirectory && it.name in androidProjects }
.asIterable()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It could be simplified to avoid unnecessary computation:

    val androidProjects = setOf(
        templateProjectsDir.dir("it-android-0"),
    )
    androidProjectsDirectories.from(androidProjects)

@adam-enko
Copy link
Member Author

I don't like the approach to writing something into the repository state and would like to propose the following changes to use build directory for temporary things:

* configure Android SDK for all integration tests in the `build/android-sdk` directory (if the user does not have it installed)

Can do, but the set up would be a little more complicated because the license files would have to be copied into build/android-sdk first. Running ./gradlew clean would also really slow down the integration tests. So I'm reluctant to move it. Unless you insist?

* Generate `local.properties` file into the `build/android-prepare/local.properties` file and pass via system property the path to the test runner

* Add to [this logic](https://github.com/Kotlin/dokka/blob/eace407bec18ae6bb54d6be8ff9f1a4c38edde5f/dokka-integration-tests/gradle/src/integrationTest/kotlin/org/jetbrains/dokka/it/gradle/Android0GradleIntegrationTest.kt#L38-L49) additionally coping `local.properties` file into test project directory. Maybe this logic could be generalized to be used in other android IT tests.

The reason why I added local.properties into the local projects is useful in case you want to open up a project in an IDE to edit/debug it. (The files are .gitignore'd so they're not in the repo state.) Again, I can do it as you suggest, but I'm reluctant to because it would make working with the projects slightly more inconvenient.

@adam-enko
Copy link
Member Author

I decided not to proceed with this PR.

I'll update the other PRs that depend on this PR.

@adam-enko adam-enko closed this Jan 10, 2024
@adam-enko adam-enko added the infrastructure Everything related to builds tools, CI configurations and project tooling label Jan 22, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
infrastructure Everything related to builds tools, CI configurations and project tooling
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants