Manually lipoing and codesigning

As I've said before, our product uses cmake for building, and vcpkg for 3rd party management. vcpkg does not (yet) support universal builds on the Mac; neither does HomeBrew, and MacPorts kinda does but some of the ports actually think "universal" is x86, x86_64, ppc, and ppc64 and won't build because you can't build ppc anymore.

So I have had serious talks with our build and we have reached a compromise where I can now build for arm64 or for x86_64. The next step would be to manually combine the executables, and then re-sign (using our Developer ID). Has anyone got suggestions on how to do that? I can just grab the codesign commands from the build output and use those; is that feasible?

(At some point I may insist on having a week or so to try getting vcpkg to build universal, but I don't have that week or so now, so that's not going to happen. I could potentially ditch cmake for the Mac builds, and then I think CocoaPods has all of the 3rd party libraries we depend on, but I'm not positive, and that then introduces guaranteed breakage when the Windows and macOS versions uses different sets of files and versions.)

Answered by DTS Engineer in 718475022

can I just grep the codesign commands from the build output, and do those again after the enfattening?

I expect so. AFAIK the Xcode build system does not include architecture-specific details in its code signing commands.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

Code signing is per architecture. If you take two correctly signed Mach-O files and lipo them together, the result will be a correctly signed Mach-O file.

Having said that, it’s likely that your build system isn’t producing correctly signed Mach-O files — most open source tooling typically produces linker-signed, that is, ad hoc signed, Mach-O files — and so you’ll want to re-sign after the merging the architectures. This isn’t particularly challenging; you just have to pass -f to codesign to overwrite the existing signature.

If I were in your shoes I’d probably adjust your open source tooling to not do any signing — disable linker signing using -no_adhoc_codesign — then do a final signing step at the end.

For general advice on how to sign from the command line, see Creating Distribution-Signed Code for Mac.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

The Apple Silicon and Intel builds are currently signed correctly -- they run on Macs of the appropriate architecture with SIPS enabled. (The various libraries are statically linked, not dynamic.)

But when I try to use lipo to combine them, it worked on my machine -- until I realized that it had SIPS disabled; when I re-enabled it, I start getting error 8. (Remember how I have now begged Apple for a code sign diagnostics tool? 😄)

(Does it matter that it's a single bundle with some contained app / extension bundles, and/or that they have entitlements?)

Hm. I am also not sure if I notarized it afterwards.

In my previous answer I was assuming that you were creating standalone Mach-O files. If you’re creating bundles, things get more complex because the code signature seals over the entire contents of the bundle. If you want this to work, you have to make sure that the code resource hash is the same in both cases. See the Special slots and Resources sections of TN3126 Inside Code Signing: Hashes for more on that.

Given that complexity, I’m going to double down on my previous advice:

If I were in your shoes I’d probably adjust your open source tooling to not do any signing — disable linker signing using -no_adhoc_codesign — then do a final signing step at the end.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

If I were in your shoes I’d probably adjust your open source tooling to not do any signing — disable linker signing using -no_adhoc_codesign — then do a final signing step at the end.

The build system is xcodebuild driven by cmake; the only code signing is done at the end, and is done by xcodebuild (for each target of the project, which is a system extension, three app-structured daemons, and the containing app). The problem is that we currently have to do two completely-different builds, one for each architecture, due to the lack of support for universal builds by ... well, anything that isn't Xcode-driven, really. In particular, vcpkg, homebrew, and macports all fail at this in various ways. (And we can't use CocoaPods with cmake, and we can't use Swift Packages.)

Presumably you’re using xcodebuild for the last steps here, so the open source build steps happen before that. Given that, can you run those steps for each architecture first, and then merge those results before xcodebuild gets a look in?

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

The way the build works -- and this isn't just us, it's standard with vcpkg and cmake building on macos -- is that you tell vcpkg the packages you need, it deals with all of the dependencis, and builds them. You then have CMakeLists.txt files that specify your targets, and what packages they link with (and, in my case, I ensured they were static libraries).

You then generate a builder (xcodeproj in this case) using CMake; this causes vcpkg to build everything, and then CMake goes and constructs an xcodeproj bundle that has all of the targets you specified, and has all the libraries/header files you said they need as external dependencies used for linking. Then you build, and this basically runs xcodebuild. The end result is the targets signed the way I told it to sign, put into bundles where applicable.

Because vcpkg does not support (yet?) universal binaries, I have to do it twice -- once for arm64, once for x86_64. These are completely separate build environments. So I don't have xcodebuild doing "the final step" -- it's actually building everything except the vcpkg-managed dependencies. And I then have one complete bundle for each architecture.

So what I want to do is then take those two bundles, and somehow merge them together. I assume this has to be done manually (well, scriptedly) using lio, and then signed again using codesign. I had (as stated originally) thought I'd just grep for the codesign commands from the xcodebuild output, and use those to re-sign after my script uses lipo to put everything together. (I do apparently need to also grab the processed entitlements files.)

If there's a way to get Xcode to do this (and this would be a separate xcodeproj bundle, I'm sure), that'd be great. I couldn't find one, since the inputs would be two different bundles.

Does that make my question, problem, and dilemma clearer?

Given your constraints I see a clear choice here:

  • Disable code signing on your individual architecture builds and then sign once after you’ve manually merged.

  • Leave code signing enabled and then re-sign after the merge.

The only difference is performance. Code signing can take time and in the first option you’re doing that three times rather than just once.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

But that gets back to my original question -- can I just grep the codesign commands from the build output, and do those again after the enfattening? Or do I need something different after that?

Accepted Answer

can I just grep the codesign commands from the build output, and do those again after the enfattening?

I expect so. AFAIK the Xcode build system does not include architecture-specific details in its code signing commands.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

Ok, so it's been a while: I did get it to work! I grepped the codesign commands from the build output, wrote a script that, given two source .app directories, copies one of them to a new bundle, and then runs codesign on each of the bundles (including, at the end, the whole enclosing bundle). I also (cleverly, I think) extract the entitlements using codesign rather than using the ones from the project directory, and apply those. It seems to work!

This is, I think, about the best I can do until and unless uSoft or MacPorts makes progress on their respective tools/environments. This is one of the reasons I always like being an OS engineer, so we don't have to (in general) rely on third party libraries/tools.

Now I got a hankering to write a copyfile class in Swift.

Manually lipoing and codesigning
 
 
Q