#19 RFE: allow filtering of BuildArches by ExcludeArch/ExclusiveArch
Closed: Fixed 7 years ago Opened 8 years ago by ausil.

Sometimes the content in a package can be run anywhere, but some underlying dependency is only available on a subset of arches

if a package has "BuildArch: noarch" and a ExcludeArch or ExclusiveArch tag we should send the build to one of the arches listed. Due to how rpm works you have to add noarch to ExclusiveArch for a noarch package so that needs to be filtered out


If a package has BuildArch: noarch, that means in which architecture to build it is not matter. But if meanwhile the package has ExcludeArch tag or ExclusiveArch tag, while after building the package will still be a noarch package, the package could only be build in several architectures. To easily talk this package and packages like it, we name these packages as 'semi-noarch' packages.

If I'm not missing anything, now koji could dispatch a semi-noarch package build task to a builder that could not build the package.

To resolve this issue, we need when a builder taking a new task it could know whether it could build the package or not. That's interesting.

The issue is that some packages are considered noarch, the content is entirely architecture independent. The compose tools support noarch packages having ExcludeArch/ExclusiveArch it uses the fields to excluding including a noarch package in some arch. however developers get to play russian roulette with the buildsystem. having to resubmit builds over and over until it lands on a builder that can actually build it due to BuildRequires only being available on a subset of arches.

The patch is here, ask for comments.

The patch is here, ask for comments.

Commented there. Basically, not going to work. We'll need a different and more complicated approach.

The kernel package's BuildArch, ExclusiveArch, and ExcludeArch are as follows

>>> h[rpm.RPMTAG_BUILDARCHS]
[]
>>> h[rpm.RPMTAG_EXCLUSIVEARCH]
['noarch', 'i686', 'x86_64', 'ppc', 'ppc64', 'ppc64le', 's390', 's390x']
>>> h[rpm.RPMTAG_EXCLUDEARCH]
[]
>>>

It's the BaseTaskHandler instance method find_arch(self, arch, host, tag) which will randomly chose an architecture when arch equals 'noarch'. But I think we cannot adjust this method to resolve this issue because it's possible too late that the builder takes this task cannot do this task.

I think we should adjust the BuildTask instance method runBuilds. And only in following situation we choose a taskarch when arch is 'noarch'

if 'noarch' in buildarchs and not [a for a in buildarchs if a != 'noarch'] and \
        (exclusivearch or excludearch)  and \
        'noarch' not in exclusivearch and 'noarch' not in excludearch:
    taskarch = choose_one_for_build_noarch

This issue could be resolved by using tag BuildArch and BuildArchitectures together. E.g.

BuildArch:          noarch                                                                                                                                                      
BuildArchitectures: %{ix86} x86_64

Then the package will be built on Intel/AMD archs.

I will explain why this solution works soon.

@ausil does this solution make sense?

I verified this solution by mock on RHEL 5, 6, 7.

A mock level solution will not help if koji gives the task to a builder of the wrong arch.

Please avoid internal links here. They don't help folks outside of the firewall.

Ah, I think I see what is happening in your example. Koji is computing an arch list of: %{ix86} x86_64. This results in several build arch tasks (none noarch). OTOH, mock is looking at the buildarch field and only building noarch rpms.

Concerns:

  • this feels like a hack
  • koji's calculation of the arch list is arguably incorrect here
  • duplicate buildarch tasks

This is really strange.

With the following in the spec:

BuildArch:          noarch
BuildArchitectures: x86_64

The resulting srpm has the following headers:

RPMTAG_BUILDARCHS:      ['x86_64']
RPMTAG_EXCLUSIVEARCH:   []
RPMTAG_EXCLUDEARCH:     []

In fact, there is no indication in the srpm headers that this srpm is supposed to be built as noarch. Yet, that is how rpmbuild builds it.

It seems that both BuildArch and BuildArchitectures feed into the same header, yet are treated differently by rpmbuild.

This is all very worrisome and feels like questionable behavior on rpm's part.

  • There is no clear way to determine that an srpm is noarch when both these tags are used
  • The differences between BuildArch and BuildArchitectures are very unclear
  • BuildArchitectures is barely documented (vim doesn't even highlight it correctly)

Nothing about this behavior seems sane, and I am extremely hesitant to rely on it. Yes, it currently works, but that seems to be a complete accident and not by design of either koji or rpm.

A mock level solution will not help if koji gives the task to a builder of the wrong arch.

No, @mikem

I tried this solution by building bash-compltion-brew package for RHEL 6 and RHEL 7 on brew, but I didn't do that for RHEL 5, hence I verified this solution for RHEL 5 by mock.

This is really strange.
With the following in the spec:
BuildArch: noarch
BuildArchitectures: x86_64

The resulting srpm has the following headers:
RPMTAG_BUILDARCHS: ['x86_64']
RPMTAG_EXCLUSIVEARCH: []
RPMTAG_EXCLUDEARCH: []

In fact, there is no indication in the srpm headers that this srpm is supposed to be built as noarch. Yet, that is how rpmbuild builds it.
It seems that both BuildArch and BuildArchitectures feed into the same header, yet are treated differently by rpmbuild.
This is all very worrisome and feels like questionable behavior on rpm's part.

There is no clear way to determine that an srpm is noarch when both these tags are used
The differences between BuildArch and BuildArchitectures are very unclear
BuildArchitectures is barely documented (vim doesn't even highlight it correctly)

Nothing about this behavior seems sane, and I am extremely hesitant to rely on it. Yes, it currently works, but that seems to be a complete accident and not by design of either koji or rpm.

This behavior should not work. BuildArch and BuildArchitectures are literally the same property, so the fact that this works is unexpected behavior. Don't expect it to keep working.

These two are really synonyms, so latter overrides the first one. Maybe worth some bug for rpm. When e. g. Name is repeated, rpm will cry. When BuildArch is placed twice in spec (or BuildArchitectures is used), it will not complain.

RPM code

To combine the tag BuildArch and tag BuildArchitectures, we could express that a package is noarch but only could be built on several hardware architectures that we chosen? How this work, and Why? That's because when parsing a spec file, if encountering one of the tag BuildArch and tag BuildArchitectures, the parseSpec function will call itself to pass the spec file again. Hence if we using the two tags as follows

BuildArch:          noarch
BuildArchitectures: x86_64

When parseSpec function first parsing spec file, when encountering BuildArch tag, it will stop parsing immediately, then set the macro _target_cpu to value noarch, and after that it calls itself again.

In the second pass, when encountering the tag BuildArch and tag BuildArchitectures, the parseSpec function will parse them in turn. Just as @tkopecek said, the two tags are really synonyms, so latter overrides the first one. Hence the value of the tag BuildArch is x86_64.

After parsing the spec file, the parseSpec function will set a architecture for each package. The package architecture is the value of the macro _target_cpu. But because the _target_cpu is set in the first pass, hence the value of the _target_cpu macro is still noarch. After that, the parseSpec function returns, hence we return to the first pass parseSpec function and continue running.

We give some demo codes as follows

static rpmSpec parseSpec(const char *specFile, rpmSpecFlags flags,
                         const char *buildRoot, int recursing)
{
  ... ...
  if (!recursive) {
    recursive = 1;
    addMacro(NULL, "_target_cpu", NULL, spec->BANames[x], RMIL_RPMRC);
    spec->BASpecs[index] = parseSpec(specFile, flags, buildRoot, recursive);
    delMacro(NULL, "_target_cpu");
    goto exit;
  }
  addTargets(spec->packages);
  ... ...
}

This behavior is there not late than May, 05, 2001 when jbj fixed the bug: Inconsistent output from rpm -q --specfile when using BuildArch.

Hence I think this solution is acceptable.

This solution also works well when there are subpackages.

A mock level solution will not help if koji gives the task to a builder of the wrong arch.

I didn't try this solution for RHEL5, hence I tested this solution by mock.

Please avoid internal links here. They don't help folks outside of the firewall.

Sorry, have fixed.

Ah, I think I see what is happening in your example. Koji is computing an arch list of: %{ix86} x86_64. This results in several build arch tasks (none noarch). OTOH, mock is looking at the buildarch field and only building noarch rpms.
Concerns:

this feels like a hack

Any patch to resolve this issue is a hack way because we need express a noarch package that is matter where to build it.

koji's calculation of the arch list is arguably incorrect here
No, the result is right, just as what I got from the code and what I tested.
duplicate buildarch tasks

This could resolved by just gives one or two arch

  BuildArchitectures: x86_64

This behavior is there not late than May, 05, 2001 when jbj fixed the bug: Inconsistent output from rpm -q --specfile when using BuildArch.
Hence I think this solution is acceptable.

I honestly think that this is a bug in rpm and I do not find it acceptable. users will find it confusing and it will be a support burden

This issue could be resolved by using tag BuildArch and BuildArchitectures together. E.g.
BuildArch: noarch
BuildArchitectures: %{ix86} x86_64

Then the package will be built on Intel/AMD archs.
I will explain why this solution works soon.
@ausil does this solution make sense?

From the links we learned

  • The BuildArchitectures tag names the architectures that a binary RPM will run on.

  • The BuildArch tag is used to define the build architecture of the package.

But this isn't how that works. BuildArch == BuildArchitectures. They do exactly the same thing!

Created an RPM bug for this issue.

Florian Festi closed the bug, and let's talk this issue solution here.

To make sure I don't forget this possible workaround, just record it here.

Because rpm supports subpackages to set their own BuildArch, hence, suppose I want to build a semi-noarch package fakepkg, I could have this fakepkg.noarch always to be built on an x86_64 builder if the fakepkg.spec file contains following lines

Name: fakepkg-wrapper
... ...
BuildArch: x86_64
... ...
%package -n fakepkg
BuildArch: noarch
... ...

This hits me with winetricks. wine-core is a dependency and not available on ppc64*, so koschei complains randomly.

I have prepared 3 patches for this issue. Each patch corresponds a definition of what is a semi-noarch package.

  1. A semi-noarch package must have a BuildArch tag. To restrict where to build the semi-noarch package, only ExcludeArch tag could be used. Hence, the semi-noarch package spec file always looks like

    BuildArchs: noarch
    ExcludeArch: ppc ppc64 s390 s390x %{x86}
    
  2. A semi-noarch package must have a BuildArch tag. To restrict where to build the semi-noarch package, both the ExcludeArch tag and ExclusiveArch could be used. Hence the semi-noarch spec file looks like

    BuildArchs: noarch
    ExcludeArch: ppc ppc64 s390 s390x %{x86}
    

    or

    BuildArchs: noarch
    ExclusiveArch: noarch x86_64
    

    or

    BuildArchs: noarch
    ExcludeArch: ppc ppc64 s390 s390x %{x86}
    ExclusiveArch: noarch x86_64
    
  3. treat each noarch package as a semi-noarch package. Hence, encountering the following case, we will have koji to build the fake.noarch package on a x86_64 builder.

    Name:  fake
    ...
    ExclusiveArch: noarch x86_64
    

The patches correspond the 3 cases are here: patch1, patch2, and patch3.

But in the third case, to make sure only x86_64 builders could take our build tasks, we need make two buildArch tasks as follows

{id: 1023, method: buildArch, label: noarch, arch: x86_64, ... }
{id: 1024, method: buildArch, label: x86_64, arch: x86_64, ... }

While, because there is not BuildArch tag, hence to build the fake.noarch package, when doing the task 1023, we must have kojid to pass '--target=noarch' to mock. Otherwise we will build the fake.x86_64 package instead of the fake.noarch package.

In the third case, unless we add an attribute for the task table or reuse some attribute (e.g., the label attribute) of the task table, the builder cannot determine whether it is building a noarch package. In this situation, the src.rpm is helpless.

The patch3 works by reusing the label attribute. if we are all agree to add an attribute (e.g., targetarch) for task table, I will give 4th patch.

Doesn't your patches mean a change to package policies? Some package review must ensure package with noarch have the correct ExcludeArch/ExclusiveArch.

@raphgro no. Sorry, I need make myself clear. I don't talk about package policy.

I think a semi-noarch package always has a BuildArch tag in its spec file, hence either of the patch1 and patch2 could resolve this issue. But @mikem think any noarch package could be a semi-noarch package (hence it's possible there is not the BuildArch tag). Hence we need patch3.

I give these 3 patches to request for comments.

Hence, what is a semi-noarch package?

noarch usgages

We can employ noarch in 3 ways.

  1. build a noarch package. This is rpmbuild function.

    BuildArch: noarch
    
  2. in a arched main package, build a noarch subpackage. This is rpmbuild function

    Name: arched-main-package
    ... ...
    %package noarch-subpackage
    BuildArch: noach
    
  3. build package in each arch given by ExclusiveArch. This is koji function.

    ExclusiveArch: noarch i386 x86_64 ppc ppc64 s390 s390x
    

For the 1st case, we can build a semi-noarch package in some aforehand specified archs by combining BuildArch tag and Exclu*Arch tag. For more details, pls see this comment

For the 2nd case, it's obvious that the noarch subpackage always is a semi-noarch package. But we can make sure that this semi-noarch package will always build on a right buildier becuase the main package is arched and the main package and the subpackage will be built on the same builder.

Now let's consider the 3rd case. In this case, the build results depends how you designed the spec file. Normally the build results are arched main package and noarch/arched subpckages. But it's also possible that the build results are noarch main package and arched/noarch subpackages.

But what if we need a semi-noarch package in the 3rd case?

Because we have used the ExclusiveArch tag, hence if there is a way that we can build a semi-noarch as need, we should combine ExclusvieArch tag with the BuildArch tag or with the_ExcludeArch_ tag or with both the BuildArch tag and the_ExcludeArch_ tag.

Because the ExclusiveArch tag and ExcludeArch tag are complementary to each other, hence for us if there are both the ExclusiveArch tag and ExcludeArch tag in the spec file, we can replace them by one of them. Hence for the 3rd case, we only need consider how to combine BuildArch tag and ExcludesiveArch tag.

To add the BuildArch tag in the spec file, suppose like this

BuildArch: x86_64
ExclusiveArch: noarch i386 x86_64 ppc ppc64 s390 s390x

While in this case the build result will always be x86_64 arch packages and subpackages (possible noarch subpackages). Hence in the 3rd case, we cannot express a semi-noarch package by combining these 3 tags.

Hence when building an noarch package in the 3rd case if we treate the package as a semi-noarch package, this is a new feature of koji.

BTW, since we have known that the ExclusiveArch tag and ExcludeArch tag are complementary to each other, hence the above analysis also considers each possbile combination of the BuildArch tag, ExclusiveArch tag, and ExcludeArch tag.

Login to comment on this ticket.

Metadata