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
6 changes: 6 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,12 @@ jobs:
- name: Validate packages
run: dotnet test tests/DatadogNet.iOS.PackageTests --logger 'trx;LogFileName=package-tests.trx'

# After the merge, on the finished artifact - the per-pass intermediates legitimately lack
# the other band's frameworks, so validating them against the published baseline reports
# every band-to-band difference as a break. See the script header.
- name: Validate package API against the published baseline
run: ./build/ValidatePackageApi.sh

- name: Upload packages
uses: actions/upload-artifact@v4
with:
Expand Down
30 changes: 30 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,36 @@ jobs:
echo "prerelease=${prerelease}" >> "$GITHUB_OUTPUT"
echo "Releasing ${version} (prerelease=${prerelease})" >> "$GITHUB_STEP_SUMMARY"

# The tag names the dd-sdk-ios line to bind; Directory.Build.props names the line this
# branch is developed and tested against. When they disagree, the release would publish
# packages binding a native SDK the committed ApiDefinitions.cs were never ported to - or
# simply a mistyped tag - so it stops here, before anything builds. Fix the tag (delete and
# re-push) or bump the props. Releasing a different line on purpose - v2.30.2.3 from a main
# that has moved on to 3.x - is still possible: set the repository variable
# RELEASE_ALLOW_VERSION_MISMATCH to 'true' for that release, and unset it afterwards.
- name: Check the tag against Directory.Build.props
if: vars.RELEASE_ALLOW_VERSION_MISMATCH != 'true'
run: |
version="${{ steps.resolve.outputs.version }}"
native="${{ steps.resolve.outputs.native-version }}"
props_native=$(sed -n 's:.*<DatadogNativeVersion>\(.*\)</DatadogNativeVersion>.*:\1:p' Directory.Build.props | head -1)
props_revision=$(sed -n 's:.*<DatadogBindingRevision>\(.*\)</DatadogBindingRevision>.*:\1:p' Directory.Build.props | head -1)

if [ "${native}" != "${props_native}" ]; then
echo "::error::tag ${GITHUB_REF_NAME} binds dd-sdk-ios ${native}, but Directory.Build.props has DatadogNativeVersion ${props_native}. Fix the tag or bump the props; for a deliberate different-line release, set the RELEASE_ALLOW_VERSION_MISMATCH repository variable to 'true'."
exit 1
fi

# The fourth component is this repository's binding revision. A tag may omit it, and a
# prerelease carries a suffix; both are stripped before comparing.
revision=$(printf '%s' "${version%%-*}" | cut -s -d. -f4)
if [ -n "${revision}" ] && [ "${revision}" != "${props_revision}" ]; then
echo "::error::tag ${GITHUB_REF_NAME} is binding revision ${revision}, but Directory.Build.props has DatadogBindingRevision ${props_revision}. Fix the tag or bump the props; for a deliberate mismatch, set the RELEASE_ALLOW_VERSION_MISMATCH repository variable to 'true'."
exit 1
fi

echo "Tag agrees with Directory.Build.props (${props_native}, revision ${props_revision})" >> "$GITHUB_STEP_SUMMARY"

build:
name: build
needs: version
Expand Down
66 changes: 66 additions & 0 deletions .github/workflows/upstream-watch.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
name: upstream watch

# Opens an issue when Datadog publishes a dd-sdk-ios release newer than the one these bindings
# are built against, so an upgrade starts as a tracked task rather than a discovery. Weekly is
# plenty for an SDK that releases every few weeks; workflow_dispatch is for checking on demand.

on:
schedule:
- cron: '30 6 * * 1'
workflow_dispatch:

permissions:
contents: read
issues: write

jobs:
check:
name: compare against the latest dd-sdk-ios release
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Open an issue if upstream is ahead
env:
GH_TOKEN: ${{ github.token }}
run: |
current=$(sed -n 's:.*<DatadogNativeVersion>\(.*\)</DatadogNativeVersion>.*:\1:p' Directory.Build.props | head -1)
if [ -z "${current}" ]; then
echo "::error::could not read DatadogNativeVersion from Directory.Build.props"
exit 1
fi

# Upstream tags releases with the bare version - no v prefix.
latest=$(gh api repos/DataDog/dd-sdk-ios/releases/latest --jq .tag_name)
echo "bound: ${current}, upstream latest: ${latest}" >> "$GITHUB_STEP_SUMMARY"

if [ "${latest}" = "${current}" ]; then
echo "Up to date." >> "$GITHUB_STEP_SUMMARY"
exit 0
fi

# sort -V puts the newer of the two last. This also covers upstream being *behind* -
# a yanked or re-cut latest release - which is not an upgrade prompt.
if [ "$(printf '%s\n%s\n' "${current}" "${latest}" | sort -V | tail -1)" != "${latest}" ]; then
echo "Upstream ${latest} is not newer than ${current}; nothing to do." >> "$GITHUB_STEP_SUMMARY"
exit 0
fi

# One open issue per upstream version, however many Mondays it stays open. Matched on
# the exact title rather than a search expression, which matches loosely.
title="dd-sdk-ios ${latest} is available"
if gh issue list --state open --limit 100 --json title --jq '.[].title' | grep -Fxq "${title}"; then
echo "An open issue for ${latest} already exists; nothing to do." >> "$GITHUB_STEP_SUMMARY"
exit 0
fi

cat > "${RUNNER_TEMP}/issue-body.md" <<BODY
Datadog published [dd-sdk-ios ${latest}](https://github.com/DataDog/dd-sdk-ios/releases/tag/${latest}); these bindings are built against \`${current}\`.

The ritual is the [Upgrading the Datadog SDK](https://github.com/sbokatuk/DatadogNet.iOS/blob/main/README.md#upgrading-the-datadog-sdk) section of the README. \`./build/BumpNativeVersion.sh ${latest}\` does the mechanical part, including re-pinning \`build/checksums.txt\` from the release digest via \`build/UpdateChecksums.sh\`; the porting work list then comes from \`./build/DiffSwiftHeaders.sh ${latest}\`.

Opened automatically by the upstream-watch workflow.
BODY

gh issue create --title "${title}" --body-file "${RUNNER_TEMP}/issue-body.md"
echo "Opened an issue for ${latest}." >> "$GITHUB_STEP_SUMMARY"
9 changes: 8 additions & 1 deletion Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,16 @@
impossible to tell which Datadog release a given CrashReporter package belonged to.
-->
<DatadogNativeVersion>3.14.0</DatadogNativeVersion>
<DatadogBindingRevision>3</DatadogBindingRevision>
<DatadogBindingRevision>4</DatadogBindingRevision>
<VersionPrefix>$(DatadogNativeVersion).$(DatadogBindingRevision)</VersionPrefix>

<!--
The last version published to nuget.org, which build/ValidatePackageApi.sh holds every
packed package's API to after the merge. Advances to the just-shipped version while
preparing the next release (BumpNativeVersion.sh rewrites it).
-->
<DatadogPackageValidationBaselineVersion>3.14.0.3</DatadogPackageValidationBaselineVersion>

<Authors>s.bokatuk</Authors>
<Company>s.bokatuk</Company>
<Copyright>Copyright © s.bokatuk</Copyright>
Expand Down
79 changes: 48 additions & 31 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,8 +103,8 @@ OS-provided Swift runtime, ABI-stable from 12.2.

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

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

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

Expand All @@ -127,9 +127,11 @@ restore them:
## Usage

The C# names are the Objective-C selectors projected into C#. Each feature is in its own namespace
now — one `using` per module. [`samples/DatadogNet.iOS.Example`](samples/DatadogNet.iOS.Example) is
a working MAUI app that does all of the below;
[`Datadog.cs`](samples/DatadogNet.iOS.Example/Datadog.cs) is the setup in one file.
now — one `using` per module.
[`samples/DatadogNet.iOS.Example`](https://github.com/sbokatuk/DatadogNet.iOS/tree/main/samples/DatadogNet.iOS.Example)
is a working MAUI app that does all of the below;
[`Datadog.cs`](https://github.com/sbokatuk/DatadogNet.iOS/blob/main/samples/DatadogNet.iOS.Example/Datadog.cs)
is the setup in one file.

### Initialize

Expand Down Expand Up @@ -394,7 +396,8 @@ all, so there is nothing for a binding to bind:
callable API. They exist so the SDK is mirrored and so a future projection needs no new package.

There is a way around this — a hand-written Swift `@objc` wrapper, which we can compile ourselves —
and a working prototype for Flags lives in [`shims/DatadogFlagsObjc/`](shims/DatadogFlagsObjc/).
and a working prototype for Flags lives in
[`shims/DatadogFlagsObjc/`](https://github.com/sbokatuk/DatadogNet.iOS/tree/main/shims/DatadogFlagsObjc).
Nothing ships yet.

---
Expand Down Expand Up @@ -453,13 +456,14 @@ DataDog/dd-sdk-ios release ──► Datadog.xcframework.zip (one archive, el

There is no Carthage step and nothing is compiled from source. Datadog publishes the built
xcframeworks as a release asset, so the build downloads one archive, verifies it against a hash
pinned in [`build/checksums.txt`](build/checksums.txt), and strips it to the two iOS slices.
pinned in [`build/checksums.txt`](https://github.com/sbokatuk/DatadogNet.iOS/blob/main/build/checksums.txt),
and strips it to the two iOS slices.

**Why the two-pass build.** Each .NET SDK's iOS workload supports the current target framework and
the previous one — the .NET 9 band builds net8 + net9, the .NET 10 band builds net9 + net10. No
single SDK builds all three, so `BuildNugets.sh` packs twice and
[`merge-packages.py`](build/merge-packages.py) grafts the net10 assets and dependency groups into
the net9 packages.
[`merge-packages.py`](https://github.com/sbokatuk/DatadogNet.iOS/blob/main/build/merge-packages.py)
grafts the net10 assets and dependency groups into the net9 packages.

### Layout

Expand Down Expand Up @@ -498,7 +502,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.3 net9.0-ios18.0
./.github/scripts/run-simulator-tests.sh 3.14.0.4 net9.0-ios18.0
```

Build and run the sample:
Expand All @@ -510,32 +514,38 @@ dotnet build samples/DatadogNet.iOS.Example/DatadogNetExample.csproj -p:RuntimeI
> Building anything that targets `net10.0-ios26.0` needs Xcode **26.0** specifically — .NET for iOS
> refuses any other version. If your default Xcode is newer, prefix the command:
> `DEVELOPER_DIR=/Applications/Xcode_26.0.1.app/Contents/Developer ...`. CI handles this with the
> [`select-xcode`](.github/actions/select-xcode/action.yml) action.
> [`select-xcode`](https://github.com/sbokatuk/DatadogNet.iOS/blob/main/.github/actions/select-xcode/action.yml) action.

---

## Upgrading the Datadog SDK

1. Record the new archive's hash in [`build/checksums.txt`](build/checksums.txt):

```bash
v=3.15.0
curl -fsSL -O "https://github.com/DataDog/dd-sdk-ios/releases/download/$v/Datadog.xcframework.zip"
shasum -a 256 Datadog.xcframework.zip
```

2. Bump `DatadogNativeVersion` in [`Directory.Build.props`](Directory.Build.props) and reset
`DatadogBindingRevision` to `1`.
3. `./build/FetchXcFrameworks.sh`
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.
1. `./build/BumpNativeVersion.sh 3.15.0` does the mechanical part: bumps `DatadogNativeVersion`
in
[`Directory.Build.props`](https://github.com/sbokatuk/DatadogNet.iOS/blob/main/Directory.Build.props)
and resets `DatadogBindingRevision` to `1`; pins the new archive's hash in
[`build/checksums.txt`](https://github.com/sbokatuk/DatadogNet.iOS/blob/main/build/checksums.txt)
from the digest GitHub publishes for the release asset, verified against a fresh download of
the same bytes (`build/UpdateChecksums.sh`); points `PackageValidationBaselineVersion` at the
version being left behind; rewrites this README's badge, prose and install snippets; and
scaffolds `docs/release-notes/3.15.0.1.md`.
2. `./build/DiffSwiftHeaders.sh 3.15.0` — before fetching, while `libs/` still holds the previous
release — writes one `build/<Framework>.Swift.h.diff` per framework whose generated header
changed, which is the porting work list;
[`docs/regenerating-bindings.md`](https://github.com/sbokatuk/DatadogNet.iOS/blob/main/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.)
3. `./build/FetchXcFrameworks.sh` — replaces `libs/` with the new release's frameworks.
4. Port the diffs into the committed `ApiDefinitions.cs` files.
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.
6. Finish the scaffolded release notes — its TODOs mark what cannot be generated — then tag (see
[Releasing](#releasing)).

`build/CheckReadmeVersions.sh` verifies the badge, the "Built against" prose and the install
snippets against `Directory.Build.props` — the bump script runs it last, and CI runs it on every
build, so a stale spot fails loudly rather than shipping.

If Datadog adds or removes a framework, `FetchXcFrameworks.sh` fails loudly rather than silently
dropping a package — update the `FRAMEWORKS` list, add or remove the binding project, and update
Expand All @@ -545,7 +555,7 @@ dropping a package — update the `FRAMEWORKS` list, add or remove the binding p
> 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. Until then the header diff **is** the upgrade path — see
> [`docs/regenerating-bindings.md`](docs/regenerating-bindings.md).
> [`docs/regenerating-bindings.md`](https://github.com/sbokatuk/DatadogNet.iOS/blob/main/docs/regenerating-bindings.md).

---

Expand All @@ -555,6 +565,12 @@ Tag it. `v3.14.0.3` builds, tests, publishes every package to nuget.org via trus
creates a GitHub release. The tag drives which native SDK is bound, so an older line can be released
by tagging it.

The tag must agree with `Directory.Build.props`: pushing `v3.15.0.1` while `DatadogNativeVersion`
still says `3.14.0` — or a fourth component that is not `DatadogBindingRevision` — fails the
workflow before anything builds, since it would publish packages whose version does not describe
their contents. For a deliberate different-line release, set the `RELEASE_ALLOW_VERSION_MISMATCH`
repository variable to `true` for that release, and unset it afterwards.

Pull requests publish a `-beta.<pr>.<run>` prerelease of the whole set.

Curated notes in `docs/release-notes/<version>.md` replace the generated commit list when present.
Expand Down Expand Up @@ -590,7 +606,8 @@ copies and restore again: `rm -rf ~/.nuget/packages/datadognet.*`.

## Licence

The binding code in this repository is [MIT](LICENSE). The native binaries the packages ship are
The binding code in this repository is
[MIT](https://github.com/sbokatuk/DatadogNet.iOS/blob/main/LICENSE). The native binaries the packages ship are
built and published by Datadog and are Apache-2.0 — which covers `dd-sdk-ios`, KSCrash and
opentelemetry-swift alike. Every package declares `MIT AND Apache-2.0` and carries both texts under
`licenses/`.
Loading
Loading