Skip to content

Commit 6ab90a7

Browse files
committed
Split debug - Refactor
- Refactor split debug tasks out of the build - Check if tooling is available to perform split debug operations
1 parent f654547 commit 6ab90a7

File tree

1 file changed

+173
-71
lines changed

1 file changed

+173
-71
lines changed

ddprof-lib/build.gradle

Lines changed: 173 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,178 @@ plugins {
88
id 'de.undercouch.download' version '4.1.1'
99
}
1010

11+
// Helper function to check if objcopy is available
12+
def checkObjcopyAvailable() {
13+
try {
14+
def process = ['objcopy', '--version'].execute()
15+
process.waitFor()
16+
return process.exitValue() == 0
17+
} catch (Exception e) {
18+
return false
19+
}
20+
}
21+
22+
// Helper function to check if dsymutil is available (for macOS)
23+
def checkDsymutilAvailable() {
24+
try {
25+
def process = ['dsymutil', '--version'].execute()
26+
process.waitFor()
27+
return process.exitValue() == 0
28+
} catch (Exception e) {
29+
return false
30+
}
31+
}
32+
33+
// Helper function to check if debug extraction should be skipped
34+
def shouldSkipDebugExtraction() {
35+
return project.hasProperty('skip-debug-extraction')
36+
}
37+
38+
// Helper function to get debug file path for a given config
39+
def getDebugFilePath(config) {
40+
def extension = os().isLinux() ? 'so' : 'dylib'
41+
return file("$buildDir/lib/main/${config.name}/${osIdentifier()}/${archIdentifier()}/debug/libjavaProfiler.${extension}.debug")
42+
}
43+
44+
// Helper function to get stripped file path for a given config
45+
def getStrippedFilePath(config) {
46+
def extension = os().isLinux() ? 'so' : 'dylib'
47+
return file("$buildDir/lib/main/${config.name}/${osIdentifier()}/${archIdentifier()}/stripped/libjavaProfiler.${extension}")
48+
}
49+
50+
// Helper function to create error message for missing tools
51+
def getMissingToolErrorMessage(toolName, installInstructions) {
52+
return """
53+
|${toolName} is not available but is required for split debug information.
54+
|
55+
|To fix this issue:
56+
|${installInstructions}
57+
|
58+
|If you want to build without split debug info, set -Pskip-debug-extraction=true
59+
""".stripMargin()
60+
}
61+
62+
// Helper function to create debug extraction task
63+
def createDebugExtractionTask(config, linkTask) {
64+
return tasks.register('extractDebugLibRelease', Exec) {
65+
onlyIf {
66+
config.active && !shouldSkipDebugExtraction()
67+
}
68+
dependsOn linkTask
69+
description = 'Extract debug symbols from release library'
70+
workingDir project.buildDir
71+
72+
doFirst {
73+
def sourceFile = linkTask.get().linkedFile.get().asFile
74+
def debugFile = getDebugFilePath(config)
75+
76+
// Check for required tools before proceeding
77+
if (os().isLinux()) {
78+
if (!checkObjcopyAvailable()) {
79+
def installInstructions = """
80+
| - On Ubuntu/Debian: sudo apt-get install binutils
81+
| - On RHEL/CentOS: sudo yum install binutils
82+
| - On Alpine: apk add binutils""".stripMargin()
83+
throw new GradleException(getMissingToolErrorMessage('objcopy', installInstructions))
84+
}
85+
} else if (os().isMacOsX()) {
86+
if (!checkDsymutilAvailable()) {
87+
def installInstructions = """
88+
| dsymutil should be available with Xcode command line tools:
89+
| xcode-select --install""".stripMargin()
90+
throw new GradleException(getMissingToolErrorMessage('dsymutil', installInstructions))
91+
}
92+
}
93+
94+
// Ensure debug directory exists
95+
debugFile.parentFile.mkdirs()
96+
97+
// Set the command line based on platform
98+
if (os().isLinux()) {
99+
commandLine = ['objcopy', '--only-keep-debug', sourceFile.absolutePath, debugFile.absolutePath]
100+
} else {
101+
// For macOS, we'll use dsymutil instead
102+
commandLine = ['dsymutil', sourceFile.absolutePath, '-o', debugFile.absolutePath.replace('.debug', '.dSYM')]
103+
}
104+
}
105+
}
106+
}
107+
108+
// Helper function to create debug link task (Linux only)
109+
def createDebugLinkTask(config, linkTask, extractDebugTask) {
110+
return tasks.register('addDebugLinkLibRelease', Exec) {
111+
onlyIf {
112+
config.active && os().isLinux() && !shouldSkipDebugExtraction()
113+
}
114+
dependsOn extractDebugTask
115+
description = 'Add debug link to the original library'
116+
117+
doFirst {
118+
def sourceFile = linkTask.get().linkedFile.get().asFile
119+
def debugFile = getDebugFilePath(config)
120+
121+
// Check for objcopy availability
122+
if (!checkObjcopyAvailable()) {
123+
def installInstructions = """
124+
| - On Ubuntu/Debian: sudo apt-get install binutils
125+
| - On RHEL/CentOS: sudo yum install binutils
126+
| - On Alpine: apk add binutils""".stripMargin()
127+
throw new GradleException(getMissingToolErrorMessage('objcopy', installInstructions))
128+
}
129+
130+
commandLine = ['objcopy', '--add-gnu-debuglink=' + debugFile.absolutePath, sourceFile.absolutePath]
131+
}
132+
}
133+
}
134+
135+
// Helper function to create debug file copy task
136+
def createDebugCopyTask(config, extractDebugTask) {
137+
return tasks.register('copyReleaseDebugFiles', Copy) {
138+
onlyIf {
139+
!shouldSkipDebugExtraction()
140+
}
141+
dependsOn extractDebugTask
142+
from file("$buildDir/lib/main/${config.name}/${osIdentifier()}/${archIdentifier()}/debug")
143+
into file(libraryTargetPath(config.name + '-debug'))
144+
include '**/*.debug'
145+
include '**/*.dSYM/**'
146+
}
147+
}
148+
149+
// Main function to setup debug extraction for release builds
150+
def setupDebugExtraction(config, linkTask) {
151+
if (config.name == 'release') {
152+
// Create all debug-related tasks
153+
def extractDebugTask = createDebugExtractionTask(config, linkTask)
154+
def addDebugLinkTask = createDebugLinkTask(config, linkTask, extractDebugTask)
155+
156+
// Create the strip task and configure it properly
157+
def stripTask = tasks.register('stripLibRelease', StripSymbols) {
158+
onlyIf {
159+
config.active
160+
}
161+
dependsOn addDebugLinkTask
162+
}
163+
164+
// Configure the strip task after registration
165+
stripTask.configure {
166+
targetPlatform = linkTask.get().targetPlatform
167+
toolChain = linkTask.get().toolChain
168+
binaryFile = linkTask.get().linkedFile.get().asFile
169+
outputFile = getStrippedFilePath(config)
170+
}
171+
172+
def copyDebugTask = createDebugCopyTask(config, extractDebugTask)
173+
174+
// Wire up the copy task to use stripped binaries
175+
def copyTask = tasks.findByName("copyReleaseLibs")
176+
if (copyTask != null) {
177+
copyTask.dependsOn stripTask
178+
copyTask.inputs.files stripTask.get().outputs.files
179+
}
180+
}
181+
}
182+
11183
def libraryName = "ddprof"
12184

13185
description = "Datadog Java Profiler Library"
@@ -366,77 +538,7 @@ tasks.whenTaskAdded { task ->
366538
outputs.file linkedFile
367539
}
368540
if (config.name == 'release') {
369-
def extractDebugTask = tasks.register('extractDebugLibRelease', Exec) {
370-
onlyIf {
371-
config.active
372-
}
373-
dependsOn linkTask
374-
description = 'Extract debug symbols from release library'
375-
workingDir project.buildDir
376-
377-
def sourceFile = tasks.linkLibRelease.linkedFile.get()
378-
def debugFile = file("$buildDir/lib/main/${config.name}/${osIdentifier()}/${archIdentifier()}/debug/libjavaProfiler.${os().isLinux() ? 'so' : 'dylib'}.debug")
379-
380-
inputs.file sourceFile
381-
outputs.file debugFile
382-
383-
doFirst {
384-
// Ensure debug directory exists
385-
debugFile.parentFile.mkdirs()
386-
}
387-
388-
if (os().isLinux()) {
389-
commandLine 'objcopy', '--only-keep-debug', sourceFile.asFile.absolutePath, debugFile.absolutePath
390-
} else {
391-
// For macOS, we'll use dsymutil instead
392-
commandLine 'dsymutil', sourceFile.asFile.absolutePath, '-o', debugFile.absolutePath.replace('.debug', '.dSYM')
393-
}
394-
}
395-
396-
def addDebugLinkTask = tasks.register('addDebugLinkLibRelease', Exec) {
397-
onlyIf {
398-
config.active && os().isLinux()
399-
}
400-
dependsOn extractDebugTask
401-
description = 'Add debug link to the original library'
402-
403-
def sourceFile = tasks.linkLibRelease.linkedFile.get()
404-
def debugFile = file("$buildDir/lib/main/${config.name}/${osIdentifier()}/${archIdentifier()}/debug/libjavaProfiler.${os().isLinux() ? 'so' : 'dylib'}.debug")
405-
406-
inputs.file sourceFile
407-
inputs.file debugFile
408-
outputs.file sourceFile // modifies the source file
409-
410-
commandLine 'objcopy', '--add-gnu-debuglink=' + debugFile.absolutePath, sourceFile.asFile.absolutePath
411-
}
412-
413-
def stripTask = tasks.register('stripLibRelease', StripSymbols) {
414-
onlyIf {
415-
config.active
416-
}
417-
dependsOn addDebugLinkTask
418-
targetPlatform = tasks.linkLibRelease.targetPlatform
419-
toolChain = tasks.linkLibRelease.toolChain
420-
binaryFile = tasks.linkLibRelease.linkedFile.get()
421-
outputFile = file("$buildDir/lib/main/${config.name}/${osIdentifier()}/${archIdentifier()}/stripped/libjavaProfiler.${os().isLinux() ? 'so' : 'dylib'}")
422-
inputs.file binaryFile
423-
outputs.file outputFile
424-
}
425-
426-
// Create a task to copy debug files to the final location
427-
def copyDebugTask = tasks.register('copyReleaseDebugFiles', Copy) {
428-
dependsOn extractDebugTask
429-
from file("$buildDir/lib/main/${config.name}/${osIdentifier()}/${archIdentifier()}/debug")
430-
into file(libraryTargetPath(config.name + '-debug'))
431-
include '**/*.debug'
432-
include '**/*.dSYM/**'
433-
}
434-
435-
def copyTask = tasks.findByName("copyReleaseLibs")
436-
if (copyTask != null) {
437-
copyTask.dependsOn stripTask
438-
copyTask.inputs.files stripTask.get().outputs.files
439-
}
541+
setupDebugExtraction(config, linkTask)
440542
}
441543
}
442544
}

0 commit comments

Comments
 (0)