Gradle源码解读(一)

  • 概述

    点击Android Studio的运行按钮,你会在Run面板的第一行看到如下信息:

    18:56:07: Executing 'assembleRelease'...
    ...
    

    assembleRelease是一个当前Task的名字:

    截屏2023-10-20 18.58.30.png

    可见,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开始会开启一个调用链,依次调用:

    1. WelcomeMessageAction.execute方法
    2. DebugLoggerWarningAction.execute方法
    3. 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来自于一种特别的工厂模式,下文叙。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,033评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,725评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,473评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,846评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,848评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,691评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,053评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,700评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,856评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,676评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,787评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,430评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,034评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,990评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,218评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,174评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,526评论 2 343

推荐阅读更多精彩内容