温馨提示×

温馨提示×

您好,登录后才能下订单哦!

密码登录×
登录注册×
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》

11. Gradle编译其他应用代码流程(一)

发布时间:2020-08-26 17:35:46 来源:网络 阅读:668 作者:rongwei84n 栏目:开发技术

由于我们可以把Gradle源代码里面的launcher包丢到gradle libs下面去编译应用程序,所以我们可以用Gradle源代码里面的launcher源代码进行分析。


至于Gradle源代码和gradle bin的关系,可以参考上一篇帖子:

Gradle自身源代码编译


这个很重要,弄清楚鸡和蛋的问题,下面正式开始。


一. GradleMain

文件路径:

subprojects\launcher\src\main\java\org\gradle\launcher\GradleMain.java

public static void main(String[] args) throws Exception {
        new ProcessBootstrap().run("org.gradle.launcher.Main", args);
    }
    
    
public class ProcessBootstrap {
    /**
     * Sets up the ClassLoader structure for the given class, creates an instance and invokes {@link EntryPoint#run(String[])} on it.
     */
    public void run(String mainClassName, String[] args) {
        try {
            runNoExit(mainClassName, args);
            System.exit(0);
        } catch (Throwable throwable) {
            throwable.printStackTrace();
            System.exit(1);
        }
    }

    private void runNoExit(String mainClassName, String[] args) throws Exception {
        ClassPathRegistry classPathRegistry = new DefaultClassPathRegistry(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(antClasspath);
        ClassLoader runtimeClassLoader = new VisitableURLClassLoader(antClassLoader, runtimeClasspath);

        ClassLoader oldClassLoader = Thread.currentThread().getContextClassLoader();
        Thread.currentThread().setContextClassLoader(runtimeClassLoader);

        try {
            Class<?> mainClass = runtimeClassLoader.loadClass(mainClassName);
            Object entryPoint = mainClass.newInstance();
            Method mainMethod = mainClass.getMethod("run", String[].class);
            mainMethod.invoke(entryPoint, new Object[]{args});
        } finally {
            Thread.currentThread().setContextClassLoader(oldClassLoader);

            ClassLoaderUtils.tryClose(runtimeClassLoader);
            ClassLoaderUtils.tryClose(antClassLoader);
        }
    }
}


这行代码其实就是启动org.gradle.launcher.main.run方法



二. org.gradle.launcher.main

其实走的是doAction方法,因为run方法是它的父类EntryPoint里面:

public abstract class EntryPoint {

    /**
     * Unless the createCompleter() method is overridden, the JVM will exit before returning from this method.
     */
    public void run(String[] args) {
        RecordingExecutionListener listener = new RecordingExecutionListener();
        try {
            doAction(args, listener);
        } catch (Throwable e) {
            createErrorHandler().execute(e);
            listener.onFailure(e);
        }

        Throwable failure = listener.getFailure();
        ExecutionCompleter completer = createCompleter();
        if (failure == null) {
            completer.complete();
        } else {
            completer.completeWithFailure(failure);
        }
    }



所以看doAction方法:

public class Main extends EntryPoint {
    public static void main(String[] args) {
        new Main().run(args);
    }

    protected void doAction(String[] args, ExecutionListener listener) {
        createActionFactory().convert(Arrays.asList(args)).execute(listener);
    }

    CommandLineActionFactory createActionFactory() {
        return new CommandLineActionFactory();
    }
}


//createActionFactory.java
public Action<ExecutionListener> convert(List<String> args) {
        ServiceRegistry loggingServices = createLoggingServices();

        LoggingConfiguration loggingConfiguration = new DefaultLoggingConfiguration();

        return new WithLogging(loggingServices,
                args,
                loggingConfiguration,
                new ExceptionReportingAction(
                        new JavaRuntimeValidationAction(
                                new ParseAndBuildAction(loggingServices, args)),
                        new BuildExceptionReporter(loggingServices.get(StyledTextOutputFactory.class), loggingConfiguration, clientMetaData())));
    }


从convert方法可以看到返回的是WithLogging对象,但是它里面又包了好几层。


其实它们都是实现了Action<ExecutionListener>接口:

private static class WithLogging implements Action<ExecutionListener> {}

public class ExceptionReportingAction implements Action<ExecutionListener> {}

public class JavaRuntimeValidationAction implements Action<ExecutionListener> {}

private class ParseAndBuildAction implements Action<ExecutionListener> {}


这是装饰者模式。


关于装饰者模式大家可以百度下,大概有两点:

  1. 类实现同一个接口

  2. 互相包装


打个比方,有这么一个场景:

咖啡店咖啡卖3块一杯,于是我们可以这样设计:

interface Drink{
  int price();
}

class Coffee implements Drink{
   int price(){return 3;}
}


哪天需要开发了新产品,嗯,加糖的咖啡,需要卖5块,那可以设计如下:

class SugarCoffee implements Drink{
    Drink mDrink;
    SugarCoffee(Drink drink){
        mDrink = drink;
    }
    
    int price(){
        return 2 + mDrink.price();
    }

}

main(){
    SugarCoffee sc = new SugarCoffee(new Coffee());
    int price = sc.price();
}


嗯,大概就是这个意思。。Gradle这里也用了同样的模式。

最后,我们看最关键的ParseAndBuildAction.execute(xxx)方法



三. ParseAndBuildAction.execute(xxx)

protected void createActionFactories(ServiceRegistry loggingServices, Collection<CommandLineAction> actions) {
    actions.add(new GuiActionsFactory());
    actions.add(new BuildActionsFactory(loggingServices, new ParametersConverter(), new CachingJvmVersionDetector(new DefaultJvmVersionDetector(new DefaultExecActionFactory(new IdentityFileResolver())))));
}

public void execute(ExecutionListener executionListener) {
        	System.out.println("ParseAndBuildAction execute");
            List<CommandLineAction> actions = new ArrayList<CommandLineAction>();
            //添加BuiltInActions
            actions.add(new BuiltInActions());
            //添加GuiActionsFactory和BuildActionsFactory
            createActionFactories(loggingServices, actions);

            CommandLineParser parser = new CommandLineParser();
            for (CommandLineAction action : actions) {
                action.configureCommandLineParser(parser);
            }

            Action<? super ExecutionListener> action;
            try {
                ParsedCommandLine commandLine = parser.parse(args);
                action = createAction(actions, parser, commandLine);
                
                Exception exx = new Exception("Sandt ParseAndBuildAction execute..");
                exx.printStackTrace();
                
                System.out.println("action: " + action);
            } catch (CommandLineArgumentException e) {
                action = new CommandLineParseFailureAction(parser, e);
            }

            action.execute(executionListener);
        }
        

private Action<? super ExecutionListener> createAction(Iterable<CommandLineAction> factories, CommandLineParser parser, ParsedCommandLine commandLine) {
      for (CommandLineAction factory : factories) {
           Runnable action = factory.createAction(parser, commandLine);
           if (action != null) {
               return Actions.toAction(action);
           }
      }
      throw new UnsupportedOperationException("No action factory for specified command-line arguments.");
}


  1. execute里面首先会添加3个ActionFactory

    BuildInActions表示gradle -help和gradle -version 命令

    GuiActionsFactory表示gradle -gui命令

    BuildActionsFactory表示其他编译命令,比如gradle assemble,下面我们主要分析这种。


  2. 调用createAction方法,其实就是挨个调用上面3个ActionsFacory的createAction方法。那我们主要看BuildActionsFactory。

       

  

四. BuildActionsFactory.createAction

public Runnable createAction(CommandLineParser parser, ParsedCommandLine commandLine) {
        Parameters parameters = parametersConverter.convert(commandLine, new Parameters());
        parameters.getDaemonParameters().applyDefaultsFor(jvmVersionDetector.getJavaVersion(parameters.getDaemonParameters().getEffectiveJvm()));

        if (parameters.getDaemonParameters().isStop()) {
        	System.out.println("createAction 1");
            return stopAllDaemons(parameters.getDaemonParameters(), loggingServices);
        }
        if (parameters.getDaemonParameters().isStatus()) {
        	System.out.println("createAction 2");
            return showDaemonStatus(parameters.getDaemonParameters(), loggingServices);
        }
        if (parameters.getDaemonParameters().isForeground()) {
        	System.out.println("createAction 3");
            DaemonParameters daemonParameters = parameters.getDaemonParameters();
            ForegroundDaemonConfiguration conf = new ForegroundDaemonConfiguration(
                UUID.randomUUID().toString(), daemonParameters.getBaseDir(), daemonParameters.getIdleTimeout(), daemonParameters.getPeriodicCheckInterval());
            return new ForegroundDaemonAction(loggingServices, conf);
        }
        if (parameters.getDaemonParameters().isEnabled()) {
        	System.out.println("createAction 4");
            return runBuildWithDaemon(parameters.getStartParameter(), parameters.getDaemonParameters(), loggingServices);
            
//        	return runBuildInProcess(parameters.getStartParameter(), parameters.getDaemonParameters(), loggingServices);
        }
        if (canUseCurrentProcess(parameters.getDaemonParameters())) {
        	System.out.println("createAction 5");
            return runBuildInProcess(parameters.getStartParameter(), parameters.getDaemonParameters(), loggingServices);
        }

        System.out.println("createAction 6");
        return runBuildInSingleUseDaemon(parameters.getStartParameter(), parameters.getDaemonParameters(), loggingServices);
    }


编译种类主要分为两种:

  1. 在本进程编译

  2. 在守护进程编译

选择逻辑是:

  1. 如果有守护进程在运行,那么连接守护进程然后在守护进程编译。

  2. 如果可以在当前进程编译,那么就在当前进程编译。

  3. 如果都不满足,那就启动守护进程,然后进行编译。


判断是否可以在当前进程编译的代码如下:

subprojects\launcher\src\main\java\org\gradle\launcher\daemon\configuration\BuildProcess.java


/**
     * Attempts to configure the current process to run with the required build parameters.
     * @return True if the current process could be configured, false otherwise.
     */
    public boolean configureForBuild(DaemonParameters requiredBuildParameters) {
        boolean javaHomeMatch = getJvm().equals(requiredBuildParameters.getEffectiveJvm());

        final JvmOptions jvmOptions = new JvmOptions(new IdentityFileResolver());
        jvmOptions.systemProperties(getJvmOptions().getImmutableSystemProperties());
        List<String> currentImmutables = jvmOptions.getAllImmutableJvmArgs();
        List<String> requiredImmutables = requiredBuildParameters.getEffectiveSingleUseJvmArgs();
        requiredImmutables.removeAll(DaemonParameters.DEFAULT_JVM_ARGS);
        

        boolean noImmutableJvmArgsRequired = requiredImmutables.equals(currentImmutables);
        
        System.out.println("javaHomeMatch: " + javaHomeMatch 
        		+ " noImmutableJvmArgsRequired: " + noImmutableJvmArgsRequired);
        
        System.out.println("getJvm(): " + getJvm()
        	+ " requiredBuildParameters.getEffectiveJvm(): " + requiredBuildParameters.getEffectiveJvm());
        
        for (String re : requiredImmutables) {
			System.out.println("requiredImmutable: " + re);
		}
        for (String re : currentImmutables) {
			System.out.println("currentImmutable: " + re);
		}
        
        if (javaHomeMatch && noImmutableJvmArgsRequired) {
            // Set the system properties and use this process
            Properties properties = new Properties();
            properties.putAll(requiredBuildParameters.getEffectiveSystemProperties());
            System.setProperties(properties);
            return true;
        }
        return false;
    }


打印日志:

javaHomeMatch: true noImmutableJvmArgsRequired: false
getJvm(): 1.8.0_25 (Oracle Corporation 25.25-b02) requiredBuildParameters.getEffectiveJvm(): 1.8.0_25 (Oracle Corporation 25.25-b02)
requiredImmutable: -Xmx1536m
requiredImmutable: -Dfile.encoding=GBK
requiredImmutable: -Duser.country=CN
requiredImmutable: -Duser.language=zh
requiredImmutable: -Duser.variant
currentImmutable: -Dfile.encoding=GBK
currentImmutable: -Duser.country=CN
currentImmutable: -Duser.language=zh
currentImmutable: -Duser.variant


从日志看环境有点不一样,所以我的环境会采用守护进程的方法。 不过为了调试,我可以修改代码强制采用本进程编译的方式。


这两种编译流程不尽相同,下一篇文章会先讲在本进程编译的流程。然后再讲启动守护进程进程编译流程。





向AI问一下细节

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

AI