Post build commands in the Ninja generator

346 views
Skip to first unread message

Johannes Spohr

unread,
Feb 19, 2017, 10:18:56 AM2/19/17
to Premake Development
Hi group,

I'm asking here because I'm not sure I have this narrowed down enough to open a Github issue.

I started using the Ninja generator, and boy, I don't want to go back to Make ever again. This should really be the standard way of building stuff, as make is just so bad compared to it.
It's not just the speed, but far more the correctness when running parallel builds (-j).
This the grep I'm currently using to trigger a rebuild if Make fails for some reason:

    if grep -q \
        -e "fatal error: opening dependency file" \
        -e "unable to open output file" \
        -e "Error opening output file" \
        -e "cannot open output file" \
        -e "internal compiler error" \
        -e "No rule to make target" \
        -e "hardware or OS problem" \
        -e "can't create precompiled header" \
        -e "one or more PCH files were found, but they were invalid" \
        makeoutput.txt; then
      (trigger rebuild...)

And yes, I did a lot of research on this, and tried to rule out broken filesystems, overheating, faulty hardware, etc. But I see these sporadic failures on 4 different machines (Linux, Mac OS), and even on occasion when running a fresh install of Ubuntu inside a VM. Maybe it's not Make's fault, and the Makefiles that Premake generates aren't compatible with parallel builds? Anyway, Ninja is much more robust in that regard, parallel builds are the default, and it shows.

So I really want to use Ninja, but can't because my project relies on post build commands to copy external libraries to the target directory. The Ninja generator (https://github.com/jimon/premake-ninja) works great so far, but it doesn't support any custom build commands.
I gave it a shot myself, but I'm not sure if this can be implemented for Ninja the same way as for Makefiles. The problem is that Ninja syntax doesn't allow more than one command for a build rule. You can chain commands on Linux with &&, but this won't work on Windows, where you would have to use a horrible cmd.exe /c kludge to wrap the commands. I'd like to avoid that if possible.
So it would be better if the post build command could be a separate rule that is chained to the "link" rule, as I only want the post build command to run whenever the project is built, and that means running after the linker. However, I tried to add a another rule like this:

targetdir = bin/macosx/ninja/debug

rule postbuildcommand1
  command = cp path/to/sdk/library.dylib $targetdir
  description = post build command

build postbuildcommands: postbuildcommand1

build bin/macosx/ninja/debug/libmyproject.dylib: link (...all of my dependencies...) || postbuildcommands


And this kinda works, but now everytime I run the ninja command, the post build command is run even if there were no changes in between. I guess this is because Ninja needs inputs and outputs for rules, otherwise it assumes it has to run the rule everytime? Inputs and outputs are not known to Premake for post build commands, though.

A workaround could be to add another build target that just executes the custom build commands, and nothing else. It's really a kludge, too, so I though maybe you guys have some ideas how to approach this.

Sorry for the long read...
Thanks for taking the time, and any help is appreciated!

-Jones

Tom van Dijck

unread,
Feb 19, 2017, 12:32:26 PM2/19/17
to Premake Development
Interesting issue indeed...

So first thought that comes to mind is, can you make it work with manual editing the ninja file.... without a reference we can't really fix much...

Then as far as I can see, the postbuildstep should just always consider the linktarget as it's input.. so the input we can implicitly infer from premake.
The output is a somewhat different problem... however, the ninja backend could easy add an API for that...

for example, if the ninja plugin specified it using:

```
    premake.api.register {
        name = "postbuildoutputs",
        scope = { "config", "rule" },
        kind = "list:path",
        tokens = true,
        pathVars = true,
    }
```

Then that variable could be used to specify the buildoutputs of a postbuildevent...

There is a question of whether information like that would be useful outside the scope of the ninja backend, and so an argument can be made for inclusion in premake-core... But for now I'd just experiment to get things working, and work towards a set of pull requests we can consider.. As for the ninja plugin itself, that is currently a plugin provided by another user of premake, and in terms on maintenance and responsibility is entirely in his capable hands. If he/she responds here, all the better, but otherwise your best avenue would be to contact him directly through github or report an issue in his repository...

Johannes Spohr

unread,
Feb 20, 2017, 3:53:01 AM2/20/17
to Premake Development
Thanks, Tom, I'll ping them as you suggested. Like you said, the first hurdle is to get this working manually. I'm a total Ninja noob so probably it can be done somehow, I'm just not seeing it...

Sam Surtees

unread,
Feb 20, 2017, 5:04:51 AM2/20/17
to Premake Development
It looks like this PR fixed a bug with the Make generator for parallel builds, is your build of Premake from before or after this patch? If you get a chance to try it out, I'd be interested to know if it fixes the issue you encountered. But it seems like Ninja does a better job for you, so no pressure! Good luck getting Ninja all working! :)

Johannes Spohr

unread,
Feb 20, 2017, 6:02:53 AM2/20/17
to Premake Development
Hi Sam, thanks for the heads up :).
My premake is from Oct. 2016, so this patch is not in there. I will give it a try, although to be sure it will take a week or two of testing because of the issue's randomness.

Sam Surtees

unread,
Feb 20, 2017, 6:34:20 AM2/20/17
to Premake Development
No worries, it sounds like the same issue so hopefully, it fixes the issue while you work on and test Ninja :)

Dmytro Ivanov

unread,
Feb 20, 2017, 1:33:48 PM2/20/17
to Premake Development
Hey there, original plugin author here :) I think one of possible workarounds for ninja would be to split commands into multiple rules (one per command) and then emit multiple build statements by daisy chaining them with order-only dependencies (see https://ninja-build.org/manual.html#ref_dependencies ) though it will be tricky because every statement needs to generate some sort of output file for ninja to function correctly. But then - if commands daisy chain data over pipe (stdin/stdout) you pretty much screwed because you do need a whole terminal to be able to run your command. When I was developing the plugin I took a look on cmake ninja generator, and it's the same shit there: https://github.com/Kitware/CMake/blob/3c0de6db2d9e0580f23cc95c4a1e00a8f66108c9/Source/cmGlobalNinjaGenerator.cxx#L296

Generally speaking this is a gray area of build systems: some of them define strict build pipelines with strict expectations, like try to make linking stage run multiple commands in any build system and you're pretty much screwed while others are generic "whatever you say" command runner, which is complicated in other ways, for example there is no cross platform way to copy files, cmake is even implements their own command for this.

PS. When I was developing a plugin for premake I discovered one huge show stopper for myself: as I was writing code of a plugin there is no feasible way to test for support for all premake features simply because there is no test framework for this at all. So after this I kinda lost interest, so if anyone wants to take over - ping me by mail, I'll transfer repo to a new maintainer and provide some support for hand over :)

Johannes Spohr

unread,
Feb 21, 2017, 4:04:04 AM2/21/17
to Premake Development
Hey Dmytro, thanks for joining the discussion :)

So this confirms my worries, there's no simple way to implement this.
The "cmd.exe /c" hack is just ugly. It also limits the length of the command line to 2048 characters, or something like that. Which can easily break if you're on a large project which has lots and lots of libraries with long relative paths.
Right now, I'm leaning towards Tom's idea of explicit build command outputs. An alternative would be to use custom build commands instead of post build commands: https://github.com/premake/premake-core/wiki/Custom-Build-Commands#custom-build-commands
Meaning, add the files I want to copy to the project, then use a filter that matches them and a custom build command for the copy. I think this could be mapped to Ninja 1:1, but it's quite verbose for a simple file copy...

PS. When I was developing a plugin for premake I discovered one huge show stopper for myself: as I was writing code of a plugin there is no feasible way to test for support for all premake features simply because there is no test framework for this at all. So after this I kinda lost interest, so if anyone wants to take over - ping me by mail, I'll transfer repo to a new maintainer and provide some support for hand over :)

Sorry to hear that! Did you try converting the tests for the Makefile generator? Or are they too specific for that particular implementation?
I would be very happy if somebody would take on the Ninja generator, because right now I'm digging it sooo much :)
I'm not yet ready to volunteer myself, because I just started using Ninja, and I need some time to get familiar with it. I also never wrote a complete Premake generator, only did some minor updates here and there. So there's a lot for me to catch up on. However, I'll put some time into understanding the code, hopefully I'll be more confident eventually.
Anyways, thanks for your work on the project and sharing your insights!
 

Dmytro Ivanov

unread,
Feb 21, 2017, 1:23:15 PM2/21/17
to Premake Development
The only problem with "cmd.exe /c" is the startup time of any executables on Windows - it's around 200ms AFAIK, while it's almost non existent on Linux (though still take some time on OS X). So even if we make a replacement for cmd.exe to run our arbitrary command, it still will be meh :) Another approach would be to modify ninja directly to run multiple commands, but it kinda is reimplementing terminal in ninja. So splitting commands in generation time into multiple build statements might be the best option performance wise.

>Did you try converting the tests for the Makefile generator? Or are they too specific for that particular implementation?

It's kinda even the same between VS and Makefile generator in premake itself, try asking questions like "Is feature X works for all generators in premake-core?", where X is for example specifying linker flags, or compile binary, or something else. It's impossible to answer to a question like this, because current test framework in premake-core only tests generators as-is, and not as whole holistic system :(
Reply all
Reply to author
Forward
0 new messages