diff --git a/Sources/XcodeGenKit/SettingsBuilder.swift b/Sources/XcodeGenKit/SettingsBuilder.swift
index e5a98181..d4b406b5 100644
--- a/Sources/XcodeGenKit/SettingsBuilder.swift
+++ b/Sources/XcodeGenKit/SettingsBuilder.swift
@@ -158,8 +158,8 @@ extension SettingsPresetFile {
if let symlink = try? bundlePath.symlinkDestination() {
possibleSettingsPaths = [
- symlink + relativePath
- ] + possibleSettingsPaths
+ symlink + relativePath,
+ ] + possibleSettingsPaths
}
guard let settingsPath = possibleSettingsPaths.first(where: { $0.exists }) else {
diff --git a/Sources/XcodeGenKit/SourceGenerator.swift b/Sources/XcodeGenKit/SourceGenerator.swift
index 8327012b..77ec842f 100644
--- a/Sources/XcodeGenKit/SourceGenerator.swift
+++ b/Sources/XcodeGenKit/SourceGenerator.swift
@@ -96,7 +96,8 @@ class SourceGenerator {
}
func getFileReference(path: Path, inPath: Path, name: String? = nil, sourceTree: PBXSourceTree = .group, lastKnownFileType: String? = nil) -> String {
- if let fileReference = fileReferencesByPath[path.string.lowercased()] {
+ let fileReferenceKey = path.string.lowercased()
+ if let fileReference = fileReferencesByPath[fileReferenceKey] {
return fileReference
} else {
let fileReferencePath = path.byRemovingBase(path: inPath)
@@ -105,17 +106,44 @@ class SourceGenerator {
fileReferenceName = nil
}
let lastKnownFileType = lastKnownFileType ?? PBXFileReference.fileType(path: path)
- let fileReference = createObject(
- id: path.byRemovingBase(path: spec.basePath).string,
- PBXFileReference(
+
+ if path.extension == "xcdatamodeld" {
+ let models = (try? path.children()) ?? []
+ let modelFileReference = models
+ .filter { $0.extension == "xcdatamodel" }
+ .sorted()
+ .map { path in
+ createObject(
+ id: path.byRemovingBase(path: spec.basePath).string,
+ PBXFileReference(
+ sourceTree: .group,
+ lastKnownFileType: "wrapper.xcdatamodel",
+ path: path.lastComponent
+ )
+ )
+ }
+ let versionGroup = addObject(id: fileReferencePath.string, XCVersionGroup(
+ currentVersion: modelFileReference.first?.reference,
+ path: fileReferencePath.string,
sourceTree: sourceTree,
- name: fileReferenceName,
- lastKnownFileType: lastKnownFileType,
- path: fileReferencePath.string
+ versionGroupType: "wrapper.xcdatamodel",
+ children: modelFileReference.map { $0.reference }
+ ))
+ fileReferencesByPath[fileReferenceKey] = versionGroup
+ return versionGroup
+ } else {
+ let fileReference = createObject(
+ id: path.byRemovingBase(path: spec.basePath).string,
+ PBXFileReference(
+ sourceTree: sourceTree,
+ name: fileReferenceName,
+ lastKnownFileType: lastKnownFileType,
+ path: fileReferencePath.string
+ )
)
- )
- fileReferencesByPath[path.string.lowercased()] = fileReference.reference
- return fileReference.reference
+ fileReferencesByPath[fileReferenceKey] = fileReference.reference
+ return fileReference.reference
+ }
}
}
@@ -125,7 +153,7 @@ class SourceGenerator {
}
if let fileExtension = path.extension {
switch fileExtension {
- case "swift", "m", "mm", "cpp", "c", "S": return .sources
+ case "swift", "m", "mm", "cpp", "c", "S", "xcdatamodeld": return .sources
case "h", "hh", "hpp", "ipp", "tpp", "hxx", "def": return .headers
case "xcconfig", "entitlements", "gpx", "lproj", "apns": return nil
default: return .resources
diff --git a/Tests/Fixtures/TestProject/App_iOS/Model.xcdatamodeld/Model.xcdatamodel/contents b/Tests/Fixtures/TestProject/App_iOS/Model.xcdatamodeld/Model.xcdatamodel/contents
new file mode 100644
index 00000000..a53d67bf
--- /dev/null
+++ b/Tests/Fixtures/TestProject/App_iOS/Model.xcdatamodeld/Model.xcdatamodel/contents
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Tests/Fixtures/TestProject/Project.xcodeproj/project.pbxproj b/Tests/Fixtures/TestProject/Project.xcodeproj/project.pbxproj
index 2559e577..d3eeabdc 100644
--- a/Tests/Fixtures/TestProject/Project.xcodeproj/project.pbxproj
+++ b/Tests/Fixtures/TestProject/Project.xcodeproj/project.pbxproj
@@ -44,6 +44,7 @@
BF_561304997165 /* Standalone.swift in Sources */ = {isa = PBXBuildFile; fileRef = FR_675266829517 /* Standalone.swift */; };
BF_612351978356 /* Interface.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = VG_264279911176 /* Interface.storyboard */; };
BF_624802436672 /* FrameworkFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = FR_172952167809 /* FrameworkFile.swift */; };
+ BF_670499288392 /* Model.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = VG_229021855709 /* Model.xcdatamodeld */; settings = {COMPILER_FLAGS = "-Werror"; }; };
BF_681504666330 = {isa = PBXBuildFile; fileRef = FR_825232110500 /* App_iOS.app */; };
BF_721498080533 /* ResourceFolder in Resources */ = {isa = PBXBuildFile; fileRef = FR_257073931060 /* ResourceFolder */; };
BF_729846993631 /* Alamofire.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FR_410645050443 /* Alamofire.framework */; };
@@ -163,6 +164,7 @@
FR_507023492251 /* App_watchOS Extension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = "App_watchOS Extension.appex"; sourceTree = BUILT_PRODUCTS_DIR; };
FR_525119120469 /* Framework.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Framework.framework; sourceTree = BUILT_PRODUCTS_DIR; };
FR_530852296303 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
+ FR_570918052822 /* Model.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = Model.xcdatamodel; sourceTree = ""; };
FR_587738154368 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
FR_602633703434 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.stringsdict; sourceTree = ""; };
FR_609193904586 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; };
@@ -378,6 +380,7 @@
VG_746876637628 /* Localizable.stringsdict */,
VG_118219888726 /* LocalizedStoryboard.storyboard */,
VG_609193904586 /* Main.storyboard */,
+ VG_229021855709 /* Model.xcdatamodeld */,
FR_481575785861 /* ViewController.swift */,
);
name = App;
@@ -928,6 +931,7 @@
buildActionMask = 2147483647;
files = (
BF_892119987440 /* AppDelegate.swift in Sources */,
+ BF_670499288392 /* Model.xcdatamodeld in Sources */,
BF_503484983186 /* MoreUnder.swift in Sources */,
BF_561304997165 /* Standalone.swift in Sources */,
BF_331192862207 /* ViewController.swift in Sources */,
@@ -2677,6 +2681,19 @@
defaultConfigurationName = "Production Debug";
};
/* End XCConfigurationList section */
+
+/* Begin XCVersionGroup section */
+ VG_229021855709 /* Model.xcdatamodeld */ = {
+ isa = XCVersionGroup;
+ children = (
+ FR_570918052822 /* Model.xcdatamodel */,
+ );
+ currentVersion = FR_570918052822 /* Model.xcdatamodel */;
+ path = Model.xcdatamodeld;
+ sourceTree = "";
+ versionGroupType = wrapper.xcdatamodel;
+ };
+/* End XCVersionGroup section */
};
rootObject = P_8448771205358 /* Project object */;
}
diff --git a/Tests/XcodeGenKitTests/FixtureTests.swift b/Tests/XcodeGenKitTests/FixtureTests.swift
index 897ad2e4..64114e46 100644
--- a/Tests/XcodeGenKitTests/FixtureTests.swift
+++ b/Tests/XcodeGenKitTests/FixtureTests.swift
@@ -11,11 +11,19 @@ func generate(specPath: Path, projectPath: Path) throws -> XcodeProj {
let generator = ProjectGenerator(spec: spec)
let project = try generator.generateProject()
let oldProject = try XcodeProj(path: projectPath)
+ let pbxProjPath = projectPath + XcodeProj.pbxprojPath(projectPath)
+ let oldProjectString: String = try pbxProjPath.read()
try project.write(path: projectPath, override: true)
+ let newProjectString: String = try pbxProjPath.read()
let newProject = try XcodeProj(path: projectPath)
- if newProject != oldProject {
- throw failure("\(projectPath.string) has changed. If change is legitimate commit the change and run test again")
+ let stringDiff = newProjectString != oldProjectString
+ if newProject != oldProject || stringDiff {
+ var message = "\(projectPath.string) has changed. If change is legitimate commit the change and run test again"
+ if stringDiff {
+ message += ":\n\n\(pbxProjPath):\n\(prettyFirstDifferenceBetweenStrings(oldProjectString, newProjectString))"
+ }
+ throw failure(message)
}
return newProject
diff --git a/Tests/XcodeGenKitTests/ProjectGeneratorTests.swift b/Tests/XcodeGenKitTests/ProjectGeneratorTests.swift
index 65a746af..8fec2c26 100644
--- a/Tests/XcodeGenKitTests/ProjectGeneratorTests.swift
+++ b/Tests/XcodeGenKitTests/ProjectGeneratorTests.swift
@@ -452,6 +452,30 @@ func projectGeneratorTests() {
try project.expectFile(paths: ["Sources", "A", "B", "b.swift"], buildPhase: .sources)
}
+ $0.it("generates core data models") {
+ let directories = """
+ Sources:
+ model.xcdatamodeld:
+ - model.xcdatamodel
+ """
+ try createDirectories(directories)
+
+ let target = Target(name: "Test", type: .application, platform: .iOS, sources: ["Sources"])
+ let spec = ProjectSpec(basePath: directoryPath, name: "Test", targets: [target])
+
+ let project = try getPbxProj(spec)
+ guard let fileReference = project.objects.fileReferences.first(where: { $0.value.nameOrPath == "model.xcdatamodel" }) else {
+ throw failure("Couldn't find model file reference")
+ }
+ guard let versionGroup = project.objects.versionGroups.values.first else {
+ throw failure("Couldn't find version group")
+ }
+ try expect(versionGroup.currentVersion) == fileReference.key
+ try expect(versionGroup.children) == [fileReference.key]
+ try expect(versionGroup.path) == "model.xcdatamodeld"
+ try expect(fileReference.value.path) == "model.xcdatamodel"
+ }
+
$0.it("handles duplicate names") {
let directories = """
Sources:
diff --git a/Tests/XcodeGenKitTests/SpecLoadingTests.swift b/Tests/XcodeGenKitTests/SpecLoadingTests.swift
index 5679e071..71a5bd0c 100644
--- a/Tests/XcodeGenKitTests/SpecLoadingTests.swift
+++ b/Tests/XcodeGenKitTests/SpecLoadingTests.swift
@@ -173,7 +173,7 @@ func specLoadingTests() {
"name": "Before Build",
"settingsTarget": "Target1",
],
- ]
+ ],
],
]
let scheme = try Scheme(name: "Scheme", jsonDictionary: schemeDictionary)
diff --git a/Tests/XcodeGenKitTests/StringDiff.swift b/Tests/XcodeGenKitTests/StringDiff.swift
new file mode 100644
index 00000000..5e1970ea
--- /dev/null
+++ b/Tests/XcodeGenKitTests/StringDiff.swift
@@ -0,0 +1,99 @@
+//https://gist.github.com/kristopherjohnson/543687c763cd6e524c91
+
+import Foundation
+
+/// Find first differing character between two strings
+///
+/// :param: s1 First String
+/// :param: s2 Second String
+///
+/// :returns: .DifferenceAtIndex(i) or .NoDifference
+public func firstDifferenceBetweenStrings(_ s1: String, _ s2: String) -> FirstDifferenceResult {
+ let len1 = s1.count
+ let len2 = s2.count
+
+ let lenMin = min(len1, len2)
+
+ for i in 0.. String {
+ let firstDifferenceResult = firstDifferenceBetweenStrings(s1, s2)
+
+ func diffString(at index: Int, _ s1: String, _ s2: String) -> String {
+ let markerArrow = "\u{2b06}" // "⬆"
+ let ellipsis = "\u{2026}" // "…"
+
+ /// Given a string and a range, return a string representing that substring.
+ ///
+ /// If the range starts at a position other than 0, an ellipsis
+ /// will be included at the beginning.
+ ///
+ /// If the range ends before the actual end of the string,
+ /// an ellipsis is added at the end.
+ func windowSubstring(_ s: String, _ range: NSRange) -> String {
+ let validRange = NSMakeRange(range.location, min(range.length, s.count - range.location))
+ let substring = (s as NSString).substring(with: validRange)
+
+ let prefix = range.location > 0 ? ellipsis : ""
+ let suffix = (s.count - range.location > range.length) ? ellipsis : ""
+
+ return "\(prefix)\(substring)\(suffix)"
+ }
+
+ // Show this many characters before and after the first difference
+ let windowLength = previewPrefixLength + 1 + previewSuffixLength
+
+ let windowIndex = max(index - previewPrefixLength, 0)
+ let windowRange = NSMakeRange(windowIndex, windowLength)
+
+ let sub1 = windowSubstring(s1, windowRange)
+ let sub2 = windowSubstring(s2, windowRange)
+
+ let markerPosition = min(previewSuffixLength, index) + (windowIndex > 0 ? 1 : 0)
+
+ let markerPrefix = String(repeating: " ", count: markerPosition)
+ let markerLine = "\(markerPrefix)\(markerArrow)"
+
+ return "Difference at index \(index):\n\(sub1)\n\(sub2)\n\(markerLine)"
+ }
+
+ switch firstDifferenceResult {
+ case .NoDifference: return "No difference"
+ case .DifferenceAtIndex(let index): return diffString(at: index, s1, s2)
+ }
+}
+
+
+/// Result type for firstDifferenceBetweenStrings()
+public enum FirstDifferenceResult {
+ /// Strings are identical
+ case NoDifference
+
+ /// Strings differ at the specified index.
+ ///
+ /// This could mean that characters at the specified index are different,
+ /// or that one string is longer than the other
+ case DifferenceAtIndex(Int)
+}