记一次Gradle插件下载异常的分析

前言

今天在微信读书上翻到一本关于Spring源码分析的书《Spring源码深度解析》,于是想着动手调试下Spring的源码,想起一年前下载的Spring-framework-5.1.5RELEASE源码,于是导入IDEA,果不其然,报错了.

问题分析

下面是IDEA console输出的错误日志

> Task :buildSrc:compileJava NO-SOURCE
> Task :buildSrc:compileGroovy UP-TO-DATE
> Task :buildSrc:processResources UP-TO-DATE
> Task :buildSrc:classes UP-TO-DATE
> Task :buildSrc:jar UP-TO-DATE
> Task :buildSrc:assemble UP-TO-DATE
> Task :buildSrc:compileTestJava NO-SOURCE
> Task :buildSrc:compileTestGroovy NO-SOURCE
> Task :buildSrc:processTestResources NO-SOURCE
> Task :buildSrc:testClasses UP-TO-DATE
> Task :buildSrc:test NO-SOURCE
> Task :buildSrc:check UP-TO-DATE
> Task :buildSrc:build UP-TO-DATE

FAILURE: Build failed with an exception.

* Where:
Build file 'D:\Դ�����\spring-framework-5.1.5.RELEASE\build.gradle' line: 16

* What went wrong:
Plugin [id: 'io.spring.dependency-management', version: '1.0.5.RELEASE', apply: false] was not found in any of the following sources:

- Gradle Core Plugins (plugin is not in 'org.gradle' namespace)
- Plugin Repositories (could not resolve plugin artifact 'io.spring.dependency-management:io.spring.dependency-management.gradle.plugin:1.0.5.RELEASE')
Searched in the following repositories:
Gradle Central Plugin Repository

* Try:
Run with --info or --debug option to get more log output. Run with --scan to get full insights.

* Exception is:
org.gradle.api.plugins.UnknownPluginException: Plugin [id: 'io.spring.dependency-management', version: '1.0.5.RELEASE', apply: false] was not found in any of the following sources:

- Gradle Core Plugins (plugin is not in 'org.gradle' namespace)
- Plugin Repositories (could not resolve plugin artifact 'io.spring.dependency-management:io.spring.dependency-management.gradle.plugin:1.0.5.RELEASE')
Searched in the following repositories:
Gradle Central Plugin Repository
at org.gradle.plugin.use.internal.DefaultPluginRequestApplicator.resolveToFoundResult(DefaultPluginRequestApplicator.java:262)
at org.gradle.plugin.use.internal.DefaultPluginRequestApplicator.access$100(DefaultPluginRequestApplicator.java:63)
...省略


* Get more help at https://help.gradle.org

BUILD FAILED in 4s

找到build.gradle文件,查看第16行,如下

// 3rd party plugin repositories can be configured in settings.gradle
plugins {
//配置spring依赖管理插件
id "io.spring.dependency-management" version "1.0.5.RELEASE" apply false
id "org.jetbrains.kotlin.jvm" version "1.2.71" apply false
id "org.jetbrains.dokka" version "0.9.17"
id "org.asciidoctor.convert" version "1.5.8"
}

就是很普通的插件配置

上面的错误日志意思大概就是无法从插件仓库中找到该插件

开始调试

因为一开始用的IDEA里的Gradle插件同步整个Project,不知道从哪入手调试,后面想到还有gradle-wrapper可以试着看能不能DEBUG.

添加gradlew && gradlew-api依赖

配置IDEA REMOTE DEBUG

在IDEA的Toolbar找到 Select Run/Debug Configuration->Edit Configurations...,

Remote Debug Configuration

修改gradlew.bat

到项目根目录下,找到gradlew.bat文件(window的批处理文件)。

修改如下

set REMOTE_DEBUG="-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=5005"
set PROXY=-Dhttp.proxyHost=localhost -Dhttp.proxyPort=58707 -Dhttps.proxyHost=localhost -Dhttps.proxyPort=58707 -Djava.net.useSystemProxies=true -DAtestValue=FuCK
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
echo "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %REMOTE_DEBUG% %PROXY% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %REMOTE_DEBUG% %PROXY% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%

上面的远程调试参数中的suspend值要为y,表示在远程调试器未连接上之前等待.

同时上面还给JVM配置了http代理,因为gradle下载需要翻墙.

为了保险起见,在项目根目录中的gradle.properties增加如下内容

systemProp.http.proxyHost=127.0.0.1
systemProp.http.proxyPort=58707
systemProp.https.proxyHost=127.0.0.1
systemProp.https.proxyPort=58707

开始调试

打开IDEA底部Terminal或者windows的命令行工具,这里以前者为例

因为IDEA的Terminal打开后,默认的目录为当前项目的根目录

输入如下命令

gradlew.bat clean --stacktrace --no-daemon

按下回车后,如果上述配置没错,会得到如下输出

"C:\Program Files\Java\jdk1.8.0_191/bin/java.exe"   "-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=5005" -Dhttp.proxyHost=localhost -D
http.proxyPort=58707 -Dhttps.proxyHost=localhost -Dhttps.proxyPort=58707 -Djava.net.useSystemProxies=true -XX:MaxMetaspaceSize=1024m
-Xmx1024m -XX:MaxHeapSize=256m "-Dorg.gradle.appname=gradlew" -classpath "D:\源码分析\spring-framework-5.1.5.RELEASE\\gradle\wrapper\gradle-wrapper.
jar" org.gradle.wrapper.GradleWrapperMain clean --stacktrace --no-daemon
Listening for transport dt_socket at address: 5005

上面的命令只是启动了GradleWrapper,我们还得知道IDEA能不能调试

  1. 在IDEA中打开org.gradle.wrapper.GradleWrapperMain这个类,添加一个断点

  2. DEBUG方式启动上面配置好后的debug-gradlew

最后断点成功触发

因为上述我们配置了代理,Gradle下载插件的话应该会用到Http(s)URLConnection

那么我们可以在sun.net.spi.DefaultProxySelector#select方法中下断点

断点触发后发现得到的代理信息和上面配置的不一样.

上面我们的代理端口号58707,而实际得到的端口号为1080,在IDEA中通过全文搜索也未发现关键字1080,同时System#Properties内存中的值也确实如此

那么会不会是系统属性后面被覆盖了呢,带着这个疑问,在System#setProperties增加条件断点

和上文一样,启动调试,该断点被触发了两次.顺着调用栈找到https.proxyHost属性被覆盖的原因

定位到org.gradle.initialization.DefaultGradlePropertiesLoader#loadProperties方法

void loadProperties(File settingsDir, StartParameter startParameter, Map<String, String> systemProperties, Map<String, String> envProperties) {
// 这里的settingDir值为当前项目的路径
this.defaultProperties.clear();
this.overrideProperties.clear();
//把项目根目录下的gradle.properties中的配置添加到Gradle属性中
this.addGradleProperties(this.defaultProperties, new File(settingsDir, "gradle.properties"));
//把用户目录下的gradle.properties中的配置添加到Gradle属性中
this.addGradleProperties(this.overrideProperties, new File(startParameter.getGradleUserHomeDir(), "gradle.properties"));
this.setSystemProperties(startParameter.getSystemPropertiesArgs());
this.overrideProperties.putAll(this.getEnvProjectProperties(envProperties));
this.overrideProperties.putAll(this.getSystemProjectProperties(systemProperties));
this.overrideProperties.putAll(startParameter.getProjectProperties());
}

在来到org.gradle.initialization.DefaultGradlePropertiesLoader#setSystemProperties这个方法


private void setSystemProperties(Map<String, String> properties) {
//把项目目录下获取的配置添加到系统属性
this.addSystemPropertiesFromGradleProperties(this.defaultProperties);
//把用户目录下获取到的配置添加到系统属性
this.addSystemPropertiesFromGradleProperties(this.overrideProperties);
System.getProperties().putAll(properties);
}

所以我们之前配置代理信息被用户目录下gradle.properties中的配置给覆盖了。

找到用户目录下的gradle.properties,打开后如下:


## For more details on how to configure your build environment visit
# http://www.gradle.org/docs/current/userguide/build_environment.html
#
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
# Default value: -Xmx1024m -XX:MaxPermSize=256m
# org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
#
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true
#Thu Mar 26 15:36:05 CST 2020
systemProp.http.proxyHost=localhost
systemProp.https.proxyPort=1080
systemProp.https.proxyHost=localhost
systemProp.http.proxyPort=1080

我也忘了啥时候添加的这个配置,真是坑啊

将用户目录下中配置的代理删除掉后,重新构建项目,没有报错.

总结

  1. 上述gradle下载插件的异常就是代理问题导致的,但是提示也太坑爹了,没有提示网络相关的东西

  2. gradle中配置文件中系统属性的优先级 gradle程序命令行参数 > USER_HOME/gradle.properties > PROJECT_DIR/gradle.properties > SystemProperties