Cleanup (minor optimization; obsolete API; quickfixes; typos; formatting)

GitOrigin-RevId: e66c5fe72bf30981bb564a0fd8744e439df5e5a7
This commit is contained in:
Roman Shevchenko
2025-06-05 19:09:53 +02:00
committed by intellij-monorepo-bot
parent 3b8e9b6693
commit 07a2facf94
9 changed files with 157 additions and 376 deletions

View File

@@ -1,11 +1,11 @@
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.intellij.build
import com.intellij.openapi.util.io.NioFiles
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.NonCancellable
import kotlinx.coroutines.withContext
import org.jetbrains.intellij.build.impl.*
import org.jetbrains.intellij.build.io.deleteDir
import org.jetbrains.intellij.build.io.zipWithCompression
import java.nio.file.Files
import java.nio.file.Path
@@ -13,10 +13,12 @@ import java.nio.file.Path
/**
* Creates JARs containing classes required to run the external build for IDEA project without IDE.
*/
suspend fun buildCommunityStandaloneJpsBuilder(targetDir: Path,
context: BuildContext,
dryRun: Boolean = false,
layoutCustomizer: ((BaseLayout) -> Unit) = {}) {
suspend fun buildCommunityStandaloneJpsBuilder(
targetDir: Path,
context: BuildContext,
dryRun: Boolean = false,
layoutCustomizer: ((BaseLayout) -> Unit) = {},
) {
val layout = PlatformLayout()
layout.withModules(sequenceOf(
@@ -121,34 +123,29 @@ suspend fun buildCommunityStandaloneJpsBuilder(targetDir: Path,
Files.createTempDirectory(targetDir, "jps-standalone-community-")
}
try {
JarPackager.pack(includedModules = layout.includedModules,
outputDir = tempDir,
context = context,
layout = layout,
platformLayout = null,
isRootDir = false,
isCodesignEnabled = false,
moduleOutputPatcher = ModuleOutputPatcher(),
dryRun = dryRun)
JarPackager.pack(
layout.includedModules, tempDir, isRootDir = false, isCodesignEnabled = false, layout, platformLayout = null, ModuleOutputPatcher(),
dryRun, context = context
)
val targetFile = targetDir.resolve("standalone-jps-$buildNumber.zip")
withContext(Dispatchers.IO) {
buildJar(targetFile = tempDir.resolve("jps-build-test-$buildNumber.jar"),
moduleNames = listOf(
"intellij.platform.jps.build",
"intellij.platform.jps.model.tests",
"intellij.platform.jps.model.serialization.tests"
),
context = context)
zipWithCompression(targetFile = targetFile, dirs = mapOf(tempDir to ""))
buildJar(
targetFile = tempDir.resolve("jps-build-test-$buildNumber.jar"),
moduleNames = listOf(
"intellij.platform.jps.build",
"intellij.platform.jps.model.tests",
"intellij.platform.jps.model.serialization.tests"
),
context)
zipWithCompression(targetFile, dirs = mapOf(tempDir to ""))
}
context.notifyArtifactBuilt(targetFile)
}
finally {
withContext(Dispatchers.IO + NonCancellable) {
deleteDir(tempDir)
NioFiles.deleteRecursively(tempDir)
}
}
}

View File

@@ -1,4 +1,4 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.intellij.build.io
import com.intellij.openapi.util.text.Formats
@@ -6,7 +6,6 @@ import io.opentelemetry.api.common.AttributeKey
import io.opentelemetry.api.common.Attributes
import io.opentelemetry.api.trace.Span
import org.jetbrains.annotations.ApiStatus.Internal
import java.io.IOException
import java.nio.channels.FileChannel
import java.nio.file.*
import java.nio.file.attribute.BasicFileAttributes
@@ -92,52 +91,6 @@ private class CopyDirectoryVisitor(
}
}
fun deleteDir(startDir: Path) {
if (!Files.exists(startDir)) {
return
}
Files.walkFileTree(startDir, object : SimpleFileVisitor<Path>() {
override fun visitFile(file: Path, attrs: BasicFileAttributes): FileVisitResult {
deleteFile(file)
return FileVisitResult.CONTINUE
}
override fun postVisitDirectory(dir: Path, exception: IOException?): FileVisitResult {
if (exception != null) {
throw exception
}
Files.deleteIfExists(dir)
return FileVisitResult.CONTINUE
}
})
}
private fun deleteFile(file: Path) {
// repeated delete is required for bad OS like Windows
val maxAttemptCount = 10
var attemptCount = 0
while (true) {
try {
Files.deleteIfExists(file)
return
}
catch (e: IOException) {
if (++attemptCount == maxAttemptCount) {
throw e
}
try {
Thread.sleep(10)
}
catch (_: InterruptedException) {
throw e
}
}
}
}
@JvmOverloads
fun substituteTemplatePlaceholders(
inputFile: Path,

View File

@@ -1,6 +1,4 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
@file:Suppress("ReplacePutWithAssignment", "ReplaceGetOrSet")
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.intellij.build.io
import com.fasterxml.jackson.jr.ob.JSON
@@ -8,17 +6,14 @@ import com.intellij.openapi.util.io.FileUtilRt
import io.opentelemetry.api.common.AttributeKey
import io.opentelemetry.api.trace.Span
import kotlinx.coroutines.*
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.channels.ClosedSendChannelException
import kotlinx.coroutines.channels.toList
import org.jetbrains.annotations.ApiStatus.Obsolete
import org.jetbrains.intellij.build.telemetry.TraceManager.spanBuilder
import org.jetbrains.intellij.build.telemetry.use
import java.io.File
import java.io.InputStream
import java.nio.charset.MalformedInputException
import java.nio.file.Files
import java.nio.file.Path
import java.util.*
import kotlin.time.Duration
import kotlin.time.Duration.Companion.milliseconds
import kotlin.time.Duration.Companion.minutes
@@ -55,11 +50,7 @@ suspend fun runJava(mainClass: String,
try {
val classpathFile = Files.createTempFile("classpath-", ".txt").also(toDelete::add)
val classPathStringBuilder = createClassPathFile(classPath, classpathFile)
val processArgs = createProcessArgs(javaExe = javaExe,
jvmArgs = jvmArgs,
classpathFile = classpathFile,
mainClass = mainClass,
args = args)
val processArgs = createProcessArgs(javaExe, jvmArgs, classpathFile, mainClass, args)
span.setAttribute(AttributeKey.stringArrayKey("processArgs"), processArgs)
val errorOutputFile = Files.createTempFile("error-out-", ".txt").also(toDelete::add)
val outputFile = customOutputFile?.also { customOutputFile.parent?.let { Files.createDirectories(it) } }
@@ -79,9 +70,10 @@ suspend fun runJava(mainClass: String,
span.setAttribute("output", runCatching { Files.readString(outputFile) }.getOrNull() ?: "output file doesn't exist")
val errorOutput = runCatching { Files.readString(errorOutputFile) }.getOrNull()
val output = runCatching { Files.readString(outputFile) }.getOrNull()
val errorMessage = StringBuilder("Cannot execute $mainClass: $reason\n${processArgs.joinToString(separator = " ")}" +
"\n--- error output ---\n" +
"$errorOutput")
val errorMessage = StringBuilder(
"Cannot execute $mainClass: $reason\n${processArgs.joinToString(separator = " ")}" +
"\n--- error output ---\n" +
"$errorOutput")
if (!useJsonOutput) {
errorMessage.append("\n--- output ---\n$output\n")
}
@@ -147,8 +139,8 @@ private fun checkOutput(outputFile: Path, span: Span, errorConsumer: (String) ->
.forEach { line ->
if (line.startsWith('{')) {
val item = JSON.std.mapFrom(line)
val message = (item.get("message") as? String) ?: error("Missing field: 'message' in $line")
val level = (item.get("level") as? String) ?: error("Missing field: 'level' in $line")
val message = (item["message"] as? String) ?: error("Missing field: 'message' in $line")
val level = (item["level"] as? String) ?: error("Missing field: 'level' in $line")
messages.append(message).append('\n')
if (level == "SEVERE") {
errorConsumer("Error reported from child process logger: $message")
@@ -182,7 +174,8 @@ private fun createClassPathFile(classPath: Collection<String>, classpathFile: Pa
classPathStringBuilder.append("-classpath").append('\n')
for (s in classPath) {
appendArg(s, classPathStringBuilder)
classPathStringBuilder.append(File.pathSeparator)
@Suppress("IO_FILE_USAGE")
classPathStringBuilder.append(java.io.File.pathSeparator)
}
classPathStringBuilder.setLength(classPathStringBuilder.length - 1)
Files.writeString(classpathFile, classPathStringBuilder)
@@ -193,13 +186,7 @@ private fun createClassPathFile(classPath: Collection<String>, classpathFile: Pa
@Obsolete
fun runProcessBlocking(args: List<String>, workingDir: Path? = null, timeoutMillis: Long = DEFAULT_TIMEOUT.inWholeMilliseconds) {
runBlocking {
runProcess(
args = args,
workingDir = workingDir,
timeout = timeoutMillis.milliseconds,
additionalEnvVariables = emptyMap(),
inheritOut = false,
)
runProcess(args, workingDir, timeoutMillis.milliseconds)
}
}
@@ -235,7 +222,7 @@ suspend fun runProcess(
builder.redirectErrorStream(inheritErrToOut)
}
}.start()
val outputChannel = Channel<String>(capacity = Channel.UNLIMITED)
val outputLines = Collections.synchronizedList(ArrayList<String>())
if (!inheritOut) {
launch(Dispatchers.Default) {
withTimeout(timeout) {
@@ -243,11 +230,7 @@ suspend fun runProcess(
span.addEvent(it)
stdOutConsumer(it)
if (attachStdOutToException) {
try {
outputChannel.send(it)
}
catch (_: ClosedSendChannelException) {
}
outputLines += it
}
}
}
@@ -257,11 +240,7 @@ suspend fun runProcess(
process.errorStream.consume(process) {
span.addEvent(it)
stdErrConsumer(it)
try {
outputChannel.send(it)
}
catch (_: ClosedSendChannelException) {
}
outputLines += it
}
}
}
@@ -279,16 +258,13 @@ suspend fun runProcess(
}
catch (e: TimeoutCancellationException) {
throw e.apply {
addSuppressed(RuntimeException("Process '$commandLine' (pid=$pid) failed to complete in $timeout" + toLines(outputChannel)))
addSuppressed(RuntimeException("Process '$commandLine' (pid=$pid) failed to complete in $timeout" + merge(outputLines)))
}
}
finally {
outputChannel.close()
}
val exitCode = process.exitValue()
if (exitCode != 0) {
throw RuntimeException("Process '$commandLine' (pid=$pid) finished with exitCode $exitCode" + toLines(outputChannel))
throw RuntimeException("Process '$commandLine' (pid=$pid) finished with exitCode $exitCode" + merge(outputLines))
}
}
finally {
@@ -349,13 +325,6 @@ private suspend fun InputStream.consume(process: Process, consume: suspend (line
}
}
private suspend fun toLines(channel: Channel<String>): String {
channel.close()
val lines = channel.toList()
return if (lines.any()) {
lines.joinToString(prefix = ":\n", separator = "\n")
}
else {
""
}
}
private fun merge(lines: List<String>): String = synchronized(lines) {
if (lines.any()) lines.joinToString(prefix = ":\n", separator = "\n") else ""
}

View File

@@ -1,4 +1,4 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.intellij.build
import org.jetbrains.annotations.ApiStatus.Internal
@@ -39,6 +39,5 @@ interface ApplicationInfoProperties {
* another product, to get the instance of the product currently being build, use [BuildContext.applicationInfo] instead.
*/
@Internal
fun BuildContext.loadApplicationInfoPropertiesForProduct(productProperties: ProductProperties): ApplicationInfoProperties {
return ApplicationInfoPropertiesImpl(project, productProperties, options)
}
fun BuildContext.loadApplicationInfoPropertiesForProduct(productProperties: ProductProperties): ApplicationInfoProperties =
ApplicationInfoPropertiesImpl(project, productProperties, options)

View File

@@ -358,25 +358,23 @@ data class BuildOptions(
var useLocalLauncher: Boolean = false
/**
* When `true`, cross-platform distribution will be packed using zip64 in AlwaysWithCompatibility mode
* When `true`, cross-platform distribution will be packed using zip64 in AlwaysWithCompatibility mode.
*/
var useZip64ForCrossPlatformDistribution: Boolean = getBooleanProperty("intellij.build.cross.platform.dist.zip64", false)
/**
* Pass `true` to this system property to produce .snap packages.
* Requires Docker.
* Pass `true` to this system property to produce .snap packages. Requires Docker.
*/
var buildUnixSnaps: Boolean = getBooleanProperty("intellij.build.unix.snaps", false)
/**
* Docker image for snap package creation
* Docker image for snap package creation.
*/
var snapDockerImage: String = System.getProperty("intellij.build.snap.docker.image") ?: DEPENDENCIES_PROPERTIES["snapDockerImage"]
var snapDockerBuildTimeoutMin: Long = System.getProperty("intellij.build.snap.timeoutMin")?.toLong() ?: 20
/**
* When `true`, `.resx` files are generated and bundled in the localization plugins.
* Requires Docker.
* When `true`, `.resx` files are generated and bundled in the localization plugins. Requires Docker.
*/
var bundleLocalizationPluginResources: Boolean = getBooleanProperty("intellij.build.localization.plugin.resources", false)

View File

@@ -1,4 +1,4 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.intellij.build
import kotlinx.collections.immutable.PersistentList
@@ -20,25 +20,25 @@ open class WindowsDistributionCustomizer {
/**
* If `true`, *.bat files (productName.bat and inspect.bat) will be included in the distribution.
*/
var includeBatchLaunchers = true
var includeBatchLaunchers: Boolean = true
/**
* If `true`, build a ZIP archive with JetBrains Runtime.
*/
var buildZipArchiveWithBundledJre = true
var buildZipArchiveWithBundledJre: Boolean = true
/**
* If `true`, build a ZIP archive without JetBrains Runtime.
*/
var buildZipArchiveWithoutBundledJre = false
var buildZipArchiveWithoutBundledJre: Boolean = false
var zipArchiveWithBundledJreSuffix = ".win"
var zipArchiveWithoutBundledJreSuffix = "-no-jbr.win"
var zipArchiveWithBundledJreSuffix: String = ".win"
var zipArchiveWithoutBundledJreSuffix: String = "-no-jbr.win"
/**
* If `true`, Windows Installer will associate *.ipr files with the IDE in Registry.
*/
var associateIpr = true
var associateIpr: Boolean = true
/**
* Path to a directory containing images for installer: `logo.bmp`, `headerlogo.bmp`, `install.ico`, `uninstall.ico`.
@@ -65,9 +65,8 @@ open class WindowsDistributionCustomizer {
/**
* Name of the Windows installation directory and Desktop shortcut.
*/
open fun getNameForInstallDirAndDesktopShortcut(appInfo: ApplicationInfoProperties, buildNumber: String): String {
return "${getFullNameIncludingEdition(appInfo)} ${if (appInfo.isEAP) buildNumber else appInfo.fullVersion}"
}
open fun getNameForInstallDirAndDesktopShortcut(appInfo: ApplicationInfoProperties, buildNumber: String): String =
"${getFullNameIncludingEdition(appInfo)} ${if (appInfo.isEAP) buildNumber else appInfo.fullVersion}"
/**
* Override this method to copy additional files to the Windows distribution of the product.
@@ -84,9 +83,8 @@ open class WindowsDistributionCustomizer {
/**
* The returned name will be used to create links on Desktop.
*/
open fun getFullNameIncludingEditionAndVendor(appInfo: ApplicationInfoProperties): String {
return appInfo.shortCompanyName + ' ' + getFullNameIncludingEdition(appInfo)
}
open fun getFullNameIncludingEditionAndVendor(appInfo: ApplicationInfoProperties): String =
appInfo.shortCompanyName + ' ' + getFullNameIncludingEdition(appInfo)
open fun getUninstallFeedbackPageUrl(appInfo: ApplicationInfoProperties): String? = null

View File

@@ -1,6 +1,4 @@
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
@file:Suppress("ReplaceGetOrSet")
package org.jetbrains.intellij.build.impl
import com.fasterxml.jackson.jr.ob.JSON
@@ -145,27 +143,14 @@ internal suspend fun buildDistribution(
val buildNonBundledPlugins = async(CoroutineName("build non-bundled plugins")) {
val compressPluginArchive = !isUpdateFromSources && context.options.compressZipFiles
buildNonBundledPlugins(
pluginsToPublish = state.pluginsToPublish,
compressPluginArchive = compressPluginArchive,
buildPlatformLibJob = buildPlatformJob,
state = state,
searchableOptionSet = searchableOptionSet,
context = context,
)
buildNonBundledPlugins(state.pluginsToPublish, compressPluginArchive, buildPlatformJob, state, searchableOptionSet, context)
}
val bundledPluginItems = buildBundledPluginsForAllPlatforms(
state = state,
pluginLayouts = pluginLayouts,
isUpdateFromSources = isUpdateFromSources,
buildPlatformJob = buildPlatformJob,
searchableOptionSetDescriptor = searchableOptionSet,
moduleOutputPatcher = moduleOutputPatcher,
context = context,
state, pluginLayouts, isUpdateFromSources, buildPlatformJob, searchableOptionSet, moduleOutputPatcher, context
)
ContentReport(platform = buildPlatformJob.await(), bundledPlugins = bundledPluginItems, nonBundledPlugins = buildNonBundledPlugins.await())
ContentReport(buildPlatformJob.await(), bundledPluginItems, buildNonBundledPlugins.await())
}
coroutineScope {
@@ -202,45 +187,25 @@ private suspend fun buildBundledPluginsForAllPlatforms(
): List<Pair<PluginBuildDescriptor, List<DistributionFileEntry>>> = coroutineScope {
val commonDeferred = async(CoroutineName("build bundled plugins")) {
doBuildBundledPlugins(
state = state,
plugins = pluginLayouts,
isUpdateFromSources = isUpdateFromSources,
buildPlatformJob = buildPlatformJob,
searchableOptionSet = searchableOptionSetDescriptor,
moduleOutputPatcher = moduleOutputPatcher,
context = context,
state, pluginLayouts, isUpdateFromSources, buildPlatformJob, searchableOptionSetDescriptor, moduleOutputPatcher, context
)
}
val additionalDeferred = async(CoroutineName("build additional plugins")) {
copyAdditionalPlugins(context = context, pluginDir = context.paths.distAllDir.resolve(PLUGINS_DIRECTORY))
copyAdditionalPlugins(context, pluginDir = context.paths.distAllDir.resolve(PLUGINS_DIRECTORY))
}
val pluginDirs = getPluginDirs(context, isUpdateFromSources)
val specificDeferred = async(CoroutineName("build OS-specific bundled plugins")) {
buildOsSpecificBundledPlugins(
state = state,
plugins = pluginLayouts,
isUpdateFromSources = isUpdateFromSources,
buildPlatformJob = buildPlatformJob,
context = context,
searchableOptionSet = searchableOptionSetDescriptor,
pluginDirs = pluginDirs,
moduleOutputPatcher = moduleOutputPatcher,
state, pluginLayouts, isUpdateFromSources, buildPlatformJob, context, searchableOptionSetDescriptor, pluginDirs, moduleOutputPatcher
)
}
val common = commonDeferred.await()
val specific = specificDeferred.await()
buildPlatformJob.join()
writePluginInfo(
moduleOutputPatcher = moduleOutputPatcher,
pluginDirs = pluginDirs,
common = common,
specific = specific,
additional = additionalDeferred.await(),
context = context,
)
writePluginInfo(moduleOutputPatcher, pluginDirs, common, specific, additionalDeferred.await(), context)
common + specific.values.flatten()
}
@@ -252,23 +217,23 @@ private fun writePluginInfo(
additional: List<Pair<Path, List<Path>>>?,
context: BuildContext,
) {
val commonClassPath = generatePluginClassPath(pluginEntries = common, moduleOutputPatcher = moduleOutputPatcher)
val commonClassPath = generatePluginClassPath(pluginEntries = common, moduleOutputPatcher)
val additionalClassPath = additional?.let { generatePluginClassPathFromPrebuiltPluginFiles(it) }
for ((supportedDist) in pluginDirs) {
val specificList = specific.get(supportedDist)
val specificClasspath = specificList?.let { generatePluginClassPath(pluginEntries = it, moduleOutputPatcher = moduleOutputPatcher) }
val specificList = specific[supportedDist]
val specificClasspath = specificList?.let { generatePluginClassPath(pluginEntries = it, moduleOutputPatcher) }
val byteOut = ByteArrayOutputStream()
val out = DataOutputStream(byteOut)
val pluginCount = common.size + (additional?.size ?: 0) + (specificList?.size ?: 0)
writePluginClassPathHeader(out = out, isJarOnly = true, pluginCount = pluginCount, moduleOutputPatcher = moduleOutputPatcher, context = context)
writePluginClassPathHeader(out, isJarOnly = true, pluginCount, moduleOutputPatcher, context)
out.write(commonClassPath)
additionalClassPath?.let { out.write(it) }
specificClasspath?.let { out.write(it) }
out.close()
context.addDistFile(DistFile(content = InMemoryDistFileContent(byteOut.toByteArray()), relativePath = PLUGIN_CLASSPATH, os = supportedDist.os, arch = supportedDist.arch, libcImpl = supportedDist.libcImpl))
context.addDistFile(DistFile(InMemoryDistFileContent(byteOut.toByteArray()), PLUGIN_CLASSPATH, supportedDist.os, supportedDist.libcImpl, supportedDist.arch))
}
}
@@ -281,16 +246,15 @@ fun validateModuleStructure(platform: PlatformLayout, context: BuildContext) {
}
}
private fun getPluginDirs(context: BuildContext, isUpdateFromSources: Boolean): List<Pair<SupportedDistribution, Path>> {
private fun getPluginDirs(context: BuildContext, isUpdateFromSources: Boolean): List<Pair<SupportedDistribution, Path>> =
if (isUpdateFromSources) {
return listOf(SupportedDistribution(OsFamily.currentOs, JvmArchitecture.currentJvmArch, LibcImpl.current(OsFamily.currentOs)) to context.paths.distAllDir.resolve(PLUGINS_DIRECTORY))
listOf(SupportedDistribution(OsFamily.currentOs, JvmArchitecture.currentJvmArch, LibcImpl.current(OsFamily.currentOs)) to context.paths.distAllDir.resolve(PLUGINS_DIRECTORY))
}
else {
return SUPPORTED_DISTRIBUTIONS.map {
SUPPORTED_DISTRIBUTIONS.map {
it to getOsAndArchSpecificDistDirectory(it.os, it.arch, it.libcImpl, context).resolve(PLUGINS_DIRECTORY)
}
}
}
suspend fun buildBundledPlugins(
state: DistributionBuilderState,
@@ -299,13 +263,7 @@ suspend fun buildBundledPlugins(
context: BuildContext,
) {
doBuildBundledPlugins(
state = state,
plugins = plugins,
isUpdateFromSources = false,
buildPlatformJob = null,
searchableOptionSet = searchableOptionSetDescriptor,
moduleOutputPatcher = ModuleOutputPatcher(),
context = context,
state, plugins, isUpdateFromSources = false, buildPlatformJob = null, searchableOptionSetDescriptor, ModuleOutputPatcher(), context
)
}
@@ -324,7 +282,7 @@ private suspend fun doBuildBundledPlugins(
.setAttribute("count", plugins.size.toLong())
.block { span ->
val pluginsToBundle = ArrayList<PluginLayout>(plugins.size)
plugins.filterTo(pluginsToBundle) { satisfiesBundlingRequirements(plugin = it, osFamily = null, arch = null, context = context) }
plugins.filterTo(pluginsToBundle) { satisfiesBundlingRequirements(plugin = it, osFamily = null, arch = null, context) }
span.setAttribute("satisfiableCount", pluginsToBundle.size.toLong())
// doesn't make sense to require passing here a list with a stable order (unnecessary complication, sorting by main module is enough)
@@ -332,23 +290,15 @@ private suspend fun doBuildBundledPlugins(
val targetDir = context.paths.distAllDir.resolve(PLUGINS_DIRECTORY)
val platformSpecificPluginDirs = getPluginDirs(context, isUpdateFromSources)
val entries = buildPlugins(
moduleOutputPatcher = moduleOutputPatcher,
plugins = pluginsToBundle,
os = null,
targetDir = targetDir,
state = state,
context = context,
buildPlatformJob = buildPlatformJob,
searchableOptionSet = searchableOptionSet,
pluginBuilt = { layout, pluginDirOrFile ->
if (layout.hasPlatformSpecificResources) {
buildPlatformSpecificPluginResources(plugin = layout, targetDirs = platformSpecificPluginDirs, context = context)
}
else {
emptyList()
}
moduleOutputPatcher, pluginsToBundle, os = null, targetDir, state, context, buildPlatformJob, searchableOptionSet
) { layout, _ ->
if (layout.hasPlatformSpecificResources) {
buildPlatformSpecificPluginResources(layout, platformSpecificPluginDirs, context)
}
)
else {
emptyList()
}
}
entries
}
@@ -375,7 +325,7 @@ private suspend fun buildOsSpecificBundledPlugins(
}
val osSpecificPlugins = plugins.filter {
satisfiesBundlingRequirements(plugin = it, osFamily = os, arch = arch, context = context)
satisfiesBundlingRequirements(it, os, arch, context)
}
if (osSpecificPlugins.isEmpty()) {
return@mapNotNull null
@@ -388,16 +338,7 @@ private suspend fun buildOsSpecificBundledPlugins(
.setAttribute("count", osSpecificPlugins.size.toLong())
.setAttribute("outDir", targetDir.toString())
.use {
buildPlugins(
moduleOutputPatcher = moduleOutputPatcher,
plugins = osSpecificPlugins,
os = os,
targetDir = targetDir,
state = state,
context = context,
buildPlatformJob = buildPlatformJob,
searchableOptionSet = searchableOptionSet,
)
buildPlugins(moduleOutputPatcher, osSpecificPlugins, os, targetDir, state, context, buildPlatformJob, searchableOptionSet)
}
}
}
@@ -460,14 +401,7 @@ internal suspend fun buildNonBundledPlugins(
!context.isStepSkipped(BuildOptions.ARCHIVE_PLUGINS)
val plugins = pluginsToPublish.sortedWith(PLUGIN_LAYOUT_COMPARATOR_BY_MAIN_MODULE)
val mappings = buildPlugins(
moduleOutputPatcher = moduleOutputPatcher,
plugins = plugins,
os = null,
targetDir = stageDir,
state = state,
context = context,
buildPlatformJob = buildPlatformLibJob,
searchableOptionSet = searchableOptionSet,
moduleOutputPatcher, plugins, os = null, stageDir, state, context, buildPlatformLibJob, searchableOptionSet
) { plugin, pluginDirOrFile ->
val pluginVersion = if (plugin.mainModule == BUILT_IN_HELP_MODULE_NAME) {
context.buildNumber
@@ -489,23 +423,17 @@ internal suspend fun buildNonBundledPlugins(
val destFile = targetDirectory.resolve("${plugin.directoryName}-$pluginVersion.zip")
val pluginXml = moduleOutputPatcher.getPatchedPluginXml(plugin.mainModule)
pluginSpecs.add(PluginRepositorySpec(destFile, pluginXml))
dirToJar.add(NonBundledPlugin(sourceDir = pluginDirOrFile, targetZip = destFile, optimizedZip = !plugin.enableSymlinksAndExecutableResources))
dirToJar.add(NonBundledPlugin(pluginDirOrFile, destFile, !plugin.enableSymlinksAndExecutableResources))
emptyList()
}
archivePlugins(items = dirToJar, compress = compressPluginArchive, withBlockMap = compressPluginArchive, context = context)
archivePlugins(dirToJar, compressPluginArchive, compressPluginArchive, context)
val helpPlugin = buildHelpPlugin(pluginVersion = context.pluginBuildNumber, context = context)
val helpPlugin = buildHelpPlugin(context.pluginBuildNumber, context)
if (helpPlugin != null) {
val spec = buildHelpPlugin(
helpPlugin = helpPlugin,
pluginsToPublishDir = stageDir,
targetDir = context.nonBundledPluginsToBePublished,
moduleOutputPatcher = moduleOutputPatcher,
state = state,
searchableOptionSetDescriptor = searchableOptionSet,
context = context,
helpPlugin, stageDir, context.nonBundledPluginsToBePublished, moduleOutputPatcher, state, searchableOptionSet, context
)
pluginSpecs.add(spec)
}
@@ -598,12 +526,7 @@ internal suspend fun generateProjectStructureMapping(platformLayout: PlatformLay
val moduleOutputPatcher = ModuleOutputPatcher()
val libDirLayout = async(CoroutineName("layout platform distribution")) {
layoutPlatformDistribution(
moduleOutputPatcher = moduleOutputPatcher,
targetDirectory = context.paths.distAllDir,
platform = platformLayout,
searchableOptionSet = null,
copyFiles = false,
context = context,
moduleOutputPatcher, context.paths.distAllDir, platform = platformLayout, searchableOptionSet = null, copyFiles = false, context
)
}
@@ -616,14 +539,7 @@ internal suspend fun generateProjectStructureMapping(platformLayout: PlatformLay
if (satisfiesBundlingRequirements(plugin, osFamily = null, arch = null, context)) {
val targetDirectory = context.paths.distAllDir.resolve(PLUGINS_DIRECTORY).resolve(plugin.directoryName)
entries.add(PluginBuildDescriptor(targetDirectory, os = null, plugin, moduleNames = emptyList()) to layoutDistribution(
layout = plugin,
platformLayout = platformLayout,
targetDirectory = targetDirectory,
copyFiles = false,
moduleOutputPatcher = moduleOutputPatcher,
includedModules = plugin.includedModules,
searchableOptionSet = null,
context = context,
plugin, platformLayout, targetDirectory, copyFiles = false, moduleOutputPatcher, plugin.includedModules, searchableOptionSet = null, context
).first)
}
}
@@ -652,16 +568,10 @@ internal suspend fun buildPlugins(
val entries = coroutineScope {
plugins.map { plugin ->
if (plugin.mainModule != BUILT_IN_HELP_MODULE_NAME) {
checkOutputOfPluginModules(mainPluginModule = plugin.mainModule, includedModules = plugin.includedModules, moduleExcludes = plugin.moduleExcludes, context = context)
checkOutputOfPluginModules(plugin.mainModule, plugin.includedModules, plugin.moduleExcludes, context)
patchPluginXml(
moduleOutputPatcher = moduleOutputPatcher,
plugin = plugin,
releaseDate = context.applicationInfo.majorReleaseDate,
releaseVersion = context.applicationInfo.releaseVersionForLicensing,
pluginsToPublish = state.pluginsToPublish,
helper = (context as BuildContextImpl).jarPackagerDependencyHelper,
platformLayout = state.platform,
context = context,
moduleOutputPatcher, plugin, context.applicationInfo.majorReleaseDate, context.applicationInfo.releaseVersionForLicensing, state.pluginsToPublish,
(context as BuildContextImpl).jarPackagerDependencyHelper, state.platform, context
)
}
@@ -670,14 +580,7 @@ internal suspend fun buildPlugins(
val task = async(CoroutineName("Build plugin (module=${plugin.mainModule})")) {
spanBuilder("plugin").setAttribute("path", context.paths.buildOutputDir.relativize(pluginDir).toString()).use {
val (entries, file) = layoutDistribution(
layout = plugin,
platformLayout = state.platform,
targetDirectory = pluginDir,
copyFiles = true,
moduleOutputPatcher = moduleOutputPatcher,
includedModules = plugin.includedModules,
searchableOptionSet = searchableOptionSet,
context = context,
plugin, state.platform, pluginDir, copyFiles = true, moduleOutputPatcher, plugin.includedModules, searchableOptionSet, context
)
if (pluginBuilt == null) {
@@ -762,9 +665,8 @@ fun getPluginLayoutsByJpsModuleNames(modules: Collection<String>, productLayout:
return result
}
private fun basePath(buildContext: BuildContext, moduleName: String): Path {
return Path.of(JpsPathUtil.urlToPath(buildContext.findRequiredModule(moduleName).contentRootsList.urls.first()))
}
private fun basePath(buildContext: BuildContext, moduleName: String): Path =
Path.of(JpsPathUtil.urlToPath(buildContext.findRequiredModule(moduleName).contentRootsList.urls.first()))
suspend fun buildLib(
moduleOutputPatcher: ModuleOutputPatcher,
@@ -774,12 +676,7 @@ suspend fun buildLib(
): List<DistributionFileEntry> {
val targetDirectory = context.paths.distAllDir
val libDirMappings = layoutPlatformDistribution(
moduleOutputPatcher = moduleOutputPatcher,
targetDirectory = targetDirectory,
platform = platform,
searchableOptionSet = searchableOptionSetDescriptor,
copyFiles = true,
context = context,
moduleOutputPatcher, targetDirectory, platform, searchableOptionSetDescriptor, copyFiles = true, context
)
context.proprietaryBuildTools.scrambleTool?.validatePlatformLayout(platform.includedModules, context)
return libDirMappings
@@ -804,8 +701,9 @@ suspend fun layoutPlatformDistribution(
val moduleName = "intellij.platform.core"
val module = context.findRequiredModule(moduleName)
val relativePath = "com/intellij/openapi/application/ApplicationNamesInfo.class"
val result = injectAppInfo(inFileBytes = context.readFileContentFromModuleOutput(module, relativePath) ?: error("app info not found"), newFieldValue = context.appInfoXml)
moduleOutputPatcher.patchModuleOutput(moduleName, relativePath, result)
val sourceBytes = context.readFileContentFromModuleOutput(module, relativePath) ?: error("app info not found")
val patchedBytes = injectAppInfo(inFileBytes = sourceBytes, newFieldValue = context.appInfoXml)
moduleOutputPatcher.patchModuleOutput(moduleName, relativePath, patchedBytes)
}
}
}
@@ -825,7 +723,7 @@ private suspend fun patchKeyMapWithAltClickReassignedToMultipleCarets(moduleOutp
}
val moduleName = "intellij.platform.resources"
val relativePath = "keymaps/\$default.xml"
val relativePath = $$"keymaps/$default.xml"
val sourceFileContent = context.readFileContentFromModuleOutput(context.findRequiredModule(moduleName), relativePath)
?: error("Not found '$relativePath' in module $moduleName output")
var text = String(sourceFileContent, StandardCharsets.UTF_8)
@@ -835,9 +733,8 @@ private suspend fun patchKeyMapWithAltClickReassignedToMultipleCarets(moduleOutp
moduleOutputPatcher.patchModuleOutput(moduleName, relativePath, text)
}
fun getOsAndArchSpecificDistDirectory(osFamily: OsFamily, arch: JvmArchitecture, libc: LibcImpl, context: BuildContext): Path {
return context.paths.buildOutputDir.resolve("dist.${osFamily.distSuffix}.${arch.name}${if (libc == LinuxLibcImpl.MUSL) { "-musl" } else {""} }")
}
fun getOsAndArchSpecificDistDirectory(osFamily: OsFamily, arch: JvmArchitecture, libc: LibcImpl, context: BuildContext): Path =
context.paths.buildOutputDir.resolve("dist.${osFamily.distSuffix}.${arch.name}${if (libc == LinuxLibcImpl.MUSL) { "-musl" } else {""} }")
private suspend fun checkOutputOfPluginModules(
mainPluginModule: String,
@@ -846,13 +743,10 @@ private suspend fun checkOutputOfPluginModules(
context: BuildContext,
) {
for (module in includedModules.asSequence().map { it.moduleName }.distinct()) {
if (module == "intellij.java.guiForms.rt" ||
!containsFileInOutput(
moduleName = module,
filePath = "com/intellij/uiDesigner/core/GridLayoutManager.class",
excludes = moduleExcludes[module] ?: emptyList(),
context,
)) {
if (
module == "intellij.java.guiForms.rt" ||
!containsFileInOutput(module, "com/intellij/uiDesigner/core/GridLayoutManager.class", moduleExcludes[module] ?: emptyList(), context)
) {
"Runtime classes of GUI designer must not be packaged to '$module' module in '$mainPluginModule' plugin, " +
"because they are included into a platform JAR. Make sure that 'Automatically copy form runtime classes " +
"to the output directory' is disabled in Settings | Editor | GUI Designer."
@@ -860,12 +754,7 @@ private suspend fun checkOutputOfPluginModules(
}
}
private suspend fun containsFileInOutput(
moduleName: String,
filePath: String,
excludes: Collection<String>,
context: BuildContext,
): Boolean {
private suspend fun containsFileInOutput(moduleName: String, filePath: String, excludes: Collection<String>, context: BuildContext): Boolean {
val exists = context.hasModuleOutputPath(context.findRequiredModule(moduleName), filePath)
if (!exists) {
return false
@@ -908,9 +797,7 @@ private fun CoroutineScope.createBuildThirdPartyLibraryListJob(entries: Sequence
return createSkippableJob(spanBuilder("generate table of licenses for used third-party libraries"),
BuildOptions.THIRD_PARTY_LIBRARIES_LIST_STEP, context) {
val generator = createLibraryLicensesListGenerator(
context = context,
licenseList = context.productProperties.allLibraryLicenses,
usedModulesNames = getIncludedModules(entries).toHashSet(),
context, context.productProperties.allLibraryLicenses, getIncludedModules(entries).toHashSet()
)
val distAllDir = context.paths.distAllDir
withContext(Dispatchers.IO) {
@@ -981,7 +868,7 @@ private suspend fun buildKeymapPlugins(targetDir: Path, context: BuildContext):
arrayOf("Sublime Text", "Sublime Text (Mac OS X)"),
).map {
async(CoroutineName("build keymap plugin for ${it[0]}")) {
buildKeymapPlugin(keymaps = it, buildNumber = context.buildNumber, targetDir = targetDir, keymapDir = keymapDir)
buildKeymapPlugin(keymaps = it, context.buildNumber, targetDir, keymapDir)
}
}
}.map { it.getCompleted() }
@@ -1122,7 +1009,7 @@ private fun copyIfChanged(targetDir: Path, sourceDir: Path, sourceFile: Path): B
private suspend fun layoutAdditionalResources(layout: BaseLayout, context: BuildContext, targetDirectory: Path) {
// quick fix for a very annoying FileAlreadyExistsException in CLion dev build
val overwrite = ("intellij.rider.plugins.clion.radler" == (layout as? PluginLayout)?.mainModule)
layoutResourcePaths(layout = layout, context = context, targetDirectory = targetDirectory, overwrite = overwrite)
layoutResourcePaths(layout, context, targetDirectory, overwrite)
if (layout !is PluginLayout) {
return
}
@@ -1183,12 +1070,7 @@ private fun addArtifactMapping(artifact: JpsArtifact, entries: MutableCollection
for (element in rootElement.children) {
if (element is JpsProductionModuleOutputPackagingElement) {
entries.add(ModuleOutputEntry(
path = artifactFile,
moduleName = element.moduleReference.moduleName,
size = 0,
hash = 0,
relativeOutputFile = "",
reason = "artifact: ${artifact.name}",
artifactFile, element.moduleReference.moduleName, size = 0, hash = 0, relativeOutputFile = "", reason = "artifact: ${artifact.name}"
))
}
else if (element is JpsTestModuleOutputPackagingElement) {
@@ -1199,24 +1081,13 @@ private fun addArtifactMapping(artifact: JpsArtifact, entries: MutableCollection
val parentReference = library!!.createReference().parentReference
if (parentReference is JpsModuleReference) {
entries.add(ModuleLibraryFileEntry(
path = artifactFile,
moduleName = parentReference.moduleName,
libraryName = getLibraryFilename(library),
libraryFile = null,
hash = 0,
size = 0,
relativeOutputFile = null,
artifactFile, parentReference.moduleName, getLibraryFilename(library), libraryFile = null, hash = 0, size = 0, relativeOutputFile = null
))
}
else {
val libraryData = ProjectLibraryData(libraryName = library.name, reason = "<- artifact ${artifact.name}")
entries.add(ProjectLibraryEntry(
path = artifactFile,
data = libraryData,
libraryFile = null,
hash = 0,
size = 0,
relativeOutputFile = null,
artifactFile, libraryData, libraryFile = null, hash = 0, size = 0, relativeOutputFile = null
))
}
}
@@ -1252,7 +1123,7 @@ private suspend fun archivePlugins(items: Collection<NonBundledPlugin>, compress
.setAttribute("outputFile", target.toString())
.setAttribute("optimizedZip", optimized)
.use {
archivePlugin(optimized = optimized, target = target, compress = compress, source = source, context = context)
archivePlugin(optimized, target, compress, source, context)
}
if (withBlockMap) {
spanBuilder("build plugin blockmap").setAttribute("file", target.toString()).use {

View File

@@ -1,4 +1,4 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.intellij.build.impl
import com.intellij.openapi.util.io.FileUtil
@@ -7,6 +7,8 @@ import java.nio.file.Files
import java.nio.file.Path
import java.util.regex.Pattern
private const val MAX_PATH = 260
internal class NsisFileListGenerator {
private val directoryToFiles = LinkedHashMap<String, MutableList<Path>>()
private val filesRelativePaths = mutableListOf<String>()
@@ -25,7 +27,7 @@ internal class NsisFileListGenerator {
out.write("\n")
val installDir = "\$INSTDIR"
val installDir = $$"$INSTDIR"
scriptWithLongPathSupport(out, installDir, relativePath, files.map { it.fileName.toString() }) {
out.write("SetOutPath \"$installDir${if (relativePath.isEmpty()) "" else "\\"}${escapeWinPath(relativePath)}\"\n")
@@ -37,7 +39,7 @@ internal class NsisFileListGenerator {
}
}
fun generateUninstallerFile(outputFile: Path, installDir: String = "\$INSTDIR") {
fun generateUninstallerFile(outputFile: Path, installDir: String = $$"$INSTDIR") {
Files.newBufferedWriter(outputFile).use { out ->
filesRelativePaths.sorted().forEach {
scriptWithLongPathSupport(out, installDir, null, listOf(it)) {
@@ -83,49 +85,45 @@ internal class NsisFileListGenerator {
}
}
}
}
private fun escapeWinPath(dir: String): String {
return dir.replace('/', '\\').replace("\\$", "\\$\\$")
}
private fun escapeWinPath(dir: String): String = dir.replace('/', '\\').replace("\\$", "\\$\\$")
private fun scriptWithLongPathSupport(
writer: BufferedWriter,
instDirVariable: String,
relativePath: String?,
files: List<String>,
actionWithOutPath: () -> Unit
) {
assert(instDirVariable.startsWith("$"))
private fun scriptWithLongPathSupport(
writer: BufferedWriter,
instDirVariable: String,
relativePath: String?,
files: List<String>,
actionWithOutPath: () -> Unit
) {
assert(instDirVariable.startsWith("$"))
val areLongPathsPresent = guessMaxPathLength(relativePath, files) > MAX_PATH
if (areLongPathsPresent) {
// For long paths in the installer, we perform a special maneuver.
// Prepend the INSTDIR with "\\?\" so that WinAPI functions won't check its length and will allow working with the file.
writer.write(
"""
val areLongPathsPresent = guessMaxPathLength(relativePath, files) > MAX_PATH
if (areLongPathsPresent) {
// For long paths in the installer, we perform a special maneuver.
// Prepend the INSTDIR with "\\?\" so that WinAPI functions won't check its length and will allow working with the file.
writer.write(
"""
Push $instDirVariable
GetFullPathName $instDirVariable $instDirVariable
StrCpy $instDirVariable "\\?\$instDirVariable"
""".trimIndent() + "\n"
)
)
}
actionWithOutPath()
if (areLongPathsPresent) {
// Clean up:
writer.write("Pop $instDirVariable\n")
}
}
actionWithOutPath()
if (areLongPathsPresent) {
// Clean up:
writer.write("Pop $instDirVariable\n")
private fun guessMaxPathLength(relativePath: String?, files: List<String>): Int {
// Guess the typical length of $INSTDIR plus a small margin to be safe.
// NOTE: The AppData path for non-admin installation might be longer than the one in Program Files, so let's consider that here.
// Also, "IntelliJ IDEA Community Edition" is the longest product name so far.
val instDirGuessedLength = "C:\\Users\\some-reasonably-long-user-name\\AppData\\Local\\JetBrains\\IntelliJ IDEA Community Edition 2024.1.2.SNAPSHOT\\".length + 10
val directoryPathLength = instDirGuessedLength + (relativePath?.length?.let { it + 1 /* backslash */ } ?: 0)
return directoryPathLength + (files.maxOfOrNull { it.length } ?: 0)
}
}
private fun guessMaxPathLength(relativePath: String?, files: List<String>): Int {
// Guess the typical length of $INSTDIR plus a small margin to be safe.
// NOTE: The AppData path for non-admin installation might be longer than the one in Program Files, so let's consider that here.
// Also, "IntelliJ IDEA Community Edition" is the longest product name so far.
val instDirGuessedLength = "C:\\Users\\some-reasonably-long-user-name\\AppData\\Local\\JetBrains\\IntelliJ IDEA Community Edition 2024.1.2.SNAPSHOT\\".length + 10
val directoryPathLength = instDirGuessedLength + (relativePath?.length?.let { it + 1 /* backslash */ } ?: 0)
return directoryPathLength + (files.maxOfOrNull { it.length } ?: 0)
}
private const val MAX_PATH = 260

View File

@@ -2,7 +2,6 @@
package org.jetbrains.intellij.build.impl
import com.intellij.openapi.util.SystemInfoRt
import com.intellij.openapi.util.io.FileUtil
import com.intellij.openapi.util.io.FileUtilRt
import com.intellij.openapi.util.io.NioFiles
import com.intellij.openapi.util.text.StringUtilRt
@@ -395,11 +394,10 @@ internal class WindowsDistributionBuilder(
.setAttribute("zipPath", zipPath.toString())
.setAttribute("exePath", exePath.toString())
.use {
runProcess(args = listOf("7z", "x", "-bd", exePath.toString()), workingDir = tempExe)
// deleting NSIS-related files that appear after manual unpacking of .exe installer and do not belong to its contents
@Suppress("SpellCheckingInspection")
NioFiles.deleteRecursively(tempExe.resolve("\$PLUGINSDIR"))
NioFiles.deleteRecursively(tempExe.resolve($$"$PLUGINSDIR"))
Files.deleteIfExists(tempExe.resolve("bin/Uninstall.exe.nsis"))
Files.deleteIfExists(tempExe.resolve("bin/Uninstall.exe"))
@@ -426,7 +424,7 @@ internal class WindowsDistributionBuilder(
else if (!compareStreams(fileInExe.inputStream().buffered(FileUtilRt.MEGABYTE), zipFile.getInputStream(entry).buffered(FileUtilRt.MEGABYTE))) {
differ.add(entryPath.toString())
}
FileUtil.delete(fileInExe)
NioFiles.deleteRecursively(fileInExe)
}
}
}