Skip to content

Commit 4297451

Browse files
authored
refactor resource localization model (#4167)
motivation: while looking to eleminate preconditions from the code notices the resource localization model could be improved to better use the type system changes: * move localization from a floating optional attribute to the process rule enum case * adjust call sites and tests
1 parent 5b7f71f commit 4297451

File tree

12 files changed

+146
-134
lines changed

12 files changed

+146
-134
lines changed

Sources/PackageLoading/ManifestJSONParser.swift

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -351,12 +351,18 @@ enum ManifestJSONParser {
351351
private static func parseResources(_ json: JSON) throws -> [TargetDescription.Resource] {
352352
guard let resourcesJSON = try? json.getArray("resources") else { return [] }
353353
return try resourcesJSON.map { json in
354-
let rawRule = try json.get(String.self, forKey: "rule")
355-
let rule = TargetDescription.Resource.Rule(rawValue: rawRule)!
354+
let rule = try json.get(String.self, forKey: "rule")
356355
let path = try RelativePath(validating: json.get(String.self, forKey: "path"))
357-
let localizationString = try? json.get(String.self, forKey: "localization")
358-
let localization = localizationString.map({ TargetDescription.Resource.Localization(rawValue: $0)! })
359-
return .init(rule: rule, path: path.pathString, localization: localization)
356+
switch rule {
357+
case "process":
358+
let localizationString = try? json.get(String.self, forKey: "localization")
359+
let localization = localizationString.map({ TargetDescription.Resource.Localization(rawValue: $0)! })
360+
return .init(rule: .process(localization: localization), path: path.pathString)
361+
case "copy":
362+
return .init(rule: .copy, path: path.pathString)
363+
default:
364+
throw InternalError("invalid resource rule \(rule)")
365+
}
360366
}
361367
}
362368

Sources/PackageLoading/PackageBuilder.swift

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1435,6 +1435,18 @@ extension Sources {
14351435
}
14361436
}
14371437

1438+
extension Target.Dependency {
1439+
fileprivate var nameAndType: String {
1440+
switch self {
1441+
case .target:
1442+
return "target-\(name)"
1443+
case .product:
1444+
return "product-\(name)"
1445+
}
1446+
}
1447+
}
1448+
1449+
14381450
// MARK: - Snippets
14391451

14401452
extension PackageBuilder {
@@ -1470,15 +1482,3 @@ extension PackageBuilder {
14701482
}
14711483
}
14721484
}
1473-
1474-
private extension Target.Dependency {
1475-
1476-
var nameAndType: String {
1477-
switch self {
1478-
case .target:
1479-
return "target-\(name)"
1480-
case .product:
1481-
return "product-\(name)"
1482-
}
1483-
}
1484-
}

Sources/PackageLoading/TargetSourcesBuilder.swift

Lines changed: 52 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -156,18 +156,18 @@ public struct TargetSourcesBuilder {
156156
/// Run the builder to produce the sources of the target.
157157
public func run() throws -> (sources: Sources, resources: [Resource], headers: [AbsolutePath], ignored: [AbsolutePath], others: [AbsolutePath]) {
158158
let contents = self.computeContents()
159-
var pathToRule: [AbsolutePath: Rule] = [:]
159+
var pathToRule: [AbsolutePath: FileRuleDescription.Rule] = [:]
160160

161161
for path in contents {
162162
pathToRule[path] = self.computeRule(for: path)
163163
}
164164

165-
let headers = pathToRule.lazy.filter { $0.value.rule == .header }.map { $0.key }.sorted()
166-
let compilePaths = pathToRule.lazy.filter { $0.value.rule == .compile }.map { $0.key }
165+
let headers = pathToRule.lazy.filter { $0.value == .header }.map { $0.key }.sorted()
166+
let compilePaths = pathToRule.lazy.filter { $0.value == .compile }.map { $0.key }
167167
let sources = Sources(paths: Array(compilePaths), root: targetPath)
168168
let resources: [Resource] = pathToRule.compactMap { resource(for: $0.key, with: $0.value) }
169-
let ignored = pathToRule.filter { $0.value.rule == .ignored }.map { $0.key }
170-
let others = pathToRule.filter { $0.value.rule == .none }.map { $0.key }
169+
let ignored = pathToRule.filter { $0.value == .ignored }.map { $0.key }
170+
let others = pathToRule.filter { $0.value == .none }.map { $0.key }
171171

172172
diagnoseConflictingResources(in: resources)
173173
diagnoseCopyConflictsWithLocalizationDirectories(in: resources)
@@ -184,43 +184,38 @@ public struct TargetSourcesBuilder {
184184
return (sources, resources, headers, ignored, others)
185185
}
186186

187-
private struct Rule {
188-
let rule: FileRuleDescription.Rule
189-
let localization: TargetDescription.Resource.Localization?
190-
}
191-
192187
/// Compute the rule for the given path.
193-
private func computeRule(for path: AbsolutePath) -> Rule {
194-
var matchedRule: Rule = Rule(rule: .none, localization: nil)
188+
private func computeRule(for path: AbsolutePath) -> FileRuleDescription.Rule {
189+
var matchedRule: FileRuleDescription.Rule = .none
195190

196191
// First match any resources explicitly declared in the manifest file.
197192
for declaredResource in target.resources {
198193
let resourcePath = self.targetPath.appending(RelativePath(declaredResource.path))
199194
if path.isDescendantOfOrEqual(to: resourcePath) {
200-
if matchedRule.rule != .none {
195+
if matchedRule != .none {
201196
self.observabilityScope.emit(error: "duplicate resource rule '\(declaredResource.rule)' found for file at '\(path)'")
202197
}
203-
matchedRule = Rule(rule: declaredResource.rule.fileRule, localization: declaredResource.localization)
198+
matchedRule = .init(declaredResource.rule)
204199
}
205200
}
206201

207202
// Match any sources explicitly declared in the manifest file.
208203
if let declaredSources = self.declaredSources {
209204
for sourcePath in declaredSources {
210205
if path.isDescendantOfOrEqual(to: sourcePath) {
211-
if matchedRule.rule != .none {
206+
if matchedRule != .none {
212207
self.observabilityScope.emit(error: "duplicate rule found for file at '\(path)'")
213208
}
214209

215210
// Check for header files as they're allowed to be mixed with sources.
216211
if let ext = path.extension,
217212
FileRuleDescription.header.fileTypes.contains(ext) {
218-
matchedRule = Rule(rule: .header, localization: nil)
213+
matchedRule = .header
219214
} else if toolsVersion >= .v5_3 {
220-
matchedRule = Rule(rule: .compile, localization: nil)
215+
matchedRule = .compile
221216
} else if let ext = path.extension,
222217
SupportedLanguageExtension.validExtensions(toolsVersion: toolsVersion).contains(ext) {
223-
matchedRule = Rule(rule: .compile, localization: nil)
218+
matchedRule = .compile
224219
}
225220
// The source file might have been declared twice so
226221
// exit on first match.
@@ -232,7 +227,7 @@ public struct TargetSourcesBuilder {
232227

233228
// We haven't found a rule using that's explicitly declared in the manifest
234229
// so try doing an automatic match.
235-
if matchedRule.rule == .none {
230+
if matchedRule == .none {
236231
let effectiveRules: [FileRuleDescription] = {
237232
// Don't automatically match compile rules if target's sources are
238233
// explicitly declared in the package manifest.
@@ -243,18 +238,18 @@ public struct TargetSourcesBuilder {
243238
}()
244239

245240
if let needle = effectiveRules.first(where: { $0.match(path: path, toolsVersion: toolsVersion) }) {
246-
matchedRule = Rule(rule: needle.rule, localization: nil)
241+
matchedRule = needle.rule
247242
} else if path.parentDirectory.extension == Resource.localizationDirectoryExtension {
248-
matchedRule = Rule(rule: .processResource, localization: nil)
243+
matchedRule = .processResource(localization: .none)
249244
}
250245
}
251246

252247
return matchedRule
253248
}
254249

255250
/// Returns the `Resource` file associated with a file and a particular rule, if there is one.
256-
private func resource(for path: AbsolutePath, with rule: Rule) -> Resource? {
257-
switch rule.rule {
251+
private func resource(for path: AbsolutePath, with rule: FileRuleDescription.Rule) -> Resource? {
252+
switch rule {
258253
case .compile, .header, .none, .modulemap, .ignored:
259254
return nil
260255
case .processResource:
@@ -267,10 +262,13 @@ public struct TargetSourcesBuilder {
267262
}()
268263

269264
let explicitLocalization: String? = {
270-
switch rule.localization {
271-
case .default?: return defaultLocalization ?? "en"
272-
case .base?: return "Base"
273-
case nil: return nil
265+
switch rule {
266+
case .processResource(localization: .default):
267+
return defaultLocalization ?? "en"
268+
case .processResource(localization: .base):
269+
return "Base"
270+
default:
271+
return .none
274272
}
275273
}()
276274

@@ -281,9 +279,9 @@ public struct TargetSourcesBuilder {
281279
return nil
282280
}
283281

284-
return Resource(rule: .process, path: path, localization: implicitLocalization ?? explicitLocalization)
282+
return Resource(rule: .process(localization: implicitLocalization ?? explicitLocalization), path: path)
285283
case .copy:
286-
return Resource(rule: .copy, path: path, localization: nil)
284+
return Resource(rule: .copy, path: path)
287285
}
288286
}
289287

@@ -481,14 +479,14 @@ public struct FileRuleDescription {
481479
/// A rule semantically describes a file/directory in a target.
482480
///
483481
/// It is up to the build system to translate a rule into a build command.
484-
public enum Rule {
482+
public enum Rule: Equatable {
485483
/// The compile rule for `sources` in a package.
486484
case compile
487485

488486
/// Process resource file rule for any type of platform-specific processing.
489487
///
490488
/// This defaults to copy if there's no specialized behavior.
491-
case processResource
489+
case processResource(localization: TargetDescription.Resource.Localization?)
492490

493491
/// The copy rule.
494492
case copy
@@ -583,7 +581,7 @@ public struct FileRuleDescription {
583581
/// File types related to the interface builder and storyboards.
584582
public static let xib: FileRuleDescription = {
585583
.init(
586-
rule: .processResource,
584+
rule: .processResource(localization: .none),
587585
toolsVersion: .v5_3,
588586
fileTypes: ["nib", "xib", "storyboard"]
589587
)
@@ -592,7 +590,7 @@ public struct FileRuleDescription {
592590
/// File types related to the asset catalog.
593591
public static let assetCatalog: FileRuleDescription = {
594592
.init(
595-
rule: .processResource,
593+
rule: .processResource(localization: .none),
596594
toolsVersion: .v5_3,
597595
fileTypes: ["xcassets"]
598596
)
@@ -601,7 +599,7 @@ public struct FileRuleDescription {
601599
/// File types related to the CoreData.
602600
public static let coredata: FileRuleDescription = {
603601
.init(
604-
rule: .processResource,
602+
rule: .processResource(localization: .none),
605603
toolsVersion: .v5_3,
606604
fileTypes: ["xcdatamodeld", "xcdatamodel", "xcmappingmodel"]
607605
)
@@ -610,7 +608,7 @@ public struct FileRuleDescription {
610608
/// File types related to Metal.
611609
public static let metal: FileRuleDescription = {
612610
.init(
613-
rule: .processResource,
611+
rule: .processResource(localization: .none),
614612
toolsVersion: .v5_3,
615613
fileTypes: ["metal"]
616614
)
@@ -656,13 +654,24 @@ public struct FileRuleDescription {
656654
}
657655
}
658656

659-
extension TargetDescription.Resource.Rule {
660-
fileprivate var fileRule: FileRuleDescription.Rule {
661-
switch self {
662-
case .process:
663-
return .processResource
657+
extension FileRuleDescription.Rule {
658+
init(_ seed: TargetDescription.Resource.Rule) {
659+
switch seed {
660+
case .process(let localization):
661+
self = .processResource(localization: localization)
664662
case .copy:
665-
return .copy
663+
self = .copy
664+
}
665+
}
666+
}
667+
668+
extension Resource {
669+
var localization: String? {
670+
switch self.rule {
671+
case .process(let localization):
672+
return localization
673+
default:
674+
return .none
666675
}
667676
}
668677
}
@@ -695,8 +704,8 @@ extension ObservabilityMetadata {
695704
}
696705
}
697706

698-
fileprivate extension PackageReference.Kind {
699-
var emitAuthorWarnings: Bool {
707+
extension PackageReference.Kind {
708+
fileprivate var emitAuthorWarnings: Bool {
700709
switch self {
701710
case .remoteSourceControl, .registry:
702711
return false

Sources/PackageModel/Manifest/TargetDescription.swift

Lines changed: 7 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,10 @@
99
*/
1010

1111
/// The description of an individual target.
12-
public struct TargetDescription: Equatable, Codable {
12+
public struct TargetDescription: Equatable, Encodable {
1313

1414
/// The target type.
15-
public enum TargetType: String, Equatable, Codable {
15+
public enum TargetType: String, Equatable, Encodable {
1616
case regular
1717
case executable
1818
case test
@@ -36,13 +36,13 @@ public struct TargetDescription: Equatable, Codable {
3636
}
3737
}
3838

39-
public struct Resource: Codable, Equatable {
40-
public enum Rule: String, Codable, Equatable {
41-
case process
39+
public struct Resource: Encodable, Equatable {
40+
public enum Rule: Encodable, Equatable {
41+
case process(localization: Localization?)
4242
case copy
4343
}
4444

45-
public enum Localization: String, Codable, Equatable {
45+
public enum Localization: String, Encodable {
4646
case `default`
4747
case base
4848
}
@@ -53,14 +53,9 @@ public struct TargetDescription: Equatable, Codable {
5353
/// The path of the resource.
5454
public let path: String
5555

56-
/// The explicit localization of the resource.
57-
public let localization: Localization?
58-
59-
public init(rule: Rule, path: String, localization: Localization? = nil) {
60-
precondition(rule == .process || localization == nil)
56+
public init(rule: Rule, path: String) {
6157
self.rule = rule
6258
self.path = path
63-
self.localization = localization
6459
}
6560
}
6661

Sources/PackageModel/ManifestSourceGeneration.swift

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -403,11 +403,11 @@ fileprivate extension SourceCodeFragment {
403403
init(from resource: TargetDescription.Resource) {
404404
var params: [SourceCodeFragment] = []
405405
params.append(SourceCodeFragment(string: resource.path))
406-
if let localization = resource.localization {
407-
params.append(SourceCodeFragment(key: "localization", enum: localization.rawValue))
408-
}
409406
switch resource.rule {
410-
case .process:
407+
case .process(let localization):
408+
if let localization = localization {
409+
params.append(SourceCodeFragment(key: "localization", enum: localization.rawValue))
410+
}
411411
self.init(enum: "process", subnodes: params)
412412
case .copy:
413413
self.init(enum: "copy", subnodes: params)

0 commit comments

Comments
 (0)