One, foreword

In the last article, we talked about the callback method configureExtension in the Apply method of the Android Plugin. See the Android Plugin source code and Gradle build (2). Today we will continue to analyze the source code of the Android Plugin, focusing on the logic of the last callback method createTasks.

Second, first know Task

A Task is a task, and Gradle builds are made up of tasks. We can define a task like this:

task myTask {
  println "this is a task"
}
Copy the code

Task has its own attributes and life cycle, which is divided into three phases: initialization phase, configuration phase, and execution phase, as shown in the following figure:

The myTask task we defined above is only in the configuration phase, and the code in the configuration phase will execute any task it executes. So, we execute the gradle Clean task and this is a task is printed. If we want to define tasks that can only be executed during the execution phase, we can put the implementation logic in doFirst, doLast. For example, here’s an example:

task myTask {
    doLast {
       println 'myTask do Last1'
    }
}

myTask.doFirst {
    println 'myTask do First2'
}

myTask.doFirst {
    println 'myTask do First1'
}

myTask.doLast {
    println 'myTask do Last2'
}
Copy the code

When we execute Gradle myTask, we print the following:

> Task :app:myTask
myTask do First1
myTask do First2
myTask do Last1
myTask do Last2
Copy the code

A task contains a queue of actions to execute. When doFirst is used, an Action is added to the queue. When doLast is used, an Action is added to the end of the queue. So when myTask uses doFirst twice, the last time it is added to the queue head; MyTask uses doLast twice before being added to the end of the queue the last time.

Task has not only the concept of life cycle but also the concept of “inheritance”. DependsOn this is implemented using the dependsOn keyword. This is not the same as class inheritance.

task task1 << {
    println 'task1'
}

task task2 << {
    println 'task2'
}

task task3 << {
    println 'task3'
}

task task4 << {
    println 'task4'
}

task1.dependsOn('task2')
task2.dependsOn('task3')
task1.dependsOn('task4')
Copy the code

We defined four tasks and identified “inheritance” relationships: Task1 ->task2/task4->task3. Run gradle task1 and print:

> Task :app:task3
task3

> Task :app:task2
task2

> Task :app:task4
task4

> Task :app:task1
task1
Copy the code

You can see that task3 is executed first, then Task2, then Task4, and finally Task1. This is because the logic of dependensOn is to enforce “higher” and then “lower” generations. Task1 ->task2/task4->task3 So task3 is the highest, Task2 is level with Task4, and Task1 is the lowest. So task3 is executed first, then task2, then task4, which is not related to Task3, and finally Task1.

This article focuses on task tasks of the Android Plugin source code. If you are interested in tasks, you can go to the “Android+Gradle Authority Guide” or official documentation.

Android plugin Task

In the last article we talked about the createExtension callback method. Now let’s look at the createTasks callback method:

private void createTasksThreadrecorder.record (executionType.task_manager_create_tasks, project.getPath(), null, () -> taskManager.createTasksBeforeEvaluate()); AfterEvaluate (project -> threadRecorder. Record ( ExecutionType.BASE_PLUGIN_CREATE_ANDROID_TASKS, project.getPath(), null, () -> createAndroidTasks(false)));
}
Copy the code

Mainly is to call the two methods, one is the taskManager. CreateTasksBeforeEvaluate, one is createAndroidTasks, the creation of a Task which related to Android in createAndroidTasks method.

@VisibleForTesting
final void createAndroidTasks(boolean force) {
    // Make sure unit tests setthe required fields. checkState(extension.getBuildToolsRevision() ! = null,"buildToolsVersion is not specified."); checkState(extension.getCompileSdkVersion() ! = null,"compileSdkVersion is not specified.");

    ndkHandler.setCompileSdkVersion(extension.getCompileSdkVersion());

    // get current plugins and look for the default Java plugin.
    if (project.getPlugins().hasPlugin(JavaPlugin.class)) {
        throw new BadPluginException(
                "The 'java' plugin has been applied, but it is not compatible with the Android plugins.");
    }

    boolean targetSetupSuccess = ensureTargetSetup();
    sdkHandler.ensurePlatformToolsIsInstalledWarnOnFailure(
            extraModelInfo.getSyncIssueHandler());
    // Stop trying to configure the project if the SDK is not ready.
    // Sync issues will already have been collected at this point in sync.
    if(! targetSetupSuccess) { project.getLogger() .warn("Aborting configuration as SDK is missing components in sync mode.");
        return;
    }

    // don't do anything if the project was not initialized. // Unless TEST_SDK_DIR is set in which case this is unit tests and we  don't return.
    // This is because project don't get evaluated in the unit test setup. // See AppPluginDslTest if (! force && (! project.getState().getExecuted() || project.getState().getFailure() ! = null) && SdkHandler.sTestSdkFolder == null) { return; } if (hasCreatedTasks) { return; } hasCreatedTasks = true; extension.disableWrite(); taskManager.configureCustomLintChecks(); ProcessProfileWriter.getProject(project.getPath()) .setCompileSdk(extension.getCompileSdkVersion()) .setBuildToolsVersion(extension.getBuildToolsRevision().toString()) .setSplits(AnalyticsUtil.toProto(extension.getSplits())); String kotlinPluginVersion = getKotlinPluginVersion(); if (kotlinPluginVersion ! = null) { ProcessProfileWriter.getProject(project.getPath()) .setKotlinPluginVersion(kotlinPluginVersion); } // setup SDK repositories. sdkHandler.addLocalRepositories(project); threadRecorder.record( ExecutionType.VARIANT_MANAGER_CREATE_ANDROID_TASKS, project.getPath(), null, () -> { variantManager.createAndroidTasks(); ApiObjectFactory apiObjectFactory = new ApiObjectFactory( androidBuilder, extension, variantFactory, project.getObjects()); for (VariantScope variantScope : variantManager.getVariantScopes()) { BaseVariantData variantData = variantScope.getVariantData(); apiObjectFactory.create(variantData); } // Make sure no SourceSets were added through the DSL without being properly configured sourceSetManager.checkForUnconfiguredSourceSets(); // must run this after scopes are created so that we can configure kotlin // kapt tasks taskManager.addDataBindingDependenciesIfNecessary( extension.getDataBinding(), variantManager.getVariantScopes()); }); // create the global lint task that depends on all the variants taskManager.configureGlobalLintTask(variantManager.getVariantScopes()); // Create and read external native build JSON files depending on what's happening right
    // now.
    //
    // CREATE PHASE:
    // Creates JSONs by shelling out to external build system when:
    //   - Any one of AndroidProject.PROPERTY_INVOKED_FROM_IDE,
    //      AndroidProject.PROPERTY_BUILD_MODEL_ONLY_ADVANCED,
    //      AndroidProject.PROPERTY_BUILD_MODEL_ONLY,
    //      AndroidProject.PROPERTY_REFRESH_EXTERNAL_NATIVE_MODEL are set.
    //   - *and* AndroidProject.PROPERTY_REFRESH_EXTERNAL_NATIVE_MODEL is set
    //      or JSON files don't exist or are out-of-date. // Create phase may cause ProcessException (from cmake.exe for example) // // READ PHASE: // Reads and deserializes JSONs when: // - Any one of AndroidProject.PROPERTY_INVOKED_FROM_IDE, // AndroidProject.PROPERTY_BUILD_MODEL_ONLY_ADVANCED, // AndroidProject.PROPERTY_BUILD_MODEL_ONLY, // AndroidProject.PROPERTY_REFRESH_EXTERNAL_NATIVE_MODEL are set. // Read phase may produce IOException if the file can't be read for standard IO reasons.
    // Read phase may produce JsonSyntaxException in the case that the content of the file is
    // corrupt.
    boolean forceRegeneration =
            projectOptions.get(BooleanOption.IDE_REFRESH_EXTERNAL_NATIVE_MODEL);

    checkSplitConfiguration();
    if (ExternalNativeBuildTaskUtils.shouldRegenerateOutOfDateJsons(projectOptions)) {
        threadRecorder.record(
                ExecutionType.VARIANT_MANAGER_EXTERNAL_NATIVE_CONFIG_VALUES,
                project.getPath(),
                null,
                () -> {
                    for (VariantScope variantScope : variantManager.getVariantScopes()) {
                        ExternalNativeJsonGenerator generator =
                                variantScope.getExternalNativeJsonGenerator();
                        if(generator ! = null) { // This will generate any out-of-date or non-existent JSONs. // When refreshExternalNativeModel() istrueit will also // force update all JSONs. generator.build(forceRegeneration); }}}); } BuildableArtifactImpl.Companion.enableResolution(); }Copy the code

The code is a bit long, so let’s focus on it. The above code mostly executes the callback method:

variantManager.createAndroidTasks();
ApiObjectFactory apiObjectFactory =
        new ApiObjectFactory(
                androidBuilder,
                extension,
                variantFactory,
                project.getObjects());
for (VariantScope variantScope : variantManager.getVariantScopes()) {
    BaseVariantData variantData = variantScope.getVariantData();
    apiObjectFactory.create(variantData);
}

// Make sure no SourceSets were added through the DSL without being properly configured
sourceSetManager.checkForUnconfiguredSourceSets();

// must run this after scopes are created so that we can configure kotlin
// kapt tasks
taskManager.addDataBindingDependenciesIfNecessary(
        extension.getDataBinding(), variantManager.getVariantScopes());
Copy the code

Which implements

public void createAndroidTasks() { variantFactory.validateModel(this); variantFactory.preVariantWork(project); // Create variantScopes when variantScopes are emptyif (variantScopes.isEmpty()) {
        recorder.record(
                ExecutionType.VARIANT_MANAGER_CREATE_VARIANTS,
                project.getPath(),
                null /*variantName*/,
                this::populateVariantDataList);
    }

    // Create top level testtasks. recorder.record( ExecutionType.VARIANT_MANAGER_CREATE_TESTS_TASKS, project.getPath(), null /*variantName*/, () -> taskManager.createTopLevelTestTasks(! productFlavors.isEmpty())); // Create related build tasks for all defined channelsfor (final VariantScope variantScope : variantScopes) {
        recorder.record(
                ExecutionType.VARIANT_MANAGER_CREATE_TASKS_FOR_VARIANT,
                project.getPath(),
                variantScope.getFullVariantName(),
                () -> createTasksForVariantData(variantScope));
    }

    taskManager.createReportTasks(variantScopes);
}
Copy the code

We see directly for all of the definition of the construction of the channel to create relevant task of logic, is called the variantScopes foreach method, for each channel, the corresponding performed createTasksForVariantData method:

Public void createTasksForVariantData (final VariantScope VariantScope) {/ / 1, parse the variant channel information such as the final BaseVariantData variantData = variantScope.getVariantData(); final VariantType variantType = variantData.getType(); final GradleVariantConfiguration variantConfig = variantScope.getVariantConfiguration(); final BuildTypeData buildTypeData = buildTypes.get(variantConfig.getBuildType().getName());if(buildTypeData getAssembleTask () = = null) {/ / 2, creating a build type (may be custom buildType) assembleTask buildTypeData.setAssembleTask(taskManager.createAssembleTask(buildTypeData)); AssembleTask taskManager.gettAskFactory (). Configure ();"assemble", task -> { assert buildTypeData.getAssembleTask() ! = null; task.dependsOn(buildTypeData.getAssembleTask().getName()); }); / / 4, create the variant exclusive assembleTask createAssembleTaskForVariantData (variantData);if(variantType.isforTesting ()) {// omit... }elseAdd assembleTask {/ / 5, build the project task required depend on the taskManager. CreateTasksForVariantScope (variantScope); }}Copy the code

CreateTasksForVariantData method is mainly carried out the following logic: Build an assembleTask for buildTypes (defined under buildTypes). Add dependencies to assemble. 4 5. Add task dependencies to assembleTask to build the project

Let’s first look at the detailed logic of step 4:

private void createAssembleTaskForVariantData(final BaseVariantData variantData) {
    final VariantScope variantScope = variantData.getScope();
    if (variantData.getType().isForTesting()) {
        variantScope.setAssembleTask(taskManager.createAssembleTask(variantData));
    } else {
        BuildTypeData buildTypeData =
                buildTypes.get(variantData.getVariantConfiguration().getBuildType().getName());

        Preconditions.checkNotNull(buildTypeData.getAssembleTask());

        if(productFlavors. IsEmpty ()) {// If there are no options}else{// omit some code.. // assembleTaskfor this flavor(dimension), created on demand if needed.
            if(variantConfig. GetProductFlavors (). The size () > 1) {/ / access to name the final String name = StringHelper.capitalize(variantConfig.getFlavorName()); . / / final assembly name String variantAssembleTaskName = StringHelper appendCapitalized ("assemble", name);
                if(! TaskManager. GetTaskFactory (). Either containsKey (variantAssembleTaskName)) {/ / create the corresponding channel Task Task = taskManager.getTaskFactory().create(variantAssembleTaskName); task.setDescription("Assembles all builds for flavor combination: "+ name); Task.setgroup (task.setGroup)"Build"); task.dependsOn(variantScope.getAssembleTask().getName()); Taskmanager.gettaskfactory ().configure()"assemble", task1 -> task1.dependsOn(variantAssembleTaskName)); }}}}Copy the code

Here the task name is assembled and the corresponding channel task is created. The channel task is now created, but when we execute the channel task, we will have to rely on other tasks if we can eventually build an APK, and we will move on to step 5:

public void createTasksForVariantScope(@NonNull final VariantScope variantScope) { BaseVariantData variantData = variantScope.getVariantData(); assert variantData instanceof ApplicationVariantData; createAnchorTasks(variantScope); createCheckManifestTask(variantScope); handleMicroApp(variantScope); // Create all current streams (dependencies mostly at this point) createDependencyStreams(variantScope); // Add a task to publish the applicationId. createApplicationIdWriterTask(variantScope); taskFactory.create(new MainApkListPersistence.ConfigAction(variantScope)); taskFactory.create(new BuildArtifactReportTask.ConfigAction(variantScope)); // Add a task to process the manifest(s) recorder.record( ExecutionType.APP_TASK_MANAGER_CREATE_MERGE_MANIFEST_TASK, project.getPath(), variantScope.getFullVariantName(), () -> createMergeApkManifestsTask(variantScope)); // Add a task to create the res values recorder.record( ExecutionType.APP_TASK_MANAGER_CREATE_GENERATE_RES_VALUES_TASK, project.getPath(), variantScope.getFullVariantName(), () -> createGenerateResValuesTask(variantScope)); Omit similar methods... }Copy the code

A number of build-time tasks are created in this method, such as creating Manifest files, merging Manifest files, processing resource files, and so on. So when we execute the Assemble task, the task created above is executed as well, and all of these tasks constitute a build of the project.

Four,

This concludes the source code analysis of the Android Plugin. From the source code of the Android Plugin, we know the entire building process of the Android project and how to create extensions and tasks, which lays the foundation for the creation of custom plug-ins later.