Subject: bug#53406: union-build incorrectly handles grafts

From: John Kehayias <[email-masked]>
Date: Sun, 15 Oct 2023 20:13:19 +0000

Hi Ludo’,

Long time since I've thought about this bug, but with all the recent
grafts I thought to return to it. I'll have to make a new example to
dig again, but wanted to think through where we might look to see
what's happening first.

On Tue, Jan 25, 2022 at 02:47 PM, Ludovic Courtès wrote:

> Hi John,
>
> John Kehayias <[email-masked]> skribis:
>
[...]
>> Perhaps I was too hasty in noting this "problem" which like I said
>> was not the error I originally encountered. I was using a package
>> that constructs both the 64- and 32-bit libraries to put in a
>> container (say, a /lib32 and /lib64 or something similar to an FHS
>> environment). A collision was happening between a file and
>> directory, one being a good symlink and the other broken, rather
>> than a "real" mismatch in file vs directory. Anyway, going back to
>> that what I see is that one link is broken for the above reasons,
>> but the good one is good because it is to the *ungrafted* library
>> store path. I don't know now if these 2 things are connected other
>> than one led me to the other, but I turn now to what demonstrates my
>> original problem.
>>
>> I don't know why this happens or if it is something in this building
>> process that is not correct, but I did come up with a minimal
>> example (attached). The code is a bit odd in its stripped down form,
>> though hopefully is clear in what way this would be used to do
>> something useful (again, like an FHS environment or other
>> container). Apologies for the old style and lack of gexps which I'm
>> finally getting used to. The example package just tries to make a
>> dummy package that has, for illustration, a "/lib64" and "/lib32"
>> which link to the respective union-build inputs (of a single library
>> for simplicity). I don't think the actual package being made matters
>> so much, or how it is constructed, but that two inputs are
>> union-builds of the same library (x86_64 and i686) which should have
>> a graft of expat. Just my guess though.
>>
>> Doing:
>>
>> ls -la $(guix build -f graft-test.scm)/lib64/lib/libexpat*
>> lrwxrwxrwx 1 root root 71 Dec 31 1969
>> /gnu/store/qbh16hfdv8pnfw01k6izbs3jkji6i978-test-pkg-0.0/lib64/lib/libexpat.la
>> ->
>> /gnu/store/2q8wwhd3prib0swky68rbx9hl0xxs6hf-expat-2.4.3/lib/libexpat.la*
>> lrwxrwxrwx 1 root root 71 Dec 31 1969
>> /gnu/store/qbh16hfdv8pnfw01k6izbs3jkji6i978-test-pkg-0.0/lib64/lib/libexpat.so
>> ->
>> /gnu/store/2q8wwhd3prib0swky68rbx9hl0xxs6hf-expat-2.4.3/lib/libexpat.so*
>> lrwxrwxrwx 1 root root 73 Dec 31 1969
>> /gnu/store/qbh16hfdv8pnfw01k6izbs3jkji6i978-test-pkg-0.0/lib64/lib/libexpat.so.1
>> ->
>> /gnu/store/2q8wwhd3prib0swky68rbx9hl0xxs6hf-expat-2.4.3/lib/libexpat.so.1*
>> lrwxrwxrwx 1 root root 77 Dec 31 1969
>> /gnu/store/qbh16hfdv8pnfw01k6izbs3jkji6i978-test-pkg-0.0/lib64/lib/libexpat.so.1.8.1
>> ->
>> /gnu/store/2q8wwhd3prib0swky68rbx9hl0xxs6hf-expat-2.4.3/lib/libexpat.so.1.8.1
>>
>> is what we saw already: libexpat is the newer (replacement, 2.4.3)
>> version, with the full version symlink broken since the version
>> number is wrong. Likewise in other pieces that have the version
>> number, like share/doc. Okay, that's expected. But now, in the
>> i686-linux union-build input:
>>
>> ls -la $(guix build -f graft-test.scm)/lib32/lib/libexpat*
>> lrwxrwxrwx 1 root root 71 Dec 31 1969
>> /gnu/store/qbh16hfdv8pnfw01k6izbs3jkji6i978-test-pkg-0.0/lib32/lib/libexpat.la
>> ->
>> /gnu/store/b0jns3vzhhpna7lim8bc3dr0payzx5yy-expat-2.4.1/lib/libexpat.la*
>> lrwxrwxrwx 1 root root 71 Dec 31 1969
>> /gnu/store/qbh16hfdv8pnfw01k6izbs3jkji6i978-test-pkg-0.0/lib32/lib/libexpat.so
>> ->
>> /gnu/store/b0jns3vzhhpna7lim8bc3dr0payzx5yy-expat-2.4.1/lib/libexpat.so*
>> lrwxrwxrwx 1 root root 73 Dec 31 1969
>> /gnu/store/qbh16hfdv8pnfw01k6izbs3jkji6i978-test-pkg-0.0/lib32/lib/libexpat.so.1
>> ->
>> /gnu/store/b0jns3vzhhpna7lim8bc3dr0payzx5yy-expat-2.4.1/lib/libexpat.so.1*
>> lrwxrwxrwx 1 root root 77 Dec 31 1969
>> /gnu/store/qbh16hfdv8pnfw01k6izbs3jkji6i978-test-pkg-0.0/lib32/lib/libexpat.so.1.8.1
>> ->
>> /gnu/store/b0jns3vzhhpna7lim8bc3dr0payzx5yy-expat-2.4.1/lib/libexpat.so.1.8.1*
>>
>> all the links are good and to the original (version 2.4.1) expat. In
>> other words, the constructed union64 and union32 inputs (in the
>> sample code) do not both get grafted, even though doing just the
>> fhs-union command on it's own (not building both for another
>> package) does graft for either architecture. At least that seems
>> like the most obvious difference between the earlier example and
>> this new one.
>>
>> Why? Does the grafting just happen "once" somehow and misses the
>> "same" input again (but built for different system)? Is this
>> expected or just a weird/wrong way to do this kind of build which is
>> causing this? I'm not sure if this is just with union-build or if it
>> would happen just with inputs of the same library but different
>> architectures. I didn't know how to do that quickly off hand, so I
>> haven't tried it yet.
>
> Woow, it’s a sophisticated example.  :-)
>

This is potentially relevant again since I wanted to see about adding
multi-arch support to guix shell --emulate-fhs. Likely there will be a
collision in the profile created for this same reason. (These days
from say libx11.)

> I don’t actually have the Expat replacement we’re talking about so I
> can’t easily test.
>
> At first sight I’d say it should work (both lib32 and lib64 should refer
> to the Expat replacement), but it could be that something somewhere
> assumes all the packages are built for the system.  That would need more
> investigation work in (guix packages) in (guix grafts).
>

Taking a quick look, and not knowing much about graft internals, is
the place to look more in (guix packages) bag-grafts, input-graft,
package->derivation maybe?

>From what you said, and my guess as well, is that mixed #:system in
the inputs is somehow "missed" by grafting. Maybe just using the same
#:system as the package? If that's the case, grafting needs to check
the system of each input package rather than assuming it follows from
the package, would that make sense? Maybe some clues in how
cross-compiling works, or some package we have that does mix systems
(like dxvk)?

I'll reconstruct an example or maybe see if dxvk sheds some light with
grafts, though looks like it is currently broken first...

Thanks!
John