SwiftPM does not create a .dll file

Hi all,

I'm just trying to figure out how to build a .dll file under Windows using the Swift Package Manager. Swift version is 5.9.2. Here are my commands:

mkdir test
cd test
swift package init --type library
swift build -c release

The build completes successfully, the test.swiftdoc, test.swiftmodule, and test.swiftsourceinfo files are created, but test.dll is nowhere to be seen.

Am I missing something?

This is the intended behaviour as of Swift 5.9, Swift libraries are built by default for static linking as a collection of object files (with .o extension for Darwin and Linux) as parts of a whole graph that has one or more executables at its root. If you don't specify an executable as its root, there's nothing to link these files to.

Compiling redistributable libraries separately outside of a given package/build graph on platforms where Swift can't be ABI stable has plenty of underlying issues and is not supported. You won't be able to get redistributable .so or .dylib files on Linux or Darwin either. Only on Darwin there's an .xcframework packaging convention for resilient ABI-stable libraries.

This is a bit of a pity - for many the same-toolchain-and-platform constraint would still make dynamic libraries useful (e.g. we end up building our own toolchain that enables xcframeworks on Linux as we need the capability - I’m sure it could be useful for others too, even with full abi stability - the library evolution stability - even with those caveats - is helpful when you want to vend API…). Have there been any thoughts on enabling this? We don’t mind the static linking in general, but for the api that we vend to customers we do need the ability to have evolution turned on and do dynamic linking….

It isn't as easy is that. Specifically "Linux" is not a single well-defined platform. Just for the Swift runtime and stdlib we have to account for their dependencies such as libc and libc++ flavors and versions, which put constraints on the Linux kernel version. If any of your code or its dependencies (even transitive ones) use swift-corelibs-foundation or swift-corelibs-dispatch (the latter is required for Swift Concurrency), we would have to account for all of their dependencies and their versions (libcurl, libxml2, and so on). If there's a mismatch anywhere, we either have to track and diagnose it, or leave users to obscure and hard to debug bugs and linker/loader errors.

At that point it's not the same-toolchain-and-platform constraint, it's the same-state-of-your-unique-system-installation constraint. SwiftPM can't currently reliably track dependencies outside of your build graph, where a simple apt-get install can break your dynamic Swift library.

Windows is a whole different story, but the reasoning is the same. You can get a good experience if all of your dependencies are in your Package.swift and we can build them all together. We can't control anything outside of the package manifest, unless it's a self-contained Swift SDK that SwiftPM could look into. And even then it's not relevant for Windows, as it's not supported as a target platform in Swift SDKs.

I really disagree with this. The spew of linker warnings is due to the fact that SPM does not have the concept of linkage applied at the target level. Windows is significantly more advanced than other systems and bakes in the concept of ABI into the code. The boundary must be explicitly delineated in each interface to indicate of it is passed off the modules ABI (__declspec(dllexport)), external to the module (__declspec(dllimport)), or internal (no attribute). Furthermore, because there is no ABI compatibility in general on windows (the C/C++ runtimes do not guarantee ABI stability), the traditional model is to package all dependencies. The fact that there’s no DT_RPATH shenanigans to deal with makes this easier and safer (there’s no injection attacks, like the ones that can happen with LD_LIBRARY_PATH or ldd). The windows model is built for dynamic linking and the SPM model does not currently provide the necessary flexibility to correctly build software outside of the simple cases.

I’m not sure I understand this. We ship Swift SDKs for all the supported architectures. I assume you are referring to the experimental tooling that copies linux files into an SDK? But that is something that can be done.

3 Likes

I'm referring to the concept of Swift SDK artifact bundles as defined in SE-0387 and supported by SwiftPM on Linux and macOS. Windows may be supported by the swift experimental-sdk command as a host platform (no way for me to say for sure as I haven't tried it myself on Windows and Windows CI doesn't run SwiftPM tests), but I'm not aware of Swift SDK artifact bundles existing for Windows as a target platform. In that sense I hesitate to say that this scenario is supported in SwiftPM as of 5.9.

My current recommendation for your use case would be to use CMake which has support for building packages with static and dynamic linking as needed and offers greater control over the build. There are examples at GitHub - compnerd/swift-cmake-examples: Swift example projects and I am more than happy to have additional examples added to the repository for reference.

Does this mean that the SwiftPM command

swift package init --type library
// or simply
swift package init

does not have a meaningful use case?

I don’t think Windows is any more advanced than Unix/Linux, it just uses a different convention. The Clang/GCC-on-Unix equivalent of __declspec(dllexport) is __attribute__((visibility("default"))). The equivalent of __declspec(dllimport) is an extern declaration without a definition. The equivalent of no __declspec is an extern with a definition in a different TU.

One can easily have this situation on Darwin, too—just don’t build your frameworks with resiliency. You still get all the benefits of frameworks, such as resource management and namespacing. I don’t see why Windows users shouldn’t get these same advantages.

I find this assertion misleading. There’s a well known attack vector of putting DLLs in the app’s working directory or elsewhere in the DLL search path. It’s called DLL preloading and it has been the cause of real-world vulnerabilities.

More that it’s only meaningful as an input to other SwiftPM packages (or build systems that can directly invoke SwiftPM), not as an end product itself.

The most fundamental SwiftPM command swift package init is "only meaningful as an input to other SwiftPM packages"? Seriously? SwiftPM should at least warn users about that when they try to init their packages.

How does this differ from File > New > Project, which is only useful as an input to the Xcode build system?

The difference is significant: In the case of Xcode you select the Product > Build menu command, and get a resulting executable file (a library in our case), but in case of SwiftPM the commands

swift package init
swift build

do not produce any executable!

Ok, to be precise, that’s not a problem with swift init or with swift build. The issue is that SwiftPM has developed the opinion that static libraries aren’t valid terminal targets. Which I agree is an… interesting opinion, considering how much of the Windows SDK and VCRuntime are delivered as .LIB files.

OK, got it. BTW I have just tried to create a new library project in Xcode, and it compiles successfully, but does not produce an executable file - exactly like in the case of SwiftPM.

I still think that this is somehow misleading.

I have a dynamic library project in Xcode written in C, and the library is used in production. Now I afraid to touch that Xcode project :(

.lib files are not static libraries. They are import libraries. There are only a handful of static libraries that Microsoft distributes, with the most important ones:

  • libcmt
  • libcmtd

These match /MT and /MTd. The recommendation from Microsoft is to use /MD or /MDd which dynamically link to the runtimes. The whole "DLL Hell" universe came about from the fact that the Windows world is almost universally in favour of dynamically linked.

I think that this is a common source of confusion for people, and therefore, while it may seem unnecessarily pedantic, I think that when it comes to discussing the build, it is important that it is cleared up.

You cannot identify a static library vs an import library by the suffix (and naming based deduction should generally be frowned upon). The import descriptor thunk's presence is a good indicator of an import library (which is a more complicated thing to do with the modern binary encoded import library).

However, the fact that SPM does not generate the static library and uses object library is a valid point, and it certainly does cause some trouble.

What I tried to achieve with SwiftPM is to build a dynamic library (.dll), not static. My quick experiments with Xcode also involved dynamic libraries.

I do think that it is more advanced than Unix (not necessarily Darwin, which I don't count as it does not define __unix__).

DLL Storage and visibility are two different topics (see also the extensions that the PlayStation format uses, which supports both of these concepts with an ELF based object file format).

The visibility does not indicate the ABI boundary but weather the symbol participates in dynamic linkage (aka, does it get a GOT slot). This becomes even more apparent when you consider the Linux-only (platform specific as per the ELF spec) behaviour of __attribute__((__visibility__("protected"))) which gives a symbol non-interpositioning global linkage (which you cannot map to any behaviour of DLL Storage as IAT patching is now extremely common with detours). The extern with a definition in a different TU is a C behaviour and makes sense on Windows as well. This is particularly important to get correct with data declarations as you may otherwise end up with a common linkage symbol which can result in ODR violations.

I am not suggesting that Windows should not have these features available to them, but rather the opposite - that Windows should grant these features to Swift users. The point was that the Windows world does default to DLLs, and that the general expectation is to bundle the DLLs into distributions.

How do I do that? Is there a documentation somewhere?
I issue the command swift package init and then do what? Or do I not ever issue this wonderful command and construct the Package.swift file by hand?

There is a problem with swift init. It defaults to --type library command line arguments, which in turn no longer has any meaningful use anymore, seemingly as of Swift 5.9. The default should have a meaningful usage.