Hacker News new | ask | show | jobs
by adrian_b 1517 days ago
> How well a tool lends itself to be used by the average person is an important property when choosing to use a technology over another.

The reason why most makefiles in open source projects are horrible has nothing to do with the qualities of make, because typically those makefiles are not written by some human who knows about make, but they are generated by some other tools, e.g. autoconf/automake.

Moreover, the generation method typically used is flawed, because it generates the entire makefiles, in an incomprehensible form. The maintenance of those makefiles would have been much easier if makefiles carefully written by a human would have been used, which would have included a file generated by the autoconf tools, and the included file should have contained only definitions, and neither rules nor targets.

Unfortunately there is an ancient tradition established decades ago to generate the makefiles in an overly complicated way and nobody has the courage to change that in any project, because probably nobody understands any more what would happen if changes would be made.

> Can you provide e.g. a project with your good makefiles, that I could use to build debug and release builds, with any of MSVC, GCC, and Clang ?

I never had any need to compile something for MSVC, GCC and Clang.

Nevertheless, many of my projects had to be cross-compiled for various embedded CPU targets.

The way I do that is that I have one makefile that contains only definitions for each project building target, so I could have e.g. 3 makefiles, 1 for a MSVC/Windows target, 1 for a GCC/Linux target and 1 for a Clang/Mac OS target.

Each such makefile will have definitions for the names of all executables that may be needed for bulding a software project, e.g. compilers, assemblers, linkers, librarians, object file converters, copy commands, move commands, rename commands and so on.

It will also have definitions for all the command-line option flags that must be provided to each executable in order to perform whatever tasks are needed for building a software project.

Writing such a makefile for a compilation is a one-time effort, I might write it in an hour or more, while searching through the documentation of various tools, but then I might use it unchanged, during many years, for all projects that I intend to build for that target.

I need to write such makefiles only infrequently, when I begin to use a new CPU, or a new operating system, or a new compiler.

These makefiles with definitions dependent on the compilation target are included with an include directive in the complete makefiles that build the software project.

I build each final file, e.g. executable file, dynamic library or static library in a separate build directory. When I want to build multiple files with a single command, then their build directories are subdirectories of a directory where there is a makefile which will invoke the makefiles in the leaf subdirectories and maybe move or copy the built files somewhere else, if the end result needs them to be in certain relative positions in a directory hierarchy.

The makefile in the build directory of some file has only a few lines. It includes the makefile with the definitions dependent on the compilation target and a makefile that is the same for all my projects, with general definitions, rules and targets.

Besides the include directive, there are a few definitions, the type of file that must be built, the name of the built file (when absent, the current directory name is assumed, with an appropriate extension) a list of directories that must be searched to find the source files (if absent the current directory is assumed) and an optional prefix for the list of directories.

Because make searches itself for source files and automatically generates their dependencies, I do not have to do anything when I add/delete/move/rename source files.

For debug and release I obviously have these 2 make targets in the included makefile with general make rules and targets, which are the same for all projects. The included files per compilation target have definitions for the debug and release command-line flags for all the tools, e.g. compilers, assemblers, linkers.

So if MSVC, GCC and Clang on certain operating systems would be my targets, I would have no problem to build these 3 targets either separately or simultaneously, without having to write a single word in the makefiles of the project. When I would create the build directories for the project, I would copy in each build directory the appropriate template makefile, in which I would change, if needed, only the name of the built file and the name of the directory or directories where the source files are located.

> in my experience make always takes a few seconds for projects with >1k targets when changing a single file. Ninja is consitently instantaneous.

You are right that this is the only case when the tool used to build the project can make a difference.

When a large number of files must be compiled, there is no chance to see any difference due to the speed of the project build tool.

However, when only a single file is recompiled, then there can be a significant difference.

Ninja is very simplified in comparison with make, so I have no doubt that it is faster.

For your example with many thousands of files where you frequently recompile only a couple of files, one could use make + ninja instead of cmake + ninja.

The general rules from my makefiles could be modified to generate ninja input files instead of invoking the build commands,

In that case I would invoke make only after making changes like add/delete/rename source files, and then ninja for the actual recompilation.

Nevertheless, I never had to do this until now, because the speed of make was always acceptable, which might be due to the fact that I have always used fast CPUs with generous quantities of installed memory and with fast SSDs.

Moreover, I usually think a lot before making changes and then I do all of them, so for me it happens very infrequently to need to recompile a single file many times in a row.

In conclusion, I agree with you that there is a use case for ninja, for very large projects where frequent recompilations of only a few files are needed.

Nevertheless, there are a lot of software developers who will never encounter this use case, so for them make is enough.

On the other hand for cmake I am not aware of any useful application, because all the examples that I have seen of cmake projects were not simpler than if those projects would have used make.

It is possible that I have seen only examples where cmake was not used well, but in any case, the best that cmake can hope is to be as easy to use as make.

1 comments

> I never had any need to compile something for MSVC, GCC and Clang.

I am lucky that this is your case ; many of the software I've worked on had this requirement.

> The general rules from my makefiles could be modified to generate ninja input files instead of invoking the build commands,

that's basically reimplementing cmake in make. I don't understand why I would do this work myself when cmake exists and is constantly updated to adapt to newer compilers, etc. without me having to lift a finger. + the generation of targets for the IDE - just in the last two weeks I had users come and ask me to have a build that can be worked on in both Visual Studio and Xcode. Good luck with doing that in make.

> When a large number of files must be compiled, there is no chance to see any difference due to the speed of the project build tool.

1k isn't very large though. There are projects out there with the source code without any asset sized in gigabytes.

> On the other hand for cmake I am not aware of any useful application, because all the examples that I have seen of cmake projects were not simpler than if those projects would have used make.

    project(bla)
    add_executable(my_app main.cpp)
is the minimal CMake project and likely supports as-is more toggles and switches than your homegrown makefile system - toggles and switches that are necessary for pretty much any non-toy project.
After reading your reply, I have browsed the cmake reference manual.

It is likely that for many people cmake may be enough, but my homegrown makefile system can do much more than cmake.

The cmake equivalent for the makefiles with compilation-target-dependent definitions is called a toolchain file.

However, unlike for make, where I have complete freedom, cmake allows in the toolchain file only a relatively small set of options from a closed set.

Because I cross-compile for many unusual combinations of embedded CPUs and operating systems, with cmake I would certainly need to write many custom toolchain files, but cmake would not allow me to specify everything that I need.

Besides C & C++, I use many less popular programming languages. Even if cmake supports a decent number of programming languages, when you want to use any language that is not on the currently supported list, you are out of luck.

With my make system, if I decide to use a new programming language, I would need only a few minutes to update the makefile with generic rules and the toolchain makefiles in order to be able to build a project with the new language.

Why would I need to use a rigid tool like cmake, when the same and more can be done with make ?

I see in the cmake documentation the following recommendation, which appears to have been followed in all the cmake projects that I have seen.

"We do not recommend using GLOB to collect a list of source files from your source tree. If no CMakeLists.txt file changes when a source is added or removed then the generated build system cannot know when to ask CMake to regenerate."

This recommendation is very wrong. Even if cmake has the limitation that it is triggered only by an updated CMakeLists.txt file, the workaround is not to avoid using GLOB.

The correct workaround is to use GLOB, but to also execute the command "touch CMakeLists.txt" whenever there are changes in the set of source files.

Executing a "touch" is much simpler than editing CMakeLists.txt.

The fact that the bad recommendation from the cmake documentation has been followed blindly in all the cmake projects that I have seen is one reason why I thought that cmake sucks.

However, like I have said above, even when cmake is used correctly, it is too rigid for those whose applications are not restricted to the targets understood by cmake.

"make" does not have any such limitations, so I still do not see any reason for the existence of "cmake", because to do what cmake does you do not need a new executable, you need just a set of generic makefiles, like those that I use, or like those that are used in some projects like FreeBSD (those, unlike cmake are not oriented towards multiple compilation targets, but they could be adapted for such a purpose with much less effort than writing a program like cmake).