Swift from the command line
Published: Sep 10, 2025
Last updated: Sep 10, 2025
The following blog post is simply a recount of me tinkering with Swift without using Xcode.
It explores initializing an executable, working through some simple Swift syntax and managing packages with SwiftPM (Swift Package Manager).
Prerequisites
- Swift (this post uses Swift 6.1).
Initializing a Swift executable package
You can create a new Swift executable package with the following (from the command line):
$ mkdir hello-swift $ cd hello-swift $ swift package init --type executable
You can run swift package init --help
to get more information around arguments like --type
. For example:
swift package init --help OVERVIEW: Initialize a new package USAGE: swift package init [--type <type>] [--enable-xctest] [--disable-xctest] [--enable-swift-testing] [--disable-swift-testing] [--name <name>] OPTIONS: --type <type> Package type: (default: library) library - A package with a library. executable - A package with an executable. tool - A package with an executable that uses Swift Argument Parser. Use this template if you plan to have a rich set of command-line arguments. build-tool-plugin - A package that vends a build tool plugin. command-plugin - A package that vends a command plugin. macro - A package that vends a macro. empty - An empty package with a Package.swift manifest. --enable-xctest/--disable-xctest Enable support for XCTest --enable-swift-testing/--disable-swift-testing Enable support for Swift Testing --name <name> Provide custom package name --version Show the version. -h, -help, --help Show help information.
With this new package initialized as an executable, you can navigate to Sources/main.swift
to see the generated entry file:
/ The Swift Programming Language // https://docs.swift.org/swift-book print("Hello, world!")
Running our executable
You can make use of swift run
to run our executable:
$ swift run [1/1] Planning build Building for debugging... [1/1] Write swift-version--58304C5D6DBC2206.txt Build of product 'hello-swift' complete! (0.19s) Hello, world!
We can re-use this to run after any file changes:
$ swift run Building for debugging... [7/7] Applying hello-swift Build of product 'hello-swift' complete! (1.01s) Hello again, world!
If you have a program installed such as watchexec, you can set it to watch swift extension files with watchexec --exts swift -- swift run
:
$ watchexec --exts swift -- swift run [Running: swift run] [1/1] Planning build Building for debugging... [1/1] Write swift-version--58304C5D6DBC2206.txt Build of product 'hello-swift' complete! (0.20s) Hello again, world! [Command was successful] [Running: swift run] [1/1] Planning build Building for debugging... [7/7] Applying hello-swift Build of product 'hello-swift' complete! (0.73s) Hello again and again, world! [Command was successful]
Building files
You can run swift build
from the project root to build out the executable.
$ swift build [1/1] Planning build Building for debugging... [1/1] Write swift-version--58304C5D6DBC2206.txt Build complete! (0.23s)
By default, swift build
will build with debug configuration.
Outputs are placed in .build
:
$ tree .build -L 3 .build ├── arm64-apple-macosx │ └── debug │ ├── description.json │ ├── hello_swift.build │ ├── hello-swift │ ├── hello-swift-entitlement.plist │ ├── hello-swift.dSYM │ ├── hello-swift.product │ ├── index │ ├── ModuleCache │ ├── Modules │ ├── plugin-tools-description.json │ └── swift-version--58304C5D6DBC2206.txt ├── artifacts ├── build.db ├── checkouts ├── debug -> arm64-apple-macosx/debug ├── debug.yaml ├── plugin-tools.yaml ├── repositories └── workspace-state.json
For an optimized build, pass --configuration release
:
$ swift build --configuration release Building for production... [5/5] Linking hello-swift Build complete! (1.21s) $ tree .build -L 3 .build ├── arm64-apple-macosx │ ├── debug │ │ ├── description.json │ │ ├── hello_swift.build │ │ ├── hello-swift │ │ ├── hello-swift-entitlement.plist │ │ ├── hello-swift.dSYM │ │ ├── hello-swift.product │ │ ├── index │ │ ├── ModuleCache │ │ ├── Modules │ │ ├── plugin-tools-description.json │ │ └── swift-version--58304C5D6DBC2206.txt │ └── release │ ├── description.json │ ├── hello_swift.build │ ├── hello-swift │ ├── hello-swift.dSYM │ ├── hello-swift.product │ ├── ModuleCache │ ├── Modules │ ├── plugin-tools-description.json │ └── swift-version--58304C5D6DBC2206.txt ├── artifacts ├── build.db ├── checkouts ├── debug -> arm64-apple-macosx/debug ├── debug.yaml ├── plugin-tools.yaml ├── release -> arm64-apple-macosx/release ├── release.yaml ├── repositories └── workspace-state.json 20 directories, 14 files
Once compiled, you'll have an optimized binary you can run directly (if your platform is the correct target):
$ ./.build/release/hello-swift Hello again and again, world!
Other useful flags include --triple
to cross-compile for another platform.
Makefile
You can make use of a Makefile
for common operations:
.PHONY: build run clean release test build: swift build release: swift build -c release run: swift run test: swift test clean: swift package clean
In use:
$ make clean swift package clean $ make build swift build Building for debugging... [8/8] Applying hello-swift Build complete! (1.06s) $ make run swift run [1/1] Planning build Building for debugging... [1/1] Write swift-version--58304C5D6DBC2206.txt Build of product 'hello-swift' complete! (0.21s) Hello again and again, world!
Managing packages
You can manage packages in Package.swift
or via the command line.
The default file that was created:
// Package.swift // swift-tools-version: 6.1 // The swift-tools-version declares the minimum version of Swift required to build this package. import PackageDescription let package = Package( name: "hello-swift", targets: [ // Targets are the basic building blocks of a package, defining a module or a test suite. // Targets can depend on other targets in this package and products from dependencies. .executableTarget( name: "hello-swift"), ] )
From the command line, you can install a package using swift package add-dependency <URL>
:
$ swift package add-dependency https://github.com/Alamofire/Alamofire --from 5.10.2 Updating package manifest at Package.swift... done.
In the above case, the package is installed via the GitHub repository for the popular networking package Alamofire.
You must also provide a version.
$ swift package add-dependency https://github.com/Alamofire/Alamofire error: must specify one of --exact, --branch, --revision, --from, or --up-to-next-minor-from
Once installed, your Package.swift
file will be updated:
// swift-tools-version: 6.1 // The swift-tools-version declares the minimum version of Swift required to build this package. import PackageDescription let package = Package( name: "hello-swift", dependencies: [ .package(url: "https://github.com/Alamofire/Alamofire", from: "5.10.2"), ], targets: [ // Targets are the basic building blocks of a package, defining a module or a test suite. // Targets can depend on other targets in this package and products from dependencies. .executableTarget( name: "hello-swift"), ] )
In order to use it, you must update the relevant targets
:
// swift-tools-version: 6.1 // The swift-tools-version declares the minimum version of Swift required to build this package. import PackageDescription let package = Package( name: "hello-swift", dependencies: [ .package(url: "https://github.com/Alamofire/Alamofire", from: "5.10.2"), ], targets: [ // Targets are the basic building blocks of a package, defining a module or a test suite. // Targets can depend on other targets in this package and products from dependencies. .executableTarget( name: "hello-swift", dependencies: [ "Alamofire" // 👈 add this line ]), ] )
We can see this in action by updating our main.swift
file:
// Sources/main.swift // The Swift Programming Language // https://docs.swift.org/swift-book import Alamofire import Foundation @main struct Hello { static func main() { AF.request("https://jsonplaceholder.typicode.com/todos/1") .responseString { response in print(response.value ?? "no data") CFRunLoopStop(CFRunLoopGetMain()) // stop loop when done } // keep the program alive until CFRunLoopStop is called CFRunLoopRun() } }
In the above code, we use `CFRunLoopRun`, `CFRunLoopStop` and `CFRunLoopGetMain` functions for writing backwards-compatible Alamofire code. If you want to use async/await, you can update your `Package.swift` platforms to target higher versions of MacOS (assuming you are on MacOS).
Let's see it in action:
$ swift run Fetching https://github.com/Alamofire/Alamofire Fetched https://github.com/Alamofire/Alamofire from cache (22.64s) Computing version for https://github.com/Alamofire/Alamofire Computed https://github.com/Alamofire/Alamofire at 5.10.2 (23.82s) Creating working copy for https://github.com/Alamofire/Alamofire Working copy of https://github.com/Alamofire/Alamofire resolved at 5.10.2 [1/1] Planning build Building for debugging... [52/52] Applying hello-swift Build of product 'hello-swift' complete! (6.15s) { "userId": 1, "id": 1, "title": "delectus aut autem", "completed": false }
Perfect, we got our response from an installed package.
Conclusion
This post was recounting some baby steps with initializing Swift packages. I will likely be writing a few tools for MacOS with this approach for learning more about the language while diving into iOS.
Links and Further Reading
Photo credit: johnnyb803
Swift from the command line
Introduction