I'd like to "link" a C source #define to a makefile variable - to be able to define or undefine via make.
For example,
// my C source file
#ifdef SOMETHING
// do something
#else
// do something else
#endif
Then SOMETHING would be triggered from the make command line:
make something=true
This works for my makefile:
ifeq ($(something),true)
COMPILER_FLAGS += -DSOMETHING
endif
I am wandering, is this the proper way?
Is there an easier or better solution?
The following is not an ad-hoc solution but it requires you to use a make library: gmtt is the GNUmake table toolkit and was designed more or less with such a use case in mind. You can specify tables which you access with a (modest) form of the select statement known from a relational database. This way you escape convoluted ifeq hierarchies and can introduce build parameters rather easily. Of course one can argue that in my example three very simple ifeq paragraphs would have done also, without introducing a make library, but the advantage of the below solution is that tables force you to a separation of concerns and you don't need to worry that someone has sneaked in an addtional functionality in one of the ifeq's aside from generating #define's.
include gmtt/gmtt.mk
#define a 3-column table; there must be no empty cells (put in a comment)!
define project-defs =
3
type-1 APPLES 15
type-1 ORANGES 0
type-1 PEARS /*empty*/
type-2 APPLES 0
type-2 ORANGES 15
type-2 PEARS /*empty*/
type-3 APPLES 15
type-3 ORANGES 15
endef
#select column 2 and 3 of the above table and create a C-source line with '#define' from them
define project-defines :=
$(call map-select,2 3,$(project-defs),$$(call str-eq,$$1,$(project-type)),\#define $$1 $$2$$(newline))
endef
$(info Building with the following defines: $(project-defines))
$(file > project_def.h,$(project-defines))
With this makefile you now can invoke make project-type=type-1 and so on and the makefile will generate for you project_def.h which will serve as a normal header file instead of invisible defines in the command line.
Related
I would like to use a preprocessor macro defined in one of my source files as a variable in my Makefile. The simplest way to do so would be to use sed like program to find the macro. However the macro is defined based on a condition (other macros), so a simple search for that macro can yield multiple results. This snippet may illustrate it:
#ifdef FOO
#define BAR 10
#else
#ifdef BAZ
#define BAR 50
#else
#define BAR 100
#endif
#endif
How can the Makefile know if FOO or BAZ are defined in the source files to eventually get the correct value of BAR during compilation?
A different take on that issue is to question oneself why a C constant has to play a role outside of the compilation process. One then often finds that this constant (and many others) are truly not a constant part of the C program but a configuration item. As such it is worth to consider if such configuration items should really be hidden inside a C source file or if it is cleaner to generate C source out of a better suited configuration format. In my experience every project sligthly larger than HelloWorld starts to profit very soon from a clean&controlled separation of concerns (source editing vs. config editing).
You may use cpp (or cc -E) for that. The easiest way to use it is to put all macro definitions into a header file, say macros.h. You need also a special C file (say macros.c), where you only use your macros. Choose a prefix which is easy to filter, like:
#include "macros.h"
valueOf_FOO = FOO
valueOf_BAR = BAR
Now, you can extract your macro values with
$(CC) -E $(CFLAGS) $(OTHER_CFLAGS) macros.c | grep '^valueOf_' | sed 's/valueOf_//'
This will print
FOO = FOO
BAR = 50
to the terminal.
What's standard practice for user configuration of a project using only Make? Specifically, I'd like to know how what's normally done to have the user communicate things like library and install locations without using a utility like autotools or CMake. I've seen some projects leave certain variables blank in the Makefile that are filled in by the user. This feels nonideal in the sense that if the Makefile itself is under version control, this will register as a change and one that's likely should not be checked in. It also makes it awkward to have multiple configurations lying around. It seems like there should be a way to have an external configuration file with the possible flags for the user to change, but I'm not sure how to accomplish this or whether it's normal practice.
Edit 1
Alright, this is one attempt at the goal, but I'm still interested in whether there's a better way to do this. For the Makefile
ifndef CONFIG
ifeq (,$(findstring config,$(MAKECMDGOALS)))
$(error Usage: make CONFIG=<configuration> or make config)
endif
else
include ${CONFIG}
endif
all:
$(info Name is $(Name))
$(info Age is $(Age))
$(info Profession is $(Profession))
#:
.PHONY: config
config:
$(info Name :=)
$(info Age :=)
$(info Profession :=)
#:
Then, running make gives
Makefile:3: *** Usage: make CONFIG=<configuration> or make config. Stop.
Running make config gives
Name :=
Age :=
Profession :=
Ostensibly, we run something like make config > bob.config and then fill in details such as
Name := Bob
Age := 23
Profession := Carpenter
Then, running make CONFIG=bob.config all gives
Name is Bob
Age is 23
Profession is Carpenter
This more or less does what I want, but, again, I'd like to know if there's a better or more standard way to handle this.
I think you've some choices.
First: you create some include files with different parameters/options and make will include depends on a param:
# Makefile
ifdef v1
include vars.v1
endif
ifdef v2
include vars.v2
endif
If you want v1: make -Dv1.
Second: you include the definitions into Makefile:
# Makefile
ifdef v1
destdir=...
flags=...
endif
ifdef v2
destdir=...
flags=...
endif
Third: you'll pass your parameters via CLI but I think it's complicated if you've many parameters (maybe you can use shell scripts or aliases).
I have a rule something, that works on the variable VAR. I also have another rule something-all, that needs to run something, with VAR set to each value in vars.
vars = hello world
something:
echo $(VAR)
something-all:
$(foreach VAR,$(vars),something)
This doesn't quite work, I get
noob#work:~/Desktop$ make something-all
something something
make: something: No such file or directory
make: *** [something-all] Error 1
It should probably print hello\nworld.
I used to do this with wildcard rules by retrieving VAR from %, but got the feeling that was the wrong way to do it. This looked like this:
vars = hello world
all: $(foreach VAR,$(vars),something-$(VAR))
something-%:
echo $*
The below should fix your problem
Using foreach (Tried on GNU Make 3.80 on sparc-solaris 2.8 and windows)
vars = hello world
something:
echo $(VAR)
something-all:
$(foreach i, $(vars), $(MAKE) something VAR=$i || exit 1;)
Using shell for-loop (Tried on GNU Make 3.80 and cc make on sparc-solaris 2.8)
vars = hello world
something:
echo $(VAR)
something-all:
for i in $(vars); do $(MAKE) something VAR=$$i || exit 1; done
TL;DR: If you want to program make, drop GNU Make in favor of BSD Make.
This is a personal recommendation. While BSD Make seems more limited than GNU Make, as it offers less programming facilities, it is much easier to program and has a few unique killer features. This is why I propose a solution with GNU Make and another solution for BSD Make:
Doing it in GNU Make
Using GNU Make, you can write a macro to define a target. The canonical way to define a sequence in a Makefile is to add the steps of the sequence as dependencies to a target, as reflected by the snippet below:
vars= hello world
define something_t =
something: something-$(1)
something-$(1):
#echo $(1)
endef
$(foreach _,$(vars),$(eval $(call something_t,$_)))
It is recommended to use this organisation (rather than defining just one target), because you can work on it to make the task easily resumable if you interrupt the sequence. A Makefile describes a job whose advancement is entirely described by the state of the file system. A task is then easily resumable, if each step is associated to a file, usually a compilation object but sometimes also an empty file which is touch'ed to indicate that important checkpoints have been passed.
Using an auxiliary macro is a flexible solution that can be adapted to more complicated tasks than just echoing a name. Note that this does work with newest versions of GNU Make (4.1). On GNU Make 3.81, you should remove the equal sign from the macro definition.
Adapting your example for BSD Make
If this is an option for you, I recommand dropping the use of GNU Make and replace it by BSD Make, which is way easier to program: it has a short and to the point documentation, while the documentation of GNU Make is very verbose and somewhat unclear, BSD Make has industrial-strength examples of complex rulesets (FreeBSD Build system or BSD Owl), and it has a simple and predictable macro language.
vars= hello world
something:
.for _var in ${vars}
echo ${_var}
.endfor
This can evolve to support more complicated tasks, just by replacing the echo by the adapted commands, or using intermediary steps.
Allow the user to override some tasks, also in BSD Make
In this slightly more advanced variation, we allow the user to override our own recipes for building targets something-hello and something-world.
For each item in our list, a target something-* is created it if it does not already exist, and added to the dependencies of something. The whole operation of defining these targets only happens if something has been left undefined. Therefore, users of these macros can:
Override the recipes for something-hello and something-world
Override the full procedure bound to something.
Implementing such customisation possibilities is mandatory if we want to write useful, reusable, macros for Make. Unluckily, customisation of this sort is nearly impossible in GNU Make.
vars = hello world
.if!target(depend)
.for _var in ${vars}
.if!target(something-${_var})
something-${_var}:
echo ${_var}
.endif
something: something-${_var}
.endfor
.endif
Here's one way to do it:
VARS := hello world
THINGS := $(addprefix something-, $(VARS))
allthings: $(THINGS)
something-%:
echo $*
It should be no surprise that
vars := hello world
something-all:
$(foreach VAR,$(vars),something)
tries to run something something. That's exactly what the foreach expands to, since you don't reference VAR in the third expression.
All you need to do is reference VAR and use a command such as echo:
vars := hello world
something-all:
$(foreach VAR,$(vars),echo $(VAR);)
$ make
echo hello; echo world;
hello
world
Note how chaining the commands with a semicolon avoids forking several shells or -- GASP! -- recursive make invocations. It doesn't get more performant than that.
Alternatively, if your command accepts several somethings as arguments,
vars := hello world
something-all:
echo $(foreach VAR,$(vars),$(VAR))
$ make
echo hello world
hello world
But that is equivalent to the super simple echo $(vars). So it might pay off to think outside the box trying to change your requirements to make this simple solution work.
Alright I am stuck on this and I have no idea what I am doing wrong. Everything was going great working on a more complicated makefile but then all of a sudden I got the "Missing separator" error. I was able to isolate it down to a very simple scenario:
test.mk
define push_dir
$(info ${1})
endef
define pop_dir
$(info ${1})
endef
define include_submake
$(call push_dir,${1})
$(call pop_dir,${1})
endef
Simple
include test.mk
INITIAL_SUBMAKE:= includeme.mk
$(call include_submake,${INITIAL_SUBMAKE})
process:
#echo Processed...
And the output:
C:\project>make -f Simple process
includeme.mk
includeme.mk
Simple:4: *** missing separator. Stop.
includeme.mk does not actually exist. I have no idea what is going wrong here I have tried a multitude of things. If I surround the call to include_submake in info like so:
$(info $(call include_submake,${INITIAL_SUBMAKE}))
The missing separator error goes away. Also If in the include_submake define I only call one of the functions it works fine. Additionally if I directly call the functions instead of calling them include_submake it works as well:
include test.mk
INITIAL_SUBMAKE:= includeme.mk
$(call push_dir,${INITIAL_SUBMAKE})
$(call pop_dir,${INITIAL_SUBMAKE})
process:
#echo Processed...
C:\project>make -f Simple process
includeme.mk
includeme.mk
Processed...
I feel like I'm overlooking something fundamental here. Thanks for your help.
The missing separator error happens because of a non-empty return value of include_submake, which is a single line feed character in your case. Make only permits whitespace characters (that is, a space or tab) to occur in an expression which is not assumed to be a part of some rule or another directive.
Rewrite your functions using plain-old Make variable assignment and the error should go away:
push_dir = \
$(info $1)
pop_dir = \
$(info $1)
include_submake = \
$(call push_dir,$1) \
$(call pop_dir,$1)
UPD.: define vs plain old variable assignment
Answering to a question from the first comment. Personally I would prefer using define directive in several cases.
Using with eval function
As the GNU Make manual suggests, define directive is very useful in conjunction with the eval function. Example from the manual (emphasis is mine):
PROGRAMS = server client
server_OBJS = server.o server_priv.o server_access.o
server_LIBS = priv protocol
client_OBJS = client.o client_api.o client_mem.o
client_LIBS = protocol
# Everything after this is generic
.PHONY: all
all: $(PROGRAMS)
define PROGRAM_template
$(1): $$($(1)_OBJS) $$($(1)_LIBS:%=-l%)
ALL_OBJS += $$($(1)_OBJS)
endef
$(foreach prog,$(PROGRAMS),$(eval $(call PROGRAM_template,$(prog))))
$(PROGRAMS):
$(LINK.o) $^ $(LDLIBS) -o $#
clean:
rm -f $(ALL_OBJS) $(PROGRAMS)
Generator templates
Verbatim variables fit perfectly for cases when you want to generate a file from GNU Make. For example, consider generating a header file based on some information from Makefile.
# Args:
# 1. Header identifier.
define header_template
/* This file is generated by GNU Make $(MAKE_VERSION). */
#ifndef $(inclusion_guard)
#define $(inclusion_guard)
$(foreach inc,$($1.includes),
#include <$(inc).h>)
/* Something else... */
#endif /* $(inclusion_guard) */
endef
# 1. Unique header identifier.
inclusion_guard = \
__GEN_$1_H
# Shell escape.
sh_quote = \
'$(subst ','"'"',$1)'
foo.includes := bar baz
HEADERS := foo.h
$(HEADERS) : %.h :
#printf "%s" $(call sh_quote,$(call header_template,$(*F)))> $#
Extended Make syntax
In our project we use our own build system called Mybuild, and it is implemented entirely on top of GNU Make. As one of low-level hacks that we used to improve the poor syntax of the builtin language of Make, we have developed a special script which allows one to use extended syntax for function definitions. The script itself is written in Make too, so it is a sort of meta-programming in Make.
In particular, one can use such features as:
Defining multiline functions without the need to use backslash
Using comments inside functions (in plain-old Make comments can only occur outside variable assignment directives)
Defining custom macros like $(assert ...) or $(lambda ...)
Inlining simple functions like $(eq s1,s2) (string equality check)
This is an example of how a function can be written using the extended syntax. Note that it becomes a valid Make function and can be called as usual after a call to $(def_all).
# Reverses the specified list.
# 1. The list
# Return:
# The list with its elements in reverse order.
define reverse
# Start from the empty list.
$(fold ,$1,
# Prepend each new element ($2) to
# the result of previous computations.
$(lambda $2 $1))
endef
$(def_all)
Using these new features we were able to implement some really cool things (well, at least for Make :-) ) including:
Object-Oriented layer with dynamic object allocation, class inheritance, method invocations and so on
LALR parser runtime engine for parsers generated by GOLD Parser Builder
Modelling library with runtime support for models generated with EMF
Feel free to use any part of the code in your own projects!
I ran into the same problem. I inserted 'tab', deleted 'tab', reinserted to be sure. Same error message.
But, I did all of this inside of XCodem which to my surprise inserted white spaces, not '\t'. Once I used different editor these 'phantom' errors went away.
HTH...
I am a newbie with make, and I'm trying to use it to deploy some javascript files. I have been struggling with the following problem for quite a while with no success.
My directory structure is as follows:
helpers/
foo/
foo.js
test/
test1.js
test2.js
...
bar/
bar.js
test/
test1.js
test2.js
...
other helpers...
distrib/
files ready for distribution
other stuff...
My makefile should build, among other things, the helpers. For each helper foo I want to produce, under distrib, the following files: foo-version.js, foo-version-uncommented.js, foo-version-packed.js and foo-version-tests.zip. The first three are obtained by foo.js, respectively as a copy, by stripping the comments and by running a javascript minifier. I already have commands to perform these tasks.
The version number should be read in the comments of the file itself, which I can easily do with
def version
$(shell cat $1 | grep #version | sed -e"s/.*version *//")
endef
My problem is that targets like foo-version.js are dynamic, since they depend on the version number, which is read while running make. I have tried to use patterns, but I have failed to make this work. The problem is that something like this will not work
helpers := foo bar
helpers: $(helpers)
$(helpers): %: $(call version, %)
because the second % is not expanded in the macro call, but it is used literally.
I need to be able to do make helpers to build all helpers or make foo to build a single one. A second step would be to delete all files under distrib with a lower version number. Any ideas how to do this?
As a side question: would a task like this be easier with a different build tool? I'm not expert and it may be worth the pain to learn something else.
In GNU make, you can use the functions call and eval, usually in combination with foreach:
%-version.js: %.js
# your recipe here
%-version-uncommented.js: %.js
# your recipe here
%-version-packed.js: %.js
# your recipe here
%-version-tests.zip: %.js
# your recipe here
versions_sfxs := .js -uncommented.js -packed.js -tests.zip
helpers := $(shell ls $(HELPERSDIR))
define JS_template
helpers: $(1)-version$(2)
endef
$(foreach h, $(helpers), \
$(foreach sfx, $(versions_sfxs), \
$(eval $(call JS_template,$(h),$(sfx)) \
) \
)
This code is untested but it gives the general idea. Expect to spend an afternoon debugging your use of spaces, tabs, dollar signs and backslashes, just like in shell scripts. Search Stack Overflow for make eval or something for more details and pointers.
At the end I decided to write my own build tool PHPmake. It has a syntax reminiscent of standard makefiles, but it is already more powerful than standard make, and it is easily extensible as the makefiles themselves are written in plain PHP.
No more debugging spaces, tabs and dollar signs! :-)