-
-
Notifications
You must be signed in to change notification settings - Fork 320
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
Helper methods required for adding build file to the target #213
Changes from 4 commits
12a5cd0
6e2b308
a7accb9
d2ca466
2613306
d5bc256
ce3cfda
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,5 @@ | ||
import Foundation | ||
import PathKit | ||
|
||
// MARK: - PBXProj.Objects Extension (Public) | ||
|
||
|
@@ -15,7 +16,176 @@ public extension PBXProj.Objects { | |
targets.append(contentsOf: Array(aggregateTargets.values) as [PBXTarget]) | ||
return targets.filter { $0.name == name } | ||
} | ||
|
||
|
||
/// Retruns target's sources build phase. | ||
/// | ||
/// - Parameter target: target object. | ||
/// - Returns: target's sources build phase, if found. | ||
public func sourcesBuildPhase(target: PBXTarget) -> PBXSourcesBuildPhase? { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nice additions @ilyapuchka π There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this is exactly what is being used, this is extension on PBXObjects. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. π sorry, missed that. |
||
return sourcesBuildPhases.first(where: { target.buildPhases.contains($0.key) })?.value | ||
} | ||
|
||
/// Returns all files in target's sources build phase. | ||
/// | ||
/// - Parameter target: target object. | ||
/// - Returns: all files in target's sources build phase, or empty array if sources build phase is not found. | ||
public func sourceFiles(target: PBXTarget) -> [PBXFileElement] { | ||
return sourcesBuildPhase(target: target)?.files | ||
.flatMap({ buildFiles[$0]?.fileRef }) | ||
.flatMap(getFileElement(reference:)) ?? [] | ||
} | ||
|
||
/// Returns group with the given name contained in the given parent group and its reference. | ||
/// | ||
/// - Parameter groupName: group name. | ||
/// - Parameter inGroup: parent group. | ||
/// - Returns: group with the given name contained in the given parent group and its reference. | ||
public func group(named groupName: String, inGroup: PBXGroup) -> (String, PBXGroup)? { | ||
let children = inGroup.children | ||
return groups.first(where: { | ||
children.contains($0.key) && ($0.value.name == groupName || $0.value.path == groupName) | ||
}) | ||
} | ||
|
||
/// Adds new group with the give name to the given parent group. | ||
/// Group name can be a path with components separated by `/`. | ||
/// Will create new groups for intermediate path components or use existing groups. | ||
/// Returns all new or existing groups in the path and their references. | ||
/// | ||
/// - Parameters: | ||
/// - groupName: group name, can be a path with components separated by `/` | ||
/// - toGroup: parent group | ||
/// - options: additional options, default is empty set. | ||
/// - Returns: all new or existing groups in the path and their references. | ||
public func addGroup(named groupName: String, to toGroup: PBXGroup, options: GroupAddingOptions = []) -> [(String, PBXGroup)] { | ||
return addGroups(groupName.components(separatedBy: "/"), to: toGroup, options: options) | ||
} | ||
|
||
private func addGroups(_ groupNames: [String], to toGroup: PBXGroup, options: GroupAddingOptions) -> [(String, PBXGroup)] { | ||
guard !groupNames.isEmpty else { return [] } | ||
let newGroup = createOrGetGroup(named: groupNames[0], in: toGroup, options: options) | ||
return [newGroup] + addGroups(Array(groupNames.dropFirst()), to: newGroup.1, options: options) | ||
} | ||
|
||
private func createOrGetGroup(named groupName: String, in parentGroup: PBXGroup, options: GroupAddingOptions) -> (String, PBXGroup) { | ||
if let existingGroup = group(named: groupName, inGroup: parentGroup) { | ||
return existingGroup | ||
} | ||
|
||
let newGroup = PBXGroup( | ||
children: [], | ||
sourceTree: .group, | ||
name: groupName, | ||
path: options.contains(.withoutFolder) ? nil : groupName | ||
) | ||
let reference = generateReference(newGroup, groupName) | ||
addObject(newGroup, reference: reference) | ||
parentGroup.children.append(reference) | ||
return (reference, newGroup) | ||
} | ||
|
||
/// Adds file at the give path to the project or returns existing file and its reference. | ||
/// | ||
/// - Parameters: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. These parameter descriptions do not match function. |
||
/// - filePath: path to the file | ||
/// - sourceTree: file source tree, default is `.group`. | ||
/// - name: file name, by default gets file name from the path | ||
/// - Returns: new or existing file and its reference. | ||
public func addFile(at filePath: Path, sourceRoot: Path) throws -> (String, PBXFileReference) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please add unit test for this function. At the moment, I have a feeling that |
||
guard filePath.isFile else { throw XCodeProjEditingError.notAFile(path: filePath) } | ||
|
||
if let existingFileReference = fileReferences.first(where: { | ||
filePath == fullPath(fileElement: $0.value, reference: $0.key, sourceRoot: sourceRoot) | ||
}) { | ||
return existingFileReference | ||
} | ||
|
||
let fileReference = PBXFileReference( | ||
sourceTree: .group, | ||
name: filePath.lastComponent, | ||
explicitFileType: PBXFileReference.fileType(path: filePath), | ||
lastKnownFileType: PBXFileReference.fileType(path: filePath), | ||
path: filePath.string | ||
) | ||
let reference = generateReference(fileReference, filePath.string) | ||
addObject(fileReference, reference: reference) | ||
return (reference, fileReference) | ||
} | ||
|
||
/// Adds file to the given group. | ||
/// If group already contains file with given reference method does nothing. | ||
/// | ||
/// - Parameter group: group to add file to | ||
/// - Parameter reference: file reference | ||
public func addFile(toGroup group: PBXGroup, reference: String) { | ||
guard !group.children.contains(reference) else { return } | ||
group.children.append(reference) | ||
} | ||
|
||
/// Adds file to the given target's sources build phase or returns existing build file and its reference. | ||
/// If target's sources build phase can't be found returns nil. | ||
/// | ||
/// - Parameter target: target obejct | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. type on work |
||
/// - Parameter reference: file reference | ||
/// - Returns: new or existing build file and its reference | ||
public func addBuildFile(toTarget target: PBXTarget, reference: String) -> (String, PBXBuildFile)? { | ||
guard let sourcesBuildPhase = sourcesBuildPhase(target: target) else { return nil } | ||
if let existingBuildFile = buildFiles.first(where: { $0.value.fileRef == reference }) { | ||
return existingBuildFile | ||
} | ||
|
||
let buildFile = PBXBuildFile(fileRef: reference) | ||
let reference = generateReference(buildFile, reference) | ||
addObject(buildFile, reference: reference) | ||
sourcesBuildPhase.files.append(reference) | ||
return (reference, buildFile) | ||
} | ||
|
||
public func fullPath(fileElement: PBXFileElement, reference: String, sourceRoot: Path) -> Path? { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. could you add few unit tests to this function. I am not sure what cases it has to cover and how. Especially with groups. |
||
switch fileElement.sourceTree { | ||
case .absolute?: | ||
return fileElement.path.flatMap({ Path($0) }) | ||
case .sourceRoot?: | ||
return fileElement.path.flatMap({ Path($0, relativeTo: sourceRoot) }) | ||
case .group?: | ||
guard let group = groups.first(where: { $0.value.children.contains(reference) }) else { return sourceRoot } | ||
guard let groupPath = fullPath(fileElement: group.value, reference: group.key, sourceRoot: sourceRoot) else { return nil } | ||
return fileElement.path.flatMap({ Path($0, relativeTo: groupPath) }) | ||
default: | ||
return nil | ||
} | ||
} | ||
|
||
} | ||
|
||
public struct GroupAddingOptions: OptionSet { | ||
public let rawValue: Int | ||
public init(rawValue: Int) { | ||
self.rawValue = rawValue | ||
} | ||
/// Create group without reference to folder | ||
static let withoutFolder = GroupAddingOptions(rawValue: 1 << 0) | ||
} | ||
|
||
public enum XCodeProjEditingError: Error, CustomStringConvertible { | ||
case notAFile(path: Path) | ||
|
||
public var description: String { | ||
switch self { | ||
case .notAFile(let path): | ||
return "\(path) is not a file path" | ||
} | ||
} | ||
} | ||
|
||
extension Path { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it should be made |
||
init(_ string: String, relativeTo relativePath: Path) { | ||
var path = Path(string) | ||
if !path.isAbsolute { | ||
path = (relativePath + path).absolute() | ||
} | ||
self.init(path.string) | ||
} | ||
} | ||
|
||
// MARK: - PBXProj.Objects Extension (Internal) | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is
fatalError
suitable here?Maybe we can make
rootGroup
optional (public var rootGroup: PBXGroup?
).There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it's more suitable than requiring to unwrap this property which will be nil only in specific situations when accessed before project is correctly setup. In my opinion it would be better if API would not allow to create objects in such invalid or incomplete states (what it does now as far as I understand), but not sure what it will take to change it in this direction.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree with @ilyapuchka here. If we use an optional attribute, the developer could wrongly access that attribute and get unexpected behaviours as a result. If we
fatalErrror
they would know that they are doing something wrong and it'd save them some debugging time.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Understood!π