gnu-makeautotoolsautoconf

Automake/autotools and using "--dry-run" and "--always-make" for make


I stumbled upon an issue when I recently switched to VSCode as editor.

I have several projects that have a full (medium-complex+) autotool setup and they all work fine. However I discovered that the makefile plugin for VSCode in order to initialize itself (and finding all dependencies and targets) starts by running

make --dry-run --always-make

as first time initialization. This throws the makefile (or actually the re-config) into an endless loop re-running "configure" (since the targets are never resolved to disk).

I have also confirmed this behavior with the smallest possible autoconf/automake setup. I can also kind-of understand why this happens (and it seems make have an internal way to discover this exact situation with the special variable MAKE_RESTARTS that could possible be used to detect a cyclic behavior)

Is there a known best-practice workaround ? or is it even a reasonable expectations that these two options in combination should work? (Good to have a second opinion before I go down the rabbit-hole of reminding myself of all the details I forgot about the magical land of autotools)?


Solution

  • The problem

    The infinite loop when dry-running autoconf projects is due to GNU Make's documented behaviour. It's not a bug, or version-specific behaviour:

    1. GNU Make remakes files makefiles unconditionally (defined as files it reads rules from, eg, given a statement "include blah.inc", the file blah.inc is a "makefile"). Remaking Makefiles says "--dry-run does not prevent updating of makefiles, because an out-of-date makefile would result in the wrong output for other targets"

    2. Automake creates rules for updating the Makefile when Makefile.am changes, and when configure.ac changes.

    3. In recursive builds, when make spawns a sub-make process, it does not pass "--assume-old" arguments to child make via the MAKEFLAGS mechanism. This is described in Communicating Options to a Sub-make

    Together, these three behaviours guarantee that an autoconf+automake project will end up in an infinite loop when "--dry-run" as the vscode "Makefile Tools" extension does by default when projects are loaded.

    Solution

    The solution is to change the command line options as follows (settings.json excerpt):

    "makefile.dryrunSwitches": [
            "--always-make",
            "--print-directory",
            "--dry-run",
            "--assume-old=Makefile",
            "Makefile",
            "all",
            "AM_MAKEFLAGS=--assume-old=Makefile Makefile"
    ],
    

    If using the GUI, each of these strings must appear on one line. Specifically, AM_MAKEFLAGS=--assume-old=Makefile Makefile is one argument. In a bash command line, this would be quoted like AM_MAKEFLAGS="--assume-old=Makefile Makefile"

    Explanation

    The --assume-old=Makefile means the file named 'Makefile', which is also a target, will not be considered for remake, in spite of --always-make, per Avoiding Compilation.

    Since the file Makefile is also a file (ie, used to read rules from), it would be re-made in spite of "--dry-run". This behaviour is overriden by specifying Makefile as a target, in addition to all, as documented in Remaking Makefiles: "on occasion you might actually wish to prevent updating of even the makefiles. You can do this by specifying the makefiles as goals in the command line as well as specifying them as makefiles. When the makefile name is specified explicitly as a goal, the options ‘-t’ and so on do apply to them."

    Since on recursive builds the --assume-old argument would not be passed to the sub-make via make's own MAKEFLAGS environment variable mechanism, the AM_MAKEFLAGS variable is used to perform override this behaviour. This works because automake projects add the value of this variable on every make invocation.

    Note 1. If your project defines the AM_MAKEFLAGS variable in Makefile.am, you'll need to adjust it to include the needed --assume-old incantation. One way would be:

    AM_MAKEFLAGS="--project-specific-make-flags $(DRYRUN_MAKEFLAGS)"
    

    Then, use use DRYRUN_MAKEFLAGS="--assume-old=Makefile Makefile" in settings.json.

    Note 2. Which of the settings.json files you put the "makefile.dryrunSwitches" settings matters. If you work on autoconf as well as cmake projects, you might want it in each project's settings.json. I have it set at the user level, as I work on very few cmake projects, and then I can override in at the project level.

    Note 3. Arguably, the default setting for makefile.dryrunSwitches in the Makefile Tools extension is wrong, and the extension can be considered buggy. However, it does not claim to be compatible with automake, so fixing this would require adding automake compatibility. See my pull request #500

    Update: This problem is avoided in the VScode extension "Autotools Integration" by using an internal, derived make utility with modified dry-running behaviour. It is a much more capable replacement for Microsoft's "Makefile Tools" when managing Autotools projects (but handwritten Makefiles are a crap-shoot with both extensions). I am the author of "Autotools Integration".