Skip to content
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

Add PackageDescription.Context API #3794

Merged
merged 12 commits into from Oct 19, 2021
2 changes: 2 additions & 0 deletions Sources/PackageDescription/CMakeLists.txt
Expand Up @@ -8,6 +8,8 @@

add_library(PackageDescription
BuildSettings.swift
Context.swift
ContextModel.swift
LanguageStandardSettings.swift
PackageDescription.swift
PackageDescriptionSerialization.swift
Expand Down
29 changes: 29 additions & 0 deletions Sources/PackageDescription/Context.swift
@@ -0,0 +1,29 @@
/*
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
*/

/// The context a Swift package is running in. This encapsulates states that are known at build-time.
/// For example where in the file system the current package resides.
@available(_PackageDescription, introduced: 5.6)
public struct Context {
private static let model = ContextModel.decode()

/// The directory containing Package.swift.
public static var packageDirectory : String {
model.packageDirectory
}

/// Snapshot of the system environment variables.
public static var environment : [String : String] {
model.environment
abertelrud marked this conversation as resolved.
Show resolved Hide resolved
}

private init() {
}
}
1 change: 1 addition & 0 deletions Sources/PackageDescription/ContextModel.swift
5 changes: 5 additions & 0 deletions Sources/PackageLoading/ManifestLoader.swift
Expand Up @@ -819,6 +819,11 @@ public final class ManifestLoader: ManifestLoaderProtocol {
#else
cmd += ["-fileno", "\(fileno(jsonOutputFileDesc))"]
#endif

let packageDirectory = manifestPath.parentDirectory.pathString
let contextModel = ContextModel(packageDirectory: packageDirectory)
cmd += ["-context", contextModel.encoded]

// If enabled, run command in a sandbox.
// This provides some safety against arbitrary code execution when parsing manifest files.
// We only allow the permissions which are absolutely necessary.
Expand Down
1 change: 1 addition & 0 deletions Sources/PackageModel/CMakeLists.txt
Expand Up @@ -10,6 +10,7 @@ add_library(PackageModel
BuildConfiguration.swift
BuildEnvironment.swift
BuildSettings.swift
ContextModel.swift
Diagnostics.swift
Manifest.swift
Manifest/PackageConditionDescription.swift
Expand Down
60 changes: 60 additions & 0 deletions Sources/PackageModel/ContextModel.swift
@@ -0,0 +1,60 @@
/*
This source file is part of the Swift.org open source project

Copyright (c) 2014 - 2021 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 class Foundation.JSONDecoder
import class Foundation.JSONEncoder
import class Foundation.ProcessInfo

public struct ContextModel {
public let packageDirectory : String

public init(packageDirectory : String) {
self.packageDirectory = packageDirectory
}

public var environment : [String : String] {
ProcessInfo.processInfo.environment
}
}

extension ContextModel : Codable {
private enum CodingKeys: CodingKey {
case packageDirectory
}

public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.packageDirectory = try container.decode(String.self, forKey: .packageDirectory)
}

public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(packageDirectory, forKey: .packageDirectory)
}

public var encoded : String {
let encoder = JSONEncoder()
let data = try! encoder.encode(self)
return String(data: data, encoding: .utf8)!
}

public static func decode() -> ContextModel {
// TODO: Look at ProcessInfo.processInfo
var args = Array(ProcessInfo.processInfo.arguments[1...]).makeIterator()
while let arg = args.next() {
if arg == "-context", let json = args.next() {
let decoder = JSONDecoder()
let data = json.data(using: .utf8)!
return (try! decoder.decode(ContextModel.self, from: data))
}
}
fatalError("Could not decode ContextModel parameter.")
}
}
6 changes: 3 additions & 3 deletions Tests/BasicsTests/ConcurrencyHelpersTests.swift
Expand Up @@ -26,7 +26,7 @@ final class ConcurrencyHelpersTest: XCTestCase {
let cache = ThreadSafeKeyValueStore<Int, Int>()
for index in 0 ..< 1000 {
self.queue.async(group: sync) {
usleep(UInt32.random(in: 100 ... 300))
Thread.sleep(forTimeInterval: Double.random(in: 100 ... 300) * 1.0e-6)
let value = Int.random(in: Int.min ..< Int.max)
lock.withLock {
expected[index] = value
Expand Down Expand Up @@ -61,7 +61,7 @@ final class ConcurrencyHelpersTest: XCTestCase {
let cache = ThreadSafeArrayStore<Int>()
for _ in 0 ..< 1000 {
self.queue.async(group: sync) {
usleep(UInt32.random(in: 100 ... 300))
Thread.sleep(forTimeInterval: Double.random(in: 100 ... 300) * 1.0e-6)
let value = Int.random(in: Int.min ..< Int.max)
lock.withLock {
expected.append(value)
Expand Down Expand Up @@ -93,7 +93,7 @@ final class ConcurrencyHelpersTest: XCTestCase {
let cache = ThreadSafeBox<Int>()
for index in 0 ..< 1000 {
self.queue.async(group: sync) {
usleep(UInt32.random(in: 100 ... 300))
Thread.sleep(forTimeInterval: Double.random(in: 100 ... 300) * 1.0e-6)
serial.async(group: sync) {
lock.withLock {
if winner == nil {
Expand Down
2 changes: 1 addition & 1 deletion Tests/PackageCollectionsTests/TrieTests.swift
Expand Up @@ -203,7 +203,7 @@ class TrieTests: XCTestCase {

for i in 0 ..< docCount {
queue.async(group: sync) {
usleep(UInt32.random(in: 100 ... 300))
Thread.sleep(forTimeInterval: Double.random(in: 100 ... 300) * 1.0e-6)

trie.remove { $0 == i }
trie.insert(word: "word-\(i)", foundIn: i)
Expand Down
12 changes: 10 additions & 2 deletions Tests/PackageLoadingTests/PDLoadingTests.swift
Expand Up @@ -16,8 +16,16 @@ import TSCBasic
import TSCUtility
import XCTest

class PackageDescriptionLoadingTests: XCTestCase {
let manifestLoader = ManifestLoader(toolchain: ToolchainConfiguration.default)
class PackageDescriptionLoadingTests: XCTestCase, ManifestLoaderDelegate {
lazy var manifestLoader = ManifestLoader(toolchain: ToolchainConfiguration.default, delegate: self)
var parsedManifest : AbsolutePath?

public func willLoad(manifest: AbsolutePath) {
}

public func willParse(manifest: AbsolutePath) {
parsedManifest = manifest
}

var toolsVersion: ToolsVersion {
fatalError("implement in subclass")
Expand Down
35 changes: 35 additions & 0 deletions Tests/PackageLoadingTests/PD_5_6_LoadingTests.swift
Expand Up @@ -153,4 +153,39 @@ class PackageDescription5_6LoadingTests: PackageDescriptionLoadingTests {
}

}

/// Tests use of Context.current.packageDirectory
func testPackageContextName() throws {
let stream = BufferedOutputByteStream()
stream <<< """
import PackageDescription
let package = Package(name: Context.packageDirectory)
"""

loadManifest(stream.bytes) { manifest in
let name = parsedManifest?.parentDirectory.pathString ?? ""
XCTAssertEqual(manifest.name, name)
}
}

/// Tests access to the package's directory contents.
func testPackageContextDirectory() throws {
let stream = BufferedOutputByteStream()
stream <<< """
import PackageDescription
import Foundation

let fileManager = FileManager.default
let contents = (try? fileManager.contentsOfDirectory(atPath: Context.packageDirectory)) ?? []
let swiftFiles = contents.filter { $0.hasPrefix("TemporaryFile") && $0.hasSuffix(".swift") }

let package = Package(name: swiftFiles.joined(separator: ","))
"""

loadManifest(stream.bytes) { manifest in
let name = parsedManifest?.components.last ?? ""
let swiftFiles = manifest.name.split(separator: ",").map(String.init)
XCTAssertNotNil(swiftFiles.firstIndex(of: name))
}
}
}