Skip to content
Closed
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
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ protocol PBXScriptsBuildPhaseMapping {
/// - Throws: If any file paths cannot be validated.
func map(
_ scriptPhases: [PBXShellScriptBuildPhase],
buildPhases: [PBXBuildPhase]
buildPhases: [PBXBuildPhase],
settings: SettingsDictionary
) throws -> [TargetScript]

/// Maps shell script build phases into a simpler, “raw” representation (`RawScriptBuildPhase`).
Expand All @@ -28,10 +29,15 @@ protocol PBXScriptsBuildPhaseMapping {
struct PBXScriptsBuildPhaseMapper: PBXScriptsBuildPhaseMapping {
func map(
_ scriptPhases: [PBXShellScriptBuildPhase],
buildPhases: [PBXBuildPhase]
buildPhases: [PBXBuildPhase],
settings: SettingsDictionary
) throws -> [TargetScript] {
try scriptPhases.compactMap {
try mapScriptPhase($0, buildPhases: buildPhases)
try mapScriptPhase(
$0,
buildPhases: buildPhases,
settings: settings
)
}
}

Expand All @@ -44,22 +50,23 @@ struct PBXScriptsBuildPhaseMapper: PBXScriptsBuildPhaseMapping {
/// Converts a single `PBXShellScriptBuildPhase` to a `TargetScript`, if valid.
private func mapScriptPhase(
_ scriptPhase: PBXShellScriptBuildPhase,
buildPhases: [PBXBuildPhase]
buildPhases: [PBXBuildPhase],
settings: SettingsDictionary
) throws -> TargetScript? {
guard let shellScript = scriptPhase.shellScript else {
return nil
}

let inputFileListPaths = try scriptPhase.inputFileListPaths?.compactMap {
try AbsolutePath(validating: $0)
try AbsolutePath(validating: $0.replacingSettingsVariables(with: settings))
} ?? []

let outputFileListPaths = try scriptPhase.outputFileListPaths?.compactMap {
try AbsolutePath(validating: $0)
try AbsolutePath(validating: $0.replacingSettingsVariables(with: settings))
} ?? []

let dependencyFile = try scriptPhase.dependencyFile.map {
try AbsolutePath(validating: $0)
try AbsolutePath(validating: $0.replacingSettingsVariables(with: settings))
}

return TargetScript(
Expand Down Expand Up @@ -114,3 +121,29 @@ struct PBXScriptsBuildPhaseMapper: PBXScriptsBuildPhaseMapping {
return scriptIndex == 0 ? .pre : .post
}
}

extension String {
fileprivate func replacingSettingsVariables(
with settings: SettingsDictionary
) -> String {
settings.compactMapValues { value -> String? in
switch value {
case .array:
return nil
case let .string(string):
return string
}
}
.reduce(self) { string, setting in
string
.replacingOccurrences(
of: "${\(setting.key)}",
with: setting.value
)
.replacingOccurrences(
of: "$(\(setting.key)",
with: setting.value
)
}
}
}
71 changes: 69 additions & 2 deletions Sources/XcodeGraphMapper/Mappers/Targets/PBXTargetMapper.swift
Original file line number Diff line number Diff line change
Expand Up @@ -145,9 +145,29 @@ struct PBXTargetMapper: PBXTargetMapping {
xcodeProj: xcodeProj,
headers: headers
)


let debugConfiguration = settings.configurations
.filter ({ $0.key.name == "Debug" })
.first
var debugSettings = debugConfiguration?.value?.settings ?? [:]
debugSettings["CONFIGURATION"] = "Debug"
if let xcconfigPath = settings.configurations
.filter ({ $0.key.name == "Debug" })
.first?.value?.xcconfig {
let xcconfigSettings = try await BuildSettingsExtractor()
.extractBuildSettings(
for: xcconfigPath,
projectPath: xcodeProj.projectPath
)
debugSettings = debugSettings.combine(with: xcconfigSettings)
}

let runScriptPhases = pbxTarget.runScriptBuildPhases()
let scripts = try scriptsMapper.map(runScriptPhases, buildPhases: pbxTarget.buildPhases)
let scripts = try scriptsMapper.map(
runScriptPhases,
buildPhases: pbxTarget.buildPhases,
settings: debugSettings
)
let rawScriptBuildPhases = scriptsMapper.mapRawScriptBuildPhases(runScriptPhases)

let copyFilesPhases = pbxTarget.copyFilesBuildPhases()
Expand Down Expand Up @@ -545,3 +565,50 @@ struct PBXTargetMapper: PBXTargetMapping {

// swiftlint:enable function_body_length
// swiftlint:enable type_body_length

import Command

struct BuildSettingsExtractor {
private let commandRunner: CommandRunning

init(
commandRunner: CommandRunning = CommandRunner()
) {
self.commandRunner = commandRunner
}

func extractBuildSettings(
for xcconfig: AbsolutePath,
projectPath: AbsolutePath
) async throws -> [String: SettingValue] {
let output = try await commandRunner
.run(
arguments: [
"/usr/bin/xcodebuild",
"-showBuildSettings",
"-xcconfig", xcconfig.pathString,
"-project", projectPath.pathString,
"-json"
]
)
.concatenatedString()
guard let data = output.data(using: .utf8) else { return [:] }
let actionBuildSettings = try JSONDecoder().decode([ActionBuildSettings].self, from: data)
guard let buildAction = actionBuildSettings.first(where: { $0.action == "build" }) else { return [:] }
return buildAction.buildSettings.mapValues { value in
let components = value.components(separatedBy: " ")
if components.count == 1 {
return .string(components[0])
} else {
return .array(components)
}

}
}


private struct ActionBuildSettings: Codable {
let action: String
let buildSettings: [String: String]
}
}
Loading