At the end of last year Apple open sourced Swift and
released a port for the Linux operating system.
At the time of the release I really wanted to try the Linux port of
Swift however I was right in the middle of writing my new book on Protocol-Oriented
programming so I was unable to really spend any time with it. Now that I am finishing up the new book, I am
able to spend some quality time with the Swift Linux port. These next few posts will show what I have discovered.
In this first post we will look at several examples that
will demonstrate how to write and build applications with the Swift port for
Linux. We will also create a shell
scripts that we can use to create the directory structure and minimum files
needed to use Swift’s package manager to build our applications.
We will not go over installing Swift on Linux because
Apple has very good documentation on how to do this. You can find the documentation on the
Swift.org
Using Swiftc
Once we install Swift on our system and set up the path we
should be able to run the Swift compile.
To run the compiler we would use the swiftc command. To see how we could use swiftc
lets create a file named helloWorld.swift
and put the following code in it.
import Foundation
print(“Hello World”)
Now run the command swiftc helloWorld.swift. If all went well, we would have an
application named helloWorld that we
can run like this: ./helloWorld. This application will (obviously) print Hello World to the console.
There are numerous options with the swiftc command and we
can see them by using the –help option like this: swiftc –help.
Setting up the directory structure for an application
We could use the Swift command line compiler to compile
our applications but if we had multiple files and/or dependencies, our compile
command could get very complicated and hard to maintain. Anyone that has used Make files or other
similar utilities to build C projects can verify that building applications in
this manner can get pretty complicated.
Apple has given us a much better approach for developing
applications and modules. This approach
does require us to set up a specific directory structure and also a manifest
file named Package.swift.
The following diagram shows how we would set up the
directory structure and also the required files:
{project dir}
|
|----
Package.swift {file}
|
|------ Sources {directory}
|
|----main.swift {file}
This diagram shows that we have one subdirectory below our
main project directory named Sources. It also shows that we need two files. The first file is the Package.swift file which is located in the main project directory
and the main.swift file which is
located in the Sources directory.
The Package.swift
file is a manifest file that tells the compiler about our project and any
dependencies that it may have. At
minimum we need to define a name for our project. The following example shows the minimum
manifest file that simply defines the name of the project which is HelloWorld.
import PackageDescription
let package = Package(
name:
"HelloWorld"
)
The main.swift file
is a special file that is the entry point for our application. It is also the only file that is allowed to
have top-level code in it. Top-level
code is code that is not encapsulated in a function or type.
To simplify the process of starting a project I created a
shell script named createPoject.sh that will create the
directory structure, manifest file and main swift file for a project. The Package.swift
and main.swift files are created
with the minimum code needed for our project.
You can find this and other scripts that I use with my Swift Linux development
on my Scripts for Linux development github page. The following
shows the code for the createproject.sh
script.
#!/bin/bash
PROJECTNAME=""
DIRNAME=""
PACKAGEFILENAME="Package.swift"
MAINFILENAME="main.swift"
SOURCESDIRNAME="Sources"
#Check to make sure at least one command
#line arg is present otherwise exit script
if [ $# -le 0 ]; then
echo "Usage:
creatproject {Name for project} {Optional directory name}"
exit 1
fi
#Assign the value of the first command line arg to the
project name
#if a second command line arg is present assign that value
to the
#the directory name otheerwise use the project name
PROJECTNAME=$1
if [ "$1" != "" ]; then
DIRNAME=$1
else
DIRNAME=$PROJECTNAME
fi
#Check to see if the directory exists and if so display an error
#and exit
if [ -d "$DIRNAME" ]; then
echo
"Directory already exists, please choose another name"
exit 1
fi
#Make the directory structure
mkdir -p $DIRNAME/$SOURCESDIRNAME
#Change to the project's directory and create the
Package.swift file
cd $DIRNAME
touch $PACKAGEFILENAME
echo "import PackageDescription" >>
$PACKAGEFILENAME
echo "" >> $PACKAGEFILENAME
echo "let package = Package(" >>
$PACKAGEFILENAME
echo "
name:
\"$PROJECTNAME\"" >> $PACKAGEFILENAME
echo ")" >> $PACKAGEFILENAME
#Change to the Sources directory and create the main.swift
file
cd $SOURCESDIRNAME
touch $MAINFILENAME
echo "import Foundation" >> $MAINFILENAME
echo ""
>> $MAINFILENAME
echo "print(\"Hello from Swift\")"
>> $MAINFILENAME
#Done
The createPoject.sh
script takes one required and one optional command line argument. The first (required) command line argument is
the name of the project and is used to create the Package.swift file. The
second (optional) command line argument is the name for the project directory. If the second command line argument is not
present then we use the name of the project (first command line argument) for
the directory name. The following
examples show how we would use the createPoject.sh
script to create a project named Hello
in a directory named Hello.
./createproject Hello
The previous command would create a directory named Hello
and also the Sources subdirectory. It would also create the Package.swift and main.swift
files. The following code shows what the
newly created Package.swift file
would look like.
import PackageDescription
let package = Package(
name: "Hello"
)
We used the project name (first command line argument) to
define the package name in this
Package.swift
file. The newly created main.swift file would look like this.
import Foundation
print("Hello from Swift")
As we mentioned earlier, the main.swift file is the entry point for our application therefore
the code that is in this file is run when our application starts.
If we wanted the main project directory to have a
different name from our project name then we would use the second command line
argument. This next example shows how we would create a project named Hello in a directory named HelloDirectory.
./createproject Hello HelloDirectory
The createPoject.sh script actually creates
a full project that can be built as is.
Lets see how we would build this new project.
Building a project
To build our project we would use the following command; swift
build. To see the options
with this command we would use the --help option like this: swift build --help. Notice the two dashes, all of the other help
options for the other swift commands (IE:
swiftc and swift commands)
use the single dash.
Let build our project that we created in the last
section. Change to the project directory
that was created by the createPoject.sh
script and then run swift build. If all went well you should see output
similar to this:
Compiling Swift Module 'hello' (1 sources)
Linking Executable:
.build/debug/hello
If we see this output, we will have an executable named hello
in the .build/debug directory. You can execute it like this: .build/debug/hello
Creating a project with multiple files
Recently I had the need to create a number of files that
were of a specific size (I was testing sftp transfer speeds over different
connection types). I decided that this
would be a good project to do in Swift.
The requirements that I had for this project was to create a number of
files that contained random characters and were of specific sizes.
What I decided to do was to create an array that contained
each character of the alphabet and then randomly select a character from the
array until the file was the size I needed.
I also decided that I would create an extension to the array type which
would randomly select an element from the array. I created a file name ArrayExtension.swift in the Sources
directory that contained the following code:
import Foundation
import Glibc
extension Array {
func
getRandomElement() -> Element {
let index =
Int(random() % self.count)
return
self[index]
}
}
Don’t worry too much about how this code works at this
time. My next post will be about using C
libraries with Swift and will explain more about modules and using C functions. You can however see that generating a random
number with the Swift port for Linux is a little different than with Swift for
OS X or iOS.
Now in our main.swift
file we can put the following code:
import Foundation
import Glibc
let num = 1024
var str = ""
let alpha =
["a","b","c","d","e","f","g","h","i","j","k",
"l","m","n","o","p","q","r","s","t","u","v","w","x","y","z"]
srandom(UInt32(NSDate().timeIntervalSince1970))
for i in 0..<num {
str +=
alpha.getRandomElement()
}
print(str)
var filename = "test\(num)file.txt"
do {
try str.writeToFile(filename,
atomically: true,encoding:
NSUTF8StringEncoding)
} catch let e {
print("Error:
\(e)")
}
Once again, we are not worried about how this code
works. We are mainly focused on creating
and building a project with multiple files.
Now we should have two files in our Sources
directory named main.swift and ArrayExtension.swift. Now if we go back to the main project
directory (the one with the Package.swift
file) we can run the swift build command and our project
should compile to an executable.
What we just saw is the swift build command
will compile all of the files in the Sources directory and include them in our
project. This is a lot easier than
creating complex Make files with C. I
would recommend that unless there is a specific requirement to use the swiftc
command that you use the swift build command as we saw in
this post.
Developers that are use to using Swift to build iOS and/or
OS X applications are also use to using the Cocoa and Cocoa Touch frameworks
however these frameworks are not present in the Linux environment. Instead we need to use the C libraries that
are provided with Linux. In my next post
I will show how to create modules that will expose those libraries so we can
use them with our Swift applications.
Hi ,I tried with your help but while building the application by "swift build" I am getting following error "error: no such file or directory: 'build'" .What should I do? .
ReplyDeleteI believe you are using the January 25 build (swift-2.2-SNAPSHOT-2016-01-25-a-ubuntu15.10). I had several issues and I believe that was one of them with the January 25 built. Try using the January 11th build instead.
DeleteCan I create subdirectory inside sources to organize code better. How to access functions written in one subdirectory to another?
ReplyDeleteYes you can and you do not need to do anything special to access the functions within those files
DeleteThis comment has been removed by the author.
DeleteThanks for your quick reply. I am not very clear how it works. For example if I build a program with following structures, it is absolutely fine
Deleteswift
|-- Package.swift
`-- src
|----- hello.swift
`-- main.swift
But if I change the structure like
swift
|-- Package.swift
`-- src
|-- main.swift
`-- subsrc
`-- hello.swift
I get following error
"error: the package has an unsupported layout, unexpected source file(s) found: /home/msspad249/suvendu/project/swift/src/main.swift
fix: move the file(s) inside a module"
Now if I move main to a module called 'main' like
|-- Package.swift
`-- src
|-- main
| `-- main.swift
`-- subsrc
`-- hello.swift
then I am getting complicated error like
Compile Swift Module 'main' (1 sources)
Compile Swift Module 'subsrc' (1 sources)
:0: error: PCH was compiled with module cache path '/home/msspad249/suvendu/project/orca/.build/debug/ModuleCache/2UK7KAIE0U9KG', but the path is currently '/home/msspad249/suvendu/project/swift/.build/debug/ModuleCache/2UK7KAIE0U9KG'
:0: error: missing required module 'SwiftShims'
:0: error: PCH was compiled with module cache path '/home/msspad249/suvendu/project/orca/.build/debug/ModuleCache/2UK7KAIE0U9KG', but the path is currently '/home/msspad249/suvendu/project/swift/.build/debug/ModuleCache/2UK7KAIE0U9KG'
:0: error: missing required module 'SwiftShims'
:0: error: build had 2 command failures
error: exit(1): /opt/swift/usr/bin/swift-build-tool -f /home/msspad249/suvendu/project/swift/.build/debug.yaml
This comment has been removed by the author.
DeleteThe directory for your source files should be named "Sources" but it looks like you have it named "src"
DeleteThis comment has been removed by the author.
ReplyDelete