From 0e60d717c624570662f91d97115f7b21a3d6b814 Mon Sep 17 00:00:00 2001 From: fortmarek Date: Mon, 10 Feb 2025 14:15:41 +0100 Subject: [PATCH] fix: mapping Info.plist when referenced with a SRCROOT or PROJECT_DIR variable --- Package.resolved | 10 +- .../Targets/PBXTarget+BuildSettings.swift | 2 +- .../Mappers/Targets/PBXTargetMapper.swift | 11 ++- .../Target/PBXTargetMapperTests.swift | 91 +++++++++++++++++++ 4 files changed, 106 insertions(+), 8 deletions(-) diff --git a/Package.resolved b/Package.resolved index e900c7e0..9b9bd94d 100644 --- a/Package.resolved +++ b/Package.resolved @@ -1,5 +1,5 @@ { - "originHash" : "d85d2800ade4b7815fc45dc86a042a0565bf66be2c0a52d40c24b35d11f9a0be", + "originHash" : "252c1ee702d5bd9c4155485fe13cdca178e46fc497b3f48683ff138bb43d7492", "pins" : [ { "identity" : "aexml", @@ -24,8 +24,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/tuist/Command.git", "state" : { - "revision" : "437e0c0ca18d1a16194c55b4690971b5bfb1f185", - "version" : "0.12.0" + "revision" : "9d03a95faa94b961edc1cf2c5f4379b0108ee97a", + "version" : "0.12.1" } }, { @@ -141,8 +141,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-service-context", "state" : { - "revision" : "0c62c5b4601d6c125050b5c3a97f20cce881d32b", - "version" : "1.1.0" + "revision" : "8946c930cae601452149e45d31d8ddfac973c3c7", + "version" : "1.2.0" } }, { diff --git a/Sources/XcodeGraphMapper/Mappers/Targets/PBXTarget+BuildSettings.swift b/Sources/XcodeGraphMapper/Mappers/Targets/PBXTarget+BuildSettings.swift index 88115db3..29f22adf 100644 --- a/Sources/XcodeGraphMapper/Mappers/Targets/PBXTarget+BuildSettings.swift +++ b/Sources/XcodeGraphMapper/Mappers/Targets/PBXTarget+BuildSettings.swift @@ -18,7 +18,7 @@ extension PBXTarget { /// Retrieves the path to the Info.plist file from the target's build settings. /// /// - Returns: The `INFOPLIST_FILE` value if present, otherwise `nil`. - func infoPlistPath() -> [BuildConfiguration: String] { + func infoPlistPaths() -> [BuildConfiguration: String] { buildConfigurationList?.stringSettings(for: .infoPlistFile) ?? [:] } diff --git a/Sources/XcodeGraphMapper/Mappers/Targets/PBXTargetMapper.swift b/Sources/XcodeGraphMapper/Mappers/Targets/PBXTargetMapper.swift index 692df994..0a7bd64a 100644 --- a/Sources/XcodeGraphMapper/Mappers/Targets/PBXTargetMapper.swift +++ b/Sources/XcodeGraphMapper/Mappers/Targets/PBXTargetMapper.swift @@ -247,8 +247,15 @@ struct PBXTargetMapper: PBXTargetMapping { /// Extracts and parses the project's Info.plist as a dictionary, or returns an empty dictionary if none is found. private func extractInfoPlist(from target: PBXTarget, xcodeProj: XcodeProj) async throws -> InfoPlist { - if let (config, plistPath) = target.infoPlistPath().first { - let path = xcodeProj.srcPath.appending(try RelativePath(validating: plistPath)) + if let (config, plistPath) = target.infoPlistPaths().sorted(by: { $0.key.name > $1.key.name }).first { + let pathString = plistPath + .replacingOccurrences(of: "$(SRCROOT)", with: xcodeProj.srcPathString) + .replacingOccurrences(of: "$(PROJECT_DIR)", with: xcodeProj.projectPath.parentDirectory.pathString) + let path = if pathString.starts(with: "/") { + try AbsolutePath(validating: pathString) + } else { + xcodeProj.srcPath.appending(try RelativePath(validating: pathString)) + } let plistDictionary = try await readPlistAsDictionary(at: path) return .dictionary(plistDictionary, configuration: config) } diff --git a/Tests/XcodeGraphMapperTests/MapperTests/Target/PBXTargetMapperTests.swift b/Tests/XcodeGraphMapperTests/MapperTests/Target/PBXTargetMapperTests.swift index e8ab4d73..e57f814b 100644 --- a/Tests/XcodeGraphMapperTests/MapperTests/Target/PBXTargetMapperTests.swift +++ b/Tests/XcodeGraphMapperTests/MapperTests/Target/PBXTargetMapperTests.swift @@ -415,6 +415,97 @@ struct PBXTargetMapperTests: Sendable { }() == true) } + @Test("Parses a valid Info.plist referenced with a SRCROOT variable") + func testMapTarget_validPlist_referenced_with_SRCROOT() async throws { + // Given + + let xcodeProj = try await XcodeProj.test() + let srcPath = xcodeProj.srcPath + let relativePath = try RelativePath(validating: "Info.plist") + let plistPath = srcPath.appending(relativePath) + + let plistContent: [String: Any] = [ + "CFBundleIdentifier": "com.example.app", + "CFBundleName": "ExampleApp", + "CFVersion": 1.4, + ] + let data = try PropertyListSerialization.data(fromPropertyList: plistContent, format: .xml, options: 0) + try data.write(to: URL(fileURLWithPath: plistPath.pathString)) + + let target = createTarget( + name: "App", + xcodeProj: xcodeProj, + productType: .application, + buildSettings: [ + "PRODUCT_BUNDLE_IDENTIFIER": "com.example.app", + "INFOPLIST_FILE": "$(SRCROOT)/\(relativePath.pathString)", + ] + ) + + try xcodeProj.write(path: xcodeProj.path!) + let mapper = PBXTargetMapper() + + // When + let mapped = try await mapper.map(pbxTarget: target, xcodeProj: xcodeProj) + + // Then + #expect( + mapped.infoPlist == .dictionary( + [ + "CFBundleIdentifier": "com.example.app", + "CFBundleName": "ExampleApp", + "CFVersion": 1.4, + ], + configuration: .release("Release") + ) + ) + } + + @Test("Parses a valid Info.plist referenced with a PROJECT_DIR variable") + func testMapTarget_validPlist_referenced_with_PROJECT_DIR() async throws { + // Given + let xcodeProj = try await XcodeProj.test() + let srcPath = xcodeProj.srcPath + let relativePath = try RelativePath(validating: "Info.plist") + let plistPath = srcPath.appending(relativePath) + + let plistContent: [String: Any] = [ + "CFBundleIdentifier": "com.example.app", + "CFBundleName": "ExampleApp", + "CFVersion": 1.4, + ] + let data = try PropertyListSerialization.data(fromPropertyList: plistContent, format: .xml, options: 0) + try data.write(to: URL(fileURLWithPath: plistPath.pathString)) + + let target = createTarget( + name: "App", + xcodeProj: xcodeProj, + productType: .application, + buildSettings: [ + "PRODUCT_BUNDLE_IDENTIFIER": "com.example.app", + "INFOPLIST_FILE": "$(PROJECT_DIR)/\(relativePath.pathString)", + ] + ) + + try xcodeProj.write(path: xcodeProj.path!) + let mapper = PBXTargetMapper() + + // When + let mapped = try await mapper.map(pbxTarget: target, xcodeProj: xcodeProj) + + // Then + #expect( + mapped.infoPlist == .dictionary( + [ + "CFBundleIdentifier": "com.example.app", + "CFBundleName": "ExampleApp", + "CFVersion": 1.4, + ], + configuration: .release("Release") + ) + ) + } + @Test("Throws invalidPlist when Info.plist cannot be parsed") func testMapTarget_invalidPlist() async throws { // Given