1. 45

  2. 23

    There’s also the ninja-compatible samurai. Written in C instead of C++, and significantly less of it.

    1. 4

      What makes samurai so much smaller? I’m not familiar with either codebase, but I would guess that Ninja has more complex optimizations for fast start up for massive builds. I vaguely recall reading something about lots of effort going into that.

      I have no bias either way, just curious.

      1. 21

        I would guess that Ninja has more complex optimizations for fast start up for massive builds

        In my personal benchmarks, samurai uses less memory and runs just as fast (or slightly faster) than ninja, even on massive builds like chromium. If you find a case where that’s not true, I’d be interesting in hearing about it.

        As for why the code is smaller, I think it’s a combination of a few things. Small code size and simplicity were deliberate goals of samurai. As a result, it uses a more concise and efficient coding style. It also lacks certain inessential features like running a python web server to browse the dependency graph, spell checking for target and tool names, and graphviz output. samurai also only supports POSIX systems currently, while ninja supports Windows as well.

        In some cases, samurai uses simpler algorithms that are easier to implement. For example, when a job is finished, ninja looks up whether each dependent job has an entry in a map, and if it does, it checks each other input of that job to see if it was finished, and if all of them are ready it starts the job. samurai, on the other hand, just keeps a count of pending inputs for each edge and when an output was built, it decreases that count for every dependent job, starting those that reached 0. This approach is thanks to @orib from his work on Myrddin’s mbld.

        I have no bias either way, just curious.

        As the author of samurai, I am a bit biased of course :)

        1. 2

          If you find a case where that’s not true, I’d be interesting in hearing about it.

          I don’t have any observations. It was just an off-the-cuff guess based on an article I read about ninja’s internals, and the possibility that you may have traded a little performance for simpler code. But on the contrary, your example of a simpler algorithm also sounds more efficient!

          Thanks for the detailed reply. I didn’t even realize ninja had all those extra features, so naturally I can see why you’d omit them. I just installed samurai on all my machines! :)

          1. 1

            samurai also only supports POSIX systems currently, while ninja supports Windows as well.

            Any chance fixing this and adding Windows?

      2. 9

        In all honesty, I don’t find ninja much of an improvement. The syntax is a little bit more intuitive than make, but not enough to lure me into looking at it seriously.

        I like the aesthetics of rake but requires ruby and gem which is a deal breaker, if you’re not a ruby shop. The make utility is omnipresent and Makefiles ubiquitous.

        1. 9

          Is it really that complex to write Makefile for such simple project? And in many projects it can became even simpler as there are implicit rules in GNU Make for C/C++/TeX/etc.

          OUTPUTS = variables.pdf
          .SUFFIXES: .svg .pdf
          .PHONY: all
          all: ${OUTPUTS}
          	rm -rf *.pdf
          	inkscape "$<" --export-text-to-path --export-pdf="$@"
          1. 12

            The makefile is simple, but knowing which incantations to use to make it simple is harder.

            1. 3

              Yeah, I was a bit surprised at Julia writing off make as arcane when she’s covered things like ptrace and kernel hacking elsewhere. Poorly documented is a better descriptor for make, and other people have done a decent job of fixing that.

              Maybe she will see this and write another article on how make isn’t too arcane. :)

              1. 3

                I’m (obviously) always interested in learning about things I thought were arcane and complicated are not actually that complicated! I’ve never read anything about make that I thought was approachable, but I haven’t tried too hard :)

                I think the thing that throws me off about Make is that by default it seems to decide how to build something based on its file extension, and I find this pretty hard to reason about – what if I have 2 different ways to build PDFs files? (which I do).

                1. 2

                  I’m no Makefile expert, but the things with the file extensions are implicit rules and are basically there to save you from having to copy/paste the same basic rule template for each file. You can always override them with your own explicit recipe since those take precedent:

                  special.pdf: special.svg
                  	do-this-command     # indent via TAB!
                  	then-that-command   # ...

                  For simple one-off uses, I usually ignore all the details of the implicit rules and just spell everything out explicitly like that. I usually don’t even bother with automatic variables like $^ and $@ since I can never remember which is which. I’ll just copy/paste and search and replace all the recipes, or write a quick Python script to generate them. It’s hard to get much simpler than that. Just remember that the first target in the Makefile is the default if you type make with no targets.

                  1. 2

                    I found the GNU Makefile manual to be very good about describing how make works.

                    The two different ways you make PDF files—do both methods use the same input files? If not, implicit rules can work just as well if the inputs are different (at work, I have to convert *.lua files to *.o files, in addition to *.c files to *.o).

                  2. 3

                    FWIW I wrote 3 substantial makefiles from scratch, and “gave up” on Make. I use those Makefiles almost daily, but they all have some minor deficiencies.

                    I want to move to a “real language” that generates Ninja. (either Python or the Oil language itself :) )

                    Some comments here but there are tons of other places where I’ve ranted about Make:


                    I think my summary is that the whole point of a build system is to get you correct and fast incremental and parallel builds. But Make helps you with neither thing. Your makefile will have bugs.

                    I never condensed my criticisms in to a blog post, but I think that’s the one-liner.

                    1. 1

                      I hear you, and I know that make is the standard make-type things are a bit more legitimate than the article I’m referencing.

                      That said, the amount of inexplicable boilerplate in something like this (from up in the thread) are why I’m super-empathetic to people who find Makefiles too complicated. And that’s ignoring things like the tab-based syntax or the fact that Makefiles effectively yoink in all of sh (or, honestly, these days, bash) as a casual comprehension dependency.

                      Makefiles can be very simple; I absolutely grant you that. They generally aren’t. And even when they are, they require a certain amount of boilerplate that e.g. ninja does not have.

                    2. 2

                      That’s about how I’d write it for a good hand-written yet scalable Makefile, but it’s worth noting that you can go even simpler still here:

                      pdfs/variables.pdf: variables.svg
                      	inkscape variables.svg --export-text-to-path --export-pdf=pdfs/variables.pdf

                      Easy enough that you can do it from memory. This is also the style that I go for when writing a quick script to generate a Makefile.

                    3. 8

                      Shake can also consume ninja build files, despite also including its own (more powerful) build language.

                      1. 2

                        Shake took me a little while to get my head around, but it’s really cool. Wound up baking it into an internal tool at work, and replaced a lot of makefiles and shell glue.

                      2. 8

                        I love using Ninja for build systems. I use it for Javascript and website building, more recently I converted our 3d-printed open source microscope build over to it.

                        I feel like the use-cases Ninja was designed for are not the same as what I want to use it for, but it’s actually quite well suited because of it’s simplicity and focus on generating the build file. I wish it did input file hashing instead of reading timestamps as an option. That would be really useful for caching CI builds. Someone implemented it but the patch was rejected. I think because with C/C++ it’s a solved problem. I am considering maintaining a fork with that feature now (read more if you want).

                        1. 3

                          Oh that’s cool … Did you see this post a few months ago? The authors were describing a system that used Ninja and content hashes (via OSTree). I have wanted to play around with it, but haven’t gotten around to it.


                        2. 5

                          For custom written rules, I personally find that nothing beats the simplicity of redo. The build system is language agnostic in that you can execute a build step in any language and redo only tracks what needs to be rebuilt. For the example at hand, you could write the conversion rule as (in a file named default.pdf.do):

                          svg2pdf $2.svg $3

                          Then by calling redo-ifchange *.svg the entire process would be taken care of.

                          I would recommend using rsvg-convert -f pdf $2.svg > $3 in the example above. It is a much faster way to convert svgs to pdfs. If I remember correctly, it’s part of librsvg, an svg library written in rust.

                          1. 4

                            What implementation are you using? If I were going to use Redo, I would want to keep it in-tree because it’s not nearly as widely deployed as Make/Ninja.

                            1. 4

                              One of the nice things about redo is that there’s a single-file pure-shell implementation that only knows how to call the all build-scripts in the right order, which is great for shipping to end-users so they can compile it and move on with their lives.

                              Meanwhile, developers who are invested enough to build your software multiple times can install redo, which does all the incremental-build and parallelisation magic to make repeated builds efficient.

                              1. 1

                                Do you have a link?

                                1. 3

                                  Not your parent commenter, but maybe they meant https://github.com/apenwarr/redo/blob/main/minimal/do

                                  1. 2

                                    Aha! Thanks. Currently I am using a build script by our very own @akkartik, which has worked for me so far:

                                    #!/bin/sh -e
                                    test "$CC" || export CC=cc
                                    export CFLAGS="$CFLAGS -O0 -g -Wall -Wextra -pedantic -fno-strict-aliasing"
                                    # return 1 if $1 is older than _any_ of the remaining args
                                    older_than() {
                                      local target=$1
                                      if [ ! -e $target ]
                                        echo "updating $target" >&2
                                        return 0  # success
                                      local f
                                      for f in $*
                                        if [ $f -nt $target ]
                                          echo "updating $target" >&2
                                          return 0  # success
                                      return 1  # failure
                                    update_if_necessary() {
                                      older_than ./bin/$1 $1.c greatest.h build && {
                                        $CC $CFLAGS $1.c -o ./bin/$1
                                      return 0  # success
                                    update_if_necessary mmap-demo
                                    update_if_necessary compiling-integers
                                    # ...
                                    exit 0
                          2. 2

                            I’m a huge fan of BSD Make. Here is a sample Makefile from a project I maintain called libpushover:

                            SHLIB=	pushover
                            SHLIB_MAJOR=	0
                            SRCS=	libpushover.c sanity.c
                            INCS=	libpushover.h
                            CFLAGS+=	-I${.CURDIR} -I/usr/local/include
                            LDFLAGS+=	-L/usr/local/lib
                            LDADD=		-lcurl -lsbuf
                            .if defined(PREFIX)
                            LIBDIR=		${PREFIX}/lib
                            INCLUDEDIR=	${PREFIX}/include
                            .include <bsd.lib.mk>
                            1. 2

                              Hot take: a frankenstack of Python generating Ninja generating commands that execute in a shell is not simple.

                              Nothing against Ninja. This blog post on its design is great. But it’s enormously subtle. The fact that Chrome and other large projects use it should be a warning to most projects, not an encouragement.

                              1. 1

                                You don’t need a frankenstack! After seeing this post I wrote a small Ninja generator in Python and it worked really well. Take a look: https://git.sr.ht/~max/ghuloum/tree/master/build.py

                                1. 2

                                  Why, though. Why is this a good idea? Why is it a better idea than generating a Makefile with Python?

                                  Introducing unnecessary dependencies is the essence of a frankenstack, to my mind. Even if you built your Ninja generator in machine code, I’d wonder why.

                                  There are certainly good reasons in context. Sometimes we find ourselves backed into a situation with existing Ninja files. And large projects have a way of bending spacetime in their neighborhood. But it’s always something to undertake with regret. I don’t understand this valorization of Ninja generators.

                                  1. 1

                                    If you don’t want to use Make, either for philosophical or technical reasons, this is a fine idea. If you have more complex build requirements but don’t want to use CMake, this is a fine idea.

                                    1. 0

                                      If you live in a universe where the only options are to use make or cmake or ninja. Or to stab yourself repeatedly in the face.

                                      My claim: start with the assumption that it’s a stupid, lousy, no-good idea. Then go ahead and do it if all the alternatives are worse, based on your philosophical or technical reasons. And don’t ever call it a fine idea.

                                      1. 2

                                        There are a lot of universes where it does not make sense to pay someone to rewrite a good build tool, and this is what ends up happening in those universes.

                                        1. 2

                                          Final note for posterity: there was a miscommunication here that we identified offline. To clarify, I am criticizing overuse of Ninja generators, not Ninja itself.

                                    2. 1

                                      Why, though. Why is this a good idea? Why is it a better idea than generating a Makefile with Python?

                                      In my experience with using cmake on a middle-sized project, the generated build.ninja is significantly faster than the Makefile at incremental builds.

                                      1. 1

                                        That totally makes sense! The blog post I linked to also discusses performance. It’s a far superior framing to think of Ninja generators as an optimization, paid for with some (bounded) additional complexity.

                                2. 2

                                  For simple builds, I like using tup. The syntax is light, and it’s very fast to build (I also like the progress output).

                                  Documentation could be better though.

                                  1. 1

                                    Should languages just specify their build systems?