-
Notifications
You must be signed in to change notification settings - Fork 1.4k
pkgconfig System Module Search Paths #257
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,161 @@ | ||
/* | ||
This source file is part of the Swift.org open source project | ||
|
||
Copyright 2015 - 2016 Apple Inc. and the Swift project authors | ||
Licensed under Apache License v2.0 with Runtime Library Exception | ||
|
||
See http://swift.org/LICENSE.txt for license information | ||
See http://swift.org/CONTRIBUTORS.txt for Swift project authors | ||
*/ | ||
|
||
import Utility | ||
import func POSIX.getenv | ||
|
||
enum PkgConfigError: ErrorProtocol { | ||
case CouldNotFindConfigFile | ||
} | ||
|
||
struct PkgConfig { | ||
static let searchPaths = ["/usr/local/lib/pkgconfig", | ||
"/usr/local/share/pkgconfig", | ||
"/usr/lib/pkgconfig", | ||
"/usr/share/pkgconfig", | ||
// FIXME: These should only be searched for linux? | ||
"/usr/lib/x86_64-linux-gnu/pkgconfig", | ||
"/usr/local/lib/x86_64-linux-gnu/pkgconfig", | ||
] | ||
|
||
let name: String | ||
let pcFile: String | ||
var cFlags = [String]() | ||
var libs = [String]() | ||
private var parser: PkgConfigParser | ||
|
||
init(name: String) throws { | ||
self.name = name | ||
self.pcFile = try PkgConfig.locatePCFile(name: name) | ||
parser = PkgConfigParser(pcFile: pcFile) | ||
} | ||
|
||
static var envSearchPaths: [String] { | ||
if let configPath = getenv("PKG_CONFIG_PATH") { | ||
return configPath.characters.split(separator: ":").map(String.init) | ||
} | ||
return [] | ||
} | ||
|
||
static func locatePCFile(name: String) throws -> String { | ||
for path in (searchPaths + envSearchPaths) { | ||
let pcFile = Path.join(path, "\(name).pc") | ||
if pcFile.isFile { | ||
return pcFile | ||
} | ||
} | ||
throw PkgConfigError.CouldNotFindConfigFile | ||
} | ||
|
||
mutating func load() throws { | ||
cFlags = [String]() | ||
libs = [String]() | ||
try parser.parse() | ||
if let cFlags = parser.cFlags { | ||
// FIXME: handle spaces in paths. | ||
self.cFlags += cFlags.characters.split(separator: " ").map(String.init) | ||
} | ||
if let libs = parser.libs { | ||
// FIXME: handle spaces in paths. | ||
self.libs += libs.characters.split(separator: " ").map(String.init) | ||
} | ||
|
||
if(parser.dependencies.isEmpty) { | ||
return | ||
} | ||
|
||
for dep in parser.dependencies { | ||
var pkg = try PkgConfig(name: dep) | ||
try pkg.load() | ||
self.cFlags += pkg.cFlags | ||
self.libs += pkg.libs | ||
} | ||
} | ||
} | ||
|
||
private struct PkgConfigParser { | ||
let pcFile: String | ||
var variables = [String: String]() | ||
var dependencies = [String]() | ||
var cFlags: String? | ||
var libs: String? | ||
|
||
enum Token { | ||
case CFlats(String) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This enum is unused... There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Removed in #265 |
||
case Libs(String) | ||
case Dependencies(String) | ||
case Variable(name: String, value: String) | ||
} | ||
|
||
init(pcFile: String) { | ||
self.pcFile = pcFile | ||
} | ||
|
||
mutating func parse() throws { | ||
let file = File(path: self.pcFile) | ||
for line in try file.enumerate() { | ||
if !line.characters.contains(":") && line.characters.contains("=") { | ||
let equalsIndex = line.characters.index(of: "=")! | ||
let name = line[line.startIndex..<equalsIndex] | ||
let value = line[equalsIndex.successor()..<line.endIndex] | ||
variables[name] = resolveVariables(value) | ||
} else if line.hasPrefix("Requires: ") { | ||
dependencies = parseDependencies(value(line: line)) | ||
} else if line.hasPrefix("Libs: ") { | ||
libs = resolveVariables(value(line: line)).chomp() | ||
} else if line.hasPrefix("Cflags: ") { | ||
cFlags = resolveVariables( value(line: line)).chomp() | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not very familiar with the format, but I would recommend trying to get an There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this is not really an exhaustive list of possible cases in the file, there are some things for eg There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is there a complete list available? If so, we could just check if it is in the known complete list and ignore if not there. We can at least partition into the following three cases, right?
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah I think those are the only cases. I'll rework the parser in another PR which should address the parser related comments There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed in fb5e87b |
||
} | ||
} | ||
|
||
func parseDependencies(_ depString: String) -> [String] { | ||
let exploded = depString.characters.split(separator: " ").map(String.init) | ||
let operators = ["=", "<", ">", "<=", ">="] | ||
var deps = [String]() | ||
var skipNext = false | ||
for depString in exploded { | ||
if skipNext { | ||
skipNext = false | ||
continue | ||
} | ||
if operators.contains(depString) { | ||
skipNext = true | ||
} else { | ||
deps.append(depString) | ||
} | ||
} | ||
return deps | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This method isn't very robust, it accepts a lot of things which (I think) are invalid ("a = = b"), and it would be good to have some comments here about what it is trying to do. Also, IMHO here is a nicer way to write this kind of loop which makes it more readable:
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. After a survey of some .pc files on one of my systems, it also looks like pkg-config allows ',' as a separator here. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed in 8d38e90 |
||
|
||
func resolveVariables(_ line: String) -> String { | ||
func resolve(_ string: String) -> String { | ||
var resolvedString = string | ||
guard let dollar = resolvedString.characters.index(of: "$") else { return string } | ||
guard let variableEndIndex = resolvedString.characters.index(of: "}") else {return string } | ||
let variable = string[dollar.successor().successor()..<variableEndIndex] | ||
let value = variables[variable]! | ||
resolvedString = resolvedString[resolvedString.startIndex..<dollar] + value + resolvedString[variableEndIndex.successor()..<resolvedString.endIndex] | ||
return resolvedString | ||
} | ||
var resolved = line | ||
while resolved.characters.contains("$") { | ||
resolved = resolve(resolved) | ||
} | ||
return resolved | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Again, need comments about what this is trying to do. Other notes:
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed in a82dcba |
||
|
||
func value(line: String) -> String { | ||
guard let colonIndex = line.characters.index(of: ":") else { | ||
return "" | ||
} | ||
return line[colonIndex.successor().successor()..<line.endIndex] | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -173,3 +173,41 @@ extension Product { | |
return ((), plist: s) | ||
} | ||
} | ||
|
||
extension SystemPackageProvider { | ||
|
||
var installText: String { | ||
switch self { | ||
case .Brew(let name): | ||
return " brew install \(name)\n" | ||
case .Apt(let name): | ||
return " apt-get install \(name)\n" | ||
} | ||
} | ||
|
||
static func providerForCurrentPlatform(providers: [SystemPackageProvider]) -> SystemPackageProvider? { | ||
guard let uname = try? popen(["uname"]).chomp().lowercased() else { return nil } | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It would be good to factor out the information about the target platform somewhere else (in particular, just using a Also, I think it probably make more sense to just have
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Updated in #265 |
||
switch uname { | ||
case "darwin": | ||
for provider in providers { | ||
if case .Brew = provider { | ||
return provider | ||
} | ||
} | ||
case "linux": | ||
if "/etc/debian_version".isFile { | ||
for provider in providers { | ||
if case .Apt = provider { | ||
return provider | ||
} | ||
} | ||
} | ||
break | ||
|
||
default: | ||
return nil | ||
} | ||
return nil | ||
} | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a weird API, having a throwing initializer + not-yet-initialized mutable
cFlags
member variables. Why not just move theload()
into the initializer? Also, I would recommend usingprivate
where possible non-private structs, I think it makes things more readable.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated in #265