Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Generate code data models properly #249

Merged
merged 3 commits into from
Feb 26, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Sources/XcodeGenKit/SettingsBuilder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
50 changes: 39 additions & 11 deletions Sources/XcodeGenKit/SourceGenerator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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
}
}
}

Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="13772" systemVersion="17D47" minimumToolsVersion="Automatic" sourceLanguage="Swift" userDefinedModelVersionIdentifier="">
<entity name="Entity" representedClassName="Entity" syncable="YES" codeGenerationType="class"/>
<elements>
<element name="Entity" positionX="-63" positionY="-18" width="128" height="45"/>
</elements>
</model>
17 changes: 17 additions & 0 deletions Tests/Fixtures/TestProject/Project.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -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 */; };
Expand Down Expand Up @@ -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 = "<group>"; };
FR_570918052822 /* Model.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = Model.xcdatamodel; sourceTree = "<group>"; };
FR_587738154368 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
FR_602633703434 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
FR_609193904586 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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 */,
Expand Down Expand Up @@ -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 = "<group>";
versionGroupType = wrapper.xcdatamodel;
};
/* End XCVersionGroup section */
};
rootObject = P_8448771205358 /* Project object */;
}
12 changes: 10 additions & 2 deletions Tests/XcodeGenKitTests/FixtureTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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))"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice👍

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, CI was failing so I needed this to see why 😄

}
throw failure(message)
}

return newProject
Expand Down
24 changes: 24 additions & 0 deletions Tests/XcodeGenKitTests/ProjectGeneratorTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
2 changes: 1 addition & 1 deletion Tests/XcodeGenKitTests/SpecLoadingTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ func specLoadingTests() {
"name": "Before Build",
"settingsTarget": "Target1",
],
]
],
],
]
let scheme = try Scheme(name: "Scheme", jsonDictionary: schemeDictionary)
Expand Down
99 changes: 99 additions & 0 deletions Tests/XcodeGenKitTests/StringDiff.swift
Original file line number Diff line number Diff line change
@@ -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..<lenMin {
if (s1 as NSString).character(at: i) != (s2 as NSString).character(at: i) {
return .DifferenceAtIndex(i)
}
}

if len1 < len2 {
return .DifferenceAtIndex(len1)
}

if len2 < len1 {
return .DifferenceAtIndex(len2)
}

return .NoDifference
}


/// Create a formatted String representation of difference between strings
///
/// :param: s1 First string
/// :param: s2 Second string
///
/// :returns: a string, possibly containing significant whitespace and newlines
public func prettyFirstDifferenceBetweenStrings(_ s1: String, _ s2: String, previewPrefixLength: Int = 25, previewSuffixLength:Int = 25) -> 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)
}