Skip to content
Draft
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
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ class NativeBuildPlugin : Plugin<Project> {
project.logger.debug("Created tasks for configuration: ${config.name}")
}

private fun findCompiler(project: Project): String = PlatformUtils.findCompiler(project)
private fun findCompiler(project: Project): String = PlatformUtils.findCompiler(project).executable

private fun createAggregationTasks(
project: Project,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ class SimpleNativeLibPlugin : Plugin<Project> {
return@afterEvaluate
}

val compiler = extension.compiler.getOrElse(PlatformUtils.findCompiler(project))
val compiler = extension.compiler.getOrElse(PlatformUtils.findCompiler(project).executable)
val linker = extension.linker.getOrElse(compiler)
val libraryName = extension.libraryName.get()
val objectDir = extension.objectDir.get().asFile
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@ package com.datadoghq.native.config
import com.datadoghq.native.model.Architecture
import com.datadoghq.native.model.BuildConfiguration
import com.datadoghq.native.model.Platform
import com.datadoghq.native.util.CompilerInfo
import com.datadoghq.native.util.PlatformUtils
import java.io.File
import org.gradle.api.logging.Logger

/**
* Provides factory methods for creating standard build configurations
Expand Down Expand Up @@ -35,31 +37,39 @@ object ConfigurationPresets {
val compiler = PlatformUtils.findCompiler(project)

project.logger.lifecycle("Setting up standard build configurations for $currentPlatform-$currentArch")
project.logger.lifecycle("Using compiler: $compiler")
project.logger.lifecycle("Using compiler: ${compiler.executable} (version ${compiler.majorVersion})")

val commonCompilerArgs = commonCompilerArgs(version, compiler, currentArch, currentPlatform, project.logger)

extension.buildConfigurations.apply {
register("release") {
configureRelease(this, currentPlatform, currentArch, version)
configureRelease(this, currentPlatform, currentArch, commonCompilerArgs)
}
register("debug") {
configureDebug(this, currentPlatform, currentArch, version)
configureDebug(this, currentPlatform, currentArch, commonCompilerArgs)
}
register("asan") {
configureAsan(this, currentPlatform, currentArch, version, rootDir, compiler)
configureAsan(this, currentPlatform, currentArch, rootDir, compiler, commonCompilerArgs)
}
register("tsan") {
configureTsan(this, currentPlatform, currentArch, version, rootDir, compiler)
configureTsan(this, currentPlatform, currentArch, rootDir, compiler, commonCompilerArgs)
}
register("fuzzer") {
configureFuzzer(this, currentPlatform, currentArch, version, rootDir)
configureFuzzer(this, currentPlatform, currentArch, rootDir, commonCompilerArgs)
}
}

val activeConfigs = extension.getActiveConfigurations(currentPlatform, currentArch)
project.logger.lifecycle("Active configurations: ${activeConfigs.map { it.name }.joinToString(", ")}")
}

private fun commonLinuxCompilerArgs(version: String): List<String> {
private fun commonCompilerArgs(
version: String,
compiler: CompilerInfo,
architecture: Architecture,
platform: Platform,
logger: Logger
): List<String> {
val args = mutableListOf(
"-fPIC",
"-fno-omit-frame-pointer",
Expand All @@ -75,6 +85,26 @@ object ConfigurationPresets {
if (PlatformUtils.isMusl()) {
args.add("-D__musl__")
}

// Use TLS descriptors (GNU2 dialect) for thread-local storage on x86_64 Linux,
// required by thread context (https://github.com/open-telemetry/opentelemetry-specification/pull/4947).
// The `-mtls-dialect=gnu2` spelling is x86-specific, so gate on platform/arch.
if (platform == Platform.LINUX && architecture == Architecture.X64) {
if (compiler.supportsTlsDialectGnu2()) {
args.add("-mtls-dialect=gnu2")
} else {
// Only clang older than 19 reaches here (gcc/others are always supported).
logger.lifecycle(
"GNU2 TLS dialect disabled: -mtls-dialect=gnu2 requires clang >= 19, " +
"but detected clang ${compiler.majorVersion} for ${compiler.executable}."
)
}
}

if (platform == Platform.MACOS) {
args += listOf("-D_XOPEN_SOURCE", "-D_DARWIN_C_SOURCE")
}

return args
}

Expand All @@ -88,14 +118,11 @@ object ConfigurationPresets {
"-Wl,--build-id"
)

private fun commonMacosCompilerArgs(version: String): List<String> =
commonLinuxCompilerArgs(version) + listOf("-D_XOPEN_SOURCE", "-D_DARWIN_C_SOURCE")

fun configureRelease(
config: BuildConfiguration,
platform: Platform,
architecture: Architecture,
version: String
commonCompilerArgs: List<String>
) {
config.platform.set(platform)
config.architecture.set(architecture)
Expand All @@ -104,7 +131,7 @@ object ConfigurationPresets {
when (platform) {
Platform.LINUX -> {
config.compilerArgs.set(
listOf("-O3", "-DNDEBUG", "-g") + commonLinuxCompilerArgs(version)
listOf("-O3", "-DNDEBUG", "-g") + commonCompilerArgs
)
config.linkerArgs.set(
commonLinuxLinkerArgs() + listOf(
Expand All @@ -118,7 +145,7 @@ object ConfigurationPresets {
}
Platform.MACOS -> {
config.compilerArgs.set(
commonMacosCompilerArgs(version) + listOf("-O3", "-DNDEBUG", "-g")
commonCompilerArgs + listOf("-O3", "-DNDEBUG", "-g")
)
config.linkerArgs.set(emptyList())
}
Expand All @@ -129,7 +156,7 @@ object ConfigurationPresets {
config: BuildConfiguration,
platform: Platform,
architecture: Architecture,
version: String
commonCompilerArgs: List<String>
) {
config.platform.set(platform)
config.architecture.set(architecture)
Expand All @@ -138,13 +165,13 @@ object ConfigurationPresets {
when (platform) {
Platform.LINUX -> {
config.compilerArgs.set(
listOf("-O0", "-g", "-DDEBUG") + commonLinuxCompilerArgs(version)
listOf("-O0", "-g", "-DDEBUG") + commonCompilerArgs
)
config.linkerArgs.set(commonLinuxLinkerArgs())
}
Platform.MACOS -> {
config.compilerArgs.set(
commonMacosCompilerArgs(version) + listOf("-O0", "-g", "-DDEBUG")
commonCompilerArgs + listOf("-O0", "-g", "-DDEBUG")
)
config.linkerArgs.set(emptyList())
}
Expand All @@ -155,13 +182,13 @@ object ConfigurationPresets {
config: BuildConfiguration,
platform: Platform,
architecture: Architecture,
version: String,
rootDir: File,
compiler: String = "gcc"
compiler: CompilerInfo,
commonCompilerArgs: List<String>
) {
config.platform.set(platform)
config.architecture.set(architecture)
config.active.set(PlatformUtils.hasAsan(compiler))
config.active.set(PlatformUtils.hasAsan(compiler.executable))

val asanCompilerArgs = listOf(
"-g",
Expand All @@ -184,9 +211,9 @@ object ConfigurationPresets {

when (platform) {
Platform.LINUX -> {
config.compilerArgs.set(asanCompilerArgs + commonLinuxCompilerArgs(version))
config.compilerArgs.set(asanCompilerArgs + commonCompilerArgs)

val libasan = PlatformUtils.locateLibasan(compiler)
val libasan = PlatformUtils.locateLibasan(compiler.executable)
// Link against the sanitizer runtime that matches the compiler:
// - clang: locateLibasan returns libclang_rt.asan-<arch>.so, which
// includes UBSan symbols; -lclang_rt.asan-<arch> satisfies -z defs
Expand Down Expand Up @@ -228,13 +255,13 @@ object ConfigurationPresets {
config: BuildConfiguration,
platform: Platform,
architecture: Architecture,
version: String,
rootDir: File,
compiler: String = "gcc"
compiler: CompilerInfo,
commonCompilerArgs: List<String>
) {
config.platform.set(platform)
config.architecture.set(architecture)
config.active.set(PlatformUtils.hasTsan(compiler))
config.active.set(PlatformUtils.hasTsan(compiler.executable))

val tsanCompilerArgs = listOf(
"-g",
Expand All @@ -247,9 +274,9 @@ object ConfigurationPresets {

when (platform) {
Platform.LINUX -> {
config.compilerArgs.set(tsanCompilerArgs + commonLinuxCompilerArgs(version))
config.compilerArgs.set(tsanCompilerArgs + commonCompilerArgs)

val libtsan = PlatformUtils.locateLibtsan(compiler)
val libtsan = PlatformUtils.locateLibtsan(compiler.executable)
// Use the library name from the resolved path so that clang's own
// libclang_rt.tsan-<arch>.so is linked by name (not as -ltsan).
val tsanLinkerArgs = if (libtsan != null) {
Expand Down Expand Up @@ -295,8 +322,8 @@ object ConfigurationPresets {
config: BuildConfiguration,
platform: Platform,
architecture: Architecture,
version: String,
rootDir: File
rootDir: File,
commonCompilerArgs: List<String>
) {
config.platform.set(platform)
config.architecture.set(architecture)
Expand All @@ -321,7 +348,7 @@ object ConfigurationPresets {

when (platform) {
Platform.LINUX -> {
config.compilerArgs.set(fuzzerCompilerArgs + commonLinuxCompilerArgs(version))
config.compilerArgs.set(fuzzerCompilerArgs + commonCompilerArgs)
config.linkerArgs.set(commonLinuxLinkerArgs() + fuzzerLinkerArgs)

config.testEnvironment.apply {
Expand All @@ -330,7 +357,7 @@ object ConfigurationPresets {
}
}
Platform.MACOS -> {
config.compilerArgs.set(fuzzerCompilerArgs + commonMacosCompilerArgs(version))
config.compilerArgs.set(fuzzerCompilerArgs + commonCompilerArgs)
config.linkerArgs.set(fuzzerLinkerArgs)

config.testEnvironment.apply {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -253,7 +253,7 @@ class GtestPlugin : Plugin<Project> {
}
}

private fun findCompiler(project: Project): String = PlatformUtils.findCompiler(project)
private fun findCompiler(project: Project): String = PlatformUtils.findCompiler(project).executable

private fun getGtestIncludes(extension: GtestExtension): List<File> {
return when (PlatformUtils.currentPlatform) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,27 @@ import com.datadoghq.native.model.Architecture
import com.datadoghq.native.model.Platform
import org.gradle.api.GradleException
import org.gradle.api.Project
import org.gradle.api.logging.Logger
import java.io.File
import kotlin.io.path.createTempFile
import kotlin.io.path.deleteIfExists
import kotlin.io.path.writeText
import java.util.concurrent.TimeUnit

/**
* A resolved C++ compiler: the executable plus its clang-family flag and major
* version, which are parsed from the `--version` banner (not the executable name).
*/
data class CompilerInfo(val executable: String, val isClang: Boolean, val majorVersion: Int) {
init {
require(executable.isNotBlank()) { "compiler executable must not be blank" }
require(majorVersion > 0) { "compiler majorVersion must be positive, was $majorVersion" }
}

// gcc has supported GNU2 TLS for x86_64 for a long time, clang only since 19.
fun supportsTlsDialectGnu2(): Boolean = !isClang || majorVersion >= 19
}

object PlatformUtils {
val currentPlatform: Platform by lazy { Platform.current() }
val currentArchitecture: Architecture by lazy { Architecture.current() }
Expand Down Expand Up @@ -199,6 +214,51 @@ object PlatformUtils {
}
}

/**
* Detect a compiler's family and major version from `<compiler> --version`,
* or null if neither can be parsed. The family comes from the matching banner
* shape (not the executable name): "clang version <major>" -> clang;
* "<vendor>) <major>.<minor>" -> non-clang. Failures are logged via [logger].
*/
fun detectCompiler(compiler: String, logger: Logger): CompilerInfo? {
val output = try {
val process = ProcessBuilder(compiler, "--version")
.redirectErrorStream(true)
.start()
val text = process.inputStream.bufferedReader().readText()
if (!process.waitFor(5, TimeUnit.SECONDS)) {
process.destroyForcibly()
process.waitFor(5, TimeUnit.SECONDS)
logger.warn("Timed out after 5s running '$compiler --version'.")
return null
}
if (process.exitValue() != 0) {
logger.warn("'$compiler --version' exited with ${process.exitValue()}: ${text.trim().take(500)}")
return null
}
text
} catch (e: InterruptedException) {
Thread.currentThread().interrupt()
return null
} catch (e: Exception) {
logger.warn("Failed to run '$compiler --version'", e)
return null
}

Regex("""clang version (\d+)""").find(output)?.let { match ->
match.groupValues[1].toIntOrNull()?.let { major ->
return CompilerInfo(compiler, isClang = true, majorVersion = major)
}
}
Regex("""\)\s+(\d+)\.\d+""").find(output)?.let { match ->
match.groupValues[1].toIntOrNull()?.let { major ->
return CompilerInfo(compiler, isClang = false, majorVersion = major)
}
}
logger.warn("Could not parse a compiler version from '$compiler --version' output: ${output.trim().take(500)}")
return null
}

fun hasAsan(compiler: String = "gcc"): Boolean {
return !isMusl() && locateLibasan(compiler) != null
}
Expand Down Expand Up @@ -294,7 +354,7 @@ object PlatformUtils {
return "$homebrewLLVM/bin/clang++"
}
}
return findCompiler(project)
return findCompiler(project).executable
}

/**
Expand All @@ -319,9 +379,18 @@ object PlatformUtils {

/**
* Find a C++ compiler, respecting -Pnative.forceCompiler property.
* Auto-detects clang++ or g++ if not specified.
* Auto-detects clang++ or g++ if not specified. Throws if the version cannot
* be determined ([detectCompiler] logs the specific reason).
*/
fun findCompiler(project: Project): String {
fun findCompiler(project: Project): CompilerInfo {
val executable = resolveCompilerExecutable(project)
return detectCompiler(executable, project.logger)
?: throw GradleException(
"Unable to determine the version of compiler '$executable'."
)
}

private fun resolveCompilerExecutable(project: Project): String {
// Check for forced compiler override
val forcedCompiler = project.findProperty("native.forceCompiler") as? String
if (forcedCompiler != null) {
Expand Down
2 changes: 1 addition & 1 deletion ddprof-lib/benchmarks/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ val shouldBuild = PlatformUtils.currentPlatform == Platform.MACOS ||
PlatformUtils.currentPlatform == Platform.LINUX

if (shouldBuild) {
val compiler = PlatformUtils.findCompiler(project)
val compiler = PlatformUtils.findCompiler(project).executable

// Compile task
val compileTask = tasks.register<NativeCompileTask>("compileBenchmark") {
Expand Down
Loading