From b97261db3615c84091b2d03a8d5bf95ceece01d0 Mon Sep 17 00:00:00 2001 From: Yonas Kolb Date: Mon, 26 Feb 2018 16:12:04 +1100 Subject: [PATCH 1/3] generate code data models properly --- Sources/XcodeGenKit/SettingsBuilder.swift | 4 +- Sources/XcodeGenKit/SourceGenerator.swift | 50 +++++++++++++++---- .../Model.xcdatamodel/contents | 7 +++ .../Project.xcodeproj/project.pbxproj | 17 +++++++ .../ProjectGeneratorTests.swift | 24 +++++++++ Tests/XcodeGenKitTests/SpecLoadingTests.swift | 2 +- 6 files changed, 90 insertions(+), 14 deletions(-) create mode 100644 Tests/Fixtures/TestProject/App_iOS/Model.xcdatamodeld/Model.xcdatamodel/contents 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..18d5095e 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: path.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 b6ceacc3..3f7dec5c 100644 --- a/Tests/Fixtures/TestProject/Project.xcodeproj/project.pbxproj +++ b/Tests/Fixtures/TestProject/Project.xcodeproj/project.pbxproj @@ -27,6 +27,7 @@ BF_303822704662 = {isa = PBXBuildFile; fileRef = FR_472296042419 /* Framework.framework */; }; BF_314868376788 /* Localizable.stringsdict in Resources */ = {isa = PBXBuildFile; fileRef = VG_746876637628 /* Localizable.stringsdict */; }; BF_324363145049 /* Framework.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FR_472296042419 /* Framework.framework */; }; + BF_329004524843 /* Model.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = VG_453922120168 /* Model.xcdatamodeld */; settings = {COMPILER_FLAGS = "-Werror"; }; }; BF_331192862207 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FR_481575785861 /* ViewController.swift */; settings = {COMPILER_FLAGS = "-Werror"; }; }; BF_334778067417 = {isa = PBXBuildFile; fileRef = FR_783122899910 /* App_iOS_Tests.xctest */; }; BF_360196406184 /* TestProjectTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FR_722239415598 /* TestProjectTests.swift */; }; @@ -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_453922120168 /* Model.xcdatamodeld */, FR_481575785861 /* ViewController.swift */, ); name = App; @@ -928,6 +931,7 @@ buildActionMask = 2147483647; files = ( BF_892119987440 /* AppDelegate.swift in Sources */, + BF_329004524843 /* 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_453922120168 /* 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/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) From b959e878b3524238950af186d17d4d1f8b620d8a Mon Sep 17 00:00:00 2001 From: Yonas Kolb Date: Mon, 26 Feb 2018 16:42:11 +1100 Subject: [PATCH 2/3] add string diffing for fixture test --- Tests/XcodeGenKitTests/FixtureTests.swift | 12 ++- Tests/XcodeGenKitTests/StringDiff.swift | 99 +++++++++++++++++++++++ 2 files changed, 109 insertions(+), 2 deletions(-) create mode 100644 Tests/XcodeGenKitTests/StringDiff.swift 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/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) +} From 5e2f427c6f67ad707375811c2eac020ab2cf2eac Mon Sep 17 00:00:00 2001 From: Yonas Kolb Date: Mon, 26 Feb 2018 16:56:24 +1100 Subject: [PATCH 3/3] fix XCVersionGroup reference id stability --- Sources/XcodeGenKit/SourceGenerator.swift | 2 +- .../TestProject/Project.xcodeproj/project.pbxproj | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Sources/XcodeGenKit/SourceGenerator.swift b/Sources/XcodeGenKit/SourceGenerator.swift index 18d5095e..77ec842f 100644 --- a/Sources/XcodeGenKit/SourceGenerator.swift +++ b/Sources/XcodeGenKit/SourceGenerator.swift @@ -122,7 +122,7 @@ class SourceGenerator { ) ) } - let versionGroup = addObject(id: path.string, XCVersionGroup( + let versionGroup = addObject(id: fileReferencePath.string, XCVersionGroup( currentVersion: modelFileReference.first?.reference, path: fileReferencePath.string, sourceTree: sourceTree, diff --git a/Tests/Fixtures/TestProject/Project.xcodeproj/project.pbxproj b/Tests/Fixtures/TestProject/Project.xcodeproj/project.pbxproj index 3f7dec5c..f737c37c 100644 --- a/Tests/Fixtures/TestProject/Project.xcodeproj/project.pbxproj +++ b/Tests/Fixtures/TestProject/Project.xcodeproj/project.pbxproj @@ -27,7 +27,6 @@ BF_303822704662 = {isa = PBXBuildFile; fileRef = FR_472296042419 /* Framework.framework */; }; BF_314868376788 /* Localizable.stringsdict in Resources */ = {isa = PBXBuildFile; fileRef = VG_746876637628 /* Localizable.stringsdict */; }; BF_324363145049 /* Framework.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FR_472296042419 /* Framework.framework */; }; - BF_329004524843 /* Model.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = VG_453922120168 /* Model.xcdatamodeld */; settings = {COMPILER_FLAGS = "-Werror"; }; }; BF_331192862207 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FR_481575785861 /* ViewController.swift */; settings = {COMPILER_FLAGS = "-Werror"; }; }; BF_334778067417 = {isa = PBXBuildFile; fileRef = FR_783122899910 /* App_iOS_Tests.xctest */; }; BF_360196406184 /* TestProjectTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FR_722239415598 /* TestProjectTests.swift */; }; @@ -45,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 */; }; @@ -380,7 +380,7 @@ VG_746876637628 /* Localizable.stringsdict */, VG_118219888726 /* LocalizedStoryboard.storyboard */, VG_609193904586 /* Main.storyboard */, - VG_453922120168 /* Model.xcdatamodeld */, + VG_229021855709 /* Model.xcdatamodeld */, FR_481575785861 /* ViewController.swift */, ); name = App; @@ -931,7 +931,7 @@ buildActionMask = 2147483647; files = ( BF_892119987440 /* AppDelegate.swift in Sources */, - BF_329004524843 /* Model.xcdatamodeld 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 */, @@ -2683,7 +2683,7 @@ /* End XCConfigurationList section */ /* Begin XCVersionGroup section */ - VG_453922120168 /* Model.xcdatamodeld */ = { + VG_229021855709 /* Model.xcdatamodeld */ = { isa = XCVersionGroup; children = ( FR_570918052822 /* Model.xcdatamodel */,