diff --git a/docs/kotlin.md b/docs/kotlin.md index 534ac1c61..1ca735d2d 100755 --- a/docs/kotlin.md +++ b/docs/kotlin.md @@ -567,3 +567,21 @@ versions.use_repository(name, kwargs |

-

| none | + + +## versions.get_major + +
+versions.get_major(version)
+
+ + + +**PARAMETERS** + + +| Name | Description | Default Value | +| :------------- | :------------- | :------------- | +| version |

-

| none | + + diff --git a/kotlin/BUILD b/kotlin/BUILD index e5919ea1b..6e04f5e26 100644 --- a/kotlin/BUILD +++ b/kotlin/BUILD @@ -54,13 +54,13 @@ release_archive( genrule( name = "stardoc", - srcs = [doc for doc in [ - "js", - "jvm", - "lint", - "core", - "repositories.doc", - ]], + srcs = [ + ":js", + ":jvm", + ":lint", + ":core", + ":repositories.doc", + ], outs = ["kotlin.md"], cmd = """ for md in $(SRCS); do diff --git a/src/main/kotlin/BUILD b/src/main/kotlin/BUILD index db305906f..1d56e8d13 100644 --- a/src/main/kotlin/BUILD +++ b/src/main/kotlin/BUILD @@ -15,7 +15,7 @@ jar_jar( jar_jar( name = "jdeps-gen", - input_jar = "//src/main/kotlin/io/bazel/kotlin/plugin/jdeps:jdeps-gen_deploy.jar", + input_jar = "//src/main/kotlin/io/bazel/kotlin/plugin:jdeps-gen_deploy.jar", rules = "shade.jarjar", visibility = ["//visibility:public"], ) diff --git a/src/main/kotlin/bootstrap.bzl b/src/main/kotlin/bootstrap.bzl index db7728d84..6a6bff9c9 100644 --- a/src/main/kotlin/bootstrap.bzl +++ b/src/main/kotlin/bootstrap.bzl @@ -30,7 +30,14 @@ def _resolve_dep_label(d): else: return d -def kt_bootstrap_library(name, srcs, visibility = [], deps = [], neverlink_deps = [], runtime_deps = []): +def kt_bootstrap_library( + name, + srcs, + visibility = [], + deps = [], + neverlink_deps = [], + runtime_deps = [], + kotlinc_repository_name = "com_github_jetbrains_kotlin"): """ Simple compilation of a kotlin library using a non-persistent worker. The target is a JavaInfo provider. @@ -41,6 +48,7 @@ def kt_bootstrap_library(name, srcs, visibility = [], deps = [], neverlink_deps deps: the dependenices, the are setup as runtime_deps of the library. neverlink_deps: deps that won't be linked. These deps are added to the `"for_ide"` target. """ + kotlinc_repository = "@" + kotlinc_repository_name jar_label = name + "_jar" dep_label = name + "_deps" native.filegroup( @@ -50,7 +58,7 @@ def kt_bootstrap_library(name, srcs, visibility = [], deps = [], neverlink_deps visibility = ["//visibility:private"], ) command = """ -function join_by { local IFS="$$1"; shift; echo "$$*"; } +function join_by {{ local IFS="$$1"; shift; echo "$$*"; }} case "$$(uname -s)" in CYGWIN*|MINGW32*|MSYS*) SEP=";" @@ -59,14 +67,14 @@ case "$$(uname -s)" in SEP=":" ;; esac -NAME=%s -CP="%s" -ARGS="%s" +NAME={name} +CP="{classpath}" +ARGS="{args}" CMD="$(JAVA) -Xmx256M -Xms32M -noverify \ - -cp $(location @com_github_jetbrains_kotlin//:kotlin-preloader) org.jetbrains.kotlin.preloading.Preloader \ - -cp $(location @com_github_jetbrains_kotlin//:kotlin-compiler) org.jetbrains.kotlin.cli.jvm.K2JVMCompiler \ - $$CP -d $(@D)/$${NAME}_temp.jar $${ARGS} $(SRCS)" + -cp $(location {kotlinc_repository}//:kotlin-preloader) org.jetbrains.kotlin.preloading.Preloader \ + -cp $(location {kotlinc_repository}//:kotlin-compiler) org.jetbrains.kotlin.cli.jvm.K2JVMCompiler \ + $$CP -d $(@D)/$${{NAME}}_temp.jar $${{ARGS}} $(SRCS)" $$CMD @@ -82,17 +90,22 @@ esac $$SJ \ --normalize \ --compression \ - --sources $(@D)/$${NAME}_temp.jar \ + --sources $(@D)/$${{NAME}}_temp.jar \ --output $(OUTS) -rm $(@D)/$${NAME}_temp.jar -""" % (name, "-cp $$(join_by $$SEP $(locations :%s)) " % dep_label if deps + neverlink_deps else "", " ".join(_BOOTSTRAP_LIB_ARGS)) +rm $(@D)/$${{NAME}}_temp.jar +""".format( + name = name, + classpath = "-cp $$(join_by $$SEP $(locations :%s)) " % dep_label if deps + neverlink_deps else "", + args = " ".join(_BOOTSTRAP_LIB_ARGS), + kotlinc_repository = kotlinc_repository, + ) native.genrule( name = jar_label, tools = [ - "@com_github_jetbrains_kotlin//:home", - "@com_github_jetbrains_kotlin//:kotlin-preloader", - "@com_github_jetbrains_kotlin//:kotlin-compiler", + "%s//:home" % kotlinc_repository, + "%s//:kotlin-preloader" % kotlinc_repository, + "%s//:kotlin-compiler" % kotlinc_repository, "@bazel_tools//tools/jdk:singlejar", dep_label, ], diff --git a/src/main/kotlin/io/bazel/kotlin/builder/toolchain/BUILD.bazel b/src/main/kotlin/io/bazel/kotlin/builder/toolchain/BUILD.bazel index 4cda9aeeb..c1e1c58a9 100644 --- a/src/main/kotlin/io/bazel/kotlin/builder/toolchain/BUILD.bazel +++ b/src/main/kotlin/io/bazel/kotlin/builder/toolchain/BUILD.bazel @@ -27,8 +27,8 @@ kt_bootstrap_library( visibility = ["//src:__subpackages__"], deps = [ "//src/main/kotlin/io/bazel/kotlin/builder/utils", + "//src/main/kotlin/io/bazel/kotlin/plugin:jdeps-gen-lib", "//src/main/kotlin/io/bazel/kotlin/plugin:skip-code-gen-lib", - "//src/main/kotlin/io/bazel/kotlin/plugin/jdeps:jdeps-gen-lib", "//src/main/protobuf:kotlin_model_java_proto", "@com_github_jetbrains_kotlin//:kotlin-preloader", "@kotlin_rules_maven//:com_google_protobuf_protobuf_java", diff --git a/src/main/kotlin/io/bazel/kotlin/builder/utils/jars/BUILD.bazel b/src/main/kotlin/io/bazel/kotlin/builder/utils/jars/BUILD.bazel index a7157e50b..acc4a3cc0 100644 --- a/src/main/kotlin/io/bazel/kotlin/builder/utils/jars/BUILD.bazel +++ b/src/main/kotlin/io/bazel/kotlin/builder/utils/jars/BUILD.bazel @@ -1,3 +1,4 @@ +load("//src/main/starlark/core/repositories/kotlin:releases.bzl", "KOTLINC_INDEX") load("//src/main/kotlin:bootstrap.bzl", "kt_bootstrap_library") kt_bootstrap_library( @@ -6,6 +7,8 @@ kt_bootstrap_library( "*.kt", "**/*.kt", ]), + # Need for the 1.6 jdeps-gen plugin + kotlinc_repository_name = KOTLINC_INDEX["1.6"].repository_name, visibility = ["//src:__subpackages__"], deps = [ "//src/main/protobuf:deps_java_proto", diff --git a/src/main/kotlin/io/bazel/kotlin/plugin/BUILD.bazel b/src/main/kotlin/io/bazel/kotlin/plugin/BUILD.bazel index eaa662124..7e424a8fa 100644 --- a/src/main/kotlin/io/bazel/kotlin/plugin/BUILD.bazel +++ b/src/main/kotlin/io/bazel/kotlin/plugin/BUILD.bazel @@ -11,35 +11,104 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. - +load("//src/main/starlark/core/repositories/kotlin:releases.bzl", "KOTLINC_INDEX") load("@rules_java//java:defs.bzl", "java_binary") load("//src/main/kotlin:bootstrap.bzl", "kt_bootstrap_library") load("//kotlin/internal/utils:generate_jvm_service.bzl", "generate_jvm_service") +load("@com_github_jetbrains_kotlin//:version.bzl", KOTLINC_MAJOR_VERSION = "MAJOR_VERSION") -# The compiler binary, this is co-located in the kotlin compiler classloader. -kt_bootstrap_library( - name = "skip-code-gen-lib", - srcs = glob(["*.kt"]), +# Generate a set of plugins for each major revision of the kotlinc compiler plugin api +[ + ( + # jdeps generator plugin + kt_bootstrap_library( + name = "jdeps-gen-lib-%s" % version, + srcs = glob(["%s/jdeps/*.kt" % release.repository_name]), + kotlinc_repository_name = release.repository_name, + visibility = ["//src:__subpackages__"], + deps = [ + "//src/main/kotlin/io/bazel/kotlin/builder/utils/jars", + "//src/main/protobuf:deps_java_proto", + "@%s//:kotlin-compiler" % release.repository_name, + "@kotlin_rules_maven//:com_google_protobuf_protobuf_java", + ], + ), + + # services to integrate with the plugin. + generate_jvm_service( + name = "jdeps-gen-services-%s" % version, + services = { + "io.bazel.kotlin.plugin.%s.jdeps.JdepsGenComponentRegistrar" % release.repository_name: "org.jetbrains.kotlin.compiler.plugin.CompilerPluginRegistrar", + "io.bazel.kotlin.plugin.%s.jdeps.JdepsGenCommandLineProcessor" % release.repository_name: "org.jetbrains.kotlin.compiler.plugin.CommandLineProcessor", + }, + ), + # The plugin binary. + java_binary( + name = "jdeps-gen-%s" % version, + visibility = ["//src:__subpackages__"], + runtime_deps = [ + ":jdeps-gen-lib-%s" % version, + ":jdeps-gen-services-%s" % version, + ], + ), + # SkipCodeGen utility plugin + kt_bootstrap_library( + name = "skip-code-gen-lib-%s" % version, + srcs = glob(["%s/*.kt" % release.repository_name]), + kotlinc_repository_name = release.repository_name, + visibility = ["//src:__subpackages__"], + deps = [ + "@%s//:kotlin-compiler" % release.repository_name, + ], + ), + # services to integrate with the plugin. + generate_jvm_service( + name = "skip-code-gen-services-%s" % version, + services = { + "io.bazel.kotlin.plugin.%s.SkipCodeGen" % release.repository_name: "org.jetbrains.kotlin.compiler.plugin.ComponentRegistrar", + }, + ), + # The plugin binary. + java_binary( + name = "skip-code-gen-%s" % version, + visibility = ["//src:__subpackages__"], + runtime_deps = [ + ":skip-code-gen-lib-%s" % version, + ":skip-code-gen-services-%s" % version, + ], + ), + ) + for version, release in KOTLINC_INDEX.items() +] + +# Deploy jars are not aliased, so repack in a java_binary. +java_binary( + name = "skip-code-gen", visibility = ["//src:__subpackages__"], - deps = [ - "@com_github_jetbrains_kotlin//:kotlin-compiler", + runtime_deps = [ + ":jdeps-gen-lib-%s" % KOTLINC_MAJOR_VERSION, + ":jdeps-gen-services-%s" % KOTLINC_MAJOR_VERSION, ], ) -# services to integrate with the plugin. -generate_jvm_service( - name = "skip-code-gen-services", - services = { - "io.bazel.kotlin.plugin.SkipCodeGen": "org.jetbrains.kotlin.compiler.plugin.ComponentRegistrar", - }, +alias( + name = "skip-code-gen-lib", + actual = "skip-code-gen-lib-%s" % KOTLINC_MAJOR_VERSION, + visibility = ["//src:__subpackages__"], ) -# The plugin binary. +# Deploy jars are not aliased, so repack in a java_binary. java_binary( - name = "skip-code-gen", + name = "jdeps-gen", visibility = ["//src:__subpackages__"], runtime_deps = [ - ":skip-code-gen-lib", - ":skip-code-gen-services", + ":jdeps-gen-lib-%s" % KOTLINC_MAJOR_VERSION, + ":jdeps-gen-services-%s" % KOTLINC_MAJOR_VERSION, ], ) + +alias( + name = "jdeps-gen-lib", + actual = "jdeps-gen-lib-%s" % KOTLINC_MAJOR_VERSION, + visibility = ["//src:__subpackages__"], +) diff --git a/src/main/kotlin/io/bazel/kotlin/plugin/com_github_jetbrains_kotlin_1_6/SkipCodeGen.kt b/src/main/kotlin/io/bazel/kotlin/plugin/com_github_jetbrains_kotlin_1_6/SkipCodeGen.kt new file mode 100644 index 000000000..13e2f0ebe --- /dev/null +++ b/src/main/kotlin/io/bazel/kotlin/plugin/com_github_jetbrains_kotlin_1_6/SkipCodeGen.kt @@ -0,0 +1,85 @@ +/* + * Copyright 2020 The Bazel Authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +@file:Suppress("ktlint:standard:package-name") + +package io.bazel.kotlin.plugin.com_github_jetbrains_kotlin_1_6 + +import com.google.common.base.Preconditions +import com.intellij.mock.MockProject +import com.intellij.openapi.project.Project +import org.jetbrains.kotlin.analyzer.AnalysisResult +import org.jetbrains.kotlin.compiler.plugin.ComponentRegistrar +import org.jetbrains.kotlin.config.CompilerConfiguration +import org.jetbrains.kotlin.container.ComponentProvider +import org.jetbrains.kotlin.context.ProjectContext +import org.jetbrains.kotlin.descriptors.ModuleDescriptor +import org.jetbrains.kotlin.psi.KtFile +import org.jetbrains.kotlin.resolve.BindingTrace +import org.jetbrains.kotlin.resolve.jvm.extensions.AnalysisHandlerExtension + +/** + * SkipCodeGen registers an extension to skip code generation. Must be the last compiler plugin. + */ +class SkipCodeGen : ComponentRegistrar { + + companion object { + val COMPILER_PLUGIN_ID = "io.bazel.kotlin.plugin.kt_1_8.SkipCodeGen" + } + + override fun registerProjectComponents( + project: MockProject, + configuration: CompilerConfiguration, + ) { + AnalysisHandlerExtension.registerExtension( + project, + SkipCodeGen, + ) + } + + /** + * SkipCodeGen ends the compilation + */ + private object SkipCodeGen : AnalysisHandlerExtension { + + override fun doAnalysis( + project: Project, + module: ModuleDescriptor, + projectContext: ProjectContext, + files: Collection, + bindingTrace: BindingTrace, + componentProvider: ComponentProvider, + ): AnalysisResult? { + return null + } + + // analysisCompleted generates the module jvm abi and requests code generation to be skipped. + override fun analysisCompleted( + project: Project, + module: ModuleDescriptor, + bindingTrace: BindingTrace, + files: Collection, + ): AnalysisResult? { + // Ensure this is the last plugin, as it will short circuit any other plugin analysisCompleted + // calls. + Preconditions.checkState( + AnalysisHandlerExtension.getInstances(project).last() == this, + "SkipCodeGen must be the last plugin: ${AnalysisHandlerExtension.getInstances(project)}", + ) + return AnalysisResult.Companion.success(bindingTrace.bindingContext, module, false) + } + } +} diff --git a/src/main/kotlin/io/bazel/kotlin/plugin/com_github_jetbrains_kotlin_1_6/jdeps/JdepsGenCommandLineProcessor.kt b/src/main/kotlin/io/bazel/kotlin/plugin/com_github_jetbrains_kotlin_1_6/jdeps/JdepsGenCommandLineProcessor.kt new file mode 100644 index 000000000..327bdbf85 --- /dev/null +++ b/src/main/kotlin/io/bazel/kotlin/plugin/com_github_jetbrains_kotlin_1_6/jdeps/JdepsGenCommandLineProcessor.kt @@ -0,0 +1,62 @@ +@file:Suppress("ktlint:standard:package-name") + +package io.bazel.kotlin.plugin.com_github_jetbrains_kotlin_1_6.jdeps + +import org.jetbrains.kotlin.compiler.plugin.AbstractCliOption +import org.jetbrains.kotlin.compiler.plugin.CliOption +import org.jetbrains.kotlin.compiler.plugin.CliOptionProcessingException +import org.jetbrains.kotlin.compiler.plugin.CommandLineProcessor +import org.jetbrains.kotlin.config.CompilerConfiguration + +class JdepsGenCommandLineProcessor : CommandLineProcessor { + companion object { + val COMPILER_PLUGIN_ID = "io.bazel.kotlin.plugin.jdeps.JDepsGen" + + val OUTPUT_JDEPS_FILE_OPTION: CliOption = + CliOption("output", "", "Output path for generated jdeps", required = true) + val TARGET_LABEL_OPTION: CliOption = + CliOption("target_label", "", "Label of target being analyzed", required = true) + val DIRECT_DEPENDENCIES_OPTION: CliOption = + CliOption( + "direct_dependencies", + "", + "List of targets direct dependencies", + required = false, + allowMultipleOccurrences = true, + ) + val STRICT_KOTLIN_DEPS_OPTION: CliOption = + CliOption("strict_kotlin_deps", "", "Report strict deps violations", required = true) + } + + override val pluginId: String + get() = COMPILER_PLUGIN_ID + override val pluginOptions: Collection + get() = listOf( + OUTPUT_JDEPS_FILE_OPTION, + TARGET_LABEL_OPTION, + DIRECT_DEPENDENCIES_OPTION, + STRICT_KOTLIN_DEPS_OPTION, + ) + + override fun processOption( + option: AbstractCliOption, + value: String, + configuration: CompilerConfiguration, + ) { + when (option) { + OUTPUT_JDEPS_FILE_OPTION -> configuration.put(JdepsGenConfigurationKeys.OUTPUT_JDEPS, value) + TARGET_LABEL_OPTION -> configuration.put(JdepsGenConfigurationKeys.TARGET_LABEL, value) + DIRECT_DEPENDENCIES_OPTION -> configuration.appendList( + JdepsGenConfigurationKeys.DIRECT_DEPENDENCIES, + value, + ) + + STRICT_KOTLIN_DEPS_OPTION -> configuration.put( + JdepsGenConfigurationKeys.STRICT_KOTLIN_DEPS, + value, + ) + + else -> throw CliOptionProcessingException("Unknown option: ${option.optionName}") + } + } +} diff --git a/src/main/kotlin/io/bazel/kotlin/plugin/jdeps/JdepsGenComponentRegistrar.kt b/src/main/kotlin/io/bazel/kotlin/plugin/com_github_jetbrains_kotlin_1_6/jdeps/JdepsGenComponentRegistrar.kt similarity index 87% rename from src/main/kotlin/io/bazel/kotlin/plugin/jdeps/JdepsGenComponentRegistrar.kt rename to src/main/kotlin/io/bazel/kotlin/plugin/com_github_jetbrains_kotlin_1_6/jdeps/JdepsGenComponentRegistrar.kt index e4d5b5641..55d450971 100644 --- a/src/main/kotlin/io/bazel/kotlin/plugin/jdeps/JdepsGenComponentRegistrar.kt +++ b/src/main/kotlin/io/bazel/kotlin/plugin/com_github_jetbrains_kotlin_1_6/jdeps/JdepsGenComponentRegistrar.kt @@ -1,4 +1,6 @@ -package io.bazel.kotlin.plugin.jdeps +@file:Suppress("ktlint:standard:package-name") + +package io.bazel.kotlin.plugin.com_github_jetbrains_kotlin_1_6.jdeps import com.intellij.mock.MockProject import org.jetbrains.kotlin.compiler.plugin.ComponentRegistrar @@ -6,7 +8,6 @@ import org.jetbrains.kotlin.config.CompilerConfiguration import org.jetbrains.kotlin.extensions.StorageComponentContainerContributor import org.jetbrains.kotlin.resolve.jvm.extensions.AnalysisHandlerExtension -@OptIn(org.jetbrains.kotlin.compiler.plugin.ExperimentalCompilerApi::class) class JdepsGenComponentRegistrar : ComponentRegistrar { override fun registerProjectComponents( diff --git a/src/main/kotlin/io/bazel/kotlin/plugin/jdeps/JdepsGenConfigurationKeys.kt b/src/main/kotlin/io/bazel/kotlin/plugin/com_github_jetbrains_kotlin_1_6/jdeps/JdepsGenConfigurationKeys.kt similarity index 89% rename from src/main/kotlin/io/bazel/kotlin/plugin/jdeps/JdepsGenConfigurationKeys.kt rename to src/main/kotlin/io/bazel/kotlin/plugin/com_github_jetbrains_kotlin_1_6/jdeps/JdepsGenConfigurationKeys.kt index a4616da16..3dcfe70b4 100644 --- a/src/main/kotlin/io/bazel/kotlin/plugin/jdeps/JdepsGenConfigurationKeys.kt +++ b/src/main/kotlin/io/bazel/kotlin/plugin/com_github_jetbrains_kotlin_1_6/jdeps/JdepsGenConfigurationKeys.kt @@ -1,4 +1,6 @@ -package io.bazel.kotlin.plugin.jdeps +@file:Suppress("ktlint:standard:package-name") + +package io.bazel.kotlin.plugin.com_github_jetbrains_kotlin_1_6.jdeps import org.jetbrains.kotlin.config.CompilerConfigurationKey diff --git a/src/main/kotlin/io/bazel/kotlin/plugin/com_github_jetbrains_kotlin_1_6/jdeps/JdepsGenExtension.kt b/src/main/kotlin/io/bazel/kotlin/plugin/com_github_jetbrains_kotlin_1_6/jdeps/JdepsGenExtension.kt new file mode 100644 index 000000000..6e08b42f7 --- /dev/null +++ b/src/main/kotlin/io/bazel/kotlin/plugin/com_github_jetbrains_kotlin_1_6/jdeps/JdepsGenExtension.kt @@ -0,0 +1,418 @@ +@file:Suppress("ktlint:standard:package-name") + +package io.bazel.kotlin.plugin.com_github_jetbrains_kotlin_1_6.jdeps + +import com.google.devtools.build.lib.view.proto.Deps +import com.intellij.mock.MockProject +import com.intellij.openapi.project.Project +import com.intellij.psi.PsiElement +import io.bazel.kotlin.builder.utils.jars.JarOwner +import org.jetbrains.kotlin.analyzer.AnalysisResult +import org.jetbrains.kotlin.config.CompilerConfiguration +import org.jetbrains.kotlin.container.StorageComponentContainer +import org.jetbrains.kotlin.container.useInstance +import org.jetbrains.kotlin.descriptors.ClassDescriptor +import org.jetbrains.kotlin.descriptors.DeclarationDescriptor +import org.jetbrains.kotlin.descriptors.DeclarationDescriptorWithSource +import org.jetbrains.kotlin.descriptors.FunctionDescriptor +import org.jetbrains.kotlin.descriptors.ModuleDescriptor +import org.jetbrains.kotlin.descriptors.ParameterDescriptor +import org.jetbrains.kotlin.descriptors.PropertyDescriptor +import org.jetbrains.kotlin.descriptors.SourceElement +import org.jetbrains.kotlin.descriptors.impl.LocalVariableDescriptor +import org.jetbrains.kotlin.extensions.StorageComponentContainerContributor +import org.jetbrains.kotlin.load.java.descriptors.JavaMethodDescriptor +import org.jetbrains.kotlin.load.java.descriptors.JavaPropertyDescriptor +import org.jetbrains.kotlin.load.java.sources.JavaSourceElement +import org.jetbrains.kotlin.load.java.structure.impl.classFiles.BinaryJavaClass +import org.jetbrains.kotlin.load.java.structure.impl.classFiles.BinaryJavaField +import org.jetbrains.kotlin.load.kotlin.KotlinJvmBinarySourceElement +import org.jetbrains.kotlin.load.kotlin.VirtualFileKotlinClass +import org.jetbrains.kotlin.load.kotlin.getContainingKotlinJvmBinaryClass +import org.jetbrains.kotlin.platform.TargetPlatform +import org.jetbrains.kotlin.psi.KtDeclaration +import org.jetbrains.kotlin.psi.KtFile +import org.jetbrains.kotlin.resolve.BindingTrace +import org.jetbrains.kotlin.resolve.FunctionImportedFromObject +import org.jetbrains.kotlin.resolve.PropertyImportedFromObject +import org.jetbrains.kotlin.resolve.calls.checkers.CallChecker +import org.jetbrains.kotlin.resolve.calls.checkers.CallCheckerContext +import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall +import org.jetbrains.kotlin.resolve.calls.util.FakeCallableDescriptorForObject +import org.jetbrains.kotlin.resolve.checkers.DeclarationChecker +import org.jetbrains.kotlin.resolve.checkers.DeclarationCheckerContext +import org.jetbrains.kotlin.resolve.jvm.extensions.AnalysisHandlerExtension +import org.jetbrains.kotlin.types.KotlinType +import org.jetbrains.kotlin.types.TypeConstructor +import org.jetbrains.kotlin.types.typeUtil.supertypes +import java.io.BufferedOutputStream +import java.io.File +import java.nio.file.Paths + +/** + * Kotlin compiler extension that tracks classes (and corresponding classpath jars) needed to + * compile current kotlin target. Tracked data should include all classes whose changes could + * affect target's compilation out : direct class dependencies (i.e external classes directly + * used), but also their superclass, interfaces, etc. + * The primary use of this extension is to improve Kotlin module compilation avoidance in build + * systems (like Buck). + * + * Tracking of classes is done with a Remapper, which exposes all object types used by the class + * bytecode being generated. Tracking of the ancestor classes is done via modules and class + * descriptors that got generated during analysis/resolve phase of Kotlin compilation. + * + * Note: annotation processors dependencies may need to be tracked separatly (and may not need + * per-class ABI change tracking) + * + * @param project the current compilation project + * @param configuration the current compilation configuration + */ +class JdepsGenExtension( + val project: MockProject, + val configuration: CompilerConfiguration, +) : + AnalysisHandlerExtension, StorageComponentContainerContributor { + + companion object { + + /** + * Returns the path of the jar archive file corresponding to the provided descriptor. + * + * @descriptor the descriptor, typically obtained from compilation analyze phase + * @return the path corresponding to the JAR where this class was loaded from, or null. + */ + fun getClassCanonicalPath(descriptor: DeclarationDescriptorWithSource): String? { + return when (val sourceElement: SourceElement = descriptor.source) { + is JavaSourceElement -> + if (sourceElement.javaElement is BinaryJavaClass) { + (sourceElement.javaElement as BinaryJavaClass).virtualFile!!.canonicalPath + } else if (sourceElement.javaElement is BinaryJavaField) { + val containingClass = (sourceElement.javaElement as BinaryJavaField).containingClass + if (containingClass is BinaryJavaClass) { + containingClass.virtualFile!!.canonicalPath + } else { + null + } + } else { + // Ignore Java source local to this module. + null + } + + is KotlinJvmBinarySourceElement -> + (sourceElement.binaryClass as VirtualFileKotlinClass).file.canonicalPath + + else -> null + } + } + + fun getClassCanonicalPath(typeConstructor: TypeConstructor): String? { + return (typeConstructor.declarationDescriptor as? DeclarationDescriptorWithSource)?.let { + getClassCanonicalPath( + it, + ) + } + } + } + + private val explicitClassesCanonicalPaths = mutableSetOf() + private val implicitClassesCanonicalPaths = mutableSetOf() + + override fun registerModuleComponents( + container: StorageComponentContainer, + platform: TargetPlatform, + moduleDescriptor: ModuleDescriptor, + ) { + container.useInstance( + ClasspathCollectingChecker( + explicitClassesCanonicalPaths, + implicitClassesCanonicalPaths, + ), + ) + } + + class ClasspathCollectingChecker( + private val explicitClassesCanonicalPaths: MutableSet, + private val implicitClassesCanonicalPaths: MutableSet, + ) : CallChecker, DeclarationChecker { + + override fun check( + resolvedCall: ResolvedCall<*>, + reportOn: PsiElement, + context: CallCheckerContext, + ) { + when (val resultingDescriptor = resolvedCall.resultingDescriptor) { + is FunctionImportedFromObject -> { + collectTypeReferences( + (resolvedCall.resultingDescriptor as FunctionImportedFromObject) + .containingObject.defaultType, + ) + } + + is PropertyImportedFromObject -> { + collectTypeReferences( + (resolvedCall.resultingDescriptor as PropertyImportedFromObject) + .containingObject.defaultType, + ) + } + + is JavaMethodDescriptor -> { + getClassCanonicalPath( + (resultingDescriptor.containingDeclaration as ClassDescriptor).typeConstructor, + )?.let { + explicitClassesCanonicalPaths.add( + it, + ) + } + } + + is FunctionDescriptor -> { + resultingDescriptor.returnType?.let { addImplicitDep(it) } + resultingDescriptor.valueParameters.forEach { valueParameter -> + addImplicitDep(valueParameter.type) + } + val virtualFileClass = + resultingDescriptor.getContainingKotlinJvmBinaryClass() as? VirtualFileKotlinClass + ?: return + explicitClassesCanonicalPaths.add(virtualFileClass.file.path) + } + + is ParameterDescriptor -> { + getClassCanonicalPath(resultingDescriptor)?.let { explicitClassesCanonicalPaths.add(it) } + } + + is FakeCallableDescriptorForObject -> { + collectTypeReferences(resultingDescriptor.type) + } + + is JavaPropertyDescriptor -> { + getClassCanonicalPath(resultingDescriptor)?.let { explicitClassesCanonicalPaths.add(it) } + } + + is PropertyDescriptor -> { + when (resultingDescriptor.containingDeclaration) { + is ClassDescriptor -> collectTypeReferences( + (resultingDescriptor.containingDeclaration as ClassDescriptor).defaultType, + ) + + else -> { + val virtualFileClass = + (resultingDescriptor).getContainingKotlinJvmBinaryClass() as? VirtualFileKotlinClass + ?: return + explicitClassesCanonicalPaths.add(virtualFileClass.file.path) + } + } + addImplicitDep(resultingDescriptor.type) + } + + else -> return + } + } + + override fun check( + declaration: KtDeclaration, + descriptor: DeclarationDescriptor, + context: DeclarationCheckerContext, + ) { + when (descriptor) { + is ClassDescriptor -> { + descriptor.typeConstructor.supertypes.forEach { + collectTypeReferences(it) + } + } + + is FunctionDescriptor -> { + descriptor.returnType?.let { collectTypeReferences(it) } + descriptor.valueParameters.forEach { valueParameter -> + collectTypeReferences(valueParameter.type) + } + descriptor.annotations.forEach { annotation -> + collectTypeReferences(annotation.type) + } + } + + is PropertyDescriptor -> { + collectTypeReferences(descriptor.type) + descriptor.annotations.forEach { annotation -> + collectTypeReferences(annotation.type) + } + descriptor.backingField?.annotations?.forEach { annotation -> + collectTypeReferences(annotation.type) + } + } + + is LocalVariableDescriptor -> { + collectTypeReferences(descriptor.type) + } + } + } + + private fun addImplicitDep(it: KotlinType) { + getClassCanonicalPath(it.constructor)?.let { implicitClassesCanonicalPaths.add(it) } + } + + private fun addExplicitDep(it: KotlinType) { + getClassCanonicalPath(it.constructor)?.let { explicitClassesCanonicalPaths.add(it) } + } + + /** + * Records direct and indirect references for a given type. Direct references are explicitly + * used in the code, e.g: a type declaration or a generic type declaration. Indirect references + * are other types required for compilation such as supertypes and interfaces of those explicit + * types. + */ + private fun collectTypeReferences(kotlinType: KotlinType, collectSuperTypes: Boolean = true) { + addExplicitDep(kotlinType) + + if (collectSuperTypes) { + kotlinType.supertypes().forEach { + addImplicitDep(it) + } + } + + collectTypeArguments(kotlinType) + } + + fun collectTypeArguments( + kotlinType: KotlinType, + visitedKotlinTypes: MutableSet = mutableSetOf(), + ) { + visitedKotlinTypes.add(kotlinType) + kotlinType.arguments.map { it.type }.forEach { typeArgument -> + addExplicitDep(typeArgument) + typeArgument.supertypes().forEach { addImplicitDep(it) } + if (!visitedKotlinTypes.contains(typeArgument)) { + collectTypeArguments(typeArgument, visitedKotlinTypes) + } + } + } + } + + override fun analysisCompleted( + project: Project, + module: ModuleDescriptor, + bindingTrace: BindingTrace, + files: Collection, + ): AnalysisResult? { + val directDeps = configuration.getList(JdepsGenConfigurationKeys.DIRECT_DEPENDENCIES) + val targetLabel = configuration.getNotNull(JdepsGenConfigurationKeys.TARGET_LABEL) + val explicitDeps = createDepsMap(explicitClassesCanonicalPaths) + + doWriteJdeps(directDeps, targetLabel, explicitDeps) + + doStrictDeps(configuration, targetLabel, directDeps, explicitDeps) + + return super.analysisCompleted(project, module, bindingTrace, files) + } + + /** + * Returns a map of jars to classes loaded from those jars. + */ + private fun createDepsMap(classes: Set): Map> { + val jarsToClasses = mutableMapOf>() + classes.forEach { + val parts = it.split("!/") + val jarPath = parts[0] + if (jarPath.endsWith(".jar")) { + jarsToClasses.computeIfAbsent(jarPath) { ArrayList() }.add(parts[1]) + } + } + return jarsToClasses + } + + private fun doWriteJdeps( + directDeps: MutableList, + targetLabel: String, + explicitDeps: Map>, + ) { + val implicitDeps = createDepsMap(implicitClassesCanonicalPaths) + + // Build and write out deps.proto + val jdepsOutput = configuration.getNotNull(JdepsGenConfigurationKeys.OUTPUT_JDEPS) + + val rootBuilder = Deps.Dependencies.newBuilder() + rootBuilder.success = true + rootBuilder.ruleLabel = targetLabel + + val unusedDeps = directDeps.subtract(explicitDeps.keys) + unusedDeps.forEach { jarPath -> + val dependency = Deps.Dependency.newBuilder() + dependency.kind = Deps.Dependency.Kind.UNUSED + dependency.path = jarPath + rootBuilder.addDependency(dependency) + } + + explicitDeps.forEach { (jarPath, _) -> + val dependency = Deps.Dependency.newBuilder() + dependency.kind = Deps.Dependency.Kind.EXPLICIT + dependency.path = jarPath + rootBuilder.addDependency(dependency) + } + + implicitDeps.keys.subtract(explicitDeps.keys).forEach { + val dependency = Deps.Dependency.newBuilder() + dependency.kind = Deps.Dependency.Kind.IMPLICIT + dependency.path = it + rootBuilder.addDependency(dependency) + } + + BufferedOutputStream(File(jdepsOutput).outputStream()).use { + it.write(rootBuilder.build().toByteArray()) + } + } + + private fun doStrictDeps( + compilerConfiguration: CompilerConfiguration, + targetLabel: String, + directDeps: MutableList, + explicitDeps: Map>, + ) { + when (compilerConfiguration.getNotNull(JdepsGenConfigurationKeys.STRICT_KOTLIN_DEPS)) { + "warn" -> checkStrictDeps(explicitDeps, directDeps, targetLabel) + "error" -> { + if (checkStrictDeps( + explicitDeps, + directDeps, + targetLabel, + ) + ) { + error("Strict Deps Violations - please fix") + } + } + } + } + + /** + * Prints strict deps warnings and returns true if violations were found. + */ + private fun checkStrictDeps( + result: Map>, + directDeps: List, + targetLabel: String, + ): Boolean { + val missingStrictDeps = result.keys + .filter { !directDeps.contains(it) } + .map { JarOwner.readJarOwnerFromManifest(Paths.get(it)).label } + + if (missingStrictDeps.isNotEmpty()) { + val open = "\u001b[35m\u001b[1m" + val close = "\u001b[0m" + val command = + """ + |$open ** Please add the following dependencies:$close ${ + missingStrictDeps.joinToString( + " ", + ) + } to $targetLabel + |$open ** You can use the following buildozer command:$close buildozer 'add deps ${ + missingStrictDeps.joinToString( + " ", + ) + }' $targetLabel + """.trimMargin() + + println(command) + return true + } + return false + } +} diff --git a/src/main/kotlin/io/bazel/kotlin/plugin/com_github_jetbrains_kotlin_1_7/SkipCodeGen.kt b/src/main/kotlin/io/bazel/kotlin/plugin/com_github_jetbrains_kotlin_1_7/SkipCodeGen.kt new file mode 100644 index 000000000..e6c20746d --- /dev/null +++ b/src/main/kotlin/io/bazel/kotlin/plugin/com_github_jetbrains_kotlin_1_7/SkipCodeGen.kt @@ -0,0 +1,85 @@ +/* + * Copyright 2020 The Bazel Authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +@file:Suppress("ktlint:standard:package-name") + +package io.bazel.kotlin.plugin.com_github_jetbrains_kotlin_1_7 + +import com.google.common.base.Preconditions +import com.intellij.mock.MockProject +import com.intellij.openapi.project.Project +import org.jetbrains.kotlin.analyzer.AnalysisResult +import org.jetbrains.kotlin.compiler.plugin.ComponentRegistrar +import org.jetbrains.kotlin.config.CompilerConfiguration +import org.jetbrains.kotlin.container.ComponentProvider +import org.jetbrains.kotlin.context.ProjectContext +import org.jetbrains.kotlin.descriptors.ModuleDescriptor +import org.jetbrains.kotlin.psi.KtFile +import org.jetbrains.kotlin.resolve.BindingTrace +import org.jetbrains.kotlin.resolve.jvm.extensions.AnalysisHandlerExtension + +/** + * SkipCodeGen registers an extension to skip code generation. Must be the last compiler plugin. + */ +class SkipCodeGen : ComponentRegistrar { + + companion object { + val COMPILER_PLUGIN_ID = "io.bazel.kotlin.plugin.SkipCodeGen" + } + + override fun registerProjectComponents( + project: MockProject, + configuration: CompilerConfiguration, + ) { + AnalysisHandlerExtension.registerExtension( + project, + SkipCodeGen, + ) + } + + /** + * SkipCodeGen ends the compilation + */ + private object SkipCodeGen : AnalysisHandlerExtension { + + override fun doAnalysis( + project: Project, + module: ModuleDescriptor, + projectContext: ProjectContext, + files: Collection, + bindingTrace: BindingTrace, + componentProvider: ComponentProvider, + ): AnalysisResult? { + return null + } + + // analysisCompleted generates the module jvm abi and requests code generation to be skipped. + override fun analysisCompleted( + project: Project, + module: ModuleDescriptor, + bindingTrace: BindingTrace, + files: Collection, + ): AnalysisResult? { + // Ensure this is the last plugin, as it will short circuit any other plugin analysisCompleted + // calls. + Preconditions.checkState( + AnalysisHandlerExtension.getInstances(project).last() == this, + "SkipCodeGen must be the last plugin: ${AnalysisHandlerExtension.getInstances(project)}", + ) + return AnalysisResult.Companion.success(bindingTrace.bindingContext, module, false) + } + } +} diff --git a/src/main/kotlin/io/bazel/kotlin/plugin/com_github_jetbrains_kotlin_1_7/jdeps/JdepsGenCommandLineProcessor.kt b/src/main/kotlin/io/bazel/kotlin/plugin/com_github_jetbrains_kotlin_1_7/jdeps/JdepsGenCommandLineProcessor.kt new file mode 100644 index 000000000..778814b90 --- /dev/null +++ b/src/main/kotlin/io/bazel/kotlin/plugin/com_github_jetbrains_kotlin_1_7/jdeps/JdepsGenCommandLineProcessor.kt @@ -0,0 +1,81 @@ +@file:Suppress("ktlint:standard:package-name") + +package io.bazel.kotlin.plugin.com_github_jetbrains_kotlin_1_7.jdeps + +import org.jetbrains.kotlin.compiler.plugin.AbstractCliOption +import org.jetbrains.kotlin.compiler.plugin.CliOption +import org.jetbrains.kotlin.compiler.plugin.CliOptionProcessingException +import org.jetbrains.kotlin.compiler.plugin.CommandLineProcessor +import org.jetbrains.kotlin.config.CompilerConfiguration +import org.jetbrains.kotlin.config.CompilerConfigurationKey + +class JdepsGenCommandLineProcessor : CommandLineProcessor { + companion object { + val COMPILER_PLUGIN_ID = "io.bazel.kotlin.plugin.jdeps.JDepsGen" + + val OUTPUT_JDEPS_FILE_OPTION: CliOption = + CliOption("output", "", "Output path for generated jdeps", required = true) + val TARGET_LABEL_OPTION: CliOption = + CliOption("target_label", "", "Label of target being analyzed", required = true) + val DIRECT_DEPENDENCIES_OPTION: CliOption = + CliOption( + "direct_dependencies", + "", + "List of targets direct dependencies", + required = false, + allowMultipleOccurrences = true, + ) + val STRICT_KOTLIN_DEPS_OPTION: CliOption = + CliOption("strict_kotlin_deps", "", "Report strict deps violations", required = true) + } + + override val pluginId: String + get() = COMPILER_PLUGIN_ID + override val pluginOptions: Collection + get() = listOf( + OUTPUT_JDEPS_FILE_OPTION, + TARGET_LABEL_OPTION, + DIRECT_DEPENDENCIES_OPTION, + STRICT_KOTLIN_DEPS_OPTION, + ) + + override fun processOption( + option: AbstractCliOption, + value: String, + configuration: CompilerConfiguration, + ) { + when (option) { + OUTPUT_JDEPS_FILE_OPTION -> configuration.put(JdepsGenConfigurationKeys.OUTPUT_JDEPS, value) + TARGET_LABEL_OPTION -> configuration.put(JdepsGenConfigurationKeys.TARGET_LABEL, value) + DIRECT_DEPENDENCIES_OPTION -> configuration.appendList( + JdepsGenConfigurationKeys.DIRECT_DEPENDENCIES, + value, + ) + + STRICT_KOTLIN_DEPS_OPTION -> configuration.put( + JdepsGenConfigurationKeys.STRICT_KOTLIN_DEPS, + value, + ) + + else -> throw CliOptionProcessingException("Unknown option: ${option.optionName}") + } + } + + override fun CompilerConfiguration.appendList( + option: CompilerConfigurationKey>, + value: T, + ) { + val paths = getList(option).toMutableList() + paths.add(value) + put(option, paths) + } + + override fun CompilerConfiguration.appendList( + option: CompilerConfigurationKey>, + values: List, + ) { + val paths = getList(option).toMutableList() + paths.addAll(values) + put(option, paths) + } +} diff --git a/src/main/kotlin/io/bazel/kotlin/plugin/com_github_jetbrains_kotlin_1_7/jdeps/JdepsGenComponentRegistrar.kt b/src/main/kotlin/io/bazel/kotlin/plugin/com_github_jetbrains_kotlin_1_7/jdeps/JdepsGenComponentRegistrar.kt new file mode 100644 index 000000000..f7dc6001e --- /dev/null +++ b/src/main/kotlin/io/bazel/kotlin/plugin/com_github_jetbrains_kotlin_1_7/jdeps/JdepsGenComponentRegistrar.kt @@ -0,0 +1,23 @@ +@file:Suppress("ktlint:standard:package-name") + +package io.bazel.kotlin.plugin.com_github_jetbrains_kotlin_1_7.jdeps + +import com.intellij.mock.MockProject +import org.jetbrains.kotlin.compiler.plugin.ComponentRegistrar +import org.jetbrains.kotlin.config.CompilerConfiguration +import org.jetbrains.kotlin.extensions.StorageComponentContainerContributor +import org.jetbrains.kotlin.resolve.jvm.extensions.AnalysisHandlerExtension + +class JdepsGenComponentRegistrar : ComponentRegistrar { + + override fun registerProjectComponents( + project: MockProject, + configuration: CompilerConfiguration, + ) { + // Capture all types referenced by the compiler for this module and look up the jar from which + // they were loaded from + val extension = JdepsGenExtension(project, configuration) + AnalysisHandlerExtension.registerExtension(project, extension) + StorageComponentContainerContributor.registerExtension(project, extension) + } +} diff --git a/src/main/kotlin/io/bazel/kotlin/plugin/com_github_jetbrains_kotlin_1_7/jdeps/JdepsGenConfigurationKeys.kt b/src/main/kotlin/io/bazel/kotlin/plugin/com_github_jetbrains_kotlin_1_7/jdeps/JdepsGenConfigurationKeys.kt new file mode 100644 index 000000000..b3f0afe69 --- /dev/null +++ b/src/main/kotlin/io/bazel/kotlin/plugin/com_github_jetbrains_kotlin_1_7/jdeps/JdepsGenConfigurationKeys.kt @@ -0,0 +1,37 @@ +@file:Suppress("ktlint:standard:package-name") + +package io.bazel.kotlin.plugin.com_github_jetbrains_kotlin_1_7.jdeps + +import org.jetbrains.kotlin.config.CompilerConfigurationKey + +object JdepsGenConfigurationKeys { + /** + * Output path of generated Jdeps proto file. + */ + val OUTPUT_JDEPS: CompilerConfigurationKey = + CompilerConfigurationKey.create( + JdepsGenCommandLineProcessor.OUTPUT_JDEPS_FILE_OPTION.description, + ) + + /** + * Label of the Bazel target being analyzed. + */ + val TARGET_LABEL: CompilerConfigurationKey = + CompilerConfigurationKey.create(JdepsGenCommandLineProcessor.TARGET_LABEL_OPTION.description) + + /** + * Label of the Bazel target being analyzed. + */ + val STRICT_KOTLIN_DEPS: CompilerConfigurationKey = + CompilerConfigurationKey.create( + JdepsGenCommandLineProcessor.STRICT_KOTLIN_DEPS_OPTION.description, + ) + + /** + * List of direct dependencies of the target. + */ + val DIRECT_DEPENDENCIES: CompilerConfigurationKey> = + CompilerConfigurationKey.create( + JdepsGenCommandLineProcessor.DIRECT_DEPENDENCIES_OPTION.description, + ) +} diff --git a/src/main/kotlin/io/bazel/kotlin/plugin/jdeps/JdepsGenExtension.kt b/src/main/kotlin/io/bazel/kotlin/plugin/com_github_jetbrains_kotlin_1_7/jdeps/JdepsGenExtension.kt similarity index 99% rename from src/main/kotlin/io/bazel/kotlin/plugin/jdeps/JdepsGenExtension.kt rename to src/main/kotlin/io/bazel/kotlin/plugin/com_github_jetbrains_kotlin_1_7/jdeps/JdepsGenExtension.kt index 6c5b68011..f87b0f63e 100644 --- a/src/main/kotlin/io/bazel/kotlin/plugin/jdeps/JdepsGenExtension.kt +++ b/src/main/kotlin/io/bazel/kotlin/plugin/com_github_jetbrains_kotlin_1_7/jdeps/JdepsGenExtension.kt @@ -1,4 +1,6 @@ -package io.bazel.kotlin.plugin.jdeps +@file:Suppress("ktlint:standard:package-name") + +package io.bazel.kotlin.plugin.com_github_jetbrains_kotlin_1_7.jdeps import com.google.devtools.build.lib.view.proto.Deps import com.intellij.mock.MockProject @@ -94,8 +96,10 @@ class JdepsGenExtension( // Ignore Java source local to this module. null } + is KotlinJvmBinarySourceElement -> (sourceElement.binaryClass as VirtualFileKotlinClass).file.canonicalPath + else -> null } } @@ -136,14 +140,17 @@ class JdepsGenExtension( is FunctionImportedFromObject -> { collectTypeReferences(resultingDescriptor.containingObject.defaultType) } + is PropertyImportedFromObject -> { collectTypeReferences(resultingDescriptor.containingObject.defaultType) } + is JavaMethodDescriptor -> { getClassCanonicalPath( (resultingDescriptor.containingDeclaration as ClassDescriptor).typeConstructor, )?.let { explicitClassesCanonicalPaths.add(it) } } + is FunctionDescriptor -> { resultingDescriptor.returnType?.let { addImplicitDep(it) } resultingDescriptor.valueParameters.forEach { valueParameter -> @@ -154,20 +161,25 @@ class JdepsGenExtension( ?: return explicitClassesCanonicalPaths.add(virtualFileClass.file.path) } + is ParameterDescriptor -> { getClassCanonicalPath(resultingDescriptor)?.let { explicitClassesCanonicalPaths.add(it) } } + is FakeCallableDescriptorForObject -> { collectTypeReferences(resultingDescriptor.type) } + is JavaPropertyDescriptor -> { getClassCanonicalPath(resultingDescriptor)?.let { explicitClassesCanonicalPaths.add(it) } } + is PropertyDescriptor -> { when (resultingDescriptor.containingDeclaration) { is ClassDescriptor -> collectTypeReferences( (resultingDescriptor.containingDeclaration as ClassDescriptor).defaultType, ) + else -> { val virtualFileClass = (resultingDescriptor).getContainingKotlinJvmBinaryClass() as? VirtualFileKotlinClass @@ -177,6 +189,7 @@ class JdepsGenExtension( } addImplicitDep(resultingDescriptor.type) } + else -> return } } @@ -192,6 +205,7 @@ class JdepsGenExtension( collectTypeReferences(it) } } + is FunctionDescriptor -> { descriptor.returnType?.let { collectTypeReferences(it) } descriptor.valueParameters.forEach { valueParameter -> @@ -204,6 +218,7 @@ class JdepsGenExtension( collectTypeReferences(it) } } + is PropertyDescriptor -> { collectTypeReferences(descriptor.type) descriptor.annotations.forEach { annotation -> @@ -213,6 +228,7 @@ class JdepsGenExtension( collectTypeReferences(annotation.type) } } + is LocalVariableDescriptor -> { collectTypeReferences(descriptor.type) } diff --git a/src/main/kotlin/io/bazel/kotlin/plugin/SkipCodeGen.kt b/src/main/kotlin/io/bazel/kotlin/plugin/com_github_jetbrains_kotlin_1_8/SkipCodeGen.kt similarity index 96% rename from src/main/kotlin/io/bazel/kotlin/plugin/SkipCodeGen.kt rename to src/main/kotlin/io/bazel/kotlin/plugin/com_github_jetbrains_kotlin_1_8/SkipCodeGen.kt index f9eea10d1..1d3b78aad 100644 --- a/src/main/kotlin/io/bazel/kotlin/plugin/SkipCodeGen.kt +++ b/src/main/kotlin/io/bazel/kotlin/plugin/com_github_jetbrains_kotlin_1_8/SkipCodeGen.kt @@ -14,7 +14,9 @@ * limitations under the License. * */ -package io.bazel.kotlin.plugin +@file:Suppress("ktlint:standard:package-name") + +package io.bazel.kotlin.plugin.com_github_jetbrains_kotlin_1_8 import com.google.common.base.Preconditions import com.intellij.mock.MockProject diff --git a/src/main/kotlin/io/bazel/kotlin/plugin/jdeps/JdepsGenCommandLineProcessor.kt b/src/main/kotlin/io/bazel/kotlin/plugin/com_github_jetbrains_kotlin_1_8/jdeps/JdepsGenCommandLineProcessor.kt similarity index 95% rename from src/main/kotlin/io/bazel/kotlin/plugin/jdeps/JdepsGenCommandLineProcessor.kt rename to src/main/kotlin/io/bazel/kotlin/plugin/com_github_jetbrains_kotlin_1_8/jdeps/JdepsGenCommandLineProcessor.kt index 122dab2a3..92ed015b8 100644 --- a/src/main/kotlin/io/bazel/kotlin/plugin/jdeps/JdepsGenCommandLineProcessor.kt +++ b/src/main/kotlin/io/bazel/kotlin/plugin/com_github_jetbrains_kotlin_1_8/jdeps/JdepsGenCommandLineProcessor.kt @@ -1,4 +1,6 @@ -package io.bazel.kotlin.plugin.jdeps +@file:Suppress("ktlint:standard:package-name") + +package io.bazel.kotlin.plugin.com_github_jetbrains_kotlin_1_8.jdeps import org.jetbrains.kotlin.compiler.plugin.AbstractCliOption import org.jetbrains.kotlin.compiler.plugin.CliOption @@ -50,10 +52,12 @@ class JdepsGenCommandLineProcessor : CommandLineProcessor { JdepsGenConfigurationKeys.DIRECT_DEPENDENCIES, value, ) + STRICT_KOTLIN_DEPS_OPTION -> configuration.put( JdepsGenConfigurationKeys.STRICT_KOTLIN_DEPS, value, ) + else -> throw CliOptionProcessingException("Unknown option: ${option.optionName}") } } diff --git a/src/main/kotlin/io/bazel/kotlin/plugin/com_github_jetbrains_kotlin_1_8/jdeps/JdepsGenComponentRegistrar.kt b/src/main/kotlin/io/bazel/kotlin/plugin/com_github_jetbrains_kotlin_1_8/jdeps/JdepsGenComponentRegistrar.kt new file mode 100644 index 000000000..7a4b94a63 --- /dev/null +++ b/src/main/kotlin/io/bazel/kotlin/plugin/com_github_jetbrains_kotlin_1_8/jdeps/JdepsGenComponentRegistrar.kt @@ -0,0 +1,21 @@ +@file:Suppress("ktlint:standard:package-name") + +package io.bazel.kotlin.plugin.com_github_jetbrains_kotlin_1_8.jdeps + +import org.jetbrains.kotlin.compiler.plugin.CompilerPluginRegistrar +import org.jetbrains.kotlin.config.CompilerConfiguration +import org.jetbrains.kotlin.extensions.StorageComponentContainerContributor +import org.jetbrains.kotlin.resolve.jvm.extensions.AnalysisHandlerExtension + +@OptIn(org.jetbrains.kotlin.compiler.plugin.ExperimentalCompilerApi::class) +class JdepsGenComponentRegistrar : CompilerPluginRegistrar() { + + override val supportsK2: Boolean = false + override fun ExtensionStorage.registerExtensions(configuration: CompilerConfiguration) { + // Capture all types referenced by the compiler for this module and look up the jar from which + // they were loaded from + val extension = JdepsGenExtension(configuration) + AnalysisHandlerExtension.registerExtension(extension) + StorageComponentContainerContributor.registerExtension(extension) + } +} diff --git a/src/main/kotlin/io/bazel/kotlin/plugin/com_github_jetbrains_kotlin_1_8/jdeps/JdepsGenConfigurationKeys.kt b/src/main/kotlin/io/bazel/kotlin/plugin/com_github_jetbrains_kotlin_1_8/jdeps/JdepsGenConfigurationKeys.kt new file mode 100644 index 000000000..74921063d --- /dev/null +++ b/src/main/kotlin/io/bazel/kotlin/plugin/com_github_jetbrains_kotlin_1_8/jdeps/JdepsGenConfigurationKeys.kt @@ -0,0 +1,37 @@ +@file:Suppress("ktlint:standard:package-name") + +package io.bazel.kotlin.plugin.com_github_jetbrains_kotlin_1_8.jdeps + +import org.jetbrains.kotlin.config.CompilerConfigurationKey + +object JdepsGenConfigurationKeys { + /** + * Output path of generated Jdeps proto file. + */ + val OUTPUT_JDEPS: CompilerConfigurationKey = + CompilerConfigurationKey.create( + JdepsGenCommandLineProcessor.OUTPUT_JDEPS_FILE_OPTION.description, + ) + + /** + * Label of the Bazel target being analyzed. + */ + val TARGET_LABEL: CompilerConfigurationKey = + CompilerConfigurationKey.create(JdepsGenCommandLineProcessor.TARGET_LABEL_OPTION.description) + + /** + * Label of the Bazel target being analyzed. + */ + val STRICT_KOTLIN_DEPS: CompilerConfigurationKey = + CompilerConfigurationKey.create( + JdepsGenCommandLineProcessor.STRICT_KOTLIN_DEPS_OPTION.description, + ) + + /** + * List of direct dependencies of the target. + */ + val DIRECT_DEPENDENCIES: CompilerConfigurationKey> = + CompilerConfigurationKey.create( + JdepsGenCommandLineProcessor.DIRECT_DEPENDENCIES_OPTION.description, + ) +} diff --git a/src/main/kotlin/io/bazel/kotlin/plugin/com_github_jetbrains_kotlin_1_8/jdeps/JdepsGenExtension.kt b/src/main/kotlin/io/bazel/kotlin/plugin/com_github_jetbrains_kotlin_1_8/jdeps/JdepsGenExtension.kt new file mode 100644 index 000000000..10dc91f12 --- /dev/null +++ b/src/main/kotlin/io/bazel/kotlin/plugin/com_github_jetbrains_kotlin_1_8/jdeps/JdepsGenExtension.kt @@ -0,0 +1,417 @@ +@file:Suppress("ktlint:standard:package-name") + +package io.bazel.kotlin.plugin.com_github_jetbrains_kotlin_1_8.jdeps + +import com.google.devtools.build.lib.view.proto.Deps +import com.intellij.openapi.project.Project +import com.intellij.psi.PsiElement +import io.bazel.kotlin.builder.utils.jars.JarOwner +import org.jetbrains.kotlin.analyzer.AnalysisResult +import org.jetbrains.kotlin.config.CompilerConfiguration +import org.jetbrains.kotlin.container.StorageComponentContainer +import org.jetbrains.kotlin.container.useInstance +import org.jetbrains.kotlin.descriptors.ClassDescriptor +import org.jetbrains.kotlin.descriptors.DeclarationDescriptor +import org.jetbrains.kotlin.descriptors.DeclarationDescriptorWithSource +import org.jetbrains.kotlin.descriptors.FunctionDescriptor +import org.jetbrains.kotlin.descriptors.ModuleDescriptor +import org.jetbrains.kotlin.descriptors.ParameterDescriptor +import org.jetbrains.kotlin.descriptors.PropertyDescriptor +import org.jetbrains.kotlin.descriptors.SourceElement +import org.jetbrains.kotlin.descriptors.impl.LocalVariableDescriptor +import org.jetbrains.kotlin.extensions.StorageComponentContainerContributor +import org.jetbrains.kotlin.load.java.descriptors.JavaMethodDescriptor +import org.jetbrains.kotlin.load.java.descriptors.JavaPropertyDescriptor +import org.jetbrains.kotlin.load.java.sources.JavaSourceElement +import org.jetbrains.kotlin.load.java.structure.impl.classFiles.BinaryJavaClass +import org.jetbrains.kotlin.load.java.structure.impl.classFiles.BinaryJavaField +import org.jetbrains.kotlin.load.kotlin.KotlinJvmBinarySourceElement +import org.jetbrains.kotlin.load.kotlin.VirtualFileKotlinClass +import org.jetbrains.kotlin.load.kotlin.getContainingKotlinJvmBinaryClass +import org.jetbrains.kotlin.platform.TargetPlatform +import org.jetbrains.kotlin.psi.KtDeclaration +import org.jetbrains.kotlin.psi.KtFile +import org.jetbrains.kotlin.resolve.BindingTrace +import org.jetbrains.kotlin.resolve.FunctionImportedFromObject +import org.jetbrains.kotlin.resolve.PropertyImportedFromObject +import org.jetbrains.kotlin.resolve.calls.checkers.CallChecker +import org.jetbrains.kotlin.resolve.calls.checkers.CallCheckerContext +import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall +import org.jetbrains.kotlin.resolve.calls.util.FakeCallableDescriptorForObject +import org.jetbrains.kotlin.resolve.checkers.DeclarationChecker +import org.jetbrains.kotlin.resolve.checkers.DeclarationCheckerContext +import org.jetbrains.kotlin.resolve.jvm.extensions.AnalysisHandlerExtension +import org.jetbrains.kotlin.types.KotlinType +import org.jetbrains.kotlin.types.TypeConstructor +import org.jetbrains.kotlin.types.typeUtil.supertypes +import java.io.BufferedOutputStream +import java.io.File +import java.nio.file.Paths + +/** + * Kotlin compiler extension that tracks classes (and corresponding classpath jars) needed to + * compile current kotlin target. Tracked data should include all classes whose changes could + * affect target's compilation out : direct class dependencies (i.e. external classes directly + * used), but also their superclass, interfaces, etc. + * The primary use of this extension is to improve Kotlin module compilation avoidance in build + * systems (like Buck). + * + * Tracking of classes and their ancestors is done via modules and class + * descriptors that got generated during analysis/resolve phase of Kotlin compilation. + * + * Note: annotation processors dependencies may need to be tracked separately (and may not need + * per-class ABI change tracking) + * + * @param project the current compilation project + * @param configuration the current compilation configuration + */ +class JdepsGenExtension( + val configuration: CompilerConfiguration, +) : + AnalysisHandlerExtension, StorageComponentContainerContributor { + + companion object { + + /** + * Returns the path of the jar archive file corresponding to the provided descriptor. + * + * @descriptor the descriptor, typically obtained from compilation analyze phase + * @return the path corresponding to the JAR where this class was loaded from, or null. + */ + fun getClassCanonicalPath(descriptor: DeclarationDescriptorWithSource): String? { + return when (val sourceElement: SourceElement = descriptor.source) { + is JavaSourceElement -> + if (sourceElement.javaElement is BinaryJavaClass) { + (sourceElement.javaElement as BinaryJavaClass).virtualFile.canonicalPath + } else if (sourceElement.javaElement is BinaryJavaField) { + val containingClass = (sourceElement.javaElement as BinaryJavaField).containingClass + if (containingClass is BinaryJavaClass) { + containingClass.virtualFile.canonicalPath + } else { + null + } + } else { + // Ignore Java source local to this module. + null + } + + is KotlinJvmBinarySourceElement -> + (sourceElement.binaryClass as VirtualFileKotlinClass).file.canonicalPath + + else -> null + } + } + + fun getClassCanonicalPath(typeConstructor: TypeConstructor): String? { + return (typeConstructor.declarationDescriptor as? DeclarationDescriptorWithSource)?.let { + getClassCanonicalPath( + it, + ) + } + } + } + + private val explicitClassesCanonicalPaths = mutableSetOf() + private val implicitClassesCanonicalPaths = mutableSetOf() + + override fun registerModuleComponents( + container: StorageComponentContainer, + platform: TargetPlatform, + moduleDescriptor: ModuleDescriptor, + ) { + container.useInstance( + ClasspathCollectingChecker(explicitClassesCanonicalPaths, implicitClassesCanonicalPaths), + ) + } + + class ClasspathCollectingChecker( + private val explicitClassesCanonicalPaths: MutableSet, + private val implicitClassesCanonicalPaths: MutableSet, + ) : CallChecker, DeclarationChecker { + + override fun check( + resolvedCall: ResolvedCall<*>, + reportOn: PsiElement, + context: CallCheckerContext, + ) { + when (val resultingDescriptor = resolvedCall.resultingDescriptor) { + is FunctionImportedFromObject -> { + collectTypeReferences(resultingDescriptor.containingObject.defaultType) + } + + is PropertyImportedFromObject -> { + collectTypeReferences(resultingDescriptor.containingObject.defaultType) + } + + is JavaMethodDescriptor -> { + getClassCanonicalPath( + (resultingDescriptor.containingDeclaration as ClassDescriptor).typeConstructor, + )?.let { explicitClassesCanonicalPaths.add(it) } + } + + is FunctionDescriptor -> { + resultingDescriptor.returnType?.let { addImplicitDep(it) } + resultingDescriptor.valueParameters.forEach { valueParameter -> + collectTypeReferences(valueParameter.type, isExplicit = false) + } + val virtualFileClass = + resultingDescriptor.getContainingKotlinJvmBinaryClass() as? VirtualFileKotlinClass + ?: return + explicitClassesCanonicalPaths.add(virtualFileClass.file.path) + } + + is ParameterDescriptor -> { + getClassCanonicalPath(resultingDescriptor)?.let { explicitClassesCanonicalPaths.add(it) } + } + + is FakeCallableDescriptorForObject -> { + collectTypeReferences(resultingDescriptor.type) + } + + is JavaPropertyDescriptor -> { + getClassCanonicalPath(resultingDescriptor)?.let { explicitClassesCanonicalPaths.add(it) } + } + + is PropertyDescriptor -> { + when (resultingDescriptor.containingDeclaration) { + is ClassDescriptor -> collectTypeReferences( + (resultingDescriptor.containingDeclaration as ClassDescriptor).defaultType, + ) + + else -> { + val virtualFileClass = + (resultingDescriptor).getContainingKotlinJvmBinaryClass() as? VirtualFileKotlinClass + ?: return + explicitClassesCanonicalPaths.add(virtualFileClass.file.path) + } + } + addImplicitDep(resultingDescriptor.type) + } + + else -> return + } + } + + override fun check( + declaration: KtDeclaration, + descriptor: DeclarationDescriptor, + context: DeclarationCheckerContext, + ) { + when (descriptor) { + is ClassDescriptor -> { + descriptor.typeConstructor.supertypes.forEach { + collectTypeReferences(it) + } + } + + is FunctionDescriptor -> { + descriptor.returnType?.let { collectTypeReferences(it) } + descriptor.valueParameters.forEach { valueParameter -> + collectTypeReferences(valueParameter.type) + } + descriptor.annotations.forEach { annotation -> + collectTypeReferences(annotation.type) + } + descriptor.extensionReceiverParameter?.value?.type?.let { + collectTypeReferences(it) + } + } + + is PropertyDescriptor -> { + collectTypeReferences(descriptor.type) + descriptor.annotations.forEach { annotation -> + collectTypeReferences(annotation.type) + } + descriptor.backingField?.annotations?.forEach { annotation -> + collectTypeReferences(annotation.type) + } + } + + is LocalVariableDescriptor -> { + collectTypeReferences(descriptor.type) + } + } + } + + private fun addImplicitDep(it: KotlinType) { + getClassCanonicalPath(it.constructor)?.let { implicitClassesCanonicalPaths.add(it) } + } + + private fun addExplicitDep(it: KotlinType) { + getClassCanonicalPath(it.constructor)?.let { explicitClassesCanonicalPaths.add(it) } + } + + /** + * Records direct and indirect references for a given type. Direct references are explicitly + * used in the code, e.g: a type declaration or a generic type declaration. Indirect references + * are other types required for compilation such as supertypes and interfaces of those explicit + * types. + */ + private fun collectTypeReferences( + kotlinType: KotlinType, + isExplicit: Boolean = true, + ) { + if (isExplicit) { + addExplicitDep(kotlinType) + } else { + addImplicitDep(kotlinType) + } + + kotlinType.supertypes().forEach { + addImplicitDep(it) + } + + collectTypeArguments(kotlinType, isExplicit) + } + + private fun collectTypeArguments( + kotlinType: KotlinType, + isExplicit: Boolean, + visitedKotlinTypes: MutableSet = mutableSetOf(), + ) { + visitedKotlinTypes.add(kotlinType) + kotlinType.arguments.map { it.type }.forEach { typeArgument -> + if (isExplicit) { + addExplicitDep(typeArgument) + } else { + addImplicitDep(typeArgument) + } + typeArgument.supertypes().forEach { addImplicitDep(it) } + if (!visitedKotlinTypes.contains(typeArgument)) { + collectTypeArguments(typeArgument, isExplicit, visitedKotlinTypes) + } + } + } + } + + override fun analysisCompleted( + project: Project, + module: ModuleDescriptor, + bindingTrace: BindingTrace, + files: Collection, + ): AnalysisResult? { + val directDeps = configuration.getList(JdepsGenConfigurationKeys.DIRECT_DEPENDENCIES) + val targetLabel = configuration.getNotNull(JdepsGenConfigurationKeys.TARGET_LABEL) + val explicitDeps = createDepsMap(explicitClassesCanonicalPaths) + + doWriteJdeps(directDeps, targetLabel, explicitDeps) + + doStrictDeps(configuration, targetLabel, directDeps, explicitDeps) + + return super.analysisCompleted(project, module, bindingTrace, files) + } + + /** + * Returns a map of jars to classes loaded from those jars. + */ + private fun createDepsMap(classes: Set): Map> { + val jarsToClasses = mutableMapOf>() + classes.forEach { + val parts = it.split("!/") + val jarPath = parts[0] + if (jarPath.endsWith(".jar")) { + jarsToClasses.computeIfAbsent(jarPath) { ArrayList() }.add(parts[1]) + } + } + return jarsToClasses + } + + private fun doWriteJdeps( + directDeps: MutableList, + targetLabel: String, + explicitDeps: Map>, + ) { + val implicitDeps = createDepsMap(implicitClassesCanonicalPaths) + + // Build and write out deps.proto + val jdepsOutput = configuration.getNotNull(JdepsGenConfigurationKeys.OUTPUT_JDEPS) + + val rootBuilder = Deps.Dependencies.newBuilder() + rootBuilder.success = true + rootBuilder.ruleLabel = targetLabel + + val unusedDeps = directDeps.subtract(explicitDeps.keys) + unusedDeps.forEach { jarPath -> + val dependency = Deps.Dependency.newBuilder() + dependency.kind = Deps.Dependency.Kind.UNUSED + dependency.path = jarPath + rootBuilder.addDependency(dependency) + } + + explicitDeps.forEach { (jarPath, _) -> + val dependency = Deps.Dependency.newBuilder() + dependency.kind = Deps.Dependency.Kind.EXPLICIT + dependency.path = jarPath + rootBuilder.addDependency(dependency) + } + + implicitDeps.keys.subtract(explicitDeps.keys).forEach { + val dependency = Deps.Dependency.newBuilder() + dependency.kind = Deps.Dependency.Kind.IMPLICIT + dependency.path = it + rootBuilder.addDependency(dependency) + } + + BufferedOutputStream(File(jdepsOutput).outputStream()).use { + it.write(rootBuilder.build().toByteArray()) + } + } + + private fun doStrictDeps( + compilerConfiguration: CompilerConfiguration, + targetLabel: String, + directDeps: MutableList, + explicitDeps: Map>, + ) { + when (compilerConfiguration.getNotNull(JdepsGenConfigurationKeys.STRICT_KOTLIN_DEPS)) { + "warn" -> checkStrictDeps(explicitDeps, directDeps, targetLabel) + "error" -> { + if (checkStrictDeps(explicitDeps, directDeps, targetLabel)) { + error( + "Strict Deps Violations - please fix", + ) + } + } + } + } + + /** + * Prints strict deps warnings and returns true if violations were found. + */ + private fun checkStrictDeps( + result: Map>, + directDeps: List, + targetLabel: String, + ): Boolean { + val missingStrictDeps = result.keys + .filter { !directDeps.contains(it) } + .map { JarOwner.readJarOwnerFromManifest(Paths.get(it)) } + + if (missingStrictDeps.isNotEmpty()) { + val missingStrictLabels = missingStrictDeps.mapNotNull { it.label } + + val open = "\u001b[35m\u001b[1m" + val close = "\u001b[0m" + + var command = + """ + $open ** Please add the following dependencies:$close + ${ + missingStrictDeps.map { it.label ?: it.jar }.joinToString(" ") + } to $targetLabel + """ + + if (missingStrictLabels.isNotEmpty()) { + command += """$open ** You can use the following buildozer command:$close + buildozer 'add deps ${ + missingStrictLabels.joinToString(" ") + }' $targetLabel + """ + } + + println(command.trimIndent()) + return true + } + return false + } +} diff --git a/src/main/kotlin/io/bazel/kotlin/plugin/jdeps/BUILD.bazel b/src/main/kotlin/io/bazel/kotlin/plugin/jdeps/BUILD.bazel deleted file mode 100644 index 60f5b07ee..000000000 --- a/src/main/kotlin/io/bazel/kotlin/plugin/jdeps/BUILD.bazel +++ /dev/null @@ -1,49 +0,0 @@ -# Copyright 2020 The Bazel Authors. All rights reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -load("@rules_java//java:defs.bzl", "java_binary") -load("//src/main/kotlin:bootstrap.bzl", "kt_bootstrap_library") -load("//kotlin/internal/utils:generate_jvm_service.bzl", "generate_jvm_service") - -# The compiler binary, this is co-located in the kotlin compiler classloader. -kt_bootstrap_library( - name = "jdeps-gen-lib", - srcs = glob(["*.kt"]), - visibility = ["//src:__subpackages__"], - deps = [ - "//src/main/kotlin/io/bazel/kotlin/builder/utils/jars", - "//src/main/protobuf:deps_java_proto", - "@com_github_jetbrains_kotlin//:kotlin-compiler", - "@kotlin_rules_maven//:com_google_protobuf_protobuf_java", - ], -) - -# services to integrate with the plugin. -generate_jvm_service( - name = "jdeps-gen-services", - services = { - "io.bazel.kotlin.plugin.jdeps.JdepsGenComponentRegistrar": "org.jetbrains.kotlin.compiler.plugin.ComponentRegistrar", - "io.bazel.kotlin.plugin.jdeps.JdepsGenCommandLineProcessor": "org.jetbrains.kotlin.compiler.plugin.CommandLineProcessor", - }, -) - -# The plugin binary. -java_binary( - name = "jdeps-gen", - visibility = ["//src:__subpackages__"], - runtime_deps = [ - ":jdeps-gen-lib", - ":jdeps-gen-services", - ], -) diff --git a/src/main/starlark/core/repositories/initialize.bzl b/src/main/starlark/core/repositories/initialize.bzl index 902d6004e..f7d8b01c4 100644 --- a/src/main/starlark/core/repositories/initialize.bzl +++ b/src/main/starlark/core/repositories/initialize.bzl @@ -17,10 +17,13 @@ load(":setup.bzl", "kt_configure") load( ":initialize.release.bzl", + _RULES_KOTLIN = "RULES_KOTLIN", _kotlinc_version = "kotlinc_version", _ksp_version = "ksp_version", _release_kotlin_repositories = "kotlin_repositories", ) +load("//src/main/starlark/core/repositories/kotlin:compiler.bzl", "kotlin_compiler_repository") +load("//src/main/starlark/core/repositories/kotlin:releases.bzl", "KOTLINC_INDEX") load(":versions.bzl", _versions = "versions") #exports @@ -39,3 +42,16 @@ def kotlin_repositories( """ _release_kotlin_repositories(compiler_release = compiler_release, ksp_compiler_release = ksp_compiler_release) kt_configure() + + # Provide versioned kotlinc repositories. These are used for compiling plugins. + for versioned_kotlinc in KOTLINC_INDEX.values(): + kotlin_compiler_repository( + name = versioned_kotlinc.repository_name, + urls = [ + url.format(version = versioned_kotlinc.release.version) + for url in versioned_kotlinc.release.url_templates + ], + sha256 = versioned_kotlinc.release.sha256, + kotlin_rules = _RULES_KOTLIN.workspace_name, + compiler_version = versioned_kotlinc.release.version, + ) diff --git a/src/main/starlark/core/repositories/initialize.release.bzl b/src/main/starlark/core/repositories/initialize.release.bzl index dccdfb492..af9ab6ff4 100644 --- a/src/main/starlark/core/repositories/initialize.release.bzl +++ b/src/main/starlark/core/repositories/initialize.release.bzl @@ -27,7 +27,15 @@ load( load("@bazel_tools//tools/build_defs/repo:utils.bzl", "maybe") load("//src/main/starlark/core/repositories/kotlin:compiler.bzl", "kotlin_compiler_repository") load(":ksp.bzl", "ksp_compiler_plugin_repository") -load(":versions.bzl", "version", _versions = "versions") +load( + ":versions.bzl", + _kotlinc_version = "kotlinc_version", + _ksp_version = "ksp_version", + _versions = "versions", +) + +kotlinc_version = _kotlinc_version +ksp_version = _ksp_version versions = _versions @@ -102,21 +110,3 @@ def kotlin_repositories( urls = ["https://github.com/bazelbuild/bazel-skylib/releases/download/%s/bazel-skylib-%s.tar.gz" % (versions.SKYLIB_VERSION, versions.SKYLIB_VERSION)], sha256 = versions.SKYLIB_SHA, ) - -def kotlinc_version(release, sha256): - return version( - version = release, - url_templates = [ - "https://github.com/JetBrains/kotlin/releases/download/v{version}/kotlin-compiler-{version}.zip", - ], - sha256 = sha256, - ) - -def ksp_version(release, sha256): - return version( - version = release, - url_templates = [ - "https://github.com/google/ksp/releases/download/{version}/artifacts.zip", - ], - sha256 = sha256, - ) diff --git a/src/main/starlark/core/repositories/kotlin/compiler.bzl b/src/main/starlark/core/repositories/kotlin/compiler.bzl index ab03814bb..9ff3ef9c8 100644 --- a/src/main/starlark/core/repositories/kotlin/compiler.bzl +++ b/src/main/starlark/core/repositories/kotlin/compiler.bzl @@ -1,3 +1,14 @@ +load("//src/main/starlark/core/repositories:versions.bzl", "versions") + +_CAPABILITIES_TEMPLATES = { + "legacy": "capabilities_legacy.bzl.com_github_jetbrains_kotlin.bazel", # keep first + "1.4": "capabilities_1.4.bzl.com_github_jetbrains_kotlin.bazel", + "1.5": "capabilities_1.5.bzl.com_github_jetbrains_kotlin.bazel", + "1.6": "capabilities_1.6.bzl.com_github_jetbrains_kotlin.bazel", + "1.7": "capabilities_1.7.bzl.com_github_jetbrains_kotlin.bazel", + "1.8": "capabilities_1.8.bzl.com_github_jetbrains_kotlin.bazel", +} + def _kotlin_compiler_impl(repository_ctx): """Creates the kotlinc repository.""" attr = repository_ctx.attr @@ -25,27 +36,28 @@ def _kotlin_compiler_impl(repository_ctx): executable = False, ) + repository_ctx.file( + "version.bzl", + """ + MAJOR_VERSION="%s" + """.strip() % versions.get_major(attr.compiler_version), + executable = False, + ) + def _get_capability_template(compiler_version, templates): + major_version = versions.get_major(compiler_version) + for ver, template in zip(_CAPABILITIES_TEMPLATES.keys(), templates): - if compiler_version.startswith(ver): + if ver == major_version: return template # After latest version - if compiler_version > _CAPABILITIES_TEMPLATES.keys()[-1]: + if major_version > _CAPABILITIES_TEMPLATES.keys()[-1]: templates[-1] # Legacy return templates[0] -_CAPABILITIES_TEMPLATES = { - "legacy": "capabilities_legacy.bzl.com_github_jetbrains_kotlin.bazel", # keep first - "1.4": "capabilities_1.4.bzl.com_github_jetbrains_kotlin.bazel", - "1.5": "capabilities_1.5.bzl.com_github_jetbrains_kotlin.bazel", - "1.6": "capabilities_1.6.bzl.com_github_jetbrains_kotlin.bazel", - "1.7": "capabilities_1.7.bzl.com_github_jetbrains_kotlin.bazel", - "1.8": "capabilities_1.8.bzl.com_github_jetbrains_kotlin.bazel", -} - kotlin_compiler_repository = repository_rule( implementation = _kotlin_compiler_impl, attrs = { diff --git a/src/main/starlark/core/repositories/kotlin/releases.bzl b/src/main/starlark/core/repositories/kotlin/releases.bzl new file mode 100644 index 000000000..73d9480e7 --- /dev/null +++ b/src/main/starlark/core/repositories/kotlin/releases.bzl @@ -0,0 +1,16 @@ +"""Kotlinc releases indexed by major versions.""" + +load("//src/main/starlark/core/repositories:versions.bzl", "versions") + +# Index of major kotlinc revision to calculated repository name and release. +KOTLINC_INDEX = { + major: struct( + # defining the expected repository name to reduce toil when updating. + repository_name = "com_github_jetbrains_kotlin_%s" % major.replace(".", "_"), + release = release, + ) + for (major, release) in [ + (versions.get_major(compiler_release.version), compiler_release) + for compiler_release in versions.KOTLIN_COMPILER_RELEASES + ] +} diff --git a/src/main/starlark/core/repositories/versions.bzl b/src/main/starlark/core/repositories/versions.bzl index 5ee0999de..9c7347b79 100644 --- a/src/main/starlark/core/repositories/versions.bzl +++ b/src/main/starlark/core/repositories/versions.bzl @@ -10,6 +10,24 @@ version = provider( }, ) +def kotlinc_version(release, sha256): + return version( + version = release, + url_templates = [ + "https://github.com/JetBrains/kotlin/releases/download/v{version}/kotlin-compiler-{version}.zip", + ], + sha256 = sha256, + ) + +def ksp_version(release, sha256): + return version( + version = release, + url_templates = [ + "https://github.com/google/ksp/releases/download/{version}/artifacts.zip", + ], + sha256 = sha256, + ) + def _use_repository(name, version, rule, **kwargs): http_archive_arguments = dict(kwargs) http_archive_arguments["sha256"] = version.sha256 @@ -19,6 +37,10 @@ def _use_repository(name, version, rule, **kwargs): maybe(rule, name = name, **http_archive_arguments) +def _get_major(version): + parts = version.split(".") + return ".".join(parts[:2]) + versions = struct( RULES_NODEJS_VERSION = "5.5.3", RULES_NODEJS_SHA = "f10a3a12894fc3c9bf578ee5a5691769f6805c4be84359681a785a0c12e8d2b6", @@ -61,13 +83,24 @@ versions = struct( ], sha256 = "2b3f6f674a944d25bb8d283c3539947bbe86074793012909a55de4b771f74bcc", ), - KOTLIN_CURRENT_COMPILER_RELEASE = version( - version = "1.8.21", - url_templates = [ - "https://github.com/JetBrains/kotlin/releases/download/v{version}/kotlin-compiler-{version}.zip", - ], + KOTLIN_CURRENT_COMPILER_RELEASE = kotlinc_version( + release = "1.8.21", sha256 = "6e43c5569ad067492d04d92c28cdf8095673699d81ce460bd7270443297e8fd7", ), + KOTLIN_COMPILER_RELEASES = [ + kotlinc_version( + release = "1.8.21", + sha256 = "6e43c5569ad067492d04d92c28cdf8095673699d81ce460bd7270443297e8fd7", + ), + kotlinc_version( + release = "1.7.22", + sha256 = "9db4b467743c1aea8a21c08e1c286bc2aeb93f14c7ba2037dbd8f48adc357d83", + ), + kotlinc_version( + release = "1.6.21", + sha256 = "632166fed89f3f430482f5aa07f2e20b923b72ef688c8f5a7df3aa1502c6d8ba", + ), + ], KSP_CURRENT_COMPILER_PLUGIN_RELEASE = version( version = "1.8.21-1.0.11", url_templates = [ @@ -111,4 +144,5 @@ versions = struct( sha256 = None, ), use_repository = _use_repository, + get_major = _get_major, )