Skip to content

Commit 0f5247e

Browse files
committed
Compile manifests instead of interpreting them
1 parent 4030940 commit 0f5247e

File tree

6 files changed

+84
-62
lines changed

6 files changed

+84
-62
lines changed

Sources/PackageLoading/ManifestLoader.swift

Lines changed: 69 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -531,39 +531,33 @@ public final class ManifestLoader: ManifestLoaderProtocol {
531531

532532
// Compute the path to runtime we need to load.
533533
let runtimePath = self.runtimePath(for: toolsVersion)
534-
let interpreterFlags = self.interpreterFlags(for: toolsVersion)
534+
let compilerFlags = self.compilerFlags(for: toolsVersion)
535535

536536
// FIXME: Workaround for the module cache bug that's been haunting Swift CI
537537
// <rdar://problem/48443680>
538538
let moduleCachePath = ProcessEnv.vars["SWIFTPM_MODULECACHE_OVERRIDE"] ?? ProcessEnv.vars["SWIFTPM_TESTS_MODULECACHE"]
539539

540-
var cmd = [String]()
541-
#if os(macOS)
542-
// If enabled, use sandbox-exec on macOS. This provides some safety against
543-
// arbitrary code execution when parsing manifest files. We only allow
544-
// the permissions which are absolutely necessary for manifest parsing.
545-
if isManifestSandboxEnabled {
546-
let cacheDirs = [
547-
cacheDir,
548-
moduleCachePath.map{ AbsolutePath($0) }
549-
].compactMap{$0}
550-
cmd += ["sandbox-exec", "-p", sandboxProfile(cacheDirs)]
551-
}
552-
#endif
540+
var cmd: [String] = []
553541
cmd += [resources.swiftCompiler.pathString]
554-
cmd += ["--driver-mode=swift"]
555542
cmd += verbosity.ccArgs
556543

557544
// If we got the binDir that means we could be developing SwiftPM in Xcode
558545
// which produces a framework for dynamic package products.
559-
let runtimeFrameworkPath = runtimePath.appending(component: "PackageFrameworks")
560-
if resources.binDir != nil, localFileSystem.exists(runtimeFrameworkPath) {
561-
cmd += ["-F", runtimeFrameworkPath.pathString, "-framework", "PackageDescription"]
546+
let packageFrameworkPath = runtimePath.appending(component: "PackageFrameworks")
547+
if resources.binDir != nil, localFileSystem.exists(packageFrameworkPath) {
548+
cmd += [
549+
"-F", packageFrameworkPath.pathString,
550+
"-framework", "PackageDescription",
551+
"-Xlinker", "-rpath", "-Xlinker", packageFrameworkPath.pathString,
552+
]
562553
} else {
563-
cmd += ["-L", runtimePath.pathString, "-lPackageDescription"]
554+
cmd += [
555+
"-L", runtimePath.pathString, "-lPackageDescription",
556+
"-Xlinker", "-rpath", "-Xlinker", resources.libDir.parentDirectory.pathString,
557+
]
564558
}
565559

566-
cmd += interpreterFlags
560+
cmd += compilerFlags
567561
if let moduleCachePath = moduleCachePath {
568562
cmd += ["-module-cache-path", moduleCachePath]
569563
}
@@ -579,36 +573,52 @@ public final class ManifestLoader: ManifestLoaderProtocol {
579573

580574
cmd += [manifestPath.pathString]
581575

582-
// Create and open a temporary file to write json to.
583-
try withTemporaryFile { file in
576+
try withTemporaryDirectory { directory in
577+
let executablePath = directory.appending(component: "manifest")
578+
579+
// Set path to compiled manifest executable.
580+
cmd += ["-o", executablePath.pathString]
581+
582+
try Process.popen(arguments: cmd)
583+
584+
// Compile the manifest.
585+
let compilerResult = try Process.popen(arguments: cmd)
586+
let compilerOutput = try (compilerResult.utf8Output() + compilerResult.utf8stderrOutput()).spm_chuzzle()
587+
manifestParseResult.compilerOutput = compilerOutput
588+
589+
// Return now if there was an error.
590+
if compilerResult.exitStatus != .terminated(code: 0) {
591+
return
592+
}
593+
584594
// Pass the fd in arguments.
585-
cmd += ["-fileno", "\(file.fileHandle.fileDescriptor)"]
586-
587-
// Prefer swiftinterface if both swiftmodule and swiftinterface files are present.
588-
//
589-
// This will avoid failures during incremental builds when the
590-
// slate swiftmodule file is still present from the previous
591-
// install. We should be able to remove this after some
592-
// transition period.
593-
var env = ProcessEnv.vars
595+
cmd = [executablePath.pathString, "-fileno", "1"]
596+
594597
#if os(macOS)
595-
env["SWIFT_FORCE_MODULE_LOADING"] = "prefer-parseable"
598+
// If enabled, use sandbox-exec on macOS. This provides some safety against
599+
// arbitrary code execution when parsing manifest files. We only allow
600+
// the permissions which are absolutely necessary for manifest parsing.
601+
if isManifestSandboxEnabled {
602+
let cacheDirectories = [
603+
cacheDir,
604+
moduleCachePath.map({ AbsolutePath($0) })
605+
].compactMap({ $0 })
606+
let profile = sandboxProfile(toolsVersion: toolsVersion, cacheDirectories: cacheDirectories)
607+
cmd += ["sandbox-exec", "-p", profile]
608+
}
596609
#endif
597610

598611
// Run the command.
599-
let result = try Process.popen(arguments: cmd, environment: env)
600-
let output = try (result.utf8Output() + result.utf8stderrOutput()).spm_chuzzle()
601-
manifestParseResult.compilerOutput = output
612+
let runResult = try Process.popen(arguments: cmd)
613+
let runOutput = try (runResult.utf8Output() + runResult.utf8stderrOutput()).spm_chuzzle()
602614

603615
// Return now if there was an error.
604-
if result.exitStatus != .terminated(code: 0) {
616+
if runResult.exitStatus != .terminated(code: 0) {
617+
manifestParseResult.errorOutput = runOutput
605618
return
606619
}
607620

608-
guard let json = try localFileSystem.readFileContents(file.path).validDescription else {
609-
throw StringError("the manifest has invalid encoding")
610-
}
611-
manifestParseResult.parsedManifest = json
621+
manifestParseResult.parsedManifest = runOutput
612622
}
613623
}
614624

@@ -661,7 +671,7 @@ public final class ManifestLoader: ManifestLoaderProtocol {
661671
private var _sdkRoot: AbsolutePath? = nil
662672

663673
/// Returns the interpreter flags for a manifest.
664-
public func interpreterFlags(
674+
public func compilerFlags(
665675
for toolsVersion: ToolsVersion
666676
) -> [String] {
667677
var cmd = [String]()
@@ -705,26 +715,31 @@ public final class ManifestLoader: ManifestLoaderProtocol {
705715
}
706716

707717
/// Returns the sandbox profile to be used when parsing manifest on macOS.
708-
private func sandboxProfile(_ cacheDirs: [AbsolutePath] = []) -> String {
718+
private func sandboxProfile(toolsVersion: ToolsVersion, cacheDirectories: [AbsolutePath] = []) -> String {
709719
let stream = BufferedOutputByteStream()
710720
stream <<< "(version 1)" <<< "\n"
711721
// Deny everything by default.
712722
stream <<< "(deny default)" <<< "\n"
713723
// Import the system sandbox profile.
714724
stream <<< "(import \"system.sb\")" <<< "\n"
715-
// Allow reading all files.
716-
stream <<< "(allow file-read*)" <<< "\n"
717-
// These are required by the Swift compiler.
718-
stream <<< "(allow process*)" <<< "\n"
719-
stream <<< "(allow sysctl*)" <<< "\n"
720-
// Allow writing in temporary locations.
721-
stream <<< "(allow file-write*" <<< "\n"
722-
for directory in Platform.darwinCacheDirectories() {
723-
stream <<< " (regex #\"^\(directory.pathString)/org\\.llvm\\.clang.*\")" <<< "\n"
724-
}
725-
for cacheDir in cacheDirs {
726-
stream <<< " (subpath \"\(cacheDir.pathString)\")" <<< "\n"
725+
726+
// The following accesses are only needed when interpreting the manifest (versus running a compiled version).
727+
if toolsVersion < .vNext {
728+
// Allow reading all files.
729+
stream <<< "(allow file-read*)" <<< "\n"
730+
// These are required by the Swift compiler.
731+
stream <<< "(allow process*)" <<< "\n"
732+
stream <<< "(allow sysctl*)" <<< "\n"
733+
// Allow writing in temporary locations.
734+
stream <<< "(allow file-write*" <<< "\n"
735+
for directory in Platform.darwinCacheDirectories() {
736+
stream <<< " (regex #\"^\(directory.pathString)/org\\.llvm\\.clang.*\")" <<< "\n"
737+
}
738+
for directory in cacheDirectories {
739+
stream <<< " (subpath \"\(directory.pathString)\")" <<< "\n"
740+
}
727741
}
742+
728743
stream <<< ")" <<< "\n"
729744
return stream.bytes.description
730745
}

Sources/PackageModel/ToolsVersion.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ public struct ToolsVersion: CustomStringConvertible, Comparable, Hashable, Codab
2121
public static let v4_2 = ToolsVersion(version: "4.2.0")
2222
public static let v5 = ToolsVersion(version: "5.0.0")
2323
public static let v5_2 = ToolsVersion(version: "5.2.0")
24+
public static let vNext = ToolsVersion(version: "999.0.0")
2425

2526
/// The current tools version in use.
2627
public static let currentToolsVersion = ToolsVersion(string:

Sources/Workspace/Workspace.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1137,15 +1137,15 @@ extension Workspace {
11371137
}
11381138

11391139
/// Returns manifest interpreter flags for a package.
1140-
public func interpreterFlags(for packagePath: AbsolutePath) -> [String] {
1140+
public func compilerFlags(for packagePath: AbsolutePath) -> [String] {
11411141
// We ignore all failures here and return empty array.
11421142
guard let manifestLoader = self.manifestLoader as? ManifestLoader,
11431143
let toolsVersion = try? toolsVersionLoader.load(at: packagePath, fileSystem: fileSystem),
11441144
currentToolsVersion >= toolsVersion,
11451145
toolsVersion >= ToolsVersion.minimumRequired else {
11461146
return []
11471147
}
1148-
return manifestLoader.interpreterFlags(for: toolsVersion)
1148+
return manifestLoader.compilerFlags(for: toolsVersion)
11491149
}
11501150

11511151
/// Load the manifests for the current dependency tree.

Sources/Xcodeproj/pbxproj.swift

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -77,12 +77,12 @@ public func xcodeProject(
7777
let compilePhase = pdTarget.addSourcesBuildPhase()
7878
compilePhase.addBuildFile(fileRef: manifestFileRef)
7979

80-
var interpreterFlags = manifestLoader.interpreterFlags(for: package.manifest.toolsVersion)
81-
if !interpreterFlags.isEmpty {
80+
var compilerFlags = manifestLoader.compilerFlags(for: package.manifest.toolsVersion)
81+
if !compilerFlags.isEmpty {
8282
// Patch the interpreter flags to use Xcode supported toolchain macro instead of the resolved path.
83-
interpreterFlags[3] = "$(TOOLCHAIN_DIR)/usr/lib/swift/pm/\(package.manifest.toolsVersion.runtimeSubpath.pathString)"
83+
compilerFlags[3] = "$(TOOLCHAIN_DIR)/usr/lib/swift/pm/\(package.manifest.toolsVersion.runtimeSubpath.pathString)"
8484
}
85-
pdTarget.buildSettings.common.OTHER_SWIFT_FLAGS += interpreterFlags
85+
pdTarget.buildSettings.common.OTHER_SWIFT_FLAGS += compilerFlags
8686
pdTarget.buildSettings.common.SWIFT_VERSION = package.manifest.toolsVersion.swiftLanguageVersion.xcodeBuildSettingValue
8787
pdTarget.buildSettings.common.LD = "/usr/bin/true"
8888
}

Tests/WorkspaceTests/WorkspaceTests.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,7 @@ final class WorkspaceTests: XCTestCase {
146146
"""
147147
}
148148

149-
XCTAssertMatch((ws.interpreterFlags(for: foo)), [.equal("-swift-version"), .equal("4")])
149+
XCTAssertMatch((ws.compilerFlags(for: foo)), [.equal("-swift-version"), .equal("4")])
150150
}
151151

152152
do {
@@ -161,7 +161,7 @@ final class WorkspaceTests: XCTestCase {
161161
"""
162162
}
163163

164-
XCTAssertEqual(ws.interpreterFlags(for: foo), [])
164+
XCTAssertEqual(ws.compilerFlags(for: foo), [])
165165
}
166166
}
167167
}

Utilities/bootstrap

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -441,6 +441,12 @@ def build_swiftpm_with_cmake(args):
441441

442442
build_with_cmake(args, cmake_flags, args.project_root, args.bootstrap_dir)
443443

444+
for runtime in ["4", "4_2"]:
445+
runtime_relative_path = "pm/%s/libPackageDescription.dylib" % runtime
446+
runtime_path = os.path.join(args.bootstrap_dir, runtime_relative_path)
447+
set_install_name_cmd = ["install_name_tool", "-id", "@rpath/%s" % runtime_relative_path, runtime_path]
448+
subprocess.check_call(set_install_name_cmd, stderr=subprocess.PIPE)
449+
444450
if args.llbuild_link_framework:
445451
swift_build = os.path.join(args.bootstrap_dir, "bin/swift-build")
446452
add_rpath_cmd = ["install_name_tool", "-add_rpath", args.llbuild_build_dir, swift_build]

0 commit comments

Comments
 (0)