Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dynamic Spec generation #1485

Merged
merged 3 commits into from
Nov 9, 2022
Merged

Conversation

ffesti
Copy link
Contributor

@ffesti ffesti commented Jan 12, 2021

This is an alternative to #1239
Instead of extending the spec syntax with dynamic elements allow the build process to generate their own spec file snippets that get read in after executing %build (technically after %check) from $BUILDDIR/__rpm/*.spec.

This allows tricks like generating sub packages from brp scripts - although I might need to think this through a bit more. While this could be used to implement debuginfo packages as brp script that might not be a great idea as they don't have easy access to the rpmbuiild internals. But for simpler use cases (e.g. language sub packages) this should be perfectly fine.

Another use case would be executing some X2rpm script that extracts the package structure from the manifest file of some scripting language module/project.

@ffesti ffesti added the RFC label Jan 12, 2021
@ignatenkobrain
Copy link
Contributor

cc @davide125

@pmatilai
Copy link
Member

Didn't look with a microscope yet (some whitespace and spelling issues there at least) but on the whole this looks really neat set of patches. Which is usually a good sign 👍

Do add some test-case(s) though, no matter how artificial.

@pmatilai
Copy link
Member

A couple of random thoughts:

  • I think we'd want the generated specs parse occur before %check because you want to get possible packaging errors as soon as possible, and test-suites can take significant amount of time. And actually, I don't think we should allow %check to affect the actual packaging.
  • I think we need to come up with a (new) way to include generated content in src.rpm's. That content is not sources, patches or specs so it needs to be flagged differently, but it probably should be included in the name of debuggability and reproducability. The need for such a thing isn't specific to this PR at all, we've come across the topic in multiple cases in the last couple of years. As the spec macro usage gets ever more complex, it'd probably be a good idea to stash a fully expanded copy of the spec someplace for people to see, and optionally use for reproducing a build.

@pmatilai
Copy link
Member

More random thoughts as they trickle through this old-but-not-obsolete (yet) processor of mine... 🧠

  • This is too implicit as it is. Existing carefully hand-crafted packages can't start behaving differently because somebody else somewhere did something (unless that something creates new files that it owns, like debuginfo does). Also, it needs to be clear from the outset whether a spec relies on dynamically generated parts or not, this will also serve as a clue to rpmspec that all is not what it seems. So given all the pre-existing content out there, I think this needs to be explicit opt-in behavior in the spec, one way or the other. If rpm was new today, it'd probably be the other way around.
  • The generated spec fragments need to have some other suffix than *.spec to avoid confusion. Call it .specpart or whatever, but they are not spec files so they must not be mistaken as such, either by humans or the computer (eg multiple .spec files in a tarball will confuse rpm)
  • If the spec fragments are to be in some special new directory, we'll need to have a macro for it. Up to now, various things have just dropped things into the per-package build directory root, which would still be feasible if a unique suffix is used. Some people dislike that practise as it is, but then I'm not too hot about the __rpm directory either.
  • For the generic case of including built content in src.rpm (such as the parsed spec), we already have a way, sort of: flag such files as RPMFILE_ARTIFACT in the src.rpm, and filter out on the regular src.rpm usage paths.

@Conan-Kudo
Copy link
Member

cc: @dcermak

@Conan-Kudo
Copy link
Member

cc: @Vogtinator

@dcermak
Copy link
Contributor

dcermak commented Jan 14, 2021

This looks intriguing!

Do I understand it correctly that you'd essentially do a my_spec_generator > %_builddir/__rpm/subpac.spec which would e.g. contain a new subpackage definition?

@Conan-Kudo
Copy link
Member

  • The generated spec fragments need to have some other suffix than *.spec to avoid confusion. Call it .specpart or whatever, but they are not spec files so they must not be mistaken as such, either by humans or the computer (eg multiple .spec files in a tarball will confuse rpm)
  • If the spec fragments are to be in some special new directory, we'll need to have a macro for it. Up to now, various things have just dropped things into the per-package build directory root, which would still be feasible if a unique suffix is used. Some people dislike that practise as it is, but then I'm not too hot about the __rpm directory either.

Is there a reason we can't just push it back into rpmbuild itself directly? Like perhaps by exposing something via Lua that lets you programmatically define binary packages?

@ffesti
Copy link
Contributor Author

ffesti commented Jan 14, 2021

Do I understand it correctly that you'd essentially do a my_spec_generator > %_builddir/__rpm/subpac.spec which would e.g. contain a new subpackage definition?

Basically yes. As this is run in the %build or %install section you would need to modify your spec generator to not output these.
Note that this is not the only possible use. I just did a POC for find_lang.sh to generate language sub packages. This is surprisingly simple and can easily be done in bash.

Long term this may also be used to generate the debuginfo sub packages. But as they are more closely coupled to the rpmbuild internals there are some more pieces needed to make this feasible.

@ffesti
Copy link
Contributor Author

ffesti commented Jan 15, 2021

Is there a reason we can't just push it back into rpmbuild itself directly? Like perhaps by exposing something via Lua that lets you programmatically define binary packages?

Yes, we could. I have thinking about this quite a lot. But this is much more complicated and harder to use. Generating a piece of spec file is easy and can be done from any programming language. This is a huge benefit if you want to do language specific solutions as you have easy access to all the domain specific tooling.

But there are a few use cases that will benefit from direct access to the rpmbuild internals. Debuginfo being one of them. Otoh I dislike the fact that the internal state of rpmbuild getting altered without an artefact that documents that. Yes, this already is the case for debuginfo packages. But I would rather have less of that than more.

@juhp
Copy link

juhp commented Jan 21, 2021

It would be nice to see some simple example(s) :-)

int argc = 0;
int i;

char * specPattern = rpmGenPath("%{u2p:%{_builddir}}", spec->buildSubdir, "__rpmbuild_*.specpart");
Copy link
Member

@pmatilai pmatilai Feb 4, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

__rpmbuild_*.specpart still looks quite the eyesore to me. These are not rpm internal things, but files we expect others to produce. I'd think *.specpart would be enough to differentiate from other stuff.

Also there's still the option of creating a special directory (.rpmbuild or such in the builddir) which could be used for such things, including but not limited to debuginfo filelists, to avoid cluttering the main build directory (that gets requested separately every now and then). Such a thing doesn't have to be part of this PR, but should need to be done before a public release.

@pmatilai
Copy link
Member

pmatilai commented Feb 4, 2021

And yeah, docs + a test-case or two needed. Once that is there we can do a fine-comb review, but generally this is so remarkably tiny amount of code that I wouldn't expect any major issues there.

@pmatilai
Copy link
Member

pmatilai commented Feb 23, 2021

Wrt implicit/explicitness of it all, it occurred to me that there are two quite distinct cases:

  1. spec invoking a script that does something - such as invoke %find_lang with extra flags
  2. upstream build system (makefiles etc) doing something

For 1. you want it to "just work" without further ado (it's already explicit in the spec afterall), but for 2. that needs to be a packager decision. One crude but relatively simple way would be using different directories for these, with the directory for 1. being outside the package builddir so upstream make system has "no access" (because it doesn't know where it is), and 2. in a well-known location in the build directory, that would only be used if explicitly enabled in the spec somehow.

That conceptual split seems resolve the implicit/explicit problem for me (actual implementation is another question). You'd still want to programmatically tell whether a spec relies on this for sub-package generation though. One could easily flag it in the SRPM one way or the other, but not the spec alone for 1.

One possibility could be (going back to) a new spec section that simply executes after %install, but without any special parsing. Then you can easily tell at spec parse whether this is used or not, like with %generate_requires. Only something there doesn't feel quite right there, probably needs more head-scratching to understand what that something is 🤔

@ffesti
Copy link
Contributor Author

ffesti commented Feb 23, 2021

When it comes to the distribution automagically generating sub packages I agree that the package need to be able to over write the behaviour. But I wonder if this really should be done on the "automatic sub packages off/on" level. Similar to what we have with debuginfo sub packages I'd rather expect each such feature providing some switch on its own. So packages would not switch off automatic sub package creation as a whole but the particular type that breaks. E.g. not splitting out precompliled (Python) files.

@pmatilai
Copy link
Member

I'd think there needs to be a global switch for at least case 2, but in addition individual scripts could/should be possible to disable case-by-case. Which might get tricky. Of course, just rm -f the bits you dislike/break at end of %install achieves that, but then you might want to prevent the script from running in the first place.

@ffesti
Copy link
Contributor Author

ffesti commented Feb 23, 2021

I guess I really need to write a proper design document...
When I think about distribution level sub package creation I think about something very close to an brp-script - most likely a literal brp-script. Not that I am a big fan of the way brp scripts are currently run. But I'd rather improve the brp mechanism to something more modular and easier to control from the spec file than adding a new mechanism to run these "distro sub package creators".

@pmatilai
Copy link
Member

I guess I really need to write a proper design document...

Please do.

I'm clearly thinking on somewhat different lines from you. Which is not to say either one is wrong, just that there are so many possible scenarios that they're easy to miss entirely, and/or don't easily fit inside ones head simultaneously 😅

@sdp5
Copy link

sdp5 commented Mar 3, 2021

thanks @ffesti this groups .mo files by language_id, for example, in __rpmbuild_gettext-runtime.lang.specpart 👍

Summary: en@quot translations for %{name}
%description lang-en@quot
Translations for package %{name} in en@quot
%files lang-en@quot
%lang(en@quot) /usr/share/locale/en@quot/LC_MESSAGES/gettext-runtime.mo
Summary: pt translations for %{name}
%description lang-pt
Translations for package %{name} in pt
%files lang-pt
%lang(pt) /usr/share/locale/pt_BR/LC_MESSAGES/gettext-runtime.mo
%lang(pt) /usr/share/locale/pt/LC_MESSAGES/gettext-runtime.mo
Summary: zh translations for %{name}
%description lang-zh
Translations for package %{name} in zh
%files lang-zh
%lang(zh) /usr/share/locale/zh_CN/LC_MESSAGES/gettext-runtime.mo
%lang(zh) /usr/share/locale/zh_HK/LC_MESSAGES/gettext-runtime.mo
%lang(zh) /usr/share/locale/zh_TW/LC_MESSAGES/gettext-runtime.mo

@romulasry
Copy link

Update?

@ffesti
Copy link
Contributor Author

ffesti commented Apr 21, 2022

Added #2032 as the promised design document that lists a bit of the back ground.

Copy link
Member

@pmatilai pmatilai left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay so to recoup the on-off discussions over many moons: there are further details to sort out. While we can work on the details later, the bare minimum requirements for merging are:

  • documentation
  • a test-case or two

For clarity, flagging as changes-needed for these two.

@ffesti
Copy link
Contributor Author

ffesti commented Oct 24, 2022

OK, added test case and some docs.
In addition I stripped the find-lang patch as this needs to g into it's own PR and was only there as a demonstration piece.
The patch now uses a SPECS sub dir in the buildsubdir to allow detecting whether the feature is supported.
I also removed the code to print out the content of the specparts to the terminal as we now have the expanded spec file in the SRPM.

@pmatilai
Copy link
Member

pmatilai commented Nov 3, 2022

Final patch might still deserve a bit longer commit message. Otoh there is docs on this and there is no need to duplicated that in the patch.

Yep, there's no point duplicating the user docs in the commit message. What belongs in the commit message is a condensed version of the design spec: #2032

Because, commit messages are forever, a discussion on a proprietary platform is less so.

@pmatilai
Copy link
Member

pmatilai commented Nov 3, 2022

...or maybe we should get into the habit of including these design docs in a directory someplace in the source tree.

@ffesti
Copy link
Contributor Author

ffesti commented Nov 4, 2022

OK, fixed the error handling and added a failing test case.
Added $RPM_SPECPARTS_DIR to the build scripts and removed the true directory name from the docs.

Turns out there is a reason that parseSpecSection() returns a Spec object: The BUILDARCHITECTURES magic may return a different Spec object. Yes, this is super ugly but nothing I am touching while adding this new feature. Anyone feel free to clean that up afterwards.

Copy link
Contributor

@dmnks dmnks left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM now, only little nitpicks, see comments.

The files need to be placed in the **%{specpartsdir}** (also available
as **$RPM_SPECPARTS_DIR** in the build scripts) and have a
**.specpart** postfix. The directory is created by **%setup** in the
**buildsubdir**. Scripts must fail if it is not present. They should
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This sentence sounds contradictory to what follows it, i.e. that they in fact don't have to fail if they do some kind of fallback.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reworded. There are just a little to many ifs and whens to make this nice to read literature. Or my poetic qualities are just lacking...

tests/data/SPECS/dynamic.spec Outdated Show resolved Hide resolved
@pmatilai
Copy link
Member

pmatilai commented Nov 7, 2022

Yes, this is super ugly but nothing I am touching while adding this new feature. Anyone feel free to clean that up afterwards.

Do stuff for others to clean up is not an attitude us maintainers can afford you know.

build/parsePrep.c Outdated Show resolved Hide resolved
@ffesti
Copy link
Contributor Author

ffesti commented Nov 7, 2022

OK, addressed all the comments above except the issue with the signature of parseSpecSection() where I not quite see what a better solution would be. Fixed for now:

  • Reworded documentation
  • Merged spec files for test cases
  • Failing test case checks rpmbuild output
  • removed spurious u2p macro

macros.in Outdated Show resolved Hide resolved
@ffesti
Copy link
Contributor Author

ffesti commented Nov 8, 2022

  • Removed u2p macro
  • Added _prefix to test case
  • Removed unnneccessary rpmSpecFree call

@ffesti
Copy link
Contributor Author

ffesti commented Nov 8, 2022

Added alternative error handling. Turns out to be less of a change than I expected. So it's probably just the way to go.

rpmRC rc = RPMRC_OK;

char * specPattern = rpmGenPath("%{specpartsdir}", NULL, "*.specpart");
if (rpmGlob(specPattern, &argc, &argv) == 0) {
Copy link
Member

@pmatilai pmatilai Nov 8, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One more thing I realized earlier today but ended up on a long detour... there's a subtle but important thing silently done by rpmGlob() here: it sorts the returned files. Without which, users of this feature may get a pretty weird spec salad of the day. I'd add a comment here because that's a not-so-known feature of rpmGlob(), and also document the order (sorted according to C locale) in the user documentation because it's something users will need to care about.

@dmnks
Copy link
Contributor

dmnks commented Nov 9, 2022

Added alternative error handling. Turns out to be less of a change than I expected. So it's probably just the way to go.

Yup, looks good.

@dmnks
Copy link
Contributor

dmnks commented Nov 9, 2022

OK, seems like all the comments are now addressed.

This allows parsing more spec pieces later in the build process
Don't require %end as first line when PART_NONE is "preceeding"
Read in *.specpart files from %{specpartsdir} aka $RPM_SPECPARTS_DIR
after the %install script. This allows the build process to add sub
packages to the build. In the future more changes to the packages may be
possible - like amending package declarations.

The %{specpartsdir} is created by the %setup pseudo macro inside of the
buildsubdir. It's presence allows build scripts to check if this feature
is supported in the running rpmbuild instance.
@ffesti
Copy link
Contributor Author

ffesti commented Nov 9, 2022

Rebased on top of #2270 and squashed latest fixes.

Copy link
Member

@pmatilai pmatilai left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay the spec parse stuff looks a whole lot nicer now - it actually looks like an improvement.

I intended to do some more practical testing with this, but enough is enough 😅

@pmatilai pmatilai merged commit e8e2a12 into rpm-software-management:master Nov 9, 2022
@ffesti ffesti deleted the dynamic branch November 27, 2022 10:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
Status: Done
Development

Successfully merging this pull request may close these issues.

None yet

9 participants