IJI-1455 reporting build steps of distribution build test as TeamCity tests

GitOrigin-RevId: a7837fe388570ae8be635dd57ed1fe182441c3cf
This commit is contained in:
Dmitriy.Panov
2024-07-26 18:31:11 +02:00
committed by intellij-monorepo-bot
parent ecce5f78de
commit fe99f89930
16 changed files with 392 additions and 283 deletions

View File

@@ -25,7 +25,9 @@ abstract class BuildMessageLoggerBase : BuildMessageLogger() {
val errorsString = (message as CompilationErrorsLogMessage).errorMessages.joinToString(separator = "\n") val errorsString = (message as CompilationErrorsLogMessage).errorMessages.joinToString(separator = "\n")
Span.current().addEvent("compilation errors (${message.compilerName}):\n$errorsString") Span.current().addEvent("compilation errors (${message.compilerName}):\n$errorsString")
} }
LogMessage.Kind.BUILD_CANCEL -> throw BuildScriptsLoggedError(message.text) LogMessage.Kind.BUILD_CANCEL, LogMessage.Kind.BUILD_PROBLEM -> {
throw BuildScriptsLoggedError(message.text)
}
else -> { else -> {
if (shouldBePrinted(message.kind)) { if (shouldBePrinted(message.kind)) {
Span.current().addEvent(message.text) Span.current().addEvent(message.text)

View File

@@ -104,7 +104,7 @@ class TeamCityBuildMessageLogger : BuildMessageLogger() {
print(ServiceMessageTypes.COMPILATION_FINISHED, "compiler" to compiler) print(ServiceMessageTypes.COMPILATION_FINISHED, "compiler" to compiler)
} }
DEBUG -> {} //debug messages are printed to a separate file available in the build artifacts DEBUG -> {} //debug messages are printed to a separate file available in the build artifacts
BUILD_PROBLEM -> { BUILD_PROBLEM -> { // The text is limited to 4000 symbols and will be truncated if the limit is exceeded
check(message is BuildProblemLogMessage) { check(message is BuildProblemLogMessage) {
"Unexpected build problem message type: ${message::class.java.canonicalName}" "Unexpected build problem message type: ${message::class.java.canonicalName}"
} }

View File

@@ -5,7 +5,6 @@ import com.intellij.openapi.application.PathManager
import com.intellij.platform.buildScripts.testFramework.createBuildOptionsForTest import com.intellij.platform.buildScripts.testFramework.createBuildOptionsForTest
import com.intellij.platform.buildScripts.testFramework.runEssentialPluginsTest import com.intellij.platform.buildScripts.testFramework.runEssentialPluginsTest
import com.intellij.platform.buildScripts.testFramework.runTestBuild import com.intellij.platform.buildScripts.testFramework.runTestBuild
import com.intellij.platform.buildScripts.testFramework.spanName
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import org.jetbrains.intellij.build.BuildPaths.Companion.COMMUNITY_ROOT import org.jetbrains.intellij.build.BuildPaths.Companion.COMMUNITY_ROOT
@@ -20,7 +19,7 @@ class IdeaCommunityBuildTest {
val productProperties = IdeaCommunityProperties(COMMUNITY_ROOT.communityRoot) val productProperties = IdeaCommunityProperties(COMMUNITY_ROOT.communityRoot)
runTestBuild( runTestBuild(
homeDir = homePath, homeDir = homePath,
traceSpanName = testInfo.spanName, testInfo = testInfo,
productProperties = productProperties, productProperties = productProperties,
) { ) {
it.classOutDir = System.getProperty(BuildOptions.PROJECT_CLASSES_OUTPUT_DIRECTORY_PROPERTY) ?: "$homePath/out/classes" it.classOutDir = System.getProperty(BuildOptions.PROJECT_CLASSES_OUTPUT_DIRECTORY_PROPERTY) ?: "$homePath/out/classes"
@@ -31,16 +30,17 @@ class IdeaCommunityBuildTest {
fun jpsStandalone(testInfo: TestInfo) { fun jpsStandalone(testInfo: TestInfo) {
val homePath = PathManager.getHomeDirFor(javaClass)!! val homePath = PathManager.getHomeDirFor(javaClass)!!
runBlocking(Dispatchers.Default) { runBlocking(Dispatchers.Default) {
val productProperties = IdeaCommunityProperties(COMMUNITY_ROOT.communityRoot) runTestBuild(testInfo, context = {
val options = createBuildOptionsForTest(productProperties = productProperties, homeDir = homePath, skipDependencySetup = true) val productProperties = IdeaCommunityProperties(COMMUNITY_ROOT.communityRoot)
val context = BuildContextImpl.createContext( val options = createBuildOptionsForTest(productProperties = productProperties, homeDir = homePath, skipDependencySetup = true, testInfo)
projectHome = homePath, BuildContextImpl.createContext(
productProperties = productProperties, projectHome = homePath,
setupTracer = false, productProperties = productProperties,
options = options, setupTracer = false,
) options = options,
runTestBuild(context = context, traceSpanName = testInfo.spanName) { )
buildCommunityStandaloneJpsBuilder(targetDir = context.paths.artifactDir.resolve("jps"), context = context) }) {
buildCommunityStandaloneJpsBuilder(targetDir = it.paths.artifactDir.resolve("jps"), context = it)
} }
} }
} }

View File

@@ -8,6 +8,7 @@ import kotlinx.collections.immutable.PersistentMap
import kotlinx.collections.immutable.persistentMapOf import kotlinx.collections.immutable.persistentMapOf
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.coroutineScope
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import org.jetbrains.intellij.build.productRunner.IntellijProductRunner import org.jetbrains.intellij.build.productRunner.IntellijProductRunner
import org.jetbrains.jps.model.module.JpsModule import org.jetbrains.jps.model.module.JpsModule
@@ -149,12 +150,27 @@ interface BuildContext : CompilationContext {
suspend inline fun <T> BuildContext.executeStep(spanBuilder: SpanBuilder, suspend inline fun <T> BuildContext.executeStep(spanBuilder: SpanBuilder,
stepId: String, stepId: String,
crossinline step: suspend CoroutineScope.(Span) -> T): T? { crossinline step: suspend CoroutineScope.(Span) -> T): T? {
if (isStepSkipped(stepId)) { return spanBuilder.useWithScope(Dispatchers.IO) { span ->
spanBuilder.startSpan().addEvent("skip '$stepId' step").end() try {
return null options.buildStepListener.onStart(stepId, messages)
} if (isStepSkipped(stepId)) {
else { span.addEvent("skip '$stepId' step")
return spanBuilder.useWithScope(Dispatchers.IO, step) options.buildStepListener.onSkipping(stepId, messages)
null
}
else {
coroutineScope {
step(span)
}
}
}
catch (failure: Throwable) {
options.buildStepListener.onFailure(stepId, failure, messages)
null
}
finally {
options.buildStepListener.onCompletion(stepId, messages)
}
} }
} }

View File

@@ -478,6 +478,10 @@ data class BuildOptions(
@ApiStatus.Internal @ApiStatus.Internal
var useReleaseCycleRelatedBundlingRestrictionsForContentReport: Boolean = true var useReleaseCycleRelatedBundlingRestrictionsForContentReport: Boolean = true
@set:TestOnly
@ApiStatus.Internal
var buildStepListener: BuildStepListener = BuildStepListener()
init { init {
val targetOsId = System.getProperty(TARGET_OS_PROPERTY, OS_ALL).lowercase() val targetOsId = System.getProperty(TARGET_OS_PROPERTY, OS_ALL).lowercase()
targetOs = when { targetOs = when {

View File

@@ -0,0 +1,31 @@
// Copyright 2000-2024 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
import org.jetbrains.intellij.build.BuildOptions.Companion.BUILD_STEPS_TO_SKIP_PROPERTY
import org.jetbrains.intellij.build.dependencies.TeamCityHelper
/**
* > Not to be confused with [TeamCity build steps](https://www.jetbrains.com/help/teamcity/configuring-build-steps.html)
*
* Listens to lifecycle events of each build step launched with [org.jetbrains.intellij.build.executeStep].
*/
@ApiStatus.Internal
open class BuildStepListener {
open suspend fun onStart(stepId: String, messages: BuildMessages) {}
open suspend fun onSkipping(stepId: String, messages: BuildMessages) {}
open suspend fun onFailure(stepId: String, failure: Throwable, messages: BuildMessages) {
val description = buildString {
append("'$stepId' build step failed")
if (TeamCityHelper.isUnderTeamCity) {
append(" (Please don't mute this problem!") // mute scope may be too broad muting similar failures in other build configuration
append(" If you really need to ignore it, you may either mark this build as green or add '$stepId' to 'system.${BUILD_STEPS_TO_SKIP_PROPERTY}')")
}
append(": ")
append(failure.stackTraceToString())
}
messages.reportBuildProblem(description, identity = stepId)
}
open suspend fun onCompletion(stepId: String, messages: BuildMessages) {}
}

View File

@@ -24,7 +24,7 @@ internal fun CoroutineScope.createStatisticsRecorderBundledMetadataProviderTask(
val featureUsageStatisticsPropertiesList = context.proprietaryBuildTools.featureUsageStatisticsProperties ?: return null val featureUsageStatisticsPropertiesList = context.proprietaryBuildTools.featureUsageStatisticsProperties ?: return null
return createSkippableJob( return createSkippableJob(
spanBuilder("bundle a default version of feature usage statistics"), spanBuilder("bundle a default version of feature usage statistics"),
taskId = BuildOptions.FUS_METADATA_BUNDLE_STEP, stepId = BuildOptions.FUS_METADATA_BUNDLE_STEP,
context = context context = context
) { ) {
for (featureUsageStatisticsProperties in featureUsageStatisticsPropertiesList) { for (featureUsageStatisticsProperties in featureUsageStatisticsPropertiesList) {

View File

@@ -7,29 +7,23 @@ import com.intellij.util.system.OS
import io.opentelemetry.api.trace.SpanBuilder import io.opentelemetry.api.trace.SpanBuilder
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.Runnable
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.jetbrains.intellij.build.BuildContext import org.jetbrains.intellij.build.BuildContext
import org.jetbrains.intellij.build.CompilationContext import org.jetbrains.intellij.build.CompilationContext
import org.jetbrains.intellij.build.OsFamily import org.jetbrains.intellij.build.OsFamily
import org.jetbrains.intellij.build.telemetry.TraceManager.spanBuilder import org.jetbrains.intellij.build.telemetry.TraceManager.spanBuilder
import org.jetbrains.intellij.build.executeStep
import org.jetbrains.intellij.build.io.copyDir import org.jetbrains.intellij.build.io.copyDir
import java.nio.file.Path import java.nio.file.Path
import java.util.function.Predicate import java.util.function.Predicate
inline fun CoroutineScope.createSkippableJob(spanBuilder: SpanBuilder, inline fun CoroutineScope.createSkippableJob(spanBuilder: SpanBuilder,
taskId: String, stepId: String,
context: BuildContext, context: BuildContext,
crossinline task: suspend () -> Unit): Job? { crossinline task: suspend () -> Unit): Job {
if (context.isStepSkipped(taskId)) { return launch {
spanBuilder.startSpan().addEvent("skip").end() context.executeStep(spanBuilder, stepId) {
return null task()
}
else {
return launch {
spanBuilder.useWithScope {
task()
}
} }
} }
} }

View File

@@ -246,65 +246,63 @@ internal class DistributionForOsTaskResult(
) )
private suspend fun buildOsSpecificDistributions(context: BuildContext): List<DistributionForOsTaskResult> { private suspend fun buildOsSpecificDistributions(context: BuildContext): List<DistributionForOsTaskResult> {
if (context.isStepSkipped(BuildOptions.OS_SPECIFIC_DISTRIBUTIONS_STEP)) { return context.executeStep(spanBuilder("build OS-specific distributions"), BuildOptions.OS_SPECIFIC_DISTRIBUTIONS_STEP) {
Span.current().addEvent("skip step", Attributes.of(AttributeKey.stringKey("name"), "build OS-specific distributions"))
return emptyList()
}
setLastModifiedTime(context.paths.distAllDir, context) setLastModifiedTime(context.paths.distAllDir, context)
if (context.isMacCodeSignEnabled) { if (context.isMacCodeSignEnabled) {
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
for (file in Files.newDirectoryStream(context.paths.distAllDir).use { stream -> for (file in Files.newDirectoryStream(context.paths.distAllDir).use { stream ->
stream.filter { !it.endsWith("help") && !it.endsWith("license") && !it.endsWith("lib") } stream.filter { !it.endsWith("help") && !it.endsWith("license") && !it.endsWith("lib") }
}) { }) {
launch { launch {
// todo exclude plugins - layoutAdditionalResources should perform codesign - // todo exclude plugins - layoutAdditionalResources should perform codesign -
// that's why we process files and zip in plugins (but not JARs) // that's why we process files and zip in plugins (but not JARs)
// and also kotlin compiler includes JNA // and also kotlin compiler includes JNA
recursivelySignMacBinaries(root = file, context = context) recursivelySignMacBinaries(root = file, context = context)
}
} }
} }
} }
}
val ideaPropertyFileContent = createIdeaPropertyFile(context) val ideaPropertyFileContent = createIdeaPropertyFile(context)
spanBuilder("Adjust executable permissions on common dist").use { spanBuilder("Adjust executable permissions on common dist").use {
val matchers = SUPPORTED_DISTRIBUTIONS.mapNotNull { val matchers = SUPPORTED_DISTRIBUTIONS.mapNotNull {
getOsDistributionBuilder(it.os, null, context) getOsDistributionBuilder(it.os, null, context)
}.flatMap { builder -> }.flatMap { builder ->
JvmArchitecture.entries.flatMap { arch -> JvmArchitecture.entries.flatMap { arch ->
builder.generateExecutableFilesMatchers(includeRuntime = true, arch = arch).keys builder.generateExecutableFilesMatchers(includeRuntime = true, arch = arch).keys
}
}
updateExecutablePermissions(context.paths.distAllDir, matchers)
}
return supervisorScope {
SUPPORTED_DISTRIBUTIONS.mapNotNull { (os, arch) ->
if (!context.shouldBuildDistributionForOS(os, arch)) {
return@mapNotNull null
}
val builder = getOsDistributionBuilder(os = os, ideaProperties = ideaPropertyFileContent, context = context) ?: return@mapNotNull null
val stepId = "${os.osId} ${arch.name}"
if (context.options.buildStepsToSkip.contains(stepId)) {
Span.current().addEvent("skip step", Attributes.of(AttributeKey.stringKey("id"), stepId))
return@mapNotNull null
}
async {
spanBuilder(stepId).useWithScope {
val osAndArchSpecificDistDirectory = getOsAndArchSpecificDistDirectory(osFamily = os, arch = arch, context = context)
builder.buildArtifacts(osAndArchSpecificDistPath = osAndArchSpecificDistDirectory, arch = arch)
checkClassFiles(root = osAndArchSpecificDistDirectory, context = context, isDistAll = false)
DistributionForOsTaskResult(builder = builder, arch = arch, outDir = osAndArchSpecificDistDirectory)
} }
} }
updateExecutablePermissions(context.paths.distAllDir, matchers)
} }
}.collectCompletedOrError()
supervisorScope {
SUPPORTED_DISTRIBUTIONS.mapNotNull { (os, arch) ->
if (!context.shouldBuildDistributionForOS(os, arch)) {
return@mapNotNull null
}
val builder = getOsDistributionBuilder(os = os, ideaProperties = ideaPropertyFileContent, context = context) ?: return@mapNotNull null
val stepId = "${os.osId} ${arch.name}"
if (context.options.buildStepsToSkip.contains(stepId)) {
Span.current().addEvent("skip step", Attributes.of(AttributeKey.stringKey("id"), stepId))
return@mapNotNull null
}
async {
spanBuilder(stepId).useWithScope {
val osAndArchSpecificDistDirectory = getOsAndArchSpecificDistDirectory(osFamily = os, arch = arch, context = context)
builder.buildArtifacts(osAndArchSpecificDistPath = osAndArchSpecificDistDirectory, arch = arch)
checkClassFiles(root = osAndArchSpecificDistDirectory, context = context, isDistAll = false)
DistributionForOsTaskResult(builder = builder, arch = arch, outDir = osAndArchSpecificDistDirectory)
}
}
}
}.collectCompletedOrError()
} ?: emptyList()
} }
// call only after supervisorScope // call only after supervisorScope
@@ -377,66 +375,71 @@ internal fun collectModulesToCompileForDistribution(context: BuildContext): Muta
private suspend fun compileModulesForDistribution(context: BuildContext): DistributionBuilderState { private suspend fun compileModulesForDistribution(context: BuildContext): DistributionBuilderState {
val compilationTasks = CompilationTasks.create(context) val compilationTasks = CompilationTasks.create(context)
collectModulesToCompileForDistribution(context).let { val moduleNames = collectModulesToCompileForDistribution(context)
compilationTasks.compileModules(moduleNames = it) compilationTasks.compileModules(moduleNames)
}
val productLayout = context.productProperties.productLayout val productLayout = context.productProperties.productLayout
val pluginsToPublish = getPluginLayoutsByJpsModuleNames(modules = productLayout.pluginModulesToPublish, productLayout = productLayout) val pluginsToPublish = getPluginLayoutsByJpsModuleNames(modules = productLayout.pluginModulesToPublish, productLayout = productLayout)
filterPluginsToPublish(plugins = pluginsToPublish, context = context) filterPluginsToPublish(plugins = pluginsToPublish, context = context)
var enabledPluginModules = getEnabledPluginModules(pluginsToPublish = pluginsToPublish, context = context) val enabledPluginModules = getEnabledPluginModules(pluginsToPublish = pluginsToPublish, context = context)
// computed only based on a bundled and plugins to publish lists, compatible plugins are not taken in an account by intention // computed only based on a bundled and plugins to publish lists, compatible plugins are not taken in an account by intention
val projectLibrariesUsedByPlugins = computeProjectLibsUsedByPlugins(enabledPluginModules = enabledPluginModules, context = context) val projectLibrariesUsedByPlugins = computeProjectLibsUsedByPlugins(enabledPluginModules = enabledPluginModules, context = context)
if (context.shouldBuildDistributions()) { return context.executeStep(spanBuilder("collecting compatible plugins"), BuildOptions.PROVIDED_MODULES_LIST_STEP) {
if (context.isStepSkipped(BuildOptions.PROVIDED_MODULES_LIST_STEP)) { if (!context.shouldBuildDistributions()) {
Span.current().addEvent("skip collecting compatible plugins because ${BuildOptions.PROVIDED_MODULES_LIST_STEP} was skipped") it.addEvent("skipped, no need to build distributions")
return@executeStep null
}
val providedModuleFile = context.paths.artifactDir.resolve("${context.applicationInfo.productCode}-builtinModules.json")
val platform = createPlatformLayout(context = context)
val moduleNames = getModulesForPluginsToPublish(platform = platform, pluginsToPublish = pluginsToPublish)
compilationTasks.compileModules(moduleNames)
val builtinModuleData = spanBuilder("build provided module list").useWithScope {
Files.deleteIfExists(providedModuleFile)
// start the product in headless mode using com.intellij.ide.plugins.BundledPluginsLister
context.createProductRunner().runProduct(args = listOf("listBundledPlugins", providedModuleFile.toString()))
context.productProperties.customizeBuiltinModules(context = context, builtinModulesFile = providedModuleFile)
try {
val builtinModuleData = readBuiltinModulesFile(file = providedModuleFile)
context.builtinModule = builtinModuleData
builtinModuleData
}
catch (_: NoSuchFileException) {
throw IllegalStateException("Failed to build provided modules list: $providedModuleFile doesn't exist")
}
}
context.notifyArtifactBuilt(artifactPath = providedModuleFile)
if (!productLayout.buildAllCompatiblePlugins) {
val distState = DistributionBuilderState(platform = platform, pluginsToPublish = pluginsToPublish, context = context)
buildProjectArtifacts(platform = distState.platform, enabledPluginModules = enabledPluginModules, compilationTasks = compilationTasks, context = context)
distState
} }
else { else {
val providedModuleFile = context.paths.artifactDir.resolve("${context.applicationInfo.productCode}-builtinModules.json")
val platform = createPlatformLayout(context = context)
getModulesForPluginsToPublish(platform = platform, pluginsToPublish = pluginsToPublish).let {
compilationTasks.compileModules(moduleNames = it)
}
val builtinModuleData = spanBuilder("build provided module list").useWithScope {
Files.deleteIfExists(providedModuleFile)
// start the product in headless mode using com.intellij.ide.plugins.BundledPluginsLister
context.createProductRunner().runProduct(args = listOf("listBundledPlugins", providedModuleFile.toString()))
context.productProperties.customizeBuiltinModules(context = context, builtinModulesFile = providedModuleFile)
try {
val builtinModuleData = readBuiltinModulesFile(file = providedModuleFile)
context.builtinModule = builtinModuleData
builtinModuleData
}
catch (_: NoSuchFileException) {
throw IllegalStateException("Failed to build provided modules list: $providedModuleFile doesn\'t exist")
}
}
context.notifyArtifactBuilt(artifactPath = providedModuleFile)
if (!productLayout.buildAllCompatiblePlugins) {
val distState = DistributionBuilderState(platform = platform, pluginsToPublish = pluginsToPublish, context = context)
buildProjectArtifacts(platform = distState.platform, enabledPluginModules = enabledPluginModules, compilationTasks = compilationTasks, context = context)
return distState
}
collectCompatiblePluginsToPublish(builtinModuleData = builtinModuleData, result = pluginsToPublish, context = context) collectCompatiblePluginsToPublish(builtinModuleData = builtinModuleData, result = pluginsToPublish, context = context)
filterPluginsToPublish(plugins = pluginsToPublish, context = context) filterPluginsToPublish(plugins = pluginsToPublish, context = context)
// update enabledPluginModules to reflect changes in pluginsToPublish - used for buildProjectArtifacts // update enabledPluginModules to reflect changes in pluginsToPublish - used for buildProjectArtifacts
enabledPluginModules = getEnabledPluginModules(pluginsToPublish = pluginsToPublish, context = context) val enabledPluginModules = getEnabledPluginModules(pluginsToPublish = pluginsToPublish, context = context)
distributionState(context, pluginsToPublish, projectLibrariesUsedByPlugins, enabledPluginModules)
} }
} } ?: distributionState(context, pluginsToPublish, projectLibrariesUsedByPlugins, enabledPluginModules)
}
private suspend fun distributionState(
context: BuildContext,
pluginsToPublish: Set<PluginLayout>,
projectLibrariesUsedByPlugins: SortedSet<ProjectLibraryData>,
enabledPluginModules: Set<String>,
): DistributionBuilderState {
val platform = createPlatformLayout(projectLibrariesUsedByPlugins = projectLibrariesUsedByPlugins, context = context) val platform = createPlatformLayout(projectLibrariesUsedByPlugins = projectLibrariesUsedByPlugins, context = context)
val distState = DistributionBuilderState(platform = platform, pluginsToPublish = pluginsToPublish, context = context) val distState = DistributionBuilderState(platform = platform, pluginsToPublish = pluginsToPublish, context = context)
distState.getModulesForPluginsToPublish().let { val moduleNames = distState.getModulesForPluginsToPublish()
compilationTasks.compileModules(moduleNames = it) val compilationTasks = CompilationTasks.create(context)
} compilationTasks.compileModules(moduleNames)
buildProjectArtifacts(platform = distState.platform, enabledPluginModules = enabledPluginModules, compilationTasks = compilationTasks, context = context) buildProjectArtifacts(platform = distState.platform, enabledPluginModules = enabledPluginModules, compilationTasks = compilationTasks, context = context)
return distState return distState
} }
@@ -553,7 +556,7 @@ private fun CoroutineScope.createMavenArtifactJob(context: BuildContext, distrib
} }
} }
private fun checkProductProperties(context: BuildContextImpl) { private suspend fun checkProductProperties(context: BuildContextImpl) {
checkProductLayout(context) checkProductLayout(context)
val properties = context.productProperties val properties = context.productProperties
@@ -604,7 +607,7 @@ private fun checkProductProperties(context: BuildContextImpl) {
listOfNotNull(macCustomizer.icnsPathForAlternativeIconForEAP), listOfNotNull(macCustomizer.icnsPathForAlternativeIconForEAP),
"productProperties.macCustomizer.icnsPathForAlternativeIconForEAP" "productProperties.macCustomizer.icnsPathForAlternativeIconForEAP"
) )
if (!context.isStepSkipped(BuildOptions.MAC_DMG_STEP)) { context.executeStep(spanBuilder("check .dmg images"), BuildOptions.MAC_DMG_STEP) {
checkMandatoryPath(macCustomizer.dmgImagePath, "productProperties.macCustomizer.dmgImagePath") checkMandatoryPath(macCustomizer.dmgImagePath, "productProperties.macCustomizer.dmgImagePath")
checkPaths(listOfNotNull(macCustomizer.dmgImagePathForEAP), "productProperties.macCustomizer.dmgImagePathForEAP") checkPaths(listOfNotNull(macCustomizer.dmgImagePathForEAP), "productProperties.macCustomizer.dmgImagePathForEAP")
} }
@@ -867,28 +870,30 @@ private fun buildCrossPlatformZip(distResults: List<DistributionForOsTaskResult>
private suspend fun checkClassFiles(root: Path, context: BuildContext, isDistAll: Boolean) { private suspend fun checkClassFiles(root: Path, context: BuildContext, isDistAll: Boolean) {
// version checking patterns are only for dist all (all non-os and non-arch specific files) // version checking patterns are only for dist all (all non-os and non-arch specific files)
if (!isDistAll || context.isStepSkipped(BuildOptions.VERIFY_CLASS_FILE_VERSIONS)) { if (!isDistAll) {
return return
} }
val versionCheckerConfig = context.productProperties.versionCheckerConfig context.executeStep(spanBuilder("checkClassFiles"), BuildOptions.VERIFY_CLASS_FILE_VERSIONS) {
val forbiddenSubPaths = context.productProperties.forbiddenClassFileSubPaths val versionCheckerConfig = context.productProperties.versionCheckerConfig
val forbiddenSubPathExceptions = context.productProperties.forbiddenClassFileSubPathExceptions val forbiddenSubPaths = context.productProperties.forbiddenClassFileSubPaths
if (forbiddenSubPaths.isNotEmpty()) { val forbiddenSubPathExceptions = context.productProperties.forbiddenClassFileSubPathExceptions
val forbiddenString = forbiddenSubPaths.let { "(${it.size}): ${it.joinToString()}" } if (forbiddenSubPaths.isNotEmpty()) {
val exceptionsString = forbiddenSubPathExceptions.let { "(${it.size}): ${it.joinToString()}" } val forbiddenString = forbiddenSubPaths.let { "(${it.size}): ${it.joinToString()}" }
Span.current().addEvent("checkClassFiles: forbiddenSubPaths $forbiddenString, exceptions $exceptionsString") val exceptionsString = forbiddenSubPathExceptions.let { "(${it.size}): ${it.joinToString()}" }
} it.addEvent("forbiddenSubPaths $forbiddenString, exceptions $exceptionsString")
else { }
Span.current().addEvent("checkClassFiles: forbiddenSubPaths: EMPTY (no scrambling checks will be done)") else {
} it.addEvent("forbiddenSubPaths: EMPTY (no scrambling checks will be done)")
}
if (versionCheckerConfig.isNotEmpty() || forbiddenSubPaths.isNotEmpty()) { if (versionCheckerConfig.isNotEmpty() || forbiddenSubPaths.isNotEmpty()) {
checkClassFiles(versionCheckConfig = versionCheckerConfig, forbiddenSubPaths = forbiddenSubPaths, forbiddenSubPathExceptions = forbiddenSubPathExceptions, root = root) checkClassFiles(versionCheckConfig = versionCheckerConfig, forbiddenSubPaths = forbiddenSubPaths, forbiddenSubPathExceptions = forbiddenSubPathExceptions, root = root)
} }
if (forbiddenSubPaths.isNotEmpty()) { if (forbiddenSubPaths.isNotEmpty()) {
Span.current().addEvent("checkClassFiles: SUCCESS for forbiddenSubPaths at '$root': ${forbiddenSubPaths.joinToString()}") it.addEvent("SUCCESS for forbiddenSubPaths at '$root': ${forbiddenSubPaths.joinToString()}")
}
} }
} }

View File

@@ -397,13 +397,9 @@ internal suspend fun buildNonBundledPlugins(
searchableOptionSet: SearchableOptionSetDescriptor?, searchableOptionSet: SearchableOptionSetDescriptor?,
context: BuildContext, context: BuildContext,
): List<Pair<PluginBuildDescriptor, List<DistributionFileEntry>>> { ): List<Pair<PluginBuildDescriptor, List<DistributionFileEntry>>> {
return spanBuilder("build non-bundled plugins").setAttribute("count", pluginsToPublish.size.toLong()).useWithScope { span -> return context.executeStep(spanBuilder("build non-bundled plugins").setAttribute("count", pluginsToPublish.size.toLong()), BuildOptions.NON_BUNDLED_PLUGINS_STEP) {
if (pluginsToPublish.isEmpty()) { if (pluginsToPublish.isEmpty()) {
return@useWithScope emptyList() return@executeStep emptyList()
}
if (context.isStepSkipped(BuildOptions.NON_BUNDLED_PLUGINS_STEP)) {
span.addEvent("skip")
return@useWithScope emptyList()
} }
val nonBundledPluginsArtifacts = context.paths.artifactDir.resolve("${context.applicationInfo.productCode}-plugins") val nonBundledPluginsArtifacts = context.paths.artifactDir.resolve("${context.applicationInfo.productCode}-plugins")
@@ -473,39 +469,44 @@ internal suspend fun buildNonBundledPlugins(
generatePluginRepositoryMetaFile(list.filter { it.pluginZip.startsWith(autoUploadingDir) }, autoUploadingDir, context) generatePluginRepositoryMetaFile(list.filter { it.pluginZip.startsWith(autoUploadingDir) }, autoUploadingDir, context)
} }
if (!context.isStepSkipped(BuildOptions.VALIDATE_PLUGINS_TO_BE_PUBLISHED)) { validatePlugins(context, pluginSpecs)
for (plugin in pluginSpecs) {
launch {
validatePlugin(path = plugin.pluginZip, context = context)
}
}
}
mappings mappings
} ?: emptyList()
}
private suspend fun validatePlugins(context: BuildContext, pluginSpecs: Collection<PluginRepositorySpec>) {
context.executeStep(spanBuilder("plugins validation"), BuildOptions.VALIDATE_PLUGINS_TO_BE_PUBLISHED) { span ->
for (plugin in pluginSpecs) {
val path = plugin.pluginZip
if (Files.notExists(path)) {
span.addEvent("doesn't exist, skipped", Attributes.of(AttributeKey.stringKey("path"), "$path"))
continue
}
launch {
validatePlugin(path, context, span)
}
}
} }
} }
private suspend fun validatePlugin(path: Path, context: BuildContext) { private fun validatePlugin(path: Path, context: BuildContext, span: Span) {
spanBuilder("plugin validation").setAttribute("path", path.toString()).useWithScope { span -> val pluginManager = IdePluginManager.createManager()
if (Files.notExists(path)) { val result = pluginManager.createPlugin(path, validateDescriptor = true)
span.addEvent("path doesn't exist, skipped") // todo fix AddStatisticsEventLogListenerTemporary
return@useWithScope val id = when (result) {
} is PluginCreationSuccess -> result.plugin.pluginId
is PluginCreationFail -> (pluginManager.createPlugin(path, validateDescriptor = false) as? PluginCreationSuccess)?.plugin?.pluginId
val pluginManager = IdePluginManager.createManager() }
val result = pluginManager.createPlugin(path, validateDescriptor = true) val problems = context.productProperties.validatePlugin(id, result, context)
// todo fix AddStatisticsEventLogListenerTemporary if (problems.isNotEmpty()) {
val id = when (result) { span.addEvent("failed", Attributes.of(AttributeKey.stringKey("path"), "$path"))
is PluginCreationSuccess -> result.plugin.pluginId context.messages.reportBuildProblem(
is PluginCreationFail -> (pluginManager.createPlugin(path, validateDescriptor = false) as? PluginCreationSuccess)?.plugin?.pluginId problems.joinToString(
}
val problems = context.productProperties.validatePlugin(id, result, context)
if (problems.isNotEmpty()) {
context.messages.reportBuildProblem(problems.joinToString(
prefix = "${id ?: path}: ", prefix = "${id ?: path}: ",
separator = ". ", separator = ". ",
), identity = "${id ?: path}") ), identity = "${id ?: path}"
} )
} }
} }
@@ -871,7 +872,7 @@ private suspend fun scramble(platform: PlatformLayout, context: BuildContext) {
} }
} }
private fun CoroutineScope.createBuildBrokenPluginListJob(context: BuildContext): Job? { private fun CoroutineScope.createBuildBrokenPluginListJob(context: BuildContext): Job {
val buildString = context.fullBuildNumber val buildString = context.fullBuildNumber
return createSkippableJob( return createSkippableJob(
spanBuilder("build broken plugin list").setAttribute("buildNumber", buildString), spanBuilder("build broken plugin list").setAttribute("buildNumber", buildString),
@@ -885,7 +886,7 @@ private fun CoroutineScope.createBuildBrokenPluginListJob(context: BuildContext)
} }
} }
private fun CoroutineScope.createBuildThirdPartyLibraryListJob(entries: Sequence<DistributionFileEntry>, context: BuildContext): Job? { private fun CoroutineScope.createBuildThirdPartyLibraryListJob(entries: Sequence<DistributionFileEntry>, context: BuildContext): Job {
return createSkippableJob(spanBuilder("generate table of licenses for used third-party libraries"), return createSkippableJob(spanBuilder("generate table of licenses for used third-party libraries"),
BuildOptions.THIRD_PARTY_LIBRARIES_LIST_STEP, context) { BuildOptions.THIRD_PARTY_LIBRARIES_LIST_STEP, context) {
val generator = createLibraryLicensesListGenerator( val generator = createLibraryLicensesListGenerator(

View File

@@ -121,14 +121,16 @@ class LinuxDistributionBuilder(
} }
} }
if (tarGzPath != null && !context.isStepSkipped(BuildOptions.REPAIR_UTILITY_BUNDLE_STEP)) { if (tarGzPath != null ) {
val tempTar = Files.createTempDirectory(context.paths.tempDir, "tar-") context.executeStep(spanBuilder("bundle repair utility"), BuildOptions.REPAIR_UTILITY_BUNDLE_STEP) {
try { val tempTar = Files.createTempDirectory(context.paths.tempDir, "tar-")
unTar(tarGzPath, tempTar) try {
RepairUtilityBuilder.generateManifest(context, unpackedDistribution = tempTar.resolve(rootDirectoryName), OsFamily.LINUX, arch) unTar(tarGzPath, tempTar)
} RepairUtilityBuilder.generateManifest(context, unpackedDistribution = tempTar.resolve(rootDirectoryName), OsFamily.LINUX, arch)
finally { }
NioFiles.deleteRecursively(tempTar) finally {
NioFiles.deleteRecursively(tempTar)
}
} }
} }
} }

View File

@@ -4,7 +4,6 @@
package org.jetbrains.intellij.build package org.jetbrains.intellij.build
import io.opentelemetry.api.common.AttributeKey import io.opentelemetry.api.common.AttributeKey
import io.opentelemetry.api.trace.Span
import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.serialization.Contextual import kotlinx.serialization.Contextual
@@ -68,42 +67,38 @@ internal suspend fun buildSearchableOptions(
context: BuildContext, context: BuildContext,
systemProperties: VmProperties = VmProperties(emptyMap()), systemProperties: VmProperties = VmProperties(emptyMap()),
): SearchableOptionSetDescriptor? { ): SearchableOptionSetDescriptor? {
val span = Span.current() return context.executeStep(spanBuilder("building searchable options index"), BuildOptions.SEARCHABLE_OPTIONS_INDEX_STEP) { span ->
if (context.isStepSkipped(BuildOptions.SEARCHABLE_OPTIONS_INDEX_STEP)) { val targetDirectory = context.paths.searchableOptionDir
span.addEvent("skip building searchable options index") // bundled maven is also downloaded during traverseUI execution in an external process,
return null // making it fragile to call more than one traverseUI at the same time (in the reproducibility test, for example),
// so it's pre-downloaded with proper synchronization
coroutineScope {
launch {
BundledMavenDownloader.downloadMaven4Libs(context.paths.communityHomeDirRoot)
}
launch {
BundledMavenDownloader.downloadMaven3Libs(context.paths.communityHomeDirRoot)
}
launch {
BundledMavenDownloader.downloadMavenDistribution(context.paths.communityHomeDirRoot)
}
launch {
BundledMavenDownloader.downloadMavenTelemetryDependencies(context.paths.communityHomeDirRoot)
}
}
// Start the product in headless mode using com.intellij.ide.ui.search.TraverseUIStarter.
// It'll process all UI elements in the `Settings` dialog and build an index for them.
productRunner.runProduct(
args = listOf("traverseUI", targetDirectory.toString(), "true"),
additionalVmProperties = systemProperties + VmProperties(mapOf("idea.l10n.keys" to "only")),
timeout = DEFAULT_TIMEOUT,
)
val index = readSearchableOptionIndex(targetDirectory)
span.setAttribute(AttributeKey.longKey("moduleCountWithSearchableOptions"), index.index.size)
span.setAttribute(AttributeKey.stringArrayKey("modulesWithSearchableOptions"), index.index.keys.toList())
index
} }
val targetDirectory = context.paths.searchableOptionDir
// bundled maven is also downloaded during traverseUI execution in an external process,
// making it fragile to call more than one traverseUI at the same time (in the reproducibility test, for example),
// so it's pre-downloaded with proper synchronization
coroutineScope {
launch {
BundledMavenDownloader.downloadMaven4Libs(context.paths.communityHomeDirRoot)
}
launch {
BundledMavenDownloader.downloadMaven3Libs(context.paths.communityHomeDirRoot)
}
launch {
BundledMavenDownloader.downloadMavenDistribution(context.paths.communityHomeDirRoot)
}
launch {
BundledMavenDownloader.downloadMavenTelemetryDependencies(context.paths.communityHomeDirRoot)
}
}
// Start the product in headless mode using com.intellij.ide.ui.search.TraverseUIStarter.
// It'll process all UI elements in the `Settings` dialog and build an index for them.
productRunner.runProduct(
args = listOf("traverseUI", targetDirectory.toString(), "true"),
additionalVmProperties = systemProperties + VmProperties(mapOf("idea.l10n.keys" to "only")),
timeout = DEFAULT_TIMEOUT,
)
val index = readSearchableOptionIndex(targetDirectory)
span.setAttribute(AttributeKey.longKey("moduleCountWithSearchableOptions"), index.index.size)
span.setAttribute(AttributeKey.stringArrayKey("modulesWithSearchableOptions"), index.index.keys.toList())
return index
} }

View File

@@ -0,0 +1,50 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.platform.buildScripts.testFramework
import jetbrains.buildServer.messages.serviceMessages.ServiceMessageTypes
import org.jetbrains.annotations.ApiStatus
import org.jetbrains.intellij.build.BuildMessages
import org.jetbrains.intellij.build.BuildStepListener
import org.jetbrains.intellij.build.logging.TeamCityBuildMessageLogger.Companion.SpanAwareServiceMessage
import org.junit.jupiter.api.TestInfo
/**
* See [BuildStepListener].
*
* Reports each build step launched with [org.jetbrains.intellij.build.executeStep] as a TeamCity test:
* * such a test must be started in its own dedicated [flow](https://www.jetbrains.com/help/teamcity/service-messages.html#Message+FlowId), see [org.jetbrains.intellij.build.executeStep]
* * each flow started withing a test should be a transitive subflow of that test's own flow, see [org.jetbrains.intellij.build.logging.TeamCityBuildMessageLogger.withFlow]
*
* Otherwise, tests will not be correctly displayed in a TeamCity build log.
*/
@ApiStatus.Internal
open class BuildStepTeamCityListener(testInfo: TestInfo) : BuildStepListener() {
private val testName: String = "${testInfo.testClass.get().canonicalName}.${testInfo.testMethod.orElseThrow().name}"
private fun reportTestEvent(testEvent: String, stepId: String, vararg attributes: Pair<String, String>) {
println(SpanAwareServiceMessage(testEvent, "name" to "$testName($stepId)", *attributes))
}
override suspend fun onStart(stepId: String, messages: BuildMessages) {
super.onStart(stepId, messages)
reportTestEvent(ServiceMessageTypes.TEST_STARTED, stepId)
messages.warning("This test is automatically generated from the build step '$stepId'")
messages.warning("To run it locally, please invoke '$testName'")
}
override suspend fun onSkipping(stepId: String, messages: BuildMessages) {
super.onSkipping(stepId, messages)
reportTestEvent(ServiceMessageTypes.TEST_IGNORED, stepId)
}
override suspend fun onFailure(stepId: String, failure: Throwable, messages: BuildMessages) {
// no need to throw the build step failure, just reporting it to TeamCity as a test failure,
// providing an option to mute it without muting the main test
reportTestEvent(ServiceMessageTypes.TEST_FAILED, stepId, "message" to "${failure.message}", "details" to failure.stackTraceToString())
}
override suspend fun onCompletion(stepId: String, messages: BuildMessages) {
super.onCompletion(stepId, messages)
reportTestEvent(ServiceMessageTypes.TEST_FINISHED, stepId)
}
}

View File

@@ -8,18 +8,21 @@ import org.jetbrains.intellij.build.ProductProperties
import org.jetbrains.intellij.build.ProprietaryBuildTools import org.jetbrains.intellij.build.ProprietaryBuildTools
import org.jetbrains.intellij.build.impl.readBuiltinModulesFile import org.jetbrains.intellij.build.impl.readBuiltinModulesFile
import org.junit.jupiter.api.Assertions.* import org.junit.jupiter.api.Assertions.*
import org.junit.jupiter.api.TestInfo
import java.nio.file.Path import java.nio.file.Path
/** /**
* Checks that frontend distribution (ex JetBrains Client) described by [frontendProperties] can be built successfully. * Checks that frontend distribution (ex JetBrains Client) described by [frontendProperties] can be built successfully.
*/ */
fun runTestBuildForFrontend(homePath: Path, frontendProperties: ProductProperties, buildTools: ProprietaryBuildTools, fun runTestBuildForFrontend(
traceSpanName: String, softly: SoftAssertions) { homePath: Path, frontendProperties: ProductProperties, buildTools: ProprietaryBuildTools,
testInfo: TestInfo, softly: SoftAssertions,
) {
runTestBuild( runTestBuild(
homeDir = homePath, homeDir = homePath,
productProperties = frontendProperties, productProperties = frontendProperties,
buildTools = buildTools, buildTools = buildTools,
traceSpanName = traceSpanName, testInfo = testInfo,
onSuccess = { context -> onSuccess = { context ->
verifyBuiltInModules(context.paths.artifactDir.resolve("${context.applicationInfo.productCode}-builtinModules.json")) verifyBuiltInModules(context.paths.artifactDir.resolve("${context.applicationInfo.productCode}-builtinModules.json"))
assertTrue(context.useModularLoader) { "Frontend distribution must use the modular loader, but $frontendProperties doesn't use it" } assertTrue(context.useModularLoader) { "Frontend distribution must use the modular loader, but $frontendProperties doesn't use it" }

View File

@@ -18,8 +18,10 @@ import kotlinx.coroutines.runBlocking
import org.assertj.core.api.SoftAssertions import org.assertj.core.api.SoftAssertions
import org.jetbrains.intellij.build.* import org.jetbrains.intellij.build.*
import org.jetbrains.intellij.build.telemetry.TraceManager.spanBuilder import org.jetbrains.intellij.build.telemetry.TraceManager.spanBuilder
import org.jetbrains.intellij.build.dependencies.TeamCityHelper.isUnderTeamCity
import org.jetbrains.intellij.build.impl.BuildContextImpl import org.jetbrains.intellij.build.impl.BuildContextImpl
import org.jetbrains.intellij.build.impl.buildDistributions import org.jetbrains.intellij.build.impl.buildDistributions
import org.jetbrains.intellij.build.telemetry.JaegerJsonSpanExporterManager
import org.jetbrains.intellij.build.telemetry.TraceManager import org.jetbrains.intellij.build.telemetry.TraceManager
import org.jetbrains.intellij.build.telemetry.useWithScope import org.jetbrains.intellij.build.telemetry.useWithScope
import org.junit.jupiter.api.TestInfo import org.junit.jupiter.api.TestInfo
@@ -28,10 +30,10 @@ import java.net.http.HttpConnectTimeoutException
import java.nio.file.Files import java.nio.file.Files
import java.nio.file.Path import java.nio.file.Path
fun createBuildOptionsForTest(productProperties: ProductProperties, homeDir: Path, skipDependencySetup: Boolean = false): BuildOptions { fun createBuildOptionsForTest(productProperties: ProductProperties, homeDir: Path, skipDependencySetup: Boolean = false, testInfo: TestInfo? = null): BuildOptions {
val outDir = createTestBuildOutDir(productProperties) val outDir = createTestBuildOutDir(productProperties)
val options = BuildOptions(cleanOutDir = false, useCompiledClassesFromProjectOutput = true, jarCacheDir = homeDir.resolve("out/dev-run/jar-cache")) val options = BuildOptions(cleanOutDir = false, useCompiledClassesFromProjectOutput = true, jarCacheDir = homeDir.resolve("out/dev-run/jar-cache"))
customizeBuildOptionsForTest(options = options, outDir = outDir, skipDependencySetup = skipDependencySetup) customizeBuildOptionsForTest(options = options, outDir = outDir, skipDependencySetup = skipDependencySetup, testInfo = testInfo)
return options return options
} }
@@ -39,13 +41,13 @@ fun createTestBuildOutDir(productProperties: ProductProperties): Path {
return FileUtil.createTempDirectory("test-build-${productProperties.baseFileName}", null, false).toPath() return FileUtil.createTempDirectory("test-build-${productProperties.baseFileName}", null, false).toPath()
} }
private inline fun createBuildOptionsForTest(productProperties: ProductProperties, homeDir: Path, customizer: (BuildOptions) -> Unit): BuildOptions { private inline fun createBuildOptionsForTest(productProperties: ProductProperties, homeDir: Path, testInfo: TestInfo, customizer: (BuildOptions) -> Unit): BuildOptions {
val options = createBuildOptionsForTest(productProperties = productProperties, homeDir = homeDir) val options = createBuildOptionsForTest(productProperties = productProperties, homeDir = homeDir, testInfo = testInfo)
customizer(options) customizer(options)
return options return options
} }
fun customizeBuildOptionsForTest(options: BuildOptions, outDir: Path, skipDependencySetup: Boolean = false) { fun customizeBuildOptionsForTest(options: BuildOptions, outDir: Path, skipDependencySetup: Boolean = false, testInfo: TestInfo?) {
options.skipDependencySetup = skipDependencySetup options.skipDependencySetup = skipDependencySetup
options.isTestBuild = true options.isTestBuild = true
@@ -56,11 +58,15 @@ fun customizeBuildOptionsForTest(options: BuildOptions, outDir: Path, skipDepend
BuildOptions.WIN_SIGN_STEP, BuildOptions.WIN_SIGN_STEP,
BuildOptions.MAC_SIGN_STEP, BuildOptions.MAC_SIGN_STEP,
BuildOptions.MAC_NOTARIZE_STEP, BuildOptions.MAC_NOTARIZE_STEP,
BuildOptions.MAC_DMG_STEP,
) )
options.buildUnixSnaps = false options.buildUnixSnaps = false
options.outRootDir = outDir options.outRootDir = outDir
options.useCompiledClassesFromProjectOutput = true options.useCompiledClassesFromProjectOutput = true
options.compilationLogEnabled = false options.compilationLogEnabled = false
if (testInfo != null && isUnderTeamCity) {
options.buildStepListener = BuildStepTeamCityListener(testInfo)
}
} }
suspend inline fun createBuildContext( suspend inline fun createBuildContext(
@@ -78,7 +84,7 @@ suspend inline fun createBuildContext(
fun runTestBuild( fun runTestBuild(
homePath: Path, homePath: Path,
productProperties: ProductProperties, productProperties: ProductProperties,
traceSpanName: String, testInfo: TestInfo,
buildTools: ProprietaryBuildTools, buildTools: ProprietaryBuildTools,
buildOptionsCustomizer: (BuildOptions) -> Unit = {}, buildOptionsCustomizer: (BuildOptions) -> Unit = {},
) { ) {
@@ -86,7 +92,7 @@ fun runTestBuild(
homeDir = homePath, homeDir = homePath,
productProperties = productProperties, productProperties = productProperties,
buildTools = buildTools, buildTools = buildTools,
traceSpanName = traceSpanName, testInfo = testInfo,
isReproducibilityTestAllowed = true, isReproducibilityTestAllowed = true,
buildOptionsCustomizer = buildOptionsCustomizer, buildOptionsCustomizer = buildOptionsCustomizer,
) )
@@ -96,28 +102,29 @@ fun runTestBuild(
homeDir: Path, homeDir: Path,
productProperties: ProductProperties, productProperties: ProductProperties,
buildTools: ProprietaryBuildTools = ProprietaryBuildTools.DUMMY, buildTools: ProprietaryBuildTools = ProprietaryBuildTools.DUMMY,
traceSpanName: String, testInfo: TestInfo,
isReproducibilityTestAllowed: Boolean = true, isReproducibilityTestAllowed: Boolean = true,
build: suspend (context: BuildContext) -> Unit = { buildDistributions(it) }, build: suspend (BuildContext) -> Unit = { buildDistributions(it) },
onSuccess: suspend (context: BuildContext) -> Unit = {}, onSuccess: suspend (BuildContext) -> Unit = {},
buildOptionsCustomizer: (BuildOptions) -> Unit = {} buildOptionsCustomizer: (BuildOptions) -> Unit = {}
) = runBlocking(Dispatchers.Default) { ) = runBlocking(Dispatchers.Default) {
if (isReproducibilityTestAllowed) { if (isReproducibilityTestAllowed && BuildArtifactsReproducibilityTest.isEnabled) {
val reproducibilityTest = BuildArtifactsReproducibilityTest() val reproducibilityTest = BuildArtifactsReproducibilityTest()
repeat(reproducibilityTest.iterations) { iterationNumber -> repeat(reproducibilityTest.iterations) { iterationNumber ->
launch { launch {
val buildContext = BuildContextImpl.createContext(
projectHome = homeDir,
productProperties = productProperties,
proprietaryBuildTools = buildTools,
setupTracer = false,
options = createBuildOptionsForTest(productProperties = productProperties, homeDir = homeDir, customizer = buildOptionsCustomizer).also {
reproducibilityTest.configure(it)
},
)
doRunTestBuild( doRunTestBuild(
context = buildContext, context = {
traceSpanName = "#$iterationNumber", BuildContextImpl.createContext(
projectHome = homeDir,
productProperties = productProperties,
proprietaryBuildTools = buildTools,
setupTracer = false,
options = createBuildOptionsForTest(productProperties = productProperties, homeDir = homeDir, testInfo, customizer = buildOptionsCustomizer).also {
reproducibilityTest.configure(it)
},
)
},
traceSpanName = "${testInfo.spanName}#$iterationNumber",
writeTelemetry = false, writeTelemetry = false,
build = { context -> build = { context ->
build(context) build(context)
@@ -130,15 +137,17 @@ fun runTestBuild(
} }
else { else {
doRunTestBuild( doRunTestBuild(
context = BuildContextImpl.createContext( context = {
projectHome = homeDir, BuildContextImpl.createContext(
productProperties = productProperties, projectHome = homeDir,
proprietaryBuildTools = buildTools, productProperties = productProperties,
setupTracer = false, proprietaryBuildTools = buildTools,
options = createBuildOptionsForTest(productProperties = productProperties, homeDir = homeDir, customizer = buildOptionsCustomizer), setupTracer = false,
), options = createBuildOptionsForTest(productProperties = productProperties, homeDir = homeDir, testInfo, customizer = buildOptionsCustomizer),
)
},
writeTelemetry = true, writeTelemetry = true,
traceSpanName = traceSpanName, traceSpanName = testInfo.spanName,
build = { context -> build = { context ->
build(context) build(context)
onSuccess(context) onSuccess(context)
@@ -149,33 +158,30 @@ fun runTestBuild(
// FIXME: test reproducibility // FIXME: test reproducibility
suspend fun runTestBuild( suspend fun runTestBuild(
context: BuildContext, testInfo: TestInfo,
traceSpanName: String, context: suspend () -> BuildContext,
build: suspend (context: BuildContext) -> Unit = { buildDistributions(it) } build: suspend (BuildContext) -> Unit = { buildDistributions(it) }
) { ) {
doRunTestBuild(context = context, traceSpanName = traceSpanName, writeTelemetry = true, build = build) doRunTestBuild(context = context, traceSpanName = testInfo.spanName, writeTelemetry = true, build = build)
} }
private val defaultLogFactory = Logger.getFactory() private val defaultLogFactory = Logger.getFactory()
private suspend fun doRunTestBuild(context: BuildContext, traceSpanName: String?, writeTelemetry: Boolean, build: suspend (context: BuildContext) -> Unit) { private suspend fun doRunTestBuild(context: suspend () -> BuildContext, traceSpanName: String, writeTelemetry: Boolean, build: suspend (context: BuildContext) -> Unit) {
context.cleanupJarCache() var outDir: Path? = null
var traceFile: Path? = null
val traceFile = if (writeTelemetry) {
val traceFile = TestLoggerFactory.getTestLogDir().resolve("${context.productProperties.baseFileName}-$traceSpanName-trace.json")
JaegerJsonSpanExporterManager.setOutput(traceFile, addShutDownHook = false)
traceFile
}
else {
null
}
val outDir = context.paths.buildOutputDir
var error: Throwable? = null var error: Throwable? = null
try { try {
spanBuilder(traceSpanName ?: "test build of ${context.productProperties.baseFileName}") spanBuilder(traceSpanName).useWithScope { span ->
.setAttribute("outDir", outDir.toString()) val context = context()
.useWithScope { span -> context.cleanupJarCache()
outDir = context.paths.buildOutputDir
span.setAttribute("outDir", "$outDir")
if (writeTelemetry) {
traceFile = TestLoggerFactory.getTestLogDir().resolve("${context.productProperties.baseFileName}-$traceSpanName-trace.json").also {
JaegerJsonSpanExporterManager.setOutput(it, addShutDownHook = false)
}
}
try { try {
build(context) build(context)
val jetBrainsClientMainModule = context.productProperties.embeddedJetBrainsClientMainModule val jetBrainsClientMainModule = context.productProperties.embeddedJetBrainsClientMainModule
@@ -203,7 +209,11 @@ private suspend fun doRunTestBuild(context: BuildContext, traceSpanName: String?
error = e error = e
} }
} }
} finally {
// close debug logging to prevent locking of the output directory on Windows
context.messages.close()
}
}
} }
finally { finally {
closeKtorClient() closeKtorClient()
@@ -213,16 +223,13 @@ private suspend fun doRunTestBuild(context: BuildContext, traceSpanName: String?
println("Performance report is written to $traceFile") println("Performance report is written to $traceFile")
} }
// close debug logging to prevent locking of output directory on Windows
context.messages.close()
/** /**
* Overridden in [org.jetbrains.intellij.build.impl.JpsCompilationRunner.runBuild] * Overridden in [org.jetbrains.intellij.build.impl.JpsCompilationRunner.runBuild]
*/ */
Logger.setFactory(defaultLogFactory) Logger.setFactory(defaultLogFactory)
try { try {
NioFiles.deleteRecursively(outDir) outDir?.also(NioFiles::deleteRecursively)
} }
catch (e: Throwable) { catch (e: Throwable) {
System.err.println("cannot cleanup $outDir:") System.err.println("cannot cleanup $outDir:")

View File

@@ -3,7 +3,6 @@ package org.jetbrains.intellij.build.pycharm
import com.intellij.openapi.application.PathManager import com.intellij.openapi.application.PathManager
import com.intellij.platform.buildScripts.testFramework.runTestBuild import com.intellij.platform.buildScripts.testFramework.runTestBuild
import com.intellij.platform.buildScripts.testFramework.spanName
import com.intellij.util.io.Compressor import com.intellij.util.io.Compressor
import org.jetbrains.intellij.build.BuildOptions import org.jetbrains.intellij.build.BuildOptions
import org.jetbrains.intellij.build.dependencies.BuildDependenciesCommunityRoot import org.jetbrains.intellij.build.dependencies.BuildDependenciesCommunityRoot
@@ -42,7 +41,7 @@ class PyCharmCommunityBuildTest {
val communityHomePath = BuildDependenciesCommunityRoot(homePath.resolve("community")) val communityHomePath = BuildDependenciesCommunityRoot(homePath.resolve("community"))
runTestBuild( runTestBuild(
homeDir = communityHomePath.communityRoot, homeDir = communityHomePath.communityRoot,
traceSpanName = testInfo.spanName, testInfo = testInfo,
productProperties = PyCharmCommunityProperties(communityHomePath.communityRoot), productProperties = PyCharmCommunityProperties(communityHomePath.communityRoot),
) { ) {
it.classOutDir = System.getProperty(BuildOptions.PROJECT_CLASSES_OUTPUT_DIRECTORY_PROPERTY) it.classOutDir = System.getProperty(BuildOptions.PROJECT_CLASSES_OUTPUT_DIRECTORY_PROPERTY)