1. 16

  2. 5

    Using the assignment operator = will instead override CC and LDD values from the environment; it means that we choose the default compiler and it cannot be changed without editing the Makefile.

    This is true, but really, if you want to ensure variables are set in a Makefile, pass them as overrides (make ... CC=blah ...), don’t set them in the environment. The environment is a notoriously fragile and confusing way to specify all these things. (They certainly don’t work with the GCC and binutils stuff I work with on a regular basis!)

    My advice for Makefile is be explicit. It’s tedious and boring, but so much easier to debug.

    1. 4

      The reason that setting things in the environment is fragile is because people follow advice to ignore the environment. It’s very useful for cross compilation to simply set the appropriate environment and go.

      1. 1

        There’s also no easy way to pass arguments and options to a makefile except through environmental variables. You can also play games with the target, but there’s only so much you can do with that.

        1. 2

          I don’t believe that’s true. You can also pass macro definitions as arguments to make; e.g., make install PREFIX=/opt/tools

          1. 1

            Yes, overrides passed on the command line can be arbitrary expansions.

            % cat Makefile
            STUFF = 1 2 3 4
                    @echo $(FOO)
            % make 'FOO=$(firstword $(STUFF))'
            1. 0

              Yeah, but environmental variables are turned into make variables in the same way as variables after the make command. The only difference is that they also get placed in the environment of subcommands.

              1. 2

                I’m reasonably sure that is not true either. From my reading of the manual, an explicit assignment of a macro within the Makefile will override a value obtained from the environment unless you pass the -e flag to make. The manual suggests the use of this flag is not recommended. In contrast, a macro assignment passed on the command line as an argument will override a regular assignment within the Makefile.

                Additionally, some macros receive special handling; e.g., $(SHELL), which it seems is never read from the environment as it would conflict with the common usage of that environment variable by user shells.

                1. 2

                  As far as I can tell, they both get placed in the environment of subcommands. The manual is (as per many GNU manuals) unclear on the matter: “When make runs a recipe, variables defined in the makefile are placed into the environment of each shell.” My reading is that anything set in Make should be passed through, but this does not appear to be the case.

                  % cat Makefile
                  FOO = set-from-make
                          @sh ./t.sh
                  % cat t.sh
                  echo "FOO is '${FOO}'"
                  % make
                  FOO is ''
                  % FOO=from-env make
                  FOO is 'set-from-make'
                  % make FOO=from-override
                  FOO is 'from-override'
                  1. 1

                    IMO the GNU make manual is pretty clear on this.


                    Variables can get values in several different ways:

                    • You can specify an overriding value when you run make. See Overriding Variables.
                    • You can specify a value in the makefile, either with an assignment (see Setting Variables) or with a verbatim definition (see Defining Multi-Line Variables).
                    • Variables in the environment become make variables. See Variables from the Environment.
                    • Several automatic variables are given new values for each rule. Each of these has a single conventional use. See Automatic Variables.
                    • Several variables have constant initial values. See Variables Used by Implicit Rules.


                    An argument that contains ‘=’ specifies the value of a variable: ‘v=x’ sets the value of the variable v to x. If you specify a value in this way, all ordinary assignments of the same variable in the makefile are ignored; we say they have been overridden by the command line argument.


                    Variables in make can come from the environment in which make is run. Every environment variable that make sees when it starts up is transformed into a make variable with the same name and value. However, an explicit assignment in the makefile, or with a command argument, overrides the environment. (If the ‘-e’ flag is specified, then values from the environment override assignments in the makefile. See Summary of Options. But this is not recommended practice.)

                    1. 1

                      Yes, I don’t disagree with any of this and it’s consistent with usage. My point was about variables getting into the environment of shell commands in recipes. The wording suggests all variables are put into the environment, but based on the first result in the example that’s clearly not the case.

                      1. 1

                        Oh I see. The manual is less clear on that point:

                        By default, only variables that came from the environment or the command line are passed to recursive invocations. You can use the export directive to pass other variables.

                        It should probably say “passed to child processes through the environment” or something similar.

                        $ cat Makefile
                        export VAR2='hi'
                                echo $$VAR1
                                echo $$VAR2
                        $ make
                        echo $VAR1
                        echo $VAR2
        2. 4

          Some of the things in the blog post like := or ?= don’t appear in the posix spec for make. Are they GNU’isms?

          1. 7

            Yes, along with $(shell ... ). The author should have mentioned he was using GNUMake.

            1. 1

              := is almost mandatory for makefiles. If you have a shell expansion it will get run every time unless you use :=. Many of the extensions in Gnu make are simply unreproducable in posix make.

            2. 4

              While it is usually safe to assume that sensible values have been set for CC and LDD, it does not harm to set them if and only if they are not already set in the environment, using the operator ?=.

              I don’t think that the ?= accomplishes that, at least with GNU make. I don’t have CC set in my environment, but GNU make uses a default of CC = cc anyway. So, as far as I know, CC ?= gcc will never be helpful in GNU make, will it?

              There’s a list of variables set by GNU make here, but you can check your own setup with make -p.

              Question: I do a lot of development with MinGW and MSYS. This environment does not use cc by default. So how can I write a Makefile that will work with MSYS and a normal Linux/macOS setup without forcing CC=gcc? Do I set CC only if ifeq (default,$(origin CC))?

              1. 1

                I think you’re right; this would be superfluous in GNU make (and also bmake – I’m not sure about other makes). AFAICT ?= is not posix, so doing this would make the makefile less portable with no obvious advantage.

              2. 1

                IMO an important best-practice for Makefiles is automatic dependency generation, rather than listing dependencies explicitly. There are two main ways to achieve this:

                1. Use a makefile generator (Old school would include automake and imake/xmkmf, but cmake seems to be the most popular these days)
                2. Use a dependency generator (e.g. makedepend)
                3. Use a compiler that can produce make-compatible dependency files as part of the build

                My preferred technique when using gcc and GNU make is #3 – it’s as easy as adding three lines to my Makefile:

                CFLAGS += -MMD -MP
                DEP_FILES = $(patsubst %.o,%.d,$(OBJS))
                -include $(DEP_FILES)
                1. -1

                  We can also try xmake. https://github.com/tboox/xmake