The Makefile I use with JavaScript projects
olioapps.com
olioapps.com
I hate hearing people using 21st century or modern as reasons for inflating complexities. Without rein on complexity (whether it is hidden or not), the future is doomed, whatever you are building. While I am not saying we should avoid complexity at all cost, I am insisting that all complexity should be balanced with merits.
The merits of filenames with spaces is they read better in a GUI explorer. Whether that merit balances out all the complexity it brings is individual dependent. For me, that merit ranks very low and I avoid spaces in my filenames at all opportunities. For some, they need those filenames to be readable. And there are solutions. One solution, from those who don't code, seems to demand ("developers", paid or not) that every tool that deal with files should handle this additional complexity, regardless of their context and what they think. Another solution would be to add an additional pre-step of copying/renaming/linking/aliasing. With the latter solution, the complexity is confined.
I guess for some, it only matters with "I do work" or "they do work" rather than the big picture. That is fine. However given the context of you are working with Makefiles, then you are a developer at some level, you are supposed to do some work.
Users expect computers to work in a non-surprising ways.
It isn't natural to use dashes or underscore in file names. Training users to be afraid of spaces is just teaching them one more way that computers are scary and unpredictable.
Meanwhile over in Windows land, all tools have been expected to deal with spaces in them for what is approaching 20 years.
That is an excellent example that deserves a second look from a different aspect.
... it has trained a crop of computer users that are afraid of command lines and with an attitude of anything beneath the GUI interface is owned by and of someone else's problem. They are scared of computers more than ever. They are very scared of it and having it heavily disguised as an appliance is mandatory.
"Users" is a very broad category with a bunch of partitioning.
The particular subgroup in question are authors of build automation systems for medium to large scale software projects. They (we) have a rather different perspective on "surprising".
Developers are used to not using spaces: oneStupidClass or AnotherStillStupidClass or others_do_it_like_this or bIhatehungariannotation or daxpy .
As you can see it is not that bad and it has been a widely used practice for decades.
Since you are comparing specifically to windows land, perhaps it is their focus on using spaces in file names that made them always be worst than unix like systems in terms of stability, sequrity or performance.
But to use that as evidence of why spaces should not be supported in filenames is putting the cart before the horse. The goal of software is not to perpetuate whatever mistakes have been made in the past. It's to solve problems for human beings.
And human beings have been using spaces to delimit words since long before computers existed.
mv old-file new-file
Spaces separate the verb, the direct object, and the indirect object. Using commas or colons instead would painfully artificial.So the question is whether you will favor naturalness on the command line or in the GUI. It's no surprise that a Unix build tool favors the command line.
The words and sentences are inside the file. The filenames are identifiers to the files. Identifiers' purpose is mainly to identify, rather than to communicate.
On the other hand, we could imagine a user interface that users don't see (actual) filenames at all.
A real world possibility.
What about real world is, it is complicated.
You can accept that or you can keep tilting at windmills.
In every branch of science, reality wins. If your model can’t accomodate reality it’s either completely wrong or it needs adjustments, at least.
Exactly. To separate things. Which, incidentally, happens to also be precisely what make, and the traditional UNIX shells do :-)
The problem isn't that space in itself is a particularly difficult character. The problem is that its meaning is overloaded and ambiguous. No matter what you do, computers will have difficulty with ambiguity. You'll always have the problem that the separator is special, but hey, I'd be all for using 0x1C instead ;)
Nope. You don't space out each word when speaking. If you were speaking about writing systems, not all writing systems use spaces as word delimiters. See: https://en.wikipedia.org/wiki/Space_(punctuation)
Wow.
So human beings should stop using allowed file names because it's too hard for you?
I've even worked with variables with spaces and special characters.
I don't see why filenames are so much more special, except a lot of old tools never got updated to world beyond ASCII
I don't understand why one would think that make would be a good tool for this...
> Demanding the support of spaces in filenames significantly complicates code as simple space delimination no longer works and other delimination schemes are much more error prone -- forgetting balancing quotes, any one?
Yes this is a limitation of make, but not a million other tools out there.
Make is not a panacea, no tool is - pressing a tool into a job it is un-suited for because you understand it is "Not good" (trade mark and copy right pending).
This is the classic case of having a hammer and a screw -
Even this merit is debatable. Is foo bar two things or one? I know foo-bar is one.
No. He's right, you're wrong, hzhou321. We want spaces in filenames. We even want UTF-8 if possible. We don't want crude tools that cannot handle the most basic names. You can argue all you want, this is a very very basic demand that could be met with very very basic tools but make is just too crude.
People like you are exactly the cancer in the developer community that argues away reasonable demands like spaces in filenames and perpetuates the garbage legacy tools we have.
#include <stdio.h>
int main(void)
{
puts("Hello, world!");
return 0;
}
And here's the minimal Makefile to generate the output: hello world:
Of course, I did have to swap out the ASCII SP (character 32) for the Unicode non-blank space (code 160) to get this to work, but hey, spaces!How?
EDIT: Ok, now I got it. Boy that was a wild ride.
Edit: rewording and typos.
Being incapable of handling spaces is a bug that's been marked Minor since 2002 - https://savannah.gnu.org/bugs/?712
(plus I find double quotation marks easier to read and write than escaping every space in a path)
Edit: https://stackoverflow.com/questions/66800/promising-alternat... (they aren't really clones tho)
Specifically, a python configure script using the ninja_syntax.py module. This seems like it's a bit more complicated, but has a lot of nice attributes.
File names with spaces should just work (unlike make). The amount of hidden complexity is very low (unlike make or SCons); all the complexity lives in your configure script. It's driven by a real, non-arcane language (unlike make). Targets are automatically rebuilt when their rules/dependencies change (unlike make).
It's more difficult to install than make, but only marginally.
And what the heck happened on 31 dec 1999 that the world became a different place where suddenly people realised: there were these space things, quite useful they were, why don't we tuck them into every file name and URL and who knows what?
People have better things to do than dealing with these things.
Then that's a shortcoming which should be addressed with the tools, because humans everywhere use spaces in filenames.
For every command-line tool I make (Windows & Linux), I ensure it handles such trivial use-cases. I can't see why such a simple task is seemingly impossible to get done in GNU coreutils.
That's true.
> and the UX for that is actually pretty close to what you would want.
That is so not true. Make has deeply woven into it the assumption that the product of workflows are files, and that the way you can tell the state of a file is by its last modification date. That's often true for builds (which is why make works reasonably well for builds), but often not true for other kinds of workflows.
But regardless of that, a tool that makes a semantic distinction between tabs and spaces is NEVER the UX you want unless you're a masochist.
I've always wondered whether Make would be seen as less of a grudging necessity, and more of an elegant panacea, if operating systems had gone the route of Plan 9, where everything is—symbolically—a file, even if it's not a file in the sense of "a byte-stream persisted on disk."
Or, to put that another way: have you ever considered writing a FUSE filesystem to expose workflow inputs as readable files, and expect outputs as file creation/write calls—and then just throw Make at that?
How are you going to make the result of a join in a relational database into a file, symbolically or otherwise?
Examples? I mean, there are some broken tools (EDA toolchains are famous for this) that generate multiple files with a single program run, which make can handle only with subtlety and care.
But actual tasks that make manages are things that are "expensive" and require checkpointing of state in some sense (if the build was cheap, no one would bother with build tooling). And the filesystem, with its monotonic date stamping of modifications, is the way we checkpoint state in almost all cases.
That's an argument that only makes sense when you state it in the abstract as you did. When it comes down to naming a real world tool or problem that has requirements that can't be solved with files, it's a much harder sell (and one not treated by most "make replacements", FWIW).
Anything where the relevant state lives in a database, or is part of a config file, or is an event that doesn't leave a file behind (like sending a notification).
Last modification date is not always a correct heuristic to use, but it's quite cheap compared to hashing things all the time.
Make is a tool for transforming files. I wonder how it's not quite natural and correct for it to assume it's working with files?
You're referring to a standard Unix tool, an operating system where EVERYTHING is a file.
I guess it depends on how you define "know", but there are implicit rules.
$ cat foo.c
#include <stdio.h>
int main() {
printf("Hello\n");
return 0;
}
$ cat Makefile
foo: foo.c
$ make
cc -O2 -pipe foo.c -o foo
$ ./foo
Hello make foo.c
Should just work. No makefile needed.IMHO what makes it annoying for projects other than C or C++ is that there isn't an equivalent portion of makes "standard library" that applies to e.g. java, but this is largely because java went down a different path to develop its build ecosystem.
In an alternate reality java tooling might have been designed to work well with make, and then make would have a substantial builtin knowledge base around how to work with java artifacts as well as having a really nice UX for automating custom workflows, but instead java went down the road of creating monolithic build tooling and for a long time java build tooling really sucked at being extensible for custom workflows.
Not really. The bit being pointed out here certainly isn't. It's not any special design going on, it's just a built-in library of rules and variables for C/C++/Pascal/Fortran/Modula-2/Assembler/TeX. These rules are no different than if you had typed them in to the Makefile yourself. And if you don't like them, you can say --no-builtin-rules --no-builtin-variables.
The only actual bit of C-specific design I can think of is .LIBPATTERNS library searching.
It's so depressing when people use arguments like "it's old", "it uses tabs", and "it's hard to learn". As described by one Peter Miller, "Make is an expert system". If a tool is the most powerful, standard, and expressive among its peers, its age or the fact that it uses tabs should be inconsequential.
If anything, the fact that it's decades old and used in every major software project is a testament to its effectiveness, not a drawback.
And if "learning Make" is a barrier, that to me is a sign that someone cares more about complaining than about their project. The same way people learn Git when it's clear that it's the best tool, people learn Make. It really isn't that hard. Even the basics are enough to reap huge benefits immediately.
Part of the reason is because people see the superficial issues (like the discussion regarding spaces) before they see the value of years (or decades) of work. It doesn't help that when folks bring this up many times you get "you're holding it wrong" type responses.
I sympathise with both sides of the argument. I don't know the solution but it's unfortunate seeing folks reinventing the wheel and struggling with problems solved in the past.
My issue is that people who need serious solutions forgo Make because "tabs, man", or because Webpack has pretty console output.
The bigger reason though is that it’s not very idiomatic for JavaScript projects to use Make. It sounds like the only reason that some people go out of their way to use it is because they actually don’t want to learn something.
Any common examples?
> "It’s not very idiomatic for JavaScript projects to use Make."
While I agree that popularity is a factor in picking a tool, it shouldn't be a deciding factor. Going by popularity is precisely how we end up with a new Build System of the Year(TM) every few years. The fact that we've gone through 4 fairly prominent tools (Gulp, Grunt, Broccoli, Webpack), all of which contending to "fix" the previous, and none of which have proper DAG or incremental builds (which Make has had for decades) is damning evidence.
In other words, I think Make could be (and I wish it was) idiomatic for JS.
And then there’s Windows...
Anyway, the fact that things change quickly in JS-land is more of a testament to how popular it is than anything else IMO. If C were used in the same environments as JS, I’m sure that you'd see just as much churn.
Just dealing with breaking changes in any one of those is a full time job!
We're stuck with Make because of network effects. I wish that it could just become "lost" forever and a different dependency-based-programming build tool could replace it... but that's just wishful thinking. The pace of our progress is doomed to be held back by the legacy of those poor design decisions for a long time to come.
In all seriousness, what's wrong with it? Significant tabs aren't great, but I feel like that's a relatively minor wart. The simple things are _very_ simple and straightforward. The more complex things are more complex, but usually still manageable...
I've seen plenty of unmanageable Makefiles, but I haven't seen another system that would make them inherently cleaner. (I love CMake, but it's a beast, and even harder to debug than make. If it weren't for its nice cross-platform capabilities, I'm not sure it would see much use. It's also too specialized for a generic build tool. Then again, I definitely prefer it to raw Makefiles for a large C++ project.)
1. Claiming a rule makes a target, but then fails to make that target, ought to be a runtime fatal error in the makefile. I can hardly even guess at how much time this one change alone would have saved people.
2. String concatenation as the fundamental composition method is a cute hack for the 1970s... no sarcasm, it really is... but there's better known ways to make "templates" nowadays. It's hard to debug template-based code, it's hard to build a non-trivial system without templates.
3. Debugging makefiles is made much more difficult than necessary by make's default expansion of every target to about 30 different extensions for specific C-based tools (many of which nobody uses anymore), so make -d output is really hard to use. Technically once you learn to read the output it tends to have all the details you need to figure out what's going wrong, but it is simply buried in piles of files that have never and will never be found in my project.
4. The distinction between runtime variables and template-time variables is really difficult and annoying.
5. I have read the description of what INTERMEDIATE does at least a dozen times and I still don't really get it. I'm pretty sure it's basically a hack on the fact the underlying model isn't rich enough to do what people want.
6. Sort of related to 2, but the only datatype being strings makes a lot of things harder than it needs to be.
7. Make really needs a debugger so I can step through the build, see the final expansions of templates and commands, etc. It's a great example of a place where printf debugging can be very difficult to make work, but it's your only choice.
That said, I'd sort of like "a fixed-up make" myself, but there's an effect I wish I had a name for where new techs that are merely improvements on an old one almost never succeed, as if they are overshadowed by the original. Make++ is probably impossible to get anybody to buy in to, so if you don't want make you pretty much have to make something substantially different just to get people to look at you at all.
Also, obviously, many of the preceding comments still apply to a lot of other build tools, too.
I'm a big fan of Make, but appreciated your detailed criticism, and found myself nodding in agreement.
#3: I like to stick MAKEFLAGS += --no-builtin-rules in my Makefiles, for this reason. This, of course, has the downside that I can't take advantage of any of the builtin rules.
#7: There is a 3rd-party GNU Make debugger called Remake https://bashdb.sourceforge.net/remake/ https://sourceforge.net/projects/bashdb/files/remake/ It comes recommended by Paul Smith, the maintainer of GNU Make.
It took many reads, but I think I get it.
As a toy example, consider the dependency chain:
foo <- foo.o <- foo.c
^^^^^
`- candidate to be considered "intermediate"
Depending on how we wrote the rules, foo.o may automatically be considered "intermediate". If we didn't write the rules in a way that foo.o is automatically considered intermediate, we may explicitly mark it as intermediate by writing: .INTERMEDIATE: foo.o
So, what does "foo.o" being "intermediate" mean? 2 things:1. Make will automatically delete "foo.o" after "foo" has been built.
2. "foo.o" doesn't need to exist for "foo" to be considered up-to-date. If "foo" exists and is newer than "foo.c", and "foo.o" doesn't exist, "foo" is considered up-to-date (if "foo" was built by make, then when it was built, "foo.o" must have existed at the time, and must have been up-to-date with foo.c at the time). This is mostly a hack so that property #1 does not break incremental builds.
These seem like useful properties if disk space is at a premium, but is something that I have never wanted Make to do. Rather than characterizing it as "a hack on the fact the underlying model isn't rich enough", I'd characterize it as "a space-saving hack from a time when drives were smaller".
----
If, like me, you don't like this, and want to disable it even on files that Make automatically decides are intermediate, you can write:
.SECONDARY:
Which tells it 2 things:a. never apply property #1; never automatically delete intermediate files
b. always apply property #2; always let us hop over missing elements in the dependency tree
I write .SECONDARY: for #a, and don't so much care for #b. But, because #1 never triggers, #b/#2 shoudn't ever come up in practice.
As soon as more JavaScript developers start writing very very simple Makefiles, tooling will improve and maybe someone will come up with a better make.
The other option is to let people keep using webpack and gulp until they come up with another JS-based build-system, webpack 5 or 6, and grulpt or whatever comes after grunt/gulp.
$< $> $* $^ ... Not particularly explicit. You also have the very useful substitution rules, like $(SRC:.c=.o) which are probably more arcane than they ought to be. You can make similar complaints about POSIX shell syntax but at least the shell has the excuse of being used interactively so it makes sense to save on the typing I suppose.
That's my major qualm with it however, the rest of the syntax is mostly straightforward in my opinion, at least for basic Makefiles.
manual: ('make' on a bsd is PMake)
https://www.freebsd.org/cgi/man.cgi?query=make&apropos=0&sek...
but most linux flavors have a package somewhere..
I use
$(patsubst %.c,%.o,$(SRC))
instead, which I find easier to remember.Not to push the particular product, but the approach:
FAKE (https://fake.build/), is an F# "Make" system that takes a fundamentally different tack to handling the complexity. Instead of having a new, restricted, purpose built language they've implemented a build DSL in F# scripts.
That yields build scripts that are strongly typed, just-in-time compiled, have full access to the .Net ecosystem and all your own code libraries, and are implemented in a first class functional language. That is to say: you can bring the full force of programming and abstraction to handle arbitrarily complex situations using the same competencies that one uses for coding.
As the build file grows in complexity and scope it can be refactored, and use functionality integrated into your infrastructure code, in the same way programs are refactored and improved to make them manageable. The result is something highly accessible, supportive, and aggressively positioned for modern build chains... If you can do it in .Net, you can do it in FAKE.
Make was created by Stuart Feldman at Bell Labs in 1976. The fact that it is still in use in any form and still being discussed here is a testament to what an amazingly good job he did at the time. Whether it is the right tool for any given modern use case is up to the people who decide to use it or pass it by. I still work with almost daily, and it's in wide use by backend system engineers if my experience is any guide. Yes, it's quite clunky but also quite powerful and reliable at the few things it does. Its also pretty much guaranteed to already be installed and working on every nix system, and that's not nothing.
You know what you have to do to build git? Type make. It's amazing.
Not necessarily.
> It’s also pretty much guaranteed to already be installed and working on every nix system, and that's not nothing.
First mover advantage.
The fact that no modern language, basically nothing outside of C/C++ uses it, says a lot. And even those are moving away, see Cmake & co.
That's how it has always been, though. In the '90s you didn't need to run Perl or Tcl through it because you weren't compiling anything. The Venn diagram of "Platforms that have make" and "Popular compiled languages" comes up with only asm/C/C++.
Many languages want to do things their way, such as Erlang, Common Lisp, Java, etc. Ruby and Python are interpreted and also don't need a build process. JS, until recently, was interpreted. Lots of NIH going around.
> And even those are moving away, see Cmake & co.
Cmake is closer to autoconf/automake/libtool. If you have serious cross-platform needs, then Cmake is a fine tool. But it's hardly less archaic than make (and only slightly less so than autoconf) and I'm dubious that too many people are really moving away rather than just picking up the newer, shiny tool for newer projects.
If I were doing a small static website or something that required a build with standard *ix tools, vanilla make would be my tool of choice, hands down. Tools like autoconf and, as the author pointed out, webpack provide a more specialized need.
True, one should not edit makefiles with notepad. Proper editors have support for editing them, though.
> Syntax which relies on bizarre punctuation...
Well documented, though.[1]
> a different dependency-based-programming build tool could replace it... but that's just wishful thinking
Use prolog[2], you should be able to write this in about 42 lines. But you'll end up with the same complaints ("the syntax, the magic variables!") because, in my experience, those are just superficial: The real problem, imho, is that declarative and rule based programming are simply not part of the curriculum, especially not for auto-didactic web developers. OTOH, it only takes an hour or two to grok it, when somebody "who knows" is around and explains. It really is dead simple.
[1] http://pubs.opengroup.org/onlinepubs/009695399/utilities/mak...
$@ is target, because @ looks sort of like a bullseye.
significant tabs are gross, yes, but this is a one-time edit to your vimrc then you can forget about it forever.
it’s also installed everywhere and has minimal dependencies. it could be a lot worse. (see also: m4, autoconf, sendmail.cf)
Incremental builds by looking for changed dependencies, a configuration file with its own significant identifiers (i.e. a build DSL shoehorned into JSON or YAML), generalized target rules, shelling out commands, sub-project builds, dry runs, dependencies for your own script, parallelization, and a unique tool with (making an generalization here) insufficient documentation.
If you're really unlucky, you'll even end up with the equivalent of a configure.sh to transpile one DSL and run environment into the DSL for your custom tool.
I'd argue that make is a half-baked and buggy implementation of make - so that's not really a drawback so much as the status quo.
E.g. I have scripts that exist mainly to carefully select the "correct" version of make for a given project to deal with path normalization and bintools selection issues on windows - and none of these ~3 versions of make work on all our Makefiles. One of those versions appears to have some kind of IO bug - it'll invoke commands with random characters missing for sufficiently large Makefiles, which I've already gone over with a hex editor to make sure there wasn't some weird control characters or invisible whitespace that were to blame. So, buggy and brittle.
EDIT: Oh, and parallel execution, but smart parallel execution. Independent tasks can be allowed to run simultaneously. Very useful when you have a lot of IO bound tasks. Like in compilation, or if you set it up to retrieve or transmit data over a network.
It's not too hard to do that in your custom script, but more care is required because until you custom script reaches make level internal complexity you will have to manually track dependencies or make sub-optimal assumptions.
If for some reason the project is ruby allergic, I'll try to use Invoke [0].
Sometimes I feel like people's usage of Make in web projects is akin to someone taking an axe and hand saw out to break down a tree for firewood when there are multiple perfectly functioning chainsaws in the garage.
And no, I don't respect the community. The community very often makes "The Way Things Are Done" its personal religion and refuses to ever change anything for the better.
Make, in contrast, works within a shell and invokes programs which may be either wildly or subtly incompatible across platforms. Add in the lacking support for conditionals in POSIX make and portability is a nightmare.
If you are invoking your functions (or functions imported from random library), I have to check them for what they do.
(I am aware that this advice does not hold true in all cases; just that for many of them overly complicated build systems is a code smell.)
Make on the other hand is completely language agnostic, you can use it to compile C, Java, LaTeX, or make your taxes. Make is a bit like shell scripts, it's great when you have a small project and you just want to build a few C files for instance[1] but when it starts growing there always comes a point where it becomes hell.
[1] And even then if you want to do it right you need compiler support to figure header dependencies out, like GCC's various -M flags.
Plus, to me C syntax is particularly good. You're writing real words and the computers does the things you tell it to. To the letter.
C became popular because Unix became popular, and when designing a language that intends to become popular one aims for a ratio of 10% novelty and 90% familiarity. Like, ever wonder why Javascript's date format numbers the months starting from zero and the days starting from one? It's because Eich was told to make JS as much like Java as he could, and java.util.Date numbers the months from zero and the days from one, which Java itself got from C's time.h. (Not coincidentally, Java and JS are both in the C-like language family.)
> You're writing real words
In C? Not compared to ALGOL, COBOL, Pascal, and Ada, you're not. :)
> the computers does the things you tell it to. To the letter.
As long as you're not using a modern compiler, whose optimizations will gleefully translate your code into whatever operations it pleases. And even if one were to bypass C and write assembly code manually, that still doesn't give you complete control over modern CPUs, who are free to do all sorts of opaque silliness in the background for the sake of performance.
Network effect. If you wanted to write for unix, you almost had to use C originally.
> Plus, to me C syntax is particularly good.
It's meh. It's not the most straightforward when defining complex types like arrays or function pointers.
> You're writing real words and the computers does the things you tell it to. To the letter.
So yeah, about that...
To cut to the chase:
* syntax = structure
* semantics = meaning
C syntax would include things like curly braces and semicolons, whereas C semantics would include things like the functionality of the reserved keywords.
This SO answer gives a more detailed explanation:
The "pat" in "patsubst" means pattern, not path, so adding an h would be incorrect. (https://www.gnu.org/software/make/manual/html_node/Text-Func...)
Make one that's generic and provides significant enough improvements that people care.
.RECIPEPREFIX option has been available for 7 years. Stop whining about non-issues.
One target to build (prod), another to run with fsevents doing auto-rebuild when a file is saved (instant gratification during dev), then a few targets for cleaup & housekeeping. All said, the file is 1/4 the size of any other JS-based build systems.
If your makefile fixes up a file using sed and your system has gnu sed, your makefile may fail on a system with BSD sed (e.g., a mac). If you rely on bash-isms, your makefile may not work on a debian system where it will be run with dash instead of bash. And so on.
If you look in your package.json, you'll surely see a dozen or so "scripts" lines that run through the same shell that make does and have all the problems you just mentioned.
I'd also like to point out that Linux and almost certainly your production environment (because it's most likely *ix) will be case sensitive. Your macOS or Windows file system? Not so much. Point is, you're already up to your neck in portability issues. My macOS coworkers often forget this detail.
Also, I use javascript where it makes sense: I rely on Browserify to resolve the web of `require`d files.
task: dependency
list of
commands
That's about as easy as it gets.FWIW, Webpack 4 (which just went final a few days ago) now has "zero-config" defaults out of the box. You may want to give that a shot.
* Parallel execution of build rules comes for free in a lot of implementations. This is really noticeable when you do heavy asset pre-processing.
* Cleanly written build rules are re-usable across projects as long as those projects have the same structure (directory layout).
* Cleanly written build rules provide incremental compilation/assembly for free: You express intermediate steps as targets and those are "cached". I put the "cached" in quotes here, because you essentially define a target file which is regenerated when it's dependencies are updated. Additional benefit: Inspection of intermediate results is easy - they are sitting there as files right in your build's output tree.
One realistic example (from the original Shake paper), is building a .tar file from the list of files contained in a file. Using Shake we can write the Action:
contents <- readFileLines "list.txt"
need contents
cmd "tar -cf" [out] contents
There are at least two aspects I'm aware of that increase the power of Make:- Using `$(shell cat list.txt)` I can splice the contents of list.txt into the Makefile, reading the contents of list.txt before the dependencies are parsed.
- Using `-include file.d` I can include additional rules that are themselves produced by the build system.
It seems every "applicative" build system contains some mechanism for extending its power. I believe some are strictly less powerful than monadic systems, while others may turn out to be an encoding of monadic rules. However, I think that an explicitly monadic definition provides a clearer foundation.
http://neilmitchell.blogspot.com/2014/07/applicative-vs-mona...
https://git.savannah.gnu.org/cgit/easejs.git/tree/Makefile.a... https://git.savannah.gnu.org/cgit/easejs.git/tree/configure....
The nice thing with using Automake is that it gives all the standard build targets with little additional effort (for example, `make dist` for producing the distribution tarball, and `make distcheck` for verifying that it's good).
I use a much simpler one for a project at work:
https://gitlab.com/lovullo/liza/blob/master/Makefile.am https://gitlab.com/lovullo/liza/blob/master/configure.ac
make -f- << sed 's/MAKE_TARGET/\$@/' Makefile.descriptive
But for the sake of compatibility I wouldn't.Use on the Makefile:
MAKE_TARGET := /some/path
And invoke $ make MAKE_TARGET=/other/pathAlso you probably meant ?= instead of := as the command you gave would still use /some/path instead of /other/path if you use :=.
1. The assumptions that it makes. Everything in and out are files (phony files notwithstanding). It is hard and painful if you want outputs to rely on and rebuild from configuration in the makefile itself. It's not impossible to implement, but it's difficult to precisely implement it: often times I've seen systems that just rebuild everything after configuration changes.
2. The mix of declarative and imperative styles, while useful for quickly throwing a build together, gets difficult to deal with as things scale up. The make language itself is pretty restricted, too (without using $(eval)).
I know that recent versions support guile extensions and even C(/C++?) extensions, but at that point it's not giving you all that much. I have often wished the make functionality was exposed in some "libmake" for me to extend.
For this reason (and others), I've recently refactored a huge build system to use shake[1] instead. Now builds are precise and correct, and properly depend on configuration and build environment.
This doesn't appear to be true, at least on GNU Make 3.81 (MacOS). Rather, the first target listed is the one that gets built on `make` with no arguments.
- https://github.com/rcarmo/azure-docker-swarm-cluster/blob/ma...
- https://github.com/rcarmo/azure-acme-foundation/blob/master/...
one tip -- remember to use .PHONY on your targets -- https://www.gnu.org/software/make/manual/html_node/Phony-Tar...
CMake does a great job of compiling executable code and linking it using your compiler of choice, but where it falls flat is in giving me a convenient mechanism for fitting that executable into the system image.
What exactly are you looking for with regards to deployment, and what tool do use, instead of CMake, to give you that ability?
The point I'm trying to make is that every build system except Make solves a really specific class of build problem(building applications, or building Linux systems using GCC) and then pretentiously claims to support everything that matters. What you're actually getting is a small sliver of what you might need in my world.
This article is another example of how Make can do something really unexpected by providing really simple features and letting you decide what matters to you. I've yet to see another build system that is as generally useful.
I'm of an opinion that Makefiles with Docker are mostly a fancy way to write `case "$1" in build) ... run) ... esac` except that one needs to have make(1) installed to run them (in my experience, Bourne shell is much more commonly available than any make variant)
I started doing makefiles as a reaction to having a bunch of bash scripts cluttering up my directory. More or less just a collection of useful commands. But as I continue to do this, I find it encourages making consistent build routines/versioning. Also, being able to set up dependencies ( like "run relies on build") can also be helpful. Finally, variable substitution is useful for versioning.
I think what makes makefiles great are you can do as much or as little as you want with them. I put an example of a common one in another reply.
SHELL=/bin/bash
VERSION=1.0
build: clean
docker build -t webservice:$(VERSION).$$(date +"%y-%m-%d") .
shell:
docker run -it -v $$(pwd)/src:/opt/app \
-p 8888:8888 \
--name webservice \
-e SECRET1=$$SECRET1 \
-e SECRET2=$$SECRET2 \
webservice:$(VERSION).$$(date +"%y-%m-%d") \
/bin/bash
run:
docker run -it \
-p 8888:8888 \
--name webservice \
-e SECRET1=$$SECRET1 \
-e SECRET2=$$SECRET2 \
webservice:$(VERSION).$$(date +"%y-%m-%d")
tag:
docker tag webservice:$(VERSION).$$(date +"%y-%m-%d") webservice:latest
push: tag
docker push webservice:$(VERSION).$$(date +"%y-%m-%d")
docker push webservice:latest
clean:
-docker kill webservice
-docker rm --force webservice
test:
docker run -it \
--name webservice \
webservice:$(VERSION).$$(date +"%y-%m-%d") \
python3 tests.py
Couple of notes:- I do major.minor.yy-mm-dd versioning, so this handles that automatically
- shell is for dropping you into a prompt with your present directory mapped to the working directory. I find this super helpful for debugging and testing containers
- run is for testing your CMD and entrypoint scripts
Thanks for sharing! I'll be using this as inspiration in the future.
Another member of the team subsequently taught make about the reverse dependencies so that you can split jobs across Travis nodes by the top-ish level image they depend on.
My favorite addition was the ability to generate a dependency graph using graphviz straight from the Dockerfiles.
N.B. Project is now moribund, the team was disbanded. May not build at all. Don't know if any of the forks are active.
I regret not considering Make for Docker administration months/years ago. I've taken to using bash scripts or, worse, tagged bash comments to recall commonly used complex commands.
https://github.com/nzoschke/gofaas/blob/master/Makefile
It started very verbose, one target for every Go program, until I figured out target patterns.
I’m also enjoying the -j flag to do things in parallel.
Now it’s 3 lines of Make to build 10 go programs in parallel in seconds.
Parallel is also enough job control to run the development server and to watchexec rebuilding all go programs on code change.
What is the best approach to make sure your Makefile will work as expected on every platform, meaning, windows included?
For example: Can we write file paths using forward-slashes, or is it still an issue? I imagine running it via git bash (bash provided by the git installer on windows).
Some systems might not support the `-p` flag in `mkdir -p`, or the `-rf` flags in `rm -rf`. There are scripts distributed on NPM called `mkdirp` and `rimraf` to work around such issues.
There's no Lost Art, no superpowers. It's just the JS scene _finally_ slowly getting up to speed.
People learn things every day, acting like it's "slow" or you are better than them is a very toxic elitist point of view that is better off not said. You think your way of doing things is better, then you should encourage others to do it that way, don't put them down when they just start using it! I'm glad the ALGOL programmers of the 60's and 70's didn't spend their time laughing and putting down that new-fangled C language when it inevitably made some of the same mistakes.
And besides, very rarely is something in life a complete upgrade. Make is nice, but it has some very real pain-points (as evidenced by the handful of utilities that are makefile-generators, because getting make to do certain things or work on all platforms is so difficult). Gulp is great too, but it also has some very big issues in some areas. There is no universal "right" answer to any of this, and assuming that everyone that's not doing it your way just doesn't know any better isn't just naive, it's also wrong.
You must've misunderstood me. I don't have any problem at all with people discovering things late. I am often a slow learner myself. What irritates me is when people make spectacular "discoveries" with tacky headlines. If the article were written in a bit more casual and technical tone, I'd be happy, much more so than with this "let me re-introduce you this amazing but forgotten lore you don't know about" nonsense...
> And besides, very rarely is something in life a complete upgrade. Make is nice, but it has some very real pain-points
I agree, but that's all the more reason to not make such "discoveries" ...
It had this and only this syntax:
If a line begins at character 0, that is a list of files whose timestamps should be checked, if any are 'old', then execute the series of command lines beneath, identifying them as starting with a tab character.
That was it, the entire syntax of "make", and it was complete. Then some smart people fucked it up and we have that piece of shit we call "make" now.
This does seem to happen in our industry over and over, another example is the way we turn every small, well designed language into Java.
Then some other people fucked it up...
I still remember how Java was hailed as a perfect replacement for C++ as the language of choice when writing applications - it truly was.
Today though I was faced with a class name that was 46 characters long. There's a reason why "Fizz Buzz Enterprise Edition" is written in Java.
not sure which make you are criticizing here..
BSD Make (aka PMake) is imho light years beyond GnuMake in terms of coherence of the extensions to the base language and usability..
unfortunately most people in linux land think make==gnumake, and so it is hardly used outside of the base build system of BSD systems..
The list of files would be the declared dependencies.
It's some weeks of my life I will never get back.
I hate make and it's 'entire syntax'.
all: data.loaded data2.loaded
data.csv: wget 'some url'
%.sql: %.csv sed/awk/custom-python-script $< > $@
%.loaded: %.sql psql < $< && touch $@
I once built a World of Warcraft addon downloader & manager (including patching) with Make. My friends thought I was insane. Personally, I wondered how else you'd do it :-)
(actually, looking back at it, I apparently built it on plan9's mk (https://9fans.github.io/plan9port/man/man1/mk.html), but it's close enough)
If part of the data changes, or new data arrives, you just run make again, and the whole pipeline re-does whatever is new. Very slick.
It has free concurrency (make -j N). I've used this effectively several times for medium-scale (~GBs) science data processing.
It works surprisingly well, but it is definitely not the right tool for the job. After a number of years of adding/removing/changing jobs, maintaining it is turning into a nightmare.
When someone says "there are numerous alternatives", I just look out the window and smile.
Almost all issues mentioned itt can be solved in barely-compatible, but easily transition-able way. If this tool exists, its name is welcome.
It is a lot easier and more manageable if your Jenkins job is set up to just run a series of generic Make commands for a project, where any specific steps for a particular project are defined in the Makefile.
This way I do not need to know or care about how any particular project or site is built when configuring the job to deploy it. The Makefile takes care of all that.
CMake is a nightmare of implied complexity. I've run into multiple situations (usually but not always involving cross-compilation) where it was simply incomprehensible. No amount of time invested would let me figure out why CMake blew up. There is no way I can build something a little out of the ordinary on top of CMake and be confident that it's solid. It's just too complicated.
This sort of balancing, upfront ease of use vs hidden complexity comes up often and I have been burned enough in the past that experience dictates to pretty much always go for model simplicity and pay the upfront costs. I don't see this often however. A lot of the time people will go for what feels or looks right, superficially, without bothering to look beneath the surface or think about model complexity costs. It's an attitude that has led to many disasters in this space.
I'd much rather a Makefile where I can override the compiler and flags for my configuration. I can easily figure out what I need to do based on compiler and linker errors for missing headers and libraries.
What would you gain if you're using it for other, less supported languages, like Javascript?
I've only ever used it for C++, but I don't see why it (or something like it) couldn't be used for other languages too.
This tutorial was making the rounds (on HN and elsewhere) last week, but in case anyone missed it, here's a link: https://github.com/pyk/cmake-tutorial I found it to be a great primer/refresher.
https://en.wikipedia.org/wiki/Punched_card#IBM_80-column_pun...
If the concepts behind Make are repackaged in a more hipster way, the resulting tool may get far more appeal. Shake https://shakebuild.com is a build system library that can naturally express even the dependencies that require Makefiles go meta and generate new Makefiles. It can be a robust backend for any build system DSL.
But you can't be serious when you say that make is out-of-touch with the average frontend developer, and then link to this:
You are right that Shake and most general purpose build tools are out-of-touch with frontend. Even for people who are comfortable with these tools, they are not necessarily better than Webpack for the typical frontend tasks. Shake place in this hypothetical tool will be at its backend - like LLVM is the backend of many compilers.
https://github.com/shmup/react-makefile/blob/master/Makefile
I opted out of checking for modification dates on node_modules and yarn.lock, because that seems like exactly what Yarn itself is for. I let it manage itself.
I also let Webpack do the heavy lifting.
So in short: I don't really need the Makefile at all and could just add the clean and dev-server commands to a script block in package.json
I still like it though
After you've tried to naïvely read a Makefile for a medium-sized C project you'll never want to write a Makefile yourself.
That said, there's no tool that works so simply and so beautifully as make when you have a lot of build steps that generate lots of intermediate files with complex dependency links between them. That situation may happen everywhere, even if you're generating a bunch of PDFs or rendering HTML templates or making images from .dot files or whatever. Use make.
Or is there an alternative tool for these situations also?
I am sure Make still has it's uses, but I am sure glad package managers of have made makefiles irrelevant to me.
I'd argue it's entirely fair with other examples (e.g. compare and contrast to, say, Rust's cargo build.) You can write a Makefile which fetches and builds your dependencies. Makefiles might be like a recipe - but these days we're building recipes for building entire OS images, including grabbing the dependencies in the first place. A single software project by comparison is trivial.
The problem is Makefiles are a jack of all trades, and master of none. You have to write or copy a distressing number of rules yourself, per project, per subproject, and know the underlying build chain in relative depth to do even some rather basic things. I'd argue makefile authors not automating their dependency fetching is a symptom of this.
As a programmer, I avoid them, because I'll end up stuck maintaining them and end up lowering the build system's bus factor. With perhaps the exception of a couple of simple rules that forward to a "proper" build system, because I've written enough of them in the past that "make" still feels like the right default action that should generally work. But if it all possible, never for the meat of the build system.
Good makefiles are simple, and should Just Work™. The dependencies should (theoretically) be listed in the project documentation, though it often isn't.
Package managers are definitely better for non-developers, though. Build systems generally aren't geared toward regular users, so users will find them confusing.
`npx babel`
This will use any installed version of babel in your node_modules, or, if not installed, will temporarily install it for the duration of the command.
Wait... doesn't that make typosquatting even more of a danger? People get used to typing "npx <command>", they mistype the command once in a while, an attacker uploads packages named after the most likely typos, and done - the attacker wins.
I question the wisdom of that shortcut.
This makefile snippet:
lib/index.js: src/index.js
mkdir -p $(dir $@)
babel $< --out-file $@ --source-maps
The source and the output are both called index.js? Why, God, WHY???The capitalized rending-of-garments is silly. This is transpiling; file names should remain the same from input to output.
It is not realistic/optimal to try to write your javascript to be supported by all browsers or runtime clients. It's better to write using the latest spec, and just have it dumbed down for you by transpilers at build time
Some more explanation here: https://www.excella.com/insights/typescript-vs-es6-vs-es2015
Of course that sentence contains it's own level of craziness, but the problem does not lie in the build step.
Previous discussion on this: https://news.ycombinator.com/item?id=15154994
In this article's case, there a reasonable exception to made when JavaScript is being "compiled" into JavaScript itself. For that, I don't know what the term is (or if there even is one).
How and why? And what do you do of compilers which can do either based simply on the backend you select?
Babel is a JavaScript compiler.
Compiler: collects sources from various places, assembles the result into machine code.
Transpiler: collects sources from various places, converts the source to another language.
Personally I cringe at the newspeak you seem to imply we should all be applying to our lives. These words mean things - quite different things, it turns out. There's a difference between human-readable source code language, and machine-executable binary code. These tools function in different ways entirely; your optimization to the language is not only un-warranted, but leads to a desultory effect: programmers get stupider when they don't know what their tools are actually doing.
gcc compiling some C and Babel compiling some JavaScript are both performing the same role. So much so the similarities in how they carry it out are striking.
^ no match.
transpiler is the newspeak.
lib/%: src/%
mkdir -p $(dir $@)
babel $< --out-file $@ --source-maps
Just look at all those magic things. The percent signs! $<! $@!Well, I know they are not magic ;). But why would I want them when I can actually use normal names like "deps"/"entries" and "target"?
It gets substantially worse as we go down the rabbit whole. Where webpack can easily walk the entire dependency tree by itself, we have to invoke
src_files := $(shell find src/ -name '*.js')
Where we can use same webpack to seamlessly output the resulting file into an output directory, we need to do the (very unintuitive) pattern substitution: transpiled_files := $(patsubst src/%,lib/%,$(src_files))
or even flow_files := $(patsubst %.js,%.js.flow,$(transpiled_files))
And when we want to watch for changes? Well, we need an external program anyway. $ yarn global add watch
$ watch make src/
The art of Makefiles is often lost for good reasons: because Makefile don't really cut it anymore.