Skip to content

apple-sdk_15: init at 15.0#347862

Merged
reckenrode merged 3 commits intoNixOS:stagingfrom
emilazy:push-ptutmzmvvskx
Oct 11, 2024
Merged

apple-sdk_15: init at 15.0#347862
reckenrode merged 3 commits intoNixOS:stagingfrom
emilazy:push-ptutmzmvvskx

Conversation

@emilazy
Copy link
Member

@emilazy emilazy commented Oct 11, 2024

In Nixpkgs, we’ve become used to the idea that adding a new macOS SDK version is a major undertaking that can take weeks or months. Indeed, before July we hadn’t added any in years (though shout‐outs to @ConnorBaker for #229210). As #346043 was being worked on, I set myself a challenge in coordination with @reckenrode: after it was merged, I would try to add the new macOS 15 SDK to Nixpkgs by myself, with no assistance from Randy, and see how long it took.

The answer is 70 minutes to have it working, tested, and used in the tree, and most of that was avoidable. That’s huge. I had an unfair advantage, of course, having been following the process from the beginning and having read the PR in full, but I had never tried to run one of the scripts, there is not yet any documentation to help me when it goes wrong, and I’m also pretty tired after a long day getting the rework PR tested and merged. The bulk of my time was spent waiting on unnecessary downloads that I had to restart, fixing my own silly mistakes, and getting confused by minor bugs and infelicities in the update scripts; I think that when it comes time to add the macOS 16 SDK in a year’s time, the same process should take no more than 10 minutes. Read on if you’d like a play‐by‐play and my takeaways.

Experience report

Before I started, I knew I’d need a program that used a macOS 15 API to test with, so I assembled the following basic package that computes √2 as a half‐precision float, using the new __sqrtf16 API:

diff --git a/pkgs/by-name/ap/apple-sdk_15-test/package.nix b/pkgs/by-name/ap/apple-sdk_15-test/package.nix
new file mode 100644
index 0000000000..331646920c
--- /dev/null
+++ b/pkgs/by-name/ap/apple-sdk_15-test/package.nix
@@ -1,0 +1,19 @@
+{
+  lib,
+  stdenv,
+  meson,
+  ninja,
+}:
+
+stdenv.mkDerivation {
+  name = "apple-sdk_15-test";
+
+  src = ./src;
+
+  nativeBuildInputs = [
+    meson
+    ninja
+  ];
+
+  meta.mainProgram = "apple-sdk_15-test";
+}
diff --git a/pkgs/by-name/ap/apple-sdk_15-test/src/apple-sdk_15-test.m b/pkgs/by-name/ap/apple-sdk_15-test/src/apple-sdk_15-test.m
new file mode 100644
index 0000000000..c2c0d93601
--- /dev/null
+++ b/pkgs/by-name/ap/apple-sdk_15-test/src/apple-sdk_15-test.m
@@ -1,0 +1,10 @@
+#include <math.h>
+#include <stdio.h>
+
+int main() {
+	if (@available(macOS 15, *)) {
+		printf("%f\n", (double) __sqrtf16(2));
+	} else {
+		printf(":(\n");
+	}
+}
diff --git a/pkgs/by-name/ap/apple-sdk_15-test/src/meson.build b/pkgs/by-name/ap/apple-sdk_15-test/src/meson.build
new file mode 100644
index 0000000000..c2d56fd595
--- /dev/null
+++ b/pkgs/by-name/ap/apple-sdk_15-test/src/meson.build
@@ -1,0 +1,2 @@
+project('apple-sdk_15-test', 'objc')
+executable('apple-sdk_15-test', 'apple-sdk_15-test.m', install : true)

I confirmed that it build and ran as expected with the Xcode toolchain. As expected, the Nix package failed to build with the default SDK version (currently 10.12 on x86_64-darwin):

[1/2] Compiling Objective-C object apple-sdk_15-test.p/apple-sdk_15-test.m.o
FAILED: apple-sdk_15-test.p/apple-sdk_15-test.m.o 
clang -Iapple-sdk_15-test.p -I. -I.. -fdiagnostics-color=always -Wall -Winvalid-pch -MD -MQ apple-sdk_15-test.p/apple-sdk_15-test.m.o -MF apple-sdk_15-test.p/apple-sdk_15-test.m.o.d -o apple-sdk_15-test.p/apple-sdk_15-test.m.o -c ../apple-sdk_15-test.m
../apple-sdk_15-test.m:6:40: error: call to undeclared function '__sqrtf16'; ISO C99 and later do not support implicit function declarations [-Wimplicit-function-declaration]
               printf("%f\n", (double) __sqrtf16(2));
                                       ^
1 error generated.
ninja: build stopped: subcommand failed.

So I had my shiny new software that wants fresh SDK features; my goal was to add the new SDK and get it building in as little time as possible. I started the clock and went looking in pkgs/by-name/ap/apple-sdk for the appropriate scripts. There’s a scripts directory with get-sdks-from-catalog.sh, lock-sdk-deps.sh, and regenerate-lockfile.sh, and after a glance at all three, it seemed like get-sdks-from-catalog.sh was a good starting point.

It wanted to be fed an Apple software update catalogue file; I looked again at the other two scripts since I figured there would be something to automatically download that for me, but nope. I remembered that swscan.apple.com is famously hard to find things in and that Randy had mentioned a Gist where he found the URLs for stuff in the past. A quick search of the Matrix room logs brought up https://gist.github.com/meyer/b14c87d162366f0428a99cd2ff0d0b8b, with a comment helpfully pointing to the correct link for macOS 15. I downloaded the catalogue and gave it to the script.

It gave me the following output:

Package URL: https://swcdn.apple.com/content/downloads/02/62/071-54303-A_EU2CL1YVT7/943i95dpeyi2ghlnj2mgyq3t202t5gf18b/CLTools_macOSNMOS_SDK.pkg
  Xcode Ver: 12.5.1 (12.5.1.0.1.1623191612)

Package URL: https://swcdn.apple.com/content/downloads/24/42/002-83793-A_74JRE8GVAT/rlnkct919wgc5c0pjq986z5bb9h62uvni2/CLTools_macOSNMOS_SDK.pkg
  Xcode Ver: 13.4.0 (13.4.0.0.1.1651278267)

Package URL: https://swcdn.apple.com/content/downloads/15/62/032-84673-A_7A1TG1RF8Z/xpc8q44ggn2pkn82iwr0fi1zeb9cxi8ath/CLTools_macOSNMOS_SDK.pkg
  Xcode Ver: 14.3.1 (14.3.1.0.1.1683849156)

Package URL: https://swcdn.apple.com/content/downloads/14/48/052-59890-A_I0F5YGAY0Y/p9n40hio7892gou31o1v031ng6fnm9sb3c/CLTools_macOSNMOS_SDK.pkg
  Xcode Ver: 15.3.0 (15.3.0.0.1.1708646388)

Package URL: https://swcdn.apple.com/content/downloads/33/46/042-32691-A_3MH7S3118O/3dblccqo9ws17dc5lk3hojfbt3s74q0ql6/CLTools_macOSNMOS_SDK.pkg
  Xcode Ver: 16.0.0 (16.0.0.0.1.1724870825)

Okay, clearly the 15.3 SDK is at https://swcdn.apple.com/content/downloads/14/48/052-59890-A_I0F5YGAY0Y/p9n40hio7892gou31o1v031ng6fnm9sb3c/CLTools_macOSNMOS_SDK.pkg, that looks great. (A twinge of doubt in my head: didn’t macOS 15.0 just come out? Are we really on the 15.3 SDK already? But I could worry about that later; the clock is ticking.)

I remembered that the SDK versions were all stored in a JSON file. I looked around briefly for tooling to automatically add the new one, but couldn’t find any. No matter – a quick nix store prefetch-file and I had the new 15.3 SDK version manually added to metadata/versions.json. I then added the apple-sdk_15 line to all-packages.nix. At this point it was about 8 minutes since I started.

Okay, job done, right? Well, no; there’s those other two scripts. Looking at them revealed that they were there to handle pinning the corresponding source releases. regenerate-lockfile.sh is a wrapper around lock-sdk-deps.sh that automates running it over all the SDK versions and a bunch of packages, so that seemed like the thing to run. It took about ten minutes redownloading the source releases for all the previous SDK versions.

At that point the script failed because apparently the macos-153 tag in https://github.com/apple-oss-distributions/distribution-macOS doesn’t exist, which indeed it doesn’t. Uh oh – I know that Apple are often slow to release corresponding source releases. There’s a macos-150 tag, though, so maybe we can just use that? It seemed like the only way to override it was to adjust the version in metadata/versions.json and run the script again; another ten minutes down the drain. About half‐way through, I looked at the metadata/apple-oss-lockfile.json file it was writing to, and saw that it had removed the outer layer of SDK versions from the JSON structure. I remembered being an annoying pedant about Randy’s jq use in these scripts – did it break in the process of responding to that review feedback? Yep, it did. I fixed the script to add the SDK version back to the JSON structure and ran it again. Success! A quick switch of the SDK version back to 15.3 and the packaging should be done.

Now for the moment of truth: I added the new SDK to the build inputs and hoped for the best.

diff --git a/pkgs/by-name/ap/apple-sdk_15-test/package.nix b/pkgs/by-name/ap/apple-sdk_15-test/package.nix
index 331646920c..6fd99ee30c 100644
--- a/pkgs/by-name/ap/apple-sdk_15-test/package.nix
+++ b/pkgs/by-name/ap/apple-sdk_15-test/package.nix
@@ -3,6 +3,7 @@
   stdenv,
   meson,
   ninja,
+  apple-sdk_15,
 }:
 
 stdenv.mkDerivation {
@@ -15,5 +16,9 @@
     ninja
   ];
 
+  buildInputs = [
+    apple-sdk_15
+  ];
+
   meta.mainProgram = "apple-sdk_15-test";
 }

…and it failed with an arcane message about cp: missing destination file operand after '/nix/store/65ifh4l4h4vpiv85nsv6w77js32rhrks-macOS-SDK-15.3'. Um, a messed up glob, maybe? Consulting the derivation told me that it was extracting the package and looking for the appropriate versioned SDK inside it. I downloaded and extracted it manually to look at and… uh, it’s the 14.3 SDK?

Did I misread the output of that catalogue‐reading script? Maybe that’s why it’s 15.3 instead of 15.0? The answer is yes, I misread the output, but no, that doesn’t explain the version thing. I thought the output was grouped like this:

Package URL: https://swcdn.apple.com/content/downloads/14/48/052-59890-A_I0F5YGAY0Y/p9n40hio7892gou31o1v031ng6fnm9sb3c/CLTools_macOSNMOS_SDK.pkg
  Xcode Ver: 15.3.0 (15.3.0.0.1.1708646388)

but it’s actually like this:

  Xcode Ver: 15.3.0 (15.3.0.0.1.1708646388)

Package URL: https://swcdn.apple.com/content/downloads/33/46/042-32691-A_3MH7S3118O/3dblccqo9ws17dc5lk3hojfbt3s74q0ql6/CLTools_macOSNMOS_SDK.pkg

I replaced the URL with the correct one and, after a slight mishap with the hash (because nix store prefetch-file doesn’t name its store path source like the Nixpkgs fetchers do), started building the SDK again. It failed with the same error. I downloaded and extracted the new .pkg, and… the SDK version inside is actually 15.0? I still don’t really understand where the 15.3 is coming from. I know Xcode and SDK versions aren’t the same, but it doesn’t look like Xcode 15.3 shipped with the macOS 15 SDK. So, I don’t know; I’ll need to wait for Randy to explain that one to me. But I put the version back to 15.0, and the SDK started actually building this time. Yay! I was getting impatient at this point because of this taking longer than I expected, and it made me think dangerous thoughts about how I should parallelize the .tbd conversion process. But, just over an hour in, it successfully built my test package, and I ran it on the community builder I was using to do the builds (because it already had the stdenv for x86_64-darwin cached from my earlier testing of the rework PR):

emily@darwin01 ~/nixpkgs (git)-[a7dc7ea2533f] % ./result/bin/apple-sdk_15-test 
:(

I’ve never been so happy to see a frowning face. This is the correct result: the community builder is running 14.7, and the SDK to build with is decoupled from the minimum deployment target we require at runtime (currently 10.12 for x86_64-darwin and 11.0 for aarch64-darwin), so this is the runtime check I wrote being handled correctly. I downloaded the binary locally, and successfully computed the world’s most accurate approximation of the square root of two:

emily@yuyuko ~/Downloads> ./apple-sdk_15-test
1.414062

Now I wanted to test the hook to set the deployment target, which is required for software that doesn’t do dynamic checks or unconditionally requires newer APIs. Thankfully, that’s also really easy with the new scheme:

diff --git a/pkgs/by-name/ap/apple-sdk_15-test/package.nix b/pkgs/by-name/ap/apple-sdk_15-test/package.nix
index 6fd99ee30c..07f0de5762 100644
--- a/pkgs/by-name/ap/apple-sdk_15-test/package.nix
+++ b/pkgs/by-name/ap/apple-sdk_15-test/package.nix
@@ -4,6 +4,7 @@
   meson,
   ninja,
   apple-sdk_15,
+  darwinMinVersionHook,
 }:
 
 stdenv.mkDerivation {
@@ -18,6 +19,7 @@
 
   buildInputs = [
     apple-sdk_15
+    (darwinMinVersionHook "15.0")
   ];
 
   meta.mainProgram = "apple-sdk_15-test";

The resulting binary continued to work on my local 15.0 machine, but correctly failed to load in spectacular fashion when run on the too‐old community builder:

emily@darwin01 ~/nixpkgs (git)-[374daa9e3926] % ./result/bin/apple-sdk_15-test 
dyld[81506]: Symbol not found: ___sqrtf16
  Referenced from: <D4B67B64-E1CF-3DC5-B8A9-AD232A2D5FD5> /nix/store/p74qqhqmdxda7nnd1f2krssh105pr2vy-apple-sdk_15-test/bin/apple-sdk_15-test (built for macOS 15.0 which is newer than running OS)
  Expected in:     <0240FF42-6851-34BD-B251-3C4704DE26AA> /usr/lib/libSystem.B.dylib
[2]    81506 abort      ./result/bin/apple-sdk_15-test

At that point, the job was done, but I remembered from reviewing the PR that there was one package of an Apple source release that was currently fetching a private header version from GitHub because of not yet having the macOS 15 SDK packaged. A quick ripgrep and I edited pkgs/os-specific/darwin/apple-source-releases/libpcap/package.nix to comment out the apple-sdk_15.sourceRelease line that was waiting for me. I built the resulting package, and around 70 minutes from when I started, the job was done.

Well, except for reorganizing the commits and writing this extremely long pull request message, of course.

Conclusion

70 minutes is incredible! Thanks to Randy, we should never again be in a position where we need a newer SDK but don’t have it. The fact that the diff here is so tiny (seriously – go look at it!) and most of the 70 minutes were spent waiting for downloads and dealing with the scripts not yet being fully polished is a massive vindication of the general approach. So I don’t at all want to seem critical here, especially given that we had to rush all of this right before the release, but I think it’s helpful to note down areas for future improvement here.

  • The URLs to the software catalogues are very predictable; our tooling should automatically compute and download them, rather than relying on people to look up Gists.
  • Similarly, downloading the new SDK and adding it to the versions file should be automated, or at least the output from the catalogue script should be formatted in a less confusing way.
  • I don’t know what went on with the weird 15.3 version thing, but if there’s any way we can avoid the confusion I went through and report the correct SDK version automatically that would be great. The SDK build error wasn’t really that helpful, either.
  • The script to pin the source releases redownloads everything every time; that’s slow and seems unnecessary, if we instead record enough information for the script to know what hasn’t changed.
  • This is really minor, but it’d be nice if the .tbd conversion process was parallelized so that the SDKs built faster. (It still only takes a few minutes, though.)

Hopefully I can tackle some of these soon, and possibly just rewrite the scripts in something other than Bash. But when you consider how much toil and suffering past attempts to package new macOS SDKs have resulted in, it’s incredible how minor these nitpicks are. The process is almost entirely automated now, and as soon as I got the correct SDK URL downloading with the correct version recorded, the SDK package built and worked the very first time. The future looks bright.

I only tested this on x86_64-darwin since I didn’t want to build another stdenv. I’m counting on you to test aarch64-darwin and tell me what I messed up, Randy :)

Things done

  • Built on platform(s)
    • x86_64-linux
    • aarch64-linux
    • x86_64-darwin
    • aarch64-darwin
  • For non-Linux: Is sandboxing enabled in nix.conf? (See Nix manual)
    • sandbox = relaxed
    • sandbox = true
  • Tested, as applicable:
  • Tested compilation of all packages that depend on this change using nix-shell -p nixpkgs-review --run "nixpkgs-review rev HEAD". Note: all changes have to be committed, also see nixpkgs-review usage
  • Tested basic functionality of all binary files (usually in ./result/bin/)
  • 24.11 Release Notes (or backporting 23.11 and 24.05 Release notes)
    • (Package updates) Added a release notes entry if the change is major or breaking
    • (Module updates) Added a release notes entry if the change is significant
    • (Module addition) Added a release notes entry if adding a new NixOS module
  • Fits CONTRIBUTING.md.

Add a 👍 reaction to pull requests you find important.

This was omitting the SDK version from the structure, probably due
to refactors in response to my own reviews on the SDK rework PR. Oops!
@emilazy emilazy added the 6.topic: darwin Running or building packages on Darwin label Oct 11, 2024
@emilazy emilazy requested a review from reckenrode October 11, 2024 07:32
@emilazy
Copy link
Member Author

emilazy commented Oct 11, 2024

Oh, I just now figured out the script output. It’s a combination of both: it actually is grouped like I originally thought, but it’s listed by Xcode version, and Xcode 16.0 is the first version that shipped the macOS 15.0 SDK. Okay, phew, mystery solved. But hopefully we can still get a automated pipeline that can download the right catalogue and map the Xcode version to the appropriate SDK version.

Copy link
Contributor

@toonn toonn left a comment

Choose a reason for hiding this comment

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

LGTM

Quite an amazing improvement required-effort wise.

Maybe you can slap most of the contents of your OP in that empty README so there's at least minimal documentation going forward? (Even just a link to this PR would be enough for now.)

@ofborg ofborg bot added 8.has: package (new) This PR adds a new package 10.rebuild-darwin: 1-10 This PR causes between 1 and 10 packages to rebuild on Darwin. 10.rebuild-darwin: 1 This PR causes 1 package to rebuild on Darwin. 10.rebuild-linux: 0 This PR does not cause any packages to rebuild on Linux. labels Oct 11, 2024
@reckenrode
Copy link
Contributor

reckenrode commented Oct 11, 2024

I’ll work on the documentation this weekend. @emilazy is correct that the script is listing Xcode version. Xcode version and SDK version are loosely coupled. Usually, the Xcode major is one higher than the SDK version, but the minor version is only loosely related. I plan to include a link to https://developer.apple.com/documentation/xcode-release-notes in the stdenv documentation and the SDK readme to help people interpret Xcode versions. The stdenv may also incorporate a table, but I don’t know yet, since it hasn’t been written (and it would need to be maintained).

Regarding the .tbd conversion, if we can assume that umbrella frameworks always embed their dependents after 11.0 is made the minimum, it should be possible to refactor the SDK to have an inner SDK that is symlinked into the final SDK. It won’t get rid of the conversion, but it will only happen once per SDK. It would be nice if find supported doing -exec in parallel (or at least not waiting for the command to finish), but I don’t know of any good alternatives. It may be possible to use find -print0 and pipe it into xargs, but the conversion does a couple of -execs per file for logging purposes.

packageHash=$(nix --extra-experimental-features nix-command hash path "$package-$packageTag")

pkgsjson="{\"$package\": {\"version\": \"$packageVersion\", \"hash\": \"$packageHash\"}}"
pkgsjson="{\"$sdkVersion\": {\"$package\": {\"version\": \"$packageVersion\", \"hash\": \"$packageHash\"}}}"
Copy link
Contributor

Choose a reason for hiding this comment

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

Probably a copy-paste victim of removing cat <<-EOF.

@reckenrode
Copy link
Contributor

The documentation will also cover regenerate-lockfile.sh and lock-sdk-deps.sh. The latter is all that’s really needed for adding an SDK. The former is useful for regenerating apple-oss-lockfile.json from scratch.

Copy link
Contributor

@reckenrode reckenrode left a comment

Choose a reason for hiding this comment

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

I tested adding the SDK in my branch while working on #346043, so this LGTM.

@reckenrode reckenrode merged commit cff8998 into NixOS:staging Oct 11, 2024
@nixos-discourse
Copy link

This pull request has been mentioned on NixOS Discourse. There might be relevant details there:

https://discourse.nixos.org/t/on-the-future-of-darwin-sdks-or-how-you-can-stop-worrying-and-put-the-sdk-in-build-inputs/50574/16

@emilazy emilazy deleted the push-ptutmzmvvskx branch October 11, 2024 15:51
@Samasaur1 Samasaur1 mentioned this pull request Oct 23, 2025
13 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

6.topic: darwin Running or building packages on Darwin 8.has: package (new) This PR adds a new package 10.rebuild-darwin: 1-10 This PR causes between 1 and 10 packages to rebuild on Darwin. 10.rebuild-darwin: 1 This PR causes 1 package to rebuild on Darwin. 10.rebuild-linux: 0 This PR does not cause any packages to rebuild on Linux.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants