Added -Xi to include new fields in JSON output#7838
Conversation
|
Thanks for your pull request and interest in making D better, @marler8997! We are looking forward to reviewing it, and you should be hearing from a maintainer soon.
Please see CONTRIBUTING.md for more information. If you have addressed all reviews or aren't sure how to proceed, don't hesitate to ping us with a simple comment. Bugzilla references
|
|
NOTE: I also plan on adding the extra fields that were added in (#7521), however, I thought that should be it's own set of changes. Hopefully we can get this one merged quickly so I can add the new fields from |
wilzbach
left a comment
There was a problem hiding this comment.
Nice. That looks a lot better than your previous approach.
I'm fully in favor of this PR and hope we can get it as soon as possible ;-)
src/dmd/json.d
Outdated
| } | ||
|
|
||
| /** | ||
| Returns true if `c` is a valid character for the start of a name.. |
src/dmd/json.d
Outdated
| json.arrayEnd(); | ||
| json.removeComma(); | ||
|
|
||
| if (global.params.jsonQuery is null) |
There was a problem hiding this comment.
- could something like this work? it would be cleaner (less special cases)
if global.params.jsonQuery is null
global.params.jsonQuery = "modules"; // or whatever we want default query to be; maybe put all the queries here
# then the rest can assume we always have `jsonQuery`
- can the default (with no jsonQuery) output the array of modules as nested under a root json object instead (as done with
jsonQuery) instead of at top-level, or would that be a breaking change we want to avoid? It kind of sucks to have 2 different formats (one with top-level array and one where it's nested)
There was a problem hiding this comment.
Its definitely better to not break backwards compatibility. If at some point in the future someone determines it's OK in this case, then it's simple to remove the special case.
src/dmd/json.d
Outdated
|
|
||
| json.objectStart(); | ||
| auto parser = QueryParser(global.params.jsonQuery); | ||
| for (;;) |
There was a problem hiding this comment.
while(true) seems more idiomatic?
There was a problem hiding this comment.
FYI: for (;;) is the "idiomatic" way of Phobos. We should probably put such trivias in the style guide, s.t. they don't constantly pop up...
src/dmd/json.d
Outdated
| { | ||
| //json.generateError(format("invalid query: expected name but got '%s' (0x%02x)", | ||
| // *parser.next, cast(ubyte)*parser.next)); | ||
| json.generateError("invalid query: expected name or EOF but got something else".ptr); |
There was a problem hiding this comment.
most useful diagnostic would be a to show byte offset from start of string where parser got confused
There was a problem hiding this comment.
workin on this one
There was a problem hiding this comment.
ok, I ended up using vsnprintf, also added tests.
| if (global.params.jsonQuery) | ||
| { | ||
| generateJson(null); | ||
| return EXIT_SUCCESS; |
There was a problem hiding this comment.
Ah nice. I didn't see this initially.
This is more or less what I am trying to achieve in #7840
| { | ||
| // Write to stdout; assume it succeeds | ||
| size_t n = fwrite(buf.data, 1, buf.offset, stdout); | ||
| assert(n == buf.offset); // keep gcc happy about return values |
There was a problem hiding this comment.
Needs a test.
Example:
#!/usr/bin/env bash
set -ueo pipefail
name="$(basename "$0" .sh)"
dir="${RESULTS_DIR}/compilable/"
out="$dir/${name}.json.out"
"$DMD" -X | ${RESULTS_DIR}/sanitize_json > "$out"
diff "$out" compilable/extra-files/$name.json
rm "$out"
"$DMD" -Xf=- | ${RESULTS_DIR}/sanitize_json > "$out"
diff "$out" compilable/extra-files/$name.json
rm "$out"(sanitize_json currently doesn't accept stdin)
There was a problem hiding this comment.
diff can take -: https://stackoverflow.com/a/9847527/1426932
"$DMD" -X | ${RESULTS_DIR}/sanitize_json | diff - compilable/extra-files/$name.json
``
There was a problem hiding this comment.
This is existing code, I think any changes to it would be a seperate PR.
ditto
| break; | ||
| case 'q': | ||
| if (!p[3]) | ||
| goto Lnoarg; |
|
|
||
| // Write buf to file | ||
| const(char)* name = global.params.jsonfilename; | ||
| if (name && name[0] == '-' && name[1] == 0) |
There was a problem hiding this comment.
- use
strcmp? - how about factoring this use case (probably useful for other flags; also more self-documenting)
enum stdout_alias="-;
if (name && !strcmp(name, stdout_alias)) {...}
There was a problem hiding this comment.
This is existing code, I think any changes to it would be a seperate PR.
src/dmd/json.d
Outdated
| // of modules representing their syntax. | ||
|
|
||
| json.arrayStart(); | ||
| for (size_t i = 0; i < modules.dim; i++) |
There was a problem hiding this comment.
foreach (+ everywhere relevant)
src/dmd/json.d
Outdated
| auto fieldID = tryParseJsonField(fieldName); | ||
| if (!fieldID.hasValue) | ||
| { | ||
| json.generateError(("invalid query: unknown field name '" ~ fieldName ~ "'\0").ptr); |
There was a problem hiding this comment.
aren't backticks "`" used instead of "'" in messages ? (for syntax highlight)
src/dmd/json.d
Outdated
| auto fieldID = tryParseJsonField(fieldName); | ||
| if (!fieldID.hasValue) | ||
| { | ||
| json.generateError(("invalid query: unknown field name '" ~ fieldName ~ "'\0").ptr); |
There was a problem hiding this comment.
.ptr is not needed; D string litterals are null terminated and can decay to const(char)*
+everywhere relevant
There was a problem hiding this comment.
Doesn't appear to work in this case, says it can't convert char[] to char*.
There was a problem hiding this comment.
looks like it did :) (not seeing .ptr after string literals in your latest commit)
if not, i'm curious in what case it didn't work? i thought string literals would convert?
There was a problem hiding this comment.
The latest doesn't using ~ to append any strings. Looks like appending strings breaks implicit conversion from string to const(char)*.
src/dmd/json.d
Outdated
| JsonField value; | ||
| } | ||
| OptionalJsonField tryParseJsonField(const(char)[] fieldName) | ||
| { |
There was a problem hiding this comment.
DRY:
foreach(a; __traits(allMembers, JsonField))
if(fieldName == a) return OptionalJsonField(true, mixin(`JsonField.`~a));
There was a problem hiding this comment.
done, I was thinking I needed to use phobos EnumMembers...I forgot I could do this with __traits.
| else | ||
| { | ||
| // Generate json file name from first obj name | ||
| const(char)* n = global.params.objfiles[0]; |
There was a problem hiding this comment.
check for global.params.objfiles.length > 0 (cf to avoid thinkgs like https://github.com/dlang/dmd/pull/7839/files)
There was a problem hiding this comment.
This is existing code, I think any changes to it would be a seperate PR.
ditto
| } | ||
| else | ||
| { | ||
| /* The filename generation code here should be harmonized with Module::setOutfile() |
| "cwd": "VALUE_REMOVED_FOR_TEST", | ||
| "importPaths": [ | ||
| "compilable", | ||
| "..\/..\/druntime\/import", |
There was a problem hiding this comment.
how about making sanitize replace \/ with / ?
There was a problem hiding this comment.
This is the JSON generated by std.json (not dmd). Dmd doesn't generate these weird escaped slashes. To fix this we would need to post-process the JSON after it is sanitized, basically another JSON parser that sanitizes after std.json...lol :)
There was a problem hiding this comment.
lol indeed... std.json really sux; i guess a JsonPolicy to not escape / would work here (yes, unrelated to this PR)
There was a problem hiding this comment.
wait, there already is an option for this! https://dlang.org/phobos/std_json.html#.JSONOptions.doNotEscapeSlashes
I'll updated the PR to use it :)
test/sanitize_json.d
Outdated
| foreach (ref obj; root.array) | ||
| { | ||
| auto kind = obj.object["kind"].str; | ||
| if (kind == "compilerInfo") |
There was a problem hiding this comment.
DRY + type safety:
s/"compilerInfo"/JsonField.compilerInfo.stringof/
+everywhere relevant
There was a problem hiding this comment.
This is the test suite, not dmd. It doesn't have access to the JsonField type defined in the compiler.
There was a problem hiding this comment.
import dmd.json : JsonField; ?
There was a problem hiding this comment.
That would add a new dependency, making the compiler a dependency on the sanitizer. The sanitizer is akin to an external tool like rdmd or dub. Like the sanitizer, they are reading the JSON file using a pre-defined interface, a public interface that needs to be well-defined. These field names are apart of that interface, and a tool shouldn't need to import the compiler source code to use it.
There was a problem hiding this comment.
Yeah, the testsuite shouldn't (directly) depend on what it is testing.
src/dmd/mars.d
Outdated
| case 'q': | ||
| if (!p[3]) | ||
| goto Lnoarg; | ||
| params.jsonQuery = p + 3 + (p[3] == '='); |
There was a problem hiding this comment.
check p[3] == '=' and params.jsonQuery = p + 4
We really should not allow things like -Xqfoo in new flags; this is old baggage from dmd cmd line (eg -offile instead of (-of=file), the downsides have these have been discussed several times: it's not standard, hard to read visually, and disallows future new flags (eg -Xquery=baz)
Also, -Xf- (at mentioned in toplevel msg) reads ambiguously: -o- means no output, but -Xf- as you wrote means output to stdout; with what I suggest there is no ambiguity: -Xf=- means: stdout
There was a problem hiding this comment.
Yes I agree with this, I'll remove support for omitting =.
src/dmd/globals.d
Outdated
| compilerInfo = 0x01, | ||
| buildInfo = 0x02, | ||
| modules = 0x04, | ||
| semantics = 0x08, |
There was a problem hiding this comment.
Typically binary shifts are used, s.t. it's easier for reviewers when this gets bumped.
src/dmd/json.d
Outdated
| string prefix = ""; | ||
| foreach (enumName; __traits(allMembers, JsonFieldFlags)) | ||
| { | ||
| if (enumName != "none") |
There was a problem hiding this comment.
How about:
foreach (idx, enumName; __traits(allMembers, JsonFieldFlags))
{
if (idx > 0)
{
s ~= ", " ~ enumName;
}
}There was a problem hiding this comment.
Hmmm, that produces a different string ", compilerInfo, ..." instead of "compilerInfo, ..."
There was a problem hiding this comment.
Ah it's too late here. I meant:
if (idx > 0)
s ~= ", ";
s ~= enumName;(the main point was about not hard-coding that the first enumName is named "none")
There was a problem hiding this comment.
Oh...but now you're hardcoding that the first member should be ignored...is that better than hardcoding that the value named "none" should be ignored?
There was a problem hiding this comment.
Eh...I suppose I don't have a preference for either...so we'll go with your preference :)
src/dmd/json.d
Outdated
| auto fieldNameString = fieldName[0 .. strlen(fieldName)]; | ||
| foreach (enumName; __traits(allMembers, JsonFieldFlags)) | ||
| { | ||
| if (enumName != "none" && fieldNameString == enumName) |
There was a problem hiding this comment.
You could move if (enumName != "none") out of the loop.
That saves a few duplicated comparisons.
test/sanitize_json.d
Outdated
| { | ||
| sanitizeSemantics(semantics.object); | ||
| } | ||
| } |
There was a problem hiding this comment.
DRY idea:
static foreach (e; [tuple("compilerInfo", "sanitizeCompilerInfo")
tuple("buildInfo", "sanitizeBuildInfo")])])
{{
auto infonfo = rootObject.get(e[0], JSONValue.init);
if (compilerInfo.type != JSON_TYPE.NULL)
mixin(e[1] ~ "(info.object);");
}}It's okay to use newer compiler features here as DMD + DRuntime + Phobos are just freshly built from master.
6fa64f8 to
aec1bb7
Compare
| if (s.length == 0 || (s[0] < 'a' || s[0] > 'z')) | ||
| return s; | ||
| return (cast(char)(s[0] - ('a' - 'A'))) ~ s[1 .. $]; | ||
| } |
There was a problem hiding this comment.
What's wrong with https://dlang.org/phobos/std_string.html#.capitalize?
There was a problem hiding this comment.
Looks like that one won't work, it converts the rest to lower-case...which is not what is wanted.
There was a problem hiding this comment.
Ah that's unexpected. I thought there's a function for this in Phobos :/
Well, you could do this:
import std. unit : asCapitalized;
return s.take(1).asCapitalized.chain(s.drop(1));It would be more idiomatic, but put more stress on the CTFE (though it's a small standalone script for which this is probably not a concern);
There was a problem hiding this comment.
Ugh...I can't get it to work. I added .array to the end but it ends up returning dchar[]...
There was a problem hiding this comment.
Interesting it worked for me (https://run.dlang.io/is/xP19Bv),
the end but it ends up returning dchar[]
You can use .byCodeUnit to opt-out of auto-decoding, but to!string or text from std.conv are both smart enough (e.g. https://run.dlang.io/is/zv8RlX)
For pure ASCII, there's also std.ascii.toLower:
s.enumerate.map!(a => a.index == 0 ? a.value.toUpper : a.value).text;https://run.dlang.io/is/rHnADh
But this is definitely nit-picking here ;-)
There was a problem hiding this comment.
looks like std.conv.text did the trick. My std.range / std.algorithm 'fu could use work.
aec1bb7 to
2b00495
Compare
|
This PR could use a statement regarding motivation (at least if you need my review). I don't have an intimate understanding of the problem this solves or the use case. Also, why do we need multiple options? Is there a problem with just generating everything? Also, I don't see why it shouldn't be documented. What's the motivation for keeping it hidden? Are we not sure if it's a good idea? The code is not 100% covered by tests. Can the lack of coverage be justified? Overall, though, I don't have any issue with this PR, and I'm not objecting to it; I'm actually trying to move it forward by adding my support. But I need some understanding (answers to the questions above) before I can support it. |
This PR is a result of a suggestion from @WalterBright . We needed a way of capturing information from the compiler without redirecting stdout/stderr. I suggested providing a way to redirect verbose output to a file but Walter suggested we add the information to the JSON file, so this PR is the result.
Generating everything is fine, but the output is meant for tools that can process the JSON output. So it makes sense to allow the tool to specify what it wants. This also has the advantage that the tool won't need to pay the overhead to generate everything if they don't need it. For example, dub only needs to generate
The only reason for not documenting it is no one has presented a good use case for this feature to be used out in the wild. The only current use cases are
I haven't checked the code coverage. Depends on which code paths aren't taken, I'll see if I can take a look. |
|
Thanks @marler8997 Who are the primary consumers of this JSON output and for what purpose? You mentioned What about feature overlap? Isn't this information something 3rd party tools can obtain for themselves using DMD-as-a-library? |
|
rdmd will use it to output all the files used during a compilation. It uses this for dependency analysis. It will also use compilerInfo to know what features a particular compiler binary supports. Same with dub. Dmd as a library really isn't applicable for these use cases. The JSON output is used to get information about a particular compiler binary or a particular build, whereas dmd as a library enables an application to have access to compiler functionality. |
src/dmd/mars.d
Outdated
| { | ||
| if (global.params.objfiles.dim == 0) | ||
| { | ||
| error(Loc.initial, "cannot determine JSON filename, use -Xf=<file> or provide a source file"); |
| REQUIRED_ARGS: -Xi=UNKNOWN_FIELD_NAME | ||
| TEST_OUTPUT: | ||
| --- | ||
| Error: unknown JSON field `-Xi=UNKNOWN_FIELD_NAME`, expected one of compilerInfo, buildInfo, modules, semantics |
There was a problem hiding this comment.
Please modify the code to surround each expected option in tick marks, e.g. `compilerInfo`, `buildInfo`, `modules`, `semantics`
There was a problem hiding this comment.
done, thanks for the suggestion
Fix issue 18367
Yes, at the moment Dub creates and compiles a probing file and invocates DMD on it to parse the
There are many other potential use cases - run.dlang.io is another or basically any build tool that needs more information about the currently installed D compiler.
Yep this was done, s.t. we have one or two releases to experiment with until we set it into stone. |
2b00495 to
939284c
Compare
|
Thank you @marler8997 and @wilzbach. I see where this is going now. I'm just waiting on coverage. |
161662f to
7c09995
Compare
Adds a new option (hidden for now) that allows an application to select what it wants in the JSON output, i.e.
This would produce a JSON file looking like this:
The option is hidden because the only users of the option (currently known) will be
rdmdanddub.NOTE: the
-probeequivalent would be:** json output now works even with no input files