Skip to content

Commit a2ae2c9

Browse files
authored
[docs] Requiring System Libraries with .systemLibrary targets (#6634)
1 parent 94cbd45 commit a2ae2c9

File tree

1 file changed

+109
-73
lines changed

1 file changed

+109
-73
lines changed

Documentation/Usage.md

Lines changed: 109 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -116,14 +116,14 @@ https://github.com/apple/example-package-fisheryates
116116

117117
## Requiring System Libraries
118118

119-
You can link against system libraries using the package manager. To do so, there
120-
needs to be a special package for each system library that contains a modulemap
121-
for that library. Such a wrapper package does not contain any code of its own.
119+
You can link against system libraries using the package manager. To do so, you'll
120+
need to add a special `target` of type `.systemLibrary`, and a `module.modulemap`
121+
for each system library you're using.
122122

123-
Let's see an example of using [libgit2](https://libgit2.github.com) from an
124-
executable.
123+
Let's see an example of adding [libgit2](https://libgit2.github.com) as a
124+
dependency to an executable target.
125125

126-
First, create a directory called `example`, and initialize it as a package that
126+
Create a directory called `example`, and initialize it as a package that
127127
builds an executable:
128128

129129
$ mkdir example
@@ -140,87 +140,124 @@ print(options)
140140
```
141141

142142
To `import Clibgit`, the package manager requires that the libgit2 library has
143-
been installed by a system packager (eg. `apt`, `brew`, `yum`, etc.). The
143+
been installed by a system packager (eg. `apt`, `brew`, `yum`, `nuget`, etc.). The
144144
following files from the libgit2 system-package are of interest:
145145

146146
/usr/local/lib/libgit2.dylib # .so on Linux
147147
/usr/local/include/git2.h
148148

149-
Swift packages that provide module maps for system libraries are handled
150-
differently from regular Swift packages.
149+
**Note:** the system library may be located elsewhere on your system, such as:
150+
- `/usr/`, or `/opt/homebrew/` if you're using Homebrew on an Apple Silicon Mac.
151+
- `C:\vcpkg\installed\x64-windows\include` on Windows, if you're using `vcpkg`.
152+
On most Unix-like systems, you can use `pkg-config` to lookup where a library is installed:
151153

152-
Note that the system library may be located elsewhere on your system, such as
153-
`/usr/` rather than `/usr/local/`.
154+
example$ pkg-config --cflags libgit2
155+
-I/usr/local/libgit2/1.6.4/include
154156

155-
Create a directory called `Clibgit` next to the `example` directory and
156-
initialize it as a package that builds a system module:
157157

158-
example$ cd ..
159-
$ mkdir Clibgit
160-
$ cd Clibgit
161-
Clibgit$ swift package init --type empty
162-
163-
This creates a `Package.swift` file in the directory.
164-
Edit `Package.swift` and add `pkgConfig` parameter:
158+
**First, let's define the `target` in the package description**:
165159

166160
```swift
167-
// swift-tools-version:5.3
161+
// swift-tools-version: 5.8
162+
// The swift-tools-version declares the minimum version of Swift required to build this package.
168163

169164
import PackageDescription
170165

171166
let package = Package(
172-
name: "Clibgit",
173-
pkgConfig: "libgit2"
167+
name: "example",
168+
targets: [
169+
// systemLibrary is a special type of build target that wraps a system library
170+
// in a target that other targets can require as their depencency.
171+
.systemLibrary(
172+
name: "Clibgit",
173+
pkgConfig: "libgit2",
174+
providers: [
175+
.brew(["libgit2"]),
176+
.apt(["libgit2-dev"])
177+
]
178+
)
179+
]
174180
)
181+
175182
```
176183

177-
The `pkgConfig` parameter helps SwiftPM in figuring out the include and library
178-
search paths for the system library. Note: If you don't want to use the `pkgConfig`
179-
parameter you can pass the path of a directory containing the library using the
180-
`-L` flag in commandline when building your app:
184+
**Note:** For Windows-only packages `pkgConfig` should be omitted as
185+
`pkg-config` is not expected to be available. If you don't want to use the
186+
`pkgConfig` parameter you can pass the path of a directory containing the
187+
library using the `-L` flag in the command line when building your package
188+
instead.
181189

182190
example$ swift build -Xlinker -L/usr/local/lib/
183191

184-
Create a `module.modulemap` file so it consists of the following:
192+
Next, create a directory `Sources/Clibgit` in your `example` project, and
193+
add a `module.modulemap` and the header file to it:
185194

186195
module Clibgit [system] {
187-
header "/usr/local/include/git2.h"
196+
header "git2.h"
188197
link "git2"
189198
export *
190199
}
191200

201+
The header file should look like this:
202+
203+
```c
204+
// git2.h
205+
#pragma once
206+
#include <git2.h>
207+
```
208+
209+
**Note:** Alternatively, you can provide an absolute path to `git2.h` provided
210+
by the library in the `modile.modulemap`. However, doing so might break
211+
cross-platform compatibility of your project.
212+
192213
> The convention we hope the community will adopt is to prefix such modules
193214
> with `C` and to camelcase the modules as per Swift module name conventions.
194215
> Then the community is free to name another module simply `libgit` which
195216
> contains more “Swifty” function wrappers around the raw C interface.
196217
197-
Packages are Git repositories, tagged with semantic versions, containing a
198-
`Package.swift` file at their root. Initializing the package created a
199-
`Package.swift` file, but to make it a usable package we need to initialize a
200-
Git repository with at least one version tag:
218+
The `example` directory structure should look like this now:
201219

202-
Clibgit$ git init
203-
Clibgit$ git add .
204-
Clibgit$ git commit -m "Initial Commit"
205-
Clibgit$ git tag 1.0.0
220+
.
221+
├── Package.swift
222+
└── Sources
223+
├── Clibgit
224+
│   ├── git2.h
225+
│   └── module.modulemap
226+
└── main.swift
206227

207-
Now to use the Clibgit package we must declare our dependency in our example
208-
app’s `Package.swift`:
228+
At this point, your system library target is fully defined, and you can now use
229+
that target as a dependency in other targets in your `Package.swift`, like this:
209230

210231
```swift
232+
211233
import PackageDescription
212234

213235
let package = Package(
214236
name: "example",
215-
dependencies: [
216-
.package(url: "../Clibgit", from: "1.0.0")
237+
targets: [
238+
.executableTarget(
239+
name: "example",
240+
241+
// example executable requires "Clibgit" target as its dependency.
242+
// It's a systemLibrary target defined below.
243+
dependencies: ["Clibgit"],
244+
path: "Sources"
245+
),
246+
247+
// systemLibrary is a special type of build target that wraps a system library
248+
// in a target that other targets can require as their depencency.
249+
.systemLibrary(
250+
name: "Clibgit",
251+
pkgConfig: "libgit2",
252+
providers: [
253+
.brew(["libgit2"]),
254+
.apt(["libgit2-dev"])
255+
]
256+
)
217257
]
218258
)
219-
```
220259

221-
Here we used a relative URL to speed up initial development. If you push your
222-
module map package to a public repository you must change the above URL
223-
reference so that it is a full, qualified Git URL.
260+
```
224261

225262
Now if we type `swift build` in our example app directory we will create an
226263
executable:
@@ -231,6 +268,8 @@ executable:
231268
git_repository_init_options(version: 0, flags: 0, mode: 0, workdir_path: nil, description: nil, template_path: nil, initial_head: nil, origin_url: nil)
232269
example$
233270

271+
### Requiring a System Library Without `pkg-config`
272+
234273
Let’s see another example of using [IJG’s JPEG library](http://www.ijg.org)
235274
from an executable, which has some caveats.
236275

@@ -250,22 +289,16 @@ let jpegData = jpeg_common_struct()
250289
print(jpegData)
251290
```
252291

253-
Install JPEG library using a system packager, e.g, `$ brew install jpeg`
254-
255-
Create a directory called `CJPEG` next to the `example` directory and
256-
initialize it as a package that builds a system module:
257-
258-
example$ cd ..
259-
$ mkdir CJPEG
260-
$ cd CJPEG
261-
CJPEG$ swift package init --type empty
292+
Install the JPEG library, on macOS you can use Homebrew package manager: `brew install jpeg`.
293+
`jpeg` is a keg-only formula, meaning it won't be linked to `/usr/local/lib`,
294+
and you'll have to link it manually at build time.
262295

263-
This creates `Package.swift` file in the directory.
264-
Create a `module.modulemap` file so it consists of the following:
296+
Just like in the previous example, run `mkdir Sources/CJPEG` and add the
297+
following `module.modulemap`:
265298

266299
module CJPEG [system] {
267300
header "shim.h"
268-
header "/usr/local/include/jpeglib.h"
301+
header "/usr/local/opt/jpeg/include/jpeglib.h"
269302
link "jpeg"
270303
export *
271304
}
@@ -279,31 +312,34 @@ This is because `jpeglib.h` is not a correct module, that is, it does not contai
279312
the required line `#include <stdio.h>`. Alternatively, you can add `#include <stdio.h>`
280313
to the top of jpeglib.h to avoid creating the `shim.h` file.
281314

282-
Create a Git repository and tag it:
283-
284-
CJPEG$ git init
285-
CJPEG$ git add .
286-
CJPEG$ git commit -m "Initial Commit"
287-
CJPEG$ git tag 1.0.0
288-
289315
Now to use the CJPEG package we must declare our dependency in our example
290316
app’s `Package.swift`:
291317

292318
```swift
319+
293320
import PackageDescription
294321

295322
let package = Package(
296323
name: "example",
297-
dependencies: [
298-
.package(url: "../CJPEG", from: "1.0.0")
324+
targets: [
325+
.executableTarget(
326+
name: "example",
327+
dependencies: ["CJPEG"],
328+
path: "Sources"
329+
),
330+
.systemLibrary(
331+
name: "CJPEG",
332+
providers: [
333+
.brew(["jpeg"])
334+
])
299335
]
300336
)
301337
```
302338

303339
Now if we type `swift build` in our example app directory we will create an
304340
executable:
305341

306-
example$ swift build -Xlinker -L/usr/local/lib/
342+
example$ swift build -Xlinker -L/usr/local/jpeg/lib
307343
308344
example$ .build/debug/example
309345
jpeg_common_struct(err: nil, mem: nil, progress: nil, client_data: nil, is_decompressor: 0, global_state: 0)
@@ -353,9 +389,9 @@ we hope that system libraries and system packagers will provide module maps and
353389
thus this component of the package manager will become redundant.
354390

355391
*Notably* the above steps will not work if you installed JPEG and JasPer with
356-
[Homebrew](http://brew.sh) since the files will be installed to `/usr/local`. For
357-
now adapt the paths, but as said, we plan to support basic relocations like
358-
these.
392+
[Homebrew](http://brew.sh) since the files will be installed to `/usr/local` on
393+
Intel Macs, or /opt/homebrew on Apple silicon Macs. For now adapt the paths,
394+
but as said, we plan to support basic relocations like these.
359395

360396
### Module Map Versioning
361397

@@ -471,9 +507,9 @@ In case the current Swift version doesn't match any version-specific manifest,
471507
the package manager will pick the manifest with the most compatible tools
472508
version. For example, if there are three manifests:
473509

474-
`Package.swift` (tools version 3.0)
475-
`[email protected]` (tools version 4.0)
476-
`[email protected]` (tools version 4.2)
510+
`Package.swift` (tools version 3.0)
511+
`[email protected]` (tools version 4.0)
512+
`[email protected]` (tools version 4.2)
477513

478514
The package manager will pick `Package.swift` on Swift 3, `[email protected]` on
479515
Swift 4, and `[email protected]` on Swift 4.2 and above because its tools

0 commit comments

Comments
 (0)