diff --git a/Sources/XcodeGraphMapper/Mappers/Phases/PBXScriptsBuildPhaseMapper.swift b/Sources/XcodeGraphMapper/Mappers/Phases/PBXScriptsBuildPhaseMapper.swift index 672ae6ba..3c5ce08d 100644 --- a/Sources/XcodeGraphMapper/Mappers/Phases/PBXScriptsBuildPhaseMapper.swift +++ b/Sources/XcodeGraphMapper/Mappers/Phases/PBXScriptsBuildPhaseMapper.swift @@ -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`). @@ -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 + ) } } @@ -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( @@ -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 + ) + } + } +} diff --git a/Sources/XcodeGraphMapper/Mappers/Targets/PBXTargetMapper.swift b/Sources/XcodeGraphMapper/Mappers/Targets/PBXTargetMapper.swift index 929bbb6b..6bb740c3 100644 --- a/Sources/XcodeGraphMapper/Mappers/Targets/PBXTargetMapper.swift +++ b/Sources/XcodeGraphMapper/Mappers/Targets/PBXTargetMapper.swift @@ -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() @@ -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] + } +}