Developing in Swift on Linux/Windows with vscode

2020-02-20

Why

Swift is a system's language developed by Apple. It's mainly used for MacOS and iOS development. But, it's open-source, and can run on Linux as well. A fast way to get started with Swift on Linux or Windows, would be to use Visual Studio Code and dev containers that can launch a docker to develop in.

How

To create a dev container we'll have to create a folder named .devcontainer with a Dockerfile and a devcontainer.json in it. The Dockerfile will define the docker environment, the devcontainer.json will contain the config.

Given the directory structure below. If you'll open the directory in vscode and have the Remote - Containers extension installed, vscode will prompt you to launch the dev container. From that point on, vscode will take control.

> tree -a
.
├── .devcontainer
   ├── devcontainer.json
   └── Dockerfile
└── src
    ├── .gitignore
    └── Hello
        ├── .gitignore
        ├── Package.swift
        ├── README.md
        ├── Sources
           └── Hello
               └── main.swift
        └── Tests
            ├── HelloTests
               ├── HelloTests.swift
               └── XCTestManifests.swift
            └── LinuxMain.swift
> code .

After vscode will build and launch the container, you'll be able to open a vscode console, and execute terminal commands.

root@8e2d11d4ead0:/workspaces/SwiftDev/src# mkdir Hello
root@8e2d11d4ead0:/workspaces/SwiftDev/src# cd Hello/
root@8e2d11d4ead0:/workspaces/SwiftDev/src/Hello# swift package init --type executable
Creating executable package: Hello
Creating Package.swift
Creating README.md
Creating .gitignore
Creating Sources/
Creating Sources/Hello/main.swift
Creating Tests/
Creating Tests/LinuxMain.swift
Creating Tests/HelloTests/
Creating Tests/HelloTests/HelloTests.swift
Creating Tests/HelloTests/XCTestManifests.swift
root@8e2d11d4ead0:/workspaces/SwiftDev/src/Hello# swift build
[4/4] Linking Hello
root@8e2d11d4ead0:/workspaces/SwiftDev/src/Hello# .build/x86_64-unknown-linux/debug/Hello
Hello, world!
root@8e2d11d4ead0:/workspaces/SwiftDev/src/Hello#

Dockerfile

We'll base it on official swift docker image, with some additional configs required by vscode.

FROM swift:latest

# This Dockerfile adds a non-root user with sudo access. Use the "remoteUser"
# property in devcontainer.json to use it. On Linux, the container user's GID/UIDs
# will be updated to match your local UID/GID (when using the dockerFile property).
# See https://aka.ms/vscode-remote/containers/non-root-user for details.
ARG USERNAME=vscode
ARG USER_UID=1000
ARG USER_GID=$USER_UID

# Avoid warnings by switching to noninteractive
ENV DEBIAN_FRONTEND=noninteractive

# Configure apt and install packages
RUN apt-get update \
    # Create a non-root user to use if preferred - see https://aka.ms/vscode-remote/containers/non-root-user.
    && groupadd --gid $USER_GID $USERNAME \
    && useradd -s /bin/bash --uid $USER_UID --gid $USER_GID -m $USERNAME \
    # [Optional] Add sudo support for the non-root user
    && apt-get install -y sudo \
    && echo $USERNAME ALL=\(root\) NOPASSWD:ALL > /etc/sudoers.d/$USERNAME\
    && chmod 0440 /etc/sudoers.d/$USERNAME \
    #
    # Clean up
    && apt-get autoremove -y \
    && apt-get clean -y \
    && rm -rf /var/lib/apt/lists/*

# Switch back to dialog for any ad-hoc use of apt-get
ENV DEBIAN_FRONTEND=dialog

devcontainers.json

Additional configs.

{
  "name": "Swift",
  "dockerFile": "Dockerfile",
  "runArgs": ["--cap-add=SYS_PTRACE", "--security-opt", "seccomp=unconfined"],
  // Use 'settings' to set *default* container specific settings.json values on container create.
  // You can edit these settings after create using File > Preferences > Settings > Remote.
  "settings": {
    "terminal.integrated.shell.linux": "/bin/bash"
  },
  // Use 'appPort' to create a container with published ports. If the port isn't working, be sure
  // your server accepts connections from all interfaces (0.0.0.0 or '*'), not just localhost.
  // "appPort": [],
  // Uncomment the next line to run commands after the container is created.
  "postCreateCommand": "swift --version",
  // Comment out the next line to run as root
  "remoteUser": "vscode",
  // Add the IDs of extensions you want installed when the container is created in the array below.
  "extensions": ["kasik96.swift"]
}

Conclusions

This approach is very handy if you're running Swift on a Linux or Windows host. Executing the swift binary from inside the container to build local(on the host machine) binaries will work. But the binaries themselves might not work, even on a Linux machine, as the Swift runtime is not available by default. Still this method will work if you're developing Swift apps for Linux(like webservers), or libraries or just learning the language.

Keep in mind that the swift image can be quite hefty, at around 400MB.