Some Linuxy Swift Module goodness

If you substitute the word “hack” for “goodness”, you’re probably closer to the truth. Here’s what I did. Thought I’d share. Caveat programmer/ hackteur.

Building a Custom Module

I created a Packages directory on Ubuntu (/home/erica/Packages). In it I built a TestPackage subdirectory and added TestPackage/Sources, along with a Package.swift file and a Makefile:

% ls
./ ../ Makefile Package.swift Sources/

The text-based Package.swift file specifies the name of the package, and that’s about all. When you make your own module, substitute a different name as needed.

% cat Package.swift 
import PackageDescription

let package = Package(
    name: "TestPackage"
)

I also created a really bottom-of-the-barrel Makefile. You can use this without edits.

all:
	swift build
	git init ; git add . ; git commit -m "Commit" ; git tag 1.0.0

clean:
	rm -rf .build
	rm -rf .git
	rm *~ Sources/*~

Notice how this builds the sources and then establishes a git repo. You need that for accessing the module. I went with a brute force “clean everything, build everything” approach to keep things simple.

Adding Content

In the Sources directory, I built a minimal test file. You can replace this with any file name and file content you want (except main.swift) and can add any number of files as desired for your module.

% ls
./ ../ testpackage.swift
% cat testpackage.swift 
public let testString = "Test Package is Installed"

public func testFunc() -> String {
    return testString
}

Then I ran make in the main TestPackage folder to build the module:

% make
swift build
Compiling Swift Module 'TestPackage' (1 sources)
Linking Library:  .build/debug/TestPackage.a
git init ; git add . ; git commit -m "Commit" ; git tag 1.0.0
Initialized empty Git repository in /home/erica/Packages/TestPackage/.git/
[master (root-commit) 9f41c4e] Commit
 15 files changed, 97 insertions(+)
 create mode 100644 .build/debug/TestPackage.a
 create mode 100644 .build/debug/TestPackage.o/Sources/testpackage.swift.o
 create mode 100644 .build/debug/TestPackage.o/TestPackage/master.swiftdeps
 create mode 100644 .build/debug/TestPackage.o/TestPackage/output-file-map.json
 create mode 100644 .build/debug/TestPackage.o/TestPackage/testpackage.d
 create mode 100644 .build/debug/TestPackage.o/TestPackage/testpackage.swiftdeps
 create mode 100644 .build/debug/TestPackage.o/TestPackage/testpackage~partial.swiftdoc
 create mode 100644 .build/debug/TestPackage.o/TestPackage/testpackage~partial.swiftmodule
 create mode 100644 .build/debug/TestPackage.o/build.db
 create mode 100644 .build/debug/TestPackage.o/llbuild.yaml
 create mode 100644 .build/debug/TestPackage.swiftdoc
 create mode 100644 .build/debug/TestPackage.swiftmodule
 create mode 100644 Makefile
 create mode 100644 Package.swift
 create mode 100644 Sources/testpackage.swift

Consuming a Module

It’s just as simple to build an application that imports and utilizes a Swift module. In my personal Dev folder, I created a new directory. Its  structure is basically identical to the module one:

% ls
./ ../ Makefile Package.swift Sources/

The Makefile here is slightly different, as you see in the following. I decided to name my target “myutility” but obviously, you can change that to whatever name you like. Other than that, you should be able to preserve this as-is.

TARGET=myutility
all:
	swift build

install:
	cp .build/debug/$(TARGET) .

clean :
	rm -rf Packages
	rm -rf .build
	rm *~ Sources/*~ $(TARGET)

The Package.swift file looks like this from the consumer end, using the same target name for the package results. Since this example uses a local module, a dependency url points to the module directory. Make sure that url matches the folder for your module.

% cat Package.swift 
import PackageDescription
let package = Package (
    name: "myutility",
    dependencies: [
        .Package(url: "/home/erica/Packages/TestPackage", majorVersion:1)
    ]
)

Once again, you can stick anything you like in the Sources folder, but here I went with a main.swift.

% cd Sources
% ls
./  ../  main.swift
% cat main.swift 
import TestPackage

print("About to run tests on TestPackage")
print("Base test string: ", testString)
print("Module-specific test string: ", TestPackage.testString)
print("Test function", testFunc())
%

And go…

To conclude, I built and “installed” the application and ran it.

% make
swift build
Cloning Packages/TestPackage
Using version 1.0.0 of package TestPackage
Compiling Swift Module 'TestPackage' (1 sources)
Linking Library:  .build/debug/TestPackage.a
Compiling Swift Module 'myutility' (1 sources)
Linking Executable:  .build/debug/myutility
% make install
cp .build/debug/myutility .
% ./myutility 
About to run tests on TestPackage
Base test string:  Test Package is Installed
Module-specific test string:  Test Package is Installed
Test function Test Package is Installed

And next?

So I’m still trying to figure out how to build modules around normal C-code that isn’t system provided (that is, through /usr/include, /usr/lib stuff). If you have any tips for creating a basic module around cc -c / ar rvs / etc, please let me know.

I’ll probably hook into the built-in libraries using dependencies but I’d like to write some raw C utilities too.

Update: To be clear, I’m not trying to link to /usr/lib. When I build my own .o/.a files, I cannot get Swift to properly link to them as the search path for libraries doesn’t include local files.

One Comment

  • Please make a post when you figure this out. Requiring consumers of modules to build from source is a first step, but it’s a mess for large projects.