This year's Advent of Code is around the corner and I was thinking of ways to organize my work environment for the different daily challenges.
This is just my take on this. I wanted to take advantage of the fantastic resources made recently available by Apple like the Swift Package Manager
and the Argument Parser
.
Of course SwiftUI could be a choice too ๐ค๐, but since the challenge are based on speed rather than UI, using executable scripts seems a better choice.
The Goal
My goal is to use Xcode to develop and also to be able to quickly use the terminal to launch my challenges for each day like this:
Last year I did the challenges in the Xcode Swift playgrounds. (till day 15: Advent-of-Code-2019)
I did encounter some problems though.
Playgrounds are much slower in executing the code on their main page, because they are doing quite a few checks in the background and while it is fun to use them for try out some code, they do not offer the true Swift speed.
This year I wanna try something different with the Swift package manager. This allows me to have an executable package and to pack all my files in it.
Also I could eventually make a library with the most used functions and import it separately.
Sounds like something new and exciting for me, so I can learn more about packages as well!
Creating an executable package
The way to do it is to create a new directory, call it like AdventOfCode2020
mkdir AdventOfCode2020
and cd
to it:
// your path will look different though
cd /Volumes/iOS/AdventOfCode2020
Next you need to init the package:
swift package init --type executable
This will create the necessary files, your folder structure will look like this:
โ tree
.
โโโ Package.swift
โโโ README.md
โโโ Sources
โ โโโ AdventOfCode2020
โ โโโ main.swift
โโโ Tests
โโโ AdventOfCode2020Tests
โ โโโ AdventOfCode2020Tests.swift
โ โโโ XCTestManifests.swift
โโโ LinuxMain.swift
4 directories, 6 files
Now build:
swift build
And run!
swift run AdventOfCode2020
// Hello, world!
This is the output of our executable because in our package we have a main.swift
file. This file is special, it is the only one file that is called automatically by the system and executed first. Also it allows to put commands on the top level!
this are the contents of the main.swift
file, a famous one liner: print("Hello, world!")
When I do run AdventOfCode2020
I execute the main swift file in the AdventOfCode2020
folder (under sources).
And I can do testing as well with swift tests
Creating folders for each day
It will be nice to just be free to execute the code for any day passing the arguments so I will create a Day1
folder containing another main.swift file to be executed like:
swift run Day1
My tree will look like this now:
โ tree
.
โโโ LICENSE
โโโ Package.swift
โโโ README.md
โโโ Sources
โ โโโ AdventOfCode2020
โ โ โโโ main.swift
โ โโโ Day1
โ โโโ main.swift
โโโ Tests
โโโ AdventOfCode2020Tests
โ โโโ AdventOfCode2020Tests.swift
โ โโโ XCTestManifests.swift
โโโ Day1Tests
โ โโโ Day1Tests.swift
โ โโโ XCTestManifests.swift
โโโ LinuxMain.swift
6 directories, 10 files
And to allow this I will need to modify the Package manifest file:
// swift-tools-version:5.3
// The swift-tools-version declares the minimum version of Swift required to build this package.
import PackageDescription
let package = Package(
name: "AdventOfCode2020",
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 this package depends on.
.target(
name: "AdventOfCode2020",
dependencies: []),
.testTarget(
name: "AdventOfCode2020Tests",
dependencies: ["AdventOfCode2020"]),
.target(
name: "Day1",
dependencies: []),
.testTarget(
name: "Day1Tests",
dependencies: ["AdventOfCode2020"]),
]
)
[...]
If I click on the package file, it opens in Xcode and there I can just easily select which target I want to run, as you see in the top left corner.
My script can receive arguments as input.
Parse Input Arguments
import ArgumentParser
// Define our parser.
struct Day1: ParsableCommand {
// Declare expected launch argument(s).
@Option(help: "Specify an Integer.")
var input: Int
func run() throws {
print("Running Day1 Challenge with input \(input)")
}
}
// Run the parser.
Hello.main()
I need to import the Argument Parser package and add it as a dependency as per Apple documentation:
let package = Package(
name: "AdventOfCode2020",
dependencies: [
.package(url: "https://github.com/apple/swift-argument-parser.git", from: "0.3.0"),
],
targets: [
.target(
name: "AdventOfCode2020",
dependencies: [.product(name: "ArgumentParser", package: "swift-argument-parser")]),
[...]
Now I run the target I get correctly a prompt, saying that my usage is not correct and I am expecting an Int!
I will add this clicking on my target and add it in arguments
This is effectively the same as writing in Terminal:
swift run Day1 --input 42
So the environment is now set up ๐
Sources
Advent Of Code 2020: https://adventofcode.com
Swift Package Manager: https://docs.swift.org/package-manager/
Five stars blog : fivestars.blog
Argument Parser: https://github.com/apple/swift-argument-parser/