Skip to content

File list support #144

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

Merged
merged 6 commits into from
Sep 12, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Sources/SwiftDriver/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ add_library(SwiftDriver
Utilities/DOTJobGraphSerializer.swift
Utilities/DateAdditions.swift
Utilities/Diagnostics.swift
Utilities/FileList.swift
Utilities/FileType.swift
Utilities/PredictableRandomNumberGenerator.swift
Utilities/RelativePathAdditions.swift
Expand Down
58 changes: 55 additions & 3 deletions Sources/SwiftDriver/Driver/Driver.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ public struct Driver {
case invalidDriverName(String)
case invalidInput(String)
case noInputFiles
case invalidArgumentValue(String, String)
case relativeFrontendPath(String)
case subcommandPassedToDriver
case integratedReplRemoved
Expand All @@ -45,6 +46,8 @@ public struct Driver {
return "invalid input: \(input)"
case .noInputFiles:
return "no input files"
case .invalidArgumentValue(let option, let value):
return "invalid value '\(value)' in '\(option)'"
case .relativeFrontendPath(let path):
// TODO: where is this error thrown
return "relative frontend path: \(path)"
Expand Down Expand Up @@ -133,6 +136,15 @@ public struct Driver {
/// The mapping from input files to output files for each kind.
internal let outputFileMap: OutputFileMap?

/// The number of files required before making a file list.
internal let fileListThreshold: Int

/// Should use file lists for inputs (number of inputs exceeds `fileListThreshold`).
public let shouldUseInputFileList: Bool

/// VirtualPath for shared all sources file list. `nil` if unused.
public let allSourcesFileList: VirtualPath?

/// The mode in which the compiler will execute.
public let compilerMode: CompilerMode

Expand Down Expand Up @@ -272,7 +284,6 @@ public struct Driver {
throw Error.subcommandPassedToDriver
}


var args = try Self.expandResponseFiles(args, fileSystem: fileSystem, diagnosticsEngine: self.diagnosticEngine)

self.driverKind = try Self.determineDriverKind(args: &args)
Expand Down Expand Up @@ -329,6 +340,16 @@ public struct Driver {
self.outputFileMap = outputFileMap
}

self.fileListThreshold = try Self.computeFileListThreshold(&self.parsedOptions, diagnosticsEngine: diagnosticsEngine)
self.shouldUseInputFileList = inputFiles.count > fileListThreshold
if shouldUseInputFileList {
let swiftInputs = inputFiles.filter(\.type.isPartOfSwiftCompilation)
let path = RelativePath(createTemporaryFileName(prefix: "sources"))
self.allSourcesFileList = .fileList(path, .list(swiftInputs.map(\.file)))
} else {
self.allSourcesFileList = nil
}

// Figure out the primary outputs from the driver.
(self.compilerOutputType, self.linkerOutputType) = Self.determinePrimaryOutputs(&parsedOptions, driverKind: driverKind, diagnosticsEngine: diagnosticEngine)

Expand Down Expand Up @@ -823,6 +844,37 @@ extension Driver {
}
}

extension Driver {
private static func computeFileListThreshold(
_ parsedOptions: inout ParsedOptions,
diagnosticsEngine: DiagnosticsEngine
) throws -> Int {
let hasUseFileLists = parsedOptions.hasArgument(.driverUseFilelists)

if hasUseFileLists {
diagnosticsEngine.emit(.warn_use_filelists_deprecated)
}

if let threshold = parsedOptions.getLastArgument(.driverFilelistThreshold)?.asSingle {
if let thresholdInt = Int(threshold) {
return thresholdInt
} else {
throw Error.invalidArgumentValue(Option.driverFilelistThreshold.spelling, threshold)
}
} else if hasUseFileLists {
return 0
}

return 128
}
}

private extension Diagnostic.Message {
static var warn_use_filelists_deprecated: Diagnostic.Message {
.warning("the option '-driver-use-filelists' is deprecated; use '-driver-filelist-threshold=0' instead")
}
}

extension Driver {
/// Compute the compiler mode based on the options.
private static func computeCompilerMode(
Expand Down Expand Up @@ -1902,15 +1954,15 @@ extension Driver {
_ = parsedOptions.hasArgument(isOutput)
}

var parentPath: VirtualPath
let parentPath: VirtualPath
if let projectDirectory = projectDirectory {
// If the build system has created a Project dir for us to include the file, use it.
parentPath = projectDirectory
} else {
parentPath = moduleOutputPath.parentDirectory
}

return try parentPath.appending(component: moduleName).replacingExtension(with: type)
return parentPath.appending(component: moduleName).replacingExtension(with: type)
}

// If the output option was not provided, don't produce this output at all.
Expand Down
10 changes: 6 additions & 4 deletions Sources/SwiftDriver/Driver/OutputFileMap.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@ import Foundation
public typealias FileSystem = TSCBasic.FileSystem

/// Mapping of input file paths to specific output files.
public struct OutputFileMap: Equatable {
public struct OutputFileMap: Hashable, Codable {
static let singleInputKey = VirtualPath.relative(RelativePath(""))

/// The known mapping from input file to specific output files.
public var entries: [VirtualPath : [FileType : VirtualPath]] = [:]

Expand Down Expand Up @@ -46,14 +48,14 @@ public struct OutputFileMap: Equatable {
}

public func existingOutputForSingleInput(outputType: FileType) -> VirtualPath? {
try! existingOutput(inputFile: VirtualPath(path: ""), outputType: outputType)
existingOutput(inputFile: Self.singleInputKey, outputType: outputType)
}

public func resolveRelativePaths(relativeTo absPath: AbsolutePath) -> OutputFileMap {
let resolvedKeyValues: [(VirtualPath, [FileType : VirtualPath])] = entries.map {
let resolvedKey: VirtualPath
// Special case for single dependency record, leave it as is
if $0.key == .relative(.init("")) {
if $0.key == Self.singleInputKey {
resolvedKey = $0.key
} else {
resolvedKey = $0.key.resolvedRelativePath(base: absPath)
Expand All @@ -78,7 +80,7 @@ public struct OutputFileMap: Equatable {
let contents = try fileSystem.readFileContents(file)
let result = try JSONDecoder().decode(OutputFileMapJSON.self, from: Data(contents.contents))

// Convert the loaded entries into virual output file map.
// Convert the loaded entries into virtual output file map.
var outputFileMap = OutputFileMap()
outputFileMap.entries = try result.toVirtualOutputFileMap()

Expand Down
86 changes: 72 additions & 14 deletions Sources/SwiftDriver/Execution/ArgsResolver.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,12 @@
//===----------------------------------------------------------------------===//

import TSCBasic
@_implementationOnly import Yams

/// Resolver for a job's argument template.
public struct ArgsResolver {
public final class ArgsResolver {
/// The map of virtual path to the actual path.
public var pathMapping: [VirtualPath: AbsolutePath]
public var pathMapping: [VirtualPath: String]

/// The file system used by the resolver.
private let fileSystem: FileSystem
Expand All @@ -24,13 +25,16 @@ public struct ArgsResolver {
// FIXME: We probably need a dedicated type for this...
private let temporaryDirectory: VirtualPath

private let lock = Lock()

public init(fileSystem: FileSystem, temporaryDirectory: VirtualPath? = nil) throws {
self.pathMapping = [:]
self.fileSystem = fileSystem

if let temporaryDirectory = temporaryDirectory {
self.temporaryDirectory = temporaryDirectory
} else {
// FIXME: withTemporaryDirectory uses FileManager.default, need to create a FileSystem.temporaryDirectory api.
let tmpDir: AbsolutePath = try withTemporaryDirectory(removeTreeOnDeinit: false) { path in
// FIXME: TSC removes empty directories even when removeTreeOnDeinit is false. This seems like a bug.
try fileSystem.writeFileContents(path.appending(component: ".keep-directory")) { $0 <<< "" }
Expand Down Expand Up @@ -60,26 +64,80 @@ public struct ArgsResolver {
return flag

case .path(let path):
// Return the path from the temporary directory if this is a temporary file.
if path.isTemporary {
let actualPath = temporaryDirectory.appending(component: path.name)
return actualPath.name
}

// If there was a path mapping, use it.
if let actualPath = pathMapping[path] {
return actualPath.pathString
return try lock.withLock {
return try unsafeResolve(path: path)
}

// Otherwise, return the path.
return path.name
case .responseFilePath(let path):
return "@\(try resolve(.path(path)))"
case let .joinedOptionAndPath(option, path):
return option + (try resolve(.path(path)))
}
}

/// Needs to be done inside of `lock`. Marked unsafe to make that more obvious.
private func unsafeResolve(path: VirtualPath) throws -> String {
// If there was a path mapping, use it.
if let actualPath = pathMapping[path] {
return actualPath
}

// Return the path from the temporary directory if this is a temporary file.
if path.isTemporary {
let actualPath = temporaryDirectory.appending(component: path.name)
if case let .fileList(_, fileList) = path {
switch fileList {
case let .list(items):
try createFileList(path: actualPath, contents: items)
case let .outputFileMap(map):
try createFileList(path: actualPath, outputFileMap: map)
}
}
let result = actualPath.name
pathMapping[path] = result
return result
}

// Otherwise, return the path.
let result = path.name
pathMapping[path] = result
return result
}

private func createFileList(path: VirtualPath, contents: [VirtualPath]) throws {
// FIXME: Need a way to support this for distributed build systems...
if let absPath = path.absolutePath {
try fileSystem.writeFileContents(absPath) { out in
for path in contents {
try! out <<< unsafeResolve(path: path) <<< "\n"
}
}
}
}

private func createFileList(path: VirtualPath, outputFileMap: OutputFileMap) throws {
// FIXME: Need a way to support this for distributed build systems...
if let absPath = path.absolutePath {
// This uses Yams to escape and quote strings, but not to output the whole yaml file because
// it sometimes outputs mappings in explicit block format (https://yaml.org/spec/1.2/spec.html#id2798057)
// and the frontend (llvm) only seems to support implicit block format.
Comment on lines +120 to +122
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I can confirm the llvm yaml parser doesn't handle explicit block format unfortunately

try fileSystem.writeFileContents(absPath) { out in
for (input, map) in outputFileMap.entries {
out <<< quoteAndEscape(path: input) <<< ":\n"
for (type, output) in map {
out <<< " " <<< type.name <<< ": " <<< quoteAndEscape(path: output) <<< "\n"
}
}
}
}
}

private func quoteAndEscape(path: VirtualPath) -> String {
let inputNode = Node.scalar(Node.Scalar(try! unsafeResolve(path: path), Tag(.str), .doubleQuoted))
let string = try! Yams.serialize(node: inputNode)
// Remove the newline from the end
return string.trimmingCharacters(in: .whitespacesAndNewlines)
}

private func createResponseFileIfNeeded(for job: Job, resolvedArguments: inout [String], forceResponseFiles: Bool) throws -> Bool {
if forceResponseFiles ||
(job.supportsResponseFiles && !commandLineFitsWithinSystemLimits(path: resolvedArguments[0], args: resolvedArguments)) {
Expand Down
8 changes: 5 additions & 3 deletions Sources/SwiftDriver/Execution/DriverExecutor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ import TSCBasic
import Foundation

public protocol DriverExecutor {
var resolver: ArgsResolver { get }

/// Execute a single job and capture the output.
@discardableResult
func execute(job: Job,
Expand Down Expand Up @@ -87,7 +89,7 @@ public final class SwiftDriverExecutor: DriverExecutor {
let diagnosticsEngine: DiagnosticsEngine
let processSet: ProcessSet
let fileSystem: FileSystem
let resolver: ArgsResolver
public let resolver: ArgsResolver
let env: [String: String]

public init(diagnosticsEngine: DiagnosticsEngine,
Expand All @@ -114,13 +116,13 @@ public final class SwiftDriverExecutor: DriverExecutor {
for (envVar, value) in job.extraEnvironment {
try ProcessEnv.setVar(envVar, value: value)
}

try exec(path: arguments[0], args: arguments)
fatalError("unreachable, exec always throws on failure")
} else {
var childEnv = env
childEnv.merge(job.extraEnvironment, uniquingKeysWith: { (_, new) in new })

let process = try Process.launchProcess(arguments: arguments, env: childEnv)
return try process.waitUntilExit()
}
Expand Down
Loading