Swift developers coming from Apple’s iOS and OS X
environments are use to using the Cocoa and Cocoa Touch frameworks however
these frameworks are not available in the Linux environment. When we develop Linux applications and
utilities with Swift we need to use the system libraries provided by the Linux
environment. In this post will look at
how we can use these system libraries with our Swift code to build useful
applications and utilities.
In this post we will look at the Glibc
module that Apple provides for us which includes the majority of the Linux
Standard Library. We will also look at
how we could create our own modules to add additional system libraries not
included in the Glibc module. To demonstrate the concepts discussed we will
conclude this post by building a simple command line utility that will list the
IP addresses of the device that it is run on.
Modules
A module in Swift is code that is distributed as a single
unit that can then be imported into other modules using Swift’s import keyword. Frameworks and applications are examples of
modules in Swift. In this post we are going to be looking at a special kind of
module that can be used to map system libraries so we can import and use them into
our Swift code.
The Linux port of Swift comes with a predefined module
named Glibc that contains most
of the Linux standard library however there are numerous headers that have not
been imported in it. This module is
similar to the Darwin module on Apple platforms. Lets start off by looking at tthe Glibc module and then we will look at
defining our own modules to map other system libraries that we may need.
To see what headers are defined in the Glibc module view the module.map file located in the usr/lib/swift/glibc directory of your
Swift installation. Don’t worry if you
do not fully understand the format of this file at this time, we will be
looking at how to create module.map
files later on in this post. For right
now, knowing that any header that is defined in this file will be included
simply by importing the Glibc
module is enough.
In my previous post, Swift for Linux part 1,
we saw an example of how to use the Glibc
module. In that example we created an
extension to the Array type that would randomly select an element from the
array. We used the random()
function provided by the system to generate the random number in the extension. Lets take a look at the code for this example
again so we can see how it worked. The
following code shows the Array extension:
import Foundation
import Glibc
extension Array {
func getRandomElement() -> Element {
let index = Int(random() % self.count)
return self[index]
}
}
In this example the code starts off with two import statements. The first import statement imports the Foundation
framework. The Foundation
framework defines the basic functionality that is needed for most
applications. Most if not all of your Swift
source files will need to import the Foundation
framework.
The second import
statement imports the Glibc
framework. This import statement allows
us to use the system libraries that are defined in the Glibc
framework. If we tried to build the code
without importing the Glibc
framework we would receive the following error.
/xxx/xxx/xxx/xxx/MakeFtpFile/Sources/ArrayExtension.swift:6:19:
error: use of unresolved identifier 'random'
let index = Int(random() % self.count)
^~~~~~
What this error tells us is the compiler does not know
anything about the random()
function. If we look at the man page for
the random() function
(command: man random) we see that
we need to import the stdlib.h
header if we want to use this function.
If we look at the headers that are imported in the Glibc
framework we will see that the Glibc
framework does include the stdlib.h
header. By importing the Glibc Framework we are essentially
importing all of the header files defined within it therefore we are importing
the stdlib.h header which
defines the random() function. This will allows us to use the random() function within our code.
If you are new to developing applications and utilities in
the Linux environment you will want to get use to using the Linux man pages to
retrieve information about the system libraries and the functionality they
provide. These man pages will give you a
wealth of knowledge about the functions that you are using.
Earlier we mentioned that the Glibc
framework contains most of the Linux standard library so what do we do if we
want include libraries that are not in the Glibc
framework? These libraries could be part
of the Linux standard library that are not currently defined in the Glibc framework or libraries that are
not part of the Linux standard library itself.
Lets look at how to include these libraries and also how to use the
functionality they provide in our code by creating a custom module.
Creating a custom module
To create a custom module we will begin by creating a directory
to put the files for the module in. This
will be the module’s main directory.
Within this directory we will need two files. The first is an empty file named Package.swift and the second is named module.modulemap.
Within the module.modulemap
file we will define the headers we want to import and the libraries that
contain the functionality defined in the headers. The example below shows the format of the
module.modulemap file
module CMyModule [system] {
header
"/usr/include/mylibheader.h"
link
"mylib"
export *
}
The first line defines the name for our module. This name is what we will import in our Swift
files. In this sample the module’s name
is CMyModule. The next line defines the full path to the
header file that we want to import. The
third line tells the compiler that the functionality defined in the header can
be found in the mylib library so we will
need to link it. The last line says to
export all of the functionality.
The Swift package manager uses git and git tags to manage
packages and modules therefore once we create both files we will want to create
a git repository for our module. To do
this we run the following commands in the main directory for the module.
git init
git add .
git commit -m "Initial Import"
git tag 0.1.0
Before we show how to use a module, lets go ahead and
create the module needed for our example.
Creating the Cifaddrs module
In this post we will be creating a utility that will list
the network addresses of the device it is running on. For this utility we will use the getifaddrs() function. The man page for the getiffaddrs()
function shows that we will need to import sys/types.h
and ifaddrs.h header
files. In addition to these two headers
we will also need to import the netdb.h,
sys/socket.h and arpa/inet.h
headers for other functions that we will be using in our code.
Since some of the headers that we need for our project are
not defined in the Glibc framework we will create our own module so we can
import them. Lets begin by creating a
directory name Cifaddrs and the two
files that we need:
mkdir Cifaddrs
cd Cifaddrs
touch Package.swift
touch module.modulemap
Now we will need to define the five headers in the module.modulemap file. To do this we would put the following code
into module.modulemap file.
module Cifaddrs [system]
{
module types {
header "/usr/include/x86_64-linux-gnu/sys/types.h"
export *
}
module ifaddrs {
header "/usr/include/ifaddrs.h"
export *
}
module Socket {
header "/usr/include/x86_64-linux-gnu/sys/socket.h"
export *
}
module inet {
header "/usr/include/arpa/inet.h"
export *
}
module netdb {
header "/usr/include/netdb.h"
export *
}
}
Now we need to create our git repository by running the
following commands in the module’s main directory.
git init
git add .
git commit -m "Initial
Import"
git tag 0.1.0
Since the swift package manager uses the tag for
versioning, you will want to update the tag whenever you update code. To make it easier to create the initial
structure for the module, I created a shell scripted named createmodule.sh. This script is very similar to the createproject.sh that I created in my
last post to create the structure for a project. The following is the code for the createmodule.sh script.
#!/bin/bash
#title:
createmodule.sh
#author: Jon
Hoffman
#description: Creates
the directories and files need for a Swift module
#date: 012816
#version: 1.0
#usage:
createmodule.sh {module name} {optional: dir name}
MODULENAME=""
DIRNAME=""
PACKAGEFILENAME="Package.swift"
MODULEFILENAME="module.modulemap"
#Check to make sure at least one command
#line arg is present otherwise exit script
if [ $# -le 0 ]; then
echo "Usage:
creatproject {Name for Module} {Optional directory name}"
exit 1
fi
#Assign the value of the first command line arg to the
module name
#if a second command line arg is present assign that value
to the
#the directory name otheerwise use the module name
MODULENAME=$1
if [ "$1" != "" ]; then
DIRNAME=$1
else
DIRNAME=$MODULENAME
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 structue and create the neccessary files
mkdir -p $DIRNAME
cd $DIRNAME
touch $PACKAGEFILENAME
touch $MODULEFILENAME
echo "module $MODULENAME [system] {" >>
$MODULEFILENAME
echo "" >> $MODULEFILENAME
echo "}" >> $MODULEFILENAME
git init
git add .
git commit -m "Initial Import"
git tag 0.0.1
The createmodule.sh
script takes one required and one optional command line argument. The first (required) command line argument is
the name of the module and is used to create the module.modulemap file. The
second (optional) command line argument is the name for the module
directory. If the second command line
argument is not present then we use the name of the module (first command line
argument) for the directory name. The
following examples show how we would use the createmodule.sh script to create a module named Clib in a directory named Clib.
./createmodule.sh Clib
The previous command would create a directory named Clib. It would also create the Package.swift and module.modulemap
files. The following code shows what the
newly created module.modulemap file
would look like.
import PackageDescription
module Clib [system] {
}
We used the module name (first command line argument) to
define the module name in this module.modulemap
file. You can find the code for the
createmodule.sh file on my Scripts for Swift Linux development github page.
Now that we have created our module, lets see how to use
it in a project.
Using the Cifaddrs module
Now that we have our module created, lets look at how we
would use it in a project. The first
thing we will want to do is to create the project. For this I will use my createproject.sh
script like this:
./createproject.sh getifaddrs
This will create the directory structure and files needed
to start the project with. To tell the
compiler to use our newly created module we will need to add a dependency to
the Package.swift file. We would add this dependency as shown with
the following code:
import PackageDescription
let package = Package(
name: "getifaddrs",
dependencies:
[.Package(url: "../Cifaddrs",
majorVersion: 0, minor: 1)]
)
The url
defines the path to the module. This can
be the full file system path as shown in our example or an Internet path to a
github repository. We can also define
multiple dependencies by separating the packages by a comma as shown here:
let package = Package(
name: "getifaddrs",
dependencies:
[.Package(url: "../modOne",
majorVersion: 0, minor: 1),
dependencies: [.Package(url: "../modTwo", majorVersion: 0, minor: 1)]
)
We are now ready to use the libraries defined in the
module within our application. The
following code shows the main.swift file that will retrieve the list of IP
addresses and print them out.
import Foundation
import Cifaddrs
// Get list of all interfaces
var ifaddr : UnsafeMutablePointer<ifaddrs>
= nil
if
getifaddrs(&ifaddr) == 0 {
// Loop through all interfaces
var ptr = ifaddr
while (ptr != nil)
{
// Get address and interface name
var addr = ptr.memory.ifa_addr.memory
var
ifname = String.fromCString(ptr.memory.ifa_name)
// If addr is IPv4 or IPv6
if addr.sa_family == UInt16(AF_INET)
||
addr.sa_family == UInt16(AF_INET6) {
// Convert interface address to a string and print it
var ad = [CChar](count: Int(NI_MAXHOST),
repeatedValue: 0)
if (getnameinfo(&addr, socklen_t(32), &ad,
socklen_t(ad.count), nil, socklen_t(0),
NI_NUMERICHOST) == 0) {
if let address
= String.fromCString(ad) {
print("\(ifname): \(address)")
}
}
}
ptr =
ptr.memory.ifa_next
}
freeifaddrs(ifaddr)
}
Notice that in the second line we import the Cifaddrs module using the import keyword. This
essentially imports all of the headers that are defined in the Cifaddrs modeul.modulemap
file.
The code is commented so you can see what is going on but
I do want to point out a couple of items that will help you when it comes to
use Linux system libraries with swift.
The first item is how to use C pointers.
The following code show how we would use the getifaddrs()
function in normal C code:
struct ifaddrs *ifap;
getifaddrs (&ifap);
In this code we create a pointer to an ifaddrs structure. In Swift we would write this same code like
this:
var ifap :
UnsafeMutablePointer<ifaddrs> = nil
if getifaddrs(&ifap)
Notice in Swift we use the UnsafeMutablePointer
structure to declare a pointer to an object type in memory. In this case the object type is the ifaddrs structure.
The other item that I want to point is how we are
accessing the information within the ifaddrs
structure using pointers. In C we would
access the information like this:
ifa->ifa_addr
In Swift we access the information like this:
var addr = ifa.memory.ifa_addr.memory
It took me a little bit of time to wrap my brain around
this. Having a pretty good C background
this line of code seemed just wrong to me but once I really wrapped my brain around
the basic concepts here it really made since.
Basically in Swift we want to avoid using pointers if we can so there
isn’t a simple interface to use them.
Notice in our Swift line of code we have ifa.memory.ifa_addr.memory. The ifa.memory
part of this line gets the value in memory that the ifa
pointer is pointing too. Then the second
part of this line ifa_addr.memory
gets the value in memory that the ifa_addr
pointer is pointing too.
If we wanted to get the pointer rather than the actual
value, we would use this line instead:
var addr = ifa.memory.ifa_addr
In this line the addr
variable would contain an UnsafeMutablePointer.
It does take a little bit of work to include the Linux
system libraries in our Swift code but overall I think Apple did a great job
making it as easy as possible while avoid a lot of the complexity of C. I will hopefully writing more posts on how to
use the Swift port for Linux.
Nice tutorial, but i'm getting unresolved identifier for all the constants: AF_INET, AF_INET6, NI_MAXHOST, NI_NUMERICHOST - any ideas why it can't find or doesn't like c constants?
ReplyDeleteThanks for providing this informative information you may also refer.
ReplyDeletehttp://www.s4techno.com/blog/2016/07/12/everything-about-ftp/