Skip to content

Commit 76dc69f

Browse files
author
Michalis Karagiorgos
committed
Merge branch 'release/0.1.6'
# Conflicts: # xcodetargets.rb
2 parents 7726a8e + 3500f8e commit 76dc69f

15 files changed

Lines changed: 116 additions & 54 deletions

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ brew install xcodetargets
2121
- `-c` lets you specify a path to your .xcode-targets.json configuration file.
2222
- `-r` sets the path XcodeTargets should scan. This defaults to your current working directory.
2323
- `-v` enables verbose output. Default value is false.
24+
- `-e` enables error only output. Default value is false. (Practical for CICD)
2425

2526
## ⚙️ Configuration
2627
You can customize the behavior of **XcodeTargets** by creating a **.xcode-targets.json** file inside the directory you want to scan.

Sources/CompositionRoot.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,6 @@ struct CompositionRoot {
112112
targetsIndex: targetsIndex
113113
)
114114
// Final message
115-
print("✅ Xcode Targets validation completed successfully.")
115+
Swift.print("✅ Xcode Targets validation completed successfully.")
116116
}
117117
}

Sources/Errors/ConfigurationLoaderError.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ enum ConfigurationLoaderError: Error, CustomStringConvertible, Equatable {
1414
var description: String {
1515
switch self {
1616
case .configurationFileNotFound(let path):
17-
"error: ❌ Configuration file not found at path: \(path)"
17+
"❌ Configuration file not found at path: \(path)"
1818
}
1919
}
2020
}

Sources/Errors/DuplicatesError.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ enum DuplicatesError: Error, CustomStringConvertible, Equatable {
1515
var description: String {
1616
switch self {
1717
case .duplicateEntries(let duplicates, let context):
18-
"error: ❌ Duplicate \(context) entries found:\n\(duplicates.joined(separator: ", "))"
18+
"❌ Duplicate \(context) entries found:\n\(duplicates.joined(separator: ", "))"
1919
}
2020
}
2121
}

Sources/Errors/ExclusivesError.swift

Lines changed: 44 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,17 +21,56 @@ enum ExclusivesError: Error, CustomStringConvertible, Equatable {
2121

2222
/// Conflicting exclusive entries were found across the provided targets (duplicates in mutually exclusive sections).
2323
/// - Parameter targetNames: A comma-separated list of conflicting target names.
24-
case exclusiveEntriesFound(targetNames: String)
24+
/// - Parameter diff: A `Target` instance representing the differences found.
25+
case exclusiveEntriesFound(targetNames: String, diff: Target)
2526

2627
/// A human-readable description of the exclusivity validation failure, suitable for logging or displaying in diagnostics.
2728
var description: String {
2829
switch self {
2930
case .invalidTargetName(let name):
30-
"error: ❌ Target name \(name) inside exclusive section doesn't exist in the project"
31+
"❌ Target name \(name) inside exclusive section doesn't exist in the project"
3132
case .invalidPathForTarget(let targetName, let path):
32-
"error: ❌ Path \(path) inside exclusive section for target \(targetName) doesn't exist in the project"
33-
case .exclusiveEntriesFound(let targetNames):
34-
"error: ❌ Exclusive entries found for targets: \(targetNames)"
33+
"❌ Path \(path) inside exclusive section for target \(targetName) doesn't exist in the project"
34+
case .exclusiveEntriesFound(let targetNames, let diff):
35+
exclusiveEntriesErrorMessage(
36+
targetNames: targetNames,
37+
diff: diff
38+
)
39+
}
40+
}
41+
42+
private func exclusiveEntriesErrorMessage(
43+
targetNames: String,
44+
diff: Target
45+
) -> String {
46+
var message = "❌ Exclusive entries found for targets: \(targetNames)\n"
47+
updateMessage(
48+
kind: "files",
49+
entries: diff.filePaths,
50+
message: &message
51+
)
52+
updateMessage(
53+
kind: "dependencies",
54+
entries: diff.dependencies,
55+
message: &message
56+
)
57+
updateMessage(
58+
kind: "frameworks",
59+
entries: diff.frameworks,
60+
message: &message
61+
)
62+
return message
63+
}
64+
65+
private func updateMessage(
66+
kind: String,
67+
entries: Set<String>,
68+
message: inout String
69+
) {
70+
guard !entries.isEmpty else { return }
71+
message += " Conflicting \(kind):\n"
72+
for entry in entries.sorted() {
73+
message += " - \(entry)\n"
3574
}
3675
}
3776
}

Sources/Errors/ForbiddenResourceError.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ struct ForbiddenResourceError: Error, CustomStringConvertible, Equatable {
1111

1212
/// A human readable, multiline description enumerating each forbidden resource path found.
1313
var description: String {
14-
"error: ❌ Forbidden resource(s) found in target \(targetName):\n" +
14+
"❌ Forbidden resource(s) found in target \(targetName):\n" +
1515
matchingPaths
1616
.map { " - \($0)" }
1717
.sorted()

Sources/Errors/XcodeProjectParserError.swift

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -35,17 +35,17 @@ enum XcodeProjectParserError: Error, CustomStringConvertible, Equatable {
3535
var description: String {
3636
switch self {
3737
case .invalidTargetName(let name):
38-
"error: ❌ Invalid target name \(name)"
38+
"❌ Invalid target name \(name)"
3939
case .invalidPath(let path):
40-
"error: ❌ Invalid path \(path)"
40+
"❌ Invalid path \(path)"
4141
case .failedToResolveBuildableFolderPath(let path):
42-
"error: ❌ Failed to resolve buildable folder path \(path)"
42+
"❌ Failed to resolve buildable folder path \(path)"
4343
case .forbiddenBuildableFoldersForGroups(let groups):
44-
"error: ❌ Forbidden buildable folders for groups: \n\(groups.joined(separator: ", \n"))"
44+
"❌ Forbidden buildable folders for groups: \n\(groups.joined(separator: ", \n"))"
4545
case .exceptionSetTargetIsNil(let groupPath):
46-
"error: ❌ Exception set target is nil for group at path: \(groupPath)"
46+
"❌ Exception set target is nil for group at path: \(groupPath)"
4747
case .exceptionSetTargetProductTypeIsNil(let groupPath):
48-
"error: ❌ Exception set target product type is nil for group at path: \(groupPath)"
48+
"❌ Exception set target product type is nil for group at path: \(groupPath)"
4949
}
5050
}
5151
}

Sources/Exclusives/ExclusivesProcessor.swift

Lines changed: 4 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -143,25 +143,12 @@ private extension ExclusivesProcessor {
143143
let diff = slice.differenceTarget()
144144
let joined = names.joined(separator: ", ")
145145

146-
logIfNotEmpty(kind: "files", entries: diff.filePaths, targets: joined)
147-
logIfNotEmpty(kind: "dependencies", entries: diff.dependencies, targets: joined)
148-
logIfNotEmpty(kind: "frameworks", entries: diff.frameworks, targets: joined)
149-
150146
if diff.containsNotEmptySets {
151-
throw ExclusivesError.exclusiveEntriesFound(targetNames: joined)
147+
throw ExclusivesError.exclusiveEntriesFound(
148+
targetNames: joined,
149+
diff: diff
150+
)
152151
}
153152
}
154153
}
155-
156-
func logIfNotEmpty(
157-
kind: String,
158-
entries: Set<String>,
159-
targets: String
160-
) {
161-
guard !entries.isEmpty else { return }
162-
print("error: ❌ Exclusive \(kind) found between targets \(targets):")
163-
for entry in entries.sorted() {
164-
print(" - \(entry)")
165-
}
166-
}
167154
}

Sources/XcodeTargets.swift

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,11 +30,17 @@ struct XcodeTargets: ParsableCommand {
3030
@Option(name: .shortAndLong, help: "Flag to enable verbose output.")
3131
var verbose: Bool = false
3232

33+
/// Flag to print only errors.
34+
@Option(name: .shortAndLong, help: "Flag to print only errors.")
35+
var errorsOnly: Bool = false
36+
3337
/// Executes the command: loads configuration, initializes dependencies, and runs processing.
3438
///
3539
/// - Throws: Rethrows any errors encountered during configuration loading, file system access,
3640
/// or Xcode project parsing and validation.
3741
func run() throws {
42+
let verbose = errorsOnly ? false : verbose
43+
let printErrorsOnly = errorsOnly
3844
let fileSystem = FileSystem(
3945
fileManager: FileManager.default,
4046
enumeratorFactory: { url in
@@ -48,7 +54,7 @@ struct XcodeTargets: ParsableCommand {
4854
configurationPath: configurationPath,
4955
rootPath: rootPath,
5056
fileSystem: fileSystem,
51-
print: { print($0) },
57+
print: { if !printErrorsOnly { print($0) } },
5258
vPrint: { if verbose { print($0) } },
5359
linkedTargetsProviderFactory: { group, proj in
5460
LinkedTargetsProvider(proj: proj).linkedTargets(group: group)

Tests/XcodeTargetsTests/Errors/DuplicatesErrorTests.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ struct DuplicatesErrorTests {
1818
let description = error.description
1919

2020
// Then
21-
let expectedDescription = "error: ❌ Duplicate testContext entries found:\nItem1, Item2, Item3"
21+
let expectedDescription = "❌ Duplicate testContext entries found:\nItem1, Item2, Item3"
2222
#expect(description == expectedDescription)
2323
}
2424

0 commit comments

Comments
 (0)