mirror of
https://github.com/yonaskolb/XcodeGen.git
synced 2024-10-27 06:21:28 +03:00
Merge pull request #249 from yonaskolb/generate_code_data_models
Generate code data models properly
This commit is contained in:
commit
1e6aef042e
@ -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 {
|
||||
|
@ -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
|
||||
|
@ -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>
|
@ -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 = "<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>"; };
|
||||
@ -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 = "<group>";
|
||||
versionGroupType = wrapper.xcdatamodel;
|
||||
};
|
||||
/* End XCVersionGroup section */
|
||||
};
|
||||
rootObject = P_8448771205358 /* Project object */;
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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:
|
||||
|
@ -173,7 +173,7 @@ func specLoadingTests() {
|
||||
"name": "Before Build",
|
||||
"settingsTarget": "Target1",
|
||||
],
|
||||
]
|
||||
],
|
||||
],
|
||||
]
|
||||
let scheme = try Scheme(name: "Scheme", jsonDictionary: schemeDictionary)
|
||||
|
99
Tests/XcodeGenKitTests/StringDiff.swift
Normal file
99
Tests/XcodeGenKitTests/StringDiff.swift
Normal file
@ -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)
|
||||
}
|
Loading…
Reference in New Issue
Block a user