-
Notifications
You must be signed in to change notification settings - Fork 1.6k
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
Add support for external frontends #1210
Conversation
0ffc551
to
9fec830
Compare
frontend/FRONTEND.md
Outdated
$ frontend/native.py 3< ninja.pb | ||
``` | ||
|
||
The serialized output of a clean Ninja build is included in `frontend/ninja.pb`. |
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.
frontend/frontend.pb
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.
Removed, frontend/frontend.pb is the compiled protobuf descriptor, not serialized build output.
frontend/FRONTEND.md
Outdated
|
||
To save serialized output to a file: | ||
``` | ||
$ ./ninja --frontend='cat /proc/self/fd/3 > ninja.pb all |
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.
cat <&3 >ninja.pb
would be more portable (at least Mac, which doesn't have /proc)
And you're missing a closing '
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.
Thanks, fixed
Running with
Anything I'm missing? |
9fec830
to
6b059e5
Compare
Oops, I misunderstood the python protobuf class structure, combined with an untested path. Pushed a quick fix for now. |
Works now, thanks :) |
frontend/frontend.py
Outdated
size = 0 | ||
shift = 0 | ||
while True: | ||
byte = self.reader.read(1) |
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.
Use byte = bytearray(self.reader.read(1))
...
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.
Thanks, fixed and tested with python3
frontend/frontend.py
Outdated
if len(byte) == 0: | ||
raise StopIteration() | ||
|
||
byte = ord(byte[0]) |
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.
... and remove ord
call here: byte = byte[0]
. It will then work in both Python 2 and 3.
Currently uses cat to forward the messages. frontend.pb, frontend.py and native.py are taken from ninja-build/ninja#1210 with minimal changes.
frontend/frontend.py
Outdated
self.status_class = self.get_status_proto() | ||
|
||
def get_status_proto(self): | ||
set = google.protobuf.descriptor_pb2.FileDescriptorSet() |
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.
[pylint] W0622:Redefining built-in 'set'
Maybe fd_set instead?
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.
Thanks, fixed
frontend/frontend.py
Outdated
|
||
def get_status_proto(self): | ||
set = google.protobuf.descriptor_pb2.FileDescriptorSet() | ||
descriptor = os.path.join(os.path.dirname(sys.argv[0]), 'frontend.pb') |
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.
Not sure if sys.argv[0]
is a good idea, maybe __file__
instead?
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.
Fixed
frontend/frontend.py
Outdated
|
||
message = self.reader.read(size) | ||
if len(message) != size: | ||
raise "Unexpected EOF reading %d bytes" % size |
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.
raise EOFError("Unexpected EOF reading {} bytes".format(size))
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.
Fixed
frontend/frontend.py
Outdated
break | ||
shift += 1 | ||
if shift > 4: | ||
raise "Expected varint32 length-delimeted message" |
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.
Instead of raising a str it's nicer to use something like RuntimeError
(which inherits from BaseException
).
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.
Fixed, and all the other raised strings
I've used this PR to write a new frontend which adds some features I've missed: https://bixense.com/ja/ It works pretty well so far :) |
3c09006
to
f5fe5af
Compare
Edges are nominally ordered by order in the build manifest, but in fact are ordered by memory address. In most cases the memory address will be monontonically increasing. Since serialized build output will need unique IDs, add a monotonically increasing ID to edges, and use that for sorting instead of memory address.
The times that end up in the build log currently originate in the status printer, and are propagated back out to the Builder. Move the edge times into the Builder instead, and move the overall start time into NinjaMain so it doesn't get reset during manifest rebuilds.
Store the number of running edges instead of trying to compute it from the started and finshed edge counts, which may be different for starting and ending status messages. Allows removing the status parameter to PrintStatus and the EdgeStatus enum.
Make BuildStatus an abstract interface, and move the current implementation to StatusPrinter, to make way for a serialized Status output.
Send all output after manifest parsing is finished to the Status interface, so that when status frontends are added they can handle build messages.
Add a tiny protobuf runtime in proto.cc and proto.h, and a python script that takes a binary proto descriptor (produced by protoc --descriptor_set_out) and produces a .pb.h file that implements a subset of the protobuf C++ Message interface.
Add a --frontend option that takes a command that will handle printing build status. The command will be executed with the read of a pipe on fd 3, and each build event will cause a serialized message to be sent on the pipe. The frontend/ directory includes a description of the interface between Ninja and a frontend, and an implementation of the interface in python that mimics the native interface in ninja, which can be run with: ninja --frontend=frontend/native.py
Is there anything I could do to help getting this merged? |
I would also really like to see this being merged. My goal is to have a frontend that logs everything to a file (similar to a simple stdout redirect with current ninja), but still provides status output. In this way, warnings and executed commands can be found in the logfile, while a status line (and output of failed commands) is shown directly on the console. |
I thought a bit about this. As said on the mail thread, I'm not a fan of putting protobuf in ninja itself (even if it's a reimplementation). At the same time, I agree that lots of people want to tweak ninja's output in many ways, and this is a good approach to attack that problem. What do you think about adding a second binary, |
Doesn't ninja also stay the way it is when one isn't using the |
The behavior stays the same, but the code still ends up in the binary. |
IMHO that's an acceptable tradeoff. What binary size difference are we talking about here? Probably a few kilobytes.
So the binary gets distributed with ninja? That way the size of the package stays the same anyway. If not, it would still be very inconvenient as frontends still need to ship a binary. |
It's more about the conceptual "ninja doesn't depend on protobuf" imho. Depends on what you mean by "distributed". The only distribution we do is the releases page on github; we can certainly include the binary there. All other distribution is up to the people owning the linux packages and whatnot. |
Can you elaborate what you mean by this exactly? Would a compile time switch be satisfactory for example? Btw: An argument why merging this might actually decrease
Yeah that's what I meant, because I think that's what most Linux people use. |
Ninja should stay small and simple. It should do builds, quickly, and not much else. Having a flexible cross-process communication protocol in there is philosophically out of scope for ninja itself. |
I'm happy to implement this if it is necessary to get this merged, but I agree with Jan that it doesn't make much sense vs. the extra complexity. The proto stuff is all encapsulated through a subclass of the Status interface anyways (StatusSerializer in status.cc, which uses the generated frontend.pb.h file and the 264 line proto runtime in src/proto.cc and src/proto.h), so moving it to its own binary won't materially change the new code. All the argument parsing logic and the main loop in ninja.cc would need to be in the new binary, which probably means moving ninja.cc into libninja and putting two new main.cc files that just configure the output class and call real_main(). As a measure of how big this patch is, for a local build before and after serialize, there is an 8% increase in text section size:
|
I think there is a simpler approach to customize ninja's output. By simpler I mean with a smaller footprint on Ninja's code base. I think I have mentioned it in the past but never got comment on it. Following the same spirit than the NINJA_STATUS environment variable we could have an environment variable specifying the template to use when printing the start of an edge (NINJA_START_EDGE), when an edge stop (NINJA_STOP_EDGE). Users can use the protocol they like (json or ad-hoc for instance) when defining the value of these variables. Eventually, we will have to support some extra placeholders (like one printing the job id so that users can match edge stop line with edge start line. Some escaping function would maybe land in ninja code base too but that will be all. Users would finally just have to write a filter understanding their protocol and do the nice printing. |
It's certainly simpler, but I have a few concerns about using NINJA_START_EDGE/NINJA_STOP_EDGE instead of proper serialization.
|
Another use case covered by this pull request but not by NINJA_START_EDGE, and one of my main motivations for pursuing this pull request, is the ability to get ANSI escape codes (colorized logs) even when outputting to something that is not a smart terminal, like a file or a pipe. I want to produce build breakage emails that retain the original colorized error messages, but ninja strips them. This pull request puts all control of the output in the frontend, so ninja doesn't have to have an option for every different way someone wants the output to look. |
It is true that we cannot achieve the same level of versatility with two environment variables than with properly designed inter-process communication protocol.
I think a compromise between versatility and code base impact should be found. |
I can assure that they aren't doing this "without problems", I've had my own share of problems with that. More likely they are doing it because of no available alternative. For example the Rust compiler added JSON output for IDEs, because the text parsing method was restricting them and IDE developers didn't like it either. Without it they wouldn't have been able to change the default output format for their error messages. Keep that in mind: Frontends need a stable interface and if you let them use Ninja's normal output it will be harder to change that.
What if the output of an edge contains that, too? Might happen when you recursively call Ninja.
I think the code base impact of this approach will actually be smaller, because lots of other issues / feature requests / etc. can then be easily moved to frontends. |
Emacs and others finds error/warning messages. It is not pretty and probably hacky but it works 90% of the time. That's what I meant when I said "without problems". It is extremely unlikely that To all: Please do not take me wrong. I am fully aware of the benefit of well defined interface and I generally prefer principled approaches. But this pull-request diffstat is |
Hi everyone, Just wondering if there's been any more traction on merging this--it'd be super useful to me. In my use case, ninja is running potentially-interactive tools, so having a way to get status updates outside of stdout/stderr is pretty valuable. Thanks! |
I already stated my use case for this in #1210 (comment), and I'd like to add that this does not necessarily require a fully customizable frontend. It would be enough if ninja would (optionally) print information about started jobs in addition to finished jobs, so when I pipe the output to a different process ("custom frontend"), I can still show a status line about the currently running unfinished job(s). Btw, this PR should be tagged with the |
There are many requests for customizations to Ninja's build output cataloged in #746. This patch set attempts to address the underlying issue by spawning a separate frontend process and feeding serialized build status updates to it. The frontend can be extended without complicating Ninja's core logic, or can be completely swapped out for a custom frontend like implemented in #1020. The output can also be serialized to disk for later parsing, which will work well for buildbots.
The interface between Ninja and the frontend is documented in FRONTEND.md in 7th patch in the series, "Add frontend option for build status output"