Kotlin

Kotlin 编译流程简介

2019-05-11  本文已影响0人  kotlon

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. 目标代码生成

上一篇下一篇

猜你喜欢

热点阅读