EmbeddedRelated.com
Forums

Makefile or IDE?

Started by pozz December 2, 2021
Am 03.12.2021 um 23:48 schrieb pozz:
> It's absurd how difficult is to create a Makefile for a simple project > with the following tree: > > Makefile > src/ >   file1.c >   module1/ >     file2.c >   module2/ >     file3.c > target1/ >   Release/
[...]
>   Debug/ >     src/ >       file1.o >       file1.d >       ... > > Just to create directories for output files (objects and dependencies) > is a mess: precious rule, cheating make adding a dot after trailing > slash, second expansion, order-only prerequisite!!!
Hypothesis: a single Makefile that does all this is not a good idea. Better: make a single Makefile that turns your source code into one instance of object code, and give it some configuration options that say whether you want target1/Release, target2/Release, or host/Debug. I'm not sure what you need order-only dependencies for. For a project like this, with GNU make I'd most likely just do something like OBJ = file1.o module1/file2.o module2/file3.o main: $(OBJ) $(CC) -o $@ $(OBJ) $(OBJ): %.o: $(SRCDIR)/%.c mkdir $(dir $@) $(CC) $(CFLAGS) -c $< -o $@
> Dependencies must be created as a side effect of compilation with > esoteric -M options for gcc.
It's not too bad with sufficiently current versions. CFLAGS += -MMD -MP -include $(OBJ:.o=.d)
> Is cmake simpler to configure?
CMake does one-configuration-per-invocation type builds like sketched above, i.e. to build target1/Release and target1/Debug, you invoke CMake on two different workspaces, once with -DCMAKE_BUILD_TYPE=Release and once with -DCMAKE_BUILD_TYPE=Debug. Stefan
Il 04/12/2021 10:31, Stefan Reuther ha scritto:
> Am 03.12.2021 um 23:48 schrieb pozz: >> It's absurd how difficult is to create a Makefile for a simple project >> with the following tree: >> >> Makefile >> src/ >> &nbsp; file1.c >> &nbsp; module1/ >> &nbsp;&nbsp;&nbsp; file2.c >> &nbsp; module2/ >> &nbsp;&nbsp;&nbsp; file3.c >> target1/ >> &nbsp; Release/ > [...] >> &nbsp; Debug/ >> &nbsp;&nbsp;&nbsp; src/ >> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp; file1.o >> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp; file1.d >> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ... >> >> Just to create directories for output files (objects and dependencies) >> is a mess: precious rule, cheating make adding a dot after trailing >> slash, second expansion, order-only prerequisite!!! > > Hypothesis: a single Makefile that does all this is not a good idea. > Better: make a single Makefile that turns your source code into one > instance of object code, and give it some configuration options that say > whether you want target1/Release, target2/Release, or host/Debug.
Oh yes, I'm using three make variables: CONF=rls|dbg TARGET=target1|target2 I also have MODEL=model1|model2 because the same source code can be compiler to produce firmware for two types of products. Anyway, even using these three variables, the Makefile is difficult to write and understand (at least for me).
> I'm not sure what you need order-only dependencies for. For a project > like this, with GNU make I'd most likely just do something like > > OBJ = file1.o module1/file2.o module2/file3.o > main: $(OBJ) > $(CC) -o $@ $(OBJ) > $(OBJ): %.o: $(SRCDIR)/%.c > mkdir $(dir $@) > $(CC) $(CFLAGS) -c $< -o $@
This is suboptimal. Every time one object file is created (because it is not present or because prerequisites aren't satisfied), mkdir command is executed, even if $(dir $@) is already created. A better approach is to use a dedicated rule for directories, but it's very complex and tricky[1]. I think your approach is better, only because is much more understandable, not because is more efficient.
>> Dependencies must be created as a side effect of compilation with >> esoteric -M options for gcc. > > It's not too bad with sufficiently current versions. > > CFLAGS += -MMD -MP > -include $(OBJ:.o=.d)
Are you sure you don't need -MT too, to specify exactly the target rule?
>> Is cmake simpler to configure? > > CMake does one-configuration-per-invocation type builds like sketched > above, i.e. to build target1/Release and target1/Debug, you invoke CMake > on two different workspaces, once with -DCMAKE_BUILD_TYPE=Release and > once with -DCMAKE_BUILD_TYPE=Debug.
Yes, I was asking if the configuration file of CMake is simpler to write compared to a Makefile. [1] https://ismail.badawi.io/blog/automatic-directory-creation-in-make/
On 04/12/2021 16:23, pozz wrote:
> Il 04/12/2021 10:31, Stefan Reuther ha scritto:
>> I'm not sure what you need order-only dependencies for. For a project >> like this, with GNU make I'd most likely just do something like >> >> &nbsp;&nbsp;&nbsp;&nbsp; OBJ = file1.o module1/file2.o module2/file3.o >> &nbsp;&nbsp;&nbsp;&nbsp; main: $(OBJ) >> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; $(CC) -o $@ $(OBJ) >> &nbsp;&nbsp;&nbsp;&nbsp; $(OBJ): %.o: $(SRCDIR)/%.c >> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; mkdir $(dir $@) >> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; $(CC) $(CFLAGS) -c $< -o $@ >
I almost never use makefiles that have the object files (or source files, or other files) specified explicitly. CFILES := $(foreach dir,$(ALLSOURCEDIRS),$(wildcard $(dir)/*.c)) CXXFILES := $(foreach dir,$(ALLSOURCEDIRS),$(wildcard $(dir)/*.cpp)) OBJSsrc := $(CFILES:.c=.o) $(CXXFILES:.cpp=.o) OBJS := $(addprefix $(OBJDIR), $(patsubst ../%,%,$(OBJSsrc))) If there is a C or C++ file in the source tree, it is part of the project. Combined with automatic dependency resolution (for which I use gcc with -M* flags) this means that the make for a project adapts automatically whenever you add new source or header files, or change the ones that are there.
> This is suboptimal. Every time one object file is created (because it is > not present or because prerequisites aren't satisfied), mkdir command is > executed, even if $(dir $@) is already created.
Use existence-only dependencies: target/%.o : %.c | target $(CC) $(CFLAGS) -c $< -o $@ target : mkdir -p target When you have a dependency given after a |, gnu make will ensure that it exists but does not care about its timestamp. So here it will check if the target directory is there before creating target/%.o, and if not it will make it. It probably doesn't matter much for directories, but it can be useful in some cases to avoid extra work. And use "mkdir -p" to make a directory including any other parts of the path needed, and to avoid an error if the directory already exists.
> > A better approach is to use a dedicated rule for directories, but it's > very complex and tricky[1].
The reference you gave is okay too. Some aspects of advanced makefiles /are/ complex and tricky, and can be hard to debug (look out for mixes of spaces instead of tabs at the start of lines!) But once you've got them in place, you can re-use them in other projects. And you can copy examples like the reference you gave, rather than figuring it out yourself.
> > I think your approach is better, only because is much more > understandable, not because is more efficient. >
My version is - IMHO - understandable /and/ efficient.
> >>> Dependencies must be created as a side effect of compilation with >>> esoteric -M options for gcc. >> >> It's not too bad with sufficiently current versions. >> >> &nbsp;&nbsp;&nbsp;&nbsp; CFLAGS += -MMD -MP >> &nbsp;&nbsp;&nbsp;&nbsp; -include $(OBJ:.o=.d) > > Are you sure you don't need -MT too, to specify exactly the target rule? >
The exact choice of -M flags depends on details of your setup. I prefer to have the dependency creation done as a separate step from the compilation - it's not strictly necessary, but I have found it neater. However, I use two -MT flags per dependency file. One makes a rule for the file.o dependency, the other is for the file.d dependency. That way, make knows when it has to re-build the dependency file.
> >>> Is cmake simpler to configure? >> >> CMake does one-configuration-per-invocation type builds like sketched >> above, i.e. to build target1/Release and target1/Debug, you invoke CMake >> on two different workspaces, once with -DCMAKE_BUILD_TYPE=Release and >> once with -DCMAKE_BUILD_TYPE=Debug. > > Yes, I was asking if the configuration file of CMake is simpler to write > compared to a Makefile. >
I've only briefly looked at CMake. It always looked a bit limited to me - sometimes I have a variety of extra programs or steps to run (like a Python script to pre-process files and generate extra C or header files, or extra post-processing steps). I also often need different compiler flags for different parts of a project. Perhaps it would work for what I need and I just haven't read enough.
> > [1] https://ismail.badawi.io/blog/automatic-directory-creation-in-make/
On 4.12.21 18.41, David Brown wrote:
> On 04/12/2021 16:23, pozz wrote: >> Il 04/12/2021 10:31, Stefan Reuther ha scritto: > >>> I'm not sure what you need order-only dependencies for. For a project >>> like this, with GNU make I'd most likely just do something like >>> >>> &nbsp;&nbsp;&nbsp;&nbsp; OBJ = file1.o module1/file2.o module2/file3.o >>> &nbsp;&nbsp;&nbsp;&nbsp; main: $(OBJ) >>> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; $(CC) -o $@ $(OBJ) >>> &nbsp;&nbsp;&nbsp;&nbsp; $(OBJ): %.o: $(SRCDIR)/%.c >>> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; mkdir $(dir $@) >>> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; $(CC) $(CFLAGS) -c $< -o $@ >> > > I almost never use makefiles that have the object files (or source > files, or other files) specified explicitly. > > CFILES := $(foreach dir,$(ALLSOURCEDIRS),$(wildcard $(dir)/*.c)) > CXXFILES := $(foreach dir,$(ALLSOURCEDIRS),$(wildcard $(dir)/*.cpp)) > > OBJSsrc := $(CFILES:.c=.o) $(CXXFILES:.cpp=.o) > OBJS := $(addprefix $(OBJDIR), $(patsubst ../%,%,$(OBJSsrc))) > > If there is a C or C++ file in the source tree, it is part of the > project. Combined with automatic dependency resolution (for which I use > gcc with -M* flags) this means that the make for a project adapts > automatically whenever you add new source or header files, or change the > ones that are there. > >> This is suboptimal. Every time one object file is created (because it is >> not present or because prerequisites aren't satisfied), mkdir command is >> executed, even if $(dir $@) is already created. > > Use existence-only dependencies: > > target/%.o : %.c | target > $(CC) $(CFLAGS) -c $< -o $@ > > target : > mkdir -p target > > > When you have a dependency given after a |, gnu make will ensure that it > exists but does not care about its timestamp. So here it will check if > the target directory is there before creating target/%.o, and if not it > will make it. It probably doesn't matter much for directories, but it > can be useful in some cases to avoid extra work. > > And use "mkdir -p" to make a directory including any other parts of the > path needed, and to avoid an error if the directory already exists. > >> >> A better approach is to use a dedicated rule for directories, but it's >> very complex and tricky[1]. > > The reference you gave is okay too. Some aspects of advanced makefiles > /are/ complex and tricky, and can be hard to debug (look out for mixes > of spaces instead of tabs at the start of lines!) But once you've got > them in place, you can re-use them in other projects. And you can copy > examples like the reference you gave, rather than figuring it out yourself. > >> >> I think your approach is better, only because is much more >> understandable, not because is more efficient. >> > > My version is - IMHO - understandable /and/ efficient. > >> >>>> Dependencies must be created as a side effect of compilation with >>>> esoteric -M options for gcc. >>> >>> It's not too bad with sufficiently current versions. >>> >>> &nbsp;&nbsp;&nbsp;&nbsp; CFLAGS += -MMD -MP >>> &nbsp;&nbsp;&nbsp;&nbsp; -include $(OBJ:.o=.d) >> >> Are you sure you don't need -MT too, to specify exactly the target rule? >> > > The exact choice of -M flags depends on details of your setup. I prefer > to have the dependency creation done as a separate step from the > compilation - it's not strictly necessary, but I have found it neater. > However, I use two -MT flags per dependency file. One makes a rule for > the file.o dependency, the other is for the file.d dependency. That > way, make knows when it has to re-build the dependency file. > >> >>>> Is cmake simpler to configure? >>> >>> CMake does one-configuration-per-invocation type builds like sketched >>> above, i.e. to build target1/Release and target1/Debug, you invoke CMake >>> on two different workspaces, once with -DCMAKE_BUILD_TYPE=Release and >>> once with -DCMAKE_BUILD_TYPE=Debug. >> >> Yes, I was asking if the configuration file of CMake is simpler to write >> compared to a Makefile. >> > > I've only briefly looked at CMake. It always looked a bit limited to me > - sometimes I have a variety of extra programs or steps to run (like a > Python script to pre-process files and generate extra C or header files, > or extra post-processing steps). I also often need different compiler > flags for different parts of a project. Perhaps it would work for what > I need and I just haven't read enough. > >> >> [1] https://ismail.badawi.io/blog/automatic-directory-creation-in-make/
CMake is on a different level than make. CMake aims to the realm of autoconf, automake and friends. One of the supported tail-ends for CMake is GNU make. -- -TV
On 4.12.21 19.23, David Brown wrote:
> On 04/12/2021 17:49, Tauno Voipio wrote: >> On 4.12.21 18.41, David Brown wrote: >>> On 04/12/2021 16:23, pozz wrote: >>>> Il 04/12/2021 10:31, Stefan Reuther ha scritto: > >>>>>> Is cmake simpler to configure? >>>>> >>>>> CMake does one-configuration-per-invocation type builds like sketched >>>>> above, i.e. to build target1/Release and target1/Debug, you invoke >>>>> CMake >>>>> on two different workspaces, once with -DCMAKE_BUILD_TYPE=Release and >>>>> once with -DCMAKE_BUILD_TYPE=Debug. >>>> >>>> Yes, I was asking if the configuration file of CMake is simpler to write >>>> compared to a Makefile. >>>> >>> >>> I've only briefly looked at CMake.&nbsp; It always looked a bit limited to me >>> - sometimes I have a variety of extra programs or steps to run (like a >>> Python script to pre-process files and generate extra C or header files, >>> or extra post-processing steps).&nbsp; I also often need different compiler >>> flags for different parts of a project.&nbsp; Perhaps it would work for what >>> I need and I just haven't read enough. >>> >>>> >>>> [1] https://ismail.badawi.io/blog/automatic-directory-creation-in-make/ >> >> >> CMake is on a different level than make. CMake aims to the realm of >> autoconf, automake and friends. One of the supported tail-ends for >> CMake is GNU make. >> > > Yes, I know. The question is, could I (or the OP, or others) use CMake > to control their builds? It doesn't really matter if the output is a > makefile, a ninja file, or whatever - it matters if it can do the job > better (for some value of "better") than a hand-written makefile. I > suspect that for projects that fit into the specific patterns it > supports, it will be a good choice - for others, it will not.
I tried to use it for some raw-iron embedded programs. IMHO, CMake belongs there more to the problem than solution set. CMake is aimed to produce code for the system it is run on, cross-compilation creates problems. I succeeded to use CMake for cross-compiling on PC Linux for Raspi OS Linux, but the compiler identification for a raw metal target was not happy when the trial compilation could not link a run file using the Linux run file creation model. -- -TV
On Thu, 2 Dec 2021 17:34:55 +0100, Stefan Reuther
<stefan.news@arcor.de> wrote:

>CMake seems to be a popular (the most popular?) choice for that language >on top, although reasons why it sucks are abundant; the most prominent >for me being that the Makefiles it generates violate pretty much every >best practice and therefore are slow. Other than that, it can build >embedded software of course.
CMake is popular because it is cross-platformm: with a bit of care, its makefiles (conditionally) can run on any platform that supports CMake. Cross-platform can be a dealbreaker in the desktop/server world. YMMV, George
On Fri, 3 Dec 2021 21:28:54 -0000 (UTC), Grant Edwards
<invalid@invalid.invalid> wrote:

>On 2021-12-03, Theo <theom+news@chiark.greenend.org.uk> wrote: > >> [*] Powershell and WSL have been trying to improve this. But I've not seen >> any build flows that make much use of them, beyond simply taking Linux flows >> and running them in WSL. > >I always had good luck using Cygwin and gnu "make" on Windows to run >various Win32 .exe command line compilers (e.g. IAR). I (thankfully) >haven't needed to do that for several years now...
The problem with Cygwin is it doesn't play well with native Windows GCC (MingW et al). Cygwin compilers produce executables that depend on the /enormous/ Cygwin library. You can statically link the library or ship the DLL (or an installer that downloads it) with your program, but by doing so your programs falls under GPL - the terms of which are not acceptable to some developers. And the Cygwin environment is ... less than stable. Any update to Windows can break it. YMMV, George
On 04/12/2021 20:53, George Neuner wrote:
> On Fri, 3 Dec 2021 21:28:54 -0000 (UTC), Grant Edwards > <invalid@invalid.invalid> wrote: > >> On 2021-12-03, Theo <theom+news@chiark.greenend.org.uk> wrote: >> >>> [*] Powershell and WSL have been trying to improve this. But I've not seen >>> any build flows that make much use of them, beyond simply taking Linux flows >>> and running them in WSL. >> >> I always had good luck using Cygwin and gnu "make" on Windows to run >> various Win32 .exe command line compilers (e.g. IAR). I (thankfully) >> haven't needed to do that for several years now... > > The problem with Cygwin is it doesn't play well with native Windows > GCC (MingW et al). > > Cygwin compilers produce executables that depend on the /enormous/ > Cygwin library. You can statically link the library or ship the DLL > (or an installer that downloads it) with your program, but by doing so > your programs falls under GPL - the terms of which are not acceptable > to some developers. > > And the Cygwin environment is ... less than stable. Any update to > Windows can break it. >
I concur with that. Cygwin made sense long ago, but for the past couple of decades the mingw-based alternatives have been more appropriate for most uses of *nix stuff on Windows. In particular, Cygwin is a thick compatibility layer that has its own filesystem, process management, and other features to fill in the gaps where Windows doesn't fulfil the POSIX standards (or does so in a way that plays badly with the rest of Windows). Very often, the changes needed in open-source or *nix-heritage software to make them more Windows-friendly are small. A common example is changing old-style "fork + exec" paths to "spawn" calls which are more modern and more efficient (even on *nix). With such small changes, programs can be compiled on thin compatibility layers like mingw instead, with the result being a lot faster, smoother, better integrated with Windows, and without that special tier of DLL-hell reserved for cygwin1.dll and its friends. So the earliest gcc versions I built and used on Windows, gcc 2.95 for the m68k IIRC, had to be built with Cygwin. By the time I was using gcc 4+, perhaps earlier, it was all mingw-based and I have rarely looked at Cygwin since. I strongly recommend msys2, with mingw-64, as the way to handle *nix programs on Windows. You can install and use as much as you want - it's fine to take most simple programs and use them independently on other Windows systems with no or a minimum of dll's. (If the program relies on many external files, it will need the msys2 file tree.) You can use msys2 bash if you like, or not if you don't like. The mingw-64 gcc has a modern, complete and efficient C library instead of the horrendous MSVCCRT dll. Most Windows ports of *nix software is made with either older mingw or newer mingw-64. I can understand using Cygwin simply because you've always used Cygwin, or if you really need fuller POSIX compatibility. But these days, WSL is probably a better option if that's what you need.
On 04/12/2021 20:25, Tauno Voipio wrote:
> On 4.12.21 19.23, David Brown wrote: >> On 04/12/2021 17:49, Tauno Voipio wrote: >>> On 4.12.21 18.41, David Brown wrote: >>>> On 04/12/2021 16:23, pozz wrote: >>>>> Il 04/12/2021 10:31, Stefan Reuther ha scritto: >> >>>>>>> Is cmake simpler to configure? >>>>>> >>>>>> CMake does one-configuration-per-invocation type builds like sketched >>>>>> above, i.e. to build target1/Release and target1/Debug, you invoke >>>>>> CMake >>>>>> on two different workspaces, once with -DCMAKE_BUILD_TYPE=Release and >>>>>> once with -DCMAKE_BUILD_TYPE=Debug. >>>>> >>>>> Yes, I was asking if the configuration file of CMake is simpler to >>>>> write >>>>> compared to a Makefile. >>>>> >>>> >>>> I've only briefly looked at CMake.&nbsp; It always looked a bit limited >>>> to me >>>> - sometimes I have a variety of extra programs or steps to run (like a >>>> Python script to pre-process files and generate extra C or header >>>> files, >>>> or extra post-processing steps).&nbsp; I also often need different compiler >>>> flags for different parts of a project.&nbsp; Perhaps it would work for what >>>> I need and I just haven't read enough. >>>> >>>>> >>>>> [1] >>>>> https://ismail.badawi.io/blog/automatic-directory-creation-in-make/ >>> >>> >>> CMake is on a different level than make. CMake aims to the realm of >>> autoconf, automake and friends. One of the supported tail-ends for >>> CMake is GNU make. >>> >> >> Yes, I know.&nbsp; The question is, could I (or the OP, or others) use CMake >> to control their builds?&nbsp; It doesn't really matter if the output is a >> makefile, a ninja file, or whatever - it matters if it can do the job >> better (for some value of "better") than a hand-written makefile.&nbsp; I >> suspect that for projects that fit into the specific patterns it >> supports, it will be a good choice - for others, it will not. > > > I tried to use it for some raw-iron embedded programs. IMHO, CMake > belongs there more to the problem than solution set. CMake is aimed > to produce code for the system it is run on, cross-compilation creates > problems. > > I succeeded to use CMake for cross-compiling on PC Linux for Raspi OS > Linux, but the compiler identification for a raw metal target was not > happy when the trial compilation could not link a run file using the > Linux run file creation model. >
That is kind of what I thought. CMake sounds like a good solution if you want to make a program that compiles on Linux with native gcc, and also with MSVC on Windows, and perhaps a few other native build combinations. But it is not really suited for microcontroller builds as far as I can see. (Again, I haven't tried it much, and don't want to do it injustice by being too categorical.)
Am 04.12.2021 um 22:17 schrieb David Brown:
> On 04/12/2021 20:53, George Neuner wrote: >> On Fri, 3 Dec 2021 21:28:54 -0000 (UTC), Grant Edwards >>> I always had good luck using Cygwin and gnu "make" on Windows to run >>> various Win32 .exe command line compilers (e.g. IAR). I (thankfully) >>> haven't needed to do that for several years now... >> >> The problem with Cygwin is it doesn't play well with native Windows >> GCC (MingW et al).
[...]
> I concur with that. Cygwin made sense long ago, but for the past couple > of decades the mingw-based alternatives have been more appropriate for > most uses of *nix stuff on Windows. In particular, Cygwin is a thick > compatibility layer that has its own filesystem, process management, and > other features to fill in the gaps where Windows doesn't fulfil the > POSIX standards (or does so in a way that plays badly with the rest of > Windows).
The problem is that both projects, Cygwin and MinGW/MSYS, provide much more than just a compiler, and in an incompatible way, which is probably incompatible with what your toolchain does, and incompatible with what Visual Studio does. "-Ic:\test" specifies one path name for Windows, but probably two for a toolchain with Unix heritage, where ":" is the separator, not a drive letter. Cygwin wants "-I/cygdrive/c" instead, (some versions of) MinGW want "-I/c". That, on the other hand, might be an option "-I" followed by an option "/c" for a toolchain with Windows heritage. The problem domain is complex, therefore solutions need to be complex. That aside, I found staying within one universe ("use all from Cygwin", "use all from MinGW") to work pretty well; when having to call into another universe (e.g. native Win32), be careful to not use, for example, any absolute paths. Stefan