Linux Hello world in Swift

Intro

Swift is one of cool modern languages, that appeared recently on the scene. Purpose of Swift is replace C/ObjectC/C++ on MacOS. In same time its works on Linux. Support of Swift for Linux is not that great there is lack of many libraries and all Siwft power opens on Macos, but its still some fun language to play. Main swift page provides only Macos and Ubuntu as main supported platforms. Anything else you may have troubles to get working. Currently supported architectures are intel64 and arm64. Lets get into this journey of running Swift on Linux.

Installing Swift

Ubuntu

Installing on Ubuntu is most easiest part, download from main page and done. https://swift.org/download/

wget -c https://swift.org/builds/swift-5.1.4-release/ubuntu1804/swift-5.1.4-RELEASE/swift-5.1.4-RELEASE-ubuntu18.04.tar.gz
tar -xvf swift-5.1.4-RELEASE-ubuntu18.04.tar.gz

Archlinux

Installing from main swift page, not able to compile because of missing GCC flags. So easiest way to install is from AUR https://aur.archlinux.org/packages/swift-bin/ Compiling other swift package from AUR have same issues, related to not able to compile source.

wget -c https://aur.archlinux.org/cgit/aur.git/snapshot/swift-bin.tar.gz
tar -xvf swift-bin.tar.gz
cd siwft-bin
makepkg
sudo pacman -U

Compile Swift

All this examples given for Swift version 5.1

mkdir HelloWorld
cd ./HelloWorld/
swift package init --type executable
swift run

Compiled file is located in

./.build/debug/HelloWorld

Static Swift Compilation

Swift static compilation compiles in all Swift runtime and uses default Linux libraries.

swift build -c release -Xswiftc -static-stdlib

File is located in ./.build/x86_64-unknown-linux/release/HelloWorld

ls -lah ./.build/x86_64-unknown-linux/release/HelloWorld
-rwxr-xr-x 1 fam fam 34M Mar  1 18:29 ./.build/x86_64-unknown-linux/release/HelloWorld

ldd ./.build/x86_64-unknown-linux/release/HelloWorld
    linux-vdso.so.1 (0x00007ffebcd17000)
    libdl.so.2 => /usr/lib/libdl.so.2 (0x00007efc5a6d4000)
    libpthread.so.0 => /usr/lib/libpthread.so.0 (0x00007efc5a6b2000)
    libatomic.so.1 => /usr/lib/libatomic.so.1 (0x00007efc5a6a8000)
    libstdc++.so.6 => /usr/lib/libstdc++.so.6 (0x00007efc5a4bf000)
    libm.so.6 => /usr/lib/libm.so.6 (0x00007efc5a379000)
    libgcc_s.so.1 => /usr/lib/libgcc_s.so.1 (0x00007efc5a35f000)
    libc.so.6 => /usr/lib/libc.so.6 (0x00007efc5a197000)
    /lib64/ld-linux-x86-64.so.2 => /usr/lib64/ld-linux-x86-64.so.2 (0x00007efc5c624000)

Swift on ARM64

Swift installation manual for RasPi4

https://swift-arm.com/install-swift/

curl -s https://packagecloud.io/install/repositories/swift-arm/release/script.deb.sh | sudo bash
sudo apt-get install swift5

Now steps from beginning could be followed. And result will be same.

Interfacing to C

Swift is based on clang/llvm and able to parse C language and create interface to Swift. Its eliminates need to write glue/binding library for Swift and simplifies C integration.

mkdir Cint0
cd Cint0
swift package init --type executable

Define C header with functions to be binded code.h

#ifndef __CCODE_H
#define __CCODE_H

#include <stdlib.h>
#include <stdio.h>

int one();
int two();
int calc(int a, int b);

#endif

Function implementation

code.c

#include "include/code.h"

int one()
{
    printf("First\n");
    return 1;
}

int two()
{
    printf("Second\n");
    return 2;
}

int calc(int a, int b)
{
    return a+b;
}

main.swift

import ccode;

print("Start program")

print(one());
print(ccode.two());
print(calc(12,12))

print("End program")

C source and header files are ready. Swift code that using C code is written. So its time to define package and build everything. Package.swift defines what is included in source and dependencies. So add target "Cint0" with dependencies on "ccode" that is our C code. And then add extra target that will compile code "ccode" and doesn't depend on anything. ... and build.

Package.swift

// swift-tools-version:5.1
// The swift-tools-version declares the minimum version of Swift required to build this package.

import PackageDescription

let package = Package(
    name: "Cint0",
    dependencies: [
        // Dependencies declare other packages that this package depends on.
        // .package(url: /* package url */, from: "1.0.0"),
    ],

    targets: [
        // Targets are the basic building blocks of a package. A target can define a module or a test suite.
        // Targets can depend on other targets in this package, and on products in packages which this package depends on.
        .target(
            name:"Cint0",
            dependencies: ["ccode"]),
        .testTarget(
            name: "Cint0Tests",
            dependencies: ["Cint0"]),
        .target(
            name:"ccode",
            dependencies: []),

    ]
)

SDL2 example

Binding SDL2 library

To use C SDL2 headers from Swift we need to create bindings. This also shows how to bind C code to Swift

mkdir CSDL2
cd CSDL2
swift package init --type system-module

Resulting files are

$ swift package init --type system-module
Creating system-module package: CSDL2
Creating Package.swift
Creating README.md
Creating .gitignore
Creating module.modulemap

Create Headers directory and add there file CSDL2-Header.h

#include <SDL2/SDL.h>

Package.swift

// swift-tools-version:5.1
// The swift-tools-version declares the minimum version of Swift required to build this package.

import PackageDescription

let package = Package(
    name: "CSDL2",
    products: [
        // Products define the executables and libraries produced by a package, and make them visible to other packages.
        .library(
            name: "CSDL2",
            targets: ["CSDL2"]),
    ],
    dependencies: [
        // Dependencies declare other packages that this package depends on.
        // .package(url: /* package url */, from: "1.0.0"),
    ],
    targets: [
        // Targets are the basic building blocks of a package. A target can define a module or a test suite.
        // Targets can depend on other targets in this package, and on products in packages which this package depends on.
        .target(
            name: "CSDL2",
            dependencies: []),
        .testTarget(
            name: "CSDL2Tests",
            dependencies: ["CSDL2"]),
    ]
)

Here is main thing, to tell Swift with headers to make accessible module.modulemap

module CSDL2 {
    header "Headers/CSDL2-Header.h"
    link "SDL2"
    export *
}

Now its time to test CSDL2 package

SDL2 test with bindings

SDL2 test

mkdir SDLtest
cd ./SDLtest
swift package init --type executable

Small SDL2 example in C

#include <SDL2/SDL.h>

#define SCREEN_WIDTH  200
#define SCREEN_HEIGHT 200

char quit = 0;
SDL_Window *window = NULL;
SDL_Renderer *renderer = NULL;

int main()
{
    SDL_Init(SDL_INIT_VIDEO);
    window = SDL_CreateWindow(
        "WEBASM",
        SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
        SCREEN_WIDTH, SCREEN_HEIGHT,
        SDL_WINDOW_SHOWN | SDL_WINDOW_OPENGL);
    renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED);
    SDL_SetRenderDrawColor(renderer, 0xff, 0xff, 0xff, 0xff);
    int quit=0;
    while(0 == quit)
    {
        SDL_Event event;
        while (SDL_PollEvent(&event))
        {
            switch (event.type)
            {
            case SDL_QUIT:
            {
                quit = 1;
                break;
            }
            }
        }
    }
    SDL_DestroyRenderer(renderer);
    SDL_DestroyWindow(window);
    SDL_Quit();
}

Lets convert C example to working with Swift code

Pointers can be defined as OpaquePointer without type so far.

All functions are called same as in C.

Enum types if they are passed then can be accessed as NAME.rawValue .

import CSDL2

var windowPtr: OpaquePointer! = nil
var renderPtr: OpaquePointer! = nil

SDL_Init(0)
SDL_CreateWindowAndRenderer(200,200,0,&windowPtr,&renderPtr) 
SDL_SetRenderDrawColor(renderPtr,0xff,0xff,0xff,0xff)
var quit = 0;
var e = SDL_Event();
while quit == 0 {
    while SDL_PollEvent(&e) != 0
    {
        switch e.type {
        case case SDL_QUIT.rawValue::
            quit = 1
        default:
            print("Unknown event")
        }
    }
}

print("Hello, world!")

SDL_DestroyRenderer(renderPtr)
SDL_DestroyWindow(windowPtr)
SDL_Quit()

So far all Swift features looks like they are working out of the box, without deep Swift knowledge was able to bind library, and use it. Also this is largest code base of Swift code that I ever wrote in my life. So far it looks like easy language to learn.

Sources

Links

[01] https://www.programiz.com/swift-programming
[02] https://swift.org/download/
[03] https://aur.archlinux.org/packages/swift-bin/
[04] https://swift-arm.com/install-swift/
[05] https://github.com/apple/swift-package-manager/blob/master/Documentation/Usage.md
[06] https://theswiftdev.com/how-to-call-c-code-from-swift/
[07] https://github.com/KevinVitale/SwiftSDL/blob/master/Package.swift
[08] https://rderik.com/blog/making-a-c-library-available-in-swift-using-the-swift-package/
[09] https://github.com/KevinVitale/SwiftSDL/tree/master/Sources
[10] https://www.uraimo.com/2016/04/07/swift-and-c-everything-you-need-to-know/#working-with-pointers
[11] https://www.objc.io/blog/2018/01/30/opaque-vs-unsafe-pointers/