There are 5 cases where I think it makes sense to add a little C to your Swift project:
To use the odd feature Swift doesn't support. Recently, I needed to call a variadic C function; Swift cannot call them, C is our only hope
To work around a Swift compiler bug. Several of my projects have this case.
To repackage an existing Xcode project where somebody used C in it. I have not investigated and don't want to investigate whether that somebody was sane or insane, but we should at least be able to build their project.
To include headers from a system C library. SwiftPM tries to solve this with module maps, however it doesn't hide the implementation details https://bugs.swift.org/browse/SR-655. This feature can actually hide them with a few different methods discussed below, which is a clear win.
To write Swift bindings for a C library. This generally involves a little C glue code (such as using a header or something), and for reasons that will become clear, using our C support is better than previous approaches at that problem.
Additional rationale:
SwiftPM will probably add this eventually
per #113, we should be a superset of their functionality
Rationale-NOT:
I would like to be very clear about my goals:
This is really only designed for the case of "need a little C in your Swift project", not anything larger
This is not a replacement for e.g. GNU Make or a general-purpose C buildsystem, nor will it become one
The preferred mechanism for building a real C library is shelling out to your real C buildsystem
Nobody should be repackaging their established C libraries as atllbuild tasks. atllbuild is designed to build Swift projects, not C projects.
Design
You can now specify .c and .h files in the sources for atllbuild tasks
Also **.c and **.h just like **.swift
Like .swift, no files are scanned by default, everything is explicit
Adding .c files causes them to be compiled and linked into the atllbuild task just like swift files
Adding .h files exposes declarations to Swift. It works much like a bridging header; put stuff in header files and then Swift code will see it.
Your .hs can import other .hs (from the system, or anywhere else) and you otherwise have access to the complete C preprocessor
New atllbuild setting c-compile-options specifies compile options for C files. compile-options is ignored for C.
C files work as you'd expect, including support for things like configurations, optimization, atbuild preprocessor macros, etc.
Linking
The standard link-options sets link options for both C and Swift; since they are linked into the same library there is no individual control. So if you want to link your C (and Swift) code against curl, you could say :link-options ["-lcurl"] for example.
The problem with this approach is that everybody who depends on you might also need -lcurl. Traditionally we've solved this with overlays that we expose to callers.
Here is the cool part though. This PR adds a new option :module-map-link ["curl"]. That will inject a link directive into both the module map we use at buildtime and the one we export e.g. into an atbin.
Emitting that link directive has the effect of injecting :link-options ["-lcurl"]. However, it will also inject that link option into any Swift module that imports this one. The result is that downstream no longer needs to add :link-options ["-lcurl"] anymore.
Additonally, since we achieve this in a single module, there is no CCurl to import anymore. The details of linking to the C library are more effectively hidden.
For these reasons, I believe using the C support in this PR is way more effective for writing bindings than any other solution.
Known issues
Using .h in sources requires a synthesized module map
Using .c in sources is not supported for bitcode
Using module-map-link requires the module map to be distributed for the link to take effect on downstream; we recommend packageatbin for packing build products
Currently, swift functions are not "visible" to C code (like they are visible to ObjC from Xcode) although presumably if you had a function, knew its calling convention, and knew its c-name, you could totally call it from C.
This PR lets you mix .swift, .h, and .c files all in the same atllbuild task. It works a lot like Xcode's behavior, if you've used that.
Rationale
I feel the need to defend this feature, since I have been previously on the record as saying "the entire value is debatable" (https://www.mail-archive.com/swift-evolution@swift.org/msg01829.html).
There are 5 cases where I think it makes sense to add a little C to your Swift project:
Additional rationale:
Rationale-NOT:
I would like to be very clear about my goals:
Design
.c
and.h
files in the sources for atllbuild tasks**.c
and**.h
just like**.swift
.swift
, no files are scanned by default, everything is explicit.c
files causes them to be compiled and linked into the atllbuild task just like swift files.h
files exposes declarations to Swift. It works much like a bridging header; put stuff in header files and then Swift code will see it..h
s can import other.h
s (from the system, or anywhere else) and you otherwise have access to the complete C preprocessorc-compile-options
specifies compile options for C files.compile-options
is ignored for C.Linking
The standard
link-options
sets link options for both C and Swift; since they are linked into the same library there is no individual control. So if you want to link your C (and Swift) code against curl, you could say:link-options ["-lcurl"]
for example.The problem with this approach is that everybody who depends on you might also need
-lcurl
. Traditionally we've solved this with overlays that we expose to callers.SwiftPM avoids this problem by requiring everyone to create e.g.
CCurl
everywhere: https://github.com/apple/swift-package-manager/blob/master/Documentation/SystemModules.mdAnd in fact people do: https://github.com/IBM-Swift/CCurl
The problem is now you have to import
CCurl
everywhere (even in files that don't directly use it). See generally, https://bugs.swift.org/browse/SR-655, https://gist.github.com/briancroom/5d0f1b966fa9ef0ae4950e97f9d76f77Here is the cool part though. This PR adds a new option
:module-map-link ["curl"]
. That will inject a link directive into both the module map we use at buildtime and the one we export e.g. into an atbin.Emitting that link directive has the effect of injecting
:link-options ["-lcurl"]
. However, it will also inject that link option into any Swift module that imports this one. The result is that downstream no longer needs to add:link-options ["-lcurl"]
anymore.Additonally, since we achieve this in a single module, there is no
CCurl
to import anymore. The details of linking to the C library are more effectively hidden.For these reasons, I believe using the C support in this PR is way more effective for writing bindings than any other solution.
Known issues
.h
insources
requires a synthesized module map.c
insources
is not supported for bitcodemodule-map-link
requires the module map to be distributed for the link to take effect on downstream; we recommendpackageatbin
for packing build products