-
Notifications
You must be signed in to change notification settings - Fork 2k
Port run to cobra #1298
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
Port run to cobra #1298
Conversation
The latest updates on your projects. Learn more about Vercel for Git ↗︎ 1 Ignored Deployment
|
68ea84a
to
f42b7b3
Compare
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.
Some comments, some questions!
func AddFlags(opts *Opts, flags *pflag.FlagSet, repoRoot fs.AbsolutePath) { | ||
// skipping remote caching not currently a flag | ||
flags.BoolVar(&opts.SkipFilesystem, "remote-only", false, _remoteOnlyHelp) | ||
fs.AbsolutePathVar(flags, &opts.Dir, "cache-dir", repoRoot, "Specify local filesystem cache directory.", "./node_modules/.cache/turbo") |
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.
The input path spec is unclear, it could be any of (Absolute|Relative)*(System|Unix). We should probably start communicating that users should use Unix separators, even on Windows.
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 might be missing something, why do windows users need to use unix separators?
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.
If we're running things in CI we should generally encourage consistent input otherwise the user can receive surprising output. I think that if we actually push path types through the entire system then we'd be okay, but right now I worry that there are edge cases is the user specifies Windows paths.
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.
Hmm. We have a few different kinds of paths. Cache directory is pretty clearly machine-local, and thus seems like it should be strictly platform-dependent. Especially since I don't think it can or should be configured via shared config file.
Other paths are shared, for instance, global dependency paths. In my mind those are two different types (the global deps one is really a glob, even if you use a file path), but I'm sure we don't do a great job calling that out or helping with that distinction.
Actually, perhaps those are the only two in the user input? Absolute-or-relative platform-dependent path for things like cache directory, and unix-like glob for any paths shared across machines? Might be some good candidates for at least some internal types, if not external documentation.
In the CI case, I don't think it would make sense to share the same input value for cache directory across a linux machine and windows machine. Unless there's a windows convention I'm not aware of, I wouldn't expect a unix path to work to point to a local directory.
|
||
func (pv *pathValue) String() string { | ||
if *pv.current == "" { | ||
return ResolveUnknownPath(pv.base, pv.defValue).ToString() |
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.
In this case, if pv.defValue
is an absolute path but doesn't use os.PathSeparator
, it still short-circuits out of calling Join
which would result in a consistent system path by virtue of calling Clean
.
In other words, on Windows: C:\path\to\thing
and C:/path/to/thing
can both come out of this. (Meets the strict definition of AbsolutePath
but I've been treating it as a system path thus far.)
Relative paths should work just fine.
(Sorry, I've spent far too much time staring at paths recently and have all of this top-of-mind.)
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.
IIUC, the issue is that if a user supplies C:/unix/like/path
, we will pass it through as an AbsolutePath
? I'm a little tempted to say "don't do that". Passing /unix/like/path
on windows ends up getting treated as a relative path and the slashes appropriately handled. It is likely a user error, but not really one we can correct because we don't know the drive letter.
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'd mostly just pitch running it through Clean
afterward so that it's consistent.
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 did some research, and it appears that /
is forbidden in windows filenames, so running Clean
should be safe, as we are not removing their ability to reference a directory called "unix/like/path" contained within C:
.
However, and I don't know if it matters, but without running it through Clean
, some combination of the Go and windows filesystem APIs end up treating /
as a separator. Calling os.MkdirAll("C:/some", ...)
and ioutil.WriteFile("C:/some/path", ...)
on windows successfully results in a file at C:\some\path
.
I think I would've expected it to fail, given that /
is both not the separator and not allowed in filenames.
_ = flags.Bool("stream", true, "Unused") | ||
if err := flags.MarkDeprecated("stream", "[WARNING] The --stream flag is unnecesary and has been deprecated. It will be removed in future versions of turbo."); err != nil { | ||
// fail fast if we've misconfigured our flags | ||
panic(err) |
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.
If I'm reading this correctly, this can only occur if we removed stream
but forgot to remove it here?
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.
Yeah. Unfortunately due to how the flags api works, there are some methods that return errors when they can't find the specified flag, as opposed to being able to set attributes on the flag declaration itself. I put those as panics since they should all be executed synchronously from main
, so it should fail quickly and consistently if it's broken.
|
||
func (l *logsModeValue) Set(value string) error { | ||
switch value { | ||
case "full": |
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.
These come in as a string but can we make our internal references to a constant instead of string literals?
} | ||
|
||
func (l *logsModeValue) String() string { | ||
if l.opts.CacheHitLogsMode == FullLogs && l.opts.CacheMissLogsMode == FullLogs { |
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.
MissMode↓ HitsMode→ | Full | No | Hash |
---|---|---|---|
Full | full | new-only | |
No | none | ||
Hash | hash-only |
if err != nil { | ||
exitErr := &process.ChildExit{} | ||
if errors.As(err, &exitErr) { | ||
return exitErr.ExitCode |
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 an interesting change; I don't know what I think about it.
- Did we fail, or did the thing we called fail?
- Should we replace the child error with a consistent error code at our level, or propagate the child error?
I'm not sure I have strong opinions here, but I do know that if we do this coding to Turbo's exit code API becomes irregular.
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.
The intention is to exit with the highest exit code that we've observed. If we generate our own error, we return 1
. I don't think it's worth doing more than checking for exit code == 0
when programming against this value currently.
If we wanted to improve this situation, we could attempt to smuggle more information through this single value, but I think we would need to see a compelling use case for an automated tool reacting differently to turbo
failing vs something that turbo
called failing.
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.
Also, fwiw, unless I've missed something, this is not new behavior, it was previously implemented here
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 may also misunderstand in trying to statically reason about it, I read it as a change from "always 1" to "whatever we have gotten back."
I agree that this isn't an API we've currently got planned, so I'm comfortable with 0 vs. non-zero.
} | ||
|
||
return exitCode | ||
return nil |
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 like the change of hoisting exit code.
opts.cacheOpts.SkipFilesystem = true | ||
func (d *dryRunValue) String() string { | ||
if d.opts.dryRunJSON { | ||
return "json" |
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.
Can we make these constants?
// fail fast if we've messed up our flag configuration | ||
panic(err) | ||
} | ||
aliases["dry"] = "dry-run" |
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.
While reading through I went back to find this, good catch. 👍
if !config.IsLoggedIn() { | ||
opts.cacheOpts.SkipRemote = true | ||
} | ||
func (d *dryRunValue) Type() string { |
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 have no idea what this is doing. Can you help me understand? (It doesn't appear in old code either.) Is it specifying the alias for some help text somewhere?
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.
The short answer is that this is required to implement the pflag.Value
interface.
The longer answer is that it is used for a variety of things internal to how flags are parsed and displayed in help/usage text. I'll add some comments, or maybe pull this out into its own utility.
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.
Added some comments. I think we need at least one more instance of this kind of flag before extracting it, and I'm not yet sure if it's feasbile or worthwhile w/ Go's type system. I expect to at least attempt this implementing #1286
Addressing some of @nathanhammond's review comments from #1298 I added a test for `AbsolutePathVar` so that we can have a way to discuss the intended behavior.
Removes some long-deprecated flags:
serial
includeDependencies
cacheFolder