Skip to content

Commit

Permalink
Very basic build setting validation
Browse files Browse the repository at this point in the history
  • Loading branch information
mattmassicotte committed Nov 8, 2023
1 parent c0d4941 commit 866c9d1
Show file tree
Hide file tree
Showing 9 changed files with 472 additions and 17 deletions.
2 changes: 1 addition & 1 deletion Package.resolved
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@
"location" : "https://github.com/mattmassicotte/XCConfig",
"state" : {
"branch" : "main",
"revision" : "26059718eab7a4fa723dd75cde45cf099d3e75a8"
"revision" : "ffe291c9237407f3f2695d10e567df63ad8b81fd"
}
},
{
Expand Down
19 changes: 19 additions & 0 deletions Sources/XCLinting/Extensions/PBXProj+BuildSettings.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import Foundation

import XcodeProj

extension PBXProj {
func enumerateBuildConfigurations(_ block: (String, XCConfigurationList) throws -> Void) rethrows {
for target in legacyTargets {
guard let list = target.buildConfigurationList else { continue }

try block(target.name, list)
}

for target in nativeTargets {
guard let list = target.buildConfigurationList else { continue }

try block(target.name, list)
}
}
}
48 changes: 48 additions & 0 deletions Sources/XCLinting/Rules/ValidateBuildSettingsRule.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import Foundation

import XcodeProj
import XCConfig

/// Detect build settings that are deprecated or no longer functional.
///
/// This currently runs a superficial check. It does not perform configuration evaluation yet.
struct ValidateBuildSettingsRule {
func run(_ environment: XCLinter.Environment) throws -> [Violation] {
var violations = [Violation]()

// check top-level
for config in environment.project.pbxproj.buildConfigurations {
violations.append(contentsOf: evaluateTargetSettings("Project", config: config))
}

// check targets
environment.project.pbxproj.enumerateBuildConfigurations { name, configList in
for config in configList.buildConfigurations {
violations.append(contentsOf: evaluateTargetSettings(name, config: config))
}
}

return violations
}

func evaluateTargetSettings(_ name: String, config: XCBuildConfiguration) -> [Violation] {
var violations = [Violation]()

for pair in config.buildSettings {
guard let setting = BuildSetting(rawValue: pair.key) else { continue }

let status = setting.evaluateValue(pair.value as? String ?? "")

switch status {
case .deprecated:
violations.append(.init("\(name):\(pair.key) = \(pair.value) is deprecated"))
case .invalid:
violations.append(.init("\(name):\(pair.key) = \(pair.value) is invalid"))
case .valid:
break
}
}

return violations
}
}
4 changes: 2 additions & 2 deletions Sources/XCLinting/Violation.swift
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import XcodeProj

public struct Violation {
public struct Violation: Hashable {
public var message: String
public var objects: [PBXObject]

public init(_ message: String, objects: [PBXObject] = []) {
self.message = message
self.objects = objects
Expand Down
4 changes: 3 additions & 1 deletion Sources/XCLinting/XCLinter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,8 @@ extension XCLinter.Environment {

extension XCLinter {
public static let defaultRuleIdentifiers: Set<String> = [
"build_files_ordered"
"build_files_ordered",
"validate_build_settings",
]

public static let defaultRules: [Rule] = Array(ruleMap.filter({ defaultRuleIdentifiers.contains($0.0) }).values)
Expand All @@ -76,5 +77,6 @@ extension XCLinter {
"embedded_build_setting": embeddedBuildSettingsRule,
"build_files_ordered": { try BuildFilesAreOrderedRule().run($0) },
"groups_sorted": groupsAreSortedRule,
"validate_build_settings": { try ValidateBuildSettingsRule().run($0) },
]
}
15 changes: 15 additions & 0 deletions Tests/XCLintTests/Bundle+TestData.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import Foundation
import XCTest

extension Bundle {
func testDataURL(named: String) throws -> URL {
let bundle = Bundle.module

let resourceURL = try XCTUnwrap(bundle.resourceURL)

return resourceURL
.appendingPathComponent("TestData", isDirectory: true)
.appendingPathComponent(named)
.standardizedFileURL
}
}
13 changes: 0 additions & 13 deletions Tests/XCLintTests/EmbeddedBuildSettingsRuleTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,6 @@ import XCTest
@testable import XCLinting
import XcodeProj

extension Bundle {
func testDataURL(named: String) throws -> URL {
let bundle = Bundle.module

let resourceURL = try XCTUnwrap(bundle.resourceURL)

return resourceURL
.appendingPathComponent("TestData", isDirectory: true)
.appendingPathComponent(named)
.standardizedFileURL
}
}

final class EmbeddedBuildSettingsRuleTests: XCTestCase {
func testProjectWithBuildSettings() throws {
let url = try Bundle.module.testDataURL(named: "StockMacOSApp.xcodeproj")
Expand Down
Loading

0 comments on commit 866c9d1

Please sign in to comment.