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

Add ability to set an order of groups #613

Merged
merged 11 commits into from
Apr 28, 2020
Merged
Show file tree
Hide file tree
Changes from 4 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
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

## Next Version

#### Added
- Added an ability to set an order of groups via `options.groupOrder` [#480](https://github.com/yonaskolb/XcodeGen/pull/613) @Beniamiiin

## 2.6.0

#### Added
Expand Down
22 changes: 22 additions & 0 deletions Docs/ProjectSpec.md
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ Note that target names can also be changed by adding a `name` property to a targ
- `none` - sorted alphabetically with all the other files
- `top` - at the top, before files
- `bottom` (default) - at the bottom, after other files
- [ ] **groupsOrder**: **[[GroupOrder]](#groupOrder)** - An order of groups.
Copy link
Owner

Choose a reason for hiding this comment

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

Not in love with the name groupsOrder, but I also can't think of something better right now.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Not only you, I don't like it too, but I couldn't find more lovely name :( I'll try to think about it again.

Copy link
Collaborator

Choose a reason for hiding this comment

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

How about groupOrdering?

- [ ] **transitivelyLinkDependencies**: **Bool** - If this is `true` then targets will link to the dependencies of their target dependencies. If a target should embed its dependencies, such as application and test bundles, it will embed these transitive dependencies as well. Some complex setups might want to set this to `false` and explicitly specify dependencies at every level. Targets can override this with [Target](#target).transitivelyLinkDependencies. Defaults to `false`.
- [ ] **generateEmptyDirectories**: **Bool** - If this is `true` then empty directories will be added to project too else will be missed. Defaults to `false`.
- [ ] **findCarthageFrameworks**: **Bool** - When this is set to `true`, all the invididual frameworks for Carthage dependencies will automatically be found. This property can be overriden individually for each carthage dependency - for more details see See **findFrameworks** in the [Dependency](#dependency) section. Defaults to `false`.
Expand All @@ -125,6 +126,27 @@ options:
tvOS: "10.0"
```

### GroupOrder

Describe an order of groups. Available parameters:

- [ ] **pattern**: **String** - A group name pattern. Can be just a single string and also can be a regex pattern. Optional option, if you don't set it, it will pattern for the main group, i.e. the project.
- [ ] **order**: **[String]** - An order of groups.
- [ ] **fileSortPosition**: **String** - Where files are sorted in relation to other groups. Either:
- `top` (default) - at the top, before groups
- `bottom` - at the bottom, after other groups

Beniamiiin marked this conversation as resolved.
Show resolved Hide resolved
```yaml
options:
groupsOrder:
- order: [Sources, Resources, Tests, Support files, Configurations]
- pattern: '^.*Screen$'
order: [View, Presenter, Interactor, Entities, Assembly]
fileSortPosition: bottom
```

In this example, we set up the order of two groups. First one is the main group, i.e. the project, note that in this case, we shouldn't set `pattern` option. The second group order is for groups whose names ends with `Screen` and note that we set `fileSortPosition`, it means that files in this group will be located after groups.

### Configs

Each config maps to a build type of either `debug` or `release` which will then apply default build settings to the project. Any value other than `debug` or `release` (for example `none`), will mean no default build settings will be applied to the project.
Expand Down
33 changes: 33 additions & 0 deletions Sources/ProjectSpec/GroupOrder.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import Foundation
import JSONUtilities

/// Describes an order of groups.
public struct GroupOrder: Equatable {

public enum FileSortPosition: String {
/// groups are at the top
case top
/// groups are at the bottom
case bottom
}

/// A group name pattern.
public var pattern: String

/// Subgroups orders.
public var order: [String]

/// File sort position in a group.
public var fileSortPosition: FileSortPosition = .top

}

extension GroupOrder: JSONObjectConvertible {

public init(jsonDictionary: JSONDictionary) throws {
pattern = jsonDictionary.json(atKeyPath: "pattern") ?? ""
order = jsonDictionary.json(atKeyPath: "order") ?? []
fileSortPosition = jsonDictionary.json(atKeyPath: "fileSortPosition") ?? .top
}

}
4 changes: 4 additions & 0 deletions Sources/ProjectSpec/SpecOptions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ public struct SpecOptions: Equatable {
public var defaultConfig: String?
public var transitivelyLinkDependencies: Bool
public var groupSortPosition: GroupSortPosition
public var groupsOrder: [GroupOrder]
public var generateEmptyDirectories: Bool
public var findCarthageFrameworks: Bool

Expand Down Expand Up @@ -81,6 +82,7 @@ public struct SpecOptions: Equatable {
defaultConfig: String? = nil,
transitivelyLinkDependencies: Bool = transitivelyLinkDependenciesDefault,
groupSortPosition: GroupSortPosition = groupSortPositionDefault,
groupsOrder: [GroupOrder] = [],
generateEmptyDirectories: Bool = generateEmptyDirectoriesDefault,
findCarthageFrameworks: Bool = findCarthageFrameworksDefault
) {
Expand All @@ -100,6 +102,7 @@ public struct SpecOptions: Equatable {
self.defaultConfig = defaultConfig
self.transitivelyLinkDependencies = transitivelyLinkDependencies
self.groupSortPosition = groupSortPosition
self.groupsOrder = groupsOrder
self.generateEmptyDirectories = generateEmptyDirectories
self.findCarthageFrameworks = findCarthageFrameworks
}
Expand Down Expand Up @@ -127,6 +130,7 @@ extension SpecOptions: JSONObjectConvertible {
defaultConfig = jsonDictionary.json(atKeyPath: "defaultConfig")
transitivelyLinkDependencies = jsonDictionary.json(atKeyPath: "transitivelyLinkDependencies") ?? SpecOptions.transitivelyLinkDependenciesDefault
groupSortPosition = jsonDictionary.json(atKeyPath: "groupSortPosition") ?? SpecOptions.groupSortPositionDefault
groupsOrder = jsonDictionary.json(atKeyPath: "groupsOrder") ?? []
generateEmptyDirectories = jsonDictionary.json(atKeyPath: "generateEmptyDirectories") ?? SpecOptions.generateEmptyDirectoriesDefault
findCarthageFrameworks = jsonDictionary.json(atKeyPath: "findCarthageFrameworks") ?? SpecOptions.findCarthageFrameworksDefault
}
Expand Down
11 changes: 11 additions & 0 deletions Sources/ProjectSpec/StringExtensions.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import Foundation

public extension String {

func isMatch(to regex: String) -> Bool {
guard let regex = try? NSRegularExpression(pattern: regex) else { return false }
Beniamiiin marked this conversation as resolved.
Show resolved Hide resolved
let range = NSRange(location: 0, length: self.utf16.count)
return regex.firstMatch(in: self, options: [], range: range) != nil
}

}
38 changes: 24 additions & 14 deletions Sources/XcodeGenKit/PBXProjGenerator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -376,23 +376,33 @@ public class PBXProjGenerator {

func sortGroups(group: PBXGroup) {
// sort children
let children = group.children
.sorted { child1, child2 in
let sortOrder1 = child1.getSortOrder(groupSortPosition: project.options.groupSortPosition)
let sortOrder2 = child2.getSortOrder(groupSortPosition: project.options.groupSortPosition)

if sortOrder1 != sortOrder2 {
return sortOrder1 < sortOrder2
} else {
if child1.nameOrPath != child2.nameOrPath {
return child1.nameOrPath.localizedStandardCompare(child2.nameOrPath) == .orderedAscending
if let groupOrder = project.options.groupsOrder.first(where: { group.nameOrPath == $0.pattern || group.nameOrPath.isMatch(to: $0.pattern) }) {
Copy link
Owner

Choose a reason for hiding this comment

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

nameOrPath evaluates to name ?? path ?? "". That means groups who have a different path than just the name won't be supported.
In either case doing something like Group1/Group2/* won't work as the name is just the end and the path is a relative path.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Okay, so should I to use just name?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@yonaskolb I need your help. I don't understand what is the correct way to do it.

let files = group.children.filter { $0 is PBXFileReference }
Beniamiiin marked this conversation as resolved.
Show resolved Hide resolved
let groups = groupOrder.order
brentleyjones marked this conversation as resolved.
Show resolved Hide resolved
.compactMap { groupName in
group.children.first(where: { $0 is PBXGroup && $0.nameOrPath == groupName })
}

group.children = groupOrder.fileSortPosition == .top ? files + groups : groups + files
} else {
let children = group.children
.sorted { child1, child2 in
let sortOrder1 = child1.getSortOrder(groupSortPosition: project.options.groupSortPosition)
let sortOrder2 = child2.getSortOrder(groupSortPosition: project.options.groupSortPosition)

if sortOrder1 != sortOrder2 {
return sortOrder1 < sortOrder2
} else {
return child1.context ?? "" < child2.context ?? ""
if child1.nameOrPath != child2.nameOrPath {
return child1.nameOrPath.localizedStandardCompare(child2.nameOrPath) == .orderedAscending
} else {
return child1.context ?? "" < child2.context ?? ""
}
}
}
}
group.children = children.filter { $0 != group }

group.children = children.filter { $0 != group }
}

// sort sub groups
let childGroups = group.children.compactMap { $0 as? PBXGroup }
childGroups.forEach(sortGroups)
Expand Down