From 5e17a4a96ab24456d043d97b11e8881e37885f77 Mon Sep 17 00:00:00 2001 From: Panu Matilainen Date: Tue, 7 Nov 2023 11:08:57 +0200 Subject: [PATCH] Implement declarative buildsystem support Remove endless boilerplate to perform the same steps in each package by just declaring which kind of buildsystem the package uses using the new BuildSystem: spec tag. Buildsystem macros are just regular parametric macros which can be invoked manually from specs too, which should help with reuse and handling complicated cases. As such, this will only work for simple and/or "perfect" upstream projects of course. In reality many/most packages need further tweaking, but this can now be easily done with the new append/prepend modes of the build scriptlets, and declaratively passed options to specific sections. Add automake upstream amhello-1.0.tar.gz as an test-case of a rather hands-free build with added distro specific doc. Fixes: #1087 --- build/parsePreamble.c | 28 +++++++ build/parseSpec.c | 102 +++++++++++++++++++++++ build/rpmbuild_internal.h | 6 ++ docs/manual/buildsystem.md | 115 ++++++++++++++++++++++++++ docs/manual/index.md | 1 + docs/manual/spec.md | 6 ++ include/rpm/rpmtag.h | 2 + macros.in | 14 ++++ tests/data/SOURCES/amhello-1.0.tar.gz | Bin 0 -> 86877 bytes tests/data/SPECS/amhello.spec | 34 ++++++++ tests/rpmbuild.at | 39 +++++++++ 11 files changed, 347 insertions(+) create mode 100644 docs/manual/buildsystem.md create mode 100644 tests/data/SOURCES/amhello-1.0.tar.gz create mode 100644 tests/data/SPECS/amhello.spec diff --git a/build/parsePreamble.c b/build/parsePreamble.c index 8d1d85464b..a23e5a8f65 100644 --- a/build/parsePreamble.c +++ b/build/parsePreamble.c @@ -783,6 +783,20 @@ int addLangTag(rpmSpec spec, Header h, rpmTagVal tag, return 0; } +static int addBuildOption(rpmSpec spec, const char *sect, const char *opt) +{ + rpmRC rc = RPMRC_FAIL; + if (*sect == '\0') + sect = "conf"; + + int sn = getSection(sect); + if (sn >= 0) { + argvAdd(&(spec->buildopts[sn]), opt); + rc = RPMRC_OK; + } + return rc; +} + static rpmRC handlePreambleTag(rpmSpec spec, Package pkg, rpmTagVal tag, const char *macro, const char *lang) { @@ -820,6 +834,18 @@ static rpmRC handlePreambleTag(rpmSpec spec, Package pkg, rpmTagVal tag, multiToken = 1; switch (tag) { + case RPMTAG_BUILDSYSTEM: + SINGLE_TOKEN_ONLY; + if (rpmCharCheck(spec, field, + ALLOWED_CHARS_NAME, ALLOWED_FIRSTCHARS_NAME)) + { + goto exit; + } + break; + case RPMTAG_BUILDOPTION: + if (addBuildOption(spec, lang, field)) + goto exit; + break; case RPMTAG_NAME: SINGLE_TOKEN_ONLY; if (rpmCharCheck(spec, field, @@ -1077,8 +1103,10 @@ static struct PreambleRec_s const preambleList[] = { {RPMTAG_BUILDARCHS, 0, 0, 0, LEN_AND_STR("buildarchitectures")}, {RPMTAG_BUILDARCHS, 0, 0, 0, LEN_AND_STR("buildarch")}, {RPMTAG_BUILDCONFLICTS, 0, 0, 0, LEN_AND_STR("buildconflicts")}, + {RPMTAG_BUILDOPTION, 2, 0, 0, LEN_AND_STR("buildoption")}, {RPMTAG_BUILDPREREQ, 0, 1, 0, LEN_AND_STR("buildprereq")}, {RPMTAG_BUILDREQUIRES, 0, 0, 0, LEN_AND_STR("buildrequires")}, + {RPMTAG_BUILDSYSTEM, 0, 0, 1, LEN_AND_STR("buildsystem")}, {RPMTAG_AUTOREQPROV, 0, 0, 0, LEN_AND_STR("autoreqprov")}, {RPMTAG_AUTOREQ, 0, 0, 0, LEN_AND_STR("autoreq")}, {RPMTAG_AUTOPROV, 0, 0, 0, LEN_AND_STR("autoprov")}, diff --git a/build/parseSpec.c b/build/parseSpec.c index c660d47eee..8ba0bd7c4b 100644 --- a/build/parseSpec.c +++ b/build/parseSpec.c @@ -25,6 +25,8 @@ #define ISMACRO(s,m,len) (rstreqn((s), (m), len) && !risalpha((s)[len])) #define ISMACROWITHARG(s,m,len) (rstreqn((s), (m), len) && (risblank((s)[len]) || !(s)[len])) +static rpmRC parseSpecParts(rpmSpec spec, const char *pattern); + typedef struct OpenFileInfo { char * fileName; FILE *fp; @@ -894,6 +896,103 @@ static int parseEmpty(rpmSpec spec, int prevParsePart) return res; } +struct sectname_s { + const char *name; + int section; +}; + +struct sectname_s sectList[] = { + { "prep", SECT_PREP }, + { "conf", SECT_CONF }, + { "generate_buildrequires", SECT_BUILDREQUIRES }, + { "build", SECT_BUILD }, + { "install", SECT_INSTALL }, + { "check", SECT_CHECK }, + { "clean", SECT_CLEAN }, + { NULL, -1 } +}; + +int getSection(const char *name) +{ + int sn = -1; + for (struct sectname_s *sc = sectList; sc->name; sc++) { + if (rstreq(name, sc->name)) { + sn = sc->section; + break; + } + } + return sn; +} + +static rpmRC parseBuildsysSect(rpmSpec spec, const char *prefix, + struct sectname_s *sc, FD_t fd) +{ + rpmRC rc = RPMRC_OK; + + if (spec->sections[sc->section] == NULL) { + char *mn = rstrscat(NULL, "buildsystem_", prefix, "_", sc->name, NULL); + if (rpmMacroIsParametric(NULL, mn)) { + char *args = NULL; + if (spec->buildopts[sc->section]) { + ARGV_t av = NULL; + argvAdd(&av, "--"); + argvAppend(&av, spec->buildopts[sc->section]); + args = argvJoin(av, " "); + free(av); + } + char *buf = rstrscat(NULL, "%", sc->name, "\n", + "%", mn, " ", + args ? args : "", + "\n", NULL); + size_t blen = strlen(buf); + if (Fwrite(buf, blen, 1, fd) < blen) { + rc = RPMRC_FAIL; + rpmlog(RPMLOG_ERR, + _("failed to write buildsystem %s %%%s: %s\n"), + prefix, sc->name, strerror(errno)); + } + free(buf); + free(args); + } + free(mn); + } + return rc; +} + +static rpmRC parseBuildsystem(rpmSpec spec) +{ + rpmRC rc = RPMRC_OK; + char *buildsystem = rpmExpand("%{?buildsystem}", NULL); + if (*buildsystem) { + char *path = NULL; + + FD_t fd = rpmMkTempFile(NULL, &path); + if (fd == NULL) { + rpmlog(RPMLOG_ERR, + _("failed to create temp file for buildsystem: %s\n"), + strerror(errno)); + rc = RPMRC_FAIL; + goto exit; + } + + for (struct sectname_s *sc = sectList; !rc && sc->name; sc++) { + rc = parseBuildsysSect(spec, buildsystem, sc, fd); + if (!rc && spec->sections[sc->section] == NULL) + rc = parseBuildsysSect(spec, "default", sc, fd); + } + + if (!rc) + rc = parseSpecParts(spec, path); + if (!rc) + unlink(path); + Fclose(fd); + } + +exit: + free(buildsystem); + return rc; +} + static rpmSpec parseSpec(const char *specFile, rpmSpecFlags flags, const char *buildRoot, int recursing); @@ -1061,6 +1160,9 @@ static rpmRC parseSpecSection(rpmSpec *specptr, int secondary) } } + if (!secondary && parseBuildsystem(spec)) + goto errxit; + /* Add arch for each package */ addArch(spec); diff --git a/build/rpmbuild_internal.h b/build/rpmbuild_internal.h index 279a329de2..e2d4f8d683 100644 --- a/build/rpmbuild_internal.h +++ b/build/rpmbuild_internal.h @@ -150,6 +150,8 @@ struct rpmSpec_s { rpmstrPool pool; StringBuf sections[NR_SECT]; /*!< spec sections (%prep etc) */ + ARGV_t buildopts[NR_SECT]; /*!< per-section buildsystem options */ + StringBuf parsed; /*!< parsed spec contents */ Package packages; /*!< Package list. */ @@ -639,6 +641,10 @@ void doSetupMacro(rpmMacroBuf mb, rpmMacroEntry me, ARGV_t margs, size_t *parsed RPM_GNUC_INTERNAL void doPatchMacro(rpmMacroBuf mb, rpmMacroEntry me, ARGV_t margs, size_t *parsed); +/* Return section number, -1 on error */ +RPM_GNUC_INTERNAL +int getSection(const char *name); + #ifdef __cplusplus } #endif diff --git a/docs/manual/buildsystem.md b/docs/manual/buildsystem.md new file mode 100644 index 0000000000..9d536097c4 --- /dev/null +++ b/docs/manual/buildsystem.md @@ -0,0 +1,115 @@ +--- +layout: default +title: rpm.org - Declarative builds +--- + +# Declarative builds + +Most software follows common patterns as to how it should be prepared and +built, such as the classic `./configure && make && make install` case. +In a spec, these all go to their own sections and when combined with +the steps to unpack sources, it creates a lot of boilerplate that is +practically identical across a lot of specs. With the odd tweak to +this or that detail for distro preferences and the like. To reduce +the repetitive boilerplate, rpm >= 4.20 adds support for a declarative +build system mechanism where these common steps can be defined centrally +per each build system. Packagers then only need to declare which build +system they use, and optionally supply additional switches and/or +arguments for the steps where needed. + +## Spec usage + +In the most simple case, a spec will have an buildsystem declaration such +as `BuildSystem: cmake` and no manually specified build scripts at all. +However it's common to have to manually tweak a thing or two. There are +several ways how to accomplish this, what's appropriate depends on the +case. + +1) Use append and/or prepend as necessary. Need to delete a file after +the install step? Add a `%install -a` section with the appropriate `rm`. +Need to `sed` something just before the build step? Add a `%build -p` +section with the necessary steps. + +2) Another very common need is to pass extra arguments to the build +commands, build configuration in particular. This is done with the +BuildOption tag, which can appear arbitrary number of times +in the spec for each section. + +``` +BuildOption(
):