Commit e2fa4f07 authored by Drew's avatar Drew

Add support for C language to atllbuild

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" (

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  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.

\# Rationale-NOT:

Additional rationale:

* SwiftPM will probably add this eventually
* per #113, we should be a superset of their functionality

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 `.h`s can import other `.h`s (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.

SwiftPM avoids this problem by requiring everyone to create e.g. `CCurl` everywhere:

And in fact people do:

The problem is now you have to import `CCurl` everywhere (even in files that don't directly use it).  See generally,,

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.
parent db6b092c
Pipeline #2333 passed with stage
in 5 minutes and 31 seconds
......@@ -5,7 +5,7 @@ linux:
stage: build
- apt-get update
- apt-get install -y xz-utils package-deb
- apt-get install -y xz-utils package-deb libcurl4-openssl-dev
- git submodule update --init --recursive
- bootstrap/ linux
- bin/atbuild check
FROM drewcrawford/swift:latest
RUN apt-get update && apt-get install curl -y && curl -s | bash && apt-get install --no-install-recommends -y package-deb xz-utils
FROM drewcrawford/buildbase:latest
RUN apt-get update && apt-get install package-deb libcurl4-openssl-dev
ADD . /atbuild
WORKDIR atbuild
RUN bootstrap/ linux
Subproject commit 362108f47d0535a37881f99a34946f4c8e4caa1d
Subproject commit 79f5d1dc15017965216aaf5b905c2ca142dd7125
This diff is collapsed.
This tests C project support. Some notes:
* Link with libcurl, which means libcurl-dev must be installed on your system. This also tests the module-map-link option.
* We also test C/iOS support, however libcurl is not available on that platform. So we just disable everything curl-related
:name "c"
:tasks {
:lib {
:tool "atllbuild"
:name "lib"
:sources ["lib/**.swift" "lib/**.c" "lib/**.h"]
:output-type "static-library"
:module-map "synthesized"
:c-compile-options ["-DGOT_OPTIONS"]
:overlays {
:atbuild.platform.osx {
:module-map-link ["curl"]
:atbuild.platform.linux {
:module-map-link ["curl"]
:tool {
:tool "atllbuild"
:name "tool"
:sources ["tool/**.swift"]
:output-type "executable"
:link-with-product ["lib.a"]
:dependencies ["lib"]
:default {
:tool "nop"
:dependencies ["tool"]
\ No newline at end of file
#include "baz.h"
void bar() {
printf("hello from C");
//compile error if we don't get GOT_OPTIONS
#error didn't get options
\ No newline at end of file
void bar();
\ No newline at end of file
#include "baz.h"
void baz() {
\ No newline at end of file
#if __arm64__
#include <curl/curl.h>
void baz();
\ No newline at end of file
public func foo() {
#if !os(iOS)
\ No newline at end of file
import lib
\ No newline at end of file
......@@ -13,6 +13,15 @@ echo "****************SELF-HOSTING TEST**************"
$ATBUILD atbuild
echo "****************C TEST**************"
cd $DIR/tests/fixtures/c
$ATBUILD --configuration release
if [ "$UNAME" == "Darwin" ]; then
$ATBUILD --platform ios-arm64
echo "****************CONFIGURATION TEST**************"
cd $DIR/tests/fixtures/configurations
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment