-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Allow long arguments to be provided via a file instead of only on the command line #60551
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
Comments
I asked Gemini that question just out of curiosity. The full answer is below, but based on it I would say that we shouldn't need to worry about using We do need to decide whether arguments on the command-line override arguments from the file or whether they're all treated equally. I like the idea of being able to override what's in the file, but that might not be the right answer. I'm not sure what advantage there would be of using spaces to separate arguments, but unless there are some I'd probably argue for one argument per line, given that the whole point of the file is to contain long lists of arguments (which would be hard to edit if they can't be broken across lines). Probably unnecessary to say, but if we do provide this support we should add it to the Q: A: Common Practices:
Key Considerations:
In essence, while a rigid standard is lacking, the "@" symbol and the use of configuration files are prevalent methods for supplying additional command-line arguments. |
I like the idea of just saying that whatever is read from the file replaces the
Yeah, I think supporting spaces would also require args to be quoted/escaped if they contain spaces, which makes things more complicated on both sides (particularly as escaping across shells/platforms can differ and is a bit of a minefield as flutter/flutter#84270 / #59604 have shown 🙃). I think with line-separated we could probably just forbid args that contain newlines and therefore have no escaping at all. |
gcc/clang/msvc call these response files. |
Yes, this would be great! Being able to pass a file also was requested in dart-lang/native#39. TL;DR: Use JSON as the format instead of plain text. FormatTerminals support spaces and newlines with quotes and interpolation:
1. Plain text &
Verbatim what one would write in the command-line invocation but no terminal-like string interpolation. This doesn't really work because it wouldn't support spaces inside arguments. Every space would be interpreted as a new Pro:
Con:
2. Plain text &
|
I think I like the idea of a JSON string array.. it's a little less simple than verbatim lines in a text file, but it avoids the newline issue and makes the format more obvious/less ambiguous (probably requiring less documentation). I'm less sure about YAML.. having comments is nice, but the format is less simple (particularly to parse) and it'd be nice to have something that's easy to use across Dart/non-Dart (in this case, Dart-Code is TypeScript).
I'm not sure I understand this - the idea was for the part after the
So you wouldn't literally write While searching for "repsonse files" as noted above, I found this:
So I guess there's a question of whether to do something like that, or just fail with an error if the file is missing/invalid. My feeling is that rejecting is better (assuming a missing file is because something is wrong, and not that it is a legit |
Ah, I completely misunderstood that. Yeah
+1 |
While JSON would be simpler on the edge cases we have existing use cases which follow the newline separated args pattern. Newlines in arguments are technically a thing but exceedingly rare. Bazel also does not support Json as a format, and this is one of our primary use cases. Technically we could create our own params files using JSON but I think its a strong signal that this support is not necessary. |
+1 for using the same newline separated format as bazel if we are using the same |
It sounds like there's a preference towards text instead of JSON. Is this enough of a consensus to implement something like this?
Some questions I'm not sure about:
|
If everyone else prefers text, and no support for newlines, that's fine for me. cc eco system @devoncarew @mosuem and dartdev flutter_tools @bkonyi in case they have an opinion. RE 3: If it is implemented in In addition to |
I think B) is the best approach here.
We need to be able to consistently decode the contents of the file to do anything, so we should be explicit about what formats are supported and try and be consistent with what we support in other situations where the user can provide a file argument to an option.
Ideally we'd add this support to
I'm not sure we should be modifying the contents of
It'd be nice to support this for VM arguments, but I think we should leave processing of Dart arguments to the user program if we can. |
adding it to package:args seems reasonable, esp. if there's some prior art / existing convention we can point to |
Adding it to And then I think it should be something you can opt in/out of for the ArgParser itself, whether it will do this expansion internally. I don't have a strong opinion on what the default should be, but opt in is less likely to cause any problems. |
Putting it in |
Good point. We could use conditional imports and only support it when dart:io is available. It could either throw or just do nothing if its not available and an |
Another option would be to take a callback that can return the contents of the file given the 'path' after the |
If its optional, then that SGTM. We should make the 99% case as easy as possible though and not require everybody to pass a callback that just uses dart:io. |
I'd first look at adding it in a separate library, potentially as an extension, so that authors explicitly import it in places with |
So are we suggesting a top-level function in a new library (which will be an empty stub when there's no import 'package:args/args_file.dart`;
void main(List<String> args) {
args = expandArgsFiles(args);
// ...
var argResults = parser.parse(args);
} And also a flag for var parser = ArgParser(
expandArgsFiles = true,
);
class ArgParser {
ArgResults parse(Iterable<String> args) {
if (expandArgsFiles) {
args = expandArgsFiles(args);
}
// ...
}
} I'm still slightly unsure about "doing nothing" if an invalid/unreadable filename is passed.. it could make the error message if there are typos less useful (although, I think I expect this feature mostly to be used by tools and not being typed by users). |
I was imagining something like import 'package:args/args_file.dart';
void main(List<String> args) {
var argResults = parser.parseWithArgsFile(args);
} A separate utility to expand into a |
I like the idea. In One question: Are nested |
My assumption was that they are not (I don't think that's supported in some of the other cases noted above), so the strings are always literal. I don't have a preference though (it's simple enough to support, though we would have to watch for infinite loops). |
Good question from Sigurd. This is a feature, especially if added to The absolute zero-smartness base feature would be: At the start of parsing command line arguments, if the last argument starts with Use- and edge-cases to consider:
A "full-featured" proposal would allow That is an iterator which has If it meets a That should be doable. It is a "new format", though. A quick search for "arg[s][-]file" and "
Other possible formats that we could adopt instead include Yaml (please don't, it just means adding |
I think adding it to |
The main upside would be that we don't need to implement this in the VM. Our VM options parsing logic is fairly complex already and could probably do with a rewrite to simplify it, so I'd be a bit worried about tacking this on to what's already there. My other concern is that, in order to support this on the VM side, we'd have to start modifying argument lists passed to Dart programs. AFAIK, this isn't something we've done before and I'd really like to know if there's any negative implications in doing so before we go down that path. |
@lrhn these are all great questions, and it makes me wonder whether publishing this in My main motivation here is to fix the eg, I was thinking of something simple like this (this supports Iterable<String> expandArgumentFiles(Iterable<String> args,
// default fileReader for dart:io version: (filePath) => File(filePath).readAsStringSync()
{ArgsFileReader? fileReader}) {
Iterable<String> readArgumentFile(String arg) {
try {
final filePath = arg.substring(1);
return fileReader != null
? LineSplitter.split(fileReader(filePath))
: [arg];
} catch (e) {
throw ArgParserException("Failed to read arguments from file '$arg': $e");
}
}
return args
.expand((arg) => arg.startsWith('@') ? readArgumentFile(arg) : [arg]);
}
typedef ArgsFileReader = String Function(String filePath); Some of your questions are still relevant there though (although I think many of them might be covered above... IMO either line ending, no comments, paths not URIs, relative to cwd at the start of the process, blank lines are empty strings in args - although probably we'd need to exclude trailing newlines). |
This feature is really only intended for automated uses by tools invoking other tools with long command lines. I think its totally fine (and actually best) to just ship the simplest possible feature here, which is exactly the one already proposed.
Shipping this in package:args gives us flexibility to change it if we really need to in the future, and it can ship as a separate top level function in a separate library to work around the dart:io issue. Most likely, very few people will actually use it. If people file issues asking for additional support we can address it at that time (but I strongly doubt they will). |
Is that the final argument before
Introducing e.g. escaping later will be a breaking change. Perhaps better to invest a little bit here... |
It doesn't. It has to be the final argument. The implementation should only look at the final argument and completely ignore the rest. The intended use case here is pretty much always just passing a single
This requires a much more sophisticated implementation, testing, etc. I really do not think it is worth while. If this is shipped as just a top level function, we can always add an argument to handle escapes later on. |
@jakemac53 So your proposal is:
That can work. I worry about paths in a file that depends on CWD. Maybe just recommend that all paths in a file are absolute. I worry about not having any way to avoid this behavior, no way to escape it, if you want to have an actual last argument that starts with I think I'd prefer making it a flag instead of special syntax: |
That would be somewhat unfortunate for people typing things on the command-line. (IMHO this is a great argument why this should be implemented before it reaches
If we're building the solution in a package, I agree with this. It'd be a feature of the arg parser, not a feature of the |
There should be no distinction at all between arguments provided via file versus command line, so there is no question about how to treat paths or anything else (this is entirely up to the thing receiving the args, which gets them exactly as if they had been provided as is on the command line). The point of this functionality is for a tool which is invoking another tool with a ton of arguments, to be able to choose to jam those arguments into a file if it thinks it might be too many to otherwise work. That is the only intended use case, and it is my opinion is that we should handle it via the simplest possible means. |
I want it as a flag, like If they want to pass Adding new syntax also means having to add new ways to escape the new syntax. Using the existing flag syntax means we already have that escape.
If there are no recursive arg-files, then that's fine. Otherwise the path in the recursive arg file must be handled by the arg-file expander, which means interpreting it before passing it to the real tool. (The recommendation to only put absolute paths into an arg file is still good, and works no matter how it interprets paths. If you write a command line, you are running from the current position. If you write a file, then you risk that file being reused. If the feature exists, then it can even be used deliberately to reuse a bunch of arguments. If the file contains only absolute paths, it'll work either way.) If the only goal is to pass a lot of arguments, then I'd rather read them from stdin than have to create a file, and find a place to put that file (in |
This is much more complicated to do in a general purpose way (when do you stop reading them, you still need a flag to put you in this mode, have to handle forwarding off stdin at some point to the actual tool being invoked, etc) and also not compatible with how bazel works. I don't see the advantage. The use case here is specifically for tools which are passing large numbers of arguments to other tools. Creating a file is trivial for tools (and yes, it should be under tmp/, but that's up to the tool). |
On Windows, there is a limit to the size of a command line (executable + arguments). It's easy to hit this limit with some operations like asking the IDE to re-run all test files that had failing tests from the last run, because each test file needs to be listed specifically:
One option to fix this for
dart test
is to support an argument like--tests-from-file testlist.txt
, however this only solves the issue for that specific case, but it would be nice to solve this more generally.@jakemac53 noted that Bazel supports a final argument of
@filename
that can be used to read arguments from a file which seems like a nice convention to follow (and I started implementing at dart-lang/test#2485), and this is also implemented in Dart compute_kernel.dart here.When trying to add the equivalent to
flutter test
, it turned out to be easier to support for the wholeflutter
command because of how theCommandRunner
works (I started this at flutter/flutter#167272). If doing this for all offlutter
it would be nice if we also supported it for all ofdart
(instead of onlypkg:test
).@bkonyi had some questions that would be worth discussing if we're going to implement this more generally across all tools/commands (and I added a few):
@
that aren't this file?I don't personally have any real preference for what the solution is, my main goal is to be able to resolve bugs like the one shown up-top. I do think a general solution implemented once in
dart ...
andflutter ...
would be better than having multiple implementations in individual sub-commands though.The text was updated successfully, but these errors were encountered: