Welcome to follow my public numberEfficient Android Development“Focuses on Android project efficiency and development experience, covering topics such as infrastructure, Kotlin Multiplatform, Gradle construction and optimization, etc. At the same time, we also talk about overseas work and life, and push the latest Podcast of” Two-part radio “.

“Build North” is a series of articles exploring Android building. It covers Gradle, Android Gradle Plugin, Kotlin Script, and other tools, as well as related architecture applications. To find the problem to solve the problem as the starting point, transfer new knowledge to improve production efficiency as the foothold.

Recently, I was working on a foot-binding project. The requirement for the upgrade of the packaging script is “step by step” (the time is tight and stable mainly) — use the new Gradle Plugin in Debug and the old version in Release. Build. Gradle = build.gradle = build.gradle = build.gradle

// apply isDebug() method from utils.gradle
apply from: project.getRootProject().rootDir.absolutePath + '/scripts/utils.gradle'
def gradlePluginVersion = isDebug() ? '2.3.3' : '2.0.0'

buildscript {

    repositories {
        jcenter()
        mavenLocal()
        mavenCentral()
    }

    dependencies {
        // use outer variable
        classpath "com.android.tools.build:gradle:$gradlePluginVersion"
        classpath. }}...Copy the code

But the operation reported an error:

Could not get unknown property ‘gradlePluginVersion’ for object of type org.gradle.api.internal.artifacts.dsl.dependencies.DefaultDependencyHandler.

It doesn’t make any sense that you can’t find this variable well, you’ve done this before when you’ve defined dependencies in various scripts, okay?

Mysterious BuildScript block

Google does a little digging and there are some similar problems, but most of them are working on how to make buildScript’s deps available to some custom scripts.

Read the official documentation and we know that buildScript () creates ScriptHandler instances:

The closure passed to the buildscript() method configures a ScriptHandler instance. You declare the build script classpath by adding dependencies to the classpath configuration. This is the same way you declare, for example, The Java compilation classpath. You can use any of the Dependency types described in Section 25.4, “How to declare your dependencies”, except project dependencies

Having declared the build script classpath, you can use the classes in your build script as you would any other classes on the classpath.

The ScriptHandler source code comment reads:

To declare the script classpath, you use the DependencyHandler provided by getDependencies() to attach dependencies to the “classpath” configuration. These dependencies are resolved just prior to script compilation, and assembled into the classpath for the script.

That said, buildScript blocks execute better than other scripts (though I guess one exception is init Script, I’ll write about that later). It’s easy to understand, because the classpath dependencies are defined, and the Android Gradle Plugin we use is imported from there. If buildScript isn’t better than other scripts, then we’ll have a problem.

A test code

println 'Hello First Line'

buildscript {

    println 'Hello Second Line'

    repositories{... }dependencies{... } } afterEvaluate {project ->
    println 'Hello Third Line'
}
Copy the code

Add –info to execute the script build and you can see the following output:

Starting Build Settings evaluated using settings file '/Path/To/Your/Project/settings.gradle'. Projects loaded. Root project using build file '/Path/To/Your/Project/build.gradle'. Included projects: [root project 'Your-Project', project ':app'] Evaluating root project 'Your-Project' using build file '/Path/To/Your/Project/build.gradle'. Hello Second Line /Path/To/Your/Project Creating new cache for metadata-2.23/module-metadata, Path/Users / 2 bab /. Gradle/caches/metadata/modules - 2-2.23 / module - metadata. Bin, access org.gradle.cache.internal.DefaultCacheAccess@24473bd5 Creating new cache for Metadata - 2.23 / an artifact - at - the repository, Path/Users / 2 bab /. Gradle/caches/metadata/modules - 2-2.23 / an artifact - at - the repository. Bin, access org.gradle.cache.internal.DefaultCacheAccess@24473bd5 Hello First Line Hello Third Line Evaluating project ':app'  using build file '/Path/To/Your/Project/app/build.gradle'.Copy the code

Gradle: Evaluate build. Gradle: Evaluate build. Gradle: Evaluate build. Gradle: Evaluate Build. Gradle: Evaluate Build. Gradle: Evaluate Build. Evaluate End Go to 3.

Breakpoint Gradle

1.DefaultScriptRunnerFactory.ScriptRunnerImpl.run() ->

@Override
public void run(Object target, ServiceRegistry scriptServices) throws GradleScriptException {
    if(! compiledScript.getRunDoesSomething()) {return;
    }
    
    ClassLoader originalLoader = Thread.currentThread().getContextClassLoader();
    T script = getScript();
    script.init(target, scriptServices);
    Thread.currentThread().setContextClassLoader(script.getContextClassloader());
    script.getStandardOutputCapture().start();
    try {
        script.run();
    } catch (Throwable e) {
        throw new GradleScriptException(String.format("A problem occurred evaluating %s.", script), e);
    } finally{ script.getStandardOutputCapture().stop(); Thread.currentThread().setContextClassLoader(originalLoader); }}Copy the code

2.ProjectScript.buildscript() ->

public void buildscript(Closure configureClosure) {
    getScriptTarget().buildscript(configureClosure);
}
Copy the code

The script. Run () method is not implemented in Java. . But by comparing call ProjectScript buildscript () the timing of the method and the test code to print the log, will find: At the time of the method call, there is no log output (that is, buildScript really takes precedence), and if you start doing anything in the BuildScript closure, such as applying a script, you will immediately go to the corresponding Apply method.

solution

With the above tests, we know that buildScript blocks should be executed first, so the problem is solved simply by putting buildScript related logic scripts into blocks; In addition, if the contents of the block need to be exposed to other scripts, ext can still be used to export:

buildscript {

    apply from: project.getRootProject().rootDir.absolutePath + '/scripts/utils.gradle'
    def gradlePluginVersion = isDebug() ? '2.3.3' : '2.0.0'
    ext.gradlePluginVersion = gradlePluginVersion
    
    repositories {
        jcenter()
        mavenLocal()
        mavenCentral()
    }

    dependencies {
        classpath "com.android.tools.build:gradle:$gradlePluginVersion". }}Copy the code

Welcome to comment like, and follow my public accountEfficient Android Development.