The code is licensed under the Apache License 2.0 (see LICENSE for details).
First of all, thanks for contributing! This document provides guidelines for contributing to this repository. To propose improvements, feel free to submit a PR.
- If you think you've found an issue, search the issue list to see if there's an existing issue.
- Then, if you find nothing, open a GitHub issue.
This guide will help you set up your development environment, understand the build process, and make your first contribution.
- Prerequisites
- Quick Setup (Recommended)
- Getting Started
- Project Structure
- Build Scripts Explained
- Development Workflow
- Testing Your Changes
- Common Tasks
- Troubleshooting
- Pull Request Guidelines
Note: The automated setup script (
./setup.sh) can install most of these for you. This section is provided for reference and manual setup.
macOS Development Machine (required for iOS development):
- macOS 13.0+ (Ventura or later)
- Xcode 15.0+ with Command Line Tools
- .NET 9 SDK or .NET 10 SDK
- Android SDK (for Android development)
Tools and SDKs:
-
.NET 9 or .NET 10 SDK
# Download from https://dotnet.microsoft.com/download # Verify installation: dotnet --version # Should show 9.0.x or 10.0.x
-
Xcode (for iOS)
# Install from Mac App Store # Install Command Line Tools: xcode-select --install
-
Android SDK
# Install via Android Studio or Visual Studio # Set ANDROID_HOME environment variable: export ANDROID_HOME=$HOME/Library/Android/sdk export PATH=$PATH:$ANDROID_HOME/emulator export PATH=$PATH:$ANDROID_HOME/platform-tools
-
Java JDK 17+ (for Android)
# Install via Homebrew: brew install openjdk@17
- Visual Studio for Mac or Visual Studio Code with C# extension
- Android Studio (for Android emulator management)
- Xcode Simulator (comes with Xcode)
Use the automated setup script to configure your development environment:
./setup.shThe script will:
- Check for required dependencies
- Offer to install missing components
- Configure environment variables
- Optionally verify the setup is functional
./setup.sh- Full interactive setup with prompts./setup.sh --help- Show usage information./setup.sh --verify- Run verification tests after setup./setup.sh --verify-only- Only run verification tests
To verify your environment is working after setup:
./setup.sh --verifyThis creates a temporary MAUI project and tests both iOS and Android builds.
For manual setup or troubleshooting, continue with the sections below.
git clone https://github.com/DataDog/dd-sdk-maui.git
cd dd-sdk-mauiRun the automated setup script:
./setup.shThe script will check all required dependencies and offer to install missing components. Follow the prompts to complete your environment setup.
Tip: If you prefer manual setup, see the Prerequisites section above for detailed instructions.
Run the root build script to compile all components:
./build.shThis will:
- Build iOS native wrapper (Swift → XCFramework)
- Build Android native wrapper (Kotlin → AAR)
- Build all C# bindings (→ NuGet packages)
- Output everything to
./local-packages/
Test that everything works:
cd example
# iOS
./build.sh --ios --run
# Android (ensure emulator is running)
./build.sh --android --runYou should see the example app launch and logs appearing in your Datadog account.
dd-sdk-maui/
│
├── build.sh # Root build script (rebuilds everything)
├── NuGet.Config # Local package source configuration
│
├── native-wrappers/ # Platform-native code
│ ├── ios/
│ │ ├── build.sh # iOS-specific build script
│ │ └── DatadogWrapper/ # Swift Package Manager project
│ │ └── Sources/DatadogWrapper/
│ │ ├── DatadogWrapper.swift # SDK initialization
│ │ ├── DdLogs.swift # Logging API
│ │ └── DdRum.swift # RUM API
│ └── android/
│ ├── gradlew # Gradle wrapper
│ └── datadogwrapper/ # Android library module
│ └── src/main/kotlin/com/datadog/wrapper/
│ ├── DatadogWrapper.kt # SDK initialization
│ ├── DdLogs.kt # Logging API
│ └── DdRum.kt # RUM API
│
├── bindings/ # C# binding projects
│ ├── Datadog.iOS.Binding/
│ ├── Datadog.Android.*/ # 4+ Android binding projects
│ │ └── Datadog.Android.Logs/ # Android Logs AAR binding
│ │ └── Datadog.Android.Trace/ # Android Trace AAR binding
│ │ └── Datadog.Android.Rum/ # Android RUM AAR binding
│ └── Datadog.Maui/ # C# intermediary layer + meta-package
│ ├── DdSdkConfiguration.cs # Configuration object
│ ├── DdSdk.cs # SDK init (unified API)
│ ├── DdLogs.cs # Logging (unified API)
│ └── DdTrace.cs # Tracing (unified API)
│ └── DdRum.cs # RUM (unified API)
│
├── example/ # Test/demo application
│ ├── build.sh # Example app build script
│ └── example.csproj # MAUI app project
│
└── local-packages/ # NuGet package output (gitignored)
Purpose: Complete rebuild of all native wrappers and C# bindings in correct dependency order.
What it does step-by-step:
cd native-wrappers/ios/DatadogWrapper
rm -rf .build .swiftpm # Clean previous build
cd .. && ./build.sh # Run iOS build scriptThe iOS build script (native-wrappers/ios/build.sh):
- Builds Swift code for iOS device (arm64)
- Builds Swift code for iOS simulator (arm64 + x86_64)
- Creates fat binary for simulator
- Packages as XCFramework
- Copies to
bindings/Datadog.iOS.Binding/NativeReference/
Output: DatadogWrapper.xcframework (~11MB)
cd native-wrappers/android
./gradlew clean # Clean previous build
./gradlew :datadogwrapper:assembleRelease # Build AAR
cp datadogwrapper/build/outputs/aar/datadogwrapper-release.aar \
../../bindings/Datadog.Android.Binding/Jars/Output: datadogwrapper-release.aar (~8KB)
cd bindings/Datadog.iOS.Binding
rm -rf bin obj # Clean
dotnet build -c Release # Build binding
dotnet pack -c Release # Create NuGet package
cp bin/Release/*.nupkg ../../local-packages/Output: Datadog.iOS.Binding.0.0.1.nupkg
Builds in dependency order:
- Datadog.Android.Internal - Internal APIs binding
- Datadog.Android.Core - Core SDK binding (depends on Internal)
- Datadog.Android.Logs - Logs module binding (depends on Core)
- Datadog.Android.Trace - Trace module binding (depends on Core)
- Datadog.Android.Binding - Wrapper binding (depends on all above)
Each project:
cd bindings/Datadog.Android.{Project}
rm -rf bin obj
dotnet build -c Release
dotnet pack -c Release
cp bin/Release/*.nupkg ../../local-packages/Why this order matters: Later projects reference earlier ones as NuGet PackageReferences. Building out of order will fail.
cd bindings/Datadog.Maui
rm -rf bin obj
dotnet build -c Release
dotnet pack -c Release
cp bin/Release/*.nupkg ../../local-packages/Output: Datadog.Maui.0.0.1.nupkg (aggregates iOS + Android bindings)
Purpose: Build and run the example MAUI application.
Usage:
./build.sh [OPTIONS]
Options:
--ios Build for iOS (default)
--android Build for Android
--run Run the app after building
-h, --help Show help messageExamples:
# Build for iOS (default)
./build.sh
# Build and run on iOS simulator
./build.sh --ios --run
# Build and run on Android emulator
./build.sh --android --runWhat it does:
-
Clean (always):
rm -rf bin obj # Remove build artifacts + stale NuGet restore data rm -rf ~/.nuget/packages/datadog.* # Clear NuGet global cache for our packages
-
Restore packages:
dotnet restore
-
Build (with
--no-restoreto avoid redundant restores):# iOS (.NET 10) dotnet build -f net10.0-ios --no-restore # Android (.NET 10) dotnet build -f net10.0-android --no-restore # iOS (.NET 9) dotnet build -f net9.0-ios --no-restore # Android (.NET 9) dotnet build -f net9.0-android --no-restore
-
Run (if
--runflag provided):# iOS dotnet build -t:Run -f net10.0-ios --no-restore # Android dotnet build -t:Run -f net10.0-android -p:AndroidAttachDebugger=false --no-restore
Or use the
build.shscript which also supports--net9:./build.sh --ios --run # .NET 10 (default) ./build.sh --ios --run --net9 # .NET 9 ./build.sh --android --run --net9
The build script always cleans obj/ and the NuGet cache to prevent stale restore data from causing build failures after SDK rebuilds.
- Make changes to native code (Swift or Kotlin)
- Rebuild native wrapper (produces XCFramework or AAR)
- Rebuild C# bindings (produces NuGet packages)
- Test in example app
# From project root
./build.sh
# Test changes
cd example
./build.sh --ios --run
./build.sh --android --runiOS only:
cd native-wrappers/ios && ./build.sh
cd ../../bindings/Datadog.iOS.Binding
dotnet pack -c Release
cp bin/Release/*.nupkg ../../local-packages/
cd ../../bindings/Datadog.Maui
dotnet pack -c Release
cp bin/Release/*.nupkg ../../local-packages/
cd ../../example
./build.sh --ios --runAndroid only:
cd native-wrappers/android
./gradlew :datadogwrapper:assembleRelease
cp datadogwrapper/build/outputs/aar/datadogwrapper-release.aar \
../../bindings/Datadog.Android.Binding/Jars/
cd ../../bindings/Datadog.Android.Binding
dotnet pack -c Release
cp bin/Release/*.nupkg ../../local-packages/
cd ../Datadog.Maui
dotnet pack -c Release
cp bin/Release/*.nupkg ../../local-packages/
cd ../../example
./build.sh --android --runTip: Use ./build.sh from root for simplicity. It's fast (~2-3 minutes).
-
Build bindings:
./build.sh
-
Run example app:
cd example ./build.sh --ios --run # iOS ./build.sh --android --run # Android
-
Verify in Datadog UI:
- Open Datadog Logs Explorer
- Filter:
service:sergio-maui-test env:dev - Look for test log messages
You should see logs like:
[INFO] iOS binding validation - LogInfo works!
[DEBUG] iOS binding validation - LogDebug works!
[WARN] iOS binding validation - LogWarn works!
[ERROR] iOS binding validation - LogError works!
Run the C# unit tests to verify SDK behavior:
# Run all tests
dotnet test tests/Datadog.Maui.Tests
# Run with verbose output
dotnet test tests/Datadog.Maui.Tests -v detailed
# Run specific test class
dotnet test tests/Datadog.Maui.Tests --filter "FullyQualifiedName~DdSdkConfigurationTests"Test coverage:
DdSdkConfigurationTests- SDK initialization with all config options via test bridgeDdSdkConversionTests- Enum-to-string conversions (site, consent, additional config)FileBasedConfigurationTests- JSON config parsing, validation, and error handlingInternalLogTests- SDK logging with verbosity filteringDdLogsConfigurationTests- Logs module configurationDdTraceConfigurationTests- Trace module configuration
iOS tests (XCTest):
cd native-wrappers/ios/DatadogWrapper
swift testAndroid tests (JUnit + MockK):
cd native-wrappers/android
./gradlew test
# View test report
open datadogwrapper/build/reports/tests/testDebugUnitTest/index.htmlEnable verbose logging:
# iOS build
cd native-wrappers/ios
./build.sh 2>&1 | tee build.log
# Android build
cd native-wrappers/android
./gradlew :datadogwrapper:assembleRelease --info
# .NET build
dotnet build -v detailedCheck XCFramework symbols (iOS):
nm -gU bindings/Datadog.iOS.Binding/NativeReference/DatadogWrapper.xcframework/ios-arm64/DatadogWrapper.framework/DatadogWrapperInspect AAR contents (Android):
unzip -l bindings/Datadog.Android.Binding/Jars/datadogwrapper-release.aariOS (native-wrappers/ios/DatadogWrapper/Sources/DatadogWrapper/DdLogs.swift):
@objc public static func logCritical(_ message: String) {
logger?.critical(message)
}Android (native-wrappers/android/datadogwrapper/src/main/kotlin/com/datadog/wrapper/DdLogs.kt):
@JvmStatic
fun logCritical(message: String) {
logger?.critical(message)
}iOS (bindings/Datadog.iOS.Binding/ApiDefinition.cs):
[Static]
[Export("logCritical:")]
void LogCritical(string message);Android: Auto-generated from AAR (no manual changes needed)
bindings/Datadog.Maui/DdLogs.cs:
public static void Critical(string message)
{
DdSdk.LogDebug($"DdLogs.Critical called: {message}");
NativeDdLogs.LogCritical(message);
}This is the method consumers will call. The #if ANDROID / #elif IOS directives are only needed when the native APIs differ between platforms (e.g. different parameter types).
RUM module pattern: The RUM module (
DdRum.cs) uses a dictionary bridge pattern rather than individual parameters. Instead of passing each configuration field as a separate argument to the native layer, the entireDdRumConfigurationobject is serialized into aDictionary<string, object>(or equivalent native map) and passed as a single argument. This keeps the native bridge stable as new configuration fields are added. SeeDdRum.csfor a worked example of this pattern.
./build.sh
cd example
./build.sh --ios --run
./build.sh --android --runUse the update-native-sdk.sh script to bump native SDK versions across all files:
# Update both platforms
./update-native-sdk.sh --ios 3.8.0 --android 3.8.0
# Update one platform
./update-native-sdk.sh --android 3.8.0This script updates versions.properties, Package.swift, build.gradle.kts, and all Android binding .csproj files in one pass.
Android transitive dependencies: When bumping the Android SDK, the script automatically runs resolve-android-deps.sh which:
- Resolves the full Gradle dependency tree
- Auto-updates
AndroidMavenLibraryversions inDatadog.Android.Core.csproj - Warns if any NuGet
PackageReferenceversions need manual updating (e.g.,Xamarin.Kotlin.StdLib,GoogleGson)
If you see NuGet warnings, check nuget.org for a compatible release and update the version in the relevant .csproj files and android-transitive-deps.json.
iOS transitive dependencies: Not a concern — SPM statically links all dependencies into the XCFramework at build time. No runtime dependency declarations are needed.
After bumping:
./build.sh # Full rebuild
./verify-artifacts.sh # Validate artifacts + dependency alignment"Permission denied" when running setup.sh:
chmod +x setup.sh
./setup.shEnvironment variables not persisting after setup:
- Restart your terminal after running setup.sh
- Or manually source your shell profile:
source ~/.zshrc(or~/.bash_profile)
Verification tests fail but dependencies show as installed:
- Try building the SDK directly:
./build.sh - Check MAUI workload:
dotnet workload list | grep maui - Verify simulator/emulator availability:
- iOS:
xcrun simctl list devices | grep iPhone - Android:
$ANDROID_HOME/emulator/emulator -list-avds
- iOS:
Android SDK licenses not accepted:
$ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager --licensesXcode Command Line Tools installation hangs:
- Cancel and install manually:
xcode-select --install - Or download from Apple Developer: https://developer.apple.com/download/
Cause: Swift method not properly exposed to Objective-C.
Solution:
- Verify
@objc(ClassName)on class - Verify
@objc public staticon methods - Check selector in
[Export("...")]matches Swift signature
Debug:
nm -gU bindings/Datadog.iOS.Binding/NativeReference/DatadogWrapper.xcframework/ios-arm64/DatadogWrapper.framework/DatadogWrapper | grep initializeCause: Binding project uses ProjectReference instead of PackageReference for Android bindings.
Solution: Use PackageReference in .csproj:
<PackageReference Include="Datadog.Android.Core" Version="0.0.1" />Cause: NuGet package cache holding old version.
Solution: Run the example build script — it always cleans and refreshes the NuGet cache:
cd example
./build.sh --ios --runCause: Android SDK path not configured.
Solution: Set ANDROID_HOME or create local.properties:
echo "sdk.dir=$HOME/Library/Android/sdk" > native-wrappers/android/local.propertiesCause: Simulator not running or not selected.
Solution:
# List available simulators
xcrun simctl list devices
# Boot a simulator
xcrun simctl boot "iPhone 15"
# Or use Xcode UI to launch simulatorCause: No emulator running.
Solution:
# List available emulators
emulator -list-avds
# Start an emulator
emulator -avd Pixel_7_API_36 &
# Or use Android Studio AVD ManagerAvoid changing too many things at once. For instance if you're fixing a bug and at the same time adding some code refactor, it makes reviewing harder and the time-to-release longer.
Please don't be this person: git commit -m "Fixed stuff". Take a moment to write meaningful commit messages.
The commit message should describe the reason for the change and give extra details that will allow someone later on to understand in 5 seconds the thing you've been working on for a day.
Rebase your changes on develop and squash your commits whenever possible. This keeps history cleaner and easier to revert things. It also makes developers happier!
-
Build and test:
./build.sh cd example ./build.sh --ios --run ./build.sh --android --run -
Verify logs in Datadog: Ensure your changes work end-to-end
-
Update documentation: If adding features, update AGENTS.md and SPEC.md
-
Follow commit conventions:
feat(ios): add critical log level fix(android): correct site parameter mapping docs: update build instructions
-
Fork the repository
-
Create a feature branch:
git checkout -b feature/add-critical-logging
-
Make your changes and commit:
git add . git commit -m "feat: add critical log level to iOS and Android"
-
Push to your fork:
git push origin feature/add-critical-logging
-
Open Pull Request on GitHub
-
Address review feedback
- Code builds without errors on both iOS and Android
- Example app runs successfully on both platforms
- Logs appear in Datadog UI
- Documentation updated (if applicable)
- No unnecessary files committed (check .gitignore)
- Commit messages follow convention
- PR description explains what and why
Recommended extensions:
- C# Dev Kit
- .NET MAUI (Preview)
- Swift (for native wrapper editing)
- Kotlin (for native wrapper editing)
For iOS native wrapper development:
- Open
native-wrappers/ios/DatadogWrapperin Xcode - Select iOS Simulator target
- Build and debug Swift code directly
For Android native wrapper development:
- Open
native-wrappers/androidin Android Studio - Sync Gradle
- Build
datadogwrappermodule
-
Issues: Open an issue on GitHub with:
- Description of problem
- Steps to reproduce
- Build output / error messages
- Platform (iOS/Android) and OS version
-
Questions: Start a GitHub Discussion
-
Documentation:
- See
SPEC.mdfor technical details - See
AGENTS.mdfor AI agent context
- See
Be respectful, inclusive, and constructive in all interactions.
This project is licensed under the Apache License 2.0 - see the LICENSE file for details.