The problem

The breakpoint is set to true every time the breakpoint is clear. The breakpoint is set to true every time the breakpoint is clear. The breakpoint is set to true every time the breakpoint is clear.

The value of ApBuildCofig.LOGCAT_DISPLAY is true:

When we break into Plog’s method, we find that the value has changed to false:

Since this value needs to be changed frequently during project development, each clear + Build time can become very long, and a complete compilation of a large project can take more than 10+ minutes, which is completely unacceptable.

Analysis of the

This problem is a recent one, not one that has occurred before

Consider the latest gradle version, Kotlin version, or IDE upgrade, or related code changes

Clear is required to work properly, without affecting the complete compilation package

Note this problem is related to compilation, specifically compilation cache

Reduction problem

This problem is a recent one, not one that has occurred before

This problem is easy to solve, check the latest version of Kotlin, the last update was two months ago, indicating that it is not the problem of kotlin version. Look at gradle versions as well. In the case of IDE, I did upgrade the latest Android Studio 4.1 version, but another colleague’s IDE version did not upgrade, and this problem also occurred. We can rule out the problem caused by the update of the compiled version. The rest is the problem caused by the modification of some code, but due to the recent modification submitted more, it is difficult to locate, and the performance of the problem may be related to compilation, first see if the second problem has a result, and then push back the changed code

Clear is required to work properly, without affecting the complete compilation package

First, decompress Kotlin directly through the IDE and get the compiled Java file:

public final class MainActivity extends AppCompatActivity {
   protected void onCreate(@Nullable Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      this.setContentView(1300051);
      // As you can see, the result of compiling is to set a value directly instead of passing ApBuildCofig.LOGCAT_DISPLAY
      Plog.setLogcatSwitch(false); }}Copy the code

At this point, the problem at the beginning of the article is nicely explained: ApBuildCofig.LOGCAT_DISPLAY is true, but the Plog is false.

Because Kotlin compiles a static final property (that is, a constant) believing that its value will not change, it simply extracts the value of the constant and no longer needs to reference it.

At this point, the question is clear: When compiling Kotlin, the corresponding Gradle task thinks that the reference constant (ApBuildCofig.LOGCAT_DISPLAY) has not changed, so there is no need to recompile the current Kotlin file, resulting in Plog getting an old value.

As for the first issue, it’s clear that the code was previously written in Java and recently switched to Kotlin

Test the restore scenario

Although the problem has been identified, the root cause has not been found, namely:

Why does Kotlin skip the recompile phase and use the previous cache, assuming that the ApBuildCofig.LOGCAT_DISPLAY value has not changed?

Related to the class

For this purpose, I specially use a demo to restore the project directly. The following is the restore Demo file, it is recommended to download the demo directly to view the relationship, or directly see the class diagram:

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?). {
        super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) Plog.setLogcatSwitch(AppBuildConfig.LOGCAT_DISPLAY) }}Copy the code
public class Plog {
    private static boolean logcatSwitch;
    public static void setLogcatSwitch(boolean logcatSwitch) { Plog.logcatSwitch = logcatSwitch; }}Copy the code
public class AppBuildConfig {
    public static final boolean LOGCAT_DISPLAY = BuildConfig.LOG;
}
Copy the code

The BuildConfig class is automatically generated by IDE compilation:

// Automatically generated classes
public final class BuildConfig {
  //other.....
  // Field from default config.
  public static final boolean LOG = false;
}
Copy the code

Gradle writes this value:

/** * get the current Log switch */
private String getCurrentProperties() {
    Properties property = new Properties()
    File propertyFile = new File(rootDir.getAbsolutePath(), "project.properties")
    property.load(propertyFile.newDataInputStream())
    return property.getProperty("log")}Copy the code

The corresponding project.properties is the configuration file for the entire project, which contains the following contents:

log=false
Copy the code

Class diagram

Among them MainActivity is written by Kotlin. Because mainactivity. kt was not recompiled, when we change the value of project.properties, Plog still gets the last compilation value of mainactivity. kt.

Viewing the compile Task

To verify the above conclusion, modify the contents of project.properties:

log=true
Copy the code

After the change, click Run to view the Build window:

As you can see, Kotlin’s task is directly followed by “up-to-date”, meaning that compilation is skipped and caching is used directly.

CompileDebugKotlin is a task that can skip running and use the compileDebugKotlin since version 1.2.20. Gradle’s build cache rules can be found in the reference link at the end of this article. One of the more important rules is that the input does not change so compileDebugKotlin skips this task. The input also contains a lot of information, such as whether the kotlin file has changed, whether the path has changed, and whether its associated classes have changed, etc.

This bug is caused by:

The kotlin file (mainActivity. kt) itself has not changed, and the AppBuildConfig class associated with it has not changed, so compileDebugKotlin will skip compilation and use the previous compilation. Kotlin automatically replaces constant references with values at compile time, so even if AppBuildConfig’s associated class BuildConfig changes, mainActivity.kt will not be affected. This causes it to pass an incorrect value to Plog, which is why clear is ok, because clear clears the previous cache.

extension

Based on the above conclusion, I tested kotlin → A. Constant → B. Constant. If you change the constant value of B, Kotlin’s compile task will not notice that the input has changed and Kotlin will need to recompile, presumably because of a Bug in Kotlin’s build cache

The solution

The best way to fix the problem is to let the task compileDebugKotlin, which needs to modify Kotlin, recognize the change.

Plan a

The simpler solution is to simply invalidate Kotlin’s compile task cache:

this.afterEvaluate { Project project ->
    // Get the task to compile kotlin
    def buildTask = project.tasks.getByName('compileDebugKotlin')
     // The task cannot be skipped
    buildTask.outputs.upToDateWhen {
        false}}Copy the code

The above method is simple and straightforward, but it requires recompiling kotlin every time, which can be expensive, especially when there are many Kotlin files in the project. We can listen for changes in the configuration file, and force the task not to be skipped if there are changes:

this.afterEvaluate { Project project ->
    // Get the task to compile kotlin
    def buildTask = project.tasks.getByName('compileDebugKotlin')
    // Read the last value
    def (String logCat, File propertyFile) = getLastProperties()
    // Read the current value
    def currentLog = getCurrentProperties()
    System.out.println("upToDateWhen:" + (logCat == currentLog))
    // Compare the two values TO see if they are equal. If they are equal, allow up-to-date, that is, allow caching and skip kotlin compilation
    buildTask.outputs.upToDateWhen {
        logCat == currentLog
    }
    // Write the current logcat value for the next compilation
    propertyFile.write("log=$currentLog")}Copy the code

Scheme 2

Kotlin’s compile task uses cache, because its input is consistent, we only need to destroy its input, there are two change points, one is to modify the compiled product, directly delete app/build/ TMP /kotlin-class file. Then Kotlin will see that the last hash is different from the saved hash, and it will automatically recompile the entire Kotlin, but this is slower, in the same way that the previous task was forced to not use caching. Another change is to modify the source file directly, append some comments to the target file, Then kotlin considers the target file changed and compiles only the specified Kotlin file:

this.afterEvaluate { Project project ->
    // Read the last value
    def (String logCat, File propertyFile) = getLastProperties()
    // Read the current value
    def currentLog = getCurrentProperties()
    System.out.println("upToDateWhen:" + (logCat == currentLog))
    
   // The second option
    File file = new File(rootDir.getAbsolutePath() + "/app/src/main/java/com/siyehua/kotlincomplierbug"."MainActivity.kt")
    System.out.println("upToDateWhen:" + file.path)

    if(logCat ! = currentLog && file.exists()) {// If the switch is different and the cache exists, delete the cache directly
        def list = file.text
        if(! list.endsWith("\n/*gradle change file*/")) {
            file.append("\n/*gradle change file*/")
            System.out.println("upToDateWhen:" + "change targe file1")}else {
            list = list.replace("\n/*gradle change file*/"."")
            file.write(list.toString())
            System.out.println("upToDateWhen:" + "change cache file2")}}if(logCat ! = currentLog &&file.exists()){// If the switch is different and the cache exists, delete the cache directly
        file.delete()
        System.out.println("upToDateWhen:" + "delete cache file")}// Write the current logcat value for the next compilation
    propertyFile.write("log=$currentLog")}Copy the code

The optimization of option 2 is much faster than option 1, mainly by compiling only the target Kotlin file

engineering

Github.com/siyehua/Kot…

The resources

Kotlin build cache feature: www.oschina.net/news/92528/… Gradle task the up – to – date: www.jianshu.com/p/eb3fb33e4…