Creating a "static" linked swift-driver

I'm trying to build the compiler and driver as “statically linked” on macOS… whereby I really mean not statically linked but only dependant on standard platform binaries. The reason being so I can deploy a signed binary in my IDE that can be code signed suitable for the App Store inside out code signing etc. and not have to do annoying rpath manipulations and then re-sign after every build of the binary, plus deploy a load of dylibs along with the binaries.

I’ve managed to get it working for swift-frontend by passing --extra-cmake-options="-DBUILD_SHARED_LIBS=NO”which makes swift-frontend that depends only on platform dylibs, but the flag doesn’t seem to propagate to swift-driver.

I haven’t understood the full chain of how utils/build-script invokes the earlyswiftdriver build (and the later swift-driver build if there is one?) but it looks like indirection through various python things means the above flag doesn’t propagate (still debugging as part of me learning how the build script works better and in particular how swift-driver fits in the ecosystem).

Three questions I’d like to ask if anyone can give some advice please…

  1. Is there an easy way I can pass a similar flag to utils/build-script, with perhaps just patches in my fork of GitHub - swiftlang/swift: The Swift Programming Language … or is there basically no way to do this and I”ll need to fork GitHub - swiftlang/swift-driver: Swift compiler driver reimplementation in Swift as well in order to do this?

  2. If no to the above, is there a more sensible/canonical way to make the build system for swift/swift-driver build “static” or “standalone” executables, something obvious that I’m missing?

  3. If no on both the above, should I draw up a patch/PR for both swift and swift-driver to allow --extra-cmake-options to propagate down into the swift-driver build(s) or is that undesirable?

Thanks so much for any advice you can give.

Final quirk, I wasn’t sure about how this works… on a standard out of the box build now, swift-driver is built, which is fine, but I was a bit surprised that by default it’s building earlyswiftdriver → cmark → llvm → swift … I can’t quite understand the point of earlyswiftdriver if I’m using the host swift compiler, because I’d expect that to be building swift-syntax, swift-driver and the swift-in-the-compiler parts of swift-frontend. And if the driver is built at the start, does it not also get rebuilt after the end of the build chain (after swift itself is built)? Sorry if I’m asking dumb questions!

EDIT: I meant to add my build-script command…

utils/build-script --skip-build-benchmarks \
  --extra-cmake-options="-DBUILD_SHARED_LIBS=NO,-DSWIFT_SHOULD_BUILD_EMBEDDED_STDLIB_CROSS_COMPILING=YES" \
  --swift-darwin-supported-archs "x86_64;arm64" \
  --release --swift-disable-dead-stripping \
  --bootstrapping=hosttools --sccache

Let me say I have never built the toolchain for macOS and don't know what a "static" build is there, but I do know build-script well.

I don't think there's a build flag you can pass in and build-script simply invokes a Python build helper in the swift-driver repo that builds using CMake, plus SwiftPM on some platforms, so you'd probably have to modify that helper.

I can't really answer 2. and 3.

You just answered your own question: earlyswiftdriver is the swift driver built by the host prebuilt Swift release compiler for the initial build of the trunk compiler. Then, near the end after SwiftPM has been bootstrapped, swift-driver is built with that trunk compiler and that final version is installed in the toolchain, throwing out the earlyswiftdriver.

No problem, build-script can get complicated.

Thanks for your help!

For the particular problem I’m trying to solve (building a macOS app only and deploying it in the Mac App Store) the restrictions are quite unique and I doubt what I need is at all helpful to anyone else.

For now, I’m just focused on the swift-driver project and whether there is a way I can invoke the Utilities/build-script-helper.py that is in that project in a way that makes it only build static libraries, not dylibs, for the dependencies it creates. That way the final executable will link the way I want. Because macOS only uses CMake when invoked this way (as far as I can see), I can ignore swiftpm build completely. And really all I need is to find some kind of passthru of parameters to the CMake that the above script invokes.

I think that’s all I need for my special case. I was thinking for anyone who is familiar with the way the above script works and the CMake build of swift-driver there might be some mechanism for passing arbitrary stuff to CMake.

I’ll poke around reading the python scripts some more and see if there’s a backdoor, it will just take me a while! If anyone intimately knew this product, and in particular the CMake build that’s invoked by Utilities/build-script-helper.py then I bet they could save me time by saying “that will never work” or “sure, just use this!”.

I’m trying to avoid patching swift-driver itself so that my rather special patches can be limited to one source tree (swiftlang/swift) and not spread around, just to make it more complicated and to keep us able to track standard code more easily.

But if I must patch/fork swift-driver then I must! :slight_smile:

Carl

For what it is worth, on Windows, I am building a mostly statically linked swift-driver (i.e. dispatch, closure, and Microsoft libraries are still dynamically linked). I’ve not pushed up the changes for enabling that yet, but I can at least put up the changes for the CMake invocation soon in build.ps1.

Edit:
utils: enable early swift driver on Windows by compnerd · Pull Request #76574 · swiftlang/swift · GitHub is the PR for that on Windows.

Interesting. Useful. Thanks.

It looks like your patch is powershell script so probably not directly what I’d use on macOS. But still gives me ideas. And I saw your review comment reply that building shared dynamic libraries for the compiler parts instead of standalone executables (depending only on platform DLLs) probably isn’t very useful, because binary size isn’t that important on the actual compiler tools themselves. I felt the same, the change was relatively recent I think (although I lose track a bit as I was in a fairly isolated corner with my product), and it didn’t really seem necessary to me when I saw it. Though I am only just now upgrading my toolchains to use the new swift-driver, so I’m not a very useful source of opinion!


For now I went with a very simple point patch on my swift-driver tree. This wouldn’t be suitable for upstreaming at all, I’d parameterise it to scale it, add tests, etc. if it were “real” swift compiler work of course…

diff --git a/Utilities/build-script-helper.py b/Utilities/build-script-helper.py
index ee161000..30a964c3 100755
--- a/Utilities/build-script-helper.py
+++ b/Utilities/build-script-helper.py
@@ -420,7 +420,7 @@ def build_using_cmake(args, toolchain_bin, build_dir, targets):
   base_swift_flags.append('-module-cache-path "{}"'.format(os.path.join(build_dir, 'module-cache')))
 
   for target in targets:
-    base_cmake_flags = []
+    base_cmake_flags = ['-DBUILD_SHARED_LIBS=NO']
     swift_flags = base_swift_flags.copy()
     swift_flags.append('-target %s' % target)
     if '-apple-macosx' in args.build_target:

Thank you for your help and advice guys.