Right now, we will go through an exercise to build a simple Swift package and learn about the important files and folders. We will also publish this package and consume it in another Swift package to show how we can publish packages and import them as dependencies. For our exercise, we will create a simple cat command-line tool which will concatenate and print the contents of the files specify relative to the current directory.
In order for us to do so we will first build a package called FileReader which will read and return the contents of the file. To build this Swift package, we need to do the following:
- Create a folder called FileReader (mkdir FileReader) and change directory (cd) into that folder
- Run Swift package init and it will generate files and folders for the package
Let's inspect the contents of the package. The following is the file and folder structure inside of FileReader:
~/W/FileReader $ tree .
.
├── Package.swift
├── README.md
├── Sources
│ └── FileReader
│ └── FileReader.swift
├── Tests
├── FileReaderTests
│ └── FileReaderTests.swift
└── LinuxMain.swift
4 directories, 5 files
- Package.swift: This file is where you describe meta-information about the package, including dependencies of the package.
- Sources: This is where you place your Swift code that will get built by the Swift package manager when you run the swift build command. It can contain multiple folders if you want to build multiple products or targets in your package.
- Tests: This is where you place your test files and that get run when swift test is run from the command line.
Now that we know the basic file and folder structure, we can start writing our Swift code to read files from disk inside of the FileReader.swift file. By default, it will contain boilerplate code which we can remove and replace with this:
import Foundation
class FileReader {
static func read(fileName: String) -> String? {
let fileManager = FileManager.default
let currentDirectoryURL = URL(fileURLWithPath:
fileManager.currentDirectoryPath)
let fileURL = currentDirectoryURL.appendingPathComponent(fileName)
return try? String(contentsOf: fileURL, encoding: .utf8)
}
}
In this file, we import Foundation, which is a standard library available in macOS and Linux and it provides us with the standard library to read from a file path using the FileManager. After that, we define the FileReader class and create one static function in it, called read, that takes a filename and this function will return the contents of the file if the file exists. The code inside the function does the following:
- Gets a singleton FileManager object:
let fileManager = FileManager.default
- Creates a URL pointing to the current directory. The current directory is set to the directory from which the OS Process using this library was called from:
let currentDirectoryURL = URL(fileURLWithPath: fileManager.currentDirectoryPath)
- Appends the filename passed to this function to the current directory:
let fileURL = currentDirectoryURL.appendingPathComponent(fileName)
- Tries to read contents of the file if it exists and return it:
return try? String(contentsOf: fileURL, encoding: .utf8)
Now that we have the code, we can build it using Swift build. To test that our code is working, we need to write a test for it and we can do so by taking the following steps:
- Editing the FileReaderTests.swift file and replacing the body of testExample function block with the following:
XCTAssertEqual(FileReader.read(fileName: "hello.txt"), "Hello World")
- Running the following command to create a hello.txt file in the root directory of the package with the contents Hello World:
printf "Hello World" > hello.txt
- Run the test for your package using the swift test command. You should see the test pass and print as such:
~/W/FileReader $ swift test
Compile Swift Module 'FileReaderTests' (1 sources)
Linking ./.build/x86_64-apple-macosx10.10/debug/FileReaderPackageTests.xctest/Contents/MacOS/FileReaderPackageTests
Test Suite 'All tests' started at 2017-09-29 12:14:57.278
Test Suite 'FileReaderPackageTests.xctest' started at 2017-09-29 12:14:57.278
Test Suite 'FileReaderTests' started at 2017-09-29 12:14:57.278
Test Case '-[FileReaderTests.FileReaderTests testExample]' started.
Test Case '-[FileReaderTests.FileReaderTests testExample]' passed (0.094 seconds).
Test Suite 'FileReaderTests' passed at 2017-09-29 12:14:57.372.
Executed 1 test, with 0 failures (0 unexpected) in 0.094 (0.094) seconds
Test Suite 'FileReaderPackageTests.xctest' passed at 2017-09-29 12:14:57.372.
Executed 1 test, with 0 failures (0 unexpected) in 0.094 (0.094) seconds
Test Suite 'All tests' passed at 2017-09-29 12:14:57.372.
Executed 1 test, with 0 failures (0 unexpected) in 0.094 (0.094) seconds
Now that we have a working Swift package, we can publish it.