Skip to content

Commit

Permalink
Handle targets with same name but in different projects
Browse files Browse the repository at this point in the history
  • Loading branch information
bmxav committed Sep 19, 2024
1 parent 2b9c352 commit 5d21b81
Show file tree
Hide file tree
Showing 4 changed files with 55 additions and 40 deletions.
4 changes: 2 additions & 2 deletions Sources/GenIR/CompilerCommandRunner.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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()

Expand All @@ -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
}

Expand Down
10 changes: 8 additions & 2 deletions Sources/GenIR/GenIR.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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<PIFDependencyProvider, Target>(
provider: .init(targets: targets, cache: pifCache),
Expand Down Expand Up @@ -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,
Expand Down
14 changes: 13 additions & 1 deletion Sources/GenIR/Target.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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

Expand All @@ -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)" {
Expand Down
67 changes: 32 additions & 35 deletions Sources/GenIR/XcodeLogParser.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)
Expand All @@ -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(
Expand All @@ -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(
Expand All @@ -69,7 +68,7 @@ class XcodeLogParser {

/// Parse the lines from the build log
func parseBuildLog() {
var seenTargets = Set<String>()
var seenTargets = Set<TargetKey>()

while let line = consumeLine() {
if line.hasPrefix("Build description path: ") {
Expand All @@ -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
}
Expand Down Expand Up @@ -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.
Expand All @@ -170,7 +170,7 @@ class XcodeLogParser {
continue
}

commands.append(compilerCommand)
commands.append(.init(target: target, command: compilerCommand))
}

return commands
Expand Down Expand Up @@ -200,25 +200,22 @@ 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..<bound])
} else if let bound = result.range(of: "with configuration ")?.lowerBound {
result = String(result[result.startIndex..<bound])
}
/// Attempts to find the name of a project and target on a given line
/// - Parameter line: the line to parse
/// - Returns: tuple containing the project name and target name, otherwise nil
private func target(from line: String) -> TargetKey? {
guard let targetStart = line.range(of: "(in target '")?.upperBound, let targetEnd = line.range(of: "' from ")?.lowerBound else {
return nil
}


Check failure on line 211 in Sources/GenIR/XcodeLogParser.swift

View workflow job for this annotation

GitHub Actions / SwiftLint

Vertical Whitespace (vertical_whitespace)

Limit vertical whitespace to a single empty line; currently 2
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..<endIndex])
guard let projectStart = line.range(of: "' from project '")?.upperBound, let projectEnd = line.range(of: "')")?.lowerBound else {
return nil
}

return nil
}
let target = String(line[targetStart..<targetEnd])
let project = String(line[projectStart..<projectEnd])

return TargetKey(projectName: project, targetName: target)
}
}

0 comments on commit 5d21b81

Please sign in to comment.