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 support for simple files excludes from sources #5. #135

Merged
merged 7 commits into from
Nov 10, 2017

Conversation

TheCoordinator
Copy link
Contributor

Hi @yonaskolb,

I understand you may be working on a solution for excludes as hinted in #25, but for now this seems to be resolving the immediate issue of excluding file(s) given a source path.

Please have a look and let me know if you have any feedbacks.

Copy link
Owner

@yonaskolb yonaskolb left a comment

Choose a reason for hiding this comment

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

Nice one @PeymanKh, thanks for this! This is great start to the feature!

@@ -43,6 +45,10 @@ extension Source: JSONObjectConvertible {
let maybeCompilerFlagsArray: [String]? = jsonDictionary.json(atKeyPath: "compilerFlags")
compilerFlags = maybeCompilerFlagsArray ??
maybeCompilerFlagsString.map{ $0.split(separator: " ").map{ String($0) } } ?? []
let maybeExcludesString: String? = jsonDictionary.json(atKeyPath: "excludes")
Copy link
Owner

Choose a reason for hiding this comment

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

We don't need to allow a string version like the compiler flags. Compiler flags were special as they are often defined as a single string in xcode and xcconfig files

@@ -568,6 +568,7 @@ public class PBXProjGenerator {
(try path.children().sorted(), path)

let excludedFiles: [String] = [".DS_Store"]
let sourceExcludedFiles: [String] = source.excludes.map { "\(source.path)/\($0)" }
Copy link
Owner

Choose a reason for hiding this comment

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

This is a good start. Though ideally I think we'd like to be able to specify excludes via a glob pattern (eg **.DS_Store) PathKit has build in support for glob patterns.
Or at the very least to specify certain file types which could be done with some simple custom logic *.filetype

And would you want to exclude a folder? I don't know, maybe it's not useful?

Source(path: "sourceWithCompilerFlagsExcludes", compilerFlags: ["-Werror"], excludes: ["Foo.swift"]),
Source(path: "sourceWithCompilerFlagsExcludesStr", compilerFlags: ["-Werror", "-Wextra"], excludes: ["Foo.swift", "Bar.swift"])]

try expect(target1.sources) == target1SourcesExpect
try expect(target2.sources) == [Source(path: "source3")]
}
Copy link
Owner

Choose a reason for hiding this comment

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

It would be great to have some tests in ProjectGeneratorTests under "Sources". You would need to make use of the getFileReference function at the bottom, maybe with a new expectMissingFile (see expectFile for example)

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 Thanks for the feedback.

I'm absolutely in for having global patterns and in my opinion excluding the whole directory should also be an option.

On a different note, do you also think it makes sense to have includes as part of Source?

Copy link
Owner

Choose a reason for hiding this comment

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

I'm not sure it would make sense to have includes as part of the Source, as that's what the path does. To have multiple sources paths you create multiple sources. But that does raise the question whether excludes should be part of the target and not Source, so that if you have multiple sources you only have to define excludes once. But then again such an approach would probably be used for simply excluding specific file types, and that could be done in the project options level...

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fair enough. Since I'm migrating all the my current work's project files to use Xcodegen instead, felt it can be a good option, but I agree with you, we can get carried away with it.

So i've already implemented pattern matching for excludes for both files and directories, have bumped into an issue with one of the test cases Sources->generates file sources.

Is including files as a source path supposed to work in general?

Here's the test case.

$0.it("generates file sources") {
                let directories = """
                Sources:
                  A:
                    - a.swift
                    - B:
                      - b.swift
                      - c.jpg
                """
                try createDirectories(directories)

                target.sources = [
                    "Sources/A/a.swift",
                    "Sources/A/B/b.swift",
                    "Sources/A/B/c.jpg",
                ]
                spec.targets = [target]

                let project = try getPbxProj(spec)
                try project.expectFile(paths: ["Sources/A", "a.swift"], names: ["A", "a.swift"], buildPhase: .sources)
                try project.expectFile(paths: ["Sources/A/B", "b.swift"], names: ["B", "b.swift"], buildPhase: .sources)
                try project.expectFile(paths: ["Sources/A/B", "c.jpg"], names: ["B", "c.jpg"], buildPhase: .resources)
            }

When this gets resolved I'll push so that you can have a look at the changes.

Copy link
Owner

Choose a reason for hiding this comment

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

Yeah adding file sources was added recently via #106

@@ -568,6 +568,7 @@ public class PBXProjGenerator {
(try path.children().sorted(), path)

let excludedFiles: [String] = [".DS_Store"]
Copy link
Owner

Choose a reason for hiding this comment

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

Just a note, these default excludedFiles should be moved up into the spec options at some point.

@TheCoordinator
Copy link
Contributor Author

@yonaskolb There you go, updated with pattern matching and tests.

let maybeExcludesArray: [String]? = jsonDictionary.json(atKeyPath: "excludes")
excludes = maybeExcludesArray ??
maybeExcludesString.map{ $0.split(separator: " ").map{ String($0) } } ?? []
excludes = maybeExcludesArray ?? []
Copy link
Owner

Choose a reason for hiding this comment

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

You can just replace this with excludes = jsonDictionary.json(atKeyPath: "excludes") ?? []

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

Copy link
Owner

@yonaskolb yonaskolb left a comment

Choose a reason for hiding this comment

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

Looking good! I left some comments

@@ -647,4 +657,52 @@ public class PBXProjGenerator {
groups.insert(group, at: 0)
return (allSourceFiles, groups)
}

func getSourceChildren(sourceMetadata source: Source, dirPath: Path) throws -> [Path] {
let excludedFiles = [".DS_Store"].map { dirPath + Path($0) }
Copy link
Owner

Choose a reason for hiding this comment

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

Can we call this defaultExcludedFiles

}
}

func getSourceExcludes(sourceMetadata source: Source, dirPath: Path) -> [Path] {
Copy link
Owner

Choose a reason for hiding this comment

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

Can we make this func live within getSourceChildren as a nested function, as it's not used anywhere else. There are so many top level source functions now that I want to cut down on them

Copy link
Owner

Choose a reason for hiding this comment

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

We could also just path the excludes here. It doesn't need to know about the whole Source

return [$0]
}

return Path.glob("\($0.string)*/**") + Path.glob("\($0.string)**")
Copy link
Owner

Choose a reason for hiding this comment

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

Could you explain what this is doing? Is this finding all files within the directory? If so you can use $0.recursiveChildren

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, to exclude all files and directories recursively.

OK I'll try that.

let excludes = [
"B",
"*.h",
"f.swift"
Copy link
Owner

Choose a reason for hiding this comment

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

If we were using true glob patterns, f.swift wouldn't be removed, as it's in sub directory. Same goes for .h files that are in sub directories. That might be ok though, as this format is easier? What do you think?

Copy link
Owner

Choose a reason for hiding this comment

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

Is it currently possible to exclude certain files from a sub directory as we're not using the exact glob spec? If so can we add a case eg. J/*.h

Copy link
Contributor Author

@TheCoordinator TheCoordinator Nov 7, 2017

Choose a reason for hiding this comment

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

So the glob patterns we are using are recursive. That is probably desirable for the ease of use as you said. For example you'd like to exclude a certain type within the whole source.

Having said that and thinking about it a bit more, I think, excluding files recursive should be provided as an option and keep it off by default. Realistically it's probably best to keep it non-recursive for now, and provide a recursive option in the future if there is demand for it.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It is also possible to exclude file(s) from sub directory with a pattern. Will add the test case.

Copy link
Contributor Author

@TheCoordinator TheCoordinator Nov 7, 2017

Choose a reason for hiding this comment

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

What do you think of this? I have removed recursive patterns based on the reasoning above.

Any other cases in mind?

$0.it("generates source groups with excludes") {
    let directories = """
    Sources:
        A:
        - a.swift
        - B:
            - b.swift
        B:
        - b.swift
        C:
        - c.swift
        - c.m
        - c.h
        D:
        - d.h
        - d.m
        E:
        - e.jpg
        - e.h
        - e.m
        - F:
            - f.swift
        G:
        H:
            - h.swift
    """
    try createDirectories(directories)

    let excludes = [
        "B",
        "C/*.h",
        "d.m",
        "E/F/*.swift",
        "G/H/"
    ]

    target.sources = [Source(path: "Sources", excludes: excludes)]
    spec.targets = [target]

    let project = try getPbxProj(spec)
    try project.expectFile(paths: ["Sources", "A", "a.swift"], buildPhase: .sources)
    try project.expectFile(paths: ["Sources", "C", "c.swift"], buildPhase: .sources)
    try project.expectFile(paths: ["Sources", "C", "c.m"], buildPhase: .sources)
    try project.expectFile(paths: ["Sources", "D", "d.h"])
    try project.expectFile(paths: ["Sources", "D", "d.m"], buildPhase: .sources)
    try project.expectFile(paths: ["Sources", "E", "e.jpg"], buildPhase: .resources)
    try project.expectFile(paths: ["Sources", "E", "e.m"], buildPhase: .sources)
    try project.expectFile(paths: ["Sources", "E", "e.h"])
    try project.expectFileMissing(paths: ["Sources/B", "b.swift"])
    try project.expectFileMissing(paths: ["Sources/C", "c.h"])
    try project.expectFileMissing(paths: ["Sources/E/F", "f.swift"])
    try project.expectFileMissing(paths: ["Sources/G/H", "h.swift"])
}

Copy link
Owner

@yonaskolb yonaskolb Nov 7, 2017

Choose a reason for hiding this comment

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

I would like to have support in for proper recursive glob patterns.
Something like **/*.a which would remove all .h files. Is that possible with how you've implemented it? If so can we add a test case for it

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think it should be possible. Will add it tomorrow :)

+ excludedFiles)

return try dirPath.children().sorted()
.filter {
Copy link
Owner

Choose a reason for hiding this comment

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

If we sort after we filter it's technically faster. Probably pre-mature optimization, but maybe someone has thousands of files

@@ -562,20 +562,20 @@ public class PBXProjGenerator {
}

func getSources(sourceMetadata source: Source, path: Path, depth: Int = 0) throws -> (sourceFiles: [SourceFile], groups: [PBXGroup]) {
// if we have a file, move it to children and use the parent as the path
let (children, path) = path.isFile ?
([path], path.parent()) :
Copy link
Owner

Choose a reason for hiding this comment

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

I guess no-one would specify a file path and then exclude it. So not taking care of the file case is fine here 👍

allSourceFiles += subGroups.sourceFiles
groupChildren.append(subGroups.groups.first!.reference)
Copy link
Owner

Choose a reason for hiding this comment

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

It's good this is in there now, as this would have been a problem with empty directories 👍

try createDirectories(directories)

let excludes = [
"B",
Copy link
Owner

Choose a reason for hiding this comment

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

Can we try excluding a sub directory? eg G/H

func expectFileMissing(paths: [String], names: [String]? = nil) throws {
let names = names ?? paths
if getFileReference(paths: paths, names: names) != nil {
throw failure("Found file at path \(paths.joined(separator: "/").quoted) and name \(paths.joined(separator: "/").quoted)")
Copy link
Owner

Choose a reason for hiding this comment

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

Can you change to Found unexpected file ...

@yonaskolb yonaskolb merged commit 91729a9 into yonaskolb:master Nov 10, 2017
@yonaskolb yonaskolb mentioned this pull request Nov 12, 2017
@yonaskolb
Copy link
Owner

@PeymanKh I don't think the excludes are working properly.

The tests that validate this are actually incorrect, so they are passing when they shouldn't

try project.expectFileMissing(paths: ["Sources/B", "b.swift"])

should be

try project.expectFileMissing(paths: ["Sources", "B", "b.swift"])

@TheCoordinator
Copy link
Contributor Author

@yonaskolb I see, saw variations of expectFile being used, I'll check whether this has to do with the test expectFileMissing or the actual implementation.

Thanks for the heads up.

@yonaskolb
Copy link
Owner

Cool. I’ve left it out of the change logs and docs for now

@TheCoordinator
Copy link
Contributor Author

@yonaskolb I've fixed the issue, would you like me to open a new branch for this and send new pull request or should I update this branch?

@yonaskolb
Copy link
Owner

Awesome. Please open a new PR

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants