This allows for somewhat easy templating for PKGBUILDs.

Signed-off-by: Florian Pritz <[email protected]>
---

v4:
 - added support for "-" to --input and --output (stdin/stdout)
 - adapted manpage and fixed ordering of the arguments to the example diff call
 - lots of code suggestion + fixes by Andrew

Dave raised that questions: Do we care that my diff example in the manpage uses
"<()" which is a bashism?

Given it's only an example and should be understandable even if you don't use
bash I think it's better to keep it short rather than have it work everywhere.

Opinions welcome.

 configure.ac                   |   9 +++
 doc/.gitignore                 |   1 +
 doc/Makefile.am                |   4 +
 doc/makepkg-template.1.txt     | 132 ++++++++++++++++++++++++++++++++
 scripts/.gitignore             |   1 +
 scripts/Makefile.am            |  11 +++
 scripts/makepkg-template.pl.in | 167 +++++++++++++++++++++++++++++++++++++++++
 7 files changed, 325 insertions(+)
 create mode 100644 doc/makepkg-template.1.txt
 create mode 100755 scripts/makepkg-template.pl.in

diff --git a/configure.ac b/configure.ac
index 81bc1b3..3b0c62c 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(template-dir,
+       AS_HELP_STRING([--with-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]),
@@ -457,6 +462,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 dir
+AC_SUBST(TEMPLATE_DIR)
+AC_DEFINE_UNQUOTED([TEMPLATE_DIR], "$TEMPLATE_DIR", [The template dir 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 +532,7 @@ ${PACKAGE_NAME}:
     package extension      : ${PKGEXT}
     source pkg extension   : ${SRCEXT}
     build script name      : ${BUILDSCRIPT}
+    template dir           : ${TEMPLATE_DIR}
 
   Compilation options:
     Use libcurl            : ${have_libcurl}
diff --git a/doc/.gitignore b/doc/.gitignore
index a96ddb3..ad496ce 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 bcb05b7..cb01255 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 0000000..2f86e8f
--- /dev/null
+++ b/doc/makepkg-template.1.txt
@@ -0,0 +1,132 @@
+/////
+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 if you wish to build 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. Values are limited to
+alphanumerics, "@", "+", ".", "-" and "_".
+
+For initial creation there is a one line shortcut which doesn't need an end:
+
+       # template input; key=value;
+
+If you use this 'makepkg-template' will replace 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 untrused 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* <buildscript>::
+       Read the package script `buildscript` instead of the `PKGBUILD` default.
+
+*-o, \--output* <buildscript>::
+       Write the updated file to `buildscript` instead of overwriting the 
input file.
+
+*-n, \--newest*::
+       Always use the newest available template file.
+
+*\--template-dir* <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;
+       options=(!emptydirs)
+       _distdir="${_distname}-${pkgver}"
+       url="https://metacpan.org/release/${_distname}";
+
+       build() {
+               export PERL_MM_USE_DEFAULT=1 PERL5LIB=""                 \
+                 PERL_AUTOINSTALL=--skipdeps                            \
+                 PERL_MM_OPT="INSTALLDIRS=vendor DESTDIR='$pkgdir'"     \
+                 PERL_MB_OPT="--installdirs vendor --destdir '$pkgdir'" \
+                 MODULEBUILDRC=/dev/null
+
+               cd "$srcdir/$_distdir"
+               /usr/bin/perl Makefile.PL
+               make
+       }
+
+       check() {
+               export PERL_MM_USE_DEFAULT=1 PERL5LIB=""
+               cd "$srcdir/$_distdir"
+               make test
+       }
+
+       package() {
+               cd "$srcdir/$_distdir"
+               make DESTDIR="$pkgdir" install
+               find "$pkgdir" -name .packlist -o -name perllocal.pod -delete
+       }
+       # template end;
+
+
+
+See Also
+--------
+linkman:makepkg[8], linkman:PKGBUILD[5]
+
+include::footer.txt[]
diff --git a/scripts/.gitignore b/scripts/.gitignore
index 9e403bf..26e088b 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 784b180..1f3bae2 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 0000000..568ad6b
--- /dev/null
+++ b/scripts/makepkg-template.pl.in
@@ -0,0 +1,167 @@
+#!/usr/bin/perl
+use warnings;
+use strict;
+use v5.14;
+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 <file>     PKGBUILD to read (default: @BUILDSCRIPT@)
+   --output, -o <file>    file to output to (default: input file)
+   --newest, -n           update templates to newest possible version
+                          (default: use specified version in the template 
markers)
+   --template-dir <dir>   dir to search for templates (default: @TEMPLATE_DIR@)
+
+=cut
+# vim: set noet:
-- 
1.8.2.2

Reply via email to