Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 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
2 changes: 1 addition & 1 deletion .github/scripts/test_alpine_aarch64.sh
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,6 @@ JAVA_VERSION=$("${JAVA_TEST_HOME}/bin/java" -version 2>&1 | awk -F '"' '/version
}')
export JAVA_VERSION

apk update && apk add curl moreutils wget hexdump linux-headers bash make g++ clang git cppcheck jq cmake gtest-dev gmock tar >/dev/null
apk update && apk add curl moreutils wget hexdump linux-headers bash make g++ clang git cppcheck jq cmake gtest-dev gmock tar binutils >/dev/null

./gradlew -PCI -PkeepJFRs :ddprof-test:test${CONFIG} --no-daemon --parallel --build-cache --no-watch-fs
6 changes: 3 additions & 3 deletions .github/workflows/test_workflow.yml
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ jobs:
if: steps.set_enabled.outputs.enabled == 'true'
run: |
sudo apt-get update
sudo apt-get install -y curl zip unzip libgtest-dev libgmock-dev
sudo apt-get install -y curl zip unzip libgtest-dev libgmock-dev binutils
if [[ ${{ matrix.java_version }} =~ "-zing" ]]; then
sudo apt-get install -y g++-9 gcc-9
sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-9 100 --slave /usr/bin/g++ g++ /usr/bin/g++-9
Expand Down Expand Up @@ -135,7 +135,7 @@ jobs:
steps:
- name: Setup OS
run: |
apk update && apk add curl moreutils wget hexdump linux-headers bash make g++ clang git cppcheck jq cmake gtest-dev gmock tar >/dev/null
apk update && apk add curl moreutils wget hexdump linux-headers bash make g++ clang git cppcheck jq cmake gtest-dev gmock tar binutils >/dev/null
- uses: actions/checkout@v3
- name: Cache Gradle Wrapper Binaries
uses: actions/cache@v4
Expand Down Expand Up @@ -286,7 +286,7 @@ jobs:
sudo apt update -y
sudo apt remove -y g++
sudo apt autoremove -y
sudo apt install -y curl zip unzip clang make build-essential
sudo apt install -y curl zip unzip clang make build-essential binutils
if [[ ${{ matrix.java_version }} =~ "-zing" ]]; then
sudo apt -y install g++-9 gcc-9
sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-9 100 --slave /usr/bin/g++ g++ /usr/bin/g++-9
Expand Down
33 changes: 33 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,39 @@ The project includes both Java and C++ unit tests. You can run them using:
### Cross-JDK Testing
`JAVA_TEST_HOME=<path to test JDK> ./gradlew testDebug`

## Release Builds and Debug Information

### Split Debug Information
Release builds automatically generate split debug information to optimize deployment size while preserving debugging capabilities:

- **Stripped libraries** (~1.2MB): Production-ready binaries with symbols removed for deployment
- **Debug symbol files** (~6.1MB): Separate `.debug` files containing full debugging information
- **Debug links**: Stripped libraries include `.gnu_debuglink` sections pointing to debug files

### Build Artifacts Structure
```
ddprof-lib/build/
├── lib/main/release/linux/x64/
│ ├── libjavaProfiler.so # Original library with debug symbols
│ ├── stripped/
│ │ └── libjavaProfiler.so # Stripped library (83% smaller)
│ └── debug/
│ └── libjavaProfiler.so.debug # Debug symbols only
├── native/release/
│ └── META-INF/native-libs/linux-x64/
│ └── libjavaProfiler.so # Final stripped library (deployed)
└── native/release-debug/
└── META-INF/native-libs/linux-x64/
└── libjavaProfiler.so.debug # Debug symbols package
```

### Build Options
- **Skip debug extraction**: `./gradlew buildRelease -Pskip-debug-extraction=true`
- **Debug extraction requires**: `objcopy` (Linux) or `dsymutil` (macOS)
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did not test on MacOS 😬
Feedback appreciated if this fails

- Ubuntu/Debian: `sudo apt-get install binutils`
- Alpine: `apk add binutils`
- macOS: Included with Xcode command line tools

## Development

### Code Quality
Expand Down
191 changes: 174 additions & 17 deletions ddprof-lib/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,179 @@ plugins {
id 'de.undercouch.download' version '4.1.1'
}

// Helper function to check if objcopy is available
def checkObjcopyAvailable() {
try {
def process = ['objcopy', '--version'].execute()
process.waitFor()
return process.exitValue() == 0
} catch (Exception e) {
return false
}
}

// Helper function to check if dsymutil is available (for macOS)
def checkDsymutilAvailable() {
try {
def process = ['dsymutil', '--version'].execute()
process.waitFor()
return process.exitValue() == 0
} catch (Exception e) {
return false
}
}

// Helper function to check if debug extraction should be skipped
def shouldSkipDebugExtraction() {
return project.hasProperty('skip-debug-extraction')
}
Comment thread
r1viollet marked this conversation as resolved.

// Helper function to get debug file path for a given config
def getDebugFilePath(config) {
def extension = os().isLinux() ? 'so' : 'dylib'
return file("$buildDir/lib/main/${config.name}/${osIdentifier()}/${archIdentifier()}/debug/libjavaProfiler.${extension}.debug")
}

// Helper function to get stripped file path for a given config
def getStrippedFilePath(config) {
def extension = os().isLinux() ? 'so' : 'dylib'
return file("$buildDir/lib/main/${config.name}/${osIdentifier()}/${archIdentifier()}/stripped/libjavaProfiler.${extension}")
}

// Helper function to create error message for missing tools
def getMissingToolErrorMessage(toolName, installInstructions) {
return """
|${toolName} is not available but is required for split debug information.
|
|To fix this issue:
|${installInstructions}
|
|If you want to build without split debug info, set -Pskip-debug-extraction=true
""".stripMargin()
}

// Helper function to create debug extraction task
def createDebugExtractionTask(config, linkTask) {
return tasks.register('extractDebugLibRelease', Exec) {
onlyIf {
!shouldSkipDebugExtraction()
}
dependsOn linkTask
description = 'Extract debug symbols from release library'
workingDir project.buildDir

doFirst {
def sourceFile = linkTask.get().linkedFile.get().asFile
def debugFile = getDebugFilePath(config)

// Check for required tools before proceeding
if (os().isLinux()) {
if (!checkObjcopyAvailable()) {
def installInstructions = """
| - On Ubuntu/Debian: sudo apt-get install binutils
| - On RHEL/CentOS: sudo yum install binutils
| - On Alpine: apk add binutils""".stripMargin()
throw new GradleException(getMissingToolErrorMessage('objcopy', installInstructions))
}
} else if (os().isMacOsX()) {
if (!checkDsymutilAvailable()) {
def installInstructions = """
| dsymutil should be available with Xcode command line tools:
| xcode-select --install""".stripMargin()
throw new GradleException(getMissingToolErrorMessage('dsymutil', installInstructions))
}
}

// Ensure debug directory exists
debugFile.parentFile.mkdirs()

// Set the command line based on platform
if (os().isLinux()) {
commandLine = ['objcopy', '--only-keep-debug', sourceFile.absolutePath, debugFile.absolutePath]
} else {
// For macOS, we'll use dsymutil instead
commandLine = ['dsymutil', sourceFile.absolutePath, '-o', debugFile.absolutePath.replace('.debug', '.dSYM')]
}
}
}
}

// Helper function to create debug link task (Linux only)
def createDebugLinkTask(config, linkTask, extractDebugTask) {
return tasks.register('addDebugLinkLibRelease', Exec) {
onlyIf {
os().isLinux() && !shouldSkipDebugExtraction()
}
dependsOn extractDebugTask
description = 'Add debug link to the original library'

doFirst {
def sourceFile = linkTask.get().linkedFile.get().asFile
def debugFile = getDebugFilePath(config)

// Check for objcopy availability
if (!checkObjcopyAvailable()) {
def installInstructions = """
| - On Ubuntu/Debian: sudo apt-get install binutils
| - On RHEL/CentOS: sudo yum install binutils
| - On Alpine: apk add binutils""".stripMargin()
throw new GradleException(getMissingToolErrorMessage('objcopy', installInstructions))
}

commandLine = ['objcopy', '--add-gnu-debuglink=' + debugFile.absolutePath, sourceFile.absolutePath]
}
}
}

// Helper function to create debug file copy task
def createDebugCopyTask(config, extractDebugTask) {
return tasks.register('copyReleaseDebugFiles', Copy) {
onlyIf {
!shouldSkipDebugExtraction()
}
dependsOn extractDebugTask
from file("$buildDir/lib/main/${config.name}/${osIdentifier()}/${archIdentifier()}/debug")
into file(libraryTargetPath(config.name + '-debug'))
include '**/*.debug'
include '**/*.dSYM/**'
}
}

// Main function to setup debug extraction for release builds
def setupDebugExtraction(config, linkTask) {
if (config.name == 'release' && config.active && !project.hasProperty('skip-native')) {
// Create all debug-related tasks
def extractDebugTask = createDebugExtractionTask(config, linkTask)
def addDebugLinkTask = createDebugLinkTask(config, linkTask, extractDebugTask)

// Create the strip task and configure it properly
def stripTask = tasks.register('stripLibRelease', StripSymbols) {
// No onlyIf needed here - setupDebugExtraction already handles the main conditions
dependsOn addDebugLinkTask
}

// Configure the strip task after registration
stripTask.configure {
targetPlatform = linkTask.get().targetPlatform
toolChain = linkTask.get().toolChain
binaryFile = linkTask.get().linkedFile.get().asFile
outputFile = getStrippedFilePath(config)
}

def copyDebugTask = createDebugCopyTask(config, extractDebugTask)

// Wire up the copy task to use stripped binaries
def copyTask = tasks.findByName("copyReleaseLibs")
if (copyTask != null) {
copyTask.dependsOn stripTask
copyTask.inputs.files stripTask.get().outputs.files

// Create an extra folder for the debug symbols
copyTask.dependsOn copyDebugTask
}
}
}

def libraryName = "ddprof"

description = "Datadog Java Profiler Library"
Expand Down Expand Up @@ -366,23 +539,7 @@ tasks.whenTaskAdded { task ->
outputs.file linkedFile
}
if (config.name == 'release') {
def stripTask = tasks.register('stripLibRelease', StripSymbols) {
onlyIf {
config.active
}
dependsOn linkTask
targetPlatform = tasks.linkLibRelease.targetPlatform
toolChain = tasks.linkLibRelease.toolChain
binaryFile = tasks.linkLibRelease.linkedFile.get()
outputFile = file("$buildDir/lib/main/${config.name}/${osIdentifier()}/${archIdentifier()}/stripped/libjavaProfiler.${os().isLinux() ? 'so' : 'dylib'}")
inputs.file binaryFile
outputs.file outputFile
}
def copyTask = tasks.findByName("copyReleaseLibs")
if (copyTask != null) {
copyTask.dependsOn stripTask
copyTask.inputs.files stripTask.get().outputs.files
}
setupDebugExtraction(config, linkTask)
}
}
}
Expand Down
Loading