Permalink
Branch: master
Find file Copy path
350 lines (308 sloc) 11 KB
/*
This source file is part of the Swift.org open source project
Copyright (c) 2018 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
*/
#if os(Linux)
import Glibc
#else
import Darwin.C
#endif
import Foundation
/// The description for a complete package.
public final class Package {
/// Represents a package dependency.
public class Dependency: Encodable {
/// The dependency requirement.
public enum Requirement {
#if PACKAGE_DESCRIPTION_4
case exactItem(Version)
case rangeItem(Range<Version>)
case revisionItem(String)
case branchItem(String)
case localPackageItem
#else
case _exactItem(Version)
case _rangeItem(Range<Version>)
case _revisionItem(String)
case _branchItem(String)
case _localPackageItem
#endif
var isLocalPackage: Bool {
#if PACKAGE_DESCRIPTION_4
if case .localPackageItem = self { return true }
#else
if case ._localPackageItem = self { return true }
#endif
return false
}
}
/// The url of the dependency.
public let url: String
/// The dependency requirement.
public let requirement: Requirement
/// Create a dependency.
init(url: String, requirement: Requirement) {
self.url = url
self.requirement = requirement
}
}
/// The name of the package.
public var name: String
#if !PACKAGE_DESCRIPTION_4
/// The list of platforms supported by this package.
public var platforms: [SupportedPlatform]?
#endif
/// pkgconfig name to use for C Modules. If present, swiftpm will try to
/// search for <name>.pc file to get the additional flags needed for the
/// system target.
public var pkgConfig: String?
/// Providers array for System target
public var providers: [SystemPackageProvider]?
/// The list of targets.
public var targets: [Target]
/// The list of products vended by this package.
public var products: [Product]
/// The list of dependencies.
public var dependencies: [Dependency]
#if PACKAGE_DESCRIPTION_4
/// The list of swift versions, this package is compatible with.
public var swiftLanguageVersions: [Int]?
#else
/// The list of swift versions, this package is compatible with.
public var swiftLanguageVersions: [SwiftVersion]?
#endif
/// The C language standard to use for all C targets in this package.
public var cLanguageStandard: CLanguageStandard?
/// The C++ language standard to use for all C++ targets in this package.
public var cxxLanguageStandard: CXXLanguageStandard?
#if PACKAGE_DESCRIPTION_4
/// Construct a package.
public init(
name: String,
pkgConfig: String? = nil,
providers: [SystemPackageProvider]? = nil,
products: [Product] = [],
dependencies: [Dependency] = [],
targets: [Target] = [],
swiftLanguageVersions: [Int]? = nil,
cLanguageStandard: CLanguageStandard? = nil,
cxxLanguageStandard: CXXLanguageStandard? = nil
) {
self.name = name
self.pkgConfig = pkgConfig
self.providers = providers
self.products = products
self.dependencies = dependencies
self.targets = targets
self.swiftLanguageVersions = swiftLanguageVersions
self.cLanguageStandard = cLanguageStandard
self.cxxLanguageStandard = cxxLanguageStandard
registerExitHandler()
}
#else
/// Construct a package.
public init(
name: String,
platforms: [SupportedPlatform]? = nil,
pkgConfig: String? = nil,
providers: [SystemPackageProvider]? = nil,
products: [Product] = [],
dependencies: [Dependency] = [],
targets: [Target] = [],
swiftLanguageVersions: [SwiftVersion]? = nil,
cLanguageStandard: CLanguageStandard? = nil,
cxxLanguageStandard: CXXLanguageStandard? = nil
) {
self.name = name
self.platforms = platforms
self.pkgConfig = pkgConfig
self.providers = providers
self.products = products
self.dependencies = dependencies
self.targets = targets
self.swiftLanguageVersions = swiftLanguageVersions
self.cLanguageStandard = cLanguageStandard
self.cxxLanguageStandard = cxxLanguageStandard
registerExitHandler()
}
#endif
private func registerExitHandler() {
// Add custom exit handler to cause package to be dumped at exit, if
// requested.
//
// FIXME: This doesn't belong here, but for now is the mechanism we use
// to get the interpreter to dump the package when attempting to load a
// manifest.
if CommandLine.argc > 0,
let fileNoOptIndex = CommandLine.arguments.index(of: "-fileno"),
let fileNo = Int32(CommandLine.arguments[fileNoOptIndex + 1]) {
dumpPackageAtExit(self, fileNo: fileNo)
}
}
}
/// Represents system package providers.
public enum SystemPackageProvider {
#if PACKAGE_DESCRIPTION_4
case brewItem([String])
case aptItem([String])
#else
case _brewItem([String])
case _aptItem([String])
#endif
/// Declare the list of packages installable using the homebrew package
/// manager on macOS.
public static func brew(_ packages: [String]) -> SystemPackageProvider {
#if PACKAGE_DESCRIPTION_4
return .brewItem(packages)
#else
return ._brewItem(packages)
#endif
}
/// Declare the list of packages installable using the apt-get package
/// manager on Ubuntu.
public static func apt(_ packages: [String]) -> SystemPackageProvider {
#if PACKAGE_DESCRIPTION_4
return .aptItem(packages)
#else
return ._aptItem(packages)
#endif
}
}
// MARK: Package JSON serialization
extension Package: Encodable {
private enum CodingKeys: CodingKey {
case name
case platforms
case pkgConfig
case providers
case products
case dependencies
case targets
case swiftLanguageVersions
case cLanguageStandard
case cxxLanguageStandard
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(name, forKey: .name)
#if !PACKAGE_DESCRIPTION_4
if let platforms = self.platforms {
// The platforms API was introduced in manifest version 5.
let versionedPlatforms = VersionedValue(platforms, api: "platforms", versions: [.v5])
try container.encode(versionedPlatforms, forKey: .platforms)
}
#endif
try container.encode(pkgConfig, forKey: .pkgConfig)
try container.encode(providers, forKey: .providers)
try container.encode(products, forKey: .products)
try container.encode(dependencies, forKey: .dependencies)
try container.encode(targets, forKey: .targets)
#if PACKAGE_DESCRIPTION_4
let slv = swiftLanguageVersions?.map({ VersionedValue(String($0), api: "") })
try container.encode(slv, forKey: .swiftLanguageVersions)
#else
try container.encode(swiftLanguageVersions, forKey: .swiftLanguageVersions)
#endif
try container.encode(cLanguageStandard, forKey: .cLanguageStandard)
try container.encode(cxxLanguageStandard, forKey: .cxxLanguageStandard)
}
}
extension SystemPackageProvider: Encodable {
private enum CodingKeys: CodingKey {
case name
case values
}
private enum Name: String, Encodable {
case brew
case apt
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
#if PACKAGE_DESCRIPTION_4
switch self {
case .brewItem(let packages):
try container.encode(Name.brew, forKey: .name)
try container.encode(packages, forKey: .values)
case .aptItem(let packages):
try container.encode(Name.apt, forKey: .name)
try container.encode(packages, forKey: .values)
}
#else
switch self {
case ._brewItem(let packages):
try container.encode(Name.brew, forKey: .name)
try container.encode(packages, forKey: .values)
case ._aptItem(let packages):
try container.encode(Name.apt, forKey: .name)
try container.encode(packages, forKey: .values)
}
#endif
}
}
extension Target.Dependency: Encodable {
private enum CodingKeys: CodingKey {
case type
case name
case package
}
private enum Kind: String, Codable {
case target
case product
case byName = "byname"
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
#if PACKAGE_DESCRIPTION_4
switch self {
case .targetItem(let name):
try container.encode(Kind.target, forKey: .type)
try container.encode(name, forKey: .name)
case .productItem(let name, let package):
try container.encode(Kind.product, forKey: .type)
try container.encode(name, forKey: .name)
try container.encode(package, forKey: .package)
case .byNameItem(let name):
try container.encode(Kind.byName, forKey: .type)
try container.encode(name, forKey: .name)
}
#else
switch self {
case ._targetItem(let name):
try container.encode(Kind.target, forKey: .type)
try container.encode(name, forKey: .name)
case ._productItem(let name, let package):
try container.encode(Kind.product, forKey: .type)
try container.encode(name, forKey: .name)
try container.encode(package, forKey: .package)
case ._byNameItem(let name):
try container.encode(Kind.byName, forKey: .type)
try container.encode(name, forKey: .name)
}
#endif
}
}
// MARK: Package Dumping
func manifestToJSON(_ package: Package) -> String {
struct Output: Encodable {
let package: Package
let errors: [String]
}
let encoder = JSONEncoder()
let data = try! encoder.encode(Output(package: package, errors: errors))
return String(data: data, encoding: .utf8)!
}
var errors: [String] = []
private var dumpInfo: (package: Package, fileNo: Int32)?
private func dumpPackageAtExit(_ package: Package, fileNo: Int32) {
func dump() {
guard let dumpInfo = dumpInfo else { return }
guard let fd = fdopen(dumpInfo.fileNo, "w") else { return }
fputs(manifestToJSON(dumpInfo.package), fd)
fclose(fd)
}
dumpInfo = (package, fileNo)
atexit(dump)
}