Compiling Individual Files

Continuing the adventure of compiling Swift files without Xcode, you may have the need to compile individual files. I’m not sure if we can use this method to build up our own incremental builds for Swift as I don’t have any projects large enough to experiment with yet, but it could be interesting.

I’m going to start out with a couple of files: foo.swift and main.swift.

foo.swift

public func foo() -> String { return "Foo" }

main.swift

println("Hello, World!")

Now, if you simply run:

$ swiftc -sdk $(xcrun --show-sdk-path) -emit-object foo.swift -o foo.o

You’ll get a binary that will contain a main function as the compiler will add one in for your implicitly. When you go to link the files, you’ll end up with duplicate symbol collision.

$ nm foo.o 
00000000000000d8 s EH_frame0
0000000000000065 s L___unnamed_1
0000000000000010 T __TF3foo3fooFT_SS
0000000000000118 S __TF3foo3fooFT_SS.eh
                 U __TFSSCfMSSFT21_builtinStringLiteralBp8byteSizeBw7isASCIIBi1__SS
                 U __TFSsa6C_ARGCVSs5Int32
                 U __TFSsa6C_ARGVGVSs20UnsafeMutablePointerGS_VSs4Int8__
0000000000000030 T _main
0000000000000140 S _main.eh
0000000000000000 t _top_level_code
00000000000000f0 s _top_level_code.eh                     

Remember how I said that there are some interesting differences between swift and swiftc, well, here’s another one.

What we need to do is tell Swift to compile foo.swift but also let the compiler know that we have some other files. We can do that like this:

swift -frontend -c -sdk $(xcrun --show-sdk-path) -emit-object -primary-file foo.swift main.swift -o foo.o

NOTE!! You cannot use the -primary-file switch with swiftc; it’s only available when you use swift -frontend -c. What I believe is happening is that the primary file is getting compiled and the compiler is using all of the other input files for determining available symbols.

After updating the files to look like this:

foo.swift

public func foo() -> String {
    let message = testing()
    return "Foo - \(message)"
}

main.swift

public func testing() -> String { return "testing!" }
println("Hello, World!")

Then compiling foo.swift:

$ swift -frontend -c -sdk $(xcrun --show-sdk-path) -emit-object -primary-file foo.swift main.swift -o foo.o

We can see all of the goodies, including the all important missing main function definition.

$ nm foo.o
0000000000000268 s EH_frame0
00000000000001f2 s L___unnamed_1
0000000000000000 T __TF3foo3fooFT_SS
0000000000000280 S __TF3foo3fooFT_SS.eh
                 U __TF3foo7testingFT_SS
                 U __TFSS30convertFromStringInterpolationfMSSFtGSaSS__SS
                 U __TFSS37convertFromStringInterpolationSegmentfMSSFSSSS
                 U __TFSSCfMSSFT21_builtinStringLiteralBp8byteSizeBw7isASCIIBi1__SS
                 U __TFSa20convertFromHeapArrayU__fMGSaQ__FTBp5ownerBo5countBw_GSaQ__
                 U __TMdSS
                 U __swift_FORCE_LOAD_$_swiftFoundation
0000000000000218 S __swift_FORCE_LOAD_$_swiftFoundation_$_foo
0000000000000160 t _arraydestroy
00000000000002a8 s _arraydestroy.eh
0000000000000200 s _metadata
                 U _swift_allocObject
                 U _swift_deallocObject
                 U _swift_unknownRelease
                 U _swift_unknownRetain

There are a lot more options available to us, like:

  • -emit-module which would let us produce partial .swiftmodule files.
  • -emit-module-path for the path of the partial .swiftmodule files.
  • -emit-module-doc-path for the path of the partial .swiftdoc files.

Of course, this is only the tip of the iceberg. Next, you’ll need to merge the

swiftmodule files together and then link the object files together to actually create a usable executable or library.

That’s for another day.

Compiling Individual Files