diff --git a/debhelper/Debian/Debhelper/Buildsystem/pgxs.pm b/debhelper/Debian/Debhelper/Buildsystem/pgxs.pm
new file mode 100644
index 0000000000000000000000000000000000000000..f1270ea63e47bdce3f71b05efa2190d05abf3366
--- /dev/null
+++ b/debhelper/Debian/Debhelper/Buildsystem/pgxs.pm
@@ -0,0 +1,58 @@
+# A debhelper build system class for building PostgreSQL extension modules using PGXS
+#
+# Per PostgreSQL major version, a `build-$version` subdirectory is created.
+#
+# Copyright: © 2020 Christoph Berg
+# License: GPL-2+
+
+package Debian::Debhelper::Buildsystem::pgxs;
+
+use strict;
+use warnings;
+use parent qw(Debian::Debhelper::Buildsystem);
+use Cwd;
+use Debian::Debhelper::Dh_Lib;
+use Debian::Debhelper::pgxs;
+
+sub DESCRIPTION {
+    "PGXS (PostgreSQL extensions), building in subdirectory per PostgreSQL version"
+}
+
+sub check_auto_buildable {
+    my $this=shift;
+    unless (-e $this->get_sourcepath("debian/pgversions")) {
+        error("debian/pgversions is required to build with PGXS");
+    }
+    return (-e $this->get_sourcepath("Makefile")) ? 1 : 0;
+}
+
+sub new {
+    my $class=shift;
+    my $this=$class->SUPER::new(@_);
+    $this->enforce_in_source_building();
+    return $this;
+}
+
+sub build {
+    my $this=shift;
+    $this->doit_in_sourcedir(qw(pg_buildext build build-%v));
+}
+
+sub install {
+    my $this=shift;
+    my $pattern = package_pattern();
+    $this->doit_in_sourcedir(qw(pg_buildext install build-%v), $pattern);
+}
+
+sub test {
+    my $this=shift;
+    verbose_print("Postponing tests to install stage");
+}
+
+sub clean {
+    my $this=shift;
+    my $pattern = package_pattern();
+    $this->doit_in_sourcedir(qw(pg_buildext clean build-%v), $pattern);
+}
+
+1;
diff --git a/debhelper/Debian/Debhelper/Buildsystem/pgxs_loop.pm b/debhelper/Debian/Debhelper/Buildsystem/pgxs_loop.pm
new file mode 100644
index 0000000000000000000000000000000000000000..716bcbfd2aba56e3ecf2931fcd8285193a1888ee
--- /dev/null
+++ b/debhelper/Debian/Debhelper/Buildsystem/pgxs_loop.pm
@@ -0,0 +1,33 @@
+# A debhelper build system class for building PostgreSQL extension modules using PGXS
+#
+# For packages not supporting building in subdirectories, the pgxs_loop variant builds
+# for each PostgreSQL major version in turn in the top-level directory.
+#
+# Copyright: © 2020 Christoph Berg
+# License: GPL-2+
+
+package Debian::Debhelper::Buildsystem::pgxs_loop;
+
+use strict;
+use warnings;
+use parent qw(Debian::Debhelper::Buildsystem::pgxs);
+use Cwd;
+use Debian::Debhelper::Dh_Lib;
+use Debian::Debhelper::pgxs;
+
+sub DESCRIPTION {
+    "PGXS (PostgreSQL extensions), building for each PostgreSQL version in top level directory"
+}
+
+sub build {
+    my $this=shift;
+    verbose_print("Postponing build to install stage; if this package supports out-of-tree builds, replace --buildsystem=pgxs_loop by --buildsystem=pgxs to build in the build stage");
+}
+
+sub install {
+    my $this=shift;
+    my $pattern = package_pattern();
+    $this->doit_in_sourcedir(qw(pg_buildext loop), $pattern);
+}
+
+1;
diff --git a/debhelper/Debian/Debhelper/Sequence/pgxs.pm b/debhelper/Debian/Debhelper/Sequence/pgxs.pm
new file mode 100644
index 0000000000000000000000000000000000000000..e6a56f1e1228ca574c81cd6b73efce6630f64a42
--- /dev/null
+++ b/debhelper/Debian/Debhelper/Sequence/pgxs.pm
@@ -0,0 +1,20 @@
+#!/usr/bin/perl
+
+use warnings;
+use strict;
+use Debian::Debhelper::Dh_Lib;
+
+# check if debian/control needs updating from debian/control.in
+insert_after("dh_clean", "pg_buildext");
+add_command_options("pg_buildext",  "checkcontrol");
+
+# use PGXS for clean, build, and install
+add_command_options("dh_auto_clean", "--buildsystem=pgxs");
+add_command_options("dh_auto_build", "--buildsystem=pgxs");
+add_command_options("dh_auto_install", "--buildsystem=pgxs");
+
+# move tests from dh_auto_test to dh_pgxs_test
+remove_command("dh_auto_test");
+insert_after("dh_auto_install", "dh_pgxs_test");
+
+1;
diff --git a/debhelper/Debian/Debhelper/Sequence/pgxs_loop.pm b/debhelper/Debian/Debhelper/Sequence/pgxs_loop.pm
new file mode 100644
index 0000000000000000000000000000000000000000..f1c5a1bf0fc5a176c9e325b7a6e6452fd2492dce
--- /dev/null
+++ b/debhelper/Debian/Debhelper/Sequence/pgxs_loop.pm
@@ -0,0 +1,21 @@
+#!/usr/bin/perl
+
+use warnings;
+use strict;
+use Debian::Debhelper::Dh_Lib;
+
+# check if debian/control needs updating from debian/control.in
+insert_after("dh_clean", "pg_buildext");
+add_command_options("pg_buildext",  "checkcontrol");
+
+# use PGXS for clean, build, and install
+add_command_options("dh_auto_clean", "--buildsystem=pgxs_loop");
+add_command_options("dh_auto_build", "--buildsystem=pgxs_loop");
+add_command_options("dh_auto_install", "--buildsystem=pgxs_loop");
+
+# move tests from dh_auto_test to dh_pgxs_test
+remove_command("dh_auto_test");
+insert_after("dh_auto_install", "dh_pgxs_test");
+add_command_options("dh_pgxs_test", "loop");
+
+1;
diff --git a/debhelper/Debian/Debhelper/pgxs.pm b/debhelper/Debian/Debhelper/pgxs.pm
new file mode 100644
index 0000000000000000000000000000000000000000..e3d86b2561198af6198ef6b4ac09769badc745c2
--- /dev/null
+++ b/debhelper/Debian/Debhelper/pgxs.pm
@@ -0,0 +1,38 @@
+# A debhelper build system class for building PostgreSQL extension modules using PGXS
+#
+# Copyright: © 2020 Christoph Berg
+# License: GPL-2+
+
+package Debian::Debhelper::pgxs;
+
+use strict;
+use warnings;
+use Exporter 'import';
+our @EXPORT = qw(package_pattern);
+
+=head1 package_pattern()
+
+From C<debian/control.in>, look for the package name containing the
+B<PGVERSION> placeholder, and return it in the format suitable for passing to
+B<pg_buildext>, i.e. with B<PGVERSION> replaced by B<%v>.
+
+For B<Package: postgresql-PGVERSION-unit> it will return B<postgresql-%v-unit>.
+
+Errors out if more than one package with the B<PGVERSION> placeholder is found.
+
+=cut
+
+sub package_pattern () {
+    open F, "debian/control.in" or die "debian/control.in: $!";
+    my $pattern;
+    while (<F>) {
+        if (/^Package: (.*)PGVERSION(.*)/) {
+            die "More than one Package with PGVERSION placeholder found in debian/control.in, cannot build with dh --buildsystem=pgxs. Use pg_buildext manually." if ($pattern);
+            $pattern = "$1%v$2";
+        }
+    }
+    close F;
+    return $pattern;
+}
+
+1;
diff --git a/debhelper/dh_pgxs_test b/debhelper/dh_pgxs_test
new file mode 100755
index 0000000000000000000000000000000000000000..c12bacdfcc6980b65eadb14ad4ae29d23e8fb0cb
--- /dev/null
+++ b/debhelper/dh_pgxs_test
@@ -0,0 +1,11 @@
+#!/usr/bin/perl
+
+use warnings;
+use strict;
+use Debian::Debhelper::Dh_Lib;
+use Debian::Debhelper::pgxs;
+
+my $target = (@ARGV and $ARGV[0] eq 'loop') ? "." : "build-%v";
+my $pattern = package_pattern();
+
+print_and_doit(qw(pg_buildext installcheck .), $target, $pattern);
diff --git a/debian/changelog b/debian/changelog
index 744d810a6cf98474623572960ff0a48f2eabc82d..5fd329f0f07a215866631754e2517b55c6212f8b 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -10,6 +10,11 @@ postgresql-common (217) UNRELEASED; urgency=medium
   * t/060_obsolete_confparams.t: Generate full config files dynamically.
   * dh_make_pgxs: Use current debhelper version instead of the oldest one,
     support setting homepage, general template copy-editing.
+  * Implement extension building as `dh $@ --with pgxs` and pgxs_loop, backed
+    by `--buildsystem=pgxs` and pgxs_loop. We also run `make installcheck` at
+    extension build time now via dh_pgxs_test and our PostgreSQL
+    extension_destdir patch. Previously extensions could only be tested at
+    runtime via autopkgtest.
   * Drop the PG major prefix from postgresql-all version number so extensions
     can declare sensible versioned dependencies on it.
   * Updated it debconf translation by Luca Monducci, thanks! (Closes: #969220)
diff --git a/debian/copyright b/debian/copyright
index ffab18e969dfbfd1032498e2ab150621dbe2d225..e307173e26682c565ee5187bd4fa5f763294e460 100644
--- a/debian/copyright
+++ b/debian/copyright
@@ -5,7 +5,7 @@ Files: *
 Copyright: 2005-2014 Martin Pitt <mpitt@debian.org>
            2009 Cyril Bouthors <cyril@bouthors.org>
            2010 Dimitri Fontaine <dfontaine@hi-media.com>
-           2011-2014 Christoph Berg <myon@debian.org>
+           2011-2020 Christoph Berg <myon@debian.org>
            2013 Peter Eisentraut <petere@debian.org>
 License: GPL-2+
  This program is free software; you can redistribute it and/or modify
diff --git a/debian/postgresql-server-dev-all.install b/debian/postgresql-server-dev-all.install
index ba66193c287455d43ff53d76b33d88721d0b3c8c..546bd2ef3158388f2d4425a9749148074d9a6556 100644
--- a/debian/postgresql-server-dev-all.install
+++ b/debian/postgresql-server-dev-all.install
@@ -1,4 +1,6 @@
 gitlab usr/share/postgresql-common
 pgxs_debian_control.mk /usr/share/postgresql-common/
+debhelper/Debian /usr/share/perl5
+debhelper/dh_pgxs_test /usr/bin
 dh_make_pgxs/dh_make_pgxs /usr/bin
 dh_make_pgxs/debian /usr/share/postgresql-common/dh_make_pgxs
diff --git a/dh_make_pgxs/debian/rules b/dh_make_pgxs/debian/rules
index 7b28bd80f0c7dcf54092326b3138f041aa210426..563f0e9a29701939a56f836b145cf3df0b775c55 100755
--- a/dh_make_pgxs/debian/rules
+++ b/dh_make_pgxs/debian/rules
@@ -1,21 +1,32 @@
 #!/usr/bin/make -f
 
-include /usr/share/postgresql-common/pgxs_debian_control.mk
-
-override_dh_auto_build:
-	+pg_buildext build build-%v
-
-override_dh_auto_test:
-	# nothing to do here, see debian/tests/* instead
-
-override_dh_auto_install:
-	+pg_buildext install build-%v postgresql-%v-@EXTNAME@
+%:
+	dh $@ --buildsystem=pgxs
 
 override_dh_installdocs:
 	dh_installdocs --all README.*
 
-override_dh_auto_clean:
-	+pg_buildext clean build-%v
+# if the package does not support building from subdirectories, use
+# `--buildsystem=pgxs_loop` above.
 
-%:
-	dh $@
+# classic `pg_buildext` interface:
+
+#include /usr/share/postgresql-common/pgxs_debian_control.mk
+#
+#override_dh_auto_build:
+#	+pg_buildext build build-%v
+#
+#override_dh_auto_test:
+#	# nothing to do here, see debian/tests/* instead
+#
+#override_dh_auto_install:
+#	+pg_buildext install build-%v postgresql-%v-@EXTNAME@
+#
+#override_dh_installdocs:
+#	dh_installdocs --all README.*
+#
+#override_dh_auto_clean:
+#	+pg_buildext clean build-%v
+#
+#%:
+#	dh $@
diff --git a/pg_buildext b/pg_buildext
index d01161ebeabee9da2689a579dd624a10517ff64a..ac7aa371ec66193e4b962793a860a7e87f0f859c 100755
--- a/pg_buildext
+++ b/pg_buildext
@@ -4,7 +4,7 @@
 # versions
 #
 # (C) 2010 Dimitri Fontaine <dfontaine@hi-media.com>
-# (C) 2011-2019 Christoph Berg <myon@debian.org>
+# (C) 2011-2020 Christoph Berg <myon@debian.org>
 #
 #  This program is free software; you can redistribute it and/or modify
 #  it under the terms of the GNU General Public License as published by
@@ -36,10 +36,12 @@ done
 # shift away args
 shift $(($OPTIND - 1))
 
+# positional arguments: action [srcdir] [target [opt]]
 [ "${1:-}" ] || die "no action given"
 action="$1"
 if [ -d "${2:-}" ] && [ "${3:-}" ]; then # optional: $2 is source directory
     srcdir="${2:-}"
+    [ "$srcdir" = "." ] && srcdir="$PWD"
     shift
 else
     srcdir="$PWD"
@@ -90,15 +92,12 @@ install() {
 
 clean() {
     prepare_env $1
-
-    # if a Makefile was created by configure, use it, else the top level Makefile
-    [ -f $vtarget/Makefile ] || makefile="-f $srcdir/Makefile"
-    [ -d $vtarget ] && make -C $vtarget clean ${makefile:-} PG_CONFIG="$pgc" VPATH="$srcdir" USE_PGXS=1 $MAKEVARS
-    rm -rf $vtarget
+    if [ "$vtarget" ]; then
+        rm -rf $vtarget
+    fi
 }
 
 loop() {
-    echo "### $1 ###"
     prepare_env $1
     package=$(echo $target | sed -e "s:%v:$1:g")
 
@@ -108,7 +107,6 @@ loop() {
     make -C "$srcdir"         PG_CONFIG="$pgc" USE_PGXS=1 $MAKEVARS
     echo "# $1: make install"
     make -C "$srcdir" install DESTDIR="$PWD/debian/$package" PG_CONFIG="$pgc" USE_PGXS=1 $MAKEVARS
-    echo "### done $1 ###"
 }
 
 installcheck() {
@@ -122,10 +120,16 @@ installcheck() {
         export NONROOT=1
     fi
 
-    if [ "$target" ]; then # if target is given, use it, else stay in the top source dir
+    # if a package pattern is given, tell pg_virtualenv where the installed files are
+    if [ "$opt" ]; then
+        pkg=$(echo "$opt" | sed -e "s:%v:$1:g")
+        PKGARGS="-o extension_destdir=$PWD/debian/$pkg -o dynamic_library_path=$PWD/debian/$pkg/usr/lib/postgresql/$1/lib:/usr/lib/postgresql/$1/lib"
+    fi
+
+    if [ "$target" ] && [ "$target" != "." ]; then # if target is given, use it, else stay in the top source dir
 	# if a Makefile was created by configure, use it, else the top level Makefile
 	[ -f $vtarget/Makefile ] || makefile="-f $srcdir/Makefile"
-	if ! pg_virtualenv $VENVARGS -v $1 \
+        if ! pg_virtualenv ${PKGARGS:-} $VENVARGS -v $1 \
 	    make -C $vtarget ${makefile:-} installcheck \
 		PG_CONFIG="$pgc" VPATH="$srcdir" USE_PGXS=1 $MAKEVARS; then
 	    if [ -r $vtarget/regression.diffs ]; then
@@ -135,7 +139,7 @@ installcheck() {
 	    exit 1
 	fi
     else
-	if ! pg_virtualenv $VENVARGS -v $1 \
+        if ! pg_virtualenv ${PKGARGS:-} $VENVARGS -v $1 \
 	    make installcheck PG_CONFIG="$pgc" USE_PGXS=1 $MAKEVARS; then
 	    if [ -r regression.diffs ]; then
 		echo "**** regression.diffs ****"
@@ -143,6 +147,8 @@ installcheck() {
 	    fi
 	    exit 1
 	fi
+        # since we are in the top-level directory, clean up behind us
+        make clean PG_CONFIG="$pgc" USE_PGXS=1 $MAKEVARS
     fi
 }
 
@@ -188,7 +194,7 @@ gencontrol() {
     if [ -f debian/tests/control.in ]; then
         tmptestscontrol=$(mktemp debian/tests/control.XXXXXX)
     fi
-    trap "rm -f $tmpcontrol ${tmptestscontrol:-}" 0 2 3 15
+    trap "rm -f $tmpcontrol ${tmptestscontrol:-}" EXIT
 
     export PGVERSIONS="$(versions)"
     [ "$PGVERSIONS" ] || die "No current PostgreSQL versions are supported by this package"
@@ -241,8 +247,9 @@ case $action in
     configure-*|build-*|install-*|clean-*|installcheck-*)
 	a=${action%%-*}
 	v=${action##$a-}
-	echo "### $a $v ###"
+        echo "### PostgreSQL $v $a ###"
 	$a $v
+        echo "### End $v $a ###"
 	exit
 	;;
 
@@ -273,21 +280,39 @@ case $action in
         exit
         ;;
 
+    clean)
+        if [ "$target" ]; then
+            pattern=$(echo "$target" | sed -e "s:%v:*:g")
+            echo rm -rf $pattern/
+            rm -rf $pattern/
+        fi
+        if [ "$opt" ]; then
+            pattern=$(echo "$opt" | sed -e "s:%v:*:g")
+            echo rm -rf debian/$pattern/ debian/$pattern.substvars
+            rm -rf debian/$pattern/ debian/$pattern.substvars
+        fi
+        make clean
+        exit
+        ;;
+
     installed-versions)
         installed_versions
         exit
         ;;
 
     installcheck)
-        if [ -f debian/control.in ]; then
+        # prefer testing installed versions over supported versions as the set
+        # of versions might have changed (unless a package-pattern is provided)
+        if [ -f debian/control.in ] && [ -z "$opt" ]; then
             versions=$(installed_versions)
         else
             versions=$(versions)
         fi
         [ "$versions" ] || exit 1
         for v in $versions; do
-            echo "### $action $v ###"
+            echo "### PostgreSQL $v $action ###"
             $action $v
+            echo "### End $v $action ###"
         done
         exit
         ;;
@@ -304,8 +329,9 @@ do
 
 	configure|build|install|clean|loop)
 	    [ "$target" ] || die "syntax: pg_buildext $action <target> [<srcdir>] [<opt>]"
-	    echo "### $action $v ###"
+            echo "### PostgreSQL $v $action ###"
 	    $action $v
+            echo "### End $v $action ###"
 	    ;;
 
 	*)
diff --git a/pg_buildext.pod b/pg_buildext.pod
index 27f1826cf817c60cdf099386088d8ae980367fcd..577e0ad94230250ce1901566d52de448e17838a4 100644
--- a/pg_buildext.pod
+++ b/pg_buildext.pod
@@ -15,6 +15,10 @@ C<debian/pgversions> (versions supported by the package) and in
 C</usr/share/postgresql-common/supported-versions> (versions supported in this
 release).
 
+Many PostgreSQL extension packages require no special handling at build time
+and can use B<dh $@ --with pgxs> or B<dh $@ --with pgxs_loop> to
+automatically execute the steps outlined below.
+
 =head1 USAGE
 
 Packages using B<pg_buildext> should be prepared to build binaries for
@@ -115,9 +119,9 @@ The third parameter specifies the package name to use. Most packages
 use B<postgresql-%v-pkgname>. Make will be
 called with DESTDIR="$(CURDIR)/debian/I<package>".
 
-=item B<clean> [I<src-dir>] I<build-dir>
+=item B<clean> [I<src-dir>] [I<build-dir>] [I<package-pattern>]
 
-Clean the build directory.
+Clean the build directories.
 
 =item B<loop> [I<src-dir>] I<package-pattern>
 
@@ -127,14 +131,19 @@ should be used if the package does not support VPATH builds. As it also invokes
 B<make install>, it should be placed were installation happens in debian/rules,
 rather than where build would normally be called.
 
-=item B<installcheck> [I<src-dir>] [I<build-dir>]
+=item B<installcheck> [I<src-dir>] [I<build-dir>] [I<package-pattern>]
 
 Use B<pg_virtualenv make installcheck> to run the extension regression tests.
 This is meant to be run from C<debian/tests/control> using B<autopkgtest>. If
 I<build-dir> is omitted, the top source directory is used.
 
+If I<package-pattern> is given, options are passed to B<pg_virtualenv> to set
+up the temporary PostgreSQL instance to find extension files in
+C<debian/package-directory/>.
+
 Other than the other actions which run on the "supported" versions, if C<debian/control.in> exists, this one
-runs on the "installed" versions as reported by B<installed-versions>.
+runs on the "installed" versions as reported by B<installed-versions> (unless
+I<package-pattern> is provided, which means we are called during a build).
 
 =back
 
@@ -193,7 +202,28 @@ configure the list of supported versions on your system.
   #9.6
   #11+
 
-=item B<debian/rules:>
+=item B<debian/rules> using B<dh $@ --with pgxs>:
+
+  #!/usr/bin/make -f
+
+  override_dh_installdocs:
+	  dh_installdocs --all README.*
+
+  %:
+	  dh $@ --with pgxs
+
+=item If the package does no support building from subdirectories, use B<dh $@ --with pgxs_loop>:
+
+  #!/usr/bin/make -f
+
+  %:
+	  dh $@ --with pgxs_loop
+
+=item If the package does not use PGXS's "make installcheck" for testing:
+
+  override_dh_pgxs_test:
+
+=item B<debian/rules> using B<pg_buildext> directly:
 
   #!/usr/bin/make -f
 
@@ -276,6 +306,9 @@ introduced in postgresql-common (>= 171~).
 The action B<installed-versions> was introduced in postgresql-common (>= 208~).
 B<installcheck> was switched to use it in the same version.
 
+B<dh $@ --with pgxs> and B<pgxs_loop>, and the corresponding B<--buildsystem>
+were introduced in postgresql-server-dev-all (>= 217~).
+
 =head1 SEE ALSO
 
 C</usr/share/postgresql-common/supported-versions>, autopkgtest(1),