Skip to content
Merged
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
5 changes: 4 additions & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name: build

# Reusable pipeline: packs all eleven Datadog binding packages at a given version, validates the
# Reusable pipeline: packs all twelve Datadog binding packages at a given version, validates the
# packed packages, builds the sample against them, runs the simulator smoke tests, and uploads the
# packages as an artifact. Callers (pr.yml, release.yml) decide the version and what to do with the
# result.
Expand Down Expand Up @@ -45,6 +45,9 @@ jobs:
steps:
- uses: actions/checkout@v4

- name: Check README versions
run: ./build/CheckReadmeVersions.sh

- name: Select Xcode
uses: ./.github/actions/select-xcode

Expand Down
14 changes: 13 additions & 1 deletion Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
impossible to tell which Datadog release a given CrashReporter package belonged to.
-->
<DatadogNativeVersion>3.14.0</DatadogNativeVersion>
<DatadogBindingRevision>2</DatadogBindingRevision>
<DatadogBindingRevision>3</DatadogBindingRevision>
<VersionPrefix>$(DatadogNativeVersion).$(DatadogBindingRevision)</VersionPrefix>

<Authors>s.bokatuk</Authors>
Expand All @@ -43,4 +43,16 @@
<ContinuousIntegrationBuild Condition=" '$(GITHUB_ACTIONS)' == 'true' ">true</ContinuousIntegrationBuild>
</PropertyGroup>

<!--
Surface the curated release notes on nuget.org. docs/release-notes/<version>.md already feeds
the GitHub release; packing it here is what makes it reach the .nupkg, so nuget.org stops
showing empty release notes. A version with no curated file - a PR prerelease of unreleased
work, typically - falls back to the releases page rather than shipping nothing.
-->
<PropertyGroup>
<DatadogReleaseNotesFile>$(MSBuildThisFileDirectory)docs/release-notes/$(VersionPrefix).md</DatadogReleaseNotesFile>
<PackageReleaseNotes Condition=" Exists('$(DatadogReleaseNotesFile)') ">$([System.IO.File]::ReadAllText('$(DatadogReleaseNotesFile)'))</PackageReleaseNotes>
<PackageReleaseNotes Condition=" '$(PackageReleaseNotes)' == '' ">https://github.com/sbokatuk/DatadogNet.iOS/releases/tag/v$(VersionPrefix)</PackageReleaseNotes>
</PropertyGroup>

</Project>
60 changes: 50 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,14 +91,20 @@ and adds `Logs`, `Trace`, `SessionReplay`, `CrashReporting` or `WebViewTracking`
**Minimum deployment target**: iOS **12.2** — the Datadog frameworks are Swift and use the
OS-provided Swift runtime, ABI-stable from 12.2.

> **net8 sunset.** The net8 head is already past its platform support window — the net8 mobile
> workloads left support with MAUI 8 on 14 May 2025 — and ships for the apps that still target it,
> with the simulator checks run against it so what works is verified rather than assumed. So the
> decision does not persist by inertia: **the net8 head is dropped in the first release after
> .NET 8 itself leaves support on 10 November 2026.**

---

## Installing

```xml
<ItemGroup>
<PackageReference Include="DatadogNet.Core.iOS" Version="3.14.0.1" />
<PackageReference Include="DatadogNet.RUM.iOS" Version="3.14.0.1" />
<PackageReference Include="DatadogNet.Core.iOS" Version="3.14.0.3" />
<PackageReference Include="DatadogNet.RUM.iOS" Version="3.14.0.3" />
</ItemGroup>
```

Expand All @@ -107,8 +113,8 @@ restore them:

```xml
<ItemGroup Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'ios'">
<PackageReference Include="DatadogNet.Core.iOS" Version="3.14.0.1" />
<PackageReference Include="DatadogNet.RUM.iOS" Version="3.14.0.1" />
<PackageReference Include="DatadogNet.Core.iOS" Version="3.14.0.3" />
<PackageReference Include="DatadogNet.RUM.iOS" Version="3.14.0.3" />
</ItemGroup>
```

Expand Down Expand Up @@ -264,6 +270,21 @@ overrides.TextAndInputPrivacy = DDTextAndInputPrivacyLevelOverride.MaskAll;
overrides.Hide = new NSNumber(true);
```

### WebView tracking

```csharp
using DatadogWebViewTracking;
using Foundation;

DDWebViewTracking.EnableWithWebView(
webView, new NSSet<NSString>(new NSString("example.com")), logsSampleRate: 100);
// ...and when the web view goes away - the bridge holds a reference to it:
DDWebViewTracking.DisableWithWebView(webView);
```

The page inside must run the Datadog Browser SDK, and its host must be on the allowlist —
which is an allowlist because the bridge lets page JavaScript write into your RUM session.

### Crash reporting

Add `DatadogNet.CrashReporting.iOS`, then enable it **after** initializing the SDK:
Expand Down Expand Up @@ -316,6 +337,14 @@ Attribute values may be strings, any numeric type, `bool`, `DateTime`, `DateTime
enums, `NSObject`s, arrays, and nested dictionaries. Anything else throws `ArgumentException` rather
than being silently dropped. `DatadogAttributes` lives in `DatadogCore`.

The convenience layer is fully documented, and the **main entry types of the generated tier carry
summaries too** — `DDDatadog`, `DDConfiguration`, `DDSite`, `DDTrackingConsent`, the `Enable`/
configuration types of every module, `DDRUMMonitor`, `DDLogger`, `DDTracer` and the header
writers — written in `ApiDefinitions.cs`, which the binding generator carries through to the
package's XML docs. The long tail of generated members (the 377 RUM model types, mostly) has no
upstream docs to import and stays bare; the C# names map 1:1 onto the Objective-C ones, so
upstream's reference covers them.

### Trace ids

`GetTraceId` returns **32 lowercase hexadecimal characters** and `GetSpanId` returns **decimal**.
Expand Down Expand Up @@ -469,7 +498,7 @@ dotnet test tests/DatadogNet.iOS.PackageTests
Run the on-simulator smoke tests against the packed packages:

```bash
./.github/scripts/run-simulator-tests.sh 3.14.0.1 net9.0-ios18.0
./.github/scripts/run-simulator-tests.sh 3.14.0.3 net9.0-ios18.0
```

Build and run the sample:
Expand Down Expand Up @@ -498,9 +527,12 @@ dotnet build samples/DatadogNet.iOS.Example/DatadogNetExample.csproj -p:RuntimeI
2. Bump `DatadogNativeVersion` in [`Directory.Build.props`](Directory.Build.props) and reset
`DatadogBindingRevision` to `1`.
3. `./build/FetchXcFrameworks.sh`
4. `./build/GenerateBindings.sh` — writes to `Binding/`, **not** over the committed sources. Diff
and port real changes across; the script's header lists the fixes the committed files carry that
regenerating would otherwise undo.
4. `./build/DiffSwiftHeaders.sh <version>` — writes one `build/<Framework>.Swift.h.diff` per
framework whose generated header changed, which is the porting work list;
[`docs/regenerating-bindings.md`](docs/regenerating-bindings.md) explains how to read one.
(`./build/GenerateBindings.sh` is the automated alternative for when Objective Sharpie works
again — it writes to `Binding/`, **not** over the committed sources, and its header lists the
fixes the committed files carry that regenerating would otherwise undo.)
5. `./build/BuildNugets.sh` and run both test suites.
6. Update the `dd-sdk-ios` badge at the top of this file — both its label and its release link.
It is hardcoded, so nothing else will notice when it goes stale.
Expand All @@ -512,13 +544,14 @@ dropping a package — update the `FRAMEWORKS` list, add or remove the binding p
> **Note.** Objective Sharpie 3.5.116 cannot currently generate these bindings — its bundled clang
> fails on recent iOS SDK module maps. The committed sources were produced by parsing the shipped
> `-Swift.h` headers directly; `GenerateBindings.sh` documents the problem and honours a `SHARPIE`
> override for when it is fixed.
> override for when it is fixed. Until then the header diff **is** the upgrade path — see
> [`docs/regenerating-bindings.md`](docs/regenerating-bindings.md).

---

## Releasing

Tag it. `v3.14.0.1` builds, tests, publishes every package to nuget.org via trusted publishing, and
Tag it. `v3.14.0.3` builds, tests, publishes every package to nuget.org via trusted publishing, and
creates a GitHub release. The tag drives which native SDK is bound, so an older line can be released
by tagging it.

Expand All @@ -543,6 +576,13 @@ alongside Crash Reporting, and dSYMs uploaded for symbolication.
**`This version of .NET for iOS requires Xcode 26.0`.** Only affects `net10.0-ios26.0`. See
[Building locally](#building-locally).

**`ArgumentNullException` passing `null` attributes to `logger.Info(message, attributes)`** — or
any other level. Faithful to upstream: the Objective-C projection declares the dictionary (and the
`NSError`) `_Nonnull`, and the Swift implementation takes a non-optional `[String: Any]`, so a
binding that allowed `null` through would crash natively in the bridging thunk instead. Use the
message-only overload, or the convenience `logger.Log(level, message, exception?, attributes?)`,
which accepts `null` for both.

**A framework fails to load at runtime.** Usually a partially restored package. Clear the cached
copies and restore again: `rm -rf ~/.nuget/packages/datadognet.*`.

Expand Down
50 changes: 50 additions & 0 deletions build/CheckReadmeVersions.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
#!/bin/sh
# Fails when README.md pins a package version that is not the one this repository currently
# builds. The install snippets are copy-paste starting points, and a hardcoded version there goes
# stale silently on every release - 3.14.0.1 sat in the snippets while 3.14.0.2 shipped. Running
# this in CI makes the version bump before a release drag the README along with it.
#
# What is checked: every <PackageReference Include="DatadogNet..." Version="..."> pin, and the
# device-check example (run-simulator-tests.sh <version> ...). Prose that explains the version
# *scheme* ("3.14.0.1 is dd-sdk-ios 3.14.0, binding revision 1") is deliberately not checked -
# it describes the format, not the current release.
set -eu

root="$(cd "$(dirname "$0")/.." && pwd)"
readme="$root/README.md"
props="$root/Directory.Build.props"

prop() {
sed -n "s/.*<$1>\(.*\)<\/$1>.*/\1/p" "$props" | head -1
}

version="$(prop DatadogNativeVersion).$(prop DatadogBindingRevision)"

bad=0

old_ifs=$IFS
IFS='
'
for pin in $(grep -oE 'Include="DatadogNet[^"]*" +Version="[0-9][^"]*"' "$readme"); do
id=$(printf '%s' "$pin" | sed -E 's/Include="([^"]*)".*/\1/')
ver=$(printf '%s' "$pin" | sed -E 's/.*Version="([^"]*)"/\1/')
if [ "$ver" != "$version" ]; then
echo "README pins $id $ver, but the current version is $version" >&2
bad=1
fi
done

for token in $(grep -oE 'run-(simulator|emulator)-tests\.sh +[0-9][0-9.]*' "$readme" | grep -oE '[0-9][0-9.]*$'); do
if [ "$token" != "$version" ]; then
echo "README runs the device checks at $token, but the current version is $version" >&2
bad=1
fi
done
IFS=$old_ifs

if [ "$bad" -ne 0 ]; then
echo "CheckReadmeVersions: README.md is stale - update the versions above (current: $version)" >&2
exit 1
fi

echo "CheckReadmeVersions: README.md agrees with Directory.Build.props ($version)"
105 changes: 105 additions & 0 deletions build/DiffSwiftHeaders.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
#!/bin/sh
set -eu

# Diffs the generated Objective-C headers (-Swift.h) between the frameworks in libs/ and another
# dd-sdk-ios release - which is exactly the delta the committed ApiDefinitions.cs must absorb on
# an upgrade, now that Objective Sharpie cannot regenerate them (its bundled clang fails on
# recent iOS SDK module maps; see GenerateBindings.sh). The headers are the source of truth the
# committed bindings were produced from, so their diff IS the porting work list.
#
# Usage:
# ./DiffSwiftHeaders.sh 3.15.0 # download that release, diff against libs/
# ./DiffSwiftHeaders.sh DIR_OLD DIR_NEW # diff two directories of *.xcframework
#
# The download mode also prints the archive's SHA-256, which is the value step 1 of the upgrade
# guide records in build/checksums.txt - so the hash is captured from the same bytes the diff
# was read from.

INVOKE_DIR="$(pwd)"
cd "$(dirname "$0")"
ROOT="$(cd .. && pwd)"

absolute() {
case "$1" in
/*) printf '%s' "$1" ;;
*) printf '%s' "$INVOKE_DIR/$1" ;;
esac
}

header_of() {
# First device slice's -Swift.h for one framework name under one root.
find "$1" -path "*/$2.xcframework/ios-arm64*/$2.framework/Headers/$2-Swift.h" 2>/dev/null | sort | head -1
}

if [ "$#" -eq 2 ]; then
OLD_DIR="$(absolute "$1")"
NEW_DIR="$(absolute "$2")"
elif [ "$#" -eq 1 ]; then
version="$1"
case "$version" in
*[!A-Za-z0-9._-]*)
echo "error: invalid version '$version'" >&2
exit 1
;;
esac
OLD_DIR="$ROOT/libs"
WORK="$(mktemp -d)"
trap 'rm -rf "$WORK"' EXIT
zip="$WORK/Datadog.xcframework.zip"

echo "==> downloading dd-sdk-ios $version (no checksum pinned yet - that is what this run produces)"
curl -fSL -o "$zip" \
"https://github.com/DataDog/dd-sdk-ios/releases/download/$version/Datadog.xcframework.zip"

echo "==> SHA-256 for build/checksums.txt:"
shasum -a 256 "$zip" | sed "s|$WORK/||"

NEW_DIR="$WORK/extracted"
mkdir -p "$NEW_DIR"
unzip -q "$zip" -d "$NEW_DIR"
else
echo "usage: $0 <version> | <old-dir> <new-dir>" >&2
exit 1
fi

changed=0
unchanged=0
missing=0

for framework_dir in "$OLD_DIR"/*.xcframework; do
[ -d "$framework_dir" ] || continue
framework="$(basename "$framework_dir" .xcframework)"

old_header="$(header_of "$OLD_DIR" "$framework")"
new_header="$(header_of "$NEW_DIR" "$framework")"

if [ -z "$old_header" ]; then
# OpenTelemetryApi ships no -Swift.h at all; that is expected, not an upgrade signal.
echo "--- $framework: no -Swift.h in the current libs/ (expected for OpenTelemetryApi)"
continue
fi
if [ -z "$new_header" ]; then
echo "!!! $framework: present in libs/ but absent from the new release - a framework was"
echo " dropped or renamed upstream. FetchXcFrameworks.sh will fail loudly on this too."
missing=$((missing + 1))
continue
fi

if diff -u "$old_header" "$new_header" > "$framework.Swift.h.diff" 2>&1; then
rm -f "$framework.Swift.h.diff"
unchanged=$((unchanged + 1))
else
lines=$(wc -l < "$framework.Swift.h.diff" | tr -d ' ')
echo "==> $framework: CHANGED - $lines diff lines in build/$framework.Swift.h.diff"
changed=$((changed + 1))
fi
done

echo
summary="==> $changed changed, $unchanged unchanged"
[ "$missing" -gt 0 ] && summary="$summary, $missing MISSING"
echo "$summary"
if [ "$changed" -gt 0 ]; then
echo " Each .diff in build/ is the porting work list for that framework's ApiDefinitions.cs."
echo " See docs/regenerating-bindings.md for how to read one."
fi
50 changes: 50 additions & 0 deletions docs/regenerating-bindings.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# Regenerating the bindings

The committed `ApiDefinitions.cs`/`StructsAndEnums.cs` files were produced from the frameworks'
generated Objective-C headers (`<Framework>-Swift.h`) and then hand-corrected. This page is the
process for carrying them across a dd-sdk-ios upgrade — because the tool that would automate it
is currently broken, and pretending otherwise makes upgrades slower, not faster.

## Why Objective Sharpie is out

Objective Sharpie 3.5.116 — the newest release — bundles a clang that fails to parse recent iOS
SDK module maps, so `./build/GenerateBindings.sh` cannot produce a fresh `Binding/` tree against
any Xcode this repository builds with. The script's header records the exact failure and honours
a `SHARPIE` override for the day a fixed release appears; until then, step "regenerate and diff"
is really "diff the headers and port by hand".

## The header diff is the work list

```bash
./build/DiffSwiftHeaders.sh 3.15.0
```

downloads that release's `Datadog.xcframework.zip`, prints its SHA-256 (the value step 1 of the
upgrade guide records in `build/checksums.txt` — same bytes, one download), and writes one
`build/<Framework>.Swift.h.diff` per framework whose generated header changed. A framework with
no diff needs nothing: its committed binding is already exact.

Reading a diff:

| In the header diff | In the committed binding |
| --- | --- |
| New `@interface` / `@protocol` | New `[BaseType]` interface in `ApiDefinitions.cs`. |
| New method/property on an existing type | New `[Export]` member on the existing interface. |
| `SWIFT_ENUM` added or extended | New/extended enum in `StructsAndEnums.cs` (check backing type). |
| Removed or renamed member | Remove/rename the export — a stale export crashes at runtime when called, not at build time. |
| `_Nullable` appearing on a parameter | Add `[NullAllowed]`. The reverse — `_Nonnull` — means do **not** add it, however tempting; see the `DDLogger` comment for a case where a `[NullAllowed]` "fix" would have turned a managed exception into a native crash. |
| Type moving between frameworks | Move the interface between binding projects and fix the project references. |

The hand-applied corrections the committed files carry on top of raw generation — the fixes a
regeneration would undo — are listed in `build/GenerateBindings.sh`'s header. Check a ported diff
against that list before committing.

## After porting

1. `./build/FetchXcFrameworks.sh` (the hash recorded above makes this pass).
2. `./build/BuildNugets.sh` and both test suites — the on-simulator smoke tests are what catch a
selector that no longer exists at runtime.
3. Re-run `./build/DiffSwiftHeaders.sh libs <extracted-new>` if anything was ported from memory:
zero diffs against the new headers is the done condition.
4. Sync `DatadogNet.Mac`: its binding sources are enforced verbatim copies of these
(`DatadogNet.Mac/build/SyncBindingsFromiOS.sh`, guarded by its CI).
Loading
Loading