Skip to content
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
22 changes: 11 additions & 11 deletions Package.resolved

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ let package = Package(
dependencies: [
.package(url: "https://github.com/Flight-School/AnyCodable", .upToNextMajor(from: "0.6.7")),
.package(url: "https://github.com/tuist/Path.git", .upToNextMajor(from: "0.3.8")),
.package(url: "https://github.com/tuist/XcodeProj", .upToNextMajor(from: "8.27.7")),
.package(url: "https://github.com/tuist/XcodeProj", .upToNextMajor(from: "9.0.0")),
.package(url: "https://github.com/tuist/Command.git", from: "0.13.0"),
.package(url: "https://github.com/tuist/FileSystem.git", .upToNextMajor(from: "0.7.7")),
.package(url: "https://github.com/apple/swift-service-context", .upToNextMajor(from: "1.2.0")),
Expand Down
13 changes: 0 additions & 13 deletions Sources/XcodeGraph/Models/Settings.swift
Original file line number Diff line number Diff line change
Expand Up @@ -145,19 +145,6 @@ extension [BuildConfiguration: Configuration?] {
}
}

extension [String: SettingValue] {
public func toAny() -> [String: Any] {
mapValues { value in
switch value {
case let .array(array):
return array
case let .string(string):
return string
}
}
}
}

#if DEBUG
extension Configuration {
public static func test(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ extension PBXProject {
///
/// - Parameter attr: The attribute key to look up.
/// - Returns: The value of the attribute if it exists, or `nil` if not found.
func attribute(for attr: ProjectAttribute) -> String? {
attributes[attr.rawValue] as? String
func attribute(for attr: ProjectAttributeKey) -> String? {
attributes[attr.rawValue]?.stringValue
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ struct PBXCopyFilesBuildPhaseMapper: PBXCopyFilesBuildPhaseMapping {
}

let absolutePath = try AbsolutePath(validating: pathString)
let attributes = buildFile.settings?.stringArray(for: .attributes)
let attributes = buildFile.attributes
let codeSignOnCopy = attributes?.contains(BuildFileAttribute.codeSignOnCopy.rawValue) ?? false

return .file(path: absolutePath, condition: nil, codeSignOnCopy: codeSignOnCopy)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ struct PBXFrameworksBuildPhaseMapper: PBXFrameworksBuildPhaseMapping {
)
if let path = fileRef.path {
let name = path.replacingOccurrences(of: ".framework", with: "")
let linkingStatus: LinkingStatus = (buildFile.settings?["ATTRIBUTES"] as? [String])?
let linkingStatus: LinkingStatus = buildFile.attributes?
.contains("Weak") == true ? .optional : .required
switch fileRef.sourceTree {
case .buildProductsDir:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ struct PBXHeadersBuildPhaseMapper: PBXHeadersBuildPhaseMapping {
}

let absolutePath = try AbsolutePath(validating: pathString)
let attributes = buildFile.settings?.stringArray(for: .attributes)
let attributes = buildFile.attributes

let visibility: HeaderInfo.HeaderVisibility
if attributes?.contains(HeaderAttribute.public.rawValue) == true {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,8 @@ struct PBXSourcesBuildPhaseMapper: PBXSourcesBuildPhaseMapping {
}

let path = try AbsolutePath(validating: pathString)
let settings = buildFile.settings ?? [:]
let compilerFlags: String? = settings.string(for: .compilerFlags)
let attributes: [String]? = settings.stringArray(for: .attributes)
let compilerFlags: String? = buildFile.compilerFlags
let attributes: [String]? = buildFile.attributes

return SourceFile(
path: path,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import Foundation

/// Attributes for project settings that can be retrieved from a `PBXProject`.
enum ProjectAttribute: String {
enum ProjectAttributeKey: String {
case classPrefix = "CLASSPREFIX"
case organization = "ORGANIZATIONNAME"
case lastUpgradeCheck = "LastUpgradeCheck"
Expand Down
85 changes: 4 additions & 81 deletions Sources/XcodeGraphMapper/Mappers/Settings/BuildSettings.swift
Original file line number Diff line number Diff line change
@@ -1,22 +1,11 @@
import Foundation
import XcodeProj

/// Keys representing various build settings that may appear in an Xcode project or workspace configuration.
enum BuildSettingKey: String {
case sdkroot = "SDKROOT"
case compilerFlags = "COMPILER_FLAGS"
case attributes = "ATTRIBUTES"
case environmentVariables = "ENVIRONMENT_VARIABLES"
case codeSignOnCopy = "CODE_SIGN_ON_COPY"
case dependencyFile = "DEPENDENCY_FILE"
case inputPaths = "INPUT_PATHS"
case outputPaths = "OUTPUT_PATHS"
case showEnvVarsInLog = "SHOW_ENV_VARS_IN_LOG"
case shellPath = "SHELL_PATH"
case launchArguments = "LAUNCH_ARGUMENTS"
case tags = "TAGS"
case mergedBinaryType = "MERGED_BINARY_TYPE"
case prune = "PRUNE"
case mergeable = "MERGEABLE"
case productBundleIdentifier = "PRODUCT_BUNDLE_IDENTIFIER"
case infoPlistFile = "INFOPLIST_FILE"
case codeSignEntitlements = "CODE_SIGN_ENTITLEMENTS"
Expand All @@ -27,74 +16,8 @@ enum BuildSettingKey: String {
case visionOSDeploymentTarget = "VISIONOS_DEPLOYMENT_TARGET"
}

/// A protocol representing a type that can parse a build setting value from a generic `Any`.
protocol BuildSettingValue {
associatedtype Value
static func parse(_ any: Any) -> Value?
}

/// A type that parses build settings as strings.
enum BuildSettingString: BuildSettingValue {
static func parse(_ any: Any) -> String? {
any as? String
}
}

/// A type that parses build settings as arrays of strings.
enum BuildSettingStringArray: BuildSettingValue {
static func parse(_ any: Any) -> [String]? {
let arr = any as? [Any]
return arr?.compactMap { $0 as? String }
}
}

/// A type that parses build settings as booleans.
enum BuildSettingBool: BuildSettingValue {
static func parse(_ any: Any) -> Bool? {
any as? Bool
}
}

/// A type that parses build settings as dictionaries of strings to strings.
enum BuildSettingStringDict: BuildSettingValue {
static func parse(_ any: Any) -> [String: String]? {
any as? [String: String]
}
}

extension [String: Any] {
/// Extracts a build setting value of a specified type from the dictionary.
///
/// - Parameters:
/// - key: The `BuildSettingKey` to look up.
/// - type: The type conforming to `BuildSettingValue` indicating the expected value type.
/// - Returns: The parsed value if found and valid, or `nil` otherwise.
func extractBuildSetting<T: BuildSettingValue>(_ key: BuildSettingKey, as _: T.Type = T.self)
-> T.Value?
{
guard let value = self[key.rawValue] else { return nil }
return T.parse(value)
}
}

extension [String: Any] {
/// Retrieves a string value for the given build setting key.
func string(for key: BuildSettingKey) -> String? {
extractBuildSetting(key, as: BuildSettingString.self)
}

/// Retrieves an array of strings for the given build setting key.
func stringArray(for key: BuildSettingKey) -> [String]? {
extractBuildSetting(key, as: BuildSettingStringArray.self)
}

/// Retrieves a boolean value for the given build setting key.
func bool(for key: BuildSettingKey) -> Bool? {
extractBuildSetting(key, as: BuildSettingBool.self)
}

/// Retrieves a dictionary of strings for the given build setting key.
func stringDict(for key: BuildSettingKey) -> [String: String]? {
extractBuildSetting(key, as: BuildSettingStringDict.self)
extension BuildSettings {
subscript(_ key: BuildSettingKey) -> BuildSetting? {
self[key.rawValue]
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ extension XCConfigurationList {
let configurationMatcher = ConfigurationMatcher()
var results = [BuildConfiguration: String]()
for config in buildConfigurations {
if let value = config.buildSettings.string(for: key) {
if let value = config.buildSettings[key]?.stringValue {
let variant = configurationMatcher.variant(for: config.name)
let buildConfig = BuildConfiguration(name: config.name, variant: variant)
results[buildConfig] = value
Expand All @@ -29,7 +29,7 @@ extension XCConfigurationList {

for key in keys {
for config in buildConfigurations {
if let value = config.buildSettings.string(for: key) {
if let value = config.buildSettings[key]?.stringValue {
results[key] = value
break // Once found, move to the next key
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,35 +84,17 @@ final class XCConfigurationMapper: SettingsMapping {
/// - Parameter buildSettings: A dictionary of raw build settings.
/// - Returns: A `SettingsDictionary` containing `SettingValue`-typed settings.
/// - Throws: If a setting value cannot be mapped (this is typically non-fatal; most values can be stringified).
func mapBuildSettings(_ buildSettings: [String: Any]) throws -> SettingsDictionary {
func mapBuildSettings(_ buildSettings: [String: BuildSetting]) throws -> SettingsDictionary {
var settingsDict = SettingsDictionary()
for (key, value) in buildSettings {
settingsDict[key] = try mapSettingValue(value)
settingsDict[key] = switch value {
case let .string(string): .string(string)
case let .array(array): .array(array)
}
}
return settingsDict
}

/// Maps a single raw setting value into a `SettingValue`.
///
/// - If the value is a `String`, it becomes a `SettingValue.string`.
/// - If the value is an `Array`, each element is converted to a string if possible, resulting in `SettingValue.array`.
/// - Otherwise, the value is stringified using `String(describing:)` and returned as `SettingValue.string`.
///
/// - Parameter value: A raw setting value from the build settings dictionary.
/// - Returns: A `SettingValue` representing the processed setting.
private func mapSettingValue(_ value: Any) throws -> SettingValue {
if let stringValue = value as? String {
return .string(stringValue)
} else if let arrayValue = value as? [Any] {
let stringArray = arrayValue.compactMap { $0 as? String }
return .array(stringArray)
} else {
// Fallback: convert unknown types to strings
let stringValue = String(describing: value)
return .string(stringValue)
}
}

/// Determines a `BuildConfiguration.Variant` (e.g., `.debug` or `.release`) from a configuration name.
///
/// Uses `ConfigurationMatcher` to infer the variant by analyzing the configuration name for known keywords.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,6 @@ import XcodeGraph
import XcodeProj

extension PBXTarget {
enum EnvironmentExtractor {
static func extract(from buildSettings: BuildSettings) -> [String: EnvironmentVariable] {
guard let envVars = buildSettings.stringDict(for: .environmentVariables) else {
return [:]
}
return envVars.reduce(into: [:]) { result, pair in
result[pair.key] = EnvironmentVariable(value: pair.value, isEnabled: true)
}
}
}

/// Retrieves the path to the Info.plist file from the target's build settings.
///
/// - Returns: The `INFOPLIST_FILE` value if present, otherwise `nil`.
Expand Down Expand Up @@ -70,18 +59,8 @@ extension PBXTarget {
)
}

/// Extracts environment variables from all build configurations of the target.
///
/// If multiple configurations define the same environment variable, the last processed configuration takes precedence.
func extractEnvironmentVariables() -> [String: EnvironmentVariable] {
buildConfigurationList?.buildConfigurations.reduce(into: [:]) { result, config in
result.merge(EnvironmentExtractor.extract(from: config.buildSettings)) { current, _ in current
}
} ?? [:]
}

/// Returns the build settings from the "Debug" build configuration, or an empty dictionary if not present.
var debugBuildSettings: [String: Any] {
var debugBuildSettings: [String: BuildSetting] {
buildConfigurationList?.buildConfigurations.first(where: { $0.name == "Debug" })?.buildSettings
?? [:]
}
Expand Down
Loading
Loading