diff mbox series

[v3] Makefile: fix use of many br2-external trees

Message ID 20220920194645.670432-1-yann.morin.1998@free.fr
State Not Applicable
Headers show
Series [v3] Makefile: fix use of many br2-external trees | expand

Commit Message

Yann E. MORIN Sept. 20, 2022, 7:46 p.m. UTC
The top level Makefile in buildroot has a recursive rule which causes
the appearance of a hang as the number of directories in BR2_EXTERNAL
increases. When the number of directories in BR2_EXTERNAL is small, the
recursion occurs, but make detects the recursion and determines the
target does not have to be remade. This allows make to progress.

This is the failing rule:

    define percent_defconfig
    # Override the BR2_DEFCONFIG from COMMON_CONFIG_ENV with the new defconfig
    %_defconfig: $(BUILD_DIR)/buildroot-config/conf $(1)/configs/%_defconfig outputmakefile
        @$$(COMMON_CONFIG_ENV) BR2_DEFCONFIG=$(1)/configs/$$@ \
                $$< --defconfig=$(1)/configs/$$@ $$(CONFIG_CONFIG_IN)
    endef
    $(eval $(foreach d,$(call reverse,$(TOPDIR) $(BR2_EXTERNAL_DIRS)),$(call percent_defconfig,$(d))$(sep)))

The rule for %defconfig is created for each directory in BR2_EXTERNAL.
When the rule is matched, the stem is 'defconfig_name'. The second
prerequisite is expanded to $(1)/configs/defconfig_name_defconfig. The
rule, and all of the other rules defined by this macro, are invoked
again, but the stem is now $(1)/configs/defconfig_name_defconfig. The
second prerequisite is now expanded to
$(1)/configs/($1)/configs/defconfig_name_defconfig. This expansion
continues until make detects the infinite recursion.

With up to 5 br2-external trees, the time is very small, so that it is
not noticeable. But starting with 6 br2-external trees, the time is
insanely big (so much so that we did not even let it finish after it ran
for hours); see timings toward the end of the commit log.

One of the rationale behind this code, is that we want the defconfig
files from br2-external trees further down the list, to override
defconfig files from those earlier in the list, even overriding the
defconfig files from Buildroot itself.

We fix that by only creating explicit rules for defconfig files.

To keep the promise that later defconfig files override previous ones
(which we do document in our manual), we need to memorise what defconfig
file we already created a rule for, and only create a rule for the
first-seen-in-reverse-order (aka the last one) defconfig.

Since some people appear to be bold enough (or insane enough?) to use
defconfig files that start with a dot, also handle those explictly.

Fixes: #14996

Reported-by: David Lawson <david.lawson1@tx.rr.com>
Signed-off-by: Yann E. MORIN <yann.morin.1998@free.fr>

----
How to test many br2-external trees:

$ for i in $(seq 1 1000); do
    mkdir -p br2-external-${i}/configs
    touch br2-external-${i}/{Config.in,external.mk}
    echo "name: BR_TEST_${i}" >br2-external-${i}/external.desc
    touch br2-external-${i}/configs/foo{,_${i}}_defconfig
done

$ time make V=1 \
    BR2_EXTERNAL="$(for i in $(seq 1 N); do printf '%s ' "$(pwd)/foo²/br2-external-${i}"; done)" \
    foo_1_defconfig

Timings ('real' as reported by 'time'):

   N    Before     After
   1    0.325s     0.328s
   5    0.957s     0.358s
   6    n/a        0.394s
  10    n/a        0.432s
 100    n/a        1.851s
1000    n/a        15.887s

So, not only does that work for a large number of br2-external trees, it
is also a little bit faster when using just a few.

---
Changes v2 -> v3:
  - fix order of comment documenting $1 and $2  (David)
  - don't use immediate assignment :=, use simple assignment
  - add timing information and a way to reproduce

Changes v1 -> v2:
  - keep comment
  - fix typo
---
 Makefile | 24 +++++++++++++++++++-----
 1 file changed, 19 insertions(+), 5 deletions(-)

Comments

Thomas Petazzoni Sept. 21, 2022, 6:13 p.m. UTC | #1
On Tue, 20 Sep 2022 21:46:45 +0200
"Yann E. MORIN" <yann.morin.1998@free.fr> wrote:

> The top level Makefile in buildroot has a recursive rule which causes
> the appearance of a hang as the number of directories in BR2_EXTERNAL
> increases. When the number of directories in BR2_EXTERNAL is small, the
> recursion occurs, but make detects the recursion and determines the
> target does not have to be remade. This allows make to progress.
> 
> This is the failing rule:
> 
>     define percent_defconfig
>     # Override the BR2_DEFCONFIG from COMMON_CONFIG_ENV with the new defconfig
>     %_defconfig: $(BUILD_DIR)/buildroot-config/conf $(1)/configs/%_defconfig outputmakefile
>         @$$(COMMON_CONFIG_ENV) BR2_DEFCONFIG=$(1)/configs/$$@ \
>                 $$< --defconfig=$(1)/configs/$$@ $$(CONFIG_CONFIG_IN)
>     endef
>     $(eval $(foreach d,$(call reverse,$(TOPDIR) $(BR2_EXTERNAL_DIRS)),$(call percent_defconfig,$(d))$(sep)))
> 
> The rule for %defconfig is created for each directory in BR2_EXTERNAL.
> When the rule is matched, the stem is 'defconfig_name'. The second
> prerequisite is expanded to $(1)/configs/defconfig_name_defconfig. The
> rule, and all of the other rules defined by this macro, are invoked
> again, but the stem is now $(1)/configs/defconfig_name_defconfig. The
> second prerequisite is now expanded to
> $(1)/configs/($1)/configs/defconfig_name_defconfig. This expansion
> continues until make detects the infinite recursion.
> 
> With up to 5 br2-external trees, the time is very small, so that it is
> not noticeable. But starting with 6 br2-external trees, the time is
> insanely big (so much so that we did not even let it finish after it ran
> for hours); see timings toward the end of the commit log.

Wow, insane stuff!

> One of the rationale behind this code, is that we want the defconfig
> files from br2-external trees further down the list, to override
> defconfig files from those earlier in the list, even overriding the
> defconfig files from Buildroot itself.

This is the part I would like to challenge. Why do we want to allow
BR2_EXTERNAL to override defconfigs from the main tree? We do not allow
this for packages, why should we allow it for defconfigs?

To me, allowing the override of defconfigs is actually a bad idea: when
you run "make foo_defconfig", it's no longer really clear which
"foo_defconfig" is really going to be used. Yes, it's well defined, but
it isn't "obvious".

Thomas
Yann E. MORIN Sept. 21, 2022, 6:28 p.m. UTC | #2
Thomas, All,

On 2022-09-21 20:13 +0200, Thomas Petazzoni spake thusly:
> On Tue, 20 Sep 2022 21:46:45 +0200
> "Yann E. MORIN" <yann.morin.1998@free.fr> wrote:
[--SNIP--]
> > One of the rationale behind this code, is that we want the defconfig
> > files from br2-external trees further down the list, to override
> > defconfig files from those earlier in the list, even overriding the
> > defconfig files from Buildroot itself.
> This is the part I would like to challenge. Why do we want to allow
> BR2_EXTERNAL to override defconfigs from the main tree? We do not allow
> this for packages, why should we allow it for defconfigs?

This patch does not change the actual behaviour: we've been allowing
this for the past 6 years, we've documented it; all that patch does is
actually fix using more than 5 br2-external trees.

> To me, allowing the override of defconfigs is actually a bad idea: when
> you run "make foo_defconfig", it's no longer really clear which
> "foo_defconfig" is really going to be used. Yes, it's well defined, but
> it isn't "obvious".

I think initially, it was far simpler to do it that way, since we did
not have a list of defconfig files, and so we (ab)used make ability to
override a rule, to justify a simpler code on our side.

Changing that behaviour is now easier, now that we do have a list of
defconfig files, but if at all, that should be done in a follow-up
patch.

However, the multi br2-external support is there to cover one case: a
br2-xternal tree provides basic support for a board (e.g. by a team in
charge of the ardware support), and a second br2-external provides
defconfig for an actual product. In this case, given a board named
'foo', it is understanble that the harware guys name their defconfig
foo_defconfig, and that the product team also name their defconfig
foo_defconfig. So, I think allowing this overide is not a bad thing.

Regards,
Yann E. MORIN.
Arnout Vandecappelle Sept. 21, 2022, 6:32 p.m. UTC | #3
On 21/09/2022 20:13, Thomas Petazzoni wrote:
> On Tue, 20 Sep 2022 21:46:45 +0200
> "Yann E. MORIN" <yann.morin.1998@free.fr> wrote:
> 
>> The top level Makefile in buildroot has a recursive rule which causes
>> the appearance of a hang as the number of directories in BR2_EXTERNAL
>> increases. When the number of directories in BR2_EXTERNAL is small, the
>> recursion occurs, but make detects the recursion and determines the
>> target does not have to be remade. This allows make to progress.
>>
>> This is the failing rule:
>>
>>      define percent_defconfig
>>      # Override the BR2_DEFCONFIG from COMMON_CONFIG_ENV with the new defconfig
>>      %_defconfig: $(BUILD_DIR)/buildroot-config/conf $(1)/configs/%_defconfig outputmakefile
>>          @$$(COMMON_CONFIG_ENV) BR2_DEFCONFIG=$(1)/configs/$$@ \
>>                  $$< --defconfig=$(1)/configs/$$@ $$(CONFIG_CONFIG_IN)
>>      endef
>>      $(eval $(foreach d,$(call reverse,$(TOPDIR) $(BR2_EXTERNAL_DIRS)),$(call percent_defconfig,$(d))$(sep)))
>>
>> The rule for %defconfig is created for each directory in BR2_EXTERNAL.
>> When the rule is matched, the stem is 'defconfig_name'. The second
>> prerequisite is expanded to $(1)/configs/defconfig_name_defconfig. The
>> rule, and all of the other rules defined by this macro, are invoked
>> again, but the stem is now $(1)/configs/defconfig_name_defconfig. The
>> second prerequisite is now expanded to
>> $(1)/configs/($1)/configs/defconfig_name_defconfig. This expansion
>> continues until make detects the infinite recursion.
>>
>> With up to 5 br2-external trees, the time is very small, so that it is
>> not noticeable. But starting with 6 br2-external trees, the time is
>> insanely big (so much so that we did not even let it finish after it ran
>> for hours); see timings toward the end of the commit log.
> 
> Wow, insane stuff!
> 
>> One of the rationale behind this code, is that we want the defconfig
>> files from br2-external trees further down the list, to override
>> defconfig files from those earlier in the list, even overriding the
>> defconfig files from Buildroot itself.
> 
> This is the part I would like to challenge. Why do we want to allow
> BR2_EXTERNAL to override defconfigs from the main tree? We do not allow
> this for packages, why should we allow it for defconfigs?

  And indeed, this is exactly the reverse of what we would have now. We have two 
pattern rules that match with the same stem. In this case, according to 'info 
make': "'make' will choose the first one found in the makefile." Since we put 
$(TOPDIR) before the externals in the foreach loop, the internal one will be the 
one that gets used.


  However, maybe this is a simpler way to solve the issue:

%_defconfig/real: $(BUILD_DIR)/buildroot-config/conf ...
	...

%_defconfig: %_defconfig/real

Since directories are only removed from the filename when there's no / in the 
pattern, it will only match the wrong thing one level deep. I.e. we still have:

foo_defconfig -> foo_defconfig/real
foo_defconfig/real -> /path/to/buildroot/configs/foo_defconfig
/path/to/buildroot/configs/foo_defconfig ->
	/path/to/buildroot/path/to/buildroot/configs/foo_defconfig/real

but there it terminates, because that last one doesn't match %_defconfig nor 
%_defconfig/real (because of the / in the latter).

  Of course, I haven't tried this, I may be talking rubbish :-)


  Regards,
  Arnout


> To me, allowing the override of defconfigs is actually a bad idea: when
> you run "make foo_defconfig", it's no longer really clear which
> "foo_defconfig" is really going to be used. Yes, it's well defined, but
> it isn't "obvious".
> 
> Thomas
Yann E. MORIN Sept. 21, 2022, 6:57 p.m. UTC | #4
Arnout, All,

On 2022-09-21 20:32 +0200, Arnout Vandecappelle spake thusly:
> On 21/09/2022 20:13, Thomas Petazzoni wrote:
> >On Tue, 20 Sep 2022 21:46:45 +0200
> >"Yann E. MORIN" <yann.morin.1998@free.fr> wrote:
[--SNIP--]
> >>One of the rationale behind this code, is that we want the defconfig
> >>files from br2-external trees further down the list, to override
> >>defconfig files from those earlier in the list, even overriding the
> >>defconfig files from Buildroot itself.
> >This is the part I would like to challenge. Why do we want to allow
> >BR2_EXTERNAL to override defconfigs from the main tree? We do not allow
> >this for packages, why should we allow it for defconfigs?
>  And indeed, this is exactly the reverse of what we would have now. We have
> two pattern rules that match with the same stem. In this case, according to
> 'info make': "'make' will choose the first one found in the makefile." Since
> we put $(TOPDIR) before the externals in the foreach loop, the internal one
> will be the one that gets used.

As discussed on IRC: except we do not put TOPDIR first; we do:
    $(call reverse,TOPDIR BR2_EXTERNALS)

so the last external does win.

I.e. that patch does not change the current behaviour; it just fixes the
use of more than 5 br2-extenal trees at once.

Sorry, I may not have been clear about the explanations in my commit
log: the rationale part refers to the current code, not the change I
did. Whoever applies it can amend as they see fit, or I can respin if
needed.

Oh, and by the way, I got that privately, so I'll paste it here so that
patchwork catches it:

Tested-by: David Lawson <david.lawson1@tx.rr.com>

Regards,
Yann E. MORIN.
diff mbox series

Patch

diff --git a/Makefile b/Makefile
index ec7c034ac1..9d4b1626ae 100644
--- a/Makefile
+++ b/Makefile
@@ -1010,13 +1010,27 @@  oldconfig syncconfig olddefconfig: $(BUILD_DIR)/buildroot-config/conf outputmake
 defconfig: $(BUILD_DIR)/buildroot-config/conf outputmakefile
 	@$(COMMON_CONFIG_ENV) $< --defconfig$(if $(DEFCONFIG),=$(DEFCONFIG)) $(CONFIG_CONFIG_IN)
 
-define percent_defconfig
-# Override the BR2_DEFCONFIG from COMMON_CONFIG_ENV with the new defconfig
-%_defconfig: $(BUILD_DIR)/buildroot-config/conf $(1)/configs/%_defconfig outputmakefile
-	@$$(COMMON_CONFIG_ENV) BR2_DEFCONFIG=$(1)/configs/$$@ \
+ALL_DEFCONFIGS =
+# $1: br2-external directory, without trailing /configs/
+# $2: defconfig name with trailing _defconfig
+define defconfig_rule
+ifeq ($$(filter $(2),$$(ALL_DEFCONFIGS)),)
+# Override the BR2_DEFCONFIG from COMMON_CONFIG_ENV with the actual defconfig
+$(2): $$(BUILD_DIR)/buildroot-config/conf outputmakefile
+	$$(Q)$$(COMMON_CONFIG_ENV) BR2_DEFCONFIG=$(1)/configs/$$@ \
 		$$< --defconfig=$(1)/configs/$$@ $$(CONFIG_CONFIG_IN)
+ALL_DEFCONFIGS += $(2)
+endif
 endef
-$(eval $(foreach d,$(call reverse,$(TOPDIR) $(BR2_EXTERNAL_DIRS)),$(call percent_defconfig,$(d))$(sep)))
+$(eval \
+	$(foreach d, \
+		$(call reverse,$(TOPDIR) $(BR2_EXTERNAL_DIRS)), \
+		$(foreach c, \
+			$(wildcard $(d)/configs/*_defconfig $(d)/configs/.*_defconfig), \
+			$(call defconfig_rule,$(d),$(notdir $(c)))$(sep) \
+		) \
+	) \
+)
 
 update-defconfig: savedefconfig