-
Notifications
You must be signed in to change notification settings - Fork 741
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
Fix: Pruning generated files for relative operations #2994
Changes from 2 commits
4ca827b
e061640
fbcf014
fa6d427
9a98154
05b9e89
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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. @calvincestari I ended up refactoring the |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -177,7 +177,7 @@ public struct Glob { | |
excludedSet.intersection(url.pathComponents).isEmpty | ||
else { continue } | ||
|
||
directories.append(url) | ||
directories.append(url.resolvingSymlinksInPath()) | ||
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. The code change in Thoughts on this? Or other suggestions of maybe handling this? |
||
} | ||
|
||
} catch(let error) { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -1446,6 +1446,60 @@ class ApolloCodegenTests: XCTestCase { | |
expect(ApolloFileManager.default.doesFileExist(atPath: testUserFileInChildPath)).to(beTrue()) | ||
expect(ApolloFileManager.default.doesFileExist(atPath: testUserFileInNestedChildPath)).to(beTrue()) | ||
} | ||
|
||
func test__fileDeletion__inOperationRelativeDirectory__whenSymlinkIsUsed() throws { | ||
// given | ||
try createFile(containing: schemaData, named: "schema.graphqls") | ||
|
||
let schemaDirectory = "SchemaModule" | ||
let codeDirectory = "code" | ||
let relativeSubPath = "Operations" | ||
let operationFilename = "TestQuery.graphql" | ||
|
||
try createOperationFile( | ||
type: .query, | ||
named: "TestQuery", | ||
filename: operationFilename, | ||
inDirectory: codeDirectory | ||
) | ||
|
||
let symLinkURL = directoryURL.appendingPathComponent("/\(codeDirectory)/\(relativeSubPath)/") | ||
let symLinkDestURL = directoryURL.appendingPathComponent("\(schemaDirectory)/Sources/Operations/") | ||
let fileValidationPath = symLinkDestURL.appendingPathComponent("\(operationFilename).swift").path | ||
|
||
//setup symlink folder | ||
try testFileManager.fileManager.createDirectory(at: symLinkDestURL, withIntermediateDirectories: true) | ||
try testFileManager.fileManager.createSymbolicLink(at: symLinkURL, withDestinationURL: symLinkDestURL) | ||
|
||
// when | ||
let config = ApolloCodegenConfiguration.mock( | ||
input: .init( | ||
schemaSearchPaths: ["schema*.graphqls"], | ||
operationSearchPaths: ["code/**/*.graphql"] | ||
), | ||
output: .init( | ||
schemaTypes: .init(path: schemaDirectory, | ||
moduleType: .swiftPackageManager), | ||
operations: .relative(subpath: relativeSubPath) | ||
), | ||
options: .init( | ||
pruneGeneratedFiles: true | ||
) | ||
) | ||
|
||
// then | ||
|
||
// running codegen multiple times to validate symlink related file creation/deletion bug | ||
try ApolloCodegen.build(with: config, rootURL: directoryURL) | ||
expect(ApolloFileManager.default.doesFileExist(atPath: fileValidationPath)).to(beTrue()) | ||
|
||
try ApolloCodegen.build(with: config, rootURL: directoryURL) | ||
expect(ApolloFileManager.default.doesFileExist(atPath: fileValidationPath)).to(beTrue()) | ||
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. Just confirming here, without the code added in this PR, this test passes on the first expectation, fails the second, and passes the third, right? 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. Correct |
||
|
||
try ApolloCodegen.build(with: config, rootURL: directoryURL) | ||
expect(ApolloFileManager.default.doesFileExist(atPath: fileValidationPath)).to(beTrue()) | ||
|
||
} | ||
|
||
func test__fileDeletion__givenGeneratedFilesExist_InOperationRelativeDirectoriesWithSubPath_deletesOnlyRelativeGeneratedFilesInOperationSearchPaths() throws { | ||
// given | ||
|
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.
This function looks like it's just removing symlink paths, not actually resolving the symlinks. Is that correct?
If so, we should probably change the name of the function. But there is also something awkward about this, because there is implicit knowledge that the paths returned from the glob have both the path with and without the symlink in the
Set
. Is that correct?I'm feeling like we should be expressing this better and removing the need for implicit knowledge here. Especially since you are now calling
resolvingSymlinksInPath()
inside of theGlob
code.If we are going to resolve the symlinks in the Glob, I'm not even clear why we also need this here? I know we had talked about this being the responsibility of the Codegen, not the
Glob
implementation, but it feels like we are violating that anyways now.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.
Good point renaming the function would make since. And yes, the issue currently is that both a real path and symlink path to a file exist in the Set which causes the deletion.
Also the reason
resolvingSymlinksInPath()
is called inGlob
is specifically due to needing to make sure paths in tests are resolved from/private/var/....
to just/var/....
, however that change would not actually fix the non-test issue because thedirectoryURL
used at that point in the code would not actually be the symlinked path. The code I showed you earlier where we could do the symlink resolution inGlob.expand
by building out the paths before adding them to the Set in the return could potentially solve for both the test and non-test issue, will have to check to verify, and if so we may just need to do that since it appears we will need some change inside ofGlob
anyways which is what we were trying to avoid.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 don't mind if we do this in the
Glob
, but I feel like we need a clear definition of what we are actually doing.The real issue here is one of separation of responsibilities. It's a current behavior that the output of the
Glob
has duplicated paths. That is – two paths that actually point to the same place because of symlinks. If we decide that is an acceptable behavior ofGlob
, because it is just used to pattern match the paths, and there are two paths there technically, then that's okay. Let's document that behavior and then deduplicate the paths inApolloCodegen
, as we are doing. In which case the fix for the unit tests/private/var
needs a different fix probably.I think it's also reasonable to say that the behavior of the
Glob
struct should do the resolving of symlinks and deduplication of paths, ensuring that its output only points to a single file one time, regardless of how many different paths are symlinked to the file's actual path. If we do that, we can fix the problem in one place, and we should be documenting (and probably testing) that as an intended and defined behavior of ourGlob
matching object.I don't have a strong opinion on where we do this, only that we do it in a way that defines a responsibility of a single object that is sensible; is documented; is tested; and works in consistently in every case (of course bugs are bugs, so "every case" means "every known case").
I think I might be misunderstanding or missing some information here, but having the symlink deduplication in the codegen but also resolving symlinks in the Glob seems to be muddying the single responsibility principle here.
If there is a specific odd edge case where
/private/var
isn't being picked up as a symlink properly we can hard code in a fix for the edge case. But if we are going to resolve all symlinks lets have a clear definition of what that means, where it's happening, and why.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 built
Glob
to be a pattern matching engine; it expands given wildcards and outputs matching paths. IMO those should be resolved symlinks and there should not be any duplicates.Glob
is at the crux of path discovery here and it feels that should be doing the work. I don't think there is ever a need for us to know both the symlink and actual path?