diff --git a/Makefile b/Makefile
index f05a5699d086643a936e46d083611f1707597412..a3e78c3e500e832843b7f763caf744920c3bdf2e 100644
--- a/Makefile
+++ b/Makefile
@@ -5,6 +5,8 @@ TOP ?= model.pdf
 TOP_NAME = $(patsubst %.pdf,%,$(TOP))
 EXT = pdf log aux out bbl blg toc snm nav fdb_latexmk fls
 
+STYS       = $(wildcard texinputs/*.sty)
+
 FIGS       = $(wildcard figs/*.fig)
 FIGS_PDF   = $(patsubst %.fig,%.pdftex,$(FIGS))
 FIGS_PDF_T = $(patsubst %.fig,%.pdftex_t,$(FIGS))
@@ -19,6 +21,7 @@ NEEDED = $(FIGS_PDF) $(FIGS_PDF_T) $(SVGS_PDF) $(ODGS_PDF)
 
 # Only for package documentation.
 NEEDED_DOC = $(TOP_NAME)-slide2.pdf
+RELEASE_SCRIPT = debian/make-release
 
 PREVIEW_OPTS  = \RequirePackage[active,delayed,tightpage,graphics,pdftex] {preview}
 PREVIEW_OPTS += \PreviewMacro[{*[][]{}}]{\incode}
@@ -27,7 +30,7 @@ export TEXINPUTS := ./texinputs/:$(TEXINPUTS)
 
 #.SECONDARY: $(FIGS_PDF)
 
-.PHONY: all clean dep preview
+.PHONY: all clean dep preview release
 
 all: $(TOP)
 
@@ -35,8 +38,8 @@ $(TOP) : dep
 
 dep: $(NEEDED) $(NEEDED_DOC)
 
-%.pdf:%.tex texinputs/beamerthemetptnew.sty
-	@echo "Latex search path $(TEXINPUTS)"
+%.pdf:%.tex $(STYS)
+	@echo "LaTeX search path $(TEXINPUTS)"
 	@latexmk -pdf $<
 
 preview: dep
@@ -66,3 +69,10 @@ clean:
 # Only for package documentation.
 $(TOP_NAME)-slide2.tex : $(TOP_NAME).tex
 	@perl -pE 'if (m{^%+ Stop ici}i) { say "\\end{document}"; last; }' $< > $@
+
+# Only for package installation and packaging.
+# Usage: make release V=-v<x>.<y>.<z>.  If V is not given,
+# it will be calculated from the latest version-like tag
+# (and incremented unless working copy is clean and at the tag).
+release:
+	perl -i.orig $(RELEASE_SCRIPT) $(V) $(STYS) $(TOP_NAME).tex
diff --git a/debian/make-release b/debian/make-release
new file mode 100755
index 0000000000000000000000000000000000000000..415c59731c4fa503ec30a3175f3abc60d56b4905
--- /dev/null
+++ b/debian/make-release
@@ -0,0 +1,161 @@
+#! /usr/bin/env perl
+
+#
+# Filter calls to some LaTeX macros:
+# \date{...} -> \date{date}
+# \ProvidesPackage{...}[...] -> \ProvidesPackage{...}[date version]
+#
+# If you want to filter and replace files instead of stdin,
+# invoke with perl -i.
+#
+# Version and date are either specified on the command line (-v, -d)
+# or determined from Git tags and/or today's date.  If the Git
+# working copy in the current directory is clean and exactly at a
+# version tag, use that version and commit date.  Otherwise use
+# a version number incremented from the last version tag,
+# and today's date.
+#
+
+use 5.012;
+use strict;
+use warnings;
+use FindBin;
+use Date::Simple qw(date today);
+use Getopt::Long qw(:config bundling);
+
+# Function prototypes and global variables.
+sub parse_options();
+sub get_version_from_git();
+sub get_date_from_git();
+sub next_version($);
+our ($version, $date);
+
+#
+# Program logic.
+#
+
+# Get options and version from Git tags.
+# Increase version from Git tag unless working copy is exactly at this tag,
+# in which case also get commit date of this tag.
+parse_options();
+my ($v, $c) = get_version_from_git() unless $version;
+$v = next_version($v) if ($v && ! $c);
+$version //= $v;
+$version =~ s{^v?}{v}; # Make sure version starts with "v".
+$date //= $c ? get_date_from_git() : today();
+say STDERR "Updating to version $version, date $date.";
+
+# Standard LaTeX date isn't ISO but uses slashes.
+my $date_l = "$date";
+$date_l =~ y{-}{/};
+
+# Filter.
+while (<>) {
+  s{(\\date\s*)\{([^{}]*)\}}{${1}\{$date\}}g;
+  s{(\\ProvidesPackage\s*\{([^{}]*)\}\s*)\[([^\[\]]*)\]
+   }{$1\[$date_l $version\]}gx;
+  print;
+}
+
+# If working copy was previously in a clean state, check whether it still is.
+# In which case everything is already up to date.
+if ($c) {
+  my ($v, $d, $c) = get_version_from_git();
+  if ($c) {
+    say STDERR "Everything already up to date.";
+    exit 0;
+  } else {
+    sat STDERR "Bad version tag.  Re-run script.";
+    exit 3;
+  }
+}
+
+# Say what to do now.
+say STDERR <<EOT;
+Now check that the version is correct, commit, and apply version tag:
+
+\tgit status
+\tgit commit -a
+\tgit tag $version
+\tgit push origin $version
+
+EOT
+
+
+exit 0;
+
+#
+# Functions.
+#
+
+sub parse_options() {
+  my $usage;
+  my $date_t;
+  GetOptions('help|h|?' => sub { $usage = 1; die('!FINISH'); },
+	     'version|v=s' => \$version,
+	     'date|d=s' => \$date_t)
+    or $usage = 1;
+  if ($version && $version !~ m{^v?\d([\d\w.-])*$}) {
+    say STDERR "Illegal version: '$version'";
+    $usage = 2;
+  }
+  if ($date_t) {
+    $date = date($date_t);
+    if (! $date) {
+      say STDERR "Illegal date: '$date_t'";
+      $usage = 2;
+    }
+  }
+  if ($usage) {
+    say STDERR "Usage: $FindBin::Script [-v <version>] [-d <YYYY-MM-DD>]";
+    exit $usage;
+  }
+}
+
+
+# Get version from Git tags.
+# Returns ($version, $clean_at_tag).
+sub get_version_from_git() {
+  #
+  # Last tag and working copy state.  Use "git describe" to get
+  # latest tag matching "v<number>".  If the working copy is not
+  # at this tag, it appends -<number of commits>-g<last commit hash>;
+  # it it is dirty, it appends "-*".  Remove these parts, but note
+  # that the working copy is not at the tag.
+  #
+  my $clean_at_tag;
+  my $tag = `git describe --tags --always --dirty='-*' --match 'v[0-9]*'`;
+  chomp $tag;
+  if ($tag =~ m{-+(\*|\d+-g[0-9a-f]+)$}) {
+    $tag = $`;
+  } else {
+    $clean_at_tag = 1;
+  }
+  return ($tag, $clean_at_tag);
+}
+
+# Get date of Git last commit (as Date::Simple object).
+sub get_date_from_git() {
+  my $commit_date =
+    `git --no-pager log -1 --date=short --pretty=format:\%cd HEAD`;
+  my $d = date($commit_date)
+    or die("Git returned invalid date '$commit_date'");
+  return $d;
+}
+
+#
+# Increase version number.
+#
+sub next_version($) {
+  my ($v) = @_;
+  my @items = split(m{\b}, $v);
+
+  # Increment the last part of the version.
+  $items[-1]++;
+
+  # Make sure the version has at least 2 parts, and starts with "v".
+  push @items, ".", "0" if ($#items < 2);
+  $v = join "", @items;
+  $v =~ s{^v?}{v};
+  return $v;
+}