美文网首页Kotlin
Kotlin 编译流程简介

Kotlin 编译流程简介

作者: kotlon | 来源:发表于2019-05-11 15:05 被阅读0次

    Kotlin 编译流程简介

    这里主要介绍 .kt 文件的处理编译过程。

    如果使用命令行编译 kotlin 文件,例如编译 hello.kt 文件,执行如下命令,参考 kotlin command-line

    kotlinc Hello.kt
    

    在 kotlin 源码中,位于 kotlin/compiler/cli/bin/kotlinc 文件中,这个 shell 文件是 kotlin 编译的入口,然后我们找到编译的入口

    declare -a kotlin_app
    
    //运行入口
    if [ -n "$KOTLIN_RUNNER" ];
    then  
        //该处理操作在 kotlin/compiler/cli/cli-runner/src/org/jetbrains/kotlin/runner/Main.kt
        //主要是创建一系列的 ClassLoader,加载 kotlin stdlib 等库的 Class
        java_args=("${java_args[@]}" "-Dkotlin.home=${KOTLIN_HOME}")
        kotlin_app=("${KOTLIN_HOME}/lib/kotlin-runner.jar" "org.jetbrains.kotlin.runner.Main")
    else
        //这里才是真正的编译入口
        [ -n "$KOTLIN_COMPILER" ] || KOTLIN_COMPILER=org.jetbrains.kotlin.cli.jvm.K2JVMCompiler
        java_args=("${java_args[@]}" "-noverify")
    
        kotlin_app=("${KOTLIN_HOME}/lib/kotlin-preloader.jar" "org.jetbrains.kotlin.preloading.Preloader" "-cp" "${KOTLIN_HOME}/lib/kotlin-compiler.jar" $KOTLIN_COMPILER)
    

    首先会执行 Main.kt 文件的 main() 函数,如下,在 main 函数里面执行了 run() 方法:

    //main 方法    
    @JvmStatic
        fun main(args: Array<String>) {
            try {
                run(args)
            }
            catch (e: RunnerException) {
                System.err.println("error: " + e.message)
                System.exit(1)
            }
        }   
    //run()方法,主要是判断命令,读取传入参数
    private fun run(args: Array<String>) {
            val classpath = arrayListOf<URL>()
            var runner: Runner? = null
            var collectingArguments = false
            val arguments = arrayListOf<String>()
            var noReflect = false    
            var i = 0
        while (i < args.size) {
            val arg = args[i]
            if (collectingArguments) {
                arguments.add(arg)
                i++
                continue
            }
    
            fun next(): String {
                if (++i == args.size) {
                    throw RunnerException("argument expected to $arg")
                }
                return args[i]
            }
    
            if ("-help" == arg || "-h" == arg) {
                printUsageAndExit()
            }
            else if ("-version" == arg) {
                printVersionAndExit()
            }
            else if ("-classpath" == arg || "-cp" == arg) {
                for (path in next().split(File.pathSeparator).filter(String::isNotEmpty)) {
                    classpath.addPath(path)
                }
            }
            else if ("-expression" == arg || "-e" == arg) {
                runner = ExpressionRunner(next())
                collectingArguments = true
            }
            else if ("-no-reflect" == arg) {
                noReflect = true
            }
            else if (arg.startsWith("-")) {
                throw RunnerException("unsupported argument: $arg")
            }
            else if (arg.endsWith(".jar")) {
                runner = JarRunner(arg)
                collectingArguments = true
            }
            else if (arg.endsWith(".kts")) {
                runner = ScriptRunner(arg)
                collectingArguments = true
            }
            else {
                runner = MainClassRunner(arg)
                collectingArguments = true
            }
            i++
        }
    
        if (classpath.isEmpty()) {
            classpath.addPath(".")
        }
    
        classpath.addPath(KOTLIN_HOME.toString() + "/lib/kotlin-stdlib.jar")
    
        if (!noReflect) {
            classpath.addPath(KOTLIN_HOME.toString() + "/lib/kotlin-reflect.jar")
        }
    
        if (runner == null) {
            runner = ReplRunner()
        }
    
        //将stdlib 库, reflect 库以及编译参数传入Runner 中
        runner.run(classpath, arguments)
    }
    

    Runner 的主要工作是

    1. 创建不同类型的 ClassLoader
    2. 将 stdlib 库等 class 加载
    3. 并且调用相应类的 main 方法,并且传入之前的编译参数
    abstract class AbstractRunner : Runner {
        protected abstract val className: String
    
        protected abstract fun createClassLoader(classpath: List<URL>): ClassLoader
    
        override fun run(classpath: List<URL>, arguments: List<String>) {
            val classLoader = createClassLoader(classpath)
    
            val mainClass = try {
                classLoader.loadClass(className)
            }
            catch (e: ClassNotFoundException) {
                throw RunnerException("could not find or load main class $className")
            }
    
            val main = try {
                mainClass.getDeclaredMethod("main", Array<String>::class.java)
            }
            catch (e: NoSuchMethodException) {
                throw RunnerException("'main' method not found in class $className")
            }
    
            if (!Modifier.isStatic(main.modifiers)) {
                throw RunnerException(
                        "'main' method of class $className is not static. " +
                        "Please ensure that 'main' is either a top level Kotlin function, " +
                        "a member function annotated with @JvmStatic, or a static Java method"
                )
            }
    
            try {
                main.invoke(null, arguments.toTypedArray())
            }
            catch (e: IllegalAccessException) {
                throw RunnerException("'main' method of class $className is not public")
            }
            catch (e: InvocationTargetException) {
                throw e.targetException
            }
        }
    }
    
    class MainClassRunner(override val className: String) : AbstractRunner() {
        override fun createClassLoader(classpath: List<URL>): ClassLoader =
                URLClassLoader(classpath.toTypedArray(), null)
    }
    

    接着来到真正的编译入口,在kotlin/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/K2JVMCompiler.kt,但是最早的入口是在 CLITools.kt 的companion object 中,这里会调用 Compile.exec() 方法

    companion object {
        /**
         * Useful main for derived command line tools
         */
        @JvmStatic
        fun doMain(compiler: CLITool<*>, args: Array<String>) {
            // We depend on swing (indirectly through PSI or something), so we want to declare headless mode,
            // to avoid accidentally starting the UI thread
            if (System.getProperty("java.awt.headless") == null) {
                System.setProperty("java.awt.headless", "true")
            }
            if (System.getProperty(PlainTextMessageRenderer.KOTLIN_COLORS_ENABLED_PROPERTY) == null) {
                System.setProperty(PlainTextMessageRenderer.KOTLIN_COLORS_ENABLED_PROPERTY, "true")
            }
            val exitCode = doMainNoExit(compiler, args)
            if (exitCode != ExitCode.OK) {
                System.exit(exitCode.code)
            }
        }
    
        @JvmStatic
        fun doMainNoExit(compiler: CLITool<*>, args: Array<String>): ExitCode = try {
            compiler.exec(System.err, *args)
        } catch (e: CompileEnvironmentException) {
            System.err.println(e.message)
            ExitCode.INTERNAL_ERROR
        }
    }
    

    但是 exec() 方法只是检查编译参数,真是执行是在抽象方法 execImpl() 该方法交由CLITools子类实现

    // Used in kotlin-maven-plugin (KotlinCompileMojoBase)
    protected abstract fun execImpl(messageCollector: MessageCollector, services: Services, arguments: A): ExitCode
    

    接着在CLICompile.java 中,看到了 execImpl()方法的实现,却发现,这里也不是真正的编译入口,只是封装了CompilerConfiguration 之类的控制参数,接着把参数传递给了 子类的doExecute() 方法:

     ExitCode code = doExecute(arguments, configuration, rootDisposable, paths);
    

    最后便是来到了 K2JVMCompile.kt 的 doExecute()方法中:

       //K2JVMCompiler.kt
    override fun doExecute(
            arguments: K2JVMCompilerArguments,
            configuration: CompilerConfiguration,
            rootDisposable: Disposable,
            paths: KotlinPaths?
        ): ExitCode {
            val messageCollector = configuration.getNotNull(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY)configureJdkHome(arguments, configuration, messageCollector).let {
            if (it != OK) return it
        }
    
        if (arguments.disableStandardScript) {
            configuration.put(JVMConfigurationKeys.DISABLE_STANDARD_SCRIPT_DEFINITION, true)
        }
    
        val pluginLoadResult = loadPlugins(arguments, configuration)
        if (pluginLoadResult != ExitCode.OK) return pluginLoadResult
    
        val commonSources = arguments.commonSources?.toSet().orEmpty()
        if (!arguments.script && arguments.buildFile == null) {
            for (arg in arguments.freeArgs) {
                val file = File(arg)
                if (file.extension == JavaFileType.DEFAULT_EXTENSION) {
                    configuration.addJavaSourceRoot(file)
                } else {
                    configuration.addKotlinSourceRoot(arg, isCommon = arg in commonSources)
                    if (file.isDirectory) {
                        configuration.addJavaSourceRoot(file)
                    }
                }
            }
        }
    
        configuration.put(CommonConfigurationKeys.MODULE_NAME, arguments.moduleName ?: JvmAbi.DEFAULT_MODULE_NAME)
    
        if (arguments.buildFile == null) {
            configureContentRoots(paths, arguments, configuration)
    
            if (arguments.freeArgs.isEmpty() && !arguments.version) {
                if (arguments.script) {
                    messageCollector.report(ERROR, "Specify script source path to evaluate")
                    return COMPILATION_ERROR
                }
                ReplFromTerminal.run(rootDisposable, configuration)
                return ExitCode.OK
            }
        }
    
        if (arguments.includeRuntime) {
            configuration.put(JVMConfigurationKeys.INCLUDE_RUNTIME, true)
        }
        val friendPaths = arguments.friendPaths?.toList()
        if (friendPaths != null) {
            configuration.put(JVMConfigurationKeys.FRIEND_PATHS, friendPaths)
        }
    
        if (arguments.jvmTarget != null) {
            val jvmTarget = JvmTarget.fromString(arguments.jvmTarget!!)
            if (jvmTarget != null) {
                configuration.put(JVMConfigurationKeys.JVM_TARGET, jvmTarget)
            } else {
                messageCollector.report(
                    ERROR, "Unknown JVM target version: ${arguments.jvmTarget}\n" +
                            "Supported versions: ${JvmTarget.values().joinToString { it.description }}"
                )
            }
        }
    
        configuration.put(JVMConfigurationKeys.PARAMETERS_METADATA, arguments.javaParameters)
    
        putAdvancedOptions(configuration, arguments)
    
        messageCollector.report(LOGGING, "Configuring the compilation environment")
        try {
            val destination = arguments.destination
    
            if (arguments.buildFile != null) {
                if (destination != null) {
                    messageCollector.report(
                        STRONG_WARNING,
                        "The '-d' option with a directory destination is ignored because '-Xbuild-file' is specified"
                    )
                }
    
                val sanitizedCollector = FilteringMessageCollector(messageCollector, VERBOSE::contains)
                val buildFile = File(arguments.buildFile)
                val moduleChunk = CompileEnvironmentUtil.loadModuleChunk(buildFile, sanitizedCollector)
    
                configuration.put(JVMConfigurationKeys.MODULE_XML_FILE, buildFile)
    
                KotlinToJVMBytecodeCompiler.configureSourceRoots(configuration, moduleChunk.modules, buildFile)
    
                val environment = createCoreEnvironment(rootDisposable, configuration, messageCollector)
                    ?: return COMPILATION_ERROR
    
                registerJavacIfNeeded(environment, arguments).let {
                    if (!it) return COMPILATION_ERROR
                }
    
                KotlinToJVMBytecodeCompiler.compileModules(environment, buildFile, moduleChunk.modules)
            } else if (arguments.script) {
                val sourcePath = arguments.freeArgs.first()
                configuration.addKotlinSourceRoot(sourcePath)
    
                configuration.put(JVMConfigurationKeys.RETAIN_OUTPUT_IN_MEMORY, true)
    
                val environment = createCoreEnvironment(rootDisposable, configuration, messageCollector)
                    ?: return COMPILATION_ERROR
    
                val scriptDefinitionProvider = ScriptDefinitionProvider.getInstance(environment.project)
                val scriptFile = File(sourcePath)
                if (scriptFile.isDirectory || !scriptDefinitionProvider.isScript(scriptFile.name)) {
                    val extensionHint =
                        if (configuration.get(JVMConfigurationKeys.SCRIPT_DEFINITIONS) == listOf(StandardScriptDefinition)) " (.kts)"
                        else ""
                    messageCollector.report(ERROR, "Specify path to the script file$extensionHint as the first argument")
                    return COMPILATION_ERROR
                }
    
                val scriptArgs = arguments.freeArgs.subList(1, arguments.freeArgs.size)
                return KotlinToJVMBytecodeCompiler.compileAndExecuteScript(environment, scriptArgs)
            } else {
                if (destination != null) {
                    if (destination.endsWith(".jar")) {
                        configuration.put(JVMConfigurationKeys.OUTPUT_JAR, File(destination))
                    } else {
                        configuration.put(JVMConfigurationKeys.OUTPUT_DIRECTORY, File(destination))
                    }
                }
    
                val environment = createCoreEnvironment(rootDisposable, configuration, messageCollector)
                    ?: return COMPILATION_ERROR
    
                registerJavacIfNeeded(environment, arguments).let {
                    if (!it) return COMPILATION_ERROR
                }
    
                if (environment.getSourceFiles().isEmpty()) {
                    if (arguments.version) {
                        return OK
                    }
                    messageCollector.report(ERROR, "No source files")
                    return COMPILATION_ERROR
                }
    
                KotlinToJVMBytecodeCompiler.compileBunchOfSources(environment)
    
                compileJavaFilesIfNeeded(environment, arguments).let {
                    if (!it) return COMPILATION_ERROR
                }
            }
    
            return OK
        } catch (e: CompilationException) {
            messageCollector.report(
                EXCEPTION,
                OutputMessageUtil.renderException(e),
                MessageUtil.psiElementToMessageLocation(e.element)
            )
            return INTERNAL_ERROR
        }
    }
    

    这里的代码非常的长,非常的长,暂时不进行解释。

    简单的可以分为以下四个步骤:

    1. 词法分析

      这一步骤,将按照 使用词法分析器_JetLexer,将源代码文件分解成特定的语法词汇。

    2. 语法分析

      这一步骤,是将第一步生成的特定语法词汇,生成语法树(ST)/抽象语法树(AST)

    3. 语义分析以及中间代码生成

      这一步骤,主要是分析语树的属性,比如是否存在编译错误,就是在改阶段发现的。

      分析过程会生成一些中间代码,这些中间代码是根据语义添加的。

    4. 目标代码生成

    相关文章

      网友评论

        本文标题:Kotlin 编译流程简介

        本文链接:https://www.haomeiwen.com/subject/kwmxaqtx.html