trycatchx / RocketX

🔥🔥 android 端编译加速插件🚀 动态识别未改动 module 并在编译流程中替换为 aar ,只编译改动模块,加速 Android apk 的编译速度。
861 stars 104 forks source link

Cannot change dependencies of dependency configuration ':app:implementation' after it has been included in dependency resolution. #18

Closed manza7014 closed 2 years ago

manza7014 commented 2 years ago

步骤 1.首次运行正常 2.再次运行报错,日志如下 另:在另外一个项目上运行正常 @trycatchx

Exception is:
org.gradle.api.InvalidUserDataException: Cannot change dependencies of dependency configuration ':app:implementation' after it has been included in dependency resolution.
    at org.gradle.api.internal.artifacts.configurations.DefaultConfiguration.preventIllegalMutation(DefaultConfiguration.java:1137)
    at org.gradle.api.internal.artifacts.configurations.DefaultConfiguration.validateMutation(DefaultConfiguration.java:1099)
    at org.gradle.api.internal.artifacts.configurations.DefaultConfiguration.lambda$validateMutationType$1(DefaultConfiguration.java:298)
    at org.gradle.internal.ImmutableActionSet$SingletonSet.execute(ImmutableActionSet.java:225)
    at org.gradle.api.internal.DefaultDomainObjectSet.assertMutableCollectionContents(DefaultDomainObjectSet.java:71)
    at org.gradle.api.internal.DefaultDomainObjectCollection$IteratorImpl.remove(DefaultDomainObjectCollection.java:486)
    at kotlin.collections.CollectionsKt__MutableCollectionsKt.filterInPlace$CollectionsKt__MutableCollectionsKt(MutableCollections.kt:220)
    at kotlin.collections.CollectionsKt__MutableCollectionsKt.removeAll(MutableCollections.kt:206)
    at plugin.utils.DependenciesHelper.modifyDependencies(DependenciesHelper.kt:80)
    at plugin.ChildProjectDependencies.doDependencies(ChildProjectDependencies.kt:64)
    at plugin.AppProjectDependencies.resolveDenpendency(AppProjectDependencies.kt:46)
    at plugin.AppProjectDependencies._init_$lambda-0(AppProjectDependencies.kt:30)
trycatchx commented 2 years ago

检查一下 是否配置 org.gradle.configureondemand = true ,关闭即可

manza7014 commented 2 years ago

检查一下 是否配置 org.gradle.configureondemand = true ,关闭即可

没有配置这个

trycatchx commented 2 years ago

目前使用gradle 版本是多少?

manza7014 commented 2 years ago

目前使用gradle 版本是多少?

gradle版本:6.5 AGP版本:4.0.2

trycatchx commented 2 years ago

目前一个项目可以正常运行,另一个不行是吧。问题是修改依赖的时机慢导致的。

你可以通过本地依赖:加入以下代码

buildscript {
    repositories {
        mavenCentral()
        google()
    }
    dependencies {
        def pluginDir = getProjectDir().getAbsolutePath() + File.separator + "zPlugin"
        //编译加速插件,需要去插件市场搜索 RockectX 并开启可以加速项目编译速度
        def rockectxFile = new File(pluginDir,"rockectx.jar")
        classpath files(rockectxFile)
    }
}

项目根目录创建一下 zPlugin 文件夹,解压下面的 zip 文件拿到本地的 jar 放进 zPlugin 文件夹 rockectx.jar.zip

上面的本地 jar 主要是剔除了 并行的开关。定位是不是并行的原因导致的

manza7014 commented 2 years ago

好的 ,我试下

manza7014 commented 2 years ago

目前一个项目可以正常运行,另一个不行是吧。问题是修改依赖的时机慢导致的。

你可以通过本地依赖:加入以下代码

buildscript {
    repositories {
        mavenCentral()
        google()
    }
    dependencies {
        def pluginDir = getProjectDir().getAbsolutePath() + File.separator + "zPlugin"
        //编译加速插件,需要去插件市场搜索 RockectX 并开启可以加速项目编译速度
        def rockectxFile = new File(pluginDir,"rockectx.jar")
        classpath files(rockectxFile)
    }
}

项目根目录创建一下 zPlugin 文件夹,解压下面的 zip 文件拿到本地的 jar 放进 zPlugin 文件夹 rockectx.jar.zip

上面的本地 jar 主要是剔除了 并行的开关。定位是不是并行的原因导致的

步骤: 1.添加rockectx本地依赖并同步 2.执行RocketXClean 3.首次run正常 4.再次run还是同样的问题

manza7014 commented 2 years ago

gradle这一块不太懂,github上扒了下这个 //The configuration has been used in a resolution, and it is an error for build logic to change any dependencies, // exclude rules or parent configurations (values that will affect the resolved graph).

private void preventIllegalMutation(MutationType type) {
        // TODO: Deprecate and eventually prevent these mutations when already resolved
        if (type == MutationType.DEPENDENCY_ATTRIBUTES) {
            return;
        }

        InternalState resolvedState = currentResolveState.get().state;
        if (resolvedState == ARTIFACTS_RESOLVED) {
            // The public result for the configuration has been calculated.
            // It is an error to change anything that would change the dependencies or artifacts
            throw new InvalidUserDataException(String.format("Cannot change %s of dependency %s after it has been resolved.", type, getDisplayName()));
        } else if (resolvedState == GRAPH_RESOLVED) {
            // The task dependencies for the configuration have been calculated using Configuration.getBuildDependencies().
            throw new InvalidUserDataException(String.format("Cannot change %s of dependency %s after task dependencies have been resolved", type, getDisplayName()));
        } else if (observedState == GRAPH_RESOLVED || observedState == ARTIFACTS_RESOLVED) {
            // The configuration has been used in a resolution, and it is an error for build logic to change any dependencies,
            // exclude rules or parent configurations (values that will affect the resolved graph).
            if (type != MutationType.STRATEGY) {
                String extraMessage = insideBeforeResolve ? " Use 'defaultDependencies' instead of 'beforeResolve' to specify default dependencies for a configuration." : "";
                throw new InvalidUserDataException(String.format("Cannot change %s of dependency %s after it has been included in dependency resolution.%s", type, getDisplayName(), extraMessage));
            }
        }
    }
trycatchx commented 2 years ago

@manza7014 好的,那应该和这个没有关系。应该还是兼容性问题。目前我排查到的位置是: //步骤1:入口 DefaultDomainObjectCollection.IteratorImpl.class

  public void remove() {
           //..
            DefaultDomainObjectCollection.this.assertMutableCollectionContents();  // 此处报错
            this.iterator.remove();
          //..
        }

//步骤2:调用 DefaultDomainObjectSet.java


    protected void assertMutableCollectionContents() {
        this.beforeContainerChange.execute((Object)null);
    }

//步骤3: beforeContainerChange 是 下面的 action

    private static Action<Void> validateMutationType(final MutationValidator mutationValidator, final MutationType type) {
        return new Action<Void>() {
            public void execute(Void arg) {
                mutationValidator.validateMutation(type); //检查都否修改依赖
            }
        };
    }

//步骤4 :报错位置:

      public void validateMutation(MutationType type) {
        this.preventIllegalMutation(type);
        this.markAsModified(type);
        this.notifyChildren(type);
    }

    private void preventIllegalMutation(MutationType type) {
        if (type != MutationType.DEPENDENCY_ATTRIBUTES) {
            if (this.resolvedState == InternalState.ARTIFACTS_RESOLVED) {
                throw new InvalidUserDataException(String.format("Cannot change %s of dependency %s after it has been resolved.", type, this.getDisplayName()));
            } else if (this.resolvedState == InternalState.GRAPH_RESOLVED) {
                throw new InvalidUserDataException(String.format("Cannot change %s of dependency %s after task dependencies have been resolved", type, this.getDisplayName()));
            } else if ((this.observedState == InternalState.GRAPH_RESOLVED || this.observedState == InternalState.ARTIFACTS_RESOLVED) && type != MutationType.STRATEGY) {
                String extraMessage = this.insideBeforeResolve ? " Use 'defaultDependencies' instead of 'beforeResolve' to specify default dependencies for a configuration." : "";
                throw new InvalidUserDataException(String.format("Cannot change %s of dependency %s after it has been included in dependency resolution.%s", type, this.getDisplayName(), extraMessage));
            }
        }
    }
trycatchx commented 2 years ago

@manza7014 可能我需要在 步骤2 反射 beforeContainerChange 这个变量为: ImmutableActionSet.empty(); ,然后再执行 :

   parentConfig.dependencies.removeAll { dependency ->
                    dependency is DefaultProjectDependency && dependency.name.equals(projectWapper.project.name)
                }

才能摆脱这个 异常。目前根据gradle 源码查看 去除检查 应该是可行: image

trycatchx commented 2 years ago

@manza7014 有兴趣帮忙提个 pr 吗?修改完毕之后并可以在你的工程检验一下效果。 如果没有时间的话,需要等待我们这边进行修复。

manza7014 commented 2 years ago

之前还没提过pr :flushed:,我试试

trycatchx commented 2 years ago

@manza7014 好的你可以先尝试一下,非常感谢

manza7014 commented 2 years ago

@manza7014 好的你可以先尝试一下,非常感谢 客气,非常感谢作者这个RocketXPlugin,体验了,效果非常显著,👍

trycatchx commented 2 years ago

@manza7014

好的,关键的提示:

修改 DependenciesHelper.kt 类下面的:

    fun modifyDependencies(projectWapper: ChildProjectDependencies) {
             /**
              *这里加入 反射   parentConfig.dependencies 变量(它是一个 DefaultDomainObjectSet 对象,直接强转)
              * 并拿到 beforeContainerChange 变量赋值为 ImmutableActionSet.empty() (记得把变量的原有值保存)
             */

              // 剔除原有的依赖
                parentConfig.dependencies.removeAll { dependency ->
                    dependency is DefaultProjectDependency && dependency.name.equals(projectWapper.project.name)
                }

             // removeAll 操作完毕之后,记得 把原有值 赋值回去 (不赋值回去也问题不大)

    }
trycatchx commented 2 years ago

@manza7014 请问有进展吗?

manza7014 commented 2 years ago

还没。

parentConfig.dependencies 变量(它是一个 DefaultDomainObjectSet 对象,直接强转)

这里有问题,强转失败,提示类型不对。 parentConfig.dependencies类型应该是DefaultDependencySet。 是不是拿到DefaultDependencySet父类DelegatingDomainObjectSet中的私有属性backingSet(类型:DomainObjectSet,子类DefaultDomainObjectSet)才能修改? 试了试,调试过程中有点问题,今天忙着项目和年报总结就没搞了,想着明天再接着搞呢

调试有点艰难,我每次打了jar包才能试一下,暂时还没有把我的项目app module以及其他module放到RocketXPlugin项目中

trycatchx commented 2 years ago

@manza7014 你可以把buildSrc 拷贝到你的项目中,然后直接剔除之前的 classpath 依赖,即可调试。

trycatchx commented 2 years ago

还没。

parentConfig.dependencies 变量(它是一个 DefaultDomainObjectSet 对象,直接强转)

这里有问题,强转失败,提示类型不对。 parentConfig.dependencies类型应该是DefaultDependencySet。 是不是拿到DefaultDependencySet父类DelegatingDomainObjectSet中的私有属性backingSet(类型:DomainObjectSet,子类DefaultDomainObjectSet)才能修改? 试了试,调试过程中有点问题,今天忙着项目和年报总结就没搞了,想着明天再接着搞呢

调试有点艰难,我每次打了jar包才能试一下,暂时还没有把我的项目app module以及其他module放到RocketXPlugin项目中

是的,我看了一下确实是要通过拿到 DelegatingDomainObjectSet 的私有变量 backingSet (DefaultDomainObjectSet )

manza7014 commented 2 years ago

@manza7014 你可以把buildSrc 拷贝到你的项目中,然后直接剔除之前的 classpath 依赖,即可调试。

好的

trycatchx commented 2 years ago

@manza7014 我编写了 setBeforeContainerChangeToEmpty 方法,你有空在你的 工程试试。

    fun modifyDependencies(projectWapper: ChildProjectDependencies) {
               //..
                setBeforeContainerChangeToEmpty(parentConfig.dependencies)
                // 剔除原有的依赖
                parentConfig.dependencies.removeAll { dependency ->
                    dependency is DefaultProjectDependency && dependency.name.equals(projectWapper.project.name)
                }
            // ..
 }

    fun setBeforeContainerChangeToEmpty(dependency :DependencySet):ImmutableActionSet<Any>? {
        var ret : ImmutableActionSet<Any>? = null
        try {
            val fBackingSet = DelegatingDomainObjectSet::class.java.getDeclaredField("backingSet")
            fBackingSet.isAccessible = true
            val domainObjectSet =  fBackingSet.get(dependency)
            val fBeforeContainerChange = DefaultDomainObjectSet::class.java.getDeclaredField("beforeContainerChange")
            fBeforeContainerChange.isAccessible = true
            ret = fBeforeContainerChange.get(domainObjectSet) as? ImmutableActionSet<Any>
            fBeforeContainerChange.set(domainObjectSet, ImmutableActionSet.empty<Any>())
        }catch (ignore:Exception) {
        }
        return ret
    }
manza7014 commented 2 years ago

还没。

parentConfig.dependencies 变量(它是一个 DefaultDomainObjectSet 对象,直接强转)

这里有问题,强转失败,提示类型不对。 parentConfig.dependencies类型应该是DefaultDependencySet。 是不是拿到DefaultDependencySet父类DelegatingDomainObjectSet中的私有属性backingSet(类型:DomainObjectSet,子类DefaultDomainObjectSet)才能修改? 试了试,调试过程中有点问题,今天忙着项目和年报总结就没搞了,想着明天再接着搞呢 调试有点艰难,我每次打了jar包才能试一下,暂时还没有把我的项目app module以及其他module放到RocketXPlugin项目中

是的,我看了一下确实是要通过拿到 DelegatingDomainObjectSet 的私有变量 backingSet (DefaultDomainObjectSet )

关键是拿不到:no_mouth:

Can not set final org.gradle.api.DomainObjectSet field org.gradle.api.internal.DelegatingDomainObjectSet.backingSet to java.lang.reflect.Field
trycatchx commented 2 years ago

你尝试用我的方法,我刚刚试了好像没问题哦

manza7014 commented 2 years ago

@manza7014 我编写了 setBeforeContainerChangeToEmpty 方法,你有空在你的 工程试试。

    fun modifyDependencies(projectWapper: ChildProjectDependencies) {
               //..
                setBeforeContainerChangeToEmpty(parentConfig.dependencies)
                // 剔除原有的依赖
                parentConfig.dependencies.removeAll { dependency ->
                    dependency is DefaultProjectDependency && dependency.name.equals(projectWapper.project.name)
                }
            // ..
 }

    fun setBeforeContainerChangeToEmpty(dependency :DependencySet):ImmutableActionSet<Any>? {
        var ret : ImmutableActionSet<Any>? = null
        try {
            val fBackingSet = DelegatingDomainObjectSet::class.java.getDeclaredField("backingSet")
            fBackingSet.isAccessible = true
            val domainObjectSet =  fBackingSet.get(dependency)
            val fBeforeContainerChange = DefaultDomainObjectSet::class.java.getDeclaredField("beforeContainerChange")
            fBeforeContainerChange.isAccessible = true
            ret = fBeforeContainerChange.get(domainObjectSet) as? ImmutableActionSet<Any>
            fBeforeContainerChange.set(domainObjectSet, ImmutableActionSet.empty<Any>())
        }catch (ignore:Exception) {
        }
        return ret
    }

是的,可以了,多谢:+1:

trycatchx commented 2 years ago

@manza7014 项目可以正常运行了是吧。那你按照这个代码提个 rq ,我合入。

trycatchx commented 2 years ago

@manza7014 可以尝试一下修改 :AppProjectDependencies.kt 以下代码,我把修改依赖的时机提前一点。

 init {

        project.afterEvaluate {
            val allProject = mutableSetOf<Project>()
            project.rootProject.allprojects.forEach {
                allProject.add(it)
            }
//        project.gradle.projectsEvaluated {
//            // 调整依赖时机
//            if (isFirst) {
//                isFirst = false
//                resolveDenpendency()
//            }
//        }
            project.gradle.afterProject {
                allProject.remove(it)
                if (canbeResolveDenpendency(allProject)) {
                    // 调整依赖时机
                    if (isFirst) {
                        isFirst = false
                        resolveDenpendency()
                    }
                }
            }
        }
    }

    fun canbeResolveDenpendency(set: MutableSet<Project>): Boolean {
        var ret = true
        set.forEach {
            if (it != project.rootProject && it.childProjects.size <= 0
                    && (hasAndroidPlugin(it) || hasJavaPlugin(it))) {
                ret = false
                return@forEach
            }
        }
        return ret
    }
manza7014 commented 2 years ago

@manza7014 可以尝试一下修改 :AppProjectDependencies.kt 以下代码,我把修改依赖的时机提前一点。

 init {

        project.afterEvaluate {
            val allProject = mutableSetOf<Project>()
            project.rootProject.allprojects.forEach {
                allProject.add(it)
            }
//        project.gradle.projectsEvaluated {
//            // 调整依赖时机
//            if (isFirst) {
//                isFirst = false
//                resolveDenpendency()
//            }
//        }
            project.gradle.afterProject {
                allProject.remove(it)
                if (canbeResolveDenpendency(allProject)) {
                    // 调整依赖时机
                    if (isFirst) {
                        isFirst = false
                        resolveDenpendency()
                    }
                }
            }
        }
    }

    fun canbeResolveDenpendency(set: MutableSet<Project>): Boolean {
        var ret = true
        set.forEach {
            if (it != project.rootProject && it.childProjects.size <= 0
                    && (hasAndroidPlugin(it) || hasJavaPlugin(it))) {
                ret = false
                return@forEach
            }
        }
        return ret
    }
* Exception is:
org.gradle.api.tasks.TaskExecutionException: Execution failed for task ':app:compileTDCFDebugJavaWithJavac'.
at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.lambda$executeIfValid$1(ExecuteActionsTaskExecuter.java:207)
at org.gradle.internal.Try$Failure.ifSuccessfulOrElse(Try.java:263)
at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.executeIfValid(ExecuteActionsTaskExecuter.java:205)
at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.execute(ExecuteActionsTaskExecuter.java:186)
at org.gradle.api.internal.tasks.execution.CleanupStaleOutputsExecuter.execute(CleanupStaleOutputsExecuter.java:114)
at org.gradle.api.internal.tasks.execution.FinalizePropertiesTaskExecuter.execute(FinalizePropertiesTaskExecuter.java:46)
at org.gradle.api.internal.tasks.execution.ResolveTaskExecutionModeExecuter.execute(ResolveTaskExecutionModeExecuter.java:62)
at org.gradle.api.internal.tasks.execution.SkipTaskWithNoActionsExecuter.execute(SkipTaskWithNoActionsExecuter.java:57)
at org.gradle.api.internal.tasks.execution.SkipOnlyIfTaskExecuter.execute(SkipOnlyIfTaskExecuter.java:56)
at org.gradle.api.internal.tasks.execution.CatchExceptionTaskExecuter.execute(CatchExceptionTaskExecuter.java:36)
at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter$1.executeTask(EventFiringTaskExecuter.java:77)
at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter$1.call(EventFiringTaskExecuter.java:55)
at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter$1.call(EventFiringTaskExecuter.java:52)
at org.gradle.internal.operations.DefaultBuildOperationExecutor$CallableBuildOperationWorker.execute(DefaultBuildOperationExecutor.java:409)
at org.gradle.internal.operations.DefaultBuildOperationExecutor$CallableBuildOperationWorker.execute(DefaultBuildOperationExecutor.java:399)
at org.gradle.internal.operations.DefaultBuildOperationExecutor$1.execute(DefaultBuildOperationExecutor.java:157)
at org.gradle.internal.operations.DefaultBuildOperationExecutor.execute(DefaultBuildOperationExecutor.java:242)
at org.gradle.internal.operations.DefaultBuildOperationExecutor.execute(DefaultBuildOperationExecutor.java:150)
at org.gradle.internal.operations.DefaultBuildOperationExecutor.call(DefaultBuildOperationExecutor.java:94)
at org.gradle.internal.operations.DelegatingBuildOperationExecutor.call(DelegatingBuildOperationExecutor.java:36)
at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter.execute(EventFiringTaskExecuter.java:52)
at org.gradle.execution.plan.LocalTaskNodeExecutor.execute(LocalTaskNodeExecutor.java:41)
at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$InvokeNodeExecutorsAction.execute(DefaultTaskExecutionGraph.java:356)
at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$InvokeNodeExecutorsAction.execute(DefaultTaskExecutionGraph.java:343)
at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$BuildOperationAwareExecutionAction.execute(DefaultTaskExecutionGraph.java:336)
at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$BuildOperationAwareExecutionAction.execute(DefaultTaskExecutionGraph.java:322)
at org.gradle.execution.plan.DefaultPlanExecutor$ExecutorWorker.lambda$run$0(DefaultPlanExecutor.java:127)
at org.gradle.execution.plan.DefaultPlanExecutor$ExecutorWorker.execute(DefaultPlanExecutor.java:191)
at org.gradle.execution.plan.DefaultPlanExecutor$ExecutorWorker.executeNextNode(DefaultPlanExecutor.java:182)
at org.gradle.execution.plan.DefaultPlanExecutor$ExecutorWorker.run(DefaultPlanExecutor.java:124)
at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:64)
at org.gradle.internal.concurrent.ManagedExecutorImpl$1.run(ManagedExecutorImpl.java:48)
at org.gradle.internal.concurrent.ThreadFactoryImpl$ManagedThreadRunnable.run(ThreadFactoryImpl.java:56)
Caused by: org.gradle.api.internal.tasks.compile.CompilationFailedException: Compilation failed; see the compiler error output for details

步骤:把昨天的修改: setBeforeContainerChangeToEmpty去掉了,只做了上面你说的这个修改然后报错

显示:

错误: 程序包com.yskj.applypermission不存在
import com.yskj.applypermission.PermissionChecker;

app module中某个文件引用的permission module中的类文件不存在,其他module文件也存在找不到的现象

trycatchx commented 2 years ago

使用 classpath 'io.github.trycatchx:rocketx:1.0.12' 解决以上问题

manza7014 commented 2 years ago

使用 classpath 'io.github.trycatchx:rocketx:1.0.12' 解决以上问题

好的