Skip to content
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

Handle targets with same name but in different projects #78

Merged
merged 1 commit into from
Sep 26, 2024
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
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
66 changes: 31 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,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..<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
}

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)
}
}