[kotlin] Migrate Kotlin plugin build from custom made groovy scripts to common IDEA scripts

KTIJ-11539

Note that those artifacts are being preserved:
```
.idea/artifacts/AppCodeKotlinPlugin.xml
.idea/artifacts/KotlinPluginCommunity.xml
.idea/artifacts/MobilePlugin.xml
.idea/artifacts/kotlin_ocswift.xml
```
because otherwise we would block MobileIde/AppCode development

GitOrigin-RevId: 53acc627ed58962c104971ec6260704614c58218
This commit is contained in:
Nikita Bobko
2021-08-26 14:22:22 +02:00
committed by intellij-monorepo-bot
parent 2e77376916
commit cb44f4e8f0
16 changed files with 370 additions and 378 deletions

View File

@@ -7,6 +7,7 @@ import org.jetbrains.annotations.NotNull
import org.jetbrains.intellij.build.impl.BaseLayout
import org.jetbrains.intellij.build.impl.BuildHelper
import org.jetbrains.intellij.build.impl.PlatformLayout
import org.jetbrains.intellij.build.kotlin.KotlinPluginBuilder
import java.nio.file.Path
import java.nio.file.Paths
@@ -88,6 +89,7 @@ abstract class BaseIdeaProperties extends JetBrainsProductProperties {
"intellij.vcs.git.featuresTrainer",
"intellij.lombok",
"intellij.searchEverywhereMl",
KotlinPluginBuilder.MAIN_KOTLIN_PLUGIN_MODULE,
]
protected static final Map<String, String> CE_CLASS_VERSIONS = [
@@ -160,7 +162,6 @@ abstract class BaseIdeaProperties extends JetBrainsProductProperties {
productLayout.compatiblePluginsToIgnore = [
"intellij.java.plugin",
"kotlin.plugin"
]
additionalModulesToCompile = ["intellij.tools.jps.build.standalone"]
modulesToCompileTests = ["intellij.platform.jps.build"]
@@ -168,11 +169,6 @@ abstract class BaseIdeaProperties extends JetBrainsProductProperties {
isAntRequired = true
}
@Override
List<Path> getAdditionalPluginPaths(@NotNull BuildContext context) {
return [context.kotlinBinaries.setUpPlugin(context)]
}
@Override
@CompileStatic(TypeCheckingMode.SKIP)
void copyAdditionalFiles(BuildContext context, String targetDirectory) {

12
build/scripts/kotlin.gant Normal file
View File

@@ -0,0 +1,12 @@
// Copyright 2000-2021 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
import org.jetbrains.intellij.build.IdeaCommunityProperties
import org.jetbrains.intellij.build.impl.BuildUtils
import org.jetbrains.intellij.build.kotlin.KotlinPluginBuilder
import org.jetbrains.jps.idea.IdeaProjectLoader
target("artifact": "Build Kotlin IDE Artifacts") {
String home = new File(IdeaProjectLoader.guessHome(this)).absolutePath
BuildUtils.addUltimateBuildScriptsToClassPath(home, ant)
new KotlinPluginBuilder(home, home, new IdeaCommunityProperties(home)).build()
}

View File

@@ -244,14 +244,6 @@ class BuildOptions {
static final String VALIDATE_MODULES_STRUCTURE = "intellij.build.module.structure"
boolean validateModuleStructure = System.getProperty(VALIDATE_MODULES_STRUCTURE, "false").toBoolean()
/**
* Path to prebuilt Kotlin plugin (not zipped).
* Currently fully-fledged Kotlin plugin distribution is being on TeamCity only via kombo.gant script.
* If this path is not specified then distribution without LLDB debugger is going to be built locally (for tests only).
*/
static final String PREBUILT_KOTLIN_PLUGIN_PATH = "intellij.build.kotlin.plugin.path"
String prebuiltKotlinPluginPath = System.getProperty(PREBUILT_KOTLIN_PLUGIN_PATH)
static final String TARGET_OS = "intellij.build.target.os"
BuildOptions() {

View File

@@ -4,6 +4,7 @@ package org.jetbrains.intellij.build
import com.intellij.openapi.util.io.FileUtil
import groovy.transform.CompileStatic
import org.jetbrains.intellij.build.impl.PluginLayout
import org.jetbrains.intellij.build.kotlin.KotlinPluginBuilder
import org.jetbrains.intellij.build.python.PythonCommunityPluginModules
import org.jetbrains.jps.model.module.JpsModule
@@ -37,6 +38,7 @@ final class CommunityRepositoryModules {
mainJarName = "uiDesigner.jar"
withModule("intellij.java.guiForms.jps", "jps/java-guiForms-jps.jar")
},
KotlinPluginBuilder.kotlinPlugin(),
plugin("intellij.properties") {
withModule("intellij.properties.psi", "properties.jar")
withModule("intellij.properties.psi.impl", "properties.jar")
@@ -219,10 +221,10 @@ final class CommunityRepositoryModules {
plugin("intellij.android.plugin") {
directoryName = "android"
mainJarName = "android.jar"
withCustomVersion({pluginXmlFile, ideVersion ->
withCustomVersion({pluginXmlFile, ideVersion, _ ->
String text = Files.readString(pluginXmlFile)
def declaredVersion = text.substring(text.indexOf("<version>") + "<version>".length(), text.indexOf("</version>"))
return "$declaredVersion.$ideVersion"
return "$declaredVersion.$ideVersion".toString()
})
withModule("intellij.android.common", "android-common.jar")

View File

@@ -3,7 +3,6 @@ package org.jetbrains.intellij.build
import groovy.transform.CompileStatic
import org.jetbrains.intellij.build.impl.JpsCompilationData
import org.jetbrains.intellij.build.kotlin.KotlinBinaries
import org.jetbrains.jps.model.JpsModel
import org.jetbrains.jps.model.JpsProject
import org.jetbrains.jps.model.module.JpsModule
@@ -21,7 +20,6 @@ interface CompilationContext {
JpsModel getProjectModel()
JpsCompilationData getCompilationData()
KotlinBinaries getKotlinBinaries()
/**
* @return directory with compiled project classes, url attribute value of output tag from .idea/misc.xml by default

View File

@@ -56,11 +56,6 @@ abstract class DelegatingCompilationContext implements CompilationContext {
return delegate.getCompilationData()
}
@Override
KotlinBinaries getKotlinBinaries() {
return delegate.getKotlinBinaries()
}
@Override
File getProjectOutputDirectory() {
return delegate.getProjectOutputDirectory()

View File

@@ -160,11 +160,6 @@ final class BuildContextImpl extends BuildContext {
compilationContext.compilationData
}
@Override
KotlinBinaries getKotlinBinaries() {
return compilationContext.kotlinBinaries
}
@Override
File getProjectOutputDirectory() {
return compilationContext.projectOutputDirectory

View File

@@ -224,14 +224,6 @@ final class BuildTasksImpl extends BuildTasks {
}
}
//todo remove this when KTIJ-11539 is fixed; currently if we add kotlin.idea module to the classpath as a transitive dependency of some other module,
// it'll cause conflicts with Kotlin plugin loaded from JAR
String pathToIgnore = new File(context.projectOutputDirectory, "production/kotlin.idea").absolutePath
if (ideClasspath.remove(pathToIgnore)) {
context.messages.debug(" remove $pathToIgnore from classpath to avoid conflicts")
}
List<String> jvmArgs = new ArrayList<>(BuildUtils.propertiesToJvmArgs(new HashMap<String, Object>([
"idea.home.path" : context.paths.projectHome,
"idea.system.path" : "${FileUtilRt.toSystemIndependentName(tempDir.toString())}/system",

View File

@@ -45,7 +45,6 @@ final class CompilationContextImpl implements CompilationContext {
final Map<String, String> oldToNewModuleName
final Map<String, String> newToOldModuleName
JpsCompilationData compilationData
KotlinBinaries kotlinBinaries
@SuppressWarnings("GrUnresolvedAccess")
@CompileDynamic
@@ -69,12 +68,12 @@ final class CompilationContextImpl implements CompilationContext {
def gradleJdk = toCanonicalPath(JdkUtils.computeJdkHome(messages, '1.8', null, "JDK_18_x64"))
GradleRunner gradle = new GradleRunner(dependenciesProjectDir, projectHome, messages, gradleJdk)
projectHome = toCanonicalPath(projectHome)
def kotlinBinaries = new KotlinBinaries(projectHome, communityHome, options, messages)
def kotlinBinaries = new KotlinBinaries(communityHome, options, messages)
kotlinBinaries.setUpCompilerIfRequired(gradle, ant)
def model = loadProject(projectHome, kotlinBinaries, messages)
def jdkHome = defineJavaSdk(model, projectHome, options, messages)
def oldToNewModuleName = loadModuleRenamingHistory(projectHome, messages) + loadModuleRenamingHistory(communityHome, messages)
def context = new CompilationContextImpl(ant, gradle, model, communityHome, projectHome, jdkHome, kotlinBinaries, messages, oldToNewModuleName,
def context = new CompilationContextImpl(ant, gradle, model, communityHome, projectHome, jdkHome, messages, oldToNewModuleName,
buildOutputRootEvaluator, options)
context.prepareForBuild()
messages.debugLogPath = context.paths.logDir.resolve("debug.log")
@@ -146,7 +145,7 @@ final class CompilationContextImpl implements CompilationContext {
}
private CompilationContextImpl(AntBuilder ant, GradleRunner gradle, JpsModel model, String communityHome,
String projectHome, String jdkHome, KotlinBinaries kotlinBinaries, BuildMessages messages,
String projectHome, String jdkHome, BuildMessages messages,
Map<String, String> oldToNewModuleName,
BiFunction<JpsProject, BuildMessages, String> buildOutputRootEvaluator, BuildOptions options) {
this.ant = ant
@@ -161,13 +160,12 @@ final class CompilationContextImpl implements CompilationContext {
String buildOutputRoot = options.outputRootPath ?: buildOutputRootEvaluator.apply(project, messages)
Path logDir = options.logPath != null ? Path.of(options.logPath) : Path.of(buildOutputRoot, "log")
this.paths = new BuildPathsImpl(communityHome, projectHome, buildOutputRoot, jdkHome, logDir)
this.kotlinBinaries = kotlinBinaries
}
CompilationContextImpl createCopy(AntBuilder ant, BuildMessages messages, BuildOptions options,
BiFunction<JpsProject, BuildMessages, String> buildOutputRootEvaluator) {
def copy = new CompilationContextImpl(ant, gradle, projectModel, paths.communityHome, paths.projectHome, paths.jdkHome,
kotlinBinaries, messages, oldToNewModuleName, buildOutputRootEvaluator, options)
messages, oldToNewModuleName, buildOutputRootEvaluator, options)
copy.compilationData = compilationData
return copy
}

View File

@@ -735,6 +735,13 @@ final class DistributionJARsBuilder {
}
}
for (Pair<String, ResourcesGenerator> item : plugin.moduleOutputPatches) {
File resources = item.second.generateResources(buildContext)
if (resources != null) {
layoutBuilder.patchModuleOutput(item.first, resources.toPath())
}
}
Path pluginDir = targetDirectory.resolve(getActualPluginDirectoryName(plugin, buildContext))
processPluginLayout(plugin, layoutBuilder, pluginDir, generatedResources, parentMapping, true)
if (!plugin.pathsToScramble.isEmpty()) {
@@ -798,7 +805,7 @@ final class DistributionJARsBuilder {
? buildContext.buildNumber + ".${new SimpleDateFormat('yyyyMMdd').format(new Date())}"
: buildContext.buildNumber
def pluginVersion = plugin.versionEvaluator.apply(patchedPluginXmlFile, defaultPluginVersion)
def pluginVersion = plugin.versionEvaluator.evaluate(patchedPluginXmlFile, defaultPluginVersion, buildContext)
Pair<String, String> sinceUntil = getCompatiblePlatformVersionRange(compatibleBuildRange, buildContext.buildNumber)
@@ -811,6 +818,7 @@ final class DistributionJARsBuilder {
pluginsToPublish.contains(plugin),
plugin.retainProductDescriptorForBundledPlugin,
)
plugin.pluginXmlPatcher.accept(patchedPluginXmlFile)
}
catch (Throwable t) {
throw new RuntimeException("Could not patch $pluginXmlPath: ${t.message}", t)

View File

@@ -11,6 +11,7 @@ import org.jetbrains.intellij.build.ResourcesGenerator
import java.nio.file.Path
import java.util.function.BiFunction
import java.util.function.BiPredicate
import java.util.function.Consumer
/**
* Describes layout of a plugin in the product distribution
@@ -19,7 +20,9 @@ import java.util.function.BiPredicate
final class PluginLayout extends BaseLayout {
final String mainModule
String directoryName
BiFunction<Path, String, String> versionEvaluator = { pluginXmlFile, ideVersion -> ideVersion } as BiFunction<Path, String, String>
VersionEvaluator versionEvaluator = { pluginXmlFile, ideVersion, context -> ideVersion } as VersionEvaluator
Consumer<Path> pluginXmlPatcher = { } as Consumer<Path>
List<Pair<String, ResourcesGenerator>> moduleOutputPatches = []
boolean directoryNameSetExplicitly
PluginBundlingRestrictions bundlingRestrictions
final List<String> pathsToScramble = new ArrayList<>()
@@ -138,6 +141,23 @@ final class PluginLayout extends BaseLayout {
return bundlingRestrictions
}
/**
* @param binPathRelativeToCommunity path to resource file or directory relative to the intellij-community repo root
* @param outputPath target path relative to the plugin root directory
*/
def withBin(String binPathRelativeToCommunity, String outputPath, boolean skipIfDoesntExist = false) {
withGeneratedResources(new ResourcesGenerator() {
@Override
File generateResources(BuildContext context) {
def file = context.paths.communityHomeDir.resolve(binPathRelativeToCommunity).toFile()
if (!skipIfDoesntExist && !file.exists()) {
throw new IllegalStateException("'$file' doesn't exist")
}
return file.exists() ? file : null
}
}, outputPath)
}
/**
* @param resourcePath path to resource file or directory relative to the plugin's main module content root
* @param relativeOutputPath target path relative to the plugin root directory
@@ -170,17 +190,23 @@ final class PluginLayout extends BaseLayout {
}
/**
* By default, version of a plugin is equal to the build number of the IDE it's built with. This method allows to specify custom version evaluator.
* In {@linkplain BiFunction}:
* <ol>
* <li> the first {@linkplain File} argument is the plugin.xml file.
* <li> the second {@linkplain String} argument is the default version (build number of the IDE).
* </ol>
* Patches module output with content produced {@code generator}
*/
void withCustomVersion(BiFunction<Path, String, String> versionEvaluator) {
void withModuleOutputPatches(String moduleName, ResourcesGenerator generator) {
layout.moduleOutputPatches.add(Pair.create(moduleName, generator))
}
/**
* By default, version of a plugin is equal to the build number of the IDE it's built with. This method allows to specify custom version evaluator.
*/
void withCustomVersion(VersionEvaluator versionEvaluator) {
layout.versionEvaluator = versionEvaluator
}
void withPluginXmlPatcher(Consumer<Path> pluginXmlPatcher) {
layout.pluginXmlPatcher = pluginXmlPatcher
}
/**
* @deprecated localizable resources are always put to the module JAR, so there is no need to call this method anymore
*/
@@ -255,4 +281,8 @@ final class PluginLayout extends BaseLayout {
layout.scrambleClasspathFilter = filter
}
}
interface VersionEvaluator {
String evaluate(Path pluginXml, String ideBuildVersion, BuildContext context)
}
}

View File

@@ -659,7 +659,6 @@ class TestingTasksImpl extends TestingTasks {
if (!dependenciesInstalled) {
dependenciesInstalled = true
context.gradle.run('Setting up testing dependencies', 'setupBundledMaven')
context.kotlinBinaries.setUpPlugin(context, true)
}
}

View File

@@ -1,47 +1,30 @@
// Copyright 2000-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package org.jetbrains.intellij.build.kotlin
import com.intellij.openapi.util.JDOMUtil
import com.intellij.openapi.util.SystemInfo
import com.intellij.openapi.util.io.FileUtil
import com.intellij.util.xmlb.XmlSerializer
import groovy.transform.CompileStatic
import org.apache.tools.ant.BuildException
import org.jetbrains.intellij.build.BuildMessages
import org.jetbrains.intellij.build.BuildOptions
import org.jetbrains.intellij.build.CompilationContext
import org.jetbrains.intellij.build.GradleRunner
import org.jetbrains.intellij.build.impl.BuildUtils
import org.jetbrains.intellij.build.impl.CompilationContextImpl
import org.jetbrains.intellij.build.impl.CompilationTasksImpl
import org.jetbrains.jps.model.serialization.JpsLoaderBase
import org.jetbrains.jps.model.serialization.JpsMacroExpander
import org.jetbrains.jps.model.serialization.PathMacroUtil
import org.jetbrains.jps.model.serialization.artifact.ArtifactState
import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.Paths
import java.nio.file.StandardCopyOption
/**
* Sets up both Kotlin compiler (downloaded from Marketplace) and Kotlin plugin (built from source) binaries
* Sets up Kotlin compiler (downloaded from Marketplace) which is required for JPS to compile the repository
*/
@CompileStatic
final class KotlinBinaries {
static final String SET_UP_COMPILER_GRADLE_TASK = 'setupKotlinCompiler'
private static final String PLUGIN_ARTIFACT_NAME = 'KotlinPlugin'
private final String projectHome
private final String communityHome
private final BuildMessages messages
private final BuildOptions options
final String compilerHome
KotlinBinaries(String projectHome, String communityHome, BuildOptions options, BuildMessages messages) {
KotlinBinaries(String communityHome, BuildOptions options, BuildMessages messages) {
this.options = options
this.messages = messages
this.communityHome = communityHome
this.projectHome = projectHome
def compilerHome = "$communityHome/build/dependencies/build/kotlin-compiler/Kotlin"
this.compilerHome = FileUtil.toSystemIndependentName(new File(compilerHome).canonicalPath)
}
@@ -59,12 +42,17 @@ final class KotlinBinaries {
if (new File(kotlinPluginLibPath).exists() && new File(kotlincLibPath).exists()) {
["jps/kotlin-jps-plugin.jar", "kotlin-plugin.jar", "kotlin-reflect.jar", "kotlin-common.jar"].each {
def completePath = "$kotlinPluginLibPath/$it"
if (new File(completePath).exists()) {
BuildUtils.addToJpsClassPath(completePath, ant)
if (!new File(completePath).exists()) {
throw new IllegalStateException("KotlinBinaries: '$completePath' doesn't exist")
}
BuildUtils.addToJpsClassPath(completePath, ant)
}
["kotlin-stdlib.jar"].each {
BuildUtils.addToJpsClassPath("$kotlincLibPath/$it", ant)
def completePath = "$kotlincLibPath/$it"
if (!new File(completePath).exists()) {
throw new IllegalStateException("KotlinBinaries: '$completePath' doesn't exist")
}
BuildUtils.addToJpsClassPath(completePath, ant)
}
}
else {
@@ -92,83 +80,4 @@ final class KotlinBinaries {
}
}
}
private volatile Path pluginArtifact
Path setUpPlugin(CompilationContext context, boolean isTestBuild = context.options.isTestBuild) {
if (pluginArtifact == null) {
synchronized (this) {
if (pluginArtifact != null) return pluginArtifact
if (context.options.prebuiltKotlinPluginPath != null) {
pluginArtifact = Paths.get(context.options.prebuiltKotlinPluginPath)
}
else {
pluginArtifact = buildPluginForTests(context)
if (!isTestBuild) {
def problem = "Prebuilt Kotlin plugin artifact is required to build an installer, " +
"please specify it in $BuildOptions.PREBUILT_KOTLIN_PLUGIN_PATH"
println("##teamcity[buildProblem description='$problem']")
}
}
copyPluginArtifactToProjectOutput()
}
}
return pluginArtifact
}
private static Path buildPluginForTests(CompilationContext context) {
context.messages.block("Building Kotlin plugin for tests") {
buildPluginForTests(context, KotlinPluginKind.IJ, PLUGIN_ARTIFACT_NAME)
?: buildPluginForTests(context, KotlinPluginKind.IJ_CE, "${PLUGIN_ARTIFACT_NAME}Community")
}
}
private static Path buildPluginForTests(CompilationContext context, KotlinPluginKind kind, String unzippedPath) {
def pluginZip = kind.build(context)
if (pluginZip == null) return null
def pluginUnzipped = Paths.get(pluginZip.outputPath)
.parent.resolve(unzippedPath)
.toAbsolutePath().normalize()
return pluginUnzipped
}
/**
* Copy artifact to path expected at least by:
* * .idea/libraries/KotlinPlugin.xml;
* * startup performance tests;
* * Code With Me tests;
* * Fleet tests.
*/
private void copyPluginArtifactToProjectOutput() {
def stream = Files.walk(pluginArtifact)
try {
stream.forEach {
if (Files.isRegularFile(it)) {
def destination = projectOutputPath().resolve(pluginArtifact.relativize(it))
Files.createDirectories(destination.parent)
Files.copy(it, destination, StandardCopyOption.REPLACE_EXISTING)
}
}
}
finally {
stream.close()
}
}
private Path projectOutputPath() {
def projectPath = Paths.get(projectHome)
def pluginArtifactXml = projectPath.resolve(".idea/artifacts/${PLUGIN_ARTIFACT_NAME}.xml")
if (!Files.exists(pluginArtifactXml)) {
pluginArtifactXml = projectPath.resolve(".idea/artifacts/${PLUGIN_ARTIFACT_NAME}Community.xml")
}
if (!Files.exists(pluginArtifactXml)) {
throw new BuildException("$pluginArtifactXml doesn't exist")
}
def rootElement = JpsLoaderBase.tryLoadRootElement(pluginArtifactXml)
def artifactElement = JDOMUtil.getChildren(rootElement, "artifact").first()
def state = XmlSerializer.deserialize(artifactElement, ArtifactState.class)
def macroExpander = new JpsMacroExpander([:])
macroExpander.addFileHierarchyReplacements(PathMacroUtil.PROJECT_DIR_MACRO_NAME, projectPath.toFile())
return Paths.get(macroExpander.substitute(state.outputPath, SystemInfo.isFileSystemCaseSensitive))
}
}

View File

@@ -1,121 +0,0 @@
// Copyright 2000-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package org.jetbrains.intellij.build.kotlin
import groovy.transform.CompileStatic
import org.jetbrains.intellij.build.CompilationContext
import org.jetbrains.intellij.build.CompilationTasks
import org.jetbrains.jps.model.artifact.JpsArtifact
import org.jetbrains.jps.model.artifact.JpsArtifactService
import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.Paths
@CompileStatic
final class KotlinPluginArtifact {
private final CompilationContext context
KotlinPluginArtifact(CompilationContext context) {
this.context = context
}
JpsArtifact build(KotlinPluginKind kind,
String sinceBuild, String untilBuild,
String version = kind.version) {
def tasks = CompilationTasks.create(context)
patchPluginXml(version, sinceBuild, untilBuild, kind)
tasks.buildProjectArtifacts([kind.jpsArtifactIdentifier])
return JpsArtifactService.instance.getArtifacts(context.project).find {
it.name == kind.jpsArtifactIdentifier
}
}
private static String replace(String oldText, String regex, String newText) {
String result = oldText.replaceFirst(regex, newText)
if (result == oldText && !oldText.contains(newText)) {
throw new IllegalStateException("Cannot find '$regex' in '$oldText'")
}
return result
}
Path jpsOutPluginXml(KotlinPluginKind kind) {
CompilationTasks.create(context).compileModules([kind.pluginXmlModuleName])
def pluginXml = "production/${kind.pluginXmlModuleName}/META-INF/plugin.xml"
Path jpsOutPluginXml = Paths.get("$context.projectOutputDirectory/$pluginXml")
if (!Files.exists(jpsOutPluginXml)) {
throw new IllegalStateException("jpsOutPluginXml=$jpsOutPluginXml doesn't exist!")
}
return jpsOutPluginXml
}
Path srcPluginXml(KotlinPluginKind kind) {
Path srcPluginXml = Paths.get("$context.paths.projectHome/${kind.pluginXmlContentRoot}/META-INF/plugin.xml")
if (!Files.exists(srcPluginXml)) {
def pluginXmlContentRoot = kind.pluginXmlContentRoot
if (pluginXmlContentRoot.startsWith('community/')) {
pluginXmlContentRoot = kind.pluginXmlContentRoot.replaceFirst('community', '')
}
srcPluginXml = context.paths.communityHomeDir.resolve("$pluginXmlContentRoot/META-INF/plugin.xml")
}
if (!Files.exists(srcPluginXml)) {
throw new IllegalStateException("srcPluginXml=$srcPluginXml doesn't exist!")
}
return srcPluginXml
}
private void patchPluginXml(String kotlinPluginVersion, String sinceBuild, String untilBuild, KotlinPluginKind kind) {
def jpsOutPluginXml = jpsOutPluginXml(kind)
switch (kind) {
case KotlinPluginKind.AC_KMM:
def extendedBuild = sinceBuild.substring(0, sinceBuild.lastIndexOf('.'))
if (!sinceBuild.matches("\\d+\\.\\d+")) {
sinceBuild = extendedBuild
}
untilBuild = extendedBuild + ".*"
break
}
String versionText = "<version>${kotlinPluginVersion}</version>"
String replaceText
switch (kind) {
case KotlinPluginKind.ROBOSCOPE:
replaceText = versionText
break
default:
replaceText = """
${versionText}
<idea-version since-build="${sinceBuild}" until-build="${untilBuild}"/>
""".stripIndent().trim()
break
}
def newText = replace(Files.readString(jpsOutPluginXml), "<version>.*?</version>", replaceText)
switch (kind) {
case KotlinPluginKind.IJ:
case KotlinPluginKind.IJ_CE:
newText = replace(
newText,
"<!-- IJ/AS-INCOMPATIBLE-PLACEHOLDER -->",
"<incompatible-with>com.intellij.modules.androidstudio</incompatible-with>"
)
break
case KotlinPluginKind.AS:
newText = replace(
newText,
"<!-- IJ/AS-DEPENDENCY-PLACEHOLDER -->",
"<plugin id=\"com.intellij.modules.androidstudio\"/>"
)
break
case KotlinPluginKind.MI:
case KotlinPluginKind.AC_KMM:
case KotlinPluginKind.ROBOSCOPE:
break
default:
throw new IllegalStateException("Unknown kind = $kind")
}
Files.writeString(jpsOutPluginXml, newText)
}
}

View File

@@ -0,0 +1,291 @@
// Copyright 2000-2021 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package org.jetbrains.intellij.build.kotlin
import com.intellij.util.io.Decompressor
import groovy.transform.CompileStatic
import org.jetbrains.intellij.build.BuildContext
import org.jetbrains.intellij.build.BuildTasks
import org.jetbrains.intellij.build.ProductProperties
import org.jetbrains.intellij.build.ResourcesGenerator
import org.jetbrains.intellij.build.impl.PluginLayout
import org.jetbrains.jps.model.library.JpsLibrary
import org.jetbrains.jps.model.library.JpsOrderRootType
import org.jetbrains.jps.model.library.JpsRepositoryLibraryType
import java.nio.file.Files
import java.nio.file.Path
import java.util.function.Consumer
@CompileStatic
class KotlinPluginBuilder {
/**
* Module which contains META-INF/plugin.xml
*/
public static String MAIN_KOTLIN_PLUGIN_MODULE = "kotlin.plugin"
private final String communityHome
private final String home
private final ProductProperties properties
private static List<String> MODULES = [
"kotlin.core",
"kotlin.idea",
"kotlin.fir.frontend-independent",
"kotlin.line-indent-provider",
"kotlin.jvm",
"kotlin.refIndex",
"kotlin.compiler-plugins.scripting-ide-services",
"kotlin.compiler-plugins.parcelize.common",
"kotlin.compiler-plugins.parcelize.gradle",
"kotlin.compiler-plugins.allopen.common",
"kotlin.compiler-plugins.allopen.gradle",
"kotlin.compiler-plugins.allopen.maven",
"kotlin.compiler-plugins.annotation-based-compiler-support.common",
"kotlin.compiler-plugins.annotation-based-compiler-support.gradle",
"kotlin.compiler-plugins.annotation-based-compiler-support.maven",
"kotlin.compiler-plugins.kapt",
"kotlin.compiler-plugins.kotlinx-serialization.common",
"kotlin.compiler-plugins.kotlinx-serialization.gradle",
"kotlin.compiler-plugins.kotlinx-serialization.maven",
"kotlin.compiler-plugins.noarg.common",
"kotlin.compiler-plugins.noarg.gradle",
"kotlin.compiler-plugins.noarg.maven",
"kotlin.compiler-plugins.sam-with-receiver.common",
"kotlin.compiler-plugins.sam-with-receiver.gradle",
"kotlin.compiler-plugins.sam-with-receiver.maven",
"kotlin.compiler-plugins.scripting",
"kotlin.maven",
"kotlin.gradle.gradle-tooling",
"kotlin.gradle.gradle-idea",
"kotlin.gradle.gradle-java",
"kotlin.gradle.gradle-native",
"kotlin.native",
"kotlin.grazie",
"kotlin.junit",
"kotlin.testng",
"kotlin.formatter",
"kotlin.repl",
"kotlin.git",
"kotlin.injection",
"kotlin.scripting",
"kotlin.coverage",
"kotlin.ml-completion",
"kotlin.groovy",
"kotlin.copyright",
"kotlin.spellchecker",
"kotlin.jvm-decompiler",
"kotlin.properties",
"kotlin.j2k.services",
"kotlin.j2k.idea",
"kotlin.j2k.old",
"kotlin.j2k.new",
"kotlin.project-wizard.cli",
"kotlin.project-wizard.core",
"kotlin.project-wizard.idea",
"kotlin.project-wizard.maven",
"kotlin.project-wizard.gradle",
"kotlin.jvm-debugger.util",
"kotlin.jvm-debugger.core",
"kotlin.jvm-debugger.evaluation",
"kotlin.jvm-debugger.coroutines",
"kotlin.jvm-debugger.sequence",
"kotlin.jvm-debugger.eval4j",
"kotlin.uast.uast-kotlin",
"kotlin.uast.uast-kotlin-idea",
"kotlin.i18n",
"kotlin.project-model",
]
private static List<String> LIBRARIES = [
"kotlinc.android-extensions-compiler-plugin",
"kotlinc.allopen-compiler-plugin",
"kotlinc.noarg-compiler-plugin",
"kotlinc.sam-with-receiver-compiler-plugin",
"kotlinc.kotlinx-serialization-compiler-plugin",
"kotlinc.parcelize-compiler-plugin",
"kotlinc.kotlin-script-util",
"kotlin-script-runtime",
"kotlinc.kotlin-scripting-compiler",
"kotlinc.kotlin-gradle-statistics",
]
KotlinPluginBuilder(String communityHome, String home, ProductProperties properties) {
this.communityHome = communityHome
this.home = home
this.properties = properties
}
static PluginLayout kotlinPlugin() {
return PluginLayout.plugin(MAIN_KOTLIN_PLUGIN_MODULE) {
directoryName = "Kotlin"
mainJarName = "kotlin-plugin.jar"
boolean isUltimate
try {
Class.forName("org.jetbrains.intellij.build.IdeaUltimateProperties")
isUltimate = true
} catch (ClassNotFoundException ignored) {
isUltimate = false
}
KotlinPluginKind kind = KotlinPluginKind.valueOf(Objects.requireNonNullElse(System.getProperty("kotlin.plugin.kind"), "IJ"))
for (String moduleName : MODULES) {
withModule(moduleName)
}
for (String library : LIBRARIES) {
withProjectLibraryUnpackedIntoJar(library, mainJarName)
}
if (isUltimate && kind == KotlinPluginKind.IJ) {
withModule("kotlin-ultimate.common-native")
withModule("kotlin-ultimate.common-noncidr-native")
withModule("kotlin-ultimate.javascript.debugger")
withModule("kotlin-ultimate.javascript.nodeJs")
withModule("kotlin-ultimate.ultimate-plugin")
withModule("kotlin-ultimate.ultimate-native")
}
String jpsPluginJar = "jps/kotlin-jps-plugin.jar"
withModule("kotlin.jps-plugin", jpsPluginJar)
withProjectLibraryUnpackedIntoJar("kotlinc.compiler-components-for-jps", jpsPluginJar)
String kotlincKotlinCompiler = "kotlinc.kotlin-compiler"
withProjectLibrary(kotlincKotlinCompiler, "", true)
withModuleOutputPatches(MAIN_KOTLIN_PLUGIN_MODULE, new ResourcesGenerator() {
@Override
File generateResources(BuildContext context) {
JpsLibrary library = context.project.libraryCollection.findLibrary(kotlincKotlinCompiler)
List<File> jars = library.getFiles(JpsOrderRootType.COMPILED)
if (jars.size() != 1) {
throw new IllegalStateException("$kotlincKotlinCompiler is expected to have only one jar")
}
def extractedDir = context.paths.tempDir.resolve("$kotlincKotlinCompiler-extracted")
new Decompressor.Zip(jars[0]).extract(extractedDir)
def compilerExtensions = context.paths.tempDir.resolve("$kotlincKotlinCompiler-compiler-extensions")
def prefix = "META-INF/extensions"
compilerExtensions.resolve(prefix).toFile().mkdirs()
for (File file : extractedDir.resolve(prefix).toFile().listFiles()) {
Files.copy(file.toPath(), compilerExtensions.resolve(prefix).resolve(file.name))
}
return compilerExtensions.toFile()
}
})
withModule("kotlin.jps-common", "kotlin-jps-common.jar")
withModule("kotlin.common", "kotlin-common.jar")
withProjectLibrary("kotlin-reflect", "", true)
withProjectLibrary("kotlin-stdlib-jdk8", "", true)
withProjectLibrary("javaslang")
withProjectLibrary("kotlinx-collections-immutable-jvm")
withProjectLibrary("javax-inject")
withProjectLibrary("kotlinx-coroutines-jdk8")
withProjectLibrary("completion-ranking-kotlin")
withGeneratedResources(new ResourcesGenerator() {
@Override
File generateResources(BuildContext context) {
def distLibName = "kotlinc.kotlin-dist"
JpsLibrary library = context.project.libraryCollection.findLibrary(distLibName)
List<File> jars = library.getFiles(JpsOrderRootType.COMPILED)
if (jars.size() != 1) {
throw new IllegalStateException("$distLibName is expected to have only one jar")
}
def extractedDist = context.paths.tempDir.resolve("kotlinc-dist")
new Decompressor.Zip(jars[0]).extract(extractedDist)
return extractedDist.toFile()
}
}, "kotlinc")
withCustomVersion(new PluginLayout.VersionEvaluator() {
@Override
String evaluate(Path pluginXml, String ideBuildVersion, BuildContext context) {
def index = ideBuildVersion.indexOf(".")
assert index > 0
def major = ideBuildVersion.substring(0, index)
def minor = ideBuildVersion.substring(index + 1)
def kotlinVersion = context.project.libraryCollection.libraries
.find { it.name.startsWith("kotlinc.") && it.type instanceof JpsRepositoryLibraryType }
?.asTyped(JpsRepositoryLibraryType.INSTANCE)
?.properties?.data?.version
assert kotlinVersion != null
return "${major}-${kotlinVersion}-${kind}${minor}"
}
})
withPluginXmlPatcher(new Consumer<Path>() {
@Override
void accept(Path path) {
String sinceBuild = System.getProperty("kotlin.plugin.since")
String untilBuild = System.getProperty("kotlin.plugin.until")
String text = Files.readString(path)
if (sinceBuild != null && untilBuild != null) {
// In kt-branches we have own since and until versions
text = replace(text, "<idea-version.*?\\/>", "<idea-version since-build=\"${sinceBuild}\" until-build=\"${untilBuild}\"/>")
}
switch (kind) {
case KotlinPluginKind.IJ:
case KotlinPluginKind.MI:
text = replace(
text,
"<!-- IJ/AS-INCOMPATIBLE-PLACEHOLDER -->",
"<incompatible-with>com.intellij.modules.androidstudio</incompatible-with>"
)
break
case KotlinPluginKind.AS:
text = replace(
text,
"<!-- IJ/AS-DEPENDENCY-PLACEHOLDER -->",
"<plugin id=\"com.intellij.modules.androidstudio\"/>"
)
break
case KotlinPluginKind.AC_KMM:
break
default:
throw new IllegalStateException("Unknown kind = $kind")
}
Files.writeString(path, text)
}
})
if (kind == KotlinPluginKind.IJ && isUltimate) {
// TODO KTIJ-11539 change to `System.getenv("TEAMCITY_VERSION") == null` later but make sure
// that `IdeaUltimateBuildTest.testBuild` passes on TeamCity
boolean skipIfDoesntExist = true
// Use 'DownloadAppCodeDependencies' run configuration to download LLDBFrontend
withBin("../CIDR/cidr-debugger/bin/lldb/linux/bin/LLDBFrontend", "bin/linux", skipIfDoesntExist)
withBin("../CIDR/cidr-debugger/bin/lldb/mac/LLDBFrontend", "bin/macos", skipIfDoesntExist)
withBin("../CIDR/cidr-debugger/bin/lldb/win/x64/bin/LLDBFrontend", "bin/windows", skipIfDoesntExist)
withBin("../mobile-ide/common-native/scripts", "scripts")
}
}
}
private static String replace(String oldText, String regex, String newText) {
String result = oldText.replaceFirst(regex, newText)
if (result == oldText) {
throw new IllegalStateException("Cannot find '$regex' in '$oldText'")
}
return result
}
def build() {
BuildContext buildContext = BuildContext.createContext(communityHome, home, properties)
BuildTasks.create(buildContext).buildNonBundledPlugins([MAIN_KOTLIN_PLUGIN_MODULE])
}
enum KotlinPluginKind {
IJ, AS, MI,
AC_KMM{ // AppCode KMM plugin
@Override
String toString() {
return "AC"
}
}
}
}

View File

@@ -1,104 +0,0 @@
// Copyright 2000-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package org.jetbrains.intellij.build.kotlin
import com.intellij.openapi.util.Couple
import groovy.transform.CompileStatic
import org.jetbrains.intellij.build.CompilationContext
import org.jetbrains.intellij.build.impl.BuildContextImpl
import org.jetbrains.jps.model.artifact.JpsArtifact
import org.jetbrains.jps.model.library.JpsRepositoryLibraryType
@CompileStatic
enum KotlinPluginKind {
IJ("KotlinPlugin", "kotlin.plugin.version"),
AS("KotlinPluginCommunity", "kotlin.plugin.version.as"),
IJ_CE("KotlinPluginCommunity", "kotlin.plugin.version"),
MI("MobilePlugin",
"kotlin.plugin.version",
"intellij.mobile.ide",
"mobile-ide/resources",
[
Couple.of("artifacts/kotlin-ocswift", "kotlin-ocswift"),
Couple.of("artifacts/MobilePlugin", "Mobile")
]),
AC_KMM(
"AppCodeKMMPlugin",
"kotlin.plugin.version",
"kotlin-ultimate.appcode-kmm",
"CIDR-appcode/appcode-kmm/resources"
),
ROBOSCOPE(
"RoboscopePlugin.zip", "kotlin.plugin.version",
"util.android-studio.android-studio-roboscope-plugin",
"plugins/kotlin/tools/android-studio/android-studio-roboscope-plugin/resources",
[
Couple.of("RoboscopePlugin_zip/RoboscopePlugin.zip", "roboscope-plugin.zip")
]),
final String jpsArtifactIdentifier
final String pluginXmlModuleName
final String pluginXmlContentRoot
final List<Couple<String>> artifactCopyRules
private final String versionPropertyName
final String version
private KotlinPluginKind(String jpsArtifactIdentifier,
String pluginXmlModuleName,
String pluginXmlContentRoot,
List<Couple<String>> artifactCopyRules,
String versionPropertyName) {
this.jpsArtifactIdentifier = jpsArtifactIdentifier
this.pluginXmlModuleName = pluginXmlModuleName
this.pluginXmlContentRoot = pluginXmlContentRoot
this.versionPropertyName = versionPropertyName
this.version = getVersionFromProperty(versionPropertyName)
this.artifactCopyRules = artifactCopyRules
}
KotlinPluginKind(String name, String versionPropertyName) {
this(name + ".zip", "kotlin.plugin", "community/plugins/kotlin/plugin/resources",
[Couple.of("${name}_zip/${name}.zip" as String, "kotlin-plugin-${getVersionFromProperty(versionPropertyName)}.zip" as String)],
versionPropertyName
)
}
KotlinPluginKind(String name, String versionPropertyName, String pluginXmlModuleName, String pluginXmlContentRoot, List<Couple<String>> artifactCopyRules) {
this(name, pluginXmlModuleName, pluginXmlContentRoot, artifactCopyRules, versionPropertyName)
}
KotlinPluginKind(String name, String versionPropertyName, String pluginXmlModuleName, String pluginXmlContentRoot) {
this(name, pluginXmlModuleName, pluginXmlContentRoot, [Couple.of(name, name)], versionPropertyName)
}
JpsArtifact build(CompilationContext context) {
def buildNumber = buildNumber(context)
def version = System.getProperty(versionPropertyName, version(context, buildNumber))
def sinceBuild = System.getProperty("build.since", buildNumber)
def untilBuild = System.getProperty("build.until", buildNumber)
return new KotlinPluginArtifact(context).build(this, sinceBuild, untilBuild, version)
}
private static String buildNumber(CompilationContext context) {
def buildNumber = context.options.buildNumber
if (buildNumber == null || !buildNumber.contains('.')) {
buildNumber = BuildContextImpl.readSnapshotBuildNumber(context.paths.communityHomeDir)
}
return buildNumber
}
private static String version(CompilationContext context, String rawBuildNumber) {
def index = rawBuildNumber.indexOf('.')
assert index > 0
def intellijMajorVersion = rawBuildNumber.substring(0, index)
def buildNumber = rawBuildNumber.substring(++index)
def kotlinVersion = context.project.libraryCollection.libraries
.find { it.name.startsWith("kotlinc.") && it.type instanceof JpsRepositoryLibraryType }
?.asTyped(JpsRepositoryLibraryType.INSTANCE)
?.properties?.data?.version ?: "UNKNOWN"
return "$intellijMajorVersion-$kotlinVersion-IJ$buildNumber"
}
private static String getVersionFromProperty(String versionPropertyName) {
return System.getProperty(versionPropertyName) ?: "TC must specify '$versionPropertyName' property"
}
}