Skip to content

Commit 2a2a824

Browse files
committed
Implement initial pkgconfig support
1 parent b313d3c commit 2a2a824

File tree

8 files changed

+195
-9
lines changed

8 files changed

+195
-9
lines changed

Sources/Build/Buildable.swift

+21
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,27 @@ extension Module: Buildable {
5959
}
6060
}
6161

62+
extension SwiftModule {
63+
var pkgConfigArgs: [String] {
64+
return recursiveDependencies.flatMap { module -> [String] in
65+
guard case let module as CModule = module, let pkgConfigName = module.pkgConfig else {
66+
return []
67+
}
68+
guard var pkgConfig = try? PkgConfig(name: pkgConfigName) else {
69+
// .pc not found
70+
return []
71+
}
72+
do {
73+
try pkgConfig.load()
74+
}
75+
catch {
76+
77+
}
78+
return pkgConfig.cFlags + pkgConfig.libs
79+
}
80+
}
81+
}
82+
6283
extension Product: Buildable {
6384
var isTest: Bool {
6485
if case .Test = type {

Sources/Build/Command.compile(SwiftModule).swift

+2-2
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@ import Utility
1414
extension Command {
1515
static func compile(swiftModule module: SwiftModule, configuration conf: Configuration, prefix: String, otherArgs: [String], SWIFT_EXEC: String) throws -> (Command, [Command]) {
1616

17-
let otherArgs = otherArgs + module.XccFlags(prefix)
18-
17+
let otherArgs = otherArgs + module.XccFlags(prefix) + module.pkgConfigArgs
18+
1919
func cmd(_ tool: ToolProtocol) -> Command {
2020
return Command(node: module.targetName, tool: tool)
2121
}

Sources/Build/Command.link().swift

+5-1
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,11 @@ extension Command {
8181
case .Executable:
8282
args.append("-emit-executable")
8383
}
84-
84+
85+
for module in product.modules {
86+
args += module.pkgConfigArgs
87+
}
88+
8589
args += objects
8690

8791
if case .Library(.Static) = product.type {

Sources/Build/PkgConfig.swift

+147
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
/*
2+
This source file is part of the Swift.org open source project
3+
4+
Copyright 2015 - 2016 Apple Inc. and the Swift project authors
5+
Licensed under Apache License v2.0 with Runtime Library Exception
6+
7+
See http://swift.org/LICENSE.txt for license information
8+
See http://swift.org/CONTRIBUTORS.txt for Swift project authors
9+
*/
10+
11+
import Utility
12+
13+
enum PkgConfigError: ErrorProtocol {
14+
case CouldNotFindConfigFile
15+
}
16+
17+
struct PkgConfig {
18+
static let searchPaths = ["/usr/local/lib/pkgconfig"]
19+
20+
let name: String
21+
let pcFile: String
22+
private var parser: PkgConfigParser
23+
24+
init(name: String) throws {
25+
self.name = name
26+
self.pcFile = try PkgConfig.locatePCFile(name: name)
27+
parser = PkgConfigParser(pcFile: pcFile)
28+
}
29+
30+
static func locatePCFile(name: String) throws -> String {
31+
for path in searchPaths {
32+
let pcFile = Path.join(path, "\(name).pc")
33+
if pcFile.isFile {
34+
return pcFile
35+
}
36+
}
37+
throw PkgConfigError.CouldNotFindConfigFile
38+
}
39+
40+
mutating func load() throws {
41+
cFlags = [String]()
42+
libs = [String]()
43+
try parser.parse()
44+
if let cFlags = parser.cFlags {
45+
// FIXME: handle spaces in paths.
46+
self.cFlags += cFlags.characters.split(separator: " ").map(String.init)
47+
}
48+
if let libs = parser.libs {
49+
// FIXME: handle spaces in paths.
50+
self.libs += libs.characters.split(separator: " ").map(String.init)
51+
}
52+
53+
if(parser.dependencies.isEmpty) {
54+
return
55+
}
56+
57+
for dep in parser.dependencies {
58+
var pkg = try PkgConfig(name: dep)
59+
try pkg.load()
60+
self.cFlags += pkg.cFlags
61+
self.libs += pkg.libs
62+
}
63+
}
64+
65+
var cFlags = [String]()
66+
var libs = [String]()
67+
}
68+
69+
private struct PkgConfigParser {
70+
let pcFile: String
71+
var variables = [String: String]()
72+
var dependencies = [String]()
73+
var cFlags: String?
74+
var libs: String?
75+
76+
enum Token {
77+
case CFlats(String)
78+
case Libs(String)
79+
case Dependencies(String)
80+
case Variable(name: String, value: String)
81+
}
82+
83+
init(pcFile: String) {
84+
self.pcFile = pcFile
85+
}
86+
87+
mutating func parse() throws {
88+
let file = File(path: self.pcFile)
89+
for line in try file.enumerate() {
90+
if !line.characters.contains(":") && line.characters.contains("=") {
91+
let equalsIndex = line.characters.index(of: "=")!
92+
let name = line[line.startIndex..<equalsIndex]
93+
let value = line[equalsIndex.successor()..<line.endIndex]
94+
variables[name] = resolveVariables(value)
95+
} else if line.hasPrefix("Requires: ") {
96+
dependencies = parseDependencies(value(line: line))
97+
} else if line.hasPrefix("Libs: ") {
98+
libs = resolveVariables(value(line: line)).chomp()
99+
} else if line.hasPrefix("Cflags: ") {
100+
cFlags = resolveVariables( value(line: line)).chomp()
101+
}
102+
}
103+
}
104+
105+
func parseDependencies(_ depString: String) -> [String] {
106+
let exploded = depString.characters.split(separator: " ").map(String.init)
107+
let operators = ["=", "<", ">", "<=", ">="]
108+
var deps = [String]()
109+
var skipNext = false
110+
for depString in exploded {
111+
if skipNext {
112+
skipNext = false
113+
continue
114+
}
115+
if operators.contains(depString) {
116+
skipNext = true
117+
} else {
118+
deps.append(depString)
119+
}
120+
}
121+
return deps
122+
}
123+
124+
func resolveVariables(_ line: String) -> String {
125+
func resolve(_ string: String) -> String {
126+
var resolvedString = string
127+
guard let dollar = resolvedString.characters.index(of: "$") else { return string }
128+
guard let variableEndIndex = resolvedString.characters.index(of: "}") else {return string }
129+
let variable = string[dollar.successor().successor()..<variableEndIndex]
130+
let value = variables[variable]!
131+
resolvedString = resolvedString[resolvedString.startIndex..<dollar] + value + resolvedString[variableEndIndex.successor()..<resolvedString.endIndex]
132+
return resolvedString
133+
}
134+
var resolved = line
135+
while resolved.characters.contains("$") {
136+
resolved = resolve(resolved)
137+
}
138+
return resolved
139+
}
140+
141+
func value(line: String) -> String {
142+
guard let colonIndex = line.characters.index(of: ":") else {
143+
return ""
144+
}
145+
return line[colonIndex.successor().successor()..<line.endIndex]
146+
}
147+
}

Sources/ManifestParser/fromTOML().swift

+6-1
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,11 @@ extension PackageDescription.Package {
2222
if case .String(let value)? = table.items["name"] {
2323
name = value
2424
}
25+
26+
var pkgConfig: String? = nil
27+
if case .String(let value)? = table.items["pkgConfig"] {
28+
pkgConfig = value
29+
}
2530

2631
// Parse the targets.
2732
var targets: [PackageDescription.Target] = []
@@ -56,7 +61,7 @@ extension PackageDescription.Package {
5661
}
5762
}
5863

59-
return PackageDescription.Package(name: name, targets: targets, dependencies: dependencies, testDependencies: testDependencies, exclude: exclude)
64+
return PackageDescription.Package(name: name, pkgConfig: pkgConfig, targets: targets, dependencies: dependencies, testDependencies: testDependencies, exclude: exclude)
6065
}
6166
}
6267

Sources/PackageDescription/Package.swift

+10-2
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,11 @@ public final class Package {
4242

4343
/// The name of the package, if specified.
4444
public let name: String?
45-
45+
46+
/// pkgconfig name to use for C Modules. If present, swiftpm will try to search for
47+
/// <name>.pc file to get the additional flags needed for the system module.
48+
public let pkgConfig: String?
49+
4650
/// The list of targets.
4751
public var targets: [Target]
4852

@@ -56,8 +60,9 @@ public final class Package {
5660
public var exclude: [String]
5761

5862
/// Construct a package.
59-
public init(name: String? = nil, targets: [Target] = [], dependencies: [Dependency] = [], testDependencies: [Dependency] = [], exclude: [String] = []) {
63+
public init(name: String? = nil, pkgConfig: String? = nil, targets: [Target] = [], dependencies: [Dependency] = [], testDependencies: [Dependency] = [], exclude: [String] = []) {
6064
self.name = name
65+
self.pkgConfig = pkgConfig
6166
self.targets = targets
6267
self.dependencies = dependencies
6368
self.testDependencies = testDependencies
@@ -96,6 +101,9 @@ extension Package: TOMLConvertible {
96101
if let name = self.name {
97102
result += "name = \"\(name)\"\n"
98103
}
104+
if let pkgConfig = self.pkgConfig {
105+
result += "pkgConfig = \"\(pkgConfig)\"\n"
106+
}
99107
result += "dependencies = ["
100108
for dependency in dependencies {
101109
result += dependency.toTOML()

Sources/PackageType/Module.swift

+3-2
Original file line numberDiff line numberDiff line change
@@ -97,9 +97,10 @@ extension SwiftModule: XcodeModuleProtocol {
9797

9898
public class CModule: Module {
9999
public let path: String
100-
101-
public init(name: String, path: String) throws {
100+
public let pkgConfig: String?
101+
public init(name: String, path: String, pkgConfig: String? = nil) throws {
102102
self.path = path
103+
self.pkgConfig = pkgConfig
103104
try super.init(name: name)
104105
}
105106
}

Sources/Transmute/Package+modules.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ extension Package {
1616
func modules() throws -> [Module] {
1717

1818
guard !Path.join(path, "module.modulemap").isFile else {
19-
return [try CModule(name: name, path: path)]
19+
return [try CModule(name: name, path: path, pkgConfig: manifest.package.pkgConfig)]
2020
}
2121

2222
let srcroot = try sourceRoot()

0 commit comments

Comments
 (0)