diff --git a/build/parsePreamble.c b/build/parsePreamble.c index 8d1d85464b..8a6e7522b1 100644 --- a/build/parsePreamble.c +++ b/build/parsePreamble.c @@ -820,6 +820,14 @@ static rpmRC handlePreambleTag(rpmSpec spec, Package pkg, rpmTagVal tag, multiToken = 1; switch (tag) { + case RPMTAG_AUTOBUILD: + SINGLE_TOKEN_ONLY; + if (rpmCharCheck(spec, field, + ALLOWED_CHARS_NAME, ALLOWED_FIRSTCHARS_NAME)) + { + goto exit; + } + break; case RPMTAG_NAME: SINGLE_TOKEN_ONLY; if (rpmCharCheck(spec, field, @@ -1080,6 +1088,7 @@ static struct PreambleRec_s const preambleList[] = { {RPMTAG_BUILDPREREQ, 0, 1, 0, LEN_AND_STR("buildprereq")}, {RPMTAG_BUILDREQUIRES, 0, 0, 0, LEN_AND_STR("buildrequires")}, {RPMTAG_AUTOREQPROV, 0, 0, 0, LEN_AND_STR("autoreqprov")}, + {RPMTAG_AUTOBUILD, 0, 0, 1, LEN_AND_STR("autobuild")}, {RPMTAG_AUTOREQ, 0, 0, 0, LEN_AND_STR("autoreq")}, {RPMTAG_AUTOPROV, 0, 0, 0, LEN_AND_STR("autoprov")}, {RPMTAG_DOCDIR, 0, 0, 0, LEN_AND_STR("docdir")}, diff --git a/build/parseSpec.c b/build/parseSpec.c index d61c477439..a9d100768a 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; @@ -891,6 +893,70 @@ static int parseEmpty(rpmSpec spec, int prevParsePart) return res; } +static rpmRC parseAutosect(rpmSpec spec, + const char *prefix, const char *section, StringBuf sb) +{ + rpmRC rc = RPMRC_OK; + + if (sb == NULL) { + char *mn = rstrscat(NULL, "autobuild_", prefix, "_", section, NULL); + if (rpmMacroIsDefined(NULL, mn)) { + char *path = NULL; + FD_t fd = rpmMkTempFile(NULL, &path); + if (fd) { + char *buf = rstrscat(NULL, "%", section, "\n", + "%{", mn, "}", NULL); + size_t blen = strlen(buf); + if (Fwrite(buf, blen, 1, fd) < blen) + rc = RPMRC_FAIL; + Fclose(fd); + } + if (fd == NULL || rc) { + rpmlog(RPMLOG_ERR, _("failed to write autobuild %%%s %s: %s\n"), + section, path, strerror(errno)); + } else { + rc = parseSpecParts(spec, path); + } + unlink(path); + free(path); + } + free(mn); + } + return rc; +} + +struct autosect_s { + const char *name; + StringBuf sb; +}; + +static rpmRC parseAutobuild(rpmSpec spec) +{ + rpmRC rc = RPMRC_OK; + char *autobuild = rpmExpand("%{?autobuild}", NULL); + if (*autobuild) { + /* XXX we should have APIs for this stuff */ + struct autosect_s autosectList[] = { + { "prep", spec->prep }, + { "conf", spec->conf }, + { "generate_buildrequires", spec->buildrequires }, + { "build", spec->build }, + { "install", spec->install }, + { "check", spec->check }, + { "clean", spec->clean }, + { NULL, NULL } + }; + + for (struct autosect_s *as = autosectList; !rc && as->name; as++) { + rc = parseAutosect(spec, autobuild, as->name, as->sb); + if (!rc && as->sb == NULL) + rc = parseAutosect(spec, "default", as->name, as->sb); + } + } + free(autobuild); + return rc; +} + static rpmSpec parseSpec(const char *specFile, rpmSpecFlags flags, const char *buildRoot, int recursing); @@ -1048,6 +1114,9 @@ static rpmRC parseSpecSection(rpmSpec *specptr, int secondary) } } + if (!secondary && parseAutobuild(spec)) + goto errxit; + /* Add arch for each package */ addArch(spec); @@ -1184,17 +1253,15 @@ rpmSpec rpmSpecParse(const char *specFile, rpmSpecFlags flags, return spec; } -rpmRC parseGeneratedSpecs(rpmSpec spec) +static rpmRC parseSpecParts(rpmSpec spec, const char *pattern) { ARGV_t argv = NULL; int argc = 0; - int i; rpmRC rc = RPMRC_OK; - char * specPattern = rpmGenPath("%{specpartsdir}", NULL, "*.specpart"); /* rpmGlob returns files sorted */ - if (rpmGlob(specPattern, &argc, &argv) == 0) { - for (i = 0; i < argc; i++) { + if (rpmGlob(pattern, &argc, &argv) == 0) { + for (int i = 0; i < argc; i++) { rpmlog(RPMLOG_NOTICE, "Reading %s\n", argv[i]); pushOFI(spec, argv[i]); snprintf(spec->fileStack->readBuf, spec->fileStack->readBufLen, @@ -1207,6 +1274,13 @@ rpmRC parseGeneratedSpecs(rpmSpec spec) } argvFree(argv); } + return rc; +} + +rpmRC parseGeneratedSpecs(rpmSpec spec) +{ + char * specPattern = rpmGenPath("%{specpartsdir}", NULL, "*.specpart"); + rpmRC rc = parseSpecParts(spec, specPattern); free(specPattern); if (!rc) { rc = finalizeSpec(spec); diff --git a/docs/manual/autobuild.md b/docs/manual/autobuild.md new file mode 100644 index 0000000000..31376bbcf4 --- /dev/null +++ b/docs/manual/autobuild.md @@ -0,0 +1,82 @@ +--- +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, and specs need to just declare which build system +they use. + +## Spec usage + +In the most simple case, a spec will have an autobuild declaration such +as `Autobuild: 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 to override the build configuration, and +this is why `%conf` is a separate section from the rest of the build, eg: + +``` +%conf +%configure --enable-experimental +``` + +However as the autobuild macros could rely on state from a previous step, +it's better to use the corresponding macro alias instead (XXX not yet +implemented in this draft): +``` +%conf +%autobuild_conf --enable-experimental +``` + +3) Complex packages can have things like multiple build systems, in +which case you might want to invoke the macros manually, eg. + +``` +%autobuild_autotools_build +cd python +%autobuild_python_build +``` + +## Supporting new build systems + +Supporting new build system types is just a matter of declaring a handful +of macros, one for any relevant build scriptlets: + +Scriptlet | Autobuild macro +------------------------------------------- +`%prep` | `%autobuild_name_prep` +`%conf` | `%autobuild_name_conf` +`%generate_buildrequires` | `%autobuild_name_generate_buildrequires` +`%build` | `%autobuild_name_build` +`%install` | `%autobuild_name_install` +`%check` | `%autobuild_name_install` +`%clean` | `%autobuiod_name_clean` + +Replace "name" with the buildsystem name, eg `%autobuild_cmake_build`. +When Autobuild: tag is set, these automatically populate the corresponding +spec section, unless the spec manually overrides it. + +For example, supporting the classic autotools case could be built on top +of existing helper macros: +``` +%autobuild_autotools_conf %configure +%autobuild_autotools_build %make_build +%autobuild_autotools_install %make_install +``` diff --git a/docs/manual/index.md b/docs/manual/index.md index f5abb3f02e..12d1ddfd3c 100644 --- a/docs/manual/index.md +++ b/docs/manual/index.md @@ -18,6 +18,7 @@ title: rpm.org - RPM Reference Manual ## Package Building * [Build Process](buildprocess.md) * [Spec Syntax](spec.md) + * [Autobuild](autobuild.md) * [Autosetup](autosetup.md) * Dependencies * [Dependencies Basics](dependencies.md) diff --git a/docs/manual/spec.md b/docs/manual/spec.md index aedf495b11..ea4310378f 100644 --- a/docs/manual/spec.md +++ b/docs/manual/spec.md @@ -206,6 +206,12 @@ with old packages that might still depend on it. Do not use in new packages. +#### Autobuild + +Automatically populate the spec build scripts for the given build system, +such as `Autobuild: autotools". See [autobuild](autobuild.md) documentation +for more details. + #### AutoReqProv #### AutoReq #### AutoProv diff --git a/include/rpm/rpmtag.h b/include/rpm/rpmtag.h index dec9c9244c..aae604f681 100644 --- a/include/rpm/rpmtag.h +++ b/include/rpm/rpmtag.h @@ -386,6 +386,7 @@ typedef enum rpmTag_e { RPMTAG_PREUNTRANSFLAGS = 5107, /* i */ RPMTAG_POSTUNTRANSFLAGS = 5108, /* i */ RPMTAG_SYSUSERS = 5109, /* s[] extension */ + RPMTAG_AUTOBUILD = 5110, /* internal */ RPMTAG_FIRSTFREE_TAG /*!< internal */ } rpmTag; diff --git a/macros.in b/macros.in index a047d1e388..8b54826d0c 100644 --- a/macros.in +++ b/macros.in @@ -1349,5 +1349,16 @@ end end } +# autobuild defaults +%autobuild_default_prep %autosetup -p1 + +# example autobuild macros for autotools +%autobuild_autotools_conf %configure +%autobuild_autotools_build %make_build +%autobuild_autotools_install\ +%make_install\ +test -d po && %find_lang %{name} ||: + # \endverbatim #*/ + diff --git a/tests/data/SOURCES/amhello-1.0.tar.gz b/tests/data/SOURCES/amhello-1.0.tar.gz new file mode 100644 index 0000000000..cd40d02ad8 Binary files /dev/null and b/tests/data/SOURCES/amhello-1.0.tar.gz differ diff --git a/tests/data/SPECS/amhello.spec b/tests/data/SPECS/amhello.spec new file mode 100644 index 0000000000..ac281d1caf --- /dev/null +++ b/tests/data/SPECS/amhello.spec @@ -0,0 +1,20 @@ +Name: amhello +Version: 1.0 +Release: 1 +Source: %{name}-%{version}.tar.gz +License: GPLv2 +Summary: Autotools example +Autobuild: autotools + +%description +%{summary} + +%build -a +cat << EOF > README.distro +Add some distro specific notes. +EOF + +%files +%doc README.distro +%{_bindir}/hello +%{_docdir}/%{name} diff --git a/tests/rpmbuild.at b/tests/rpmbuild.at index 03fcd1bb7e..09bd083b39 100644 --- a/tests/rpmbuild.at +++ b/tests/rpmbuild.at @@ -44,6 +44,27 @@ GPL []) RPMTEST_CLEANUP +AT_SETUP([rpmbuild -b autobuild]) +AT_KEYWORDS([build]) +RPMDB_INIT + +runroot rpmbuild -bb \ + --define "_prefix /usr" \ + --define "_docdir_fmt %%{NAME}" \ + --quiet /data/SPECS/amhello.spec + +RPMTEST_CHECK([ +runroot rpm -qpl --noartifact /build/RPMS/*/amhello-1.0-1.*.rpm +], +[0], +[/usr/bin/hello +/usr/share/doc/amhello +/usr/share/doc/amhello/README +/usr/share/doc/amhello/README.distro +], +[ignore]) +RPMTEST_CLEANUP + AT_SETUP([rpmbuild -b steps]) AT_KEYWORDS([build]) RPMDB_INIT