Skip to content

Commit d9958ba

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

File tree

6 files changed

+89
-61
lines changed

6 files changed

+89
-61
lines changed

Sources/PackageLoading/ManifestLoader.swift

Lines changed: 72 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -531,39 +531,39 @@ 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+
#if os(macOS)
555+
let rpath = resources.libDir.parentDirectory
556+
#else
557+
let rpath = runtimePath
558+
#endif
559+
cmd += [
560+
"-L", runtimePath.pathString,
561+
"-lPackageDescription",
562+
"-Xlinker", "-rpath", "-Xlinker", rpath.pathString
563+
]
564564
}
565565

566-
cmd += interpreterFlags
566+
cmd += compilerFlags
567567
if let moduleCachePath = moduleCachePath {
568568
cmd += ["-module-cache-path", moduleCachePath]
569569
}
@@ -579,36 +579,50 @@ public final class ManifestLoader: ManifestLoaderProtocol {
579579

580580
cmd += [manifestPath.pathString]
581581

582-
// Create and open a temporary file to write json to.
583582
try withTemporaryFile { file in
583+
// Set path to compiled manifest executable.
584+
cmd += ["-o", file.path.pathString]
585+
586+
try Process.popen(arguments: cmd)
587+
588+
// Compile the manifest.
589+
let compilerResult = try Process.popen(arguments: cmd)
590+
let compilerOutput = try (compilerResult.utf8Output() + compilerResult.utf8stderrOutput()).spm_chuzzle()
591+
manifestParseResult.compilerOutput = compilerOutput
592+
593+
// Return now if there was an error.
594+
if compilerResult.exitStatus != .terminated(code: 0) {
595+
return
596+
}
597+
584598
// 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
599+
cmd = [file.path.pathString, "-fileno", "1"]
600+
594601
#if os(macOS)
595-
env["SWIFT_FORCE_MODULE_LOADING"] = "prefer-parseable"
602+
// If enabled, use sandbox-exec on macOS. This provides some safety against
603+
// arbitrary code execution when parsing manifest files. We only allow
604+
// the permissions which are absolutely necessary for manifest parsing.
605+
if isManifestSandboxEnabled {
606+
let cacheDirectories = [
607+
cacheDir,
608+
moduleCachePath.map({ AbsolutePath($0) })
609+
].compactMap({ $0 })
610+
let profile = sandboxProfile(toolsVersion: toolsVersion, cacheDirectories: cacheDirectories)
611+
cmd += ["sandbox-exec", "-p", profile]
612+
}
596613
#endif
597614

598615
// 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
616+
let runResult = try Process.popen(arguments: cmd)
617+
let runOutput = try (runResult.utf8Output() + runResult.utf8stderrOutput()).spm_chuzzle()
602618

603619
// Return now if there was an error.
604-
if result.exitStatus != .terminated(code: 0) {
620+
if runResult.exitStatus != .terminated(code: 0) {
621+
manifestParseResult.errorOutput = runOutput
605622
return
606623
}
607624

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

@@ -661,7 +675,7 @@ public final class ManifestLoader: ManifestLoaderProtocol {
661675
private var _sdkRoot: AbsolutePath? = nil
662676

663677
/// Returns the interpreter flags for a manifest.
664-
public func interpreterFlags(
678+
public func compilerFlags(
665679
for toolsVersion: ToolsVersion
666680
) -> [String] {
667681
var cmd = [String]()
@@ -705,26 +719,31 @@ public final class ManifestLoader: ManifestLoaderProtocol {
705719
}
706720

707721
/// Returns the sandbox profile to be used when parsing manifest on macOS.
708-
private func sandboxProfile(_ cacheDirs: [AbsolutePath] = []) -> String {
722+
private func sandboxProfile(toolsVersion: ToolsVersion, cacheDirectories: [AbsolutePath] = []) -> String {
709723
let stream = BufferedOutputByteStream()
710724
stream <<< "(version 1)" <<< "\n"
711725
// Deny everything by default.
712726
stream <<< "(deny default)" <<< "\n"
713727
// Import the system sandbox profile.
714728
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"
729+
730+
// The following accesses are only needed when interpreting the manifest (versus running a compiled version).
731+
if toolsVersion < .vNext {
732+
// Allow reading all files.
733+
stream <<< "(allow file-read*)" <<< "\n"
734+
// These are required by the Swift compiler.
735+
stream <<< "(allow process*)" <<< "\n"
736+
stream <<< "(allow sysctl*)" <<< "\n"
737+
// Allow writing in temporary locations.
738+
stream <<< "(allow file-write*" <<< "\n"
739+
for directory in Platform.darwinCacheDirectories() {
740+
stream <<< " (regex #\"^\(directory.pathString)/org\\.llvm\\.clang.*\")" <<< "\n"
741+
}
742+
for directory in cacheDirectories {
743+
stream <<< " (subpath \"\(directory.pathString)\")" <<< "\n"
744+
}
727745
}
746+
728747
stream <<< ")" <<< "\n"
729748
return stream.bytes.description
730749
}

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: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -441,6 +441,14 @@ def build_swiftpm_with_cmake(args):
441441

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

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

0 commit comments

Comments
 (0)