Hacker Newsnew | past | comments | ask | show | jobs | submitlogin
A Simple Makefile for Medium-Sized C/C++ Projects (atomicobject.com)
125 points by ingve on Aug 26, 2016 | hide | past | favorite | 115 comments


I do like simple makefiles. I adopted a C++-project without having much C++-knowledge, and simplifying the build setup was a big step in making it mine.

Thinking that it might be nice, I tried this one instead of my own approach at a simple makefile (https://github.com/onli/simdock/blob/master/Makefile) and I get this error:

> Makefile:16: * missing separator. Stop.

Which raises kind of an important point: Simple means understanding what is happening. I have no idea why `$(CC) $(OBJS) -o $@ $(LDFLAGS)` does raise that error - and just like that it is not simple for me anymore.

Edit: Now with the tab-error out of the way, it of course does not find the dependencies. That might be caused by a strange project organization (like said, it is an adopted project) or maybe the makefile is just not complete – I'm not sure how far the auto-generation is supposed to go. Played a bit with it, but that just removes the simplicity. For now, I think that the pkg-config way is nice and explicit, and would recommend to take a look at the linked makefile if a simple one is searched. Though I still would be happy about optimizations for that file!

I of course just might miss the point. Well, after all that supports mine: Not that simple after all.


The tabs in the makefile have been converted to spaces by the blog-posting software.

The commands for implementing rules are always indented by a tab. This is a classic makefile newbie make gotcha & everyone gets bitten by it at some point or other.


I recently tried to improve this error, but it looks like the maintainers aren't interested: https://savannah.gnu.org/bugs/index.php?48276


Make unfortunately depends on hard-tabs for the recipes. Your editor probably switched it to spaces.


If you cut & paste the makefile in the blogpost, you’ll discover that the blog itself has converted the tabs in the makefile to spaces. Not very helpful to any confused newbies out there!


Yep, that's exactly what happened. Thanks for the explications.

I actually did know that once, but forgot. Make definitely has its hidden traps.


I’ve made a comment to that effect on the original blog post. Hopefully they’ll be able to fix it.


Now fixed!


Your editor probably automatically converted tabs into spaces. And just like that, the Makefile stops working.

Which proves the point: Makefiles are at a point in life where they should be exclusively automatically generated.

The only exception are throwaway projects where you and only you use it.


I strongly disagree. The tab requirement for Make is very well known and less strange than custom Makefile generation. Simple Makefiles work extremely well in lots of projects.


Somebody listed a number of common build system requirements below, but of course there are usually many many more like custom compilers, cross-compilation, any number of things that your "simple makefile" can't work with.

By the time the Makefile has grown to support all these things it no longer has the properties that made it useful in the first place (parsimonious, understandable, small).


Supporting custom compilers and cross‐compilation is as simple as using ${CC} in the Makefile instead of gcc…


This is how we got into this mess in the first place. "Surely it can't be very difficult, let me just add this.."


Perhaps, but I suspect writing a portable Makefile is not as difficult as you think.

Speaking as a packager, I have to patch many CMake build systems for portability issues. And don’t even get me started on SCons. Autotools is better in that regard, but has many flaws of its own.

Here’s an example of a portable Makefile I’ve contributed to a project which handles the common packaging issues (staging directories, cross‐compilation, parallel builds): https://github.com/sinamas/gambatte/pull/6/commits/8d6fffc2b...

Compared to the original build system (two SConstructs): https://github.com/sinamas/gambatte/blob/master/gambatte_sdl... https://github.com/sinamas/gambatte/blob/master/libgambatte/...


I mean, if you're going to be generating it anyways, just generate ninja files. They are faster, but the downside is just that you need to generate them, as opposed to being generic.


Sadly you're bitten by the biggest misfeature in Make - You must use TABs not spaces.


It always annoys me that we don't have better solutions for building C and C++ projects by now. My own take on the generic drop-in makefile[1] has over 1000 stars on github, which says pretty loudly to me that this is a common pain point for many people. Alas I haven't had a brilliant idea for a solution (yet).

[1]: https://github.com/mbcrawfo/GenericMakefile


True. Other languages have it way easier because:

- proper module system, none of this header file nonsense

- no compiler flags to worry about

- no preprocessor definitions to worry about

- as a consequence of the above, no dependencies that require their own linker flags, include directories and preprocessor definitions to work

- no need to support different compilers/platforms with different configurations (which work differently with libraries that require different linker flags and include directories and...)


Out of curiosity, how do other languages cope with:

a. Targeting different release types (debug vs release)

b. Changing behaviour of the compiler

c. Having different code paths between debug and release (#ifdef _DEBUG)

d. Having macros and helpful macro expansion

I do not understand the dislike of header files: how would you recommend distributing DLLs and libs while exposing their functions to be developed against?

I suppose you could implement everything in a header file if you wanted a simple "module" system (thinking of the header as a module), but this would establish a very simple system with no interdependencies or reusability.


> I do not understand the dislike of header files: how would you recommend distributing DLLs and libs while exposing their functions to be developed against?

Do you enjoy writing out all your function signatures twice? Haven't you ever wished the compiler could automatically build the header file for you?


Examples from Java:

a. debug/release doesn't matter much so all Java code is in "debug" mode (http://stackoverflow.com/questions/8613535/does-java-have-de... ); the actual debugging is handled via source code/source code jars (jars are just zip files with a standard structure) -> Maven: https://maven.apache.org/plugins/maven-source-plugin/jar-moj...

b. configuration files -> Maven: https://maven.apache.org/plugins/maven-compiler-plugin/compi...

c. don't do that? :)

d. no macros and most people seem to agree that's a good thing; C macros are not really the kind of tool you want to be using, unless forced to; code generation is used instead

Regarding header files: Javadoc for humans, code inspection on the class files for IDEs. No need for separate files in a system designed after the 70s :)


In Java with maven as a package manager:

a. Different build goals with different settings in a build file.

b. Build steps can be changed in the build file but there are a lot of sane defaults.

c. Plain constants and if statements. The compiler optimizes away dead code.

d. Code gen is done as another step before the build and not really a task of the compiler.


We do. We have decent tooling (Visual Studio, Xcode) which let us set options and let us run compilers from the commandline using the options in the project and solution files (nmake, msbuild, xcodebuild) to save us having to use a hand-written Makefile.

If you're still writing small projects and insisting on running a build in a command window, or writing code in a simple editor then I can perhaps understand the complaint. But you quickly grow out of that when you start writing larger projects.

I couldn't imagine the 2+ million line codebase at work using hand-written makefiles. It builds for debug, release, MBCS and Unicode, and with different versions from source control every night, so multiple versions for different branches are built.


Usually if you're distributing a Makefile with a project, it's for portability. Insisting that your Linux users of a C/C++ library install Visual Studio or Xcode is a complete non-starter. It's different at a company - every company I've been at had its own proprietary wrapper around make - but then you need to be able to integrate all the open source libs you're depending on.

At least there's autotools, which has its own set of problems, but you can be reasonably confident it'll generate a portable Makefile.


The solution is good tooling, Visual Studio makes this a non-issue with sane defaults that can be extended if needed.

I haven't tried CLion yet but I would assume it's decent given JetBrain's track record.


I'm a fan of qmake for my projects, even the ones that don't use Qt.

Are there any other make systems that have Project Mode like qmake does?


Please don't brew your own build system. It likely won't handle at least one of the following situations:

* installing into a DESTDIR

* setting custom CFLAGS/LDFLAGS

* changing prefix, libdir, sysconfdir

* cross-compilation

* parallel builds

* conditional features

* missing requirements

and will make packaging your software in Linux distros much harder than it needs to be.


> installing into a DESTDIR

> setting custom CFLAGS/LDFLAGS

> changing prefix, libdir, sysconfdir

> cross-compilation

Pretty much all of this can be done trivially in a makefile, using a very innovative construct called "variables".

> parallel builds

make -j. Works especially well if you have working dependencies. This makefile does.

> conditional features

make has ifdef... Granted this makefile uses find to discover source files, which won't totally work. But it's a small fix. Or you can do conditional features through CFLAGS.


Can be done. The question is will the half-assed Makefile that a developer wrote for themselves do any of those things? And I can tell you from bitter experience that is very very unlikely. Plus I will still have to spend the time to understand the Makefile, which I won't need to do if they'd used an established build system in the first place.


We can agree that half assed makefiles are bad. I don't believe in throwing out the tool on that basis. If that were the bar, we might as well not write any software in any language with any tool.


I imagine most projects will never leave the hard drive they're first written to. Writing a basic "half-assed" makefile that works for the initial author is fine. If a project turns out useful, rewrite the makefile as people need the compatibility. There's little point in wasting the time when you're just starting out.


There’s no point telling people not to do something if you don’t give them an alternative that does do all those things.

(And no, “just use autotools” is not a viable answer.)


Autotools does do all of those things. I'm not a particular fan of it either, but when I'm packaging software for a Linux distro then I'd rather see autotools or cmake being used instead of some crappy custom build system that will require patching to make it work.

There is room for someone to come up with a good build system. It had better do everything autotools and cmake do, and be well documented and widely used, so there's a rather large barrier to entry.


Whenever "configure" is looking for a fortran compiler in a clearly non-fortran program, one has to wonder why no one fixes the configure scripts. From all I learned it is, because the underlying scripts are close to unmaintainable. So from that perspective, a better build tooling should be a high priority.


No one fixes the configure scripts because they're thousands of lines of code in about three different archaic languages that nobody knows or cares about.


Fix configure.ac, not configure which is effectively a binary.


Auto tools is great. To compile all you do is

    ./configure
      make
      make install
It couldn't be simpler. Even though I am part of the GitHub generation, I don't see why so many of my peers hate it. I actually like it.


This is kinda hilarious because, yes, "Autotools is great" [as a consumer]. But autotools and m4 is kinda awful to write IMO.


... and autotools is actively hate-inducing if you have to debug it.


autotools is pretty great for the end-user who wants to compile your code or the distributor who wants to package it up for Debian or whatever.

It’s a crawling horror for the programmer that wants to use autotools as the build solution for their code however, which is the topic under discussion.


This only works in the Unix world, on Mac only for command line tools (but only after installing autoconf and make), and not at all on Windows unless you install a complete Unix environment (cygwin or mingw).


Why would it only work with command line programs on the Mac? That's definitely not true, unless something changed very recently.

Also, you don't need autotools itself to build a autotoolized program. The generated configure script is portable sh.


Not really. the end user doesn't need autotools installed actually. To build the configure script from the "configure.in" file, you need autotools. Go ahead and try!


That's the ideal situation, yes, but god help you when something goes wrong.


It's not that hard. I recently had to debug a memcached configure.in script. But, I consider myself a C programmer, so that fact might be source of bias.


>And no, “just use autotools” is not a viable answer.

After packaging hundreds of programs for a GNU/Linux distro and dealing with handwritten Makefiles and other so-called autotools replacements: Yes, it is.

If you want someone to be able to build and install your software, just use the Autotools. Yup, they are ugly, but they're still the only set of tools that works acceptably.


Not only that, but every time I'm up against a problem building software that uses something else, it's like, "oh great now I have to figure out what their secret badly-documented variable names are and how to configure their XML/JSON/YAML/whatever"

It's a pleasure when autotools is used because in generally I know it's going to be nothing much more than

> --with-stuff/--without-stuff

and

> --prefix=$HOME/.local

and when it's not, at least I know I can just grep the shell script and figure it out pretty quickly without having to learn a whole new programming language.


I'm surprised no one says, "Just use waf."

It does all those things. It's fast. It's cross-platform. The language is Python.

It's a bit steep to learn and maybe doesn't have the most mature built-in recipes for certain complex builds like mobile or desktop frameworks, but it seems like those things would be addressed quickly if more people were using it. The maintainer is a class act, consistently putting out new features and bug fixes and working with pull requests.


WAF is not so much a build system, but a bunch of python scripts to create your own build system, it's in the same zoo as cmake, scons or premake, but (in my opion) worse than any of those. I might have had a bad first impression though because of the project I had to use WAF in had terribly complex WAF scripts.


True, they do call it a build system toolkit. But I think worse than the others is on the wrong track and the sentiment is too heavily influenced by your negative first impression.

swtoolkit made scons really nice to work with (but couldn't fix the speed issues). It's a shame it was abandoned. In the same way, a thin layer of functions as demonstrated in the waf "toolkit" examples could lean on the power of waf and provide a build system every bit as snazzy as big, polished systems like bazel and buck, but it would work cross-platform out of the box and be extensible with only Python. But I think waf is close enough to being high level that no one bothers to create and polish a build system layer (maybe internally in companies, but the open source uses I've seen use pretty standard waf scripts).


Ehm ehm... every second reply around here is shouting "cmake"...


CMake is icky. I’ve never been comfortable with it. I’ll take hand written makefiles over CMake, unless I have to have Windows as a build target.


The LibreOffice build system (which is plain GNU Make, without generated unreadable intermediate build files) does all these and more. Doing the same with autotools would be a nightmare.


We do however have some serious gnu make gurus to make that tractable.


... as we would have needed for CMake or Autotools. It might have worked with the former for something as complex as LibreOffice, but GNU make being so simple/basic and stable over the years was a big plus: Im sure even with CMake LibreOffice would have had fun with lots of small cornercase regressions.


Just use cmake or scons. In 2016, you should not hand write a single makefile !

A simple cmake/sconcs build file will be as simple as the one shown (or even simpler !) and it will definitively will be easier to integrate external dependencies if you have to.


Please do not use scons in your projects. Poorly written SConstructs are as common as poorly written Makefiles, and are more difficult to patch to work correctly because of all the magic that goes on under the hood.

SCons has many issues that negatively affect packagers. Some are listed on the Gentoo wiki here: https://wiki.gentoo.org/wiki/SCons

As an OpenBSD packager, I hit many of those issues and a few more. For example, scons’s C preprocessor support is not great, and often brings in header dependencies that should be #ifdef'd out; when those unnecessary headers are removed during build (happens often on a machine bulk building packages), the build breaks.

I’ve replaced SConstructs with Makefiles before. Typically it makes the build faster, simpler, and more portable, and is popular with other OS packagers. Example: https://github.com/sinamas/gambatte/pull/6


Honestly cmake is such a shitty language though. Scons is slow.

CMake is almost great, but I have no idea why they went ahead and designed the _language_ the way they did. (Lists are concatenated strings, everything is basically macro expanded, etc..) I mean, if they were trying to improve on m4 and shell script, I wouldn't exactly say they succeeded.

I realize that CMake has better cross-platform support, but I can't stand using it. For small projects it's fine, but for medium- to large-sized projects it's a bigger headache than autotools imho.

(That said, I explicitly don't care about Windows support, which is the only place where it really matters to have something different than autotools, and even that is not so bad with MingW and will be even better, it seems, with the coming "linux-compatible subsystem")


cmake generally works great, but the larger and more complex the project gets (and the more platforms you need to support), it gets exponentially uglier. I've had the displeasure of needing to maintain a single cmake infrastructure for a large (~500K or so lines plus 3rd party libs) project that targeted Windows, Linux, Mac, iOS, Android, and a few other obscure mobile platforms. Here was the progression:

1. Linux target: "Wow this is a breeze!"

2. Mac target: "Ahh, a little learning curve, but no problem!"

3. Windows/VC target: "Jeez, I hope I don't have to hand-edit this monstrous vcproj this thing generates..."

4. iOS: "WTF!! Simulator targets, device targets, provisioning profiles, code signing.. This stuff was all just hacked into cmake and barely made to work wasn't it??"

5. Android+NDK: "Kill me now"

6. Other obscure mobile targets with toolchains themselves that barely worked: "La La La, I'm ready for the funny farm. The cucumbers! They're dancing! And glowing!! The room is getting hazy and dark.. Where's my vodka?"


Would you write an article detailing your experience?

It sounds fun to read.


Honestly, I don't think I could afford the therapy that I would need to re-live that part of my career in the detail that it would take to do a proper write-up. To this day, I still have no idea how an Android project actually winds up getting built, and their build system philosophy seems to boil down to "keep adding layers of magic, each making lower levels of magic harder to hand-edit and understand".

Most of the difficulty to be honest was with: 1. third party libraries, and 2 the Android NDK.

For third party libraries, every one was slightly different in some frustrating way. Some of them used bog-standard makefiles (thank you, third party developers who do this!), so they basically just worked. Some of them used autoconf/automake, which makes me sad but you can generally get cmake to mate successfully with them. Some of them helpfully had their own CMakeLists.txt, which would build the library, but invariably could never be successfully integrated into a larger project. Some non-open-source libraries came with just one platform's build support, so you'd have to hand-build CMake support for them, and guess which files actually needed to be built/linked. Some of them cleverly generated some of their own source files during the build process ("This is fun to maintain" said nobody ever).

The Android NDK seems to be some Googler's frankenstein hobby project that got accidentally electrified into life and set loose on the world. Like Donald Trump, the NDK is kind of an embarrassing offshoot from the "mainstream Android development story," one that Google would like to pretend doesn't exists. They, in fact, tell you in their docs "Yea, you can use native C and C++ to write Android apps with this thing, but you really shouldn't!" [1] Seriously, their "Getting Started" guide spends a paragraph or so trying to convince you not to get started. Convincing Google's own development tools to build C++/NDK is pretty frustrating itself, so you can imagine how difficult it is to convince cmake to do it.

In short, if you are working on a project that targets desktop, embedded, and mobile platforms and has lots of third party code, and one day that little voice in your head starts saying "Hey we waste a lot of time maintaining separate Makefile, .xcodeproj, .vcproj, etc. Why don't we switch to a single glorious build system that lets us list our source files in one place!?" IGNORE THAT VOICE. Go play some foosball, fix a few bugs, have a cup of coffee, but by no means try to use cmake for this.

1: https://developer.android.com/ndk/guides/index.html


I've had a similar experience getting shit to work on multiple platforms. I decided not to use cmake for mobile targets. While the NDK in general sucks to build, if you follow certain patterns you can get a system that works and is fairly straightforward. The path to get there is not easy though, I'll give you that.


Absolutely crazy.

Thanks for the write up.


That is just kicking the can further down the road. Now, instead of mantaining makefile for make, you are mantaining CMakeLists.txt for cmake.


I use CMake for everything-that-isn't-$work-now because that's the only thing CLion supports. Even at work I've taken to creating my own CMakeLists.txt so I can get CLion support. I like the IDE that much.

It's not perfect, as others have touched on. But it overall satisfies my requirements and I can usually get things done with it.

Being a Google employee, I'm somewhat partial to Bazel (https://bazel.io/) which is based on what we use internally. I've started to ponder what it would take to write a CMakeLists.txt generator for it.


Prr cmake and scons are dog slow. Make isn't exactly fast either. I didn't realise how slow they were until I used QBS which is so fast you sometimes think it hasn't done anything.


Would be nice if you could give an example, not just "use that".


Especially since scons and cmake are in no way easy to learn, imho.


Still hell of a lot simpler than hand-writing Makefiles though. For both small and large projects.

I mean, a "hello world" CMakeLists.txt can be literally just this:

    add_executable(hello hello.c)
And it's cross-platform, can generate Visual Studio/Xcode/... projects, etc.


I think we have to go deeper than that to really see an advantage of cmake. A corresponding makefile would be:

    all: hello
That's not really complicated either.


Exactly. When evaluating a language / build system / whatever, it is definitely not sufficient to look at Hello World. You have to evaluate the worst case and average case if you want to get an idea of what you're in for. Looking at the "best case" (simple one-file project) tells you almost nothing except how hard it is to get started.


When evaluating build systems, one of my major questions is: How much "magic" will I have to work-around in order to do something off the beaten path that the build system's developers never considered?

This is a good criteria for languages as well.


The thing is that these are not equivalent hello worlds. I mean, if only for the fact that the cmake example does proper header dependency management, which is essential to correctly compiling c/c++ code as it changes. It's not something you can leave out of a makefile and still call it correct.


`scons` is very easy to start with:

    Program("main", ["main.cpp"])
The problems start once you want configure stages, build directories, portability and install targets, as all of those aspects are extremely lackluster and incomplete in `scons` and you have to reinvent large parts of the build system yourself essentially for anything even mildly complex.

`cmake` is much better. The language itself is ugly and takes some getting used to, so it's a little more complicated to get started with then `scons`. `cmake` generating `Makefile`s instead of actually building the project itself also adds a layer of complexity. But once you understand the basics it's much easier to get a fully functioning build going, including configure stages, build directories, portability and all that, as cmake has all of that integrated and working right out of the box.

`cmake` feels like a complete build system, while `scons` feels like a good start for a build system that was abandoned at the halfway point.

I still use plain `make` for some Python projects (mostly .PHONY targets to run `pylint`, `autopep` and friends), but for everything that needs to get compiled `cmake` is much easier to use than plain `make`, as you don't need to reinvent a build system yourself, it already comes with almost everything you need.


cmake is arguably easier to learn than make


and I argue with your conclusion.


Still easier than make...


hello.c in a directory

make hello

Easier?


I would not be using cmake if this repository did not exist:

https://github.com/Akagi201/learning-cmake



$ ls src

demo.cxx demo_b.cxx CMakeLists.txt

$ cat src/CMakeLists.txt

cmake_minimum_required (VERSION 2.8.11)

project (HELLO)

add_executable (helloDemo demo.cxx demo_b.cxx)

$ mkdir bin

$ cd bin

$ cmake ../src

SNIP

$ make

$ ./helloDemo


# SConstruct file env = Environment() hello = Program(["hello.c"])

$ scons


This is a good start. Could be made a bit more minimal.

I'll throw my base Makefile into the fray [0].

It supports cross compilation, CFLAGS & LDFLAGS usage, dependency file generation, source file autodetection (add the file to right folder and it's automatically included in the build), library building (`make lib`), Continuous Integration (`make start_ci/stop_ci`), Continuous Testing (`make start_ct/stop_ct`), Continuous Deployment (`make start_cd/stop_cd`), Installation (`make install`), Uninstallation (`make uninstall`), parallel compilation (`make -j4`), there's some build machine detecting/autoconfiguration support, build artifact generation, and even some git shortcuts (`make gstat/make make gpush`).

All in about 190 lines of Makefile (including some comments, and lots of blank space lines). It does depend on GNU watch if you use any of the `start_/stop_` targets. I'm about to add self-documentation to it as well.

For more minimal, comprehensible approaches, see the work of the suckless people [1][2][3].

[0] https://github.com/lpsantil/NewProj

[1] http://git.suckless.org/dwm/tree/Makefile

[2] http://git.suckless.org/sbase/tree/Makefile

[3] http://git.suckless.org/ii/tree/Makefile


> a bit more minimal

ie. smaller, which is ironically smaller than the quote

edit: compared to 'gcc $CFLAGS $LDFLAGs <...> *.c' it's all rather bloated


Neat! I've been putting off learning how to use those `-M` flags to automatically generate dependencies, but it looks like it's not that hard :)

What I also like to do is hide the actual compilation command with recipes like this, inspired by the Linux kernel

    %.o : %.c
        @echo "      CC $<"
        @$(CC) -o $@ -c $(CFLAGS) $<
I think it looks a lot tidier, it turns a ton of dense uninteresting command lines into a few dozen short lines.


A variant we use is:

    Q ?= @

    %.o : %.c
        @echo 'CC $@'
        $(Q)$(CC) -o $@ -c $(CFLAGS) $<
Now you can make the commands visible again via `make Q=`.

A more subjective question is, if you echo $@ or $<.


In a past life, the mega-but-autogenerated Makefile I helped maintain would store the executed command and only echo it if the command failed. And we logged everything to file, so you got nice output but all the info was there if we needed it. Didn't take that long to work out how to do it, once we'd decided it would be a nice thing to have.


I like that. It lives the Unix philosophy to be silent unless an error occurs.


I did the hiding for a while until I noticed that it slows my debugging down way too much when something breaks and I want to manually fiddle with some compiler flags. Now I let make use two separate colors: One for info I'm usually interested in and one for stuff I need to know when I need to get my hands dirty.


This approach is flawed because it uses "find" to discover the source files which means that if you delete a source file, it won't cause a rebuild since it won't see any changed files.


It's probably possible to adapt it to do that by using find much like you use gcc -MM to get header deps, generating a make snippet that you then depend the executable output on. But then we're getting pretty nasty.


Also you can't conditionally build a .c or .cpp file without ifdefs in the source.

Also you have to wait for find to run before any building starts. Can be slow for a large project.


The problem with Makefiles is that they don't work on Windows with Visual Studio, and generally only have very limited support in IDEs like Xcode, QtCreator, etc... and once stuff like cross-compilation and feature selection comes in, they get really messy really fast. That's where meta-build-systems like cmake etc come in. They all have their faults (cmake has a terrible scripting language), but they solve real problems for non-trivial projects.


I agree that cross-platform is tricky and in the past I have used SCons with some success for that.

Although, at least for Xcode, I can build my entire Xcode project from a Makefile: you just have to use command-line tools like "xcodebuild", and if you want a clickable version you set up a build phase that runs "make". And, while Apple’s ".xcconfig" files are not exactly "make" syntax, they support a subset that looks exactly the same so it is possible to have files with variable settings, etc. that can be referenced by both Xcode and any Makefile that needs to stay in sync. Environment variables are yet another option.


I'm going to take this opportunity to rant about sad state of C++ open source. I would really really like to write some C++ but I'm not a C++ guru; so I tried installing facebook/folly and facebook/fatal on my Mac OS. That just doesn't work. It wasn't written cross platform and it probably never will. So I spun up a ubuntu VM and tried there. fatal compiles, folly still doesn't... And so ends my brief foray into C++. At that point I started researching how can I bind the library I need to Rust so I can skip all this nonsense.

C++ is not a bad language and I would like to write a project hat uses it. But I'm just a user of the language. I don't have time and expertise to tackle those build issues and quite frankly installing any library is a major PITA.


C++, regardless of open/closed source is not in a sad state IMO. The problems you describe sound like they're from the perspective of someone who's worked with much higher level languages. C++ is as good (or as bad) as it's always been, it hasn't evolved to a bad state.

New language designers have the luxury of having been bit by C/C++'s misfeatures and wrinkles. They also have the luxury of high end computers which can solve lots of problems in reasonable computation time. This means that modern languages can afford to trade off more computation resources in favor of development simplicity.

> But I'm just a user of the language. I don't have time and expertise to tackle those build issues and quite frankly installing any library is a major PITA.

I have a hard time understanding what "just a user" of the language means. It sounds like you're confessing to being a novice. That's fine, but you should be prepared to have to do a lot of work before you become proficient in C/C++. Much more so than modern languages.


Have a look at Conan [https://www.conan.io/] then. It has solved the problem you described and its usability is great


Why not just use Xcode and xcodebuild from the CLI if you don't like the IDE?

I write all my OSX projects in C++ using it.

Learning to build projects and solve compiler and linker errors is part of the journey. Stroustrup's book takes longer to read than 5 minutes, so writing C++ will take longer. It is a rewarding journey, where you will suddenly find all other languages (Java, C#, C, JavaScript, Swift) very similar or simplified in comparison.

To get you started you could install xcode and the commandline tools, write your file.cpp and use:

g++ -o myprogram file.cpp

This assumes no linker options, no preprocessor options or anything, and on OSX will use clang, which it secretly symbolic links as g++. You might find it easier to get your head around it by starting a project in Xcode, setting the options in the IDE and then observing the output that it runs.

Note that installing a library on most Unix platforms isn't complicated - configure, make, make install.


BSD make (bmake) and its .mk includes are really simple, it's basically make with batteries included. Many makefiles may be reduced to two lines while no new syntax is introduced:

  PROG=${.CURDIR:T}
  .include <prog.mk>
It indirectly extends original BSD make. There are some syntactic differences from GNU make, but no big depart.

See http://www.crufty.net/help/sjg/bmake.htm

Here is a custom BSDmakefile for my website, a more extensive example where the mk libraries can't apply:

  # makefile -- Build the website.
  #
  # $Id: makefile,v 1.10 2016/08/15 12:55:49 igk Exp $
  #
  #
  
  M4=m4
  AWK=awk
  SED=sed
  TIDY=tidy5
  TARGS=-i -utf8
  
  include config.mk
  
  # Common templates for each output file.
  MACROES=html.m4 config.m4
  TMPL=${MACROES} meta.m4 header.html.m4 HERE footer.html.m4
  HTML+=${LISTS:S/$/.html/}
  ATOM=${LISTS:S/$/.atom.xml/}
  OUT=${HTML} ${HTML:S/html$/&.m4/} ${HTML:S/html$/&.dirty/} ${ATOM}
  
  .MAIN: ${HTML} ${ATOM}
  
  # Generate lists.
  .for L in ${LISTS}
  $L.txt: $L.list
  	${AWK} -f list.awk ${.ALLSRC} > ${.TARGET}
  
  $L.atom.xml: $L.list
  	${AWK} -f atom.awk ${.ALLSRC} \
  		| ${M4} ${MACROES} - \
  		| ${SED} '1,2d' > ${.TARGET}
  .endfor
  
  .ifndef NOTIDY
  
  .for P in ${HTML}
  $P : ${P}.dirty
  	${TIDY} ${TARGS} ${.ALLSRC} 2>/dev/null > ${.TARGET} || true
  .endfor
  
  .for P in ${HTML}
  $P.dirty : ${TMPL:S/HERE/$P.m4/}
  	$(M4) ${.ALLSRC} > ${.TARGET}
  .endfor
  
  .else
  
  .for P in ${HTML}
  $P : ${TMPL:S/HERE/$P.m4/}
  	$(M4) ${.ALLSRC} > ${.TARGET}
  .endfor
  
  .endif # NOTIDY
  
  .for P in ${HTML}
  ${P}.m4 : ${P:S/html$/txt/}
  	$(AWK) -f page.awk ${.ALLSRC} > ${.TARGET}
  .endfor
  
  clean:
  	rm -f ${OUT}
  
  .PHONY: uuid pub ssh anal
  uuid:
  	sh ./make-uuid.sh
  pub:
  	sh ./publish.sh
  ssh:
  	sh ./s.sh
  anal:
  	sh ./analytics.sh


We must be using a different definition of simple. I've never seen a simple makefile.


Well, with implicit rules IMO you can get really simple but productive makefiles.

e.g. (not "medium-sized" project, but illustrates the simplicity) https://gist.github.com/androm3da/9b5575fb288045775c178a4fb2...

here's a slightly more feature-rich example, still relatively simple IMO.

https://gist.github.com/androm3da/9126ff51b5b47f69b99b1e3db8...


Nice to see people spreading good practices with Make.

Too many folks don't understand good use of Make and thus are doomed to re-invent it badly.

I've had to use too many build systems that are, basically, big and flat lists of variables you can set. And yes I'm pointing at everything from esoteric in house systems to mainstream ones. Make's system of overrides is something especially useful.


This. Make is highly underrated. It's old so it's probably clunky, has a weird syntax and dog slow. Well no. So many examples of people reinventing the wheel, it's just sad.


"But because Make works backward from the object files to the source, we need to compute all the object files we want from our source files".

No.... don't do this.

Make is intended to work the other way around... it finds the source from your targets.

Define your VPATH and let it find them.

This is what loads of people do and is why there are so many Makefiles are packed full of macro-magic!


I do agree that VPATH is helpful, but you'd still need to put the Makefile into the build directory in that case, which is distasteful, as it's not a built file.

Also you can't really get around specifying the files or searching, unless you have one c file per executable.


Projects that make blind use of VPATH are a pain. You can't have the same filename in two different directories! It's a hack at best, and using explicit paths (as this Makefile does) is a lot more reliable.


Totally agree, they should use VPATH. And that way they could've just skipped to the implicit rules version.


I feel like the only person in the world that uses Boost Bjam (for a proprietary project). I feel like I should switch but every time I look into it and read comments it's not at all clear that Bjam was a bad decision - even if it feels like I was the only one to choose it :-) It's simple, seems pretty fast but it is very hard to integrate custom build steps.


I'm a big fan of meson [0].

The one thing it doesn't have over this Makefile is automatic detection of source files - you need to manually specify them. I haven't found that to be prohibitive in practice.

0: http://mesonbuild.com/



Translating that Makefile to Shake http://shakebuild.com/ would be a good Shake learning project...


This is not simple. I haven't made my own Makefiles, because when I see them, they are complex and not easy to understand.

That's why I chose CMake for my projects. If this is simple, I chose the right build tool.

This build file of a large project is easier to understand than the "Simple Makefile"

https://gitlab.kitware.com/vtk/vtk/blob/master/CMakeLists.tx...


> they are complex and not easy to understand.

Makefiles are declarative, which is a bit strange at first. If you start with a simple one you pick it up really fast.


Can we kill raw makefiles already? Projects that don't handle out of source builds in a sane way are awful to integrate.


[flagged]


Please comment civilly and substantively or not at all.

https://news.ycombinator.com/newsguidelines.html




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: