From 42151c1da59590c5fa1bd02483d23ff0ef77b47b Mon Sep 17 00:00:00 2001 From: Nicolas Savoire Date: Fri, 12 Jun 2026 15:36:49 +0200 Subject: [PATCH 1/3] build(native): compile x86_64 Linux lib with -mtls-dialect=gnu2 Use TLS descriptors as required by thread context. --- .../native/config/ConfigurationPresets.kt | 27 ++++++++++++++----- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/build-logic/conventions/src/main/kotlin/com/datadoghq/native/config/ConfigurationPresets.kt b/build-logic/conventions/src/main/kotlin/com/datadoghq/native/config/ConfigurationPresets.kt index b2be2b586..d05781062 100644 --- a/build-logic/conventions/src/main/kotlin/com/datadoghq/native/config/ConfigurationPresets.kt +++ b/build-logic/conventions/src/main/kotlin/com/datadoghq/native/config/ConfigurationPresets.kt @@ -59,7 +59,7 @@ object ConfigurationPresets { project.logger.lifecycle("Active configurations: ${activeConfigs.map { it.name }.joinToString(", ")}") } - private fun commonLinuxCompilerArgs(version: String): List { + private fun commonCompilerArgs(version: String): List { val args = mutableListOf( "-fPIC", "-fno-omit-frame-pointer", @@ -78,6 +78,19 @@ object ConfigurationPresets { return args } + private fun commonLinuxCompilerArgs( + version: String, + architecture: Architecture = Architecture.X64 + ): List { + val args = commonCompilerArgs(version).toMutableList() + // Use TLS descriptors (GNU2 dialect) for thread-local storage on x86_64. + // This is required by thread context (https://github.com/open-telemetry/opentelemetry-specification/pull/4947) + if (architecture == Architecture.X64) { + args.add("-mtls-dialect=gnu2") + } + return args + } + private fun commonLinuxLinkerArgs(): List = listOf( "-ldl", "-Wl,-z,defs", @@ -89,7 +102,7 @@ object ConfigurationPresets { ) private fun commonMacosCompilerArgs(version: String): List = - commonLinuxCompilerArgs(version) + listOf("-D_XOPEN_SOURCE", "-D_DARWIN_C_SOURCE") + commonCompilerArgs(version) + listOf("-D_XOPEN_SOURCE", "-D_DARWIN_C_SOURCE") fun configureRelease( config: BuildConfiguration, @@ -104,7 +117,7 @@ object ConfigurationPresets { when (platform) { Platform.LINUX -> { config.compilerArgs.set( - listOf("-O3", "-DNDEBUG", "-g") + commonLinuxCompilerArgs(version) + listOf("-O3", "-DNDEBUG", "-g") + commonLinuxCompilerArgs(version, architecture) ) config.linkerArgs.set( commonLinuxLinkerArgs() + listOf( @@ -138,7 +151,7 @@ object ConfigurationPresets { when (platform) { Platform.LINUX -> { config.compilerArgs.set( - listOf("-O0", "-g", "-DDEBUG") + commonLinuxCompilerArgs(version) + listOf("-O0", "-g", "-DDEBUG") + commonLinuxCompilerArgs(version, architecture) ) config.linkerArgs.set(commonLinuxLinkerArgs()) } @@ -184,7 +197,7 @@ object ConfigurationPresets { when (platform) { Platform.LINUX -> { - config.compilerArgs.set(asanCompilerArgs + commonLinuxCompilerArgs(version)) + config.compilerArgs.set(asanCompilerArgs + commonLinuxCompilerArgs(version, architecture)) val libasan = PlatformUtils.locateLibasan(compiler) // Link against the sanitizer runtime that matches the compiler: @@ -247,7 +260,7 @@ object ConfigurationPresets { when (platform) { Platform.LINUX -> { - config.compilerArgs.set(tsanCompilerArgs + commonLinuxCompilerArgs(version)) + config.compilerArgs.set(tsanCompilerArgs + commonLinuxCompilerArgs(version, architecture)) val libtsan = PlatformUtils.locateLibtsan(compiler) // Use the library name from the resolved path so that clang's own @@ -321,7 +334,7 @@ object ConfigurationPresets { when (platform) { Platform.LINUX -> { - config.compilerArgs.set(fuzzerCompilerArgs + commonLinuxCompilerArgs(version)) + config.compilerArgs.set(fuzzerCompilerArgs + commonLinuxCompilerArgs(version, architecture)) config.linkerArgs.set(commonLinuxLinkerArgs() + fuzzerLinkerArgs) config.testEnvironment.apply { From da017b9f0e0a372280ecb001604cfbc964ec0996 Mon Sep 17 00:00:00 2001 From: Nicolas Savoire Date: Tue, 16 Jun 2026 14:37:34 +0200 Subject: [PATCH 2/3] Only use tlsdesc if supported by compiler --- .../native/config/ConfigurationPresets.kt | 32 ++++++++------- .../datadoghq/native/util/PlatformUtils.kt | 39 +++++++++++++++++++ 2 files changed, 58 insertions(+), 13 deletions(-) diff --git a/build-logic/conventions/src/main/kotlin/com/datadoghq/native/config/ConfigurationPresets.kt b/build-logic/conventions/src/main/kotlin/com/datadoghq/native/config/ConfigurationPresets.kt index d05781062..2064babd4 100644 --- a/build-logic/conventions/src/main/kotlin/com/datadoghq/native/config/ConfigurationPresets.kt +++ b/build-logic/conventions/src/main/kotlin/com/datadoghq/native/config/ConfigurationPresets.kt @@ -39,10 +39,10 @@ object ConfigurationPresets { extension.buildConfigurations.apply { register("release") { - configureRelease(this, currentPlatform, currentArch, version) + configureRelease(this, currentPlatform, currentArch, version, compiler) } register("debug") { - configureDebug(this, currentPlatform, currentArch, version) + configureDebug(this, currentPlatform, currentArch, version, compiler) } register("asan") { configureAsan(this, currentPlatform, currentArch, version, rootDir, compiler) @@ -51,7 +51,7 @@ object ConfigurationPresets { configureTsan(this, currentPlatform, currentArch, version, rootDir, compiler) } register("fuzzer") { - configureFuzzer(this, currentPlatform, currentArch, version, rootDir) + configureFuzzer(this, currentPlatform, currentArch, version, rootDir, compiler) } } @@ -80,12 +80,15 @@ object ConfigurationPresets { private fun commonLinuxCompilerArgs( version: String, - architecture: Architecture = Architecture.X64 + architecture: Architecture = Architecture.X64, + compiler: String = "gcc" ): List { val args = commonCompilerArgs(version).toMutableList() // Use TLS descriptors (GNU2 dialect) for thread-local storage on x86_64. // This is required by thread context (https://github.com/open-telemetry/opentelemetry-specification/pull/4947) - if (architecture == Architecture.X64) { + // The dialect is only supported by GCC and clang >= 19, so probe the + // compiler before adding the flag rather than relying on its version. + if (architecture == Architecture.X64 && PlatformUtils.supportsTlsDialectGnu2(compiler)) { args.add("-mtls-dialect=gnu2") } return args @@ -108,7 +111,8 @@ object ConfigurationPresets { config: BuildConfiguration, platform: Platform, architecture: Architecture, - version: String + version: String, + compiler: String = "gcc" ) { config.platform.set(platform) config.architecture.set(architecture) @@ -117,7 +121,7 @@ object ConfigurationPresets { when (platform) { Platform.LINUX -> { config.compilerArgs.set( - listOf("-O3", "-DNDEBUG", "-g") + commonLinuxCompilerArgs(version, architecture) + listOf("-O3", "-DNDEBUG", "-g") + commonLinuxCompilerArgs(version, architecture, compiler) ) config.linkerArgs.set( commonLinuxLinkerArgs() + listOf( @@ -142,7 +146,8 @@ object ConfigurationPresets { config: BuildConfiguration, platform: Platform, architecture: Architecture, - version: String + version: String, + compiler: String = "gcc" ) { config.platform.set(platform) config.architecture.set(architecture) @@ -151,7 +156,7 @@ object ConfigurationPresets { when (platform) { Platform.LINUX -> { config.compilerArgs.set( - listOf("-O0", "-g", "-DDEBUG") + commonLinuxCompilerArgs(version, architecture) + listOf("-O0", "-g", "-DDEBUG") + commonLinuxCompilerArgs(version, architecture, compiler) ) config.linkerArgs.set(commonLinuxLinkerArgs()) } @@ -197,7 +202,7 @@ object ConfigurationPresets { when (platform) { Platform.LINUX -> { - config.compilerArgs.set(asanCompilerArgs + commonLinuxCompilerArgs(version, architecture)) + config.compilerArgs.set(asanCompilerArgs + commonLinuxCompilerArgs(version, architecture, compiler)) val libasan = PlatformUtils.locateLibasan(compiler) // Link against the sanitizer runtime that matches the compiler: @@ -260,7 +265,7 @@ object ConfigurationPresets { when (platform) { Platform.LINUX -> { - config.compilerArgs.set(tsanCompilerArgs + commonLinuxCompilerArgs(version, architecture)) + config.compilerArgs.set(tsanCompilerArgs + commonLinuxCompilerArgs(version, architecture, compiler)) val libtsan = PlatformUtils.locateLibtsan(compiler) // Use the library name from the resolved path so that clang's own @@ -309,7 +314,8 @@ object ConfigurationPresets { platform: Platform, architecture: Architecture, version: String, - rootDir: File + rootDir: File, + compiler: String = "gcc" ) { config.platform.set(platform) config.architecture.set(architecture) @@ -334,7 +340,7 @@ object ConfigurationPresets { when (platform) { Platform.LINUX -> { - config.compilerArgs.set(fuzzerCompilerArgs + commonLinuxCompilerArgs(version, architecture)) + config.compilerArgs.set(fuzzerCompilerArgs + commonLinuxCompilerArgs(version, architecture, compiler)) config.linkerArgs.set(commonLinuxLinkerArgs() + fuzzerLinkerArgs) config.testEnvironment.apply { diff --git a/build-logic/conventions/src/main/kotlin/com/datadoghq/native/util/PlatformUtils.kt b/build-logic/conventions/src/main/kotlin/com/datadoghq/native/util/PlatformUtils.kt index c6fb32129..22ee6c240 100644 --- a/build-logic/conventions/src/main/kotlin/com/datadoghq/native/util/PlatformUtils.kt +++ b/build-logic/conventions/src/main/kotlin/com/datadoghq/native/util/PlatformUtils.kt @@ -199,6 +199,45 @@ object PlatformUtils { } } + /** + * Check whether the given C++ compiler accepts -mtls-dialect=gnu2 on x86_64. + * GCC has supported the GNU2 TLS dialect for a long time; clang only gained + * x86 support in clang 19. Probing the real compiler is more robust than + * parsing version strings (it also handles vendor-patched toolchains). + * Always false off x86_64 Linux — the flag is ELF/x86_64 specific. + */ + fun supportsTlsDialectGnu2(compiler: String): Boolean { + if (currentPlatform != Platform.LINUX || currentArchitecture != Architecture.X64) { + return false + } + return try { + val testFile = createTempFile("tls_dialect_check", ".cpp") + try { + testFile.writeText("int main() { return 0; }") + val process = ProcessBuilder( + compiler, + "-mtls-dialect=gnu2", + "-fsyntax-only", + testFile.toAbsolutePath().toString() + ).redirectErrorStream(true).start() + + if (!process.waitFor(10, TimeUnit.SECONDS)) { + process.destroyForcibly() + process.waitFor(5, TimeUnit.SECONDS) + return false + } + process.exitValue() == 0 + } finally { + testFile.deleteIfExists() + } + } catch (e: InterruptedException) { + Thread.currentThread().interrupt() + false + } catch (e: Exception) { + false + } + } + fun hasAsan(compiler: String = "gcc"): Boolean { return !isMusl() && locateLibasan(compiler) != null } From 6dd0183a03c87ffea103bdf1738ed6275fd484a0 Mon Sep 17 00:00:00 2001 From: Nicolas Savoire Date: Wed, 17 Jun 2026 10:21:27 +0200 Subject: [PATCH 3/3] build(native): gate -mtls-dialect=gnu2 on clang >= 19 The GNU2 TLS dialect is required by thread context but is only supported by GCC and clang >= 19, so detect the compiler before adding the flag on x86_64 Linux. Compiler detection now parses ` --version` into a CompilerInfo (executable, clang-family flag, major version); the family is read from the banner rather than guessed from the executable name. Non-clang compilers are assumed to support the dialect; clang is gated on its parsed major version and the build fails loudly if that version cannot be determined. Common compiler args are also consolidated into a single commonCompilerArgs computed once in setupStandardConfigurations and threaded into the per-config factories. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../com/datadoghq/native/NativeBuildPlugin.kt | 2 +- .../datadoghq/native/SimpleNativeLibPlugin.kt | 2 +- .../native/config/ConfigurationPresets.kt | 100 ++++++++++-------- .../com/datadoghq/native/gtest/GtestPlugin.kt | 2 +- .../datadoghq/native/util/PlatformUtils.kt | 96 +++++++++++------ ddprof-lib/benchmarks/build.gradle.kts | 2 +- 6 files changed, 121 insertions(+), 83 deletions(-) diff --git a/build-logic/conventions/src/main/kotlin/com/datadoghq/native/NativeBuildPlugin.kt b/build-logic/conventions/src/main/kotlin/com/datadoghq/native/NativeBuildPlugin.kt index 33628c1f3..c2acf83cc 100644 --- a/build-logic/conventions/src/main/kotlin/com/datadoghq/native/NativeBuildPlugin.kt +++ b/build-logic/conventions/src/main/kotlin/com/datadoghq/native/NativeBuildPlugin.kt @@ -149,7 +149,7 @@ class NativeBuildPlugin : Plugin { 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, diff --git a/build-logic/conventions/src/main/kotlin/com/datadoghq/native/SimpleNativeLibPlugin.kt b/build-logic/conventions/src/main/kotlin/com/datadoghq/native/SimpleNativeLibPlugin.kt index f39087c02..1189a3c5e 100644 --- a/build-logic/conventions/src/main/kotlin/com/datadoghq/native/SimpleNativeLibPlugin.kt +++ b/build-logic/conventions/src/main/kotlin/com/datadoghq/native/SimpleNativeLibPlugin.kt @@ -46,7 +46,7 @@ class SimpleNativeLibPlugin : Plugin { 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 diff --git a/build-logic/conventions/src/main/kotlin/com/datadoghq/native/config/ConfigurationPresets.kt b/build-logic/conventions/src/main/kotlin/com/datadoghq/native/config/ConfigurationPresets.kt index 2064babd4..cea7c8d58 100644 --- a/build-logic/conventions/src/main/kotlin/com/datadoghq/native/config/ConfigurationPresets.kt +++ b/build-logic/conventions/src/main/kotlin/com/datadoghq/native/config/ConfigurationPresets.kt @@ -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 @@ -35,23 +37,25 @@ 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, compiler) + configureRelease(this, currentPlatform, currentArch, commonCompilerArgs) } register("debug") { - configureDebug(this, currentPlatform, currentArch, version, compiler) + 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, compiler) + configureFuzzer(this, currentPlatform, currentArch, rootDir, commonCompilerArgs) } } @@ -59,7 +63,13 @@ object ConfigurationPresets { project.logger.lifecycle("Active configurations: ${activeConfigs.map { it.name }.joinToString(", ")}") } - private fun commonCompilerArgs(version: String): List { + private fun commonCompilerArgs( + version: String, + compiler: CompilerInfo, + architecture: Architecture, + platform: Platform, + logger: Logger + ): List { val args = mutableListOf( "-fPIC", "-fno-omit-frame-pointer", @@ -75,22 +85,26 @@ object ConfigurationPresets { if (PlatformUtils.isMusl()) { args.add("-D__musl__") } - return args - } - private fun commonLinuxCompilerArgs( - version: String, - architecture: Architecture = Architecture.X64, - compiler: String = "gcc" - ): List { - val args = commonCompilerArgs(version).toMutableList() - // Use TLS descriptors (GNU2 dialect) for thread-local storage on x86_64. - // This is required by thread context (https://github.com/open-telemetry/opentelemetry-specification/pull/4947) - // The dialect is only supported by GCC and clang >= 19, so probe the - // compiler before adding the flag rather than relying on its version. - if (architecture == Architecture.X64 && PlatformUtils.supportsTlsDialectGnu2(compiler)) { - args.add("-mtls-dialect=gnu2") + // 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 } @@ -104,15 +118,11 @@ object ConfigurationPresets { "-Wl,--build-id" ) - private fun commonMacosCompilerArgs(version: String): List = - commonCompilerArgs(version) + listOf("-D_XOPEN_SOURCE", "-D_DARWIN_C_SOURCE") - fun configureRelease( config: BuildConfiguration, platform: Platform, architecture: Architecture, - version: String, - compiler: String = "gcc" + commonCompilerArgs: List ) { config.platform.set(platform) config.architecture.set(architecture) @@ -121,7 +131,7 @@ object ConfigurationPresets { when (platform) { Platform.LINUX -> { config.compilerArgs.set( - listOf("-O3", "-DNDEBUG", "-g") + commonLinuxCompilerArgs(version, architecture, compiler) + listOf("-O3", "-DNDEBUG", "-g") + commonCompilerArgs ) config.linkerArgs.set( commonLinuxLinkerArgs() + listOf( @@ -135,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()) } @@ -146,8 +156,7 @@ object ConfigurationPresets { config: BuildConfiguration, platform: Platform, architecture: Architecture, - version: String, - compiler: String = "gcc" + commonCompilerArgs: List ) { config.platform.set(platform) config.architecture.set(architecture) @@ -156,13 +165,13 @@ object ConfigurationPresets { when (platform) { Platform.LINUX -> { config.compilerArgs.set( - listOf("-O0", "-g", "-DDEBUG") + commonLinuxCompilerArgs(version, architecture, compiler) + 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()) } @@ -173,13 +182,13 @@ object ConfigurationPresets { config: BuildConfiguration, platform: Platform, architecture: Architecture, - version: String, rootDir: File, - compiler: String = "gcc" + compiler: CompilerInfo, + commonCompilerArgs: List ) { 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", @@ -202,9 +211,9 @@ object ConfigurationPresets { when (platform) { Platform.LINUX -> { - config.compilerArgs.set(asanCompilerArgs + commonLinuxCompilerArgs(version, architecture, compiler)) + 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-.so, which // includes UBSan symbols; -lclang_rt.asan- satisfies -z defs @@ -246,13 +255,13 @@ object ConfigurationPresets { config: BuildConfiguration, platform: Platform, architecture: Architecture, - version: String, rootDir: File, - compiler: String = "gcc" + compiler: CompilerInfo, + commonCompilerArgs: List ) { 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", @@ -265,9 +274,9 @@ object ConfigurationPresets { when (platform) { Platform.LINUX -> { - config.compilerArgs.set(tsanCompilerArgs + commonLinuxCompilerArgs(version, architecture, compiler)) + 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-.so is linked by name (not as -ltsan). val tsanLinkerArgs = if (libtsan != null) { @@ -313,9 +322,8 @@ object ConfigurationPresets { config: BuildConfiguration, platform: Platform, architecture: Architecture, - version: String, rootDir: File, - compiler: String = "gcc" + commonCompilerArgs: List ) { config.platform.set(platform) config.architecture.set(architecture) @@ -340,7 +348,7 @@ object ConfigurationPresets { when (platform) { Platform.LINUX -> { - config.compilerArgs.set(fuzzerCompilerArgs + commonLinuxCompilerArgs(version, architecture, compiler)) + config.compilerArgs.set(fuzzerCompilerArgs + commonCompilerArgs) config.linkerArgs.set(commonLinuxLinkerArgs() + fuzzerLinkerArgs) config.testEnvironment.apply { @@ -349,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 { diff --git a/build-logic/conventions/src/main/kotlin/com/datadoghq/native/gtest/GtestPlugin.kt b/build-logic/conventions/src/main/kotlin/com/datadoghq/native/gtest/GtestPlugin.kt index d2196cf8d..eafe356ac 100644 --- a/build-logic/conventions/src/main/kotlin/com/datadoghq/native/gtest/GtestPlugin.kt +++ b/build-logic/conventions/src/main/kotlin/com/datadoghq/native/gtest/GtestPlugin.kt @@ -253,7 +253,7 @@ class GtestPlugin : Plugin { } } - 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 { return when (PlatformUtils.currentPlatform) { diff --git a/build-logic/conventions/src/main/kotlin/com/datadoghq/native/util/PlatformUtils.kt b/build-logic/conventions/src/main/kotlin/com/datadoghq/native/util/PlatformUtils.kt index 22ee6c240..2b7c2dad1 100644 --- a/build-logic/conventions/src/main/kotlin/com/datadoghq/native/util/PlatformUtils.kt +++ b/build-logic/conventions/src/main/kotlin/com/datadoghq/native/util/PlatformUtils.kt @@ -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() } @@ -200,42 +215,48 @@ object PlatformUtils { } /** - * Check whether the given C++ compiler accepts -mtls-dialect=gnu2 on x86_64. - * GCC has supported the GNU2 TLS dialect for a long time; clang only gained - * x86 support in clang 19. Probing the real compiler is more robust than - * parsing version strings (it also handles vendor-patched toolchains). - * Always false off x86_64 Linux — the flag is ELF/x86_64 specific. + * Detect a compiler's family and major version from ` --version`, + * or null if neither can be parsed. The family comes from the matching banner + * shape (not the executable name): "clang version " -> clang; + * ") ." -> non-clang. Failures are logged via [logger]. */ - fun supportsTlsDialectGnu2(compiler: String): Boolean { - if (currentPlatform != Platform.LINUX || currentArchitecture != Architecture.X64) { - return false - } - return try { - val testFile = createTempFile("tls_dialect_check", ".cpp") - try { - testFile.writeText("int main() { return 0; }") - val process = ProcessBuilder( - compiler, - "-mtls-dialect=gnu2", - "-fsyntax-only", - testFile.toAbsolutePath().toString() - ).redirectErrorStream(true).start() - - if (!process.waitFor(10, TimeUnit.SECONDS)) { - process.destroyForcibly() - process.waitFor(5, TimeUnit.SECONDS) - return false - } - process.exitValue() == 0 - } finally { - testFile.deleteIfExists() + 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() - false + return null } catch (e: Exception) { - false + 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 { @@ -333,7 +354,7 @@ object PlatformUtils { return "$homebrewLLVM/bin/clang++" } } - return findCompiler(project) + return findCompiler(project).executable } /** @@ -358,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) { diff --git a/ddprof-lib/benchmarks/build.gradle.kts b/ddprof-lib/benchmarks/build.gradle.kts index d3f0d2f3a..bc4295c2a 100644 --- a/ddprof-lib/benchmarks/build.gradle.kts +++ b/ddprof-lib/benchmarks/build.gradle.kts @@ -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("compileBenchmark") {