diff --git a/Sources/GenIR/CompilerCommandRunner.swift b/Sources/GenIR/CompilerCommandRunner.swift index 2d1e6f3..b29404e 100644 --- a/Sources/GenIR/CompilerCommandRunner.swift +++ b/Sources/GenIR/CompilerCommandRunner.swift @@ -47,7 +47,7 @@ struct CompilerCommandRunner { /// Starts the runner /// - Parameter targets: the targets holding the commands to run - func run(targets: [Target], commands: [String: [CompilerCommand]]) throws { + func run(targets: [Target], commands: [TargetKey: [CompilerCommand]]) throws { // Quick, do a hack! try buildCacheManipulator.manipulate() @@ -59,7 +59,7 @@ struct CompilerCommandRunner { var totalModulesRun = 0 for target in targets.filter({ $0.isBuildable }) { - guard let targetCommands = commands[target.name] else { + guard let targetCommands = commands[TargetKey(projectName: target.projectName, targetName: target.name)] else { continue } diff --git a/Sources/GenIR/GenIR.swift b/Sources/GenIR/GenIR.swift index 50047cb..936ce77 100644 --- a/Sources/GenIR/GenIR.swift +++ b/Sources/GenIR/GenIR.swift @@ -112,7 +112,13 @@ let programName = CommandLine.arguments.first! // Find and parse the PIF cache let pifCache = try PIFCache(buildCache: log.buildCachePath) - let targets = pifCache.targets.map { Target(from: $0) } + let targets = pifCache.projects.flatMap { project in + project.targets.compactMap { Target(from: $0, in: project) } + } + + let targetCommands = log.commandLog.reduce(into: [TargetKey: [CompilerCommand]]()) { commands, entry in + commands[entry.target, default: []].append(entry.command) + } let builder = DependencyGraphBuilder( provider: .init(targets: targets, cache: pifCache), @@ -148,7 +154,7 @@ let programName = CommandLine.arguments.first! buildCacheManipulator: buildCacheManipulator, dryRun: dryRun ) - try runner.run(targets: targets, commands: log.targetCommands) + try runner.run(targets: targets, commands: targetCommands) let postprocessor = try OutputPostprocessor( archive: archive, diff --git a/Sources/GenIR/Target.swift b/Sources/GenIR/Target.swift index 0a23ba0..6d7bd08 100644 --- a/Sources/GenIR/Target.swift +++ b/Sources/GenIR/Target.swift @@ -9,6 +9,11 @@ import Foundation import PIFSupport import DependencyGraph +struct TargetKey: Hashable { + let projectName: String + let targetName: String +} + /// Represents a product to build (app, framework, plugin, package). It contains the identifying /// information about a target and its output when it is built or archived. In the future the /// `PIF` package will likely be modified so that it is usable within the context of Gen-IR @@ -18,6 +23,8 @@ class Target { let guid: String /// Name of the target. let name: String + /// Name of the project that the target belongs to. + let projectName: String /// The product name refers to the output of this target when it is built or copied into an archive. let productName: String @@ -33,9 +40,14 @@ class Target { let isSwiftPackage: Bool - init(from baseTarget: PIF.BaseTarget) { + init(from baseTarget: PIF.BaseTarget, in project: PIF.Project) { guid = baseTarget.guid name = baseTarget.name + // Each target is associated with a project, but the name of the project is not + // always available. We fallback to the GUID to maintain uniqueness of the target, + // but this isn't useful for finding the target in the build log, since only + // the project name is present in there. + projectName = project.projectName ?? project.groupTree.name ?? project.guid if let target = baseTarget as? PIF.Target, !target.productName.isEmpty { productName = target.productName } else if baseTarget.guid == "PACKAGE-PRODUCT:\(baseTarget.name)" { diff --git a/Sources/GenIR/XcodeLogParser.swift b/Sources/GenIR/XcodeLogParser.swift index 5da9d26..78c7932 100644 --- a/Sources/GenIR/XcodeLogParser.swift +++ b/Sources/GenIR/XcodeLogParser.swift @@ -10,6 +10,11 @@ import LogHandlers /// An XcodeLogParser extracts targets and their compiler commands from a given Xcode build log class XcodeLogParser { + struct CommandEntry { + let target: TargetKey + let command: CompilerCommand + } + /// The Xcode build log contents private let log: [String] /// The current line offset in the log @@ -18,8 +23,7 @@ class XcodeLogParser { private(set) var settings: [String: String] = [:] /// The path to the Xcode build cache private(set) var buildCachePath: URL! - /// A mapping of target names to the compiler commands that relate to them - private(set) var targetCommands: [String: [CompilerCommand]] = [:] + private(set) var commandLog: [CommandEntry] = [] enum Error: Swift.Error { case noCommandsFound(String) @@ -37,7 +41,7 @@ class XcodeLogParser { func parse() throws { parseBuildLog() - if targetCommands.isEmpty { + if commandLog.isEmpty { logger.debug("Found no targets in log") throw Error.noTargetsFound( @@ -47,12 +51,7 @@ class XcodeLogParser { ) } - let totalCommandCount = targetCommands - .values - .compactMap { $0.count } - .reduce(0, +) - - if totalCommandCount == 0 { + if commandLog.count == 0 { logger.debug("Found no commands in log") throw Error.noCommandsFound( @@ -69,7 +68,7 @@ class XcodeLogParser { /// Parse the lines from the build log func parseBuildLog() { - var seenTargets = Set() + var seenTargets = Set() while let line = consumeLine() { if line.hasPrefix("Build description path: ") { @@ -90,13 +89,14 @@ class XcodeLogParser { logger.debug("Found target: \(target)") } - let compilerCommands = parseCompilerCommands() + let commands = parseCompilerCommands(target: target) - compilerCommands.forEach { - logger.debug("Found \($0.compiler.rawValue) compiler command for target: \(target)") + commands.forEach { + logger.debug("Found \($0.command.compiler.rawValue) compiler command for target: \(target)") } - targetCommands[target, default: []].append(contentsOf: compilerCommands) + commandLog.append(contentsOf: commands) + default: continue } @@ -157,8 +157,8 @@ class XcodeLogParser { } /// Parsecompiler commands from the current block - private func parseCompilerCommands() -> [CompilerCommand] { - var commands: [CompilerCommand] = [] + private func parseCompilerCommands(target: TargetKey) -> [CommandEntry] { + var commands: [CommandEntry] = [] while let line = consumeLine() { // Assume we have reached the end of this build task's block when we encounter an unindented line. @@ -170,7 +170,7 @@ class XcodeLogParser { continue } - commands.append(compilerCommand) + commands.append(.init(target: target, command: compilerCommand)) } return commands @@ -200,25 +200,21 @@ class XcodeLogParser { return nil } - /// Returns the target from the given line - /// - Parameter line: the line to parse - /// - Returns: the name of the target if one was found, otherwise nil - private func target(from line: String) -> String? { - if line.contains("Build target ") { - var result = line.replacingOccurrences(of: "Build target ", with: "") - - if let bound = result.range(of: "of ")?.lowerBound { - result = String(result[result.startIndex.. TargetKey? { + guard let targetStart = line.range(of: "(in target '")?.upperBound, let targetEnd = line.range(of: "' from ")?.lowerBound else { + return nil + } - return result.trimmingCharacters(in: .whitespacesAndNewlines) - } else if let startIndex = line.range(of: "(in target '")?.upperBound, let endIndex = line.range(of: "' from ")?.lowerBound { - // sometimes (seemingly for archives) build logs follow a different format for targets - return String(line[startIndex..