To me, blatant avoidance of make is a big, red, burning flag that whoever made that decision values novelty over value and that he's likely to be a bandwagon-jumper or a NIHer in other aspects of his professional life too, and such people are best avoided.
Autoconf is similarly an abomination that should be put out of our misery. The syntax is so opaque that 99% of people copy-and-paste the configuration file into their project, leading to 10 minute ./configure runs that check for 200 things the project doesn't use in addition to the one that it does.
Not to mention that 99% of what it's checking for is OBE: A simple check for whether you're trying to build on a modern Linux, one of 2-3 Windows build flavors, or OS X, is sufficient to set the 5-10 typical flags most projects need.
Look at LuaJIT's build process, for example. It builds practically everywhere and digs into OS internals, and yet doesn't need anything complicated to build.
I agree that Make should be retired, and yet I'm typically the "Makefile expert" where ever I work. I've worked with a lot of smart people, and most don't know anything other than the basics of Make. I have to suspect that most people who are defending Make haven't had to REALLY use it to do anything complex, because when you do, it sucks.
I'm the Makefile expert in my software shop. I still use GNU Make simply because I haven't found anything "better enough" to justify switching a toolchain. CMake was probably the closest--and that mostly because I know that it works for KDE, so I should be able to learn by example.
What sort of complexity do you think shows Make's problems? It's just a big dependency graph, so IMHO the hardest part is defining the dependencies in a way that's accurate (for parallelism and incremental builds) without being redundant. Multi-versioned builds shouldn't really add any complexity beyond a single variable per input dimension. Complex serial sub-processes can easily be factored out into scripts. Platform detection, likewise, can easily be factored out into a combination of scxripts and multi-versioned builds.
So where have you seen it suck the most? I like to think I've done reasonably complex things with it, but maybe not.
When people (myself included) start taking advantage of the fact that Make is Turing-complete and writing arbitrary "programs" in their Makefiles.
It typically starts simple; you want to do something like build ALL the files in a folder, so you use a wildcard. Then you want to add dependency checking, so you use the wildcard to convert between .o to .d, keeping the same folder structure.
And I don't want the .o and .d files to be generated where the .c files live, so I need to add this code here that converts the paths.
OOPS, this project uses a slightly different folder structure, and so I need to add a case where it looks in DIFFERENT relative paths.
Oh dear; I just realized that I need this code to work ALMOST the same in a different project that needs to be built with the same Makefile; that means I need to include it twice, using different options each time.
And it turns out that it DOESN'T work the way I expect, so now I have to use $(eval), meaning some of my Make variables are referenced with $(VAR), and some with $$(VAR), depending on whether I want them to grab the CURRENT version of the variable or the calculated version.
But now, now I have all of my code to create my project in one convenient place, and creating a new project Makefile is quite trivial! It's all very clean and nice. But the next person to try to change the folder structure, or to otherwise try to get this now-crazy-complicated house of cards to do something that I didn't anticipate has to become just as adept at the subtleties of $(eval ...) and Makefile functions (define ...); error messages when you get things wrong tend to make early C and C++ compiler errors look straightforward and useful by comparison.
For a far more complicated example, take a look at the Android NDK Makefile build system. 5430 lines of .mk files that make your life very easy...right up until you want to do something they didn't anticipate, or until they ship a version with a bug (which they've done a several times now) that screws up your build.
Here's one small excerpt for your viewing pleasure, just to get the flavor:
Hah, my latest Makefile work has been a set of functions which generate Make-syntax output, which then gets $(eval)ed. I hear you on the debugging nightmare that this can be: does a given variable get resolved when the function is first $(call)ed, when the block gets $(eval)ed, or when the recipe is invoked? But IMHO it's not too bad to do printf-style debugging. Replace $(eval $(call ...)) with $(error $(call ...)), then work backwards from there.
It also helps to be very disciplined about immediate assignment (`var := stmt`) and to always use recipe-local variables, rather than global variables.
I do feel like all of this aspect would be cleaner in Python or Lua... but the problem is, the _rest_ of the build, which more people interact with on a daily basis, gets more complex when that happens. Because there are always the ancillary targets and recipes where normal Makefile syntax works just fine.
Thanks for the NDK reference, I'm interested in seeing other "ugly" Makefile support infrastructure for comparison :)
The worst problem I had, though, was REALLY annoying; I was getting an inscrutable error in the middle of a function, and I could delete large parts of the code to get the error to go away, but putting ANY of the code back brought the error back -- it didn't matter which parts I put back.
It turned out that git had changed LF to CRLF in the file, and some end-of-line character was screwing up the spacing. Tweaking .gitattributes and fixing the files made everything work.
I SO hate significant white-space. I never really forgave Python for that "feature" either. But I could totally get behind Lua for the logic. :)
Actually, if it were my job, I would use LuaJIT to write a make replacement; the dependencies could all be specified in tables or extended strings, and any more complicated logic could be explicitly outside of the "rules".
>but the problem is, the _rest_ of the build, which more people interact with on a daily basis, gets more complex when that happens
I think a good design would NOT have that problem. You could have it say "these files get built by default rules" separately from "these rules trigger this bit of Lua code, which can spit out warnings, add dependencies dynamically (oh wouldn't THAT be nice!), or do this other bit of complicated build processing that doesn't fit well into the rule-based system".
If you're doing it in Makefiles, then yes, you could make everything more complicated that way. But I think a fresh design could really do a good job in killing make. I'm just so busy with other things right now, though...
Another reason I would STRONGLY choose Lua over any other scripting system is that the entire tool can embed Lua trivially, while Python or Ruby or Perl would each bring an entire ecosystem with it. You can have a dozen different Lua installs on your system without requiring a separate infrastructure for managing Lua installs.
Oh yeah, I like that idea. I'm just not so thrilled when I hear about modern build systems when they require me to install recent versions of relatively bulky scripting languages. I'm not a big fan of Lua in general, but this sounds like a perfect application.
there are lot's of people that use PHP for data crunching and bash for GUI and C without caring for managing memory properly.
should we retire all languages that can be abused?
also, your idea of how $ and $$ is wrong. but it could be that you messing up with = and := before that point :) so i guess your point stands. but again, all languages can be abused.
blame the bad coder, not the tool.
No, he's spot-on about that. If you are using a function from within a Makefile to generate Make code which then gets $(eval)ed, then then inner function must output $${variable} so that the outer function sees ${variable} and does not immediately resolve it.
It's hairy. Hairier than macros in C. But like any other specialization, it can potentially save an immense amount of time for the rest of the tam.
Sorry, but I wasn't messing up those two. That's Makefile 101 knowledge; I'm talking about crazy advanced stuff, where := doesn't work the way you expect.
Even := doesn't do what you want if, after the Makefile has been loaded and you've used := three times on the same variable, ALL the instances of that variable are replaced by the last assignment. Here's an example:
FOO:=1
rule1 :
echo $(FOO)
FOO:=2
rule2 :
echo $(FOO)
make rule1 and make rule2 both echo 2. $(FOO) is evaluated in both cases AFTER the Makefile is loaded. rule1 : FOO:=1
rule1 :
echo $(FOO)
rule2 : FOO:=2
rule2 :
echo $(FOO)Turns out it wouldn't work for the usage pattern I needed (my example was simplified -- typically the variable settings would all happen in another file, and they couldn't happen on a target line because there wouldn't be a single target to use, in addition to just being ugly for that use), but it's good to know.
For what it's worth, as much as I rag on Make, I also find myself using it most of the time. To paraphrase Churchil, it's the worst build system imaginable, except for all the others.
I would just really, really love a system like Make crossed with an imperative language for everything that doesn't fit well with the auto-dependency-tracking model. There's a product in there somewhere.
Microsoft's crazy-ass xml build mechanism is a giant pain in the ass, even compared to their old nmake (I think) tool.
The new system is a great example of a system where someone presumably said "Make is garbage, a big red flag, let's build something better."
If you're on a *nix system, building C/C++ programs (or automating one-off builds like the example), what would you recommend in make's place?
I recorded an example of installing and using it here:
http://rachelbythebay.com/jvt/view?bb_install
You can get a copy for experimentation here:
It isn't for everyone, but only you can know if it'll meet your needs. Hopefully this helps.
So you're proposing that people give up screwdrivers because figuring out what bit to use is hard, and that they should instead use your potato peeler, never mind that it doesn't actually drive screws?
Regarding the build tool source, I haven't decided what to do about that just yet. It could be particularly valuable in a corporate environment.
[1]: http://gittup.org/tup/ [2]: http://community.haskell.org/~ndm/shake/
The completely unprofessional tone here really turns me off to the entire system. If you write like a typical teenager, you probably code like a typical teenager, and I don't want a typical teenager writing my goddamn build system.
Besides: who the hell is bottlenecked on the build system? The compiler and linker (or the equivalent for your favorite language) do all the work. Anyone who believes this article makes a different is completely ignorant of Amdahl's Law.
Many projects are bottlenecked on the build system. You can benchmark this by timing a null build (running 'time make' after building everything). Some examples from my machine are the Linux kernel (28 seconds), and Firefox (1m 23 seconds). Some of this time is from unnecessarily recompiling things, but that is a separate issue from the inherent lack of scalability in make.
Suppose I want to change a single C/C++ file in one of these projects - the total turnaround time from when I type 'make' to when the build finishes can be described as:
T(total) = T(build system) + T(sub-processes)
Ideally T(total) would be zero, meaning we get an instant response from when we change the file to when we can test the result. Here, T(build system) is the null build time, and T(sub-processes) is the time it takes to run the compiler and such. Using the Linux kernel as an example again, compiling fs/ext3/balloc.c takes 0.478 seconds. In comparison to the null build of 28 seconds, there are significant gains to be had by optimizing T(build system).
Amdahl's Law is a little tricky to apply since tup is not parallelizing T(build system), but rather changing it from a linear-time algorithm to a logarithmic-time algorithm. So you can set P easily based on the relative values of T(build system) and T(sub-processes), but S is not a simple "count-the-cores" metric. The speedup is effectively N/log(N), where N is the number of files. This is much better than simple parallelization - T(build system) for tup with these projects is only about 4ms. The total turnaround time for the balloc.c file in the Linux kernel is 1.1 seconds (which includes compilation and all the linking steps afterward), in comparison to make's total turnaround time of 29.5 seconds.
I have never seen cases so extreme, but my opinion on the matter is that this is a "build smell". If the Makefile has to resolve a DAG this large, that means that developers have to worry about compile- or link-time interactions this large, as well. 100k source files all linked into a single executable is more complex than 10k source files split across 10 executables, and a handful (say <100) of headers which represent "public" APIs. Because if you have 100k source files and your developers haven't all killed themselves already, then there are some firewalls separating various modules already. Formalize it at an API level and split apart the builds, so that it's _impossible_ for anything outside of the API itself to trigger a full rebuild.
I don't deal with this by not using make, I deal with this by not writing recursive makefiles.
Yes, reinvoking Make repeatedly tends to force redundant `stat` calls. But I have worked in environments where heavily-templated code was hosted over a remote filesystem, and every `stat` call was something like 10msec. That adds up _extremely_ fast, even with non-recursive make. Ugh.
Most developers will never see such a system. Optimizing for that kind of scale at an early stage has all the problems of any other premature optimization. It's most important to just get the build system out of the way so you can get your real work done, and you do that by writing makefiles, since makefiles are universally understood.
Now, when a project does grow to the proportions you mention, you can start looking at alternatives --- but I'd argue that these alternatives should amount to more efficient ways to load and evaluate existing makefile rules, not entirely different build paradigms. Make's simplicity is too important to give up.
Also, I am bottlenecked on my build system at my workplace, which takes ~45 seconds to realize nothing needs to be done (It isn't "make", because "make" does not support our build process).
I like how he uses Sauron's All Seeing Eye as a drop in replacement for gods algorithm.
Feature-wise premake isn't entirely caught up to CMake but it has everything important. Also the premake files look a lot cleaner and more readable compared to cmake files.
It's called bloody "make". If Prolog is a language, so is make. Make is still a scripting language even if it's not boneheadedly sequential and imperative the way, say, Python is.
I get the feeling CMake works fine as long as you color within the lines, but that it's much harder to extend than autotools is.
For me the overall factor is the ease for any of my users, installing something I have written.
It is also impossible to debug, partly because of the abomination the cmake language is: even understanding where a variable is defined is hard, and because the language is so unexpressive, the Find*.cmake modules are often in the 1000s lines count.
For all its suckiness, I take autoconf/automake over cmake.
CMake is not "impossible to debug." In fact, it is a lot easier to debug than autotools-- partly because you end up writing so much less code. Also you don't have the three levels of "generated files generating other file generators" that you do in autotools.
For all its suckiness, I take autoconf/automake over cmake.
Well, we agree on one thing. autotools does suck.
Your experience with debugging autotools vs cmake does not match mine: cmake is not an improvement over autotools (if only because at least with autotools, there is some decent doc out there and google knows a lot about autoconf insanity). It took me hours to debug trivial issues with cmake, because you can't easily trace where variables are defined.
CMake also supports cross-compiling. www.vtk.org/Wiki/CMake_Cross_Compiling
And, there's a minimalist version called "do" which is a hundred-or-so lines of shell, that does a complete rebuild (no dependency tracking) - so you can package that with your project, and not have to worry about your users having to install yet another build system.
Other alternatives I'd recommend, with some degree of success:
SCons - cross platform, but slow (every big enough project eventually abandons it)
waf - a unix-only (AFAIK) SCons derivative that's a bit more limited, but much faster
CMake - cross platform, very complete, on par with Make on every level (including ugliness and complexity)
Premake - cross platform, makes IDE file but can also write makefiles for you.
In a nutshell, credo tries to advance the build-tool state of the art, by replacing *ake files with shell-scriptable commands to build a system from bits of library code. More at http://github.com/catenate/credo See especially the first literate test, which has a more complete introduction.
http://news.ycombinator.com/item?id=4190804
I use it on Windows too with an strace replacement I wrote which isn't online but if anyone's interested then just ask.
Syntax is not intuitive? It's not bad once you've read the docs.
Complex Makefiles with huge included boilerplate libraries (e.g., the Android build system) are completely impossible to debug in any reasonable way? THAT is the problem.
As soon as you do anything non-trivial in your Makefile, you've created something that, when it fails, the reason will be COMPLETELY opaque to anyone who isn't intimately familiar with the ENTIRE program.
The implicit connections that a Makefile makes for building are great for JUST that part. As soon as you try to use them to write a program, you have to commit all manner of write-only-code abominations. I know because I've both DONE this and tried to debug OTHER people's code.
There is NO good way to debug Makefiles. And that alone means that they should be consigned to historic projects only, to be replaced by SOME kind of a better system.
There's your answer: there are many alternatives, but only one make. Ok maybe 2 o 4. :) But a build system should for the most part be platform agnostic. I have such a hard time understanding why so many programming languages seem to need their own Make alternative (Rake, Cake, Fake, ...?)
...but so are bash scripts.
I wouldn't advocate actually using either of these though, unless the situation is appropriate.
Every system and platform has different requirements, and its a bit of a big ask to want make to be 'the right tool' for all of them.
Certainly, I'd never use make to look after a ruby project, or a c project. That doesn't mean I think make is horrible and I'd never use it for anything, but you can see, perhaps, how something as low level as make might not provide all the tools (dependency management, downloading resources from git hub, automatically tracking system information, a templating system for generating system-dependent headers) you might need for some things.
You could think about it this way: If make did all of these things and was easy to use, there wouldnt be all of these other clones and different attempts at the same thing.
...but there are. So there's certainly something about it isn't making people happy.
There are a lot of people who use makefiles for C projects, but most of those people don't write them by hand.
They use a frontend that generates a vastly complex and arcane makefile using either automake, cmake, qmake, etc.
These makefiles are utterly unmaintainable and deserve a place next to 'goto' in the section labeled 'considered harmful'... but they serve a purpose; correctly collecting build settings, templates and metadata and using those to construct the correct makefile.
That doesn't count as 'using make'.
There are a vanishingly few projects that actually use make; google for it's NDK builds (and a few other things; but these are massive recursive makefile monsters that you have a tiny safe api to work with), LUA with its 15 makefiles, one for each platform. There are a couple of other examples, but not many. I can't think of any big ones off the top of my head.
I think we can safely say that writing a Makefile to build your C code is a bad idea.
Fact is, a makefile for building C code is typically smaller than the autoconf files required to get autotools to work on the same code. Most horrible makefiles are written by terrible build automation software (autotools being among the worst offenders here) or by people who don't understand the dependency graph model. A ten line Makefile that automated something repetitive is fantastically important, even if it is just writing down something in an executable fashion such that you don't have to remember it later. Almost every build automation tool out there either doesn't scale up (too simple) or is too hard for small work, or occasionally both, like Ant.
If I need to do something simple, a Makefile is only a very little bit more complex than a command line—I frequently crib the command I just ran to start off the Makefile. If I needed to do nontrivial logic in a Makefile and couldn't avoid it, I wouldn't use jam or tup or redo or SCons—I couldn't, because they're less useful than make! I would probably end up using Rake, which is the only build automation tool I've seen so far that isn't a make clone and can do implicit dependency generation.
I don't think that's a safe assumption at all, I think that's a dangerous overgeneralization. You're only considering open-source projects... and as you point out, both NDK and Lua (projects in the embedded space) use Make. I would not be surprised to find hordes of non-OSS embedded developers using Make natively precisely _because_ it is the "assembler" of build systems.
Can you give an example with the ubiquity of make and better expressiveness?
Nothing is more frustrating than discovering that you need to install a new component or build system just to build a specific component. It gets worse when dealing with multiple external components in different build systems.
"It's also very hard to learn and very difficult to maintain"
I agree insofar as most people go into make without trying to learn it properly. There's a manpage and pretty good documentation for the GNU extensions. But in my experience, with custom build setups using stuff like Maven, a lot of time is spent fighting the build system when a simple Makefile would suffice.
"with the added benefit of not having to learn a new language."
You've shifted the burden from "learning a new but very simple language" to "learning a framework atop your language", which (based on my reading of gradle's docs) is not very concise.
If you're talking about a FOSS component that random people will have to rebuild: Yes, absolutely. (Go listen to the Linux From Scratch community complain about CMake!)
If you're talking about a proprietary project that will only be distributed as binaries, or maybe even not distributed externally at all, why not pick the best tool for the job?
Of course there are tradeoffs in anything, and if the expressiveness of your build script is the deal-breaker for your project, by all means use make. On the whole, I will still argue that more modern alternatives provide a better total experience. Also, note that the OP is directed at beginners.
Also, I quite specifically didn't claim that make is never the right choice.
I wouldn't say that I love make, but it is my tool of choice. I have tried out SCons and CMake, and both seemed to require more hoop-jumping for customizations. That is, Makefiles are very close to being raw shell scripts. That, and SCons had performance issues (IIRC due to the md5sum it was doing on every input file). I have casually perused bjam files and even _building_ boost seemed awkward to me, with the weirdo string-parsing within individual flags. I am happy with autoconf as a package user, but I've never developed any software packages of my own with it.
I typically work with C and C++ on Linux server environments. I'm quite happy with non-recursive Makefiles, timestamp-based change detection, and GCC-generated dependency graphs. This setup does incremental builds correctly for me, and parallizes linearly (and occasionally superlinearly due to I/O). I'd genuinely like to know what magic sauce you would recommend for build automation on this sort of a platform.
There's a clear downside to having dependencies on relatively obscure, cutting-edge technologies that may not necessarily work or be available elsewhere. And since build systems usually don't really need to be fancy, as much as they need to work, being conservative in this area usually doesn't hurt.
That works against a portable build. That still matters to some of us.
We are not stuck on any particular platform, we target numerous platforms new and old. Using make, the target may determine which of those wonderful tools in our ever changing (improving, failing and obsoleting) ecosystem does the actual build.
build:
xcodebuild -target MyApp -configuration Release clean
xcodebuild -target MyApp-universal -configuration Release-universal clean
But all I need to do is type makeI have a few websites with very short makefiles to package static files. That's 5 simple lines of mostly patterns, very easy to understand and there's no tooling related overhead on any system we use. I wouldn't say that make is better than all of the alternatives but you're in serious diminishing returns territory trying to make further improvements.
The real advice I would offer is the bottom line observation that your build system is supposed to save you time. Don't start looking for a cool-kid approved new one until you're spending time on the tool rather than the complexity specific to your project.
There are many, many way better alternatives to make. Which one is better depends on the platform you're on. The majority of them throws in automatic dependency management for free.
Yes, I know that the essence of the post is "use a build system". I agree completely. In fact, script everything. Then script your scripts. Then refactor your scripts because they are getting messy. But don't give impressionable souls the idea that "make" is anywhere near an acceptable (generalised, default) choice today.