mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-02-04 06:36:56 +07:00
IJI-1455 reporting build steps of distribution build test as TeamCity tests
GitOrigin-RevId: a7837fe388570ae8be635dd57ed1fe182441c3cf
This commit is contained in:
committed by
intellij-monorepo-bot
parent
ecce5f78de
commit
fe99f89930
@@ -25,7 +25,9 @@ abstract class BuildMessageLoggerBase : BuildMessageLogger() {
|
||||
val errorsString = (message as CompilationErrorsLogMessage).errorMessages.joinToString(separator = "\n")
|
||||
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 -> {
|
||||
if (shouldBePrinted(message.kind)) {
|
||||
Span.current().addEvent(message.text)
|
||||
|
||||
@@ -104,7 +104,7 @@ class TeamCityBuildMessageLogger : BuildMessageLogger() {
|
||||
print(ServiceMessageTypes.COMPILATION_FINISHED, "compiler" to compiler)
|
||||
}
|
||||
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) {
|
||||
"Unexpected build problem message type: ${message::class.java.canonicalName}"
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ import com.intellij.openapi.application.PathManager
|
||||
import com.intellij.platform.buildScripts.testFramework.createBuildOptionsForTest
|
||||
import com.intellij.platform.buildScripts.testFramework.runEssentialPluginsTest
|
||||
import com.intellij.platform.buildScripts.testFramework.runTestBuild
|
||||
import com.intellij.platform.buildScripts.testFramework.spanName
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.jetbrains.intellij.build.BuildPaths.Companion.COMMUNITY_ROOT
|
||||
@@ -20,7 +19,7 @@ class IdeaCommunityBuildTest {
|
||||
val productProperties = IdeaCommunityProperties(COMMUNITY_ROOT.communityRoot)
|
||||
runTestBuild(
|
||||
homeDir = homePath,
|
||||
traceSpanName = testInfo.spanName,
|
||||
testInfo = testInfo,
|
||||
productProperties = productProperties,
|
||||
) {
|
||||
it.classOutDir = System.getProperty(BuildOptions.PROJECT_CLASSES_OUTPUT_DIRECTORY_PROPERTY) ?: "$homePath/out/classes"
|
||||
@@ -31,16 +30,17 @@ class IdeaCommunityBuildTest {
|
||||
fun jpsStandalone(testInfo: TestInfo) {
|
||||
val homePath = PathManager.getHomeDirFor(javaClass)!!
|
||||
runBlocking(Dispatchers.Default) {
|
||||
val productProperties = IdeaCommunityProperties(COMMUNITY_ROOT.communityRoot)
|
||||
val options = createBuildOptionsForTest(productProperties = productProperties, homeDir = homePath, skipDependencySetup = true)
|
||||
val context = BuildContextImpl.createContext(
|
||||
projectHome = homePath,
|
||||
productProperties = productProperties,
|
||||
setupTracer = false,
|
||||
options = options,
|
||||
)
|
||||
runTestBuild(context = context, traceSpanName = testInfo.spanName) {
|
||||
buildCommunityStandaloneJpsBuilder(targetDir = context.paths.artifactDir.resolve("jps"), context = context)
|
||||
runTestBuild(testInfo, context = {
|
||||
val productProperties = IdeaCommunityProperties(COMMUNITY_ROOT.communityRoot)
|
||||
val options = createBuildOptionsForTest(productProperties = productProperties, homeDir = homePath, skipDependencySetup = true, testInfo)
|
||||
BuildContextImpl.createContext(
|
||||
projectHome = homePath,
|
||||
productProperties = productProperties,
|
||||
setupTracer = false,
|
||||
options = options,
|
||||
)
|
||||
}) {
|
||||
buildCommunityStandaloneJpsBuilder(targetDir = it.paths.artifactDir.resolve("jps"), context = it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import kotlinx.collections.immutable.PersistentMap
|
||||
import kotlinx.collections.immutable.persistentMapOf
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
import kotlinx.serialization.Serializable
|
||||
import org.jetbrains.intellij.build.productRunner.IntellijProductRunner
|
||||
import org.jetbrains.jps.model.module.JpsModule
|
||||
@@ -149,12 +150,27 @@ interface BuildContext : CompilationContext {
|
||||
suspend inline fun <T> BuildContext.executeStep(spanBuilder: SpanBuilder,
|
||||
stepId: String,
|
||||
crossinline step: suspend CoroutineScope.(Span) -> T): T? {
|
||||
if (isStepSkipped(stepId)) {
|
||||
spanBuilder.startSpan().addEvent("skip '$stepId' step").end()
|
||||
return null
|
||||
}
|
||||
else {
|
||||
return spanBuilder.useWithScope(Dispatchers.IO, step)
|
||||
return spanBuilder.useWithScope(Dispatchers.IO) { span ->
|
||||
try {
|
||||
options.buildStepListener.onStart(stepId, messages)
|
||||
if (isStepSkipped(stepId)) {
|
||||
span.addEvent("skip '$stepId' 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -478,6 +478,10 @@ data class BuildOptions(
|
||||
@ApiStatus.Internal
|
||||
var useReleaseCycleRelatedBundlingRestrictionsForContentReport: Boolean = true
|
||||
|
||||
@set:TestOnly
|
||||
@ApiStatus.Internal
|
||||
var buildStepListener: BuildStepListener = BuildStepListener()
|
||||
|
||||
init {
|
||||
val targetOsId = System.getProperty(TARGET_OS_PROPERTY, OS_ALL).lowercase()
|
||||
targetOs = when {
|
||||
|
||||
@@ -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) {}
|
||||
}
|
||||
@@ -24,7 +24,7 @@ internal fun CoroutineScope.createStatisticsRecorderBundledMetadataProviderTask(
|
||||
val featureUsageStatisticsPropertiesList = context.proprietaryBuildTools.featureUsageStatisticsProperties ?: return null
|
||||
return createSkippableJob(
|
||||
spanBuilder("bundle a default version of feature usage statistics"),
|
||||
taskId = BuildOptions.FUS_METADATA_BUNDLE_STEP,
|
||||
stepId = BuildOptions.FUS_METADATA_BUNDLE_STEP,
|
||||
context = context
|
||||
) {
|
||||
for (featureUsageStatisticsProperties in featureUsageStatisticsPropertiesList) {
|
||||
|
||||
@@ -7,29 +7,23 @@ import com.intellij.util.system.OS
|
||||
import io.opentelemetry.api.trace.SpanBuilder
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.Runnable
|
||||
import kotlinx.coroutines.launch
|
||||
import org.jetbrains.intellij.build.BuildContext
|
||||
import org.jetbrains.intellij.build.CompilationContext
|
||||
import org.jetbrains.intellij.build.OsFamily
|
||||
import org.jetbrains.intellij.build.telemetry.TraceManager.spanBuilder
|
||||
import org.jetbrains.intellij.build.executeStep
|
||||
import org.jetbrains.intellij.build.io.copyDir
|
||||
import java.nio.file.Path
|
||||
import java.util.function.Predicate
|
||||
|
||||
inline fun CoroutineScope.createSkippableJob(spanBuilder: SpanBuilder,
|
||||
taskId: String,
|
||||
stepId: String,
|
||||
context: BuildContext,
|
||||
crossinline task: suspend () -> Unit): Job? {
|
||||
if (context.isStepSkipped(taskId)) {
|
||||
spanBuilder.startSpan().addEvent("skip").end()
|
||||
return null
|
||||
}
|
||||
else {
|
||||
return launch {
|
||||
spanBuilder.useWithScope {
|
||||
task()
|
||||
}
|
||||
crossinline task: suspend () -> Unit): Job {
|
||||
return launch {
|
||||
context.executeStep(spanBuilder, stepId) {
|
||||
task()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -246,65 +246,63 @@ internal class DistributionForOsTaskResult(
|
||||
)
|
||||
|
||||
private suspend fun buildOsSpecificDistributions(context: BuildContext): List<DistributionForOsTaskResult> {
|
||||
if (context.isStepSkipped(BuildOptions.OS_SPECIFIC_DISTRIBUTIONS_STEP)) {
|
||||
Span.current().addEvent("skip step", Attributes.of(AttributeKey.stringKey("name"), "build OS-specific distributions"))
|
||||
return emptyList()
|
||||
}
|
||||
return context.executeStep(spanBuilder("build OS-specific distributions"), BuildOptions.OS_SPECIFIC_DISTRIBUTIONS_STEP) {
|
||||
|
||||
setLastModifiedTime(context.paths.distAllDir, context)
|
||||
setLastModifiedTime(context.paths.distAllDir, context)
|
||||
|
||||
if (context.isMacCodeSignEnabled) {
|
||||
withContext(Dispatchers.IO) {
|
||||
for (file in Files.newDirectoryStream(context.paths.distAllDir).use { stream ->
|
||||
stream.filter { !it.endsWith("help") && !it.endsWith("license") && !it.endsWith("lib") }
|
||||
}) {
|
||||
launch {
|
||||
// todo exclude plugins - layoutAdditionalResources should perform codesign -
|
||||
// that's why we process files and zip in plugins (but not JARs)
|
||||
// and also kotlin compiler includes JNA
|
||||
recursivelySignMacBinaries(root = file, context = context)
|
||||
if (context.isMacCodeSignEnabled) {
|
||||
withContext(Dispatchers.IO) {
|
||||
for (file in Files.newDirectoryStream(context.paths.distAllDir).use { stream ->
|
||||
stream.filter { !it.endsWith("help") && !it.endsWith("license") && !it.endsWith("lib") }
|
||||
}) {
|
||||
launch {
|
||||
// todo exclude plugins - layoutAdditionalResources should perform codesign -
|
||||
// that's why we process files and zip in plugins (but not JARs)
|
||||
// and also kotlin compiler includes JNA
|
||||
recursivelySignMacBinaries(root = file, context = context)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val ideaPropertyFileContent = createIdeaPropertyFile(context)
|
||||
val ideaPropertyFileContent = createIdeaPropertyFile(context)
|
||||
|
||||
spanBuilder("Adjust executable permissions on common dist").use {
|
||||
val matchers = SUPPORTED_DISTRIBUTIONS.mapNotNull {
|
||||
getOsDistributionBuilder(it.os, null, context)
|
||||
}.flatMap { builder ->
|
||||
JvmArchitecture.entries.flatMap { arch ->
|
||||
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)
|
||||
spanBuilder("Adjust executable permissions on common dist").use {
|
||||
val matchers = SUPPORTED_DISTRIBUTIONS.mapNotNull {
|
||||
getOsDistributionBuilder(it.os, null, context)
|
||||
}.flatMap { builder ->
|
||||
JvmArchitecture.entries.flatMap { arch ->
|
||||
builder.generateExecutableFilesMatchers(includeRuntime = true, arch = arch).keys
|
||||
}
|
||||
}
|
||||
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
|
||||
@@ -377,66 +375,71 @@ internal fun collectModulesToCompileForDistribution(context: BuildContext): Muta
|
||||
|
||||
private suspend fun compileModulesForDistribution(context: BuildContext): DistributionBuilderState {
|
||||
val compilationTasks = CompilationTasks.create(context)
|
||||
collectModulesToCompileForDistribution(context).let {
|
||||
compilationTasks.compileModules(moduleNames = it)
|
||||
}
|
||||
val moduleNames = collectModulesToCompileForDistribution(context)
|
||||
compilationTasks.compileModules(moduleNames)
|
||||
|
||||
val productLayout = context.productProperties.productLayout
|
||||
val pluginsToPublish = getPluginLayoutsByJpsModuleNames(modules = productLayout.pluginModulesToPublish, productLayout = productLayout)
|
||||
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
|
||||
val projectLibrariesUsedByPlugins = computeProjectLibsUsedByPlugins(enabledPluginModules = enabledPluginModules, context = context)
|
||||
|
||||
if (context.shouldBuildDistributions()) {
|
||||
if (context.isStepSkipped(BuildOptions.PROVIDED_MODULES_LIST_STEP)) {
|
||||
Span.current().addEvent("skip collecting compatible plugins because ${BuildOptions.PROVIDED_MODULES_LIST_STEP} was skipped")
|
||||
return context.executeStep(spanBuilder("collecting compatible plugins"), BuildOptions.PROVIDED_MODULES_LIST_STEP) {
|
||||
if (!context.shouldBuildDistributions()) {
|
||||
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 {
|
||||
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)
|
||||
filterPluginsToPublish(plugins = pluginsToPublish, context = context)
|
||||
|
||||
// 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 distState = DistributionBuilderState(platform = platform, pluginsToPublish = pluginsToPublish, context = context)
|
||||
distState.getModulesForPluginsToPublish().let {
|
||||
compilationTasks.compileModules(moduleNames = it)
|
||||
}
|
||||
|
||||
val moduleNames = distState.getModulesForPluginsToPublish()
|
||||
val compilationTasks = CompilationTasks.create(context)
|
||||
compilationTasks.compileModules(moduleNames)
|
||||
buildProjectArtifacts(platform = distState.platform, enabledPluginModules = enabledPluginModules, compilationTasks = compilationTasks, context = context)
|
||||
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)
|
||||
|
||||
val properties = context.productProperties
|
||||
@@ -604,7 +607,7 @@ private fun checkProductProperties(context: BuildContextImpl) {
|
||||
listOfNotNull(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")
|
||||
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) {
|
||||
// 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
|
||||
}
|
||||
|
||||
val versionCheckerConfig = context.productProperties.versionCheckerConfig
|
||||
val forbiddenSubPaths = context.productProperties.forbiddenClassFileSubPaths
|
||||
val forbiddenSubPathExceptions = context.productProperties.forbiddenClassFileSubPathExceptions
|
||||
if (forbiddenSubPaths.isNotEmpty()) {
|
||||
val forbiddenString = forbiddenSubPaths.let { "(${it.size}): ${it.joinToString()}" }
|
||||
val exceptionsString = forbiddenSubPathExceptions.let { "(${it.size}): ${it.joinToString()}" }
|
||||
Span.current().addEvent("checkClassFiles: forbiddenSubPaths $forbiddenString, exceptions $exceptionsString")
|
||||
}
|
||||
else {
|
||||
Span.current().addEvent("checkClassFiles: forbiddenSubPaths: EMPTY (no scrambling checks will be done)")
|
||||
}
|
||||
context.executeStep(spanBuilder("checkClassFiles"), BuildOptions.VERIFY_CLASS_FILE_VERSIONS) {
|
||||
val versionCheckerConfig = context.productProperties.versionCheckerConfig
|
||||
val forbiddenSubPaths = context.productProperties.forbiddenClassFileSubPaths
|
||||
val forbiddenSubPathExceptions = context.productProperties.forbiddenClassFileSubPathExceptions
|
||||
if (forbiddenSubPaths.isNotEmpty()) {
|
||||
val forbiddenString = forbiddenSubPaths.let { "(${it.size}): ${it.joinToString()}" }
|
||||
val exceptionsString = forbiddenSubPathExceptions.let { "(${it.size}): ${it.joinToString()}" }
|
||||
it.addEvent("forbiddenSubPaths $forbiddenString, exceptions $exceptionsString")
|
||||
}
|
||||
else {
|
||||
it.addEvent("forbiddenSubPaths: EMPTY (no scrambling checks will be done)")
|
||||
}
|
||||
|
||||
if (versionCheckerConfig.isNotEmpty() || forbiddenSubPaths.isNotEmpty()) {
|
||||
checkClassFiles(versionCheckConfig = versionCheckerConfig, forbiddenSubPaths = forbiddenSubPaths, forbiddenSubPathExceptions = forbiddenSubPathExceptions, root = root)
|
||||
}
|
||||
if (versionCheckerConfig.isNotEmpty() || forbiddenSubPaths.isNotEmpty()) {
|
||||
checkClassFiles(versionCheckConfig = versionCheckerConfig, forbiddenSubPaths = forbiddenSubPaths, forbiddenSubPathExceptions = forbiddenSubPathExceptions, root = root)
|
||||
}
|
||||
|
||||
if (forbiddenSubPaths.isNotEmpty()) {
|
||||
Span.current().addEvent("checkClassFiles: SUCCESS for forbiddenSubPaths at '$root': ${forbiddenSubPaths.joinToString()}")
|
||||
if (forbiddenSubPaths.isNotEmpty()) {
|
||||
it.addEvent("SUCCESS for forbiddenSubPaths at '$root': ${forbiddenSubPaths.joinToString()}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -397,13 +397,9 @@ internal suspend fun buildNonBundledPlugins(
|
||||
searchableOptionSet: SearchableOptionSetDescriptor?,
|
||||
context: BuildContext,
|
||||
): 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()) {
|
||||
return@useWithScope emptyList()
|
||||
}
|
||||
if (context.isStepSkipped(BuildOptions.NON_BUNDLED_PLUGINS_STEP)) {
|
||||
span.addEvent("skip")
|
||||
return@useWithScope emptyList()
|
||||
return@executeStep emptyList()
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
if (!context.isStepSkipped(BuildOptions.VALIDATE_PLUGINS_TO_BE_PUBLISHED)) {
|
||||
for (plugin in pluginSpecs) {
|
||||
launch {
|
||||
validatePlugin(path = plugin.pluginZip, context = context)
|
||||
}
|
||||
}
|
||||
}
|
||||
validatePlugins(context, pluginSpecs)
|
||||
|
||||
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) {
|
||||
spanBuilder("plugin validation").setAttribute("path", path.toString()).useWithScope { span ->
|
||||
if (Files.notExists(path)) {
|
||||
span.addEvent("path doesn't exist, skipped")
|
||||
return@useWithScope
|
||||
}
|
||||
|
||||
val pluginManager = IdePluginManager.createManager()
|
||||
val result = pluginManager.createPlugin(path, validateDescriptor = true)
|
||||
// todo fix AddStatisticsEventLogListenerTemporary
|
||||
val id = when (result) {
|
||||
is PluginCreationSuccess -> result.plugin.pluginId
|
||||
is PluginCreationFail -> (pluginManager.createPlugin(path, validateDescriptor = false) as? PluginCreationSuccess)?.plugin?.pluginId
|
||||
}
|
||||
val problems = context.productProperties.validatePlugin(id, result, context)
|
||||
if (problems.isNotEmpty()) {
|
||||
context.messages.reportBuildProblem(problems.joinToString(
|
||||
private fun validatePlugin(path: Path, context: BuildContext, span: Span) {
|
||||
val pluginManager = IdePluginManager.createManager()
|
||||
val result = pluginManager.createPlugin(path, validateDescriptor = true)
|
||||
// todo fix AddStatisticsEventLogListenerTemporary
|
||||
val id = when (result) {
|
||||
is PluginCreationSuccess -> result.plugin.pluginId
|
||||
is PluginCreationFail -> (pluginManager.createPlugin(path, validateDescriptor = false) as? PluginCreationSuccess)?.plugin?.pluginId
|
||||
}
|
||||
val problems = context.productProperties.validatePlugin(id, result, context)
|
||||
if (problems.isNotEmpty()) {
|
||||
span.addEvent("failed", Attributes.of(AttributeKey.stringKey("path"), "$path"))
|
||||
context.messages.reportBuildProblem(
|
||||
problems.joinToString(
|
||||
prefix = "${id ?: path}: ",
|
||||
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
|
||||
return createSkippableJob(
|
||||
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"),
|
||||
BuildOptions.THIRD_PARTY_LIBRARIES_LIST_STEP, context) {
|
||||
val generator = createLibraryLicensesListGenerator(
|
||||
|
||||
@@ -121,14 +121,16 @@ class LinuxDistributionBuilder(
|
||||
}
|
||||
}
|
||||
|
||||
if (tarGzPath != null && !context.isStepSkipped(BuildOptions.REPAIR_UTILITY_BUNDLE_STEP)) {
|
||||
val tempTar = Files.createTempDirectory(context.paths.tempDir, "tar-")
|
||||
try {
|
||||
unTar(tarGzPath, tempTar)
|
||||
RepairUtilityBuilder.generateManifest(context, unpackedDistribution = tempTar.resolve(rootDirectoryName), OsFamily.LINUX, arch)
|
||||
}
|
||||
finally {
|
||||
NioFiles.deleteRecursively(tempTar)
|
||||
if (tarGzPath != null ) {
|
||||
context.executeStep(spanBuilder("bundle repair utility"), BuildOptions.REPAIR_UTILITY_BUNDLE_STEP) {
|
||||
val tempTar = Files.createTempDirectory(context.paths.tempDir, "tar-")
|
||||
try {
|
||||
unTar(tarGzPath, tempTar)
|
||||
RepairUtilityBuilder.generateManifest(context, unpackedDistribution = tempTar.resolve(rootDirectoryName), OsFamily.LINUX, arch)
|
||||
}
|
||||
finally {
|
||||
NioFiles.deleteRecursively(tempTar)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
package org.jetbrains.intellij.build
|
||||
|
||||
import io.opentelemetry.api.common.AttributeKey
|
||||
import io.opentelemetry.api.trace.Span
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.serialization.Contextual
|
||||
@@ -68,42 +67,38 @@ internal suspend fun buildSearchableOptions(
|
||||
context: BuildContext,
|
||||
systemProperties: VmProperties = VmProperties(emptyMap()),
|
||||
): SearchableOptionSetDescriptor? {
|
||||
val span = Span.current()
|
||||
if (context.isStepSkipped(BuildOptions.SEARCHABLE_OPTIONS_INDEX_STEP)) {
|
||||
span.addEvent("skip building searchable options index")
|
||||
return null
|
||||
return context.executeStep(spanBuilder("building searchable options index"), BuildOptions.SEARCHABLE_OPTIONS_INDEX_STEP) { span ->
|
||||
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())
|
||||
|
||||
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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -8,18 +8,21 @@ import org.jetbrains.intellij.build.ProductProperties
|
||||
import org.jetbrains.intellij.build.ProprietaryBuildTools
|
||||
import org.jetbrains.intellij.build.impl.readBuiltinModulesFile
|
||||
import org.junit.jupiter.api.Assertions.*
|
||||
import org.junit.jupiter.api.TestInfo
|
||||
import java.nio.file.Path
|
||||
|
||||
/**
|
||||
* Checks that frontend distribution (ex JetBrains Client) described by [frontendProperties] can be built successfully.
|
||||
*/
|
||||
fun runTestBuildForFrontend(homePath: Path, frontendProperties: ProductProperties, buildTools: ProprietaryBuildTools,
|
||||
traceSpanName: String, softly: SoftAssertions) {
|
||||
fun runTestBuildForFrontend(
|
||||
homePath: Path, frontendProperties: ProductProperties, buildTools: ProprietaryBuildTools,
|
||||
testInfo: TestInfo, softly: SoftAssertions,
|
||||
) {
|
||||
runTestBuild(
|
||||
homeDir = homePath,
|
||||
productProperties = frontendProperties,
|
||||
buildTools = buildTools,
|
||||
traceSpanName = traceSpanName,
|
||||
testInfo = testInfo,
|
||||
onSuccess = { context ->
|
||||
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" }
|
||||
|
||||
@@ -18,8 +18,10 @@ import kotlinx.coroutines.runBlocking
|
||||
import org.assertj.core.api.SoftAssertions
|
||||
import org.jetbrains.intellij.build.*
|
||||
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.buildDistributions
|
||||
import org.jetbrains.intellij.build.telemetry.JaegerJsonSpanExporterManager
|
||||
import org.jetbrains.intellij.build.telemetry.TraceManager
|
||||
import org.jetbrains.intellij.build.telemetry.useWithScope
|
||||
import org.junit.jupiter.api.TestInfo
|
||||
@@ -28,10 +30,10 @@ import java.net.http.HttpConnectTimeoutException
|
||||
import java.nio.file.Files
|
||||
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 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
|
||||
}
|
||||
|
||||
@@ -39,13 +41,13 @@ fun createTestBuildOutDir(productProperties: ProductProperties): Path {
|
||||
return FileUtil.createTempDirectory("test-build-${productProperties.baseFileName}", null, false).toPath()
|
||||
}
|
||||
|
||||
private inline fun createBuildOptionsForTest(productProperties: ProductProperties, homeDir: Path, customizer: (BuildOptions) -> Unit): BuildOptions {
|
||||
val options = createBuildOptionsForTest(productProperties = productProperties, homeDir = homeDir)
|
||||
private inline fun createBuildOptionsForTest(productProperties: ProductProperties, homeDir: Path, testInfo: TestInfo, customizer: (BuildOptions) -> Unit): BuildOptions {
|
||||
val options = createBuildOptionsForTest(productProperties = productProperties, homeDir = homeDir, testInfo = testInfo)
|
||||
customizer(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.isTestBuild = true
|
||||
|
||||
@@ -56,11 +58,15 @@ fun customizeBuildOptionsForTest(options: BuildOptions, outDir: Path, skipDepend
|
||||
BuildOptions.WIN_SIGN_STEP,
|
||||
BuildOptions.MAC_SIGN_STEP,
|
||||
BuildOptions.MAC_NOTARIZE_STEP,
|
||||
BuildOptions.MAC_DMG_STEP,
|
||||
)
|
||||
options.buildUnixSnaps = false
|
||||
options.outRootDir = outDir
|
||||
options.useCompiledClassesFromProjectOutput = true
|
||||
options.compilationLogEnabled = false
|
||||
if (testInfo != null && isUnderTeamCity) {
|
||||
options.buildStepListener = BuildStepTeamCityListener(testInfo)
|
||||
}
|
||||
}
|
||||
|
||||
suspend inline fun createBuildContext(
|
||||
@@ -78,7 +84,7 @@ suspend inline fun createBuildContext(
|
||||
fun runTestBuild(
|
||||
homePath: Path,
|
||||
productProperties: ProductProperties,
|
||||
traceSpanName: String,
|
||||
testInfo: TestInfo,
|
||||
buildTools: ProprietaryBuildTools,
|
||||
buildOptionsCustomizer: (BuildOptions) -> Unit = {},
|
||||
) {
|
||||
@@ -86,7 +92,7 @@ fun runTestBuild(
|
||||
homeDir = homePath,
|
||||
productProperties = productProperties,
|
||||
buildTools = buildTools,
|
||||
traceSpanName = traceSpanName,
|
||||
testInfo = testInfo,
|
||||
isReproducibilityTestAllowed = true,
|
||||
buildOptionsCustomizer = buildOptionsCustomizer,
|
||||
)
|
||||
@@ -96,28 +102,29 @@ fun runTestBuild(
|
||||
homeDir: Path,
|
||||
productProperties: ProductProperties,
|
||||
buildTools: ProprietaryBuildTools = ProprietaryBuildTools.DUMMY,
|
||||
traceSpanName: String,
|
||||
testInfo: TestInfo,
|
||||
isReproducibilityTestAllowed: Boolean = true,
|
||||
build: suspend (context: BuildContext) -> Unit = { buildDistributions(it) },
|
||||
onSuccess: suspend (context: BuildContext) -> Unit = {},
|
||||
build: suspend (BuildContext) -> Unit = { buildDistributions(it) },
|
||||
onSuccess: suspend (BuildContext) -> Unit = {},
|
||||
buildOptionsCustomizer: (BuildOptions) -> Unit = {}
|
||||
) = runBlocking(Dispatchers.Default) {
|
||||
if (isReproducibilityTestAllowed) {
|
||||
if (isReproducibilityTestAllowed && BuildArtifactsReproducibilityTest.isEnabled) {
|
||||
val reproducibilityTest = BuildArtifactsReproducibilityTest()
|
||||
repeat(reproducibilityTest.iterations) { iterationNumber ->
|
||||
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(
|
||||
context = buildContext,
|
||||
traceSpanName = "#$iterationNumber",
|
||||
context = {
|
||||
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,
|
||||
build = { context ->
|
||||
build(context)
|
||||
@@ -130,15 +137,17 @@ fun runTestBuild(
|
||||
}
|
||||
else {
|
||||
doRunTestBuild(
|
||||
context = BuildContextImpl.createContext(
|
||||
projectHome = homeDir,
|
||||
productProperties = productProperties,
|
||||
proprietaryBuildTools = buildTools,
|
||||
setupTracer = false,
|
||||
options = createBuildOptionsForTest(productProperties = productProperties, homeDir = homeDir, customizer = buildOptionsCustomizer),
|
||||
),
|
||||
context = {
|
||||
BuildContextImpl.createContext(
|
||||
projectHome = homeDir,
|
||||
productProperties = productProperties,
|
||||
proprietaryBuildTools = buildTools,
|
||||
setupTracer = false,
|
||||
options = createBuildOptionsForTest(productProperties = productProperties, homeDir = homeDir, testInfo, customizer = buildOptionsCustomizer),
|
||||
)
|
||||
},
|
||||
writeTelemetry = true,
|
||||
traceSpanName = traceSpanName,
|
||||
traceSpanName = testInfo.spanName,
|
||||
build = { context ->
|
||||
build(context)
|
||||
onSuccess(context)
|
||||
@@ -149,33 +158,30 @@ fun runTestBuild(
|
||||
|
||||
// FIXME: test reproducibility
|
||||
suspend fun runTestBuild(
|
||||
context: BuildContext,
|
||||
traceSpanName: String,
|
||||
build: suspend (context: BuildContext) -> Unit = { buildDistributions(it) }
|
||||
testInfo: TestInfo,
|
||||
context: suspend () -> BuildContext,
|
||||
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 suspend fun doRunTestBuild(context: BuildContext, traceSpanName: String?, writeTelemetry: Boolean, build: suspend (context: BuildContext) -> Unit) {
|
||||
context.cleanupJarCache()
|
||||
|
||||
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
|
||||
private suspend fun doRunTestBuild(context: suspend () -> BuildContext, traceSpanName: String, writeTelemetry: Boolean, build: suspend (context: BuildContext) -> Unit) {
|
||||
var outDir: Path? = null
|
||||
var traceFile: Path? = null
|
||||
var error: Throwable? = null
|
||||
try {
|
||||
spanBuilder(traceSpanName ?: "test build of ${context.productProperties.baseFileName}")
|
||||
.setAttribute("outDir", outDir.toString())
|
||||
.useWithScope { span ->
|
||||
spanBuilder(traceSpanName).useWithScope { span ->
|
||||
val context = context()
|
||||
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 {
|
||||
build(context)
|
||||
val jetBrainsClientMainModule = context.productProperties.embeddedJetBrainsClientMainModule
|
||||
@@ -203,7 +209,11 @@ private suspend fun doRunTestBuild(context: BuildContext, traceSpanName: String?
|
||||
error = e
|
||||
}
|
||||
}
|
||||
}
|
||||
finally {
|
||||
// close debug logging to prevent locking of the output directory on Windows
|
||||
context.messages.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
finally {
|
||||
closeKtorClient()
|
||||
@@ -213,16 +223,13 @@ private suspend fun doRunTestBuild(context: BuildContext, traceSpanName: String?
|
||||
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]
|
||||
*/
|
||||
Logger.setFactory(defaultLogFactory)
|
||||
|
||||
try {
|
||||
NioFiles.deleteRecursively(outDir)
|
||||
outDir?.also(NioFiles::deleteRecursively)
|
||||
}
|
||||
catch (e: Throwable) {
|
||||
System.err.println("cannot cleanup $outDir:")
|
||||
|
||||
@@ -3,7 +3,6 @@ package org.jetbrains.intellij.build.pycharm
|
||||
|
||||
import com.intellij.openapi.application.PathManager
|
||||
import com.intellij.platform.buildScripts.testFramework.runTestBuild
|
||||
import com.intellij.platform.buildScripts.testFramework.spanName
|
||||
import com.intellij.util.io.Compressor
|
||||
import org.jetbrains.intellij.build.BuildOptions
|
||||
import org.jetbrains.intellij.build.dependencies.BuildDependenciesCommunityRoot
|
||||
@@ -42,7 +41,7 @@ class PyCharmCommunityBuildTest {
|
||||
val communityHomePath = BuildDependenciesCommunityRoot(homePath.resolve("community"))
|
||||
runTestBuild(
|
||||
homeDir = communityHomePath.communityRoot,
|
||||
traceSpanName = testInfo.spanName,
|
||||
testInfo = testInfo,
|
||||
productProperties = PyCharmCommunityProperties(communityHomePath.communityRoot),
|
||||
) {
|
||||
it.classOutDir = System.getProperty(BuildOptions.PROJECT_CLASSES_OUTPUT_DIRECTORY_PROPERTY)
|
||||
|
||||
Reference in New Issue
Block a user