Gradle源码分析

前言

因为之前一直做Android开发所有的东西都是IDE给做了,只知道配置一下基本的依赖。静静等待神奇的apk文件生成,这给在学习心得语言比如kontlin或者了解一下相关东西的时候造成巨大的困难,所以下定决心,在重新搞一下Gradle。可是gradle相关资料都是面对特定编译场景的。很难完全通过文档和demo了解所有用法,这里我们还是通过梳理源码流程。梳理一下gradle的脉络,了解支持什么编译控制语法。

正文

这里就不一一介绍gradle的使用流程,反正就是通过特定工程的目录,通过调用一个名字叫gradlew的脚本然后加载了gradle-wrapper.jar最终才开始执行gradle命令。
我们先开始稍微了解一下gradlew脚本
这里我们参考的诗Linux下的脚本如下:

APP_HOME="`pwd -P`"
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar

if [ -n "$JAVA_HOME" ] ; then
    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
    if [ ! -x "$JAVACMD" ] ; then
        die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME

Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
    fi
else
    JAVACMD="java"
    which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.

Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi

exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"

这里部分我省略了,从中我们了解到这里仅仅是调用了$APP_HOME/gradle/wrapper/gradle-wrapper.jar他中的org.gradle.wrapper.GradleWrapperMain,然后把所有参数直接传入到GradleWrapperMain。对于不熟悉脚本语言的童鞋,没必要纠结这里。只要知道进入了我们第一个wrapper中就好了,
第二步wrapper(包装),这东西其实就是根据配置信息,下载特定版本的gradle,然后开始用特定版本给我们干活,他的功能其实就是找到特定版本的gradle,让他们干活。
下面我们分析一下具体内容。

public static void main(String[] args) throws Exception {
        File wrapperJar = wrapperJar();
        File propertiesFile = wrapperProperties(wrapperJar);
        File rootDir = rootDir(wrapperJar);
        CommandLineParser parser = new CommandLineParser();
        parser.allowUnknownOptions();
        parser.option(GRADLE_USER_HOME_OPTION, GRADLE_USER_HOME_DETAILED_OPTION).hasArgument();
        parser.option(GRADLE_QUIET_OPTION, GRADLE_QUIET_DETAILED_OPTION);

        SystemPropertiesCommandLineConverter converter = new SystemPropertiesCommandLineConverter();
        converter.configure(parser);

        ParsedCommandLine options = parser.parse(args);

        Properties systemProperties = System.getProperties();
        systemProperties.putAll(converter.convert(options, new HashMap<String, String>()));

        File gradleUserHome = gradleUserHome(options);

        addSystemProperties(gradleUserHome, rootDir);
        Logger logger = logger(options);

        WrapperExecutor wrapperExecutor = WrapperExecutor.forWrapperPropertiesFile(propertiesFile);
        wrapperExecutor.execute(
                args,
                new Install(logger, new Download(logger, "gradlew", wrapperVersion()), new PathAssembler(gradleUserHome)),
                new BootstrapMainStarter());
    }

高版本的根本不适合阅读,里面的函数流程基本无法追踪,这里只是为了分析下setting.gradle和build.gradle如何被转换成具体的一个个task的,并且各种配置如何生效,这里没必要阅读最新版的强大的各种功能,浪费大量时间,并且可读性极差。这里我们直接读0.1的版本,只用知道setting.gradle build.gradle是如何转换成可执行的代码即可。并且到底plugin是干啥的?那些方法片段有啥用?
关于gradle-0.1我们直接进入main.groovy

Closure buildFactory = Build.newInstanceFactory(gradleUserHomeDir, pluginProperties, gradleImportsFile)
Build build = buildFactory(buildScriptFinder, null)
......            
build.run(tasks, currentDir, startProperties, systemProperties)

//这里构建了一个闭包,用来构建build。
static Closure newInstanceFactory(File gradleUserHomeDir, File pluginProperties, File defaultImportsFile) {
        {BuildScriptFinder buildScriptFinder, File buildResolverDir ->
            DefaultDependencyManagerFactory dependencyManagerFactory = new DefaultDependencyManagerFactory()
            new Build(gradleUserHomeDir, new SettingsProcessor(
                    new SettingsFileHandler(),
                    new ImportsReader(defaultImportsFile),
                    new SettingsFactory(),
                    dependencyManagerFactory,
                    null, gradleUserHomeDir, buildResolverDir),
                    new ProjectsLoader(new ProjectFactory(dependencyManagerFactory),
                            new BuildScriptProcessor(new ImportsReader(defaultImportsFile)),
                            buildScriptFinder, new PluginRegistry(pluginProperties)),
                    new BuildConfigurer(new ProjectDependencies2TasksResolver(), new BuildClasspathLoader(), new ProjectsTraverser(),
                            new ProjectTasksPrettyPrinter()),
                    new BuildExecuter(new Dag()))
        }
    }

这里仅仅是通过一个闭包构建一个新的build对象。我们其实去看build的润函数即可。

    void run(List taskNames, File currentDir, Map projectProperties, Map systemPropertiesArgs) {
        runInternal(init(currentDir, projectProperties, systemPropertiesArgs), taskNames, currentDir, false,
                projectProperties)
    }

    private void runInternal(DefaultSettings settings, List taskNames, File currentDir, boolean recursive,
                             Map projectProperties) {
        ClassLoader classLoader = settings.createClassLoader()
        boolean unknownTaskCheck = false
        taskNames.each {String taskName ->
            projectLoader.load(settings, gradleUserHomeDir, projectProperties, allSystemProperties, allEnvProperties)
            buildConfigurer.process(projectLoader.rootProject, classLoader)
            if (!unknownTaskCheck) {
                List unknownTasks = buildExecuter.unknownTasks(taskNames, recursive, projectLoader.currentProject)
                if (unknownTasks) {throw new UnknownTaskException("Task(s) $unknownTasks are unknown!")}
                unknownTaskCheck = true
            }
            buildExecuter.execute(taskName, recursive, projectLoader.currentProject, projectLoader.rootProject)
        }
    }

taskNames这是传入参数task的列表。也就是对每个任务都会运行一遍,这里我们只管看循环体内的函数即可。projectLoader.load(settings, gradleUserHomeDir, projectProperties, allSystemProperties, allEnvProperties)这里是对project加载的核心。我们来一一深扒这个函数。

    ProjectsLoader load(DefaultSettings settings, File gradleUserHomeDir, Map projectProperties,
                        Map systemProperties, Map envProperties) {
        rootProject = createProjects(settings, gradleUserHomeDir, projectProperties, systemProperties, envProperties)
        currentProject = DefaultProject.findProject(rootProject,
                PathHelper.getCurrentProjectPath(rootProject.rootDir, settings.currentDir))
        this
    }

    private DefaultProject createProjects(DefaultSettings settings, File gradleUserHomeDir, Map projectProperties,Map systemProperties, Map envProperties) {
        ......
        DefaultProject rootProject = projectFactory.createProject(settings.rootDir.name, null, settings.rootDir, null,projectFactory, buildScriptProcessor, buildScriptFinder, pluginRegistry)
        ......
        settings.projectPaths.each {
            List folders = it.split(Project.PATH_SEPARATOR)
            DefaultProject parent = rootProject
            folders.each {name ->
                if (!parent.childProjects[name]) {
                    parent.childProjects[name] = parent.addChildProject(name)
                    addPropertiesToProject(gradleUserHomeDir, userHomeProperties, systemAndEnvProjectProperties, parent.childProjects[name])
                }
                parent = parent.childProjects[name]
            }
        }
        rootProject
    }

这里传入的参数包括以setting其实这里已经把setting.gradle已经解析了。但是这里我们暂时不去追究这个问题,因为setting里面试试通过动态注册了一个include函数。可以让我们在一个项目中管理不同的项目,setting.gradle的形式大概是这样:

include ':app'

其实就是把:app作为参数,传入setting的路径,这里有兴趣的童鞋可以深入了解一下,如果可以读懂project生成,对于一个相对来说更简单的setting类,会更容易理解。我们下面继续开始主线任务。看下到底projects是如何生成的。

    DefaultProject createProject(String name, DefaultProject parent, File rootDir, DefaultProject rootProject, ProjectFactory projectFactory, BuildScriptProcessor buildScriptProcessor, BuildScriptFinder buildScriptFinder, PluginRegistry pluginRegistry) {
        return new DefaultProject(name, parent, rootDir, rootProject, projectFactory, dependencyManagerFactory.createDependencyManager(), buildScriptProcessor, buildScriptFinder, pluginRegistry)
    }

这里终于把所有的projects给构造完了,但是没有根本没有解析我们需要的build.gradle脚本,那我我们回到从前,在build对象润方法中load完后,有个

buildConfigurer.process(projectLoader.rootProject, classLoader)

就是这个,我们现在看看真的开始解析脚本,

    void process(Project rootProject, ClassLoader classLoader) {

        projectsTraverser.traverse([rootProject]) {DefaultProject project ->
            project.evaluate()
        }
        projectDependencies2TasksResolver.resolve(rootProject)
    }

    void traverse(Collection projects, Closure action) {
        projects = new TreeSet(projects)
        if (!projects) return
        projects.each {
            action(it)
        }
        projects.each {traverse(it.childProjects.values(), action)}
    }
````
其实就是把rootproject递归调用```evaluate()```下面开始真的解析 ```build.gradle```




<div class="se-preview-section-delimiter"></div>
DefaultProject evaluate() {
    importsLineCount = buildScriptProcessor.evaluate(this)
}
int evaluate(DefaultProject project, Map bindingVariables = [:]) {
    Binding binding = new Binding(bindingVariables)
    CompilerConfiguration conf = new CompilerConfiguration()
    conf.scriptBaseClass = 'org.gradle.api.internal.project.ProjectScript'
    Map buildScript
    try {
        buildScript = buildScriptWithImports(project)
        GroovyShell groovyShell = new GroovyShell(classLoader, binding, conf)
        Script script = groovyShell.parse(buildScript.text, project.buildScriptFinder.buildFileName) 
        replaceMetaclass(script, project)
        project.projectScript = script
        script.run()
    } catch (Throwable t) {
        throw new GradleScriptException(t, project.buildScriptFinder.buildFileName,
                buildScript.importsLineCount)
    }
    project.additionalProperties.putAll(binding.variables)
    buildScript.importsLineCount
}

private void replaceMetaclass(Script script, DefaultProject project) {
    ExpandoMetaClass projectScriptExpandoMetaclass = new ExpandoMetaClass(script.class, false)
    projectScriptExpandoMetaclass.methodMissing = {String name, args ->
        logger.debug("Project: $project.path Method $name not found in script! Delegating to project.")
        project.invokeMethod(name, args)
    }
    projectScriptExpandoMetaclass.propertyMissing = {String name ->
        if (name == 'out') {
            return System.out
        }
        logger.debug("Project: $project.path Property $name not found in script! Delegating to project.")
        project."$name"
    }
    projectScriptExpandoMetaclass.setProperty = {String name, value ->
        logger.debug("Project: $project.path Property $name set a project property.")
        project."$name" = value
    }
    projectScriptExpandoMetaclass.initialize()
    script.metaClass = projectScriptExpandoMetaclass
}


这里就比较简单,
groovyShell.parsebuild.gradle编译成一个scrip对象。嗯嗯,groovy中任何的scrip最终都被编译成scrip对象。下面最关键的一个函数replaceMetaclass“`

    DefaultProject evaluate() {
        importsLineCount = buildScriptProcessor.evaluate(this)
    }
    int evaluate(DefaultProject project, Map bindingVariables = [:]) {
        Binding binding = new Binding(bindingVariables)
        CompilerConfiguration conf = new CompilerConfiguration()
        conf.scriptBaseClass = 'org.gradle.api.internal.project.ProjectScript'
        Map buildScript
        try {
            buildScript = buildScriptWithImports(project)
            GroovyShell groovyShell = new GroovyShell(classLoader, binding, conf)
            Script script = groovyShell.parse(buildScript.text, project.buildScriptFinder.buildFileName) 
            replaceMetaclass(script, project)
            project.projectScript = script
            script.run()
        } catch (Throwable t) {
            throw new GradleScriptException(t, project.buildScriptFinder.buildFileName,
                    buildScript.importsLineCount)
        }
        project.additionalProperties.putAll(binding.variables)
        buildScript.importsLineCount
    }

    private void replaceMetaclass(Script script, DefaultProject project) {
        ExpandoMetaClass projectScriptExpandoMetaclass = new ExpandoMetaClass(script.class, false)
        projectScriptExpandoMetaclass.methodMissing = {String name, args ->
            logger.debug("Project: $project.path Method $name not found in script! Delegating to project.")
            project.invokeMethod(name, args)
        }
        projectScriptExpandoMetaclass.propertyMissing = {String name ->
            if (name == 'out') {
                return System.out
            }
            logger.debug("Project: $project.path Property $name not found in script! Delegating to project.")
            project."$name"
        }
        projectScriptExpandoMetaclass.setProperty = {String name, value ->
            logger.debug("Project: $project.path Property $name set a project property.")
            project."$name" = value
        }
        projectScriptExpandoMetaclass.initialize()
        script.metaClass = projectScriptExpandoMetaclass
    }

这里就比较简单,groovyShell.parsebuild.gradle编译成一个scrip对象。嗯嗯,groovy中任何的scrip最终都被编译成scrip对象。下面最关键的一个函数replaceMetaclass,这里通过动态注册,吧build中的函数都映射到project对象中。
这里用到的是groovy的动态映射机制,这里就不再详细介绍,反正就是假如用的是函数,就通过project.invokeMethod(name, args)调用projects中的函数,假如是定义的参数,project."$name" = value来实现。
其实我们就完全可以想象成build.gradle是project的子类。可以调用父类的所有工作,并且可以添加属性。
因为这是灰常简单的代码,现阶段只能支持比较少的特性。我们找几个简单的。

    DependencyManager dependencies(Closure configureClosure) {
        dependencies.configure(configureClosure)
    }

    def configure(Closure closure) {
        closure.resolveStrategy = Closure.DELEGATE_FIRST
        closure.delegate = this
        closure()
        this
    }

其实也就是行了dependencies参数内的闭包的东东。
因为当前的gradle可以下载jar包,并且这里部分问题最好是着手最新版本,也没必要在之类纠结了。这里我们知道一点,所有的函数调用,大部分都是执行了函数体的闭包的内容,至于plugin是如何实现。我们在看一下,因为这提供了大量的可调用函数。

    Project usePlugin(String pluginName) {
        usePluginInternal(pluginName)
    }
    Project usePluginInternal(def pluginId) {
        Plugin plugin = pluginRegistry.getPlugin(pluginId)
        if (!plugin) {throw new InvalidUserDataException("Plugin with id $pluginId can not be found!")}
        plugin.apply(this, pluginRegistry)
        plugins << plugin
        this
    }


PluginRegistry(File pluginProperties) {
        if (!pluginProperties.isFile()) { return }
        logger.debug("Checking file=$pluginProperties")
        properties = new Properties()
        properties.load(new FileInputStream(pluginProperties))
    }

    Plugin getPlugin(String pluginId) {
        if (!properties[pluginId]) { return null }
        getPlugin(properties[pluginId] as Class)
    }

    Plugin getPlugin(String pluginId) {
        if (!properties[pluginId]) { return null }
        getPlugin(properties[pluginId] as Class)
    }

这里基本没事干。也就是加载了以一个文件,这是:

java=org.gradle.api.plugins.JavaPlugin
groovy=org.gradle.api.plugins.GroovyPlugin

也就是加载了那个类,进入内存,返回。下面就我们找个简单的例子java这个pluglin首先是:

   void apply(Project project, PluginRegistry pluginRegistry, def convention = null) {
        def javaConvention = convention ?: new JavaConvention(project)
        project.convention = javaConvention

        configureDependencyManager(project)

        project.status = 'integration'

        project.createTask(INIT)

        project.createTask(CLEAN, type: Clean).convention(javaConvention, DefaultConventionsToPropertiesMapping.CLEAN)

        project.createTask(JAVADOC, type: Javadoc).convention(javaConvention, DefaultConventionsToPropertiesMapping.JAVADOC)

        project.createTask(RESOURCES, type: Resources, dependsOn: INIT).convention(javaConvention, DefaultConventionsToPropertiesMapping.RESOURCES)

        configureCompile(project.createTask(COMPILE, dependsOn: RESOURCES, type: Compile), javaConvention,
                DefaultConventionsToPropertiesMapping.COMPILE)

        project.createTask(TEST_RESOURCES, dependsOn: COMPILE, type: Resources).configure {
            skipProperties << "$Task.AUTOSKIP_PROPERTY_PREFIX$TEST"
            // Warning: We need to add the delegate here, because otherwise the method argument with the name
            // convention is addressed.
            delegate.convention(javaConvention, DefaultConventionsToPropertiesMapping.TEST_RESOURCES)
        }

        configureTestCompile(project.createTask(TEST_COMPILE, dependsOn: TEST_RESOURCES, type: Compile),
                project.task(COMPILE),
                javaConvention,
                DefaultConventionsToPropertiesMapping.TEST_COMPILE)

        project.createTask(TEST, dependsOn: TEST_COMPILE, type: Test).configure {
            delegate.convention(javaConvention, DefaultConventionsToPropertiesMapping.TEST)
            doFirst {Test test ->
                test.unmanagedClasspath(test.project.task(TEST_COMPILE).unmanagedClasspath as Object[])
            }
        }

        Closure lateInitClosureForPackage = {
            def type = 'jar'
            if (project.hasProperty('type') && project.type) {
                type = project.type
            }
            createArchive(javaConvention.archiveTypes[type])
        }
        project.createTask(LIBS, type: Bundle, lateInitializer: [lateInitClosureForPackage], dependsOn: TEST).configure {
            // Warning: We need to add the delegate here, because otherwise the method argument with the name
            // convention is addressed.
            delegate.convention(javaConvention, DefaultConventionsToPropertiesMapping.LIB)
        }

        project.createTask(UPLOAD_LIBS, type: Upload, dependsOn: LIBS).configure {
            bundles << project.task(LIBS)
            uploadResolvers.add(project.dependencies.buildResolver)
            uploadModuleDescriptor = true
        }

        project.createTask(DISTS, type: Bundle, dependsOn: UPLOAD_LIBS).configure {
            // Warning: We need to add the delegate here, because otherwise the method argument with the name
            // convention is addressed.
            delegate.convention(javaConvention, DefaultConventionsToPropertiesMapping.DIST)
        }

        project.createTask(UPLOAD_DISTS, type: Upload, dependsOn: DISTS).configure {
            configurations << DISTS
        }
    }

这我们看一个简单的task clean

    final static Map CLEAN = [
            dir: {_(it).project.buildDir}
    ]

emmm这里我也搞不懂了,貌似是直接调用了Clean的convention方法,具体如何调用,我就不懂了,这高级语言实在是懒得搞了,下次有机会完全分析一下Android的插件,其实这里我们大概知道了,插件本质上是提供了几个默认的task和可以向系统设置几个全局的变量。

后记

这里写这篇博客比较凌乱,其实是自己在读代码过程中遇到好多坑,这里仅仅是用来记录一下思路。如果有机会或者想做的话,我会重新阅读一下1.0的版本,毕竟功能相对更全面,不过这里最关键的是阅读一下plugin是如何生成,这部分代码对于整个系统的构建,还是比较重要的。end story

  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值