-
Notifications
You must be signed in to change notification settings - Fork 1.9k
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(dep-graph): allow mix of internal and external deps with same name #802
Conversation
This pull request is being automatically deployed with Vercel (learn more). 🔍 Inspect: https://vercel.com/vercel/turbo-site/CoqtuFcwmnRsm8ngoyTKxGdF8Axr [Deployment for 6daf7f5 failed] |
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.
Two general comments:
- The first change I think makes sense, with the caveat that we should not error out if we fail to parse a version, as I think that would be a regression
- I think I'm on board with the second commit in principle, but I think it ought to be a separate PR. In the event that it needs to be reverted, it would be better if it were independent.
To confirm, if we fail to parse either the semver range or the package.json version, we should fallback to an internal package reference in that case to keep existing behavior? I'll work on splitting the other commit out to its own PR. Its a little annoying because it touches the same code, but I can just target them both at |
Correct. |
8b3a2d0
to
4136e11
Compare
4136e11
to
a851905
Compare
cli/internal/context/context.go
Outdated
if item, ok := c.PackageInfos[dependencyName]; ok { | ||
internalDepsSet.Add(item.Name) | ||
for dependencyName, version := range depMap { | ||
if IsReferenceToLocalPackage(c.PackageInfos, dependencyName, version) { |
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 rolled the code that looks up from the c.PackageInfos
map into the isInternal check since logically that is what it means for the package to not be found in the map. But I suppose this could also be:
if item, ok := c.PackageInfos[dependencyName]; ok && IsReferenceToLocalPackage(item, version) {
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.
It's not the biggest deal, but I think I like your proposed alternative better. That would allow isReferenceToLocalPackage to be a more focused function.
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 is coming along. As a general Go note: to mark a function or method as package-private, start the name with a lowercase letter. For example, isReferenceToLocalPackage
. Test code is typically colocated in the same package as the code under test, so this doesn't impact the ability to right tests.
And on that topic, I think it would be helpful to have a few examples of internal and external references in a test of isReferenceToLocalPackage
to illustrate how the feature is intended to work.
cli/internal/context/context.go
Outdated
// for an exact match. | ||
return true | ||
} else if IsProtocolExternal(protocol) { | ||
// Other protocols are assumed to be external references ("git:", "npm:", "file:" etc) |
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 comment implies that npm:
is an external reference, however the implementation will return false
if protocol == "npm"
.
cli/internal/context/context.go
Outdated
if item, ok := c.PackageInfos[dependencyName]; ok { | ||
internalDepsSet.Add(item.Name) | ||
for dependencyName, version := range depMap { | ||
if IsReferenceToLocalPackage(c.PackageInfos, dependencyName, version) { |
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.
It's not the biggest deal, but I think I like your proposed alternative better. That would allow isReferenceToLocalPackage to be a more focused function.
When turbo currently tries to build the dependency graph, it doesn't consider the specified version/range of the dependency, it just assumes that if that package name exists in the workspace it must be an internal dependency. Because of this the cycles are detected and the process hangs. This PR adds some additional logic to ensure that dependencies are only considered internal if the local/workspace package's version matches the semver range specified for the dependency. Fixes: vercel#796
7fb922b
to
d176f03
Compare
@gsoltis thanks for the review and golang tips. I have refactored things so that Having it just take two strings as input does make it a lot easier to test. I think it should have good coverage of the branches. |
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.
Awesome!
Splitting this out of #802. I tried this first when attempting to fix my own build issues in that PR after discovering Issue #407 which suggested peerDependencies might be the problem. It wasn't enough to solve the build hangs with my project, but seems to have its own merits. If a package has a peer dependency but there is no corresponding devDependency, then it should be presumed that there are no build-time dependency on that package. For example, if there was code that would be executed by one of the scripts that tried to import from the peer package, this would generally speaking fail to run, because node would be unable to resolve the dependency unless another package was pulling it in and it got hoisted, etc. Workspace dependencies are kind of a special case in that they won't be hoisted and can be resolved by node, but if you were to ever move that package out of the monorepo then that same situation as above would fail to build. As a result, my impression is that for any use-case where peerDependencies need to be explicitly part of the dependency graph is actually just a missing dependency declaration bug where that package should actually be part of the dev deps. I have also changed the order in which dependencies, optionalDependencies, and devDependencies are selected to match the order that lerna uses for cases where the package shows up in multiple places. See: https://github.com/lerna/lerna/blob/a47fc294393a3e9507a8207a5a2f07648a524722/core/package-graph/index.js#L53-L58 The attached issues mentions making a flag to control this behavior, but given the reasoning above, I'm not sure if there is ever a situation where you would want to have a peer-dependency impact the dependency graph if its not also a regular or dev (or optional) dependency. Fixes: #407
This is my first time coding with golang, so please let me know if there is anything non-idiomatic or where things can be written better.
When turbo currently tries to build the dependency graph, it doesn't consider the specified
version/range of the dependency, it just assumes that if that package name exists in the
workspace it must be an internal dependency. Because of this, cycles are detected and
the process hangs.
This PR adds some additional logic to ensure that dependencies are only considered
internal if the local/workspace package's version matches the semver range specified for
the dependency.
Fixes: #796