bazel: add debugger-agent as a kotlin test dependency

GitOrigin-RevId: 1244ce715c8039dae45ce29306da890a8e0fcd3b
This commit is contained in:
Evgenii Ilichev
2025-08-21 18:02:57 +00:00
committed by intellij-monorepo-bot
parent 1513f7024b
commit 1f46eeef22
10 changed files with 146 additions and 47 deletions
+9
View File
@@ -17,6 +17,8 @@ local_path_override(
path = "build/jvm-rules",
)
http_file = use_repo_rule("@bazel_tools//tools/build_defs/repo:http.bzl", "http_file")
bazel_dep(name = "hermetic_cc_toolchain", version = "4.0.1")
toolchains = use_extension("@hermetic_cc_toolchain//toolchain:ext.bzl", "toolchains")
@@ -88,6 +90,13 @@ use_repo(
"kotlin_test_deps_parcelize-compiler-plugin-for-ide",
)
http_file(
name = "debugger_test_deps_debugger_agent",
url = "https://cache-redirector.jetbrains.com/packages.jetbrains.team/maven/p/ij/intellij-dependencies/org/jetbrains/intellij/deps/debugger-agent/1.79/debugger-agent-1.79.jar",
sha256 = "ba7a0aa7bbb813acab3b0edf36c0a15449f41896993c97e0d133fa4ea0a3e6d9",
downloaded_file_path = "debugger-agent.jar"
)
# GraalVM
# git_override(
# module_name = "rules_graalvm",
@@ -26,6 +26,8 @@
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/resources" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/test" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/testResources" type="java-test-resource" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
@@ -100,6 +102,7 @@
<orderEntry type="module" module-name="intellij.platform.debugger.impl.rpc" />
<orderEntry type="module" module-name="fleet.rpc" />
<orderEntry type="module" module-name="intellij.platform.rpc" />
<orderEntry type="module" module-name="intellij.platform.testFramework.common" scope="TEST" />
</component>
<component name="copyright">
<Base>
@@ -13,10 +13,12 @@ import com.intellij.debugger.memory.utils.StackFrameItem;
import com.intellij.debugger.requests.ClassPrepareRequestor;
import com.intellij.debugger.settings.CaptureSettingsProvider;
import com.intellij.debugger.settings.DebuggerSettings;
import com.intellij.debugger.testFramework.TestDebuggerAgentArtifactsProvider;
import com.intellij.debugger.ui.breakpoints.StackCapturingLineBreakpoint;
import com.intellij.execution.JavaExecutionUtil;
import com.intellij.execution.configurations.JavaParameters;
import com.intellij.execution.configurations.ParametersList;
import com.intellij.ide.plugins.PluginManagerCore;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.application.AccessToken;
import com.intellij.openapi.application.PathManager;
@@ -353,6 +355,13 @@ public final class AsyncStacksUtils {
}
}
/**
* returns true if Bazel-test specific env variables are set
*/
private static Boolean isBazelTestRun() {
return System.getenv("TEST_SRCDIR") != null && System.getenv("TEST_UNDECLARED_OUTPUTS_DIR") != null;
}
@NativePath
private static @Nullable String getAgentArtifactPath(@Nullable Project project, @Nullable Disposable disposable) {
String relevantJarsRoot = PathManager.getArchivedCompliedClassesLocation();
@@ -375,13 +384,37 @@ public final class AsyncStacksUtils {
try {
Path agentArtifactPath = createTemporaryAgentPath(project, disposable);
Path communityRoot = Path.of(PathManager.getCommunityHomePath());
Path iml = BuildDependenciesJps.getProjectModule(communityRoot, "intellij.java.debugger.agent.holder");
Path downloadedAgent = BuildDependenciesJps.INSTANCE.getModuleLibrarySingleRootSync(
iml,
"debugger-agent",
"https://cache-redirector.jetbrains.com/intellij-dependencies",
new BuildDependenciesCommunityRoot(Path.of(PathManager.getCommunityHomePath())));
Path downloadedAgent;
// In jps runs we resolve the debugger agent via the standard BuildDependenciesJps path below.
// However, when tests are executed under Bazel, they run inside a hermetic sandbox. In this environment:
// - Network/downloads and access to arbitrary files are restricted.
// - All runtime inputs must come from explicit Bazel-declared dependencies (runfiles).
// Therefore, in Bazel unit-test mode we must obtain the agent JAR from the test classpath via a test-only
// service. The TestDebuggerAgentArtifactsProvider is implemented in test sources and knows how to locate
// the agent artifact provided by Bazel via runfiles, so we use ServiceLoader to find that provider
// on the tests classpath and resolve the agent from there. Outside Bazel (regular production/dev runs), we
// keep using the standard resolution path that downloads the dependency if needed.
if (PluginManagerCore.isUnitTestMode && isBazelTestRun()) {
ServiceLoader<TestDebuggerAgentArtifactsProvider> providerClasses = ServiceLoader.load(TestDebuggerAgentArtifactsProvider.class);
var iterator = providerClasses.iterator();
if (!iterator.hasNext()) {
throw new IllegalStateException("TestDebuggerAgentArtifactsProvider service provider not found");
}
TestDebuggerAgentArtifactsProvider provider = iterator.next();
if (iterator.hasNext()) {
throw new IllegalStateException("more than one TestDebuggerAgentArtifactsProvider service providers found. Only one is expected");
}
downloadedAgent = provider.getDebuggerAgentJar();
} else {
Path communityRoot = Path.of(PathManager.getCommunityHomePath());
Path iml = BuildDependenciesJps.getProjectModule(communityRoot, "intellij.java.debugger.agent.holder");
downloadedAgent = BuildDependenciesJps.INSTANCE.getModuleLibrarySingleRootSync(
iml,
"debugger-agent",
"https://cache-redirector.jetbrains.com/intellij-dependencies",
new BuildDependenciesCommunityRoot(Path.of(PathManager.getCommunityHomePath())));
}
// The agent file must have a fixed name (AGENT_JAR_NAME) which is mentioned in MANIFEST.MF inside.
// The copy operation is used as the rename operation.
@@ -0,0 +1,21 @@
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.debugger.testFramework;
import java.nio.file.Path;
/**
* Test-only service used to resolve debugger agent artifacts when running tests (especially under Bazel).
* <p>
* In production and regular dev runs, the agent is resolved via standard mechanisms (e.g., BuildDependenciesJps).
* But Bazel tests execute in a hermetic sandbox where downloads and arbitrary file access are restricted and all
* inputs must be declared as dependencies (runfiles). To make the agent available in that environment, Bazel wires
* the artifact into the test's classpath/runfiles. This service, implemented in test sources, knows how to locate
* that artifact and provide its path to the runtime code.
* <p>
* The implementation must be present on the tests classpath and is discovered via {@link java.util.ServiceLoader}.
*/
public interface TestDebuggerAgentArtifactsProvider {
Path getDebuggerAgentJar();
}
@@ -0,0 +1,23 @@
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.debugger.artifacts;
import com.intellij.debugger.testFramework.TestDebuggerAgentArtifactsProvider;
import com.intellij.testFramework.common.bazel.BazelLabel;
import com.intellij.testFramework.common.BazelTestUtil;
import java.nio.file.Path;
public class TestDebuggerAgentArtifactsProviderImpl implements TestDebuggerAgentArtifactsProvider {
@Override
public Path getDebuggerAgentJar() {
String debuggerAgentLabel = "@debugger_test_deps_debugger_agent//file:debugger-agent.jar";
BazelLabel label = BazelLabel.Companion.fromString(debuggerAgentLabel);
if (BazelTestUtil.isUnderBazelTest()) {
return BazelTestUtil.getFileFromBazelRuntime(label);
} else {
throw new IllegalStateException("Expected to be used only in bazel-test environment");
}
}
}
@@ -0,0 +1 @@
com.intellij.debugger.artifacts.TestDebuggerAgentArtifactsProviderImpl
@@ -1,10 +1,12 @@
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.testFramework.common
import com.intellij.testFramework.common.bazel.BazelLabel
import org.jetbrains.annotations.ApiStatus
import java.nio.file.Path
import kotlin.io.path.absolute
import kotlin.io.path.isDirectory
import kotlin.io.path.isRegularFile
import kotlin.io.path.useLines
@ApiStatus.Experimental
@@ -64,6 +66,22 @@ object BazelTestUtil {
path
}
@JvmStatic
fun getFileFromBazelRuntime(label: BazelLabel): Path {
val repoEntry = bazelTestRepoMapping.getOrElse(label.repo) {
error("Unable to determine dependency path '${label.asLabel}'")
}
val file = bazelTestRunfilesPath.resolve(
repoEntry.runfilesRelativePath + "/${label.packageName}/${label.file}"
)
return when {
file.isRegularFile() || file.isDirectory() -> file.toAbsolutePath()
else -> {
error("Unable to find test dependency '${label.asLabel}' at $file")
}
}
}
/**
* Tests under community root may run in community (OSS) or in the ultimate monorepo context.
*
@@ -0,0 +1,27 @@
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.testFramework.common.bazel
import org.jetbrains.annotations.ApiStatus
// https://bazel.build/rules/lib/builtins/Label.html
@ApiStatus.Experimental
data class BazelLabel(
val repo: String,
val packageName: String,
val file: String,
) {
companion object {
private val regex = Regex("@([a-zA-Z0-9_-]+)//([a-z0-9_/-]+):([a-zA-Z0-9._-]+)")
fun fromString(label: String): BazelLabel {
val match = regex.matchEntire(label) ?: error("Bazel label must match '${regex.pattern}': $label")
return BazelLabel(
repo = match.groupValues[1],
packageName = match.groupValues[2],
file = match.groupValues[3],
)
}
}
val asLabel: String
get() = "@$repo//$packageName:$file"
}
@@ -3,6 +3,7 @@ package org.jetbrains.kotlin.idea.base.plugin.artifacts
import com.intellij.openapi.application.PathManager
import com.intellij.testFramework.common.BazelTestUtil
import com.intellij.testFramework.common.bazel.BazelLabel
import com.intellij.util.io.createParentDirectories
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.runBlocking
@@ -81,43 +82,6 @@ object TestKotlinArtifacts {
listOf(getKotlinDepsByLabel("@kotlin_test_deps_kotlin-jps-plugin-classpath//file:kotlin-jps-plugin-classpath.jar").toPath())
}
// https://bazel.build/rules/lib/builtins/Label.html
data class BazelLabel(
val repo: String,
val packageName: String,
val file: String,
) {
companion object {
private val regex = Regex("@([a-zA-Z0-9_-]+)//([a-z0-9_/-]+):([a-zA-Z0-9._-]+)")
fun fromString(label: String): BazelLabel {
val match = regex.matchEntire(label) ?: error("Bazel label must match '${regex.pattern}': $label")
return BazelLabel(
repo = match.groupValues[1],
packageName = match.groupValues[2],
file = match.groupValues[3],
)
}
}
val asLabel: String
get() = "@$repo//$packageName:$file"
}
private fun getFileFromBazelRuntime(label: BazelLabel): Path {
val repoEntry = BazelTestUtil.bazelTestRepoMapping.getOrElse(label.repo) {
error("Unable to determine dependency path '${label.asLabel}'")
}
val file = BazelTestUtil.bazelTestRunfilesPath.resolve(
repoEntry.runfilesRelativePath + "/${label.packageName}/${label.file}"
)
return when {
file.exists() -> file.toAbsolutePath()
else -> {
error("Unable to find test dependency '${label.asLabel}' at $file")
}
}
}
private data class HttpFile(val downloadFilePath: String, val name: String, val url: String)
private val kotlinTestDependenciesHttpFiles by lazy {
@@ -183,7 +147,7 @@ object TestKotlinArtifacts {
private fun getKotlinDepsByLabel(label: BazelLabel): File {
// Why it is different
val dependency = if (BazelTestUtil.isUnderBazelTest) {
getFileFromBazelRuntime(label)
BazelTestUtil.getFileFromBazelRuntime(label)
} else {
downloadFile(label)
}
@@ -5,12 +5,12 @@ jps_test(
runtime_deps = [
":k2_test_lib",
"//:main_test_lib",
"@lib//:java-debugger-agent-holder-debugger-agent",
],
data = [
"@kotlin_test_deps_org_jetbrains_kotlinx_kotlinx_coroutines_core_1_10_2//file:kotlinx_coroutines_core_1_10_2.jar",
"@kotlin_test_deps_org_jetbrains_kotlinx_kotlinx_coroutines_core_jvm_1_10_2//file:kotlinx_coroutines_core_jvm_1_10_2.jar",
"@debugger_test_deps_debugger_agent//file:debugger-agent.jar",
"@community//plugins/kotlin/jvm-debugger/test:testData",
"@kotlin_test_deps_annotations//file:annotations.jar",
"@kotlin_test_deps_junit//file:junit.jar",
@@ -54,7 +54,7 @@ jvm_library(
name = "k2_test_lib",
module_name = "kotlin.jvm-debugger.test.k2",
visibility = ["//visibility:public"],
srcs = glob(["test/**/*.kt", "test/**/*.java", "test/**/*.form"], allow_empty = True),
srcs = glob(["test/**/*.kt", "test/**/*.java"], allow_empty = True),
deps = [
"//platform/core-ui",
"//plugins/kotlin/plugin/k2",