Skip to content

Fix automake infinite loop#4

Open
drok wants to merge 2 commits intomainfrom
fix-automake-infinite-loop
Open

Fix automake infinite loop#4
drok wants to merge 2 commits intomainfrom
fix-automake-infinite-loop

Conversation

@drok
Copy link

@drok drok commented Oct 18, 2023

When --dry-running autoconf projects, the default make invocation flags, along causes the dry-run process to enter an infinite loop where the Makefile is updated, by running config.status, which requires reconfiguration.

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.

Marked WIP because of these issues:

  1. adding "all" as a target makes the assumption that "all" is the default target, which may not be true, especially in non-automake projects.
  2. The workaround for recursive sub-makes only works if automake is in use. Otherwise, another way to pass --assume-old to sub-makes should be found.

The problem was reported at StackOverflow, and this is my solution to that question.

This commit changes the default makefile.dryrunSwitches flags as documented above.

Originally proposed upstream as microsoft#500

drok and others added 2 commits October 13, 2023 15:14
'all' is a well-known name

The default target must be explicitly named on the dry-run command line,
because of [Remaking Makefiles](https://www.gnu.org/software/make/manual/html_node/Remaking-Makefiles.html)
rule of GNU Make. In order to avoid remaking makefiles during a dry-run,
the makefile must also be given as an explicit target.

If the makefile were given as the only target, the dry-run would not
mean 'dry-run the default target', but 'dry-run nothing'. Thus, if the
makefile must be given as a target, the default target must be made
explicit, and thus is must be defined.

Also, previously 'all' was added to the make invokation for listing
targets. This would prevent the .DEFAULT_GOAL to be correctly set as the
Makefile declared, and instead be set to the value given on the command
line. An explicit target is no longer given on the --print-data-base
invocation, so the output .DEFAULT_GOAL will be the genuine default goal.

This commit makes currentTarget always defined, with the default value
'all'; this is a reasonable, well-known default.
The top makefile in projects that use autotools and automake contains a
rule to remake the makefile itself when the configuration changes
(configure.ac).

Even when dry-running, GNU make regenerates the makefile, in a bid to
generate a 'correct' dry-run output.

VScode needs to add --always-make in order to get a complete view of the
dry-run. Without it, it would only get the commands needed for outdated
targets.

These two behaviours combined cause a naive 'make --dry-run --always-make'
to continuously remake the Makefile. In order to avoid this infinite loop,
make must be instructed as in the "Remaking Makefiles" man page, to
avoid remaking the makefile. This is done by adding two options:
  --assume-old=Makefile, and
  Makefile (ie, target).

Make requires the Makefile to be explicitly specified as target, otherwise
it ignores the --assume-old=Makefile option.

Furthermore, Makefiles generated by automake cause the top invocation to
be a recursive sub-make invocation. On recursive makes, make itself calls
submake without passing the --assume-old option, thus breaking the combo
required for the sub-make to avoid remaking the makefile. As a result,
automake Makefiles need one more workaround. The --assume-old option must
be manually passed to sub-make via the AM_MAKEFLAGS, which is always
appended to sub-make's command line.

This commit implements the above incantation to enable automake Makefiles
to be dry-run without an infinite loop.

Additionally, the makefilePath, makeDirectory, updatedMakefilePath and
updatedMakeDirectory variables are made to store the respective setting,
rather then re-purposing them to store the corresponding resolved,
absolute paths. makefilePath cannot be undefined because for dry-running
the name of the makefile must always be supplied on the make commandline.
@drok drok mentioned this pull request Oct 18, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant