Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
105 changes: 105 additions & 0 deletions clwb/src/com/google/idea/blaze/clwb/run/BazelDebugFlagsBuilder.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
/*
* Copyright 2025 The Bazel Authors. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.idea.blaze.clwb.run

import com.google.common.collect.ImmutableList
import com.intellij.openapi.util.registry.Registry
import com.intellij.util.system.OS
import com.jetbrains.cidr.lang.workspace.compiler.*

/**
* Builds flags for debugging a blaze target, flags are either used for just
* building a binary [BlazeCidrRunConfigurationRunner] or for running actually
* running the binary [BlazeGDBServerProvider].
*
* In theory we would like to have one builder that builds a BlazeCommand to
* have full control over the environment in the builder.
*/
class BazelDebugFlagsBuilder(
private val debuggerKind: BlazeDebuggerKind,
private val compilerKind: OCCompilerKind,
private val targetOS: OS,
private val withClangTrimPaths: Boolean = true,
private val withFissionFlag: Boolean = false,
) {

companion object {

@JvmStatic
fun fromDefaults(
debuggerKind: BlazeDebuggerKind,
compilerKind: OCCompilerKind,
) = BazelDebugFlagsBuilder(
debuggerKind,
compilerKind,
OS.CURRENT,
withClangTrimPaths = Registry.`is`("bazel.trim.absolute.path.disabled"),
withFissionFlag = Registry.`is`("bazel.clwb.debug.fission.disabled"),
)
}

private val flags = ImmutableList.builder<String>()

fun withBuildFlags(workspaceRoot: String? = null) {
flags.add("--compilation_mode=dbg")
flags.add("--strip=never")
flags.add("--dynamic_mode=off")

val switchBuilder = when (compilerKind) {
MSVCCompilerKind -> MSVCSwitchBuilder()
ClangClCompilerKind -> ClangClSwitchBuilder()
ClangCompilerKind -> ClangSwitchBuilder()
else -> GCCSwitchBuilder() // default to GCC, as usual
}

switchBuilder.withDebugInfo(2) // ignored for msvc/clangcl
switchBuilder.withDisableOptimization()

if (debuggerKind == BlazeDebuggerKind.BUNDLED_LLDB && withClangTrimPaths && workspaceRoot != null) {
switchBuilder.withSwitch("-fdebug-compilation-dir=\"$workspaceRoot\"")
}

if (debuggerKind == BlazeDebuggerKind.GDB_SERVER && withFissionFlag) {
switchBuilder.withSwitch("--fission=yes")
}

flags.addAll(switchBuilder.buildRaw().map { "--copt=$it" })

if (debuggerKind == BlazeDebuggerKind.BUNDLED_LLDB && targetOS == OS.macOS) {
flags.add("--linkopt=-Wl,-oso_prefix,.", "--linkopt=-Wl,-reproducible", "--remote_download_regex='.*_objs/.*.o$'")
}
}

fun withTestFlags(timeout: Int? = 3600) {
flags.add("--nocache_test_results")
flags.add("--test_strategy=exclusive")
flags.add("--test_sharding_strategy=disabled")

if (timeout != null) {
flags.add("--test_timeout=$timeout")
}
}

fun withRunUnderGDBServer(gdbserver: String, port: Int, wrapperScript: String?) {
if (wrapperScript != null) {
flags.add("--run_under='bash' '$wrapperScript' '$gdbserver' --once localhost:$port --target")
} else {
flags.add("--run_under='$gdbserver' --once localhost:$port")
}
}

fun build(): ImmutableList<String> = flags.build()
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
package com.google.idea.blaze.clwb.run;

import com.google.common.collect.ImmutableList;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.idea.blaze.base.command.BlazeCommandName;
import com.google.idea.blaze.base.command.BlazeInvocationContext;
import com.google.idea.blaze.base.command.buildresult.GetArtifactsException;
Expand All @@ -38,7 +37,6 @@
import com.intellij.execution.executors.DefaultDebugExecutor;
import com.intellij.execution.runners.ExecutionEnvironment;
import com.intellij.execution.runners.ExecutionUtil;
import com.intellij.openapi.util.SystemInfo;
import com.intellij.openapi.util.registry.Registry;
import com.intellij.util.PathUtil;
import com.jetbrains.cidr.execution.CidrCommandLineState;
Expand Down Expand Up @@ -98,36 +96,6 @@ private static Label getSingleTarget(BlazeCommandRunConfiguration config)
return (Label) targets.get(0);
}

private ImmutableList<String> getExtraDebugFlags(ExecutionEnvironment env) {
if (Registry.is("bazel.clwb.debug.extraflags.disabled")) {
return ImmutableList.of();
}

final var debuggerKind = RunConfigurationUtils.getDebuggerKind(configuration);
if (debuggerKind == BlazeDebuggerKind.GDB_SERVER) {
return BlazeGDBServerProvider.getFlagsForDebugging(configuration.getHandler().getState());
}

final var flagsBuilder = ImmutableList.<String>builder();

if (debuggerKind == BlazeDebuggerKind.BUNDLED_LLDB && !Registry.is("bazel.trim.absolute.path.disabled")) {
flagsBuilder.add("--copt=-fdebug-compilation-dir=" + WorkspaceRoot.fromProject(env.getProject()));

if (SystemInfo.isMac) {
flagsBuilder.add("--linkopt=-Wl,-oso_prefix,.");
}
}

flagsBuilder.add("--compilation_mode=dbg");
flagsBuilder.add("--copt=-O0");
flagsBuilder.add("--copt=-g");
flagsBuilder.add("--strip=never");
flagsBuilder.add("--dynamic_mode=off");
flagsBuilder.addAll(BlazeGDBServerProvider.getOptionalFissionArguments());

return flagsBuilder.build();
}

/**
* Builds blaze C/C++ target in debug mode, and returns the output build artifact.
*
Expand All @@ -136,15 +104,23 @@ private ImmutableList<String> getExtraDebugFlags(ExecutionEnvironment env) {
private File getExecutableToDebug(ExecutionEnvironment env) throws ExecutionException {
SaveUtil.saveAllFiles();

ListenableFuture<BuildEventStreamProvider> streamProviderFuture =
BlazeBeforeRunCommandHelper.runBlazeCommand(
BlazeCommandName.BUILD,
configuration,
ImmutableList.of(),
getExtraDebugFlags(env),
BlazeInvocationContext.runConfigContext(
ExecutorType.fromExecutor(env.getExecutor()), configuration.getType(), true),
"Building debug binary");
final var flagsBuilder = BazelDebugFlagsBuilder.fromDefaults(
RunConfigurationUtils.getDebuggerKind(configuration),
RunConfigurationUtils.getCompilerKind(configuration)
);

if (!Registry.is("bazel.clwb.debug.extraflags.disabled")) {
flagsBuilder.withBuildFlags(WorkspaceRoot.fromProject(env.getProject()).toString());
}

final var streamProviderFuture = BlazeBeforeRunCommandHelper.runBlazeCommand(
BlazeCommandName.BUILD,
configuration,
ImmutableList.of(),
flagsBuilder.build(),
BlazeInvocationContext.runConfigContext(ExecutorType.fromExecutor(env.getExecutor()), configuration.getType(), true),
"Building debug binary"
);

Label target = getSingleTarget(configuration);
try (BuildEventStreamProvider streamProvider = streamProviderFuture.get()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,25 +17,20 @@ package com.google.idea.blaze.clwb.run

import com.google.common.collect.ImmutableList
import com.google.idea.blaze.base.command.BlazeCommandName
import com.google.idea.blaze.base.command.BlazeFlags
import com.google.idea.blaze.base.run.state.RunConfigurationState
import com.google.idea.blaze.clwb.ToolchainUtils
import com.google.idea.common.experiments.BoolExperiment
import com.google.idea.sdkcompat.clion.debug.CidrDebuggerPathManagerAdapter
import com.intellij.openapi.diagnostic.logger
import com.intellij.openapi.util.registry.Registry
import com.intellij.util.PathUtil
import com.jetbrains.cidr.cpp.toolchains.CPPDebugger
import com.jetbrains.cidr.cpp.toolchains.CPPToolchains
import com.jetbrains.cidr.execution.debugger.CidrDebuggerPathManager
import com.jetbrains.cidr.lang.workspace.compiler.GCCCompilerKind
import java.nio.file.Files
import java.nio.file.Path
import kotlin.io.path.absolutePathString

private val LOG = logger<BlazeGDBServerProvider>()

private val USE_REMOTE_DEBUGGING_WRAPPER: BoolExperiment = BoolExperiment("cc.remote.debugging.wrapper", true)

private const val GDB_SERVER_PROPERTY = "clwb.gdbserverPath"

/** CLion-specific class that provides the slightly customized Toolchain for use with gdbserver */
Expand All @@ -55,80 +50,24 @@ object BlazeGDBServerProvider {
}
}

// These flags are used when debugging cc_binary targets when remote debugging
// is enabled (cc.remote.debugging)
private val EXTRA_FLAGS_FOR_DEBUG_RUN = ImmutableList.of<String>(
"--compilation_mode=dbg", "--strip=never", "--dynamic_mode=off"
)

// These flags are used when debugging cc_test targets when remote debugging
// is enabled (cc.remote.debugging)
private val EXTRA_FLAGS_FOR_DEBUG_TEST = ImmutableList.of<String>(
"--compilation_mode=dbg",
"--strip=never",
"--dynamic_mode=off",
"--test_timeout=3600",
BlazeFlags.NO_CACHE_TEST_RESULTS,
BlazeFlags.EXCLUSIVE_TEST_EXECUTION,
BlazeFlags.DISABLE_TEST_SHARDING
)

// Allows the fission flag to be disabled as workaround for
@JvmStatic
fun getOptionalFissionArguments(): ImmutableList<String> {
return if (Registry.`is`("bazel.clwb.debug.fission.disabled")) {
ImmutableList.of()
} else {
ImmutableList.of("--fission=yes")
}
}

@JvmStatic
fun getFlagsForDebugging(state: RunConfigurationState?): ImmutableList<String> {
if (state !is BlazeCidrRunConfigState) {
return ImmutableList.of()
}

val commandName = state.commandState.command
val builder = ImmutableList.builder<String>()
val builder = BazelDebugFlagsBuilder.fromDefaults(BlazeDebuggerKind.GDB_SERVER, GCCCompilerKind)
builder.withBuildFlags()

val toolchain = ToolchainUtils.getToolchain()

// if gdbserver could not be found, fall back to trying PATH
val gdbServerPath = getGDBServerPath(toolchain) ?: "gdbserver"

if (USE_REMOTE_DEBUGGING_WRAPPER.value) {
builder.add(
String.format(
"--run_under='bash' '%s' '%s' --once localhost:%d --target",
GDBSERVER_WRAPPER,
gdbServerPath,
state.getDebugPortState().port,
)
)
} else {
builder.add(
String.format(
"--run_under='%s' --once localhost:%d",
gdbServerPath,
state.getDebugPortState().port,
)
)
if (state.commandState.command == BlazeCommandName.TEST) {
builder.withTestFlags()
}

if (BlazeCommandName.RUN == commandName) {
builder.addAll(EXTRA_FLAGS_FOR_DEBUG_RUN)
builder.addAll(getOptionalFissionArguments())
return builder.build()
}

if (BlazeCommandName.TEST == commandName) {
builder.addAll(EXTRA_FLAGS_FOR_DEBUG_TEST)
builder.addAll(getOptionalFissionArguments())
return builder.build()
}
// if gdbserver could not be found, fall back to trying PATH
val gdbServerPath = getGDBServerPath(ToolchainUtils.getToolchain()) ?: "gdbserver"
builder.withRunUnderGDBServer(gdbServerPath, state.getDebugPortState().port, GDBSERVER_WRAPPER)

return ImmutableList.of()
return builder.build()
}

private fun getGDBServerPath(toolchain: CPPToolchains.Toolchain): String? {
Expand Down