diff --git a/configure.ac b/configure.ac index 0d08e862..1ea0fce8 100644 --- a/configure.ac +++ b/configure.ac @@ -97,6 +97,11 @@ AC_ARG_WITH(buildscript, AS_HELP_STRING([--with-buildscript=name], [set the build script name used by makepkg]), [BUILDSCRIPT=$withval], [BUILDSCRIPT=PKGBUILD]) +# Help line for buildscript filename +AC_ARG_WITH(makepkg-template-dir, + AS_HELP_STRING([--with-makepkg-template-dir=name], [set the template dir used by makepkg-template]), + [TEMPLATE_DIR=$withval], [TEMPLATE_DIR=/usr/share/makepkg-template]) + # Help line for debug package suffix AC_ARG_WITH(debug-suffix, AS_HELP_STRING([--with-debug-suffix=name], [set the suffix for split debugging symbol packages used by makepkg]), @@ -166,6 +171,16 @@ AC_PROG_INSTALL AC_CHECK_PROGS([PYTHON], [python2.7 python2.6 python2.5 python2 python], [false]) AC_PATH_PROGS([BASH_SHELL], [bash bash4], [false]) +# check for perl 5.10.1 (needed by makepkg-template) +AC_PATH_PROG([PERL],[perl]) +AC_DEFUN([AX_PROG_PERL_VERSION], + [AC_CACHE_CHECK([for Perl version $1 or later], [ax_cv_prog_perl_version], + [AS_IF(["$PERL" -e 'require v$1;' >/dev/null 2>&1], + [ax_cv_prog_perl_version=yes], + [ax_cv_prog_perl_version=no])]) + AS_IF([test x"$ax_cv_prog_perl_version" = xyes], [$2], [$3])]) +AX_PROG_PERL_VERSION([5.10.1], [], [AC_MSG_ERROR([perl is too old])]) + AS_IF([test "x$BASH_SHELL" = "xfalse"], AC_MSG_WARN([*** bash >= 4.1.0 is required for pacman scripts]), [bash_version_major=`$BASH_SHELL -c 'echo "${BASH_VERSINFO[[0]]}"'` @@ -457,6 +472,9 @@ AC_DEFINE_UNQUOTED([SRCEXT], "$SRCEXT", [The file extension used by pacman sourc # Set makepkg build script name AC_SUBST(BUILDSCRIPT) AC_DEFINE_UNQUOTED([BUILDSCRIPT], "$BUILDSCRIPT", [The build script name used by makepkg]) +# Set makepkg-template template directory +AC_SUBST(TEMPLATE_DIR) +AC_DEFINE_UNQUOTED([TEMPLATE_DIR], "$TEMPLATE_DIR", [The template directory used by makepkg-teplate]) # Set makepkg split debugging symbol package suffix AC_SUBST(DEBUGSUFFIX) AC_DEFINE_UNQUOTED([DEBUGSUFFIX], "$DEBUGSUFFIX", [The suffix for debugging symbol packages used by makepkg]) @@ -524,6 +542,7 @@ ${PACKAGE_NAME}: package extension : ${PKGEXT} source pkg extension : ${SRCEXT} build script name : ${BUILDSCRIPT} + template directory : ${TEMPLATE_DIR} Compilation options: Use libcurl : ${have_libcurl} diff --git a/doc/.gitignore b/doc/.gitignore index a96ddb30..ad496ce0 100644 --- a/doc/.gitignore +++ b/doc/.gitignore @@ -1,6 +1,7 @@ PKGBUILD.5 libalpm.3 makepkg.8 +makepkg-template.1 makepkg.conf.5 pacman.8 pacman-key.8 diff --git a/doc/Makefile.am b/doc/Makefile.am index bcb05b74..cb012551 100644 --- a/doc/Makefile.am +++ b/doc/Makefile.am @@ -6,6 +6,7 @@ ASCIIDOC_MANS = \ pacman.8 \ makepkg.8 \ + makepkg-template.1 \ repo-add.8 \ vercmp.8 \ pkgdelta.8 \ @@ -21,6 +22,7 @@ DOXYGEN_MANS = $(wildcard man3/*.3) HTML_MANPAGES = \ pacman.8.html \ makepkg.8.html \ + makepkg-template.1.html \ repo-add.8.html \ vercmp.8.html \ pkgdelta.8.html \ @@ -46,6 +48,7 @@ EXTRA_DIST = \ asciidoc-override.css \ pacman.8.txt \ makepkg.8.txt \ + makepkg-template.1.txt \ repo-add.8.txt \ vercmp.8.txt \ pkgdelta.8.txt \ @@ -147,6 +150,7 @@ $(HTML_OTHER): asciidoc.conf Makefile.am # Dependency rules pacman.8 pacman.8.html: pacman.8.txt makepkg.8 makepkg.8.html: makepkg.8.txt +makepkg-template.1 makepkg-template.1.html: makepkg-template.1.txt repo-add.8 repo-add.8.html: repo-add.8.txt vercmp.8 vercmp.8.html: vercmp.8.txt pkgdelta.8 pkgdelta.8.html: pkgdelta.8.txt diff --git a/doc/makepkg-template.1.txt b/doc/makepkg-template.1.txt new file mode 100644 index 00000000..8fa5c2e1 --- /dev/null +++ b/doc/makepkg-template.1.txt @@ -0,0 +1,120 @@ +///// +vim:set ts=4 sw=4 syntax=asciidoc noet spell spelllang=en_us: +///// +makepkg-template(1) +=================== + +Name +---- +makepkg-template - package build templating utility + + +Synopsis +-------- +'makepkg-template' [options] + + +Description +----------- + +'makepkg-template' is a script to ease the work of maintaining multiple similar +PKGBUILDs. It allows you to move most of the code from the PKGBUILD into a +template file and uses markers to allow in-place updating of existing PKGBUILDs +if the template has been changed. + +Template files can contain any code allowed in a PKGBUILD. You can think of +them like external files included with "." or "source", but they will be +inlined into the PKGBUILD by 'makepkg-template' so you do not depend on the +template file when building the package. + +Markers are bash comments in the form of: + + # template start; key=value; key2=value2; ... + +and + + # template end; + +Currently used keys are: name (mandatory) and version. Template names are limited to +alphanumerics, "@", "+", ".", "-" and "_". Versions are limited to numbers and ".". + +For initial creation there is a one line short cut which does not need an end marker: + + # template input; key=value; + +Using this short-cut will result in 'makepkg-template' replacing it with start +and end markers and the template code on the first run. + +Template files should be stored in one directory and filenames should be +"$template_name-$version.template" with a symlink "$template_name.template" +pointing to the most recent template. If the version is not set in the marker, +'makepkg-template' will automatically use the most recent version of the +template, otherwise the specified version will be used. This allows for easier +verification of untrusted PKGBUILDs if the template is trusted. You verify the +non-template code and then use a command similar to this: + + diff -u <(makepkg-template -o -) PKGBUILD + +Template files may also contain markers leading to nested templates in the +resulting PKGBUILD. If you use markers in a template, please set the version +you used/tested with in the start/input marker so other people can properly +recreate from templates. + +Options +------- + +*-p, \--input* :: + Read the package script `build script` instead of the default. + +*-o, \--output* :: + Write the updated file to `build script` instead of overwriting the input file. + +*-n, \--newest*:: + Always use the newest available template file. + +*\--template-dir* :: + Change the dir where we are looking for template files. + +Example PKGBUILD +---------------- + + pkgname=perl-config-simple + pkgver=4.58 + pkgrel=1 + pkgdesc="simple configuration file class" + arch=('any') + license=('PerlArtistic' 'GPL') + depends=('perl') + source=("http://search.cpan.org/CPAN/authors/id/S/SH/SHERZODR/Config-Simple-${pkgver}.tar.gz") + md5sums=('f014aec54f0a1e2e880d317180fce502') + _distname="Config-Simple" + + # template start; name=perl-module; version=1.0; + _distdir="${_distname}-${pkgver}" + url="https://metacpan.org/release/${_distname}" + options+=('!emptydirs') + + build() { + cd "$srcdir/$_distdir" + perl Makefile.PL INSTALLDIRS=vendor + make + } + + check() { + cd "$srcdir/$_distdir" + make test + } + + package() { + cd "$srcdir/$_distdir" + make DESTDIR="$pkgdir" install + } + # template end; + + + +See Also +-------- +linkman:makepkg[8], linkman:PKGBUILD[5] + +include::footer.txt[] diff --git a/scripts/.gitignore b/scripts/.gitignore index 9e403bfb..26e088b9 100644 --- a/scripts/.gitignore +++ b/scripts/.gitignore @@ -1,4 +1,5 @@ makepkg +makepkg-template pacman-db-upgrade pacman-key pacman-optimize diff --git a/scripts/Makefile.am b/scripts/Makefile.am index 784b1802..1f3bae24 100644 --- a/scripts/Makefile.am +++ b/scripts/Makefile.am @@ -5,6 +5,7 @@ SUBDIRS = po bin_SCRIPTS = \ $(OURSCRIPTS) \ + makepkg-template \ repo-remove \ repo-elephant @@ -18,6 +19,7 @@ OURSCRIPTS = \ EXTRA_DIST = \ makepkg.sh.in \ + makepkg-template.pl.in \ pacman-db-upgrade.sh.in \ pacman-key.sh.in \ pacman-optimize.sh.in \ @@ -54,6 +56,7 @@ edit = sed \ -e 's|@PACKAGE_BUGREPORT[@]|$(PACKAGE_BUGREPORT)|g' \ -e 's|@PACKAGE_NAME[@]|$(PACKAGE_NAME)|g' \ -e 's|@BUILDSCRIPT[@]|$(BUILDSCRIPT)|g' \ + -e 's|@TEMPLATE_DIR[@]|$(TEMPLATE_DIR)|g' \ -e 's|@DEBUGSUFFIX[@]|$(DEBUGSUFFIX)|g' \ -e "s|@INODECMD[@]|$(INODECMD)|g" \ -e 's|@SIZECMD[@]|$(SIZECMD)|g' \ @@ -76,6 +79,14 @@ makepkg: \ $(srcdir)/makepkg.sh.in \ $(srcdir)/library/parseopts.sh +makepkg-template: \ + $(srcdir)/makepkg-template.pl.in \ + Makefile + + $(AM_V_at)$(RM) -f makepkg-template + $(AM_V_GEN)$(edit) $< > $@ + $(AM_V_at)chmod +x,a-w $@ + pacman-db-upgrade: \ $(srcdir)/pacman-db-upgrade.sh.in \ $(srcdir)/library/output_format.sh diff --git a/scripts/makepkg-template.pl.in b/scripts/makepkg-template.pl.in new file mode 100755 index 00000000..1ba007bb --- /dev/null +++ b/scripts/makepkg-template.pl.in @@ -0,0 +1,168 @@ +#!/usr/bin/perl +use warnings; +use strict; +use v5.10.1; +use Cwd qw(abs_path); +use File::Spec; +use Getopt::Long; +use Pod::Usage; + +my %opts = ( + input => '@BUILDSCRIPT@', + template_dir => '@TEMPLATE_DIR@', +); + +my $template_name_charset = qr/[[:alnum:]+_.@-]/; +my $template_marker = qr/# template/; + +sub burp { + my ($file_name, @lines) = @_; + open (my $fh, ">", $file_name) || die "can't create $file_name $!" ; + print $fh @lines; + close $fh; +} + +# read a template marker line and parse values into a hash +# format is "# template (start|input); key=value; key2=value2; ..." +sub parse_template_line { + my ($line, $filename, $linenumber) = @_; + my %values; + + my ($marker, @elements) = split(/;\s?/, $line); + + ($values{command}) = ($marker =~ /$template_marker (.*)/); + + foreach my $element (@elements) { + my ($key, $val) = ($element =~ /^([a-z0-9]+)=(.*)$/); + die "invalid key/value pair $filename:$linenumber: $line" + unless $key and $val; + $values{$key} = $val; + } + + # end doesn't take arguments + if ($values{command} ne "end") { + if (!$values{name}) { + die "invalid template line: can't find template name\n", + "$filename:$linenumber: $line"; + } + + unless ($values{name} =~ /^$template_name_charset+$/) { + die "invalid chars used in name '$values{name}'. allowed: [:alnum:]+_.@-\n", + "$filename:$linenumber: $line"; + } + } + + return \%values; +} + +# load a template, process possibly existing markers (nested templates) +sub load_template { + my ($values) = @_; + + my $ret = ""; + + my $path; + if (!$opts{newest} and $values->{version}) { + $path = "$opts{template_dir}/$values->{name}-$values->{version}.template"; + } else { + $path = "$opts{template_dir}/$values->{name}.template"; + } + + # resolve symlink(s) and use the real file's name for version detection + my ($version) = (abs_path($path) =~ /-([0-9.]+)[.]template$/); + + if (!$version) { + die "Couldn't detect version for template '$values->{name}'"; + } + + my $parsed = process_file($path); + + $ret .= "# template start; name=$values->{name}; version=$version;\n"; + $ret .= $parsed; + $ret .= "# template end;\n"; + return $ret; +} + +# process input file and load templates for all markers found +sub process_file { + my ($filename) = @_; + + my $ret = ""; + my $nesting_level = 0; + my $linenumber = 0; + + open (my $fh, "<", $filename) or die "failed to open '$filename': $!"; + my @lines = <$fh>; + close $fh; + + foreach my $line (@lines) { + $linenumber++; + + if ($line =~ $template_marker) { + my $values = parse_template_line($line, $filename, $linenumber); + + given ($values->{command}) { + when (['start', 'input']) { + if ($nesting_level == 0) { + $ret .= load_template($values); + } + } + + when ('end') { + # nothing to do here, just for completeness + } + + default { + die "Unknown template marker '$values->{command}'\n", + "$filename:$linenumber: $line"; + } + } + + $nesting_level++ if $values->{command} eq "start"; + $nesting_level-- if $values->{command} eq "end"; + + # marker lines should never be added + next; + } + + # we replace code inside blocks with the template + # so we ignore the content of the block + next if $nesting_level > 0; + + $ret .= $line; + } + return $ret; +} + +Getopt::Long::Configure ("bundling"); +GetOptions( + "help" => sub {pod2usage(-exitval => 0, -verbose => 1); }, + "h" => sub {pod2usage(-exitval => 0, -verbose => 0); }, + "input|p=s" => \$opts{input}, + "output|o=s" => \$opts{output}, + "newest|n" => \$opts{newest}, + "template-dir=s" => \$opts{template_dir}, +) or pod2usage(1); + +$opts{output} = $opts{input} unless $opts{output}; + +$opts{input} = "/dev/stdin" if $opts{input} eq "-"; +$opts{output} = "/dev/stdout" if $opts{output} eq "-"; + +burp($opts{output}, process_file($opts{input})); + +__END__ +=head1 SYNOPSIS + +makepkg-template [options] + + Options: + --input, -p Build script to read (default: @BUILDSCRIPT@) + --output, -o file to output to (default: input file) + --newest, -n update templates to newest version + (default: use specified version in the template markers) + --template-dir directory to search for templates + (default: @TEMPLATE_DIR@) + +=cut +# vim: set noet: