Skip to content

Commit 7bb14b2

Browse files
committed
Merge pull request #257 from aciidb0mb3r/pkg-config
2 parents ad7bc5f + 2a434f4 commit 7bb14b2

File tree

9 files changed

+316
-11
lines changed

9 files changed

+316
-11
lines changed

Sources/Build/Buildable.swift

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,30 @@ extension Module: Buildable {
5959
}
6060
}
6161

62+
extension SwiftModule {
63+
func pkgConfigArgs() throws -> [String] {
64+
return try recursiveDependencies.flatMap { module -> [String] in
65+
guard case let module as CModule = module, let pkgConfigName = module.pkgConfig else {
66+
return []
67+
}
68+
69+
do {
70+
var pkgConfig = try PkgConfig(name: pkgConfigName)
71+
try pkgConfig.load()
72+
return pkgConfig.cFlags.map{["-Xcc", $0]}.flatten() + pkgConfig.libs
73+
}
74+
catch PkgConfigError.CouldNotFindConfigFile {
75+
if let providers = module.providers,
76+
provider = SystemPackageProvider.providerForCurrentPlatform(providers: providers) {
77+
print("note: you may be able to install \(pkgConfigName) using your system-packager:\n")
78+
print(provider.installText)
79+
}
80+
}
81+
return []
82+
}
83+
}
84+
}
85+
6286
extension Product: Buildable {
6387
var isTest: Bool {
6488
if case .Test = type {

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

Lines changed: 2 additions & 2 deletions
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) + (try module.pkgConfigArgs())
18+
1919
func cmd(_ tool: ToolProtocol) -> Command {
2020
return Command(node: module.targetName, tool: tool)
2121
}

Sources/Build/Command.link().swift

Lines changed: 5 additions & 1 deletion
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 += try module.pkgConfigArgs()
87+
}
88+
8589
args += objects
8690

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

Sources/Build/PkgConfig.swift

Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
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+
import func POSIX.getenv
13+
14+
enum PkgConfigError: ErrorProtocol {
15+
case CouldNotFindConfigFile
16+
}
17+
18+
struct PkgConfig {
19+
static let searchPaths = ["/usr/local/lib/pkgconfig",
20+
"/usr/local/share/pkgconfig",
21+
"/usr/lib/pkgconfig",
22+
"/usr/share/pkgconfig",
23+
// FIXME: These should only be searched for linux?
24+
"/usr/lib/x86_64-linux-gnu/pkgconfig",
25+
"/usr/local/lib/x86_64-linux-gnu/pkgconfig",
26+
]
27+
28+
let name: String
29+
let pcFile: String
30+
var cFlags = [String]()
31+
var libs = [String]()
32+
private var parser: PkgConfigParser
33+
34+
init(name: String) throws {
35+
self.name = name
36+
self.pcFile = try PkgConfig.locatePCFile(name: name)
37+
parser = PkgConfigParser(pcFile: pcFile)
38+
}
39+
40+
static var envSearchPaths: [String] {
41+
if let configPath = getenv("PKG_CONFIG_PATH") {
42+
return configPath.characters.split(separator: ":").map(String.init)
43+
}
44+
return []
45+
}
46+
47+
static func locatePCFile(name: String) throws -> String {
48+
for path in (searchPaths + envSearchPaths) {
49+
let pcFile = Path.join(path, "\(name).pc")
50+
if pcFile.isFile {
51+
return pcFile
52+
}
53+
}
54+
throw PkgConfigError.CouldNotFindConfigFile
55+
}
56+
57+
mutating func load() throws {
58+
cFlags = [String]()
59+
libs = [String]()
60+
try parser.parse()
61+
if let cFlags = parser.cFlags {
62+
// FIXME: handle spaces in paths.
63+
self.cFlags += cFlags.characters.split(separator: " ").map(String.init)
64+
}
65+
if let libs = parser.libs {
66+
// FIXME: handle spaces in paths.
67+
self.libs += libs.characters.split(separator: " ").map(String.init)
68+
}
69+
70+
if(parser.dependencies.isEmpty) {
71+
return
72+
}
73+
74+
for dep in parser.dependencies {
75+
var pkg = try PkgConfig(name: dep)
76+
try pkg.load()
77+
self.cFlags += pkg.cFlags
78+
self.libs += pkg.libs
79+
}
80+
}
81+
}
82+
83+
private struct PkgConfigParser {
84+
let pcFile: String
85+
var variables = [String: String]()
86+
var dependencies = [String]()
87+
var cFlags: String?
88+
var libs: String?
89+
90+
enum Token {
91+
case CFlats(String)
92+
case Libs(String)
93+
case Dependencies(String)
94+
case Variable(name: String, value: String)
95+
}
96+
97+
init(pcFile: String) {
98+
self.pcFile = pcFile
99+
}
100+
101+
mutating func parse() throws {
102+
let file = File(path: self.pcFile)
103+
for line in try file.enumerate() {
104+
if !line.characters.contains(":") && line.characters.contains("=") {
105+
let equalsIndex = line.characters.index(of: "=")!
106+
let name = line[line.startIndex..<equalsIndex]
107+
let value = line[equalsIndex.successor()..<line.endIndex]
108+
variables[name] = resolveVariables(value)
109+
} else if line.hasPrefix("Requires: ") {
110+
dependencies = parseDependencies(value(line: line))
111+
} else if line.hasPrefix("Libs: ") {
112+
libs = resolveVariables(value(line: line)).chomp()
113+
} else if line.hasPrefix("Cflags: ") {
114+
cFlags = resolveVariables( value(line: line)).chomp()
115+
}
116+
}
117+
}
118+
119+
func parseDependencies(_ depString: String) -> [String] {
120+
let exploded = depString.characters.split(separator: " ").map(String.init)
121+
let operators = ["=", "<", ">", "<=", ">="]
122+
var deps = [String]()
123+
var skipNext = false
124+
for depString in exploded {
125+
if skipNext {
126+
skipNext = false
127+
continue
128+
}
129+
if operators.contains(depString) {
130+
skipNext = true
131+
} else {
132+
deps.append(depString)
133+
}
134+
}
135+
return deps
136+
}
137+
138+
func resolveVariables(_ line: String) -> String {
139+
func resolve(_ string: String) -> String {
140+
var resolvedString = string
141+
guard let dollar = resolvedString.characters.index(of: "$") else { return string }
142+
guard let variableEndIndex = resolvedString.characters.index(of: "}") else {return string }
143+
let variable = string[dollar.successor().successor()..<variableEndIndex]
144+
let value = variables[variable]!
145+
resolvedString = resolvedString[resolvedString.startIndex..<dollar] + value + resolvedString[variableEndIndex.successor()..<resolvedString.endIndex]
146+
return resolvedString
147+
}
148+
var resolved = line
149+
while resolved.characters.contains("$") {
150+
resolved = resolve(resolved)
151+
}
152+
return resolved
153+
}
154+
155+
func value(line: String) -> String {
156+
guard let colonIndex = line.characters.index(of: ":") else {
157+
return ""
158+
}
159+
return line[colonIndex.successor().successor()..<line.endIndex]
160+
}
161+
}

Sources/Build/misc.swift

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,3 +173,41 @@ extension Product {
173173
return ((), plist: s)
174174
}
175175
}
176+
177+
extension SystemPackageProvider {
178+
179+
var installText: String {
180+
switch self {
181+
case .Brew(let name):
182+
return " brew install \(name)\n"
183+
case .Apt(let name):
184+
return " apt-get install \(name)\n"
185+
}
186+
}
187+
188+
static func providerForCurrentPlatform(providers: [SystemPackageProvider]) -> SystemPackageProvider? {
189+
guard let uname = try? popen(["uname"]).chomp().lowercased() else { return nil }
190+
switch uname {
191+
case "darwin":
192+
for provider in providers {
193+
if case .Brew = provider {
194+
return provider
195+
}
196+
}
197+
case "linux":
198+
if "/etc/debian_version".isFile {
199+
for provider in providers {
200+
if case .Apt = provider {
201+
return provider
202+
}
203+
}
204+
}
205+
break
206+
207+
default:
208+
return nil
209+
}
210+
return nil
211+
}
212+
}
213+

Sources/ManifestParser/fromTOML().swift

Lines changed: 31 additions & 2 deletions
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] = []
@@ -30,7 +35,15 @@ extension PackageDescription.Package {
3035
targets.append(PackageDescription.Target.fromTOML(item))
3136
}
3237
}
33-
38+
39+
var providers: [PackageDescription.SystemPackageProvider]? = nil
40+
if case .Array(let array)? = table.items["providers"] {
41+
providers = []
42+
for item in array.items {
43+
providers?.append(PackageDescription.SystemPackageProvider.fromTOML(item))
44+
}
45+
}
46+
3447
// Parse the dependencies.
3548
var dependencies: [PackageDescription.Package.Dependency] = []
3649
if case .Array(let array)? = table.items["dependencies"] {
@@ -56,7 +69,7 @@ extension PackageDescription.Package {
5669
}
5770
}
5871

59-
return PackageDescription.Package(name: name, targets: targets, dependencies: dependencies, testDependencies: testDependencies, exclude: exclude)
72+
return PackageDescription.Package(name: name, pkgConfig: pkgConfig, providers: providers, targets: targets, dependencies: dependencies, testDependencies: testDependencies, exclude: exclude)
6073
}
6174
}
6275

@@ -85,6 +98,22 @@ extension PackageDescription.Package.Dependency {
8598
}
8699
}
87100

101+
extension PackageDescription.SystemPackageProvider {
102+
private static func fromTOML(_ item: TOMLItem) -> PackageDescription.SystemPackageProvider {
103+
guard case .Table(let table) = item else { fatalError("unexpected item") }
104+
guard case .String(let name)? = table.items["name"] else { fatalError("missing name") }
105+
guard case .String(let value)? = table.items["value"] else { fatalError("missing value") }
106+
switch name {
107+
case "Brew":
108+
return .Brew(value)
109+
case "Apt":
110+
return .Apt(value)
111+
default:
112+
fatalError("unexpected string")
113+
}
114+
}
115+
}
116+
88117
extension PackageDescription.Target {
89118
private static func fromTOML(_ item: TOMLItem) -> PackageDescription.Target {
90119
// This is a private API, currently, so we do not currently try and

0 commit comments

Comments
 (0)