-
概述
点击Android Studio的运行按钮,你会在Run面板的第一行看到如下信息:
18:56:07: Executing 'assembleRelease'... ...
assembleRelease是一个当前Task的名字:
可见,Run按钮背后的动作也会执行gradlew命令,和我们手动使用gradlew构建是一样的:
./gradlew assembleRelease
gradle就是通过gradlew这个程序完成构建的,我们就从这个命令入手看看gradle是如何工作的。
-
gradlew脚本
在每个项目的根目录下都会有一个叫做gradlew的脚本,方便我们通过./gradlew就可以调用,脚本内容简化如下:
#!/usr/bin/env sh # 运行脚本时键入的名字,这里是"./gradlew" PRG="$0" # -h判断文件是否是软连接,如果是软连接,则找到源文件 while [ -h "$PRG" ] ; do #通过ls得到该文件的信息 ls=`ls -ld "$PRG"` #expr expr1 : expr2的意思是盘端expr1是否是expr2的格式,软连接的标志就是ls信息中的最后为"link_name->original_file",因为正则表达式内含有(),因此如果匹配成功,link的值是()内匹配到的子字符串 link=`expr "$ls" : '.*-> \(.*\)$'` # 如果匹配到了,则PRG就是指向源文件(就不是软连接了,就可以跳出循环) if expr "$link" : '/.*' > /dev/null; then PRG="$link" else PRG=`dirname "$PRG"`"/$link" fi done # 保存当前目录,即脚本gradlew的父目录,也就是App项目目录 SAVED="`pwd`" # 真正的gradlew脚本源文件目录,如果当前脚本不是软link的话则和SAVED是一样的 cd "`dirname \"$PRG\"`/" >/dev/null # APP_HOME是gradlew脚本源文件所在的目录,即App项目目录 APP_HOME="`pwd -P`" # 回到调用脚本的目录 cd "$SAVED" >/dev/null APP_NAME="Gradle" # 当前调用的脚本名字,如果是软link的话可能不叫“gradlew”,否则就是“gradlew” APP_BASE_NAME=`basename "$0"` # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # 打印信息并结束脚本进程 die () { echo echo "$*" echo exit 1 } ... # 指定gradle-wrapper.jar CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar # Determine the Java command to use to start the JVM. # 如果JAVA_HOME定义了的话,这个变量来自系统环境变量(-n表示验证字符串是否为空) if [ -n "$JAVA_HOME" ] ; then # 获取java脚本的路径(-x表示是否可执行) if [ -x "$JAVA_HOME/jre/sh/java" ] ; then # IBM's JDK on AIX uses strange locations for the executables JAVACMD="$JAVA_HOME/jre/sh/java" else JAVACMD="$JAVA_HOME/bin/java" fi ...#容错处理 else # 如果没配置JAVA_HOME环境变量的话,可能是因为java路径被设置到了其他名字的环境变量中,这里直接尝试读取 JAVACMD="java" # 如果没找到java程序文件则结束进程(>/dev/null 2>&1表示不输出任何信息,在这里和which -s作用一样 which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH...." fi # cygin等都是虚拟机名字,如果不是虚拟机则进入下面逻辑 if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then # ulimit -H -n表示查询当前进程硬资源的文件描述符的最大打开数量 MAX_FD_LIMIT=`ulimit -H -n` if [ $? -eq 0 ] ; then if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then MAX_FD="$MAX_FD_LIMIT" fi # 默认设置成查询到的系统默认配置 ulimit -n $MAX_FD if [ $? -ne 0 ] ; then warn "Could not set maximum file descriptor limit: $MAX_FD" fi else warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" fi fi ... # For Cygwin or MSYS, switch paths to Windows format before running java ...#虚拟机的话需要转换格式 # 该方法把gradlew后面跟的参数转成如:'assembleRelease' \的格式 save () { for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done echo " " } APP_ARGS=`save "$@"` # 设置positional parameter为DEFAULT_JVM_OPTS、JAVA_OPTS等参数,最后再加上命令行参数APP_ARGS eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" # 执行java命令 #如: #java -Xmx64m -Xms64m SOME_OPTS -Dorg.gradle.appname=gradlew -classpath $APP_HOME/gradle/wrapper/gradle-wrapper.jar org.gradle.wrapper.GradleWrapperMain 'assembleRelease' \ exec "$JAVACMD" "$@"
可见,gradlew最后其实是执行了java命令,很明显,要执行的是org.gradle.wrapper.GradleWrapperMain:
public static void main(String[] args) throws Exception { //App项目目录下/gradle/wrapper/gradle-wrapper.jar File wrapperJar = wrapperJar(); //得到gradle-wrapper.properties File propertiesFile = wrapperProperties(wrapperJar); //App项目目录 File rootDir = rootDir(wrapperJar); CommandLineParser parser = new CommandLineParser(); parser.allowUnknownOptions(); parser.option(new String[]{"g", "gradle-user-home"}).hasArgument(); parser.option(new String[]{"q", "quiet"}); SystemPropertiesCommandLineConverter converter = new SystemPropertiesCommandLineConverter(); converter.configure(parser); //解析参数(这是第一次解析,目的是为了设置系统级变量) ParsedCommandLine options = parser.parse(args); Properties systemProperties = System.getProperties(); //将解析出的gradlew脚本中配置的系统变量设置到系统的Properties中 systemProperties.putAll(converter.convert(options, new HashMap())); //获取安装时在user.home下生成的.gradle目录,是一个隐藏目录,比如我的是/User/mph/.gradle File gradleUserHome = gradleUserHome(options); //将.gradle目录下的gradle.properties和App项目目录下的gradle.properties的属性都设置到系统的Properties中 addSystemProperties(systemProperties, gradleUserHome, rootDir); Logger logger = logger(options); //WrapperExecutor构造中会把gradle/wrapper/gradle-wrapper.properties的值读取保存 WrapperExecutor wrapperExecutor = WrapperExecutor.forWrapperPropertiesFile(propertiesFile); wrapperExecutor.execute(args, new Install(logger, new Download(logger, "gradlew", "0"), new PathAssembler(gradleUserHome, rootDir)), new BootstrapMainStarter()); }
可知,{user_home}/.gradle/gradle.properties是总的配置,对所有项目都有效;{App项目目录}/gradle.properties是只用于当前项目的配置。
继续看execute方法:
public void execute(String[] args, Install install, BootstrapMainStarter bootstrapMainStarter) throws Exception { //gradleHome是下载完后的当前使用版本的gradle跟目录,比如如果我用的是gradle-7.4-all,那最终路径是:/User/mph/.gradle/wrapper/dists/gradle-7.4-all/aadb4xli5jkdsnukm30eibyiu(生成的hash码)/gradle-7.4, File gradleHome = install.createDist(this.config); bootstrapMainStarter.start(args, gradleHome); }
其实aadb4xli5jkdsnukm30eibyiu文件夹下有多个文件,最终会取第一个,也就是gradle-7.4文件夹:
Mac-mini:aadb4xli5jkdsnukm30eibyiu hardy$ ls gradle-7.4 gradle-7.4-all.zip.lck gradle-7.4-all.zip gradle-7.4-all.zip.ok
createDist方法中做了什么呢?其实gradle/wrapper/gradle-wrapper.properties中的信息是这样的:
distributionBase=GRADLE_USER_HOME distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-all.zip distributionPath=wrapper/dists zipStorePath=wrapper/dists zipStoreBase=GRADLE_USER_HOME
createDist方法就是会按照这个属性文件中配置的地址去下载所有的当前版本的gradle需要的文件保存到其指定的路径中去,然后解压。
随后调用BootstrapMainStarter.start方法:
public void start(String[] args, File gradleHome) throws Exception { //正则表达式tip:.是特殊符号,表示任意字符,因此\.表示.符号本身 //查找在{gradleHome}/lib下的所有名字为“gradle-launcher-.*\.jar”的文件,取第一个,这里是gradle-launcher-7.4.jar File gradleJar = findLauncherJar(gradleHome); if (gradleJar == null) { throw... } else { //使用gradle-launcher-x.x.jar为源创建ClassLoader URLClassLoader contextClassLoader = new URLClassLoader(new URL[]{gradleJar.toURI().toURL()}, ClassLoader.getSystemClassLoader().getParent()); Thread.currentThread().setContextClassLoader(contextClassLoader); Class<?> mainClass = contextClassLoader.loadClass("org.gradle.launcher.GradleMain"); Method mainMethod = mainClass.getMethod("main", String[].class); mainMethod.invoke((Object)null, args); if (contextClassLoader instanceof Closeable) { contextClassLoader.close(); } } }
可见,最后使用反射调用org.gradle.launcher.GradleMain的main方法:
public static void main(String[] args) throws Exception { String javaVersion = System.getProperty("java.specification.version"); //我这里是7.4版本,需要java1.8及以上 if (javaVersion.equals("1.6") || javaVersion.equals("1.7")) { String gradleVersion = GradleVersionNumberLoader.loadGradleVersionNumber(); System.err.printf("%s %s requires Java 1.8 or later to run. You are currently using Java %s.%n", "Gradle", gradleVersion, javaVersion); System.exit(1); } Class<?> mainClass = Class.forName("org.gradle.launcher.bootstrap.ProcessBootstrap"); Method mainMethod = mainClass.getMethod("run", String.class, String[].class); mainMethod.invoke((Object)null, "org.gradle.launcher.Main", args); }
调用ProcessBootstrap的run方法:
public static void run(String mainClassName, String[] args) { try { runNoExit(mainClassName, args); System.exit(0); } catch (Throwable var3) { var3.printStackTrace(); System.exit(1); } }
private static void runNoExit(String mainClassName, String[] args) throws Exception { ClassPathRegistry classPathRegistry = new DefaultClassPathRegistry(new ClassPathProvider[]{new DefaultClassPathProvider(new DefaultModuleRegistry(CurrentGradleInstallation.get()))}); ClassLoaderFactory classLoaderFactory = new DefaultClassLoaderFactory(); ClassPath antClasspath = classPathRegistry.getClassPath("ANT"); ClassPath runtimeClasspath = classPathRegistry.getClassPath("GRADLE_RUNTIME"); ClassLoader antClassLoader = classLoaderFactory.createIsolatedClassLoader("ant-loader", antClasspath); ClassLoader runtimeClassLoader = new VisitableURLClassLoader("ant-and-gradle-loader", antClassLoader, runtimeClasspath); ClassLoader oldClassLoader = Thread.currentThread().getContextClassLoader(); Thread.currentThread().setContextClassLoader(runtimeClassLoader); try { //传过来的mainClassName是org.gradle.launcher.Main, Class<?> mainClass = runtimeClassLoader.loadClass(mainClassName); Object entryPoint = mainClass.getConstructor().newInstance(); Method mainMethod = mainClass.getMethod("run", String[].class); mainMethod.invoke(entryPoint, args); } finally { Thread.currentThread().setContextClassLoader(oldClassLoader); ClassLoaderUtils.tryClose(runtimeClassLoader); ClassLoaderUtils.tryClose(antClassLoader); } }
Main的run方法来自其父类EntryPoint,内部调用了Main的实现方法doAction:
protected void doAction(String[] args, ExecutionListener listener) { this.createActionFactory().convert(Arrays.asList(args)).execute(listener); } CommandLineActionFactory createActionFactory() { return new DefaultCommandLineActionFactory(); }
convert返回一个DefaultCommandLineActionFactory.WithLogging:
public CommandLineExecution convert(List<String> args) { ServiceRegistry loggingServices = this.createLoggingServices(); LoggingConfiguration loggingConfiguration = new DefaultLoggingConfiguration(); return new DefaultCommandLineActionFactory.WithLogging(loggingServices, args, loggingConfiguration, new DefaultCommandLineActionFactory.ParseAndBuildAction(loggingServices, args), new BuildExceptionReporter((StyledTextOutputFactory)loggingServices.get(StyledTextOutputFactory.class), loggingConfiguration, clientMetaData())); }
它的execute方法如下:
public void execute(ExecutionListener executionListener) { BuildOptionBackedConverter<LoggingConfiguration> loggingBuildOptions = new BuildOptionBackedConverter(new LoggingConfigurationBuildOptions()); InitialPropertiesConverter propertiesConverter = new InitialPropertiesConverter(); BuildLayoutConverter buildLayoutConverter = new BuildLayoutConverter(); LayoutToPropertiesConverter layoutToPropertiesConverter = new LayoutToPropertiesConverter(new BuildLayoutFactory()); BuildLayoutResult buildLayout = buildLayoutConverter.defaultValues(); CommandLineParser parser = new CommandLineParser(); propertiesConverter.configure(parser); buildLayoutConverter.configure(parser); loggingBuildOptions.configure(parser); parser.allowUnknownOptions(); //allowMixedOptions设置为true,此后onNonOption方法中会用到这个值 parser.allowMixedSubcommandsAndOptions(); try { //解析参数(第二次解析,为了获取其他参数属性) //这一次解析加载属性最终都是为了Logger服务的 ParsedCommandLine parsedCommandLine = parser.parse(this.args); InitialProperties initialProperties = propertiesConverter.convert(parsedCommandLine); buildLayout = buildLayoutConverter.convert(initialProperties, parsedCommandLine, (File)null); AllProperties properties = layoutToPropertiesConverter.convert(initialProperties, buildLayout); ... } ... ... try { Action<ExecutionListener> exceptionReportingAction = new ExceptionReportingAction(this.reporter, loggingManager, new NativeServicesInitializingAction(buildLayout, this.loggingConfiguration, loggingManager, new WelcomeMessageAction(buildLayout, new DebugLoggerWarningAction(this.loggingConfiguration, this.action)))); //开启任务Action的入口 exceptionReportingAction.execute(executionListener); } finally { loggingManager.stop(); } }
这里有三个Convertor,loggingBuildOptions就不看了,是关于日志打印的。分成configure和convert两段逻辑。
先看configure阶段做了什么。
首先是InitialPropertiesConverter,它的configure方法最终会调用CommandLineParser的option方法:给其内部的optionsByString添加key为"D"和"system-prop"的两个CommandLineOption(这俩是一样的):
CommandLineOption option = new CommandLineOption(Arrays.asList(options)); //var7是字符串数组["D","system-prop"] Iterator var7 = option.getOptions().iterator(); while(var7.hasNext()) { String optionStr = (String)var7.next(); this.optionsByString.put(optionStr, option); }
第二个是BuildLayoutConverter,它的configure方法最终也是和上面一样会给CommandLineParser的optionsByString添加key为“gradle-user-home”和“g”的两个相同CommandLineOption。
然后调用CommandLineParser的parse方法:
public ParsedCommandLine parse(Iterable<String> commandLine) throws CommandLineArgumentException { //configure阶段的optionsByString拿来创建出ParsedCommandLine的optionsByString(此时内容变成了ParsedCommandLineOption) ParsedCommandLine parsedCommandLine = new ParsedCommandLine(new HashSet(this.optionsByString.values())); CommandLineParser.ParserState parseState = new CommandLineParser.BeforeFirstSubCommand(parsedCommandLine); Iterator var4 = commandLine.iterator(); while(true) { while(true) { while(var4.hasNext()) { String arg = (String)var4.next(); //这里只贴出和gradlew脚本中相关的解析代码 //maybeStartOption最终:arg.matches("(?s)-.+"),即匹配“-Xxx”格式的 if (((CommandLineParser.ParserState)parseState).maybeStartOption(arg)) { if (arg.equals("--")) { ... } else { CommandLineParser.OptionParserState parsedOption; if (arg.matches("--[^=]+")) { ... } else { CommandLineParser.OptionParserState parsedOption; if (arg.matches("(?s)--[^=]+=.*")) { ... } else if (arg.matches("(?s)-[^=]=.*")) { //-Dorg.gradle.appname=$APP_BASE_NAME在此解析,(?s)是单行模式的意思 parsedOption = ((CommandLineParser.ParserState)parseState).onStartOption(arg, arg.substring(1, 2)); //(1,2)取的就是D,(3)取的就是D后面的值,也就是org.gradle.appname=$APP_BASE_NAME parseState = parsedOption.onArgument(arg.substring(3)); } else { //-classpath在此解析 assert arg.matches("(?s)-[^-].*"); ... } } } } else { //其他格式的参数,比如'assembleRelease' \ parseState = ((CommandLineParser.ParserState)parseState).onNonOption(arg); } } //无操作 ((CommandLineParser.ParserState)parseState).onCommandLineEnd(); return parsedCommandLine; } } }
onStartOption中如果配置参数可知则会返回CommandLineParser.KnownOptionParserState对象,它的onArgument会把设置的属性值存到其values集合中,并且调用onComplete方法:
public CommandLineParser.ParserState onComplete() { if (this.getHasArgument() && this.values.isEmpty()) { ... } else { //把之前CommandLineOption的options添加到ParsedCommandLine的presentOptions中 ParsedCommandLineOption parsedOption = this.commandLine.addOption(this.optionString.option, this.option); if (this.values.size() + parsedOption.getValues().size() > 1 && !this.option.getAllowsMultipleArguments()) { ... } else { Iterator var2 = this.values.iterator(); while(var2.hasNext()) { String value = (String)var2.next(); //把解析的属性值添加到ParsedCommandLineOption的values集合中 parsedOption.addArgument(value); } ... return this.state; } } }
其他格式的就是执行gradlew命令后面跟着的命令行参数(positional parameter)的onNonOption方法如下:
public CommandLineParser.ParserState onNonOption(String arg) { this.commandLine.addExtraValue(arg); return (CommandLineParser.ParserState)(CommandLineParser.this.allowMixedOptions ? CommandLineParser.this.new AfterFirstSubCommand(this.commandLine) : new CommandLineParser.AfterOptions(this.commandLine)); }
因为前面设置了allowMixedOptions为true,所以这里返回AfterFirstSubCommand,不过这不重要,主要是addExtraValue方法中会把参数保存到
ParsedCommandLine的extraArguments
。然后开始convert阶段。
首先调用InitialPropertiesConverter的convert方法:
public InitialProperties convert(ParsedCommandLine commandLine) { final Map<String, String> requestedSystemProperties = (Map)this.systemPropertiesCommandLineConverter.convert(commandLine, new HashMap()); return new InitialProperties() { public Map<String, String> getRequestedSystemProperties() { return Collections.unmodifiableMap(requestedSystemProperties); } }; }
它返回一个InitialProperties,它的getRequestedSystemProperties方法的返回值来自于systemPropertiesCommandLineConverter.convert,systemPropertiesCommandLineConverter是SystemPropertiesCommandLineConverter:
public Map<String, String> convert(ParsedCommandLine options, Map<String, String> properties) throws CommandLineArgumentException { //这里通过option方法获取前面configure阶段设置的CommandLineOption Iterator var3 = options.option(this.getPropertyOption()).getValues().iterator(); while(var3.hasNext()) { //例如-D属性,这里会取出org.gradle.appname=$APP_BASE_NAME String keyValueExpression = (String)var3.next(); int pos = keyValueExpression.indexOf("="); if (pos < 0) { properties.put(keyValueExpression, ""); } else { //设置到properties这个Map中 properties.put(keyValueExpression.substring(0, pos), keyValueExpression.substring(pos + 1)); } } return properties; }
这个properties返回赋值给requestedSystemProperties,这样就解析拿到了设置的系统属性值集合。
接下来调用的是BuildLayoutConverter的convert方法:
//会调用这个重载方法,即convert(systemProperties, commandLine, null, (parameters) -> {}); public BuildLayoutResult convert(InitialProperties systemProperties, ParsedCommandLine commandLine, @Nullable File workingDir, Consumer<BuildLayoutParameters> defaults) { BuildLayoutParameters layoutParameters = new BuildLayoutParameters(); ... //这里拿到之前的requestedSystemProperties Map<String, String> requestedSystemProperties = systemProperties.getRequestedSystemProperties(); //即PropertiesConverter的convert(尝试读取user.dir的值设置成gradleUserHomeDir) (new BuildLayoutParametersBuildOptions()).propertiesConverter().convert(requestedSystemProperties, layoutParameters); //尝试读取gradle-user-home的值设置成gradleUserHomeDir,并且尝试读取project-dir设置了ProjectDir this.buildLayoutConverter.convert(commandLine, layoutParameters); return new BuildLayoutConverter.Result(layoutParameters); }
PropertiesConverter的convert方法:
public T convert(Map<String, String> args, T target) throws CommandLineArgumentException { //得到两个option:GradleUserHomeOption和ProjectDirOption Iterator var3 = BuildOptionSet.this.getAllOptions().iterator(); while(var3.hasNext()) { BuildOption<? super T> option = (BuildOption)var3.next(); //ProjectDirOption其实最终没调用applyTo方法(因为其gradleProperty是null) option.applyFromProperty(args, target); } return target; }
applyFromProperty方法会调用applyTo方法。
GradleUserHomeOption的applyTo方法负责读取gradle.user.home属性值赋值给BuildLayoutParameters的gradleUserHomeDir:
public void applyTo(String value, BuildLayoutParameters settings, Origin origin) { Transformer<File, String> resolver = new BasicFileResolver(settings.getCurrentDir()); //transform方法中就是解析gradle.user.home属性值(file:格式或路径格式) settings.setGradleUserHomeDir((File)resolver.transform(value)); }
BuildLayoutParameters的构造方法中:
public BuildLayoutParameters() { //获取gradle安装目录、gradle.user.home目录和SystemProperties所在目录(user.dir设置的值) this(findGradleInstallationHomeDir(), findGradleUserHomeDir(), (File)null, FileUtils.canonicalize(SystemProperties.getInstance().getCurrentDir())); }
紧接着的this.buildLayoutConverter.convert方法其实和上一步的流程差不多,它会通过applyFromCommandLine方法(也就是尝试从命令行参数中获取配置信息)设置gradle.user.home,最终执行的是applyFromCommandLine方法:
public void applyFromCommandLine(ParsedCommandLine options, T settings) { Iterator var3 = this.commandLineOptionConfigurations.iterator(); while(var3.hasNext()) { CommandLineOptionConfiguration config = (CommandLineOptionConfiguration)var3.next(); //这里的config.getLongOption()是“gradle-user-home”、“project-dir” if (options.hasOption(config.getLongOption())) { String value = options.option(config.getLongOption()).getValue(); this.applyTo(value, settings, Origin.forCommandLine(config.getLongOption())); } } }
这里没从ParsedCommandLine中读取到这两项配置(未在命令行参数中配置这俩选项)。上面的这两步也可以说明为什么命令行参数优先级会高于配置文件,因为命令行参数会后读取进行覆盖(如果有的话)。
最后是LayoutToPropertiesConverter的convert方法:
public AllProperties convert(InitialProperties initialProperties, BuildLayoutResult layout) { BuildLayoutParameters layoutParameters = new BuildLayoutParameters(); //从上面的结果中获取currentDir、projectDir、gradleUserHomeDir、gradleInstallationHomeDir设置到layoutParameters中 layout.applyTo(layoutParameters); //把所有的属性都解析到properties这个Map中 Map<String, String> properties = new HashMap(); //加载{user_home}/.gradle/gradle.properties的属性 this.configureFromHomeDir(layoutParameters.getGradleInstallationHomeDir(), properties); //加载{App项目跟目录(settings所在目录)}/gradle.properties的属性 this.configureFromBuildDir(layoutParameters.getSearchDir(), properties); //这里其实就是获取命令行配置的.gradle/gradle.properties文件(如果有的话),为了遵循命令行参数优先 this.configureFromHomeDir(layout.getGradleUserHomeDir(), properties); //获取所有JVM默认配置属性 this.configureFromSystemPropertiesOfThisJvm((Map)Cast.uncheckedNonnullCast(properties)); properties.putAll(initialProperties.getRequestedSystemProperties()); return new LayoutToPropertiesConverter.Result(properties, initialProperties); }
看一下configureFromHomeDir方法:
private void configureFromHomeDir(File gradleUserHomeDir, Map<String, String> result) { //比如:/User/mph/.gradle/gradle.properties this.maybeConfigureFrom(new File(gradleUserHomeDir, "gradle.properties"), result); } private void maybeConfigureFrom(File propertiesFile, Map<String, String> result) { ... Properties properties = new Properties(); ... FileInputStream inputStream = new FileInputStream(propertiesFile); ... //会检索每一行的信息,按照=或:划分成key和value properties.load(inputStream); ... Iterator var10 = properties.keySet().iterator(); while(var10.hasNext()) { final Object key = var10.next(); ... result.put(key.toString(), properties.get(key).toString()); ... } }
接下来是configureFromBuildDir方法:
private void configureFromBuildDir(File currentDir, Map<String, String> result) { BuildLayout layout = this.buildLayoutFactory.getLayoutFor(currentDir, true); this.maybeConfigureFrom(new File(layout.getRootDirectory(), "gradle.properties"), result); }
首先layoutParameters.getSearchDir()方法如下:
public File getSearchDir() { return this.projectDir != null ? this.projectDir : this.currentDir; }
这里的projectDir是null(看构造),因此返回的是currentDir,同样在构造时初始化,值为系统属性“user.dir”的value,这个值是JVM默认属性之一,在JVM启动的时候就自动初始化了,代表着用户当前目录(gradlew的所在目录),即App项目根目录。
configureFromBuildDir方法中会调用getLayoutFor方法:
BuildLayout getLayoutFor(File currentDir, File stopAt) { //找到项目根目录下的settings文件 File settingsFile = this.findExistingSettingsFileIn(currentDir); if (settingsFile != null) { return this.layout(currentDir, settingsFile); } else { //如果buildSrc目录的情况,则一直往上查询到拥有settings文件的目录 for(File candidate = currentDir.getParentFile(); candidate != null && !candidate.equals(stopAt); candidate = candidate.getParentFile()) { settingsFile = this.findExistingSettingsFileIn(candidate); if (settingsFile != null) { return this.layout(candidate, settingsFile); } } return this.layout(currentDir, new File(currentDir, "settings.gradle")); } } /** * 可见,找到了项目根目录下的settings文件(可能是settings.gradle,也可能是settings.kt,所以这里没限制后缀) **/ @Nullable public File findExistingSettingsFileIn(File directory) { return this.scriptFileResolver.resolveScriptFile(directory, "settings"); } private BuildLayout layout(File rootDir, File settingsFile) { return new BuildLayout(rootDir, settingsFile.getParentFile(), FileUtils.canonicalize(settingsFile), this.scriptFileResolver); }
后面也是调用maybeConfigureFrom方法load文件获取属性。
DefaultCommandLineActionFactory的execute方法最后的ExceptionReportingAction.execute开始会开启一个调用链,依次调用:
- WelcomeMessageAction.execute方法
- DebugLoggerWarningAction.execute方法
- DefaultCommandLineActionFactory.ParseAndBuildAction.execute方法
DefaultCommandLineActionFactory.ParseAndBuildAction.execute方法如下:
public void execute(ExecutionListener executionListener) { List<CommandLineAction> actions = new ArrayList(); // actions.add(new DefaultCommandLineActionFactory.BuiltInActions()); //添加BuildActionsFactory DefaultCommandLineActionFactory.this.createActionFactories(this.loggingServices, actions); CommandLineParser parser = new CommandLineParser(); Iterator var4 = actions.iterator(); while(var4.hasNext()) { CommandLineAction actionx = (CommandLineAction)var4.next(); //BuiltInActions的configureCommandLineParser中设置了“help”、“h”、“?”和“v”、“version”这些基础option //BuildActionsFactory的configureCommandLineParser中又会执行一遍上面的解析加载属性过程(为什么又再次重复?我唯一能想到的就是解偶,之前的此操作是为了Logger服务的,这里的则是为了执行Action) actionx.configureCommandLineParser(parser); } Object action; try { //第三次解析了 ParsedCommandLine commandLine = parser.parse(this.args); //创建任务action action = this.createAction(actions, parser, commandLine); } catch (CommandLineArgumentException var6) { action = new DefaultCommandLineActionFactory.CommandLineParseFailureAction(parser, var6); } //执行action,比如:“assembleRelease” ((Action)action).execute(executionListener); }
createAction方法中会调用BuildActionsFactory的createAction方法:
public Runnable createAction(CommandLineParser parser, ParsedCommandLine commandLine) { Parameters parameters = this.parametersConverter.convert(commandLine, (File)null); ... } else if (parameters.getDaemonParameters().isEnabled()) { return this.runBuildWithDaemon(parameters.getStartParameter(), parameters.getDaemonParameters()); } else { return this.canUseCurrentProcess(parameters.getDaemonParameters()) ? this.runBuildInProcess(parameters.getStartParameter(), parameters.getDaemonParameters()) : this.runBuildInSingleUseDaemon(parameters.getStartParameter(), parameters.getDaemonParameters()); } }
parametersConverter是ParametersConverter,它的convert方法如下:
public Parameters convert(ParsedCommandLine args, @Nullable File currentDir) throws CommandLineArgumentException { //前三行和前面分析的一样 InitialProperties initialProperties = this.initialPropertiesConverter.convert(args); BuildLayoutResult buildLayout = this.buildLayoutConverter.convert(initialProperties, args, currentDir); AllProperties properties = this.layoutToPropertiesConverter.convert(initialProperties, buildLayout); StartParameterInternal startParameter = new StartParameterInternal(); //这行很关键 this.startParameterConverter.convert(args, buildLayout, properties, startParameter); DaemonParameters daemonParameters = new DaemonParameters(buildLayout, this.fileCollectionFactory, properties.getRequestedSystemProperties()); this.daemonParametersConverter.convert(args, properties, daemonParameters); return new Parameters(buildLayout, startParameter, daemonParameters); }
StartParameterConverter的convert方法如下:
public StartParameterInternal convert(ParsedCommandLine parsedCommandLine, BuildLayoutResult buildLayout, AllProperties properties, StartParameterInternal startParameter) throws CommandLineArgumentException { buildLayout.applyTo(startParameter); ... if (!parsedCommandLine.getExtraArguments().isEmpty()) { //终于用到了extraArguments,还记得吗,它是gradlew后面跟着的命令行参数,也就是通常使用的任务名,比如assembleRelease,它会赋值给StartParameter的taskRequests startParameter.setTaskNames(parsedCommandLine.getExtraArguments()); } //gradle的build参数option都在这里 this.buildOptionsConverter.convert(parsedCommandLine, properties, startParameter); return startParameter; }
最终返回一个Parameters:
public Parameters(BuildLayoutResult layout, StartParameterInternal startParameter, DaemonParameters daemonParameters) { this.layout = layout; this.startParameter = startParameter; this.daemonParameters = daemonParameters; }
BuildActionsFactory的createAction方法中构造完Parameters之后会根据状态执行不同的方法,比如是否在后台执行,最终都会执行到runBuildAndCloseServices方法,该方法最终会返回一个RunBuildAction,它的run方法如下:
public void run() { try { BuildActionResult result = this.executer.execute(new ExecuteBuildAction(this.startParameter), this.buildActionParameters, new DefaultBuildRequestContext(new DefaultBuildRequestMetaData(this.clientMetaData, this.startTime, ((ConsoleDetector)this.sharedServices.get(ConsoleDetector.class)).isConsoleInput()), new DefaultBuildCancellationToken(), new NoOpBuildEventConsumer())); if (result.hasFailure()) { throw new ReportedException(); } } finally { if (this.stoppable != null) { this.stoppable.stop(); } } }
this.executer是什么?牵扯到一个巧妙的工厂模式,我们再开一篇分析。
-
小结
从执行gradlew脚本开始,会以GradleWrapperMain的main作为入口,首先解析gradlew的参数添加到系统环境中,然后会根据gradlew所在目录位置来找到gradle-wrapper.properties配置的地址去下载对应版本的gradle库(如果没下载过)。
然后通过org.gradle.launcher.GradleMain的main方法判断gradle版本号和java版本是否适配,如果适配则会继续执行org.gradle.launcher.bootstrap.ProcessBootstrap的run方法,这里会设置很多ClassLoader,比如ant。
然后会执行org.gradle.launcher.Main的run方法,最终执行DefaultCommandLineActionFactory.WithLogging的execute方法,这个方法里会配置和log相关的东西,这是装饰器模式的最外层,之后会按照装饰器模式的调用链开始顺序调用,最终BuildActionsFactory的createAction会根据需要创建不同模式的执行不同的Action,包括后台和前台的执行,最终都会执行RunBuildAction的run方法,里面会调用一个this.executer的execute方法,这个this.executer来自于一种特别的工厂模式,下文叙。
Gradle源码解读(一)
最后编辑于 :
©著作权归作者所有,转载或内容合作请联系作者
- 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
- 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
- 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
推荐阅读更多精彩内容
- 一、知识详解模块 1.dex/class深入讲解 2.jvm/dvm/art三个虚拟机的深入讲解 3.class ...
- 本文来自于CSDN博客,作者:晏博,已获授权,版权归原作者所有,未经作者同意,请勿转载。 背景 陆陆续续一年多,总...
- 这一章主要针对项目中可以用到的一些实用功能来介绍Android Gradle,比如如何隐藏我们的证书文件,降低风险...
- 本系统开发环境为Visual Studio 2010,使用.net 4.0开发,使用AForge库和Aipsdk库...