summaryrefslogtreecommitdiffstats
path: root/dh
diff options
context:
space:
mode:
Diffstat (limited to '')
-rwxr-xr-xdh882
-rwxr-xr-xdh_assistant1449
-rwxr-xr-xdh_auto_build63
-rwxr-xr-xdh_auto_clean65
-rwxr-xr-xdh_auto_configure68
-rwxr-xr-xdh_auto_install113
-rwxr-xr-xdh_auto_test74
-rwxr-xr-xdh_bugfiles145
-rwxr-xr-xdh_builddeb186
-rwxr-xr-xdh_clean199
-rwxr-xr-xdh_compress253
-rwxr-xr-xdh_dwz185
-rwxr-xr-xdh_fixperms174
-rwxr-xr-xdh_gencontrol231
-rwxr-xr-xdh_icons87
-rwxr-xr-xdh_install393
-rwxr-xr-xdh_installalternatives193
-rwxr-xr-xdh_installcatalogs138
-rwxr-xr-xdh_installchangelogs411
-rwxr-xr-xdh_installcron90
-rwxr-xr-xdh_installdeb431
-rwxr-xr-xdh_installdebconf243
-rwxr-xr-xdh_installdirs141
-rwxr-xr-xdh_installdocs448
-rwxr-xr-xdh_installemacsen149
-rwxr-xr-xdh_installexamples192
-rwxr-xr-xdh_installgsettings106
-rwxr-xr-xdh_installifupdown82
-rwxr-xr-xdh_installinfo133
-rwxr-xr-xdh_installinit427
-rwxr-xr-xdh_installinitramfs103
-rwxr-xr-xdh_installlogcheck91
-rwxr-xr-xdh_installlogrotate63
-rwxr-xr-xdh_installman430
-rwxr-xr-xdh_installmanpages208
-rwxr-xr-xdh_installmenu100
-rwxr-xr-xdh_installmime73
-rwxr-xr-xdh_installmodules119
-rwxr-xr-xdh_installpam81
-rwxr-xr-xdh_installppp78
-rwxr-xr-xdh_installsystemd456
-rwxr-xr-xdh_installsystemduser288
-rwxr-xr-xdh_installsysusers115
-rwxr-xr-xdh_installtmpfiles128
-rwxr-xr-xdh_installudev112
-rwxr-xr-xdh_installwm140
-rwxr-xr-xdh_installxfonts100
-rwxr-xr-xdh_link178
-rwxr-xr-xdh_lintian72
-rwxr-xr-xdh_listpackages44
-rwxr-xr-xdh_makeshlibs511
-rwxr-xr-xdh_md5sums128
-rwxr-xr-xdh_missing271
-rwxr-xr-xdh_movefiles171
-rwxr-xr-xdh_movetousr230
-rwxr-xr-xdh_perl198
-rwxr-xr-xdh_prep80
-rwxr-xr-xdh_shlibdeps214
-rwxr-xr-xdh_strip445
-rwxr-xr-xdh_systemd_enable292
-rwxr-xr-xdh_systemd_start292
-rwxr-xr-xdh_testdir72
-rwxr-xr-xdh_testroot103
-rwxr-xr-xdh_ucf111
-rwxr-xr-xdh_update_autotools_config82
-rwxr-xr-xdh_usrlocal146
66 files changed, 14076 insertions, 0 deletions
diff --git a/dh b/dh
new file mode 100755
index 0000000..89cc776
--- /dev/null
+++ b/dh
@@ -0,0 +1,882 @@
+#!/usr/bin/perl
+
+=head1 NAME
+
+dh - debhelper command sequencer
+
+=cut
+
+use strict;
+use warnings;
+use constant {
+ 'UNSKIPPABLE_CLI_OPTIONS_BUILD_SYSTEM' => q(-S|--buildsystem|-D|--sourcedir(?:ectory)?|-B|--builddir(?:ectory)?),
+ 'BUILD_STAMP_FILE' => 'debian/debhelper-build-stamp',
+};
+use Debian::Debhelper::Dh_Lib;
+use Debian::Debhelper::Sequence;
+use Debian::Debhelper::SequencerUtil;
+use Debian::Debhelper::DH::SequenceState ();
+
+our $VERSION = DH_BUILTIN_VERSION;
+
+=head1 SYNOPSIS
+
+B<dh> I<sequence> [B<--with> I<addon>[B<,>I<addon> ...]] [B<--list>] [S<I<debhelper options>>]
+
+=head1 DESCRIPTION
+
+B<dh> runs a sequence of debhelper commands. The supported I<sequence>s
+correspond to the targets of a F<debian/rules> file: B<build-arch>,
+B<build-indep>, B<build>, B<clean>, B<install-indep>, B<install-arch>,
+B<install>, B<binary-arch>, B<binary-indep>, and B<binary>.
+
+=head1 OVERRIDE AND HOOK TARGETS
+
+A F<debian/rules> file using B<dh> can override the command that is run
+at any step in a sequence, by defining an override target. It is also
+possible to inject a command before or after any step without affecting
+the step itself.
+
+=head2 Injecting commands before or after a step
+
+I<Note>: This feature requires debhelper 12.8 or later plus the package must
+use compatibility mode 10 or later.
+
+To inject commands before I<dh_command>, add a target named
+B<execute_before_>I<dh_command> to the rules files. Similarly, if you
+want to inject commands after I<dh_command>, add the target
+B<execute_after_>I<dh_command>. Both targets can be used for the same
+I<dh_command> and also even if the command is overridden (as described in
+L</Overriding a command> below).
+
+When these targets are defined, B<dh> will call the targets respectively
+before or after it would invoke I<dh_command> (or its override target).
+
+=head2 Overriding a command
+
+To override I<dh_command>, add a target named B<override_>I<dh_command> to
+the rules file. When it would normally run I<dh_command>, B<dh> will
+instead call that target. The override target can then run the command with
+additional options, or run entirely different commands instead. See
+examples below.
+
+=head2 Architecture dependent/independent override and hook targets
+
+The override and hook targets can also be defined to run only
+when building architecture dependent or architecture independent
+packages. Use targets with names like B<override_>I<dh_command>B<-arch>
+and B<execute_after_>I<dh_command>B<-indep>.
+
+This feature is available since debhelper 8.9.7 (for override targets)
+and 12.8 (for hook targets).
+
+=head2 Completely empty targets
+
+As a special optimization, B<dh> will skip a target if it is
+completely empty and does not depend on any other target. This is
+mostly useful for override targets, where the command will simply be
+skipped without the overhead of invoking a dummy target.
+
+Note that the target has to be completely empty for this to work:
+
+ # Skip dh_bar - the good and optimized way
+ # Some rationale for skipping dh_bar goes here
+ override_dh_bar:
+
+
+ # Skip dh_foo - the slow way
+ override_dh_foo:
+ # Some rationale for skipping dh_foo goes here
+ # (these comments causes a dummy target to be run)
+
+=head2 Verifying targets are picked up by dh
+
+As of debhelper 13.10, you can use L<dh_assistant(1)> to see which override
+and hook targets will be seen by B<dh>. Here is an example run of L<dh_assistant(1)>
+along with its output:
+
+ $ dh_assistant detect-hook-targets
+ {
+ "commands-not-in-path": [
+ "dh_foo"
+ ],
+ "hook-targets": [
+ {
+ "command": "dh_strip_nondeterminism",
+ "is-empty": true,
+ "package-section-param": null,
+ "target-name": "override_dh_strip_nondeterminism"
+ },
+ {
+ "command": "dh_foo",
+ "is-empty": false,
+ "package-section-param": "-a",
+ "target-name": "override_dh_foo-arch"
+ }
+ ]
+ }
+
+The B<commands-not-in-path> is useful for spotting mistakes in the hook target
+names. A non-empty value implies one of more hook targets are related to a command
+that is either not installed or no command with that name exists at all. It is
+generally worth double checking these.
+
+Additionally, the B<is-empty> attribute for each hook target can be used for seeing
+whether a hook target triggers the L</Completely empty targets> optimization.
+
+If you are interested in the other attributes, please read the L<dh_assistant(1)>
+for the details.
+
+=head3 Verifying targets are picked up by dh (when debhelper is older than 13.10)
+
+
+
+On older versions of debhelper, you have to use B<dh> with B<--no-act>.
+You can use the following command as an example:
+
+ $ dh binary --no-act | grep dh_install | head -n5
+ dh_installdirs
+ dh_install
+ debian/rules execute_after_dh_install
+ dh_installdocs
+ dh_installchangelogs
+
+The B<debian/rules execute_after_dh_install> in the output, which signals
+that B<dh> registered a B<execute_after_dh_install> target and would
+run it directly after L<dh_install(1)>.
+
+Note that L</Completely empty targets> will be omitted in the listing above.
+This makes it a bit harder to spot as you are looking for the omission of
+a command name. But otherwise, the principle remains the same.
+
+=head2 Caveats with hook targets and makefile conditionals
+
+If you choose to wrap a hook target in makefile conditionals, please
+be aware that B<dh> computes all the hook targets a head of time and
+caches the result for that run. Furthermore, the conditionals will be
+invoked again when B<dh> calls the hook target later and will assume
+the answer did not change.
+
+The parsing and caching I<often> happens before B<dh> knows whether it
+will build arch:any (-a) or/and arch:all (-i) packages, which can
+produce confusing results - especially when L<dh_listpackages(1)> is
+part of the conditional.
+
+Most of the problems can be avoided by making the hook target
+unconditional and then have the "body" be partially or completely
+conditional. As an example:
+
+ # SIMPLE: It is well-defined what happens. The hook target
+ # is always considered. The "maybe run this" bit is
+ # conditional but dh_foo is definitely skipped.
+ #
+ # Note: The conditional is evaluated "twice" where its
+ # influences what happens. Once when dh check which hook
+ # targets exist and once when the override_dh_foo hook target
+ # is run. If *either* times return false, "maybe run this"
+ # is skipped.
+ override_dh_foo:
+ ifneq (...)
+ maybe run this
+ endif
+
+ # SIMPLE: This is also well-defined. The hook target is always
+ # run and dh_bar is skipped. The "maybe run this" bit is
+ # conditional as one might expect.
+ #
+ # Note: The conditional is still evaluated multiple times (in
+ # different process each time). However, only the evaluation
+ # that happens when the hook target is run influences what
+ # happens.
+ override_dh_bar:
+ : # Dummy command to force the target to always be run
+ ifneq (...)
+ maybe run this
+ endif
+
+
+ # COMPLICATED: This case can be non-trivial and have sharp edges.
+ # Use at your own peril if dh_listpackages in the conditional.
+ #
+ # Here, either dh_baz is run normally OR "maybe run this" is run
+ # instead.
+ #
+ # And it gets even more complicated to reason about if dh needs to
+ # recurse into debian/rules because you have an "explicit"
+ # standard target (e.g. a "build-arch:" target separate from "%:").
+ ifneq (...)
+ override_dh_baz:
+ maybe run this
+ endif
+
+These recipes are also relevant for conditional dependency targets,
+which are often seen in a variant of the following example:
+
+ COND_TASKS =
+ ifneq (...)
+ COND_TASKS += maybe-run-this
+ endif
+ ...
+
+ maybe-run-this:
+ ...
+
+ # SIMPLE: It is well-defined what happens. Either the
+ # $(COND_TASKS) are skipped or run.
+ #
+ # Note: The conditional is evaluated "twice" where its
+ # influences what happens. Once when dh check which hook
+ # targets exist and once when the override_dh_foo hook target
+ # is run. If *either* times return false, $(COND_TASKS)
+ # is skipped.
+ override_dh_foo: $(COND_TASKS)
+
+
+ # SIMPLE: This is also well-defined. The hook target is always
+ # run and dh_bar is skipped. The $(COND_TASKS) bit is
+ # conditional as one might expect.
+ #
+ # Note: The conditional is still evaluated multiple times (in
+ # different process each time). However, only the evaluation
+ # that happens when the hook target is run influences what
+ # happens.
+ override_dh_bar: $(COND_TASKS)
+ : # Dummy command to force the target to always be run
+
+ # COMPLICATED: This case can be non-trivial and have sharp edges.
+ # Use at your own peril if dh_listpackages in the conditional.
+ #
+ ifneq (...)
+ override_dh_baz: $(COND_TASKS)
+ endif
+
+
+When in doubt, pick the relevant B<SIMPLE> case in the examples above
+that match your need.
+
+=head1 OPTIONS
+
+=over 4
+
+=item B<--with> I<addon>[B<,>I<addon> ...]
+
+Add the debhelper commands specified by the given addon to appropriate places
+in the sequence of commands that is run. This option can be repeated more
+than once, or multiple addons can be listed, separated by commas.
+This is used when there is a third-party package that provides
+debhelper commands. See the F<PROGRAMMING> file for documentation about
+the sequence addon interface.
+
+A B<Build-Depends> relation on the package B<dh-sequence->I<addon>
+implies a B<--with> I<addon>. This avoids the need for an explicit
+B<--with> in F<debian/rules> that only duplicates what is already
+declared via the build dependencies in F<debian/control>. The
+relation can (since 12.5) be made optional via e.g.
+build-profiles. This enables you to easily disable an addon that
+is only useful with certain profiles (e.g. to facilitate
+bootstrapping).
+
+Since debhelper 12.5, addons can also be activated in B<indep>-only
+mode (via B<Build-Depends-Indep>) or B<arch>-only mode (via
+B<Build-Depends-Arch>). Such addons are only active in the particular
+sequence (e.g. B<binary-indep>) which simplifies dependency
+management for cross-builds.
+
+Please note that addons activated via B<Build-Depends-Indep> or
+B<Build-Depends-Arch> are subject to additional limitations to
+ensure the result is deterministic even when the addon is
+unavailable (e.g. during clean). This implies that some addons
+are incompatible with these restrictions and can only be used via
+B<Build-Depends> (or manually via F<debian/rules>). Currently,
+such addons can only add commands to sequences.
+
+=item B<--without> I<addon>
+
+The inverse of B<--with>, disables using the given addon. This option
+can be repeated more than once, or multiple addons to disable can be
+listed, separated by commas.
+
+=item B<--list>, B<-l>
+
+List all available addons.
+
+When called only with this option, B<dh> can be called from any
+directory (i.e. it does not need access to files from a source
+package).
+
+=item B<--no-act>
+
+Prints commands that would run for a given sequence, but does not run them.
+
+Note that dh normally skips running commands that it knows will do nothing.
+With --no-act, the full list of commands in a sequence is printed.
+
+=back
+
+Other options passed to B<dh> are passed on to each command it runs. This
+can be used to set an option like B<-v> or B<-X> or B<-N>, as well as for more
+specialised options.
+
+=head1 EXAMPLES
+
+To see what commands are included in a sequence, without actually doing
+anything:
+
+ dh binary-arch --no-act
+
+This is a very simple rules file, for packages where the default sequences of
+commands work with no additional options.
+
+ #!/usr/bin/make -f
+ %:
+ dh $@
+
+Often you'll want to pass an option to a specific debhelper command. The
+easy way to do with is by adding an override target for that command.
+
+ #!/usr/bin/make -f
+ %:
+ dh $@
+
+ override_dh_strip:
+ dh_strip -Xfoo
+
+ override_dh_auto_configure:
+ dh_auto_configure -- --with-foo --disable-bar
+
+Sometimes the automated L<dh_auto_configure(1)> and L<dh_auto_build(1)>
+can't guess what to do for a strange package. Here's how to avoid running
+either and instead run your own commands.
+
+ #!/usr/bin/make -f
+ %:
+ dh $@
+
+ override_dh_auto_configure:
+ ./mondoconfig
+
+ override_dh_auto_build:
+ make universe-explode-in-delight
+
+Another common case is wanting to do something manually before or
+after a particular debhelper command is run.
+
+ #!/usr/bin/make -f
+ %:
+ dh $@
+
+ # Example assumes debhelper/12.8 and compat 10+
+ execute_after_dh_fixperms:
+ chmod 4755 debian/foo/usr/bin/foo
+
+If you are on an older debhelper or compatibility level, the above
+example would have to be written as.
+
+ #!/usr/bin/make -f
+ %:
+ dh $@
+
+ # Older debhelper versions or using compat 9 or lower.
+ override_dh_fixperms:
+ dh_fixperms
+ chmod 4755 debian/foo/usr/bin/foo
+
+Python tools are not run by dh by default, due to the continual change
+in that area. Here is how to use B<dh_python2>.
+
+ #!/usr/bin/make -f
+ %:
+ dh $@ --with python2
+
+Here is how to force use of Perl's B<Module::Build> build system,
+which can be necessary if debhelper wrongly detects that the package
+uses MakeMaker.
+
+ #!/usr/bin/make -f
+ %:
+ dh $@ --buildsystem=perl_build
+
+Here is an example of overriding where the B<dh_auto_>I<*> commands find
+the package's source, for a package where the source is located in a
+subdirectory.
+
+ #!/usr/bin/make -f
+ %:
+ dh $@ --sourcedirectory=src
+
+And here is an example of how to tell the B<dh_auto_>I<*> commands to build
+in a subdirectory, which will be removed on B<clean>.
+
+ #!/usr/bin/make -f
+ %:
+ dh $@ --builddirectory=build
+
+If your package can be built in parallel, please either use compat 10 or
+pass B<--parallel> to dh. Then B<dpkg-buildpackage -j> will work.
+
+ #!/usr/bin/make -f
+ %:
+ dh $@ --parallel
+
+If your package cannot be built reliably while using multiple threads,
+please pass B<--no-parallel> to dh (or the relevant B<dh_auto_>I<*>
+command):
+
+
+ #!/usr/bin/make -f
+ %:
+ dh $@ --no-parallel
+
+Here is a way to prevent B<dh> from running several commands that you don't
+want it to run, by defining empty override targets for each command.
+
+ #!/usr/bin/make -f
+ %:
+ dh $@
+
+ # Commands not to run:
+ override_dh_auto_test override_dh_compress override_dh_fixperms:
+
+A long build process for a separate documentation package can
+be separated out using architecture independent overrides.
+These will be skipped when running build-arch and binary-arch sequences.
+
+ #!/usr/bin/make -f
+ %:
+ dh $@
+
+ override_dh_auto_build-indep:
+ $(MAKE) -C docs
+
+ # No tests needed for docs
+ override_dh_auto_test-indep:
+
+ override_dh_auto_install-indep:
+ $(MAKE) -C docs install
+
+Adding to the example above, suppose you need to chmod a file, but only
+when building the architecture dependent package, as it's not present
+when building only documentation.
+
+ # Example assumes debhelper/12.8 and compat 10+
+ execute_after_dh_fixperms-arch:
+ chmod 4755 debian/foo/usr/bin/foo
+
+=head1 DEBHELPER PROVIDED DH ADDONS
+
+The primary purpose of B<dh> addons is to provide easy integration with
+third-party provided features for debhelper. However, debhelper itself
+also provide a few sequences that can be useful in some cases. These
+are documented in this list:
+
+=over 4
+
+=item build-stamp
+
+A special addon for controlling whether B<dh> (in compat 10 or later)
+will create stamp files to tell whether the build target has been run
+successfully. See L</INTERNALS> for more details.
+
+This addon is active by default but can disabled by using
+B<dh $@ --without build-stamp>
+
+=item dwz (obsolete)
+
+Adds L<dh_dwz(1)> to the sequence in compat level 11 or below. Obsolete
+in compat 12 or later.
+
+=item elf-tools
+
+This addon adds tools related to ELF files to the sequence such as
+L<dh_strip(1)> and L<dh_shlibdeps(1)>
+
+This addon is I<conditionally> active by default for architecture
+specific packages - that is, it is skipped for arch:all packages.
+In the special case where you need these tools to work on arch:all
+packages, you can use B<--with elf-tools> to activate it
+unconditionally.
+
+=item installinitramfs (obsolete)
+
+Adds L<dh_installinitramfs(1)> to the sequence in compat level 11 or below.
+Obsolete in compat 12 or later.
+
+=item root-sequence (internal)
+
+This is reserved for internal usage.
+
+=item single-binary
+
+A special-purpose addon that makes debhelper run in "single binary" mode.
+
+When active, it will pass B<< --destdir=debian/I<package>/ >> to
+L<dh_auto_install(1)>. This makes every file "installed" by the upstream
+build system part of the (only) binary package by default without having
+to use other helpers such as L<dh_install(1)>.
+
+The addon will refuse to activate when the source package lists 2 or more
+binary packages in F<debian/control> as a precaution.
+
+Before compat 15. this behaviour was the default when there was only a single
+binary package listed in F<debian/control>. In compat 15 and later, this
+addon must explicitly be activated for this feature to work.
+
+The rationale for requiring this as an explicit choice is that if it is
+implicit then debhelper will silently change behaviour on adding a new
+binary package. This has caused many RC bugs when maintainers renamed
+a binary and added transitional packages with the intention of supporting
+seamless upgrades. The result would often be two empty binary packages that
+were uploaded to archive with users frustrated as their "upgrade" removed
+their programs.
+
+=item systemd (obsolete)
+
+Adds L<dh_systemd_enable(1)> and L<dh_systemd_start(1)> to the sequence in
+compat level 10 or below. Obsolete in compat 11 or later.
+
+=back
+
+=head1 INTERNALS
+
+If you're curious about B<dh>'s internals, here's how it works under the hood.
+
+In compat 10 (or later), B<dh> creates a stamp file
+F<debian/debhelper-build-stamp> after the build step(s) are complete
+to avoid re-running them. It is possible to avoid the stamp file by
+passing B<--without=build-stamp> to B<dh>. This makes "no clean"
+builds behave more like what some people expect at the expense of
+possibly running the build and test twice (the second time as root or
+under L<fakeroot(1)>).
+
+Inside an override target, B<dh_*> commands will create a log file
+F<debian/package.debhelper.log> to keep track of which packages the
+command(s) have been run for. These log files are then removed once
+the override target is complete.
+
+In compat 9 or earlier, each debhelper command will record
+when it's successfully run in F<debian/package.debhelper.log>. (Which
+B<dh_clean> deletes.) So B<dh> can tell which commands have already
+been run, for which packages, and skip running those commands again.
+
+Each time B<dh> is run (in compat 9 or earlier), it examines the log,
+and finds the last logged command that is in the specified
+sequence. It then continues with the next command in the sequence.
+
+A sequence can also run dependent targets in debian/rules. For
+example, the "binary" sequence runs the "install" target.
+
+B<dh> uses the B<DH_INTERNAL_OPTIONS> environment variable to pass information
+through to debhelper commands that are run inside override targets. The
+contents (and indeed, existence) of this environment variable, as the name
+might suggest, is subject to change at any time.
+
+Commands in the B<build-indep>, B<install-indep> and B<binary-indep>
+sequences are passed the B<-i> option to ensure they only work on
+architecture independent packages, and commands in the B<build-arch>,
+B<install-arch> and B<binary-arch> sequences are passed the B<-a>
+option to ensure they only work on architecture dependent packages.
+
+=cut
+
+# Stash this away before init modifies it.
+my @ARGV_orig=@ARGV;
+my (@addons, @addon_requests);
+
+# Reset umask to 0022 per #944691
+umask(0022);
+
+init(options => {
+ "until=s" => \$dh{UNTIL},
+ "after=s" => \$dh{AFTER},
+ "before=s" => \$dh{BEFORE},
+ "remaining" => \$dh{REMAINING},
+ "with=s" => sub {
+ my ($option, $value) = @_;
+ push(@addon_requests, map { "+${_}" } split(",", $value));
+ },
+ "without=s" => sub {
+ my ($option, $value) = @_;
+ push(@addon_requests, map { "-${_}" } split(",", $value));
+ },
+ "l" => \&list_addons,
+ "list" => \&list_addons,
+ },
+ # Disable complaints about unknown options; they are passed on to
+ # the debhelper commands.
+ ignore_unknown_options => 1,
+ # Bundling does not work well since there are unknown options.
+ bundling => 0,
+ internal_parse_dh_sequence_info => 1,
+ inhibit_log => 1,
+);
+set_buildflags();
+reject_obsolete_params();
+
+# If make is using a jobserver, but it is not available
+# to this process, clean out MAKEFLAGS. This avoids
+# ugly warnings when calling make.
+if (is_make_jobserver_unavailable()) {
+ clean_jobserver_makeflags();
+}
+
+# Process the sequence parameter.
+my $sequence;
+if (! compat(7)) {
+ # From v8, the sequence is the very first parameter.
+ $sequence=shift @ARGV_orig;
+ if (defined $sequence && $sequence=~/^-/) {
+ error "Unknown sequence $sequence (options should not come before the sequence)";
+ }
+}
+else {
+ # Before v8, the sequence could be at any position in the parameters,
+ # so was what was left after parsing.
+ $sequence=shift;
+ if (defined $sequence) {
+ @ARGV_orig=grep { $_ ne $sequence } @ARGV_orig;
+ }
+}
+if (! defined $sequence) {
+ error "specify a sequence to run";
+}
+# make -B causes the rules file to be run as a target.
+# Also support completely empty override targets.
+# Note: it's not safe to use rules_explicit_target before this check,
+# since it causes dh to be run.
+if ($sequence eq 'debian/rules' ||
+ $sequence =~ /^override_dh_/ ||
+ $sequence =~ /^execute_(?:after|before)_dh_/ ||
+ $sequence eq DUMMY_TARGET) {
+ exit 0;
+}
+
+
+load_sequence_addon('root-sequence', SEQUENCE_TYPE_BOTH);
+
+
+sub list_addons {
+ my %addons;
+
+ for my $inc (@INC) {
+ require File::Spec;
+ my $path = File::Spec->catdir($inc, "Debian/Debhelper/Sequence");
+ if (-d $path) {
+ for my $module_path (glob "$path/*.pm") {
+ my $name = basename($module_path);
+ $name =~ s/\.pm$//;
+ $name =~ s/_/-/g;
+ next if $name eq 'root-sequence';
+ $addons{$name} = 1;
+ }
+ }
+ }
+
+ for my $name (sort keys %addons) {
+ print "$name\n";
+ }
+
+ exit 0;
+}
+
+
+# The list of all packages that can be acted on.
+my @packages=@{$dh{DOPACKAGES}};
+my @arch_packages = getpackages("arch");
+my @indep_packages = getpackages("indep");
+my %sequence2packages = (
+ 'build-arch' => \@arch_packages,
+ 'install-arch' => \@arch_packages,
+ 'binary-arch' => \@arch_packages,
+
+ 'build-indep' => \@indep_packages,
+ 'install-indep' => \@indep_packages,
+ 'binary-indep' => \@indep_packages,
+
+ 'clean' => \@packages,
+ 'build' => \@packages,
+ 'install' => \@packages,
+ 'binary' => \@packages,
+);
+
+my $sequence_unpack_flags = 0;
+
+if ($sequence eq 'build-arch' ||
+ $sequence eq 'install-arch' ||
+ $sequence eq 'binary-arch') {
+ push(@Debian::Debhelper::DH::SequenceState::options, "-a");
+ # as an optimisation, remove from the list any packages
+ # that are not arch dependent
+ @packages = @{$sequence2packages{$sequence}};
+}
+elsif ($sequence eq 'build-indep' ||
+ $sequence eq 'install-indep' ||
+ $sequence eq 'binary-indep') {
+ push(@Debian::Debhelper::DH::SequenceState::options, "-i");
+ # ditto optimisation for arch indep
+ @packages = @{$sequence2packages{$sequence}};
+}
+
+if (not @arch_packages) {
+ $sequence_unpack_flags = FLAG_OPT_SOURCE_BUILDS_NO_ARCH_PACKAGES;
+} elsif (not @indep_packages) {
+ $sequence_unpack_flags = FLAG_OPT_SOURCE_BUILDS_NO_INDEP_PACKAGES;
+}
+
+@addons = compute_selected_addons($sequence, @addon_requests);
+
+# Load addons, which can modify sequences.
+foreach my $addon (@addons) {
+ my $addon_name = $addon->{'name'};
+ my $addon_type = $addon->{'addon-type'};
+ load_sequence_addon($addon_name, $addon_type);
+}
+
+if (%Debian::Debhelper::DH::SequenceState::commands_added_by_addon) {
+ while (my ($cmd, $addon) = each(%Debian::Debhelper::DH::SequenceState::commands_added_by_addon)) {
+ my $addon_type = $addon->{'addon-type'};
+ if ($addon_type eq 'indep') {
+ unshift(@{$Debian::Debhelper::DH::SequenceState::command_opts{$cmd}}, '-i');
+ } elsif ($addon_type eq 'arch') {
+ unshift(@{$Debian::Debhelper::DH::SequenceState::command_opts{$cmd}}, '-a');
+ }
+ }
+}
+
+
+if (! exists($Debian::Debhelper::DH::SequenceState::sequences{$sequence})) {
+ error("Unknown sequence $sequence (choose from: ".
+ join(" ", sort(keys(%Debian::Debhelper::DH::SequenceState::sequences))).")");
+}
+
+parse_dh_cmd_options(@ARGV_orig);
+
+_hoist_profile_into_dbo('nodoc');
+_hoist_profile_into_dbo('nocheck');
+
+if (is_cross_compiling() && !get_buildoption('nocheck') && !get_buildoption('crossbuildcanrunhostbinaries')) {
+ warning('Running tests during cross-builds is not supported in the general case (except for special cases or by');
+ warning('using emulation)');
+ warning('If the build fails, please consider rebuilding with DEB_BUILD_OPTIONS=nocheck.');
+ if (!is_build_profile_active('cross')) {
+ warning('Additionally, you might have to set the "cross" profile. For dpkg-buildpackage, you do this by');
+ warning('passing the -Pcross option. Check the documentation of your build tool for how to set');
+ warning('DEB_BUILD_PROFILES if you use another build tool that does not auto-configure cross-building for you');
+ }
+ warning('If you have setup the relevant support for running cross-compiled binaries and want to silence this');
+ warning('warning, please use DEB_BUILD_OPTIONS=crossbuildcanrunhostbinaries')
+}
+
+# Figure out at what point in the sequence to start for each package.
+my (%logged, %startpoint, $completed_sequences);
+
+$completed_sequences = _check_for_completed_sequences(\%sequence2packages);
+
+# In compat <= 8, the sequences are always inlined (those versions do not
+# recurse into debian/rules anyway). In compat 9+, we never inline an
+# existing rules target.
+my ($rules_targets, $full_sequence) = unpack_sequence(\%Debian::Debhelper::DH::SequenceState::sequences,
+ $sequence,
+ (!compat(8) ? 0 : 1),
+ $completed_sequences,
+ $sequence_unpack_flags,
+ );
+check_for_obsolete_commands($full_sequence);
+
+%startpoint = compute_starting_point_in_sequences(\@packages, $full_sequence, \%logged);
+
+for my $rules_command (@{$rules_targets}) {
+ my $rules_target = extract_rules_target_name($rules_command)
+ // error("Internal error: $rules_command was not a rules target!?");
+ # Don't pass DH_ environment variables, since this is
+ # a fresh invocation of debian/rules and any sub-dh commands.
+ delete($ENV{DH_INTERNAL_OPTIONS});
+ delete($ENV{DH_INTERNAL_OVERRIDE});
+ run_sequence_command_and_exit_on_failure("debian/rules", $rules_target);
+ my $override_packages = $sequence2packages{$rules_target} // \@packages;
+ for my $package (@{$override_packages}) {
+ my (undef, $seq) = unpack_sequence(\%Debian::Debhelper::DH::SequenceState::sequences, $rules_target, 1);
+ COMMAND: for my $c (reverse(@{$seq})) {
+ for my $j (0 .. $#{$full_sequence}) {
+ if ($c eq $full_sequence->[$j]) {
+ # Unfortunately, we do not guarantee any order
+ # between the run targets. Assuming e.g.
+ # "install-arch" and "build" are opaque targets
+ # then we could process "install-arch" first and
+ # then "build". In this case, it is important
+ # that we do not "reset" the starting point for
+ # "arch" packages. Otherwise, we might repeat
+ # part of the "install-arch" sequence when we
+ # should not.
+ $startpoint{$package} = $j + 1 if $j + 1 > $startpoint{$package};
+ last COMMAND;
+ }
+ }
+ }
+ }
+}
+
+run_through_command_sequence($full_sequence, \%startpoint, \%logged,
+ \@Debian::Debhelper::DH::SequenceState::options,
+ \@packages, \@arch_packages, \@indep_packages);
+
+
+sub _check_for_completed_sequences {
+ my ($sequence2packages) = @_;
+ my (%completed, %stamp_file_content);
+ if ( -f BUILD_STAMP_FILE and not compat(9)) {
+ open(my $fd, '<', BUILD_STAMP_FILE) or error("open(${\BUILD_STAMP_FILE}, ro) failed: $!");
+ while (my $line = <$fd>) {
+ chomp($line);
+ $stamp_file_content{$line} = 1;
+ }
+ close($fd);
+ my $build_indep_target_done = 1;
+ my $build_arch_target_done = 1;
+ for my $pkg (@{$sequence2packages->{'build-arch'}}) {
+ if (not $stamp_file_content{$pkg}) {
+ $build_arch_target_done = 0;
+ last;
+ }
+ }
+ for my $pkg (@{$sequence2packages->{'build-indep'}}) {
+ if (not $stamp_file_content{$pkg}) {
+ $build_indep_target_done = 0;
+ last;
+ }
+ }
+ $completed{'build-arch'} = 1 if $build_arch_target_done;
+ $completed{'build-indep'} = 1 if $build_indep_target_done;
+ $completed{'build'} = 1 if $build_indep_target_done and $build_arch_target_done;
+ }
+ return \%completed;
+}
+
+
+sub reject_obsolete_params {
+ foreach my $deprecated ('until', 'after', 'before', 'remaining') {
+ if (defined $dh{uc $deprecated}) {
+ error("The --$deprecated option is not supported any longer (#932537). Use override targets instead.");
+ }
+ }
+}
+
+sub _hoist_profile_into_dbo {
+ my ($name) = @_;
+ if (is_build_profile_active($name) && !get_buildoption($name)) {
+ $ENV{'DEB_BUILD_OPTIONS'} //= '';
+ $ENV{'DEB_BUILD_OPTIONS'} .= ' ' . $name;
+ warning("Copying ${name} into DEB_BUILD_OPTIONS: It was in DEB_BUILD_PROFILES but not in DEB_BUILD_OPTIONS");
+ }
+}
+
+=head1 SEE ALSO
+
+L<debhelper(7)>
+
+This program is a part of debhelper.
+
+=head1 AUTHOR
+
+Joey Hess <joeyh@debian.org>
+
+=cut
diff --git a/dh_assistant b/dh_assistant
new file mode 100755
index 0000000..3d3337f
--- /dev/null
+++ b/dh_assistant
@@ -0,0 +1,1449 @@
+#!/usr/bin/perl
+
+=head1 NAME
+
+dh_assistant - tool for supporting debhelper tools and provide introspection
+
+=cut
+
+use strict;
+use warnings;
+use constant EXIT_CODE_LINT_ISSUES_FOUND => 2;
+use Debian::Debhelper::Dh_Lib;
+use JSON::PP ();
+
+=head1 SYNOPSIS
+
+B<dh_assistant> B<I<command>> [S<I<additional options>>]
+
+=head1 DESCRIPTION
+
+B<dh_assistant> is a debhelper program that provides introspection into the
+debhelper stack to assist third-party tools (e.g. linters) or third-party
+debhelper implementations not using the debhelper script API (e.g., because
+they are not written in Perl).
+
+=head1 COMMANDS
+
+The B<dh_assistant> supports the following commands:
+
+=head2 active-compat-level (AJSON)
+
+B<Synopsis>: B<dh_assistant> B<active-compat-level>
+
+Outputs information about which compat level the package is using.
+
+For packages without valid debhelper compatibility information (whether missing, ambiguous,
+not supported or simply invalid), this command operates on a "best effort" basis and may abort
+when error instead of providing data.
+
+The returned JSON dictionary contains the following key-value pairs:
+
+=over 4
+
+=item active-compat-level
+
+The compat level that debhelper will be using. This is the same as B<DH_COMPAT> when present
+or else B<declared-compat-level>. This can be B<null> when no compat level can be detected.
+
+=item declared-compat-level
+
+The compat level that the package declared as its default compat level. This can be B<null>
+if the package does not declare any compat level at all.
+
+=item declared-compat-level-source
+
+Defines how the compat level was declared. This is null (for the same reason as
+B<declared-compat-level>) or one of:
+
+=over 4
+
+=item debian/compat
+
+The compatibility level was declared in the first line F<debian/compat> file.
+
+=item X-DH-Compat: <C>
+
+The compatibility was declared in the F<debian/control> via a the B<X-DH-Compat>
+field. In the output, the B<C> is replaced by the actual compatibility level.
+A full example value would be:
+
+ X-DH-Compat: 15
+
+=item Build-Depends: debhelper-compat (= <C>)
+
+The compatibility was declared in the F<debian/control> via a build dependency on the
+B<< debhelper-compat (= <C>) >> package in the B<Build-Depends> field. In the output,
+the B<C> is replaced by the actual compatibility level. A full example value would be:
+
+ Build-Depends: debhelper-compat (= 15)
+
+=back
+
+=back
+
+=head2 supported-compat-levels (AJSON, CRFA)
+
+B<Synopsis>: B<dh_assistant> B<supported-compat-levels>
+
+Outputs information about which compat levels, this build of debhelper knows
+about.
+
+This command accepts no options or arguments.
+
+=head2 which-build-system (AJSON)
+
+B<Synopsis>: B<dh_assistant> B<which-build-system> [S<I<build step>>] [S<I<build system options>>]
+
+Output information about which build system would be used for a particular build step. The build step
+must be one of B<configure>, B<build>, B<test>, B<install> or B<clean> and must be the first argument
+after B<which-build-system> when provided. If omitted, it defaults to B<configure> as it is the
+most reliable step to use auto-detection on in a clean source directory. Note that build steps do not
+always agree when using auto-detection - particularly if the B<configure> step has not been run.
+
+Additionally, the B<clean> step can also provide "surprising" results for builds that rely on
+a separate build directory. In such cases, debhelper will return the first build system that
+uses a separate build directory rather than the one build system that B<configure> would detect.
+This is generally a cosmetic issue as both build systems are all basically a glorified
+B<rm -fr builddir> and more precise detection is functionally irrelevant as far as debhelper is
+concerned.
+
+The option accepts all debhelper build system arguments - i.e., options you can pass to all of
+the B<dh_auto_*> commands plus (for the B<install> step) the B<--destdir> option. These options
+affect the output and auto-detection in various ways. Passing B<-S> or B<--buildsystem>
+overrides the auto-detection (as it does for B<dh_auto_*>) but it still provides introspection
+into the chosen build system.
+
+Things that are useful to know about the output:
+
+=over 4
+
+=item *
+
+The key B<build-system> is the build system that would be used by debhelper for the given
+step (with the given options, debhelper compat level, environment variables and the given
+working directory). When B<-S> and B<--buildsystem> are omitted, this is the result of
+debhelper's auto-detection logic.
+
+The value is valid as a parameter for the B<--buildsystem> option.
+
+The special value B<none> is used to denote that no build system would be used. This value
+is not present in B<--list> parameter for the B<dh_auto_*> commands, but since debhelper/12.9
+the value is accepted for the B<--buildsystem> option.
+
+Note that auto-detection is subject to limitations in regards to third-party build systems.
+While debhelper I<does> support auto-detecting some third-party build systems, they must be
+installed for the detection to work. If they are not installed, the detection logic silently
+skips that build system (often resulting in B<build-system> being B<none> in the output).
+
+=item *
+
+The B<build-directory> and B<buildpath> values serve different but related purposes. The
+B<build-directory> generally mirrors the B<--builddirectory> option where as B<buildpath>
+is the output directory that debhelper will use. Therefore the former will often be null
+when B<--builddirectory> has not been passed while the latter will generally not be null
+(except when B<build-system> is B<none>).
+
+=item *
+
+The B<dest-directory> (B<--destdir>) is undefined for all build steps except the B<install> build
+step (will be output as null or absent). For the same reason, B<--destdir> should only be
+passed for B<install> build step.
+
+Note that if not specified, this value is currently null by default.
+
+=item *
+
+The B<parallel> value is subject to B<DEB_BUILD_OPTIONS>. Notably, if that does not include
+the B<parallel> keyword, then B<parallel> field in the output will always be 1.
+
+=item *
+
+Most fields in the output I<can> be null. Particular if there is no build system is detected
+(or when B<--buildsystem=none>). Additionally, many of the fields can be null even if there
+is a build system if the build system does not use/set/define that variable.
+
+=back
+
+=head2 detect-hook-targets (AJSON)
+
+B<Synopsis>: B<dh_assistant> B<detect-hook-targets>
+
+Detects possible override targets and hook targets that L<dh(1)> might use (provided that the
+relevant command is in the sequence).
+
+The detection is based on scanning the rules file for any target that I<might look> like a hook
+target and can therefore list targets that are in fact not hook targets (or are but will never
+be triggered for other reasons).
+
+The detection uses a similar logic for scanning the rules file and is therefore subject to
+makefile conditionals (i.e., the truth value of makefile conditionals can change whether a hook
+target is visible in the output of this command). In theory, you would have to setup up the
+environment to look like it would during a build for getting the most accurate output. Though,
+a lot of packages will not have conditional hook targets, so the "out of the box" behaviour
+will work well in most cases.
+
+The output looks something like this:
+
+ {
+ "commands-not-in-path": [
+ "dh_foo"
+ ],
+ "hook-targets": [
+ {
+ "command": "dh_strip_nondeterminism",
+ "is-empty": true,
+ "package-section-param": null,
+ "filename": "debian/rules",
+ "target-name": "override_dh_strip_nondeterminism"
+ },
+ {
+ "command": "dh_foo",
+ "is-empty": false,
+ "package-section-param": "-a",
+ "filename": "debian/rules",
+ "target-name": "override_dh_foo-arch"
+ }
+ ]
+ }
+
+In more details:
+
+=over 4
+
+=item commands-not-in-path
+
+This attribute lists all the commands related to hook targets, which B<dh_assistant> could B<not>
+find in PATH. These are usually caused by either the command not being installed on the system
+where B<dh_assistant> is run or by the command not existing at all.
+
+If you are using this command to verify an hook target is present, please double check that the
+command is spelled correctly.
+
+=item hook-targets
+
+List over hook targets found along with additional information about them.
+
+=over 4
+
+=item command
+
+Attribute that lists which command this hook target is related too.
+
+=item target-name
+
+The actual target name detected in the F<debian/rules> file.
+
+=item is-empty
+
+A boolean that determines whether L<dh(1)> will optimize the hook out at runtime (see "Completely empty targets" in
+L<dh(1)>). Note that empty override targets will still cause L<dh(1)> to skip the original command.
+
+=item package-section-param
+
+This attribute defines what package selection parameter should be passed to B<dh_*> commands used
+in the hook target. It can either be B<-a>, B<-i> or (if no parameter should be used) C<null>.
+
+=item filename
+
+This attribute reports which file the target was found it. In most cases, this will always be "debian/rules"
+though in case of include files, the target could appear in an include file. Note this attribute is not
+super reliable as L<make(1)> only reports it for targets with a "recipe" (targets with commands inside
+them). When B<make> does not provide the filename, B<dh_assistant> blindly assumes the filename is
+"debian/rules" (as overrides via includes is not a commonly used feature).
+
+Note this accuracy of this attribute is limited about what data B<dh_assistant> can read out from the
+following command:
+
+ LC_ALL=C make -Rrnpsf debian/rules debhelper-fail-me 2>/dev/null
+
+=back
+
+=back
+
+This command accepts no options or arguments.
+
+
+=head2 detect-unknown-hook-targets (AJSON, LINT)
+
+B<Synopsis>: B<dh_assistant> B<detect-unknown-hook-targets> [--output-format=json] [command-options]
+
+Detects unknown and possibly misspelled override targets and hook targets in F<debian/rules> that
+will most likely not be used by L<dh(1)>.
+
+This command differs from B<detect-hook-targets> subtly in the scope. The B<detect-hook-targets>
+will list all targets that looks like hook targets whether they are applicable or not. This
+command show all hook targets, for which a command cannot be found in any sequence. Accordingly,
+this command is better for linting purposes whereas B<detect-hook-targets> is better if you want
+to know which hook targets are present. All the limitations listed in B<detect-hook-targets>
+about scanning the rules file apply equally to this command.
+
+This command will attempt will attempt to load any sequence add-on listed via build-dependencies
+and therefore these must be installed. Additional modules can be passed via B<--with> like with
+L<dh(1)> as needed.
+
+This command will also need one of the following perl modules to be available:
+L<Text::Levenshtein>, L<Text::LevenshteinXS>, L<Text::Levenshtein::XS>. The first one can be
+installed via B<apt install libtext-levenshtein-perl>.
+
+The text output is intended for human consumption and should be self-explanatory. Since it is
+not stable, it will not be documented. The JSON output looks something like this:
+
+ {
+ "unknown-hook-targets": [
+ {
+ "target-name": "execute_before_dh_instlal",
+ "filename": "debian/rules",
+ "candidates": [
+ "execute_before_dh_install"
+ ]
+ }
+ ]
+ }
+
+In more details:
+
+=over 4
+
+=item unknown-hook-targets
+
+List of all the unknown hook targets found along with additional information about them.
+
+=over 4
+
+=item target-name
+
+The actual target name detected in the file (usually F<debian/rules>).
+
+=item filename
+
+This attribute reports which file the target was found it. In most cases, this will always be "debian/rules"
+though in case of include files, the target could appear in an include file. Note this attribute is not
+super reliable as L<make(1)> only reports it for targets with a "recipe" (targets with commands inside
+them). When B<make> does not provide the filename, B<dh_assistant> blindly assumes the filename is
+"debian/rules" (as overrides via includes is not a commonly used feature).
+
+Note this accuracy of this attribute is limited about what data B<dh_assistant> can read out from the
+following command:
+
+ LC_ALL=C make -Rrnpsf debian/rules debhelper-fail-me 2>/dev/null
+
+=item candidates
+
+When not null and not empty, each element in this list are names for likely candidates for the
+"correct" name of this target.
+
+=item filename
+
+=back
+
+=item issues
+
+If present, then it is a list of one or more reasons why this output is definitely incomplete. Each element
+in the list is an object with the following keys:
+
+=over 4
+
+=item issue
+
+A key defining the issue. Currently, it is always B<load-addon>, which signals that B<dh_assistant> could
+not load the add-on listed in the B<addon> key.
+
+Parsers should assume new issue types may appear in the future.
+
+=item addon
+
+If present, it defines the name of a B<dh> sequence add-on that is related to the failure.
+
+=back
+
+=back
+
+This command accepts the following options:
+
+=over 4
+
+=item B<--output-format=>I<FORMAT>
+
+Request a certain type of output format. Valid values are B<text> or B<json>.
+
+The text format is intended for human consumption and may change between versions without any
+regard for machine consumption. If you want to use this command for machine consumption, please
+use the JSON format.
+
+=item B<--no-linter-exit-code>, B<--linter-exit-code>
+
+These options control whether the command should exit with the linter exit code (2) or not (0)
+when an unknown target is found. By default, it uses the linter exit code when an unknown target
+is found.
+
+=item B<--with> I<addon>, B<--without> I<addon>
+
+These options behave the same as the L<dh(1)> options with the same name.
+
+=back
+
+=head2 list-commands (RJSON)
+
+B<Synopsis>: B<dh_assistant> B<list-commands> [--output-format=json] [command-options]
+
+Load all B<dh> sequence add-ons and extract a full list of all commands that will be invoked across
+all sequences. The command makes no attempt to filter out commands that will not be run due to
+override targets or due to certain sequences not being run (by B<dh> or at all).
+
+As the command will attempt to load all plugins, they must be installed.
+
+The text output is intended for human consumption and should be self-explanatory. Since it is
+not stable, it will not be documented. The JSON output looks something like this:
+
+ {
+ "commands": [
+ {
+ "command": "dh_auto_build"
+ },
+ {
+ "command": "dh_auto_clean"
+ },
+ [... more commands listed here... ]
+ ],
+ "issues": [
+ {
+ "issue": "load-addon",
+ "addon": "foo"
+ }
+ ]
+ }
+
+=over 4
+
+=item commands
+
+The top level key containing the list of all commands. Each element in the list are an object and
+can have the following keys:
+
+=over 4
+
+=item command
+
+The name of the command.
+
+While most commands are resolved via PATH, a sequence add-on could register a command via a full path
+(by passing the path search). If so, the command provided in this output will also use the full path.
+
+=back
+
+=item issues
+
+If present, then it is a list of one or more reasons why this output is definitely incomplete. Each element
+in the list is an object with the following keys:
+
+=over 4
+
+=item issue
+
+A key defining the issue. Currently, it is always B<load-addon>, which signals that B<dh_assistant> could
+not load the add-on listed in the B<addon> key.
+
+Parsers should assume new issue types may appear in the future.
+
+=item addon
+
+If present, it defines the name of a B<dh> sequence add-on that is related to the failure.
+
+=back
+
+=back
+
+This command accepts the following options:
+
+=over 4
+
+=item B<--output-format=>I<FORMAT>
+
+Request a certain type of output format. Valid values are B<text> or B<json>.
+
+The text format is intended for human consumption and may change between versions without any
+regard for machine consumption. If you want to use this command for machine consumption, please
+use the JSON format.
+
+=item B<--with> I<addon>, B<--without> I<addon>
+
+These options behave the same as the L<dh(1)> options with the same name.
+
+=back
+
+=head2 list-guessed-dh-config-files (AJSON)
+
+B<Synopsis>: B<dh_assistant> B<list-guessed-dh-config-files> [command-options]
+
+Load all B<dh> sequence add-ons, determine the full list of commands could be used by this
+source package and for each command used, then attempt to I<guess> which "config files"
+these commands are interested in.
+
+Note this command only guesses "per command config files". Standard global config files
+such as F<debian/control>, F<debian/rules>, and F<debian/compat> are not included in this
+output.
+
+As the command name implies, the resulting output is not a full list (and will never be).
+The B<dh_assistant> tool have to derive this from optional metadata that commands can
+choose to provide and B<dh_assistant> has no means to validate that this metadata is up
+to date.
+
+As the command will attempt to load all plugins, they must be installed.
+
+The text output is intended for human consumption and should be self-explanatory. Since it is
+not stable, it will not be documented. The JSON output looks something like this:
+
+ {
+ "config-files": [
+ {
+ "commands": [
+ {
+ "command": "dh_autoreconf_clean"
+ }
+ ],
+ "file-type": "pkgfile",
+ "pkgfile": "autoreconf.before"
+ },
+ {
+ "commands": [
+ {
+ "command": "dh_installgsettings"
+ }
+ ],
+ "file-type": "pkgfile",
+ "pkgfile": "gsettings-override"
+ },
+ # [ ... more entries here ...]
+ ],
+ "issues": [
+ {
+ "issue": "load-addon",
+ "addon": "foo"
+ }
+ ]
+ }
+
+
+=over 4
+
+=item config-files
+
+The top level key containing the list of all config-files. Each element in the list are an object and
+can have the following keys:
+
+=over 4
+
+=item file-type
+
+The type of config file detected. At the time of writing, this will always be B<pkgfile>. However,
+other values may appear in the future.
+
+The B<pkgfile> key means that the config file is a B<debhelper pkgfile> (named after the B<pkgfile> sub
+in L<Debian::Debhelper::Dh_Lib> that locates the file).
+
+=item pkgfile
+
+When B<file-type> is B<pkgfile>, this key defines the name stem of the B<pkgfile>. An example, this will
+be B<install> for L<dh_install(1)>'s config file and B<docs> for L<dh_installdocs(1)>'s config file.
+
+When B<file-type> is B<not> B<pkgfile>, then this key will be absent.
+
+Typically names for these files are:
+
+ debian/PKGFILE
+ debian/PACKAGE.PKGFILE
+
+However, there are more variants caused by B<--name> plus architecture specific suffixes.
+
+=item internal
+
+This key may exist and any value for it is not standardized. Use at own peril.
+
+It used for document certain specific implementation details such as bug compatibility and may change
+as the situation changes.
+
+=item commands
+
+This key will be a list with each element in it being an object with the following keys:
+
+=over 4
+
+=item command
+
+Name of the command that is interested in this config file. Multiple commands can be interested in the same
+config file. An example of this would be B<dh_installinit>, B<dh_installsystemd> and B<dh_installtmpfiles>,
+which all reacts to (the now) deprecated B<tmpfile> pkgfile. In the particular case, only one command reacts
+to the file for a given compat level (but that information is not available to B<dh_assistant> and therefore
+is not available in this output either).
+
+=back
+
+=back
+
+=item issues
+
+If present, then it is a list of one or more reasons why this output is definitely incomplete. Each element
+in the list is an object with the following keys:
+
+=over 4
+
+=item issue
+
+A key defining the issue. Currently, it is always B<load-addon>, which signals that B<dh_assistant> could
+not load the add-on listed in the B<addon> key.
+
+Parsers should assume new issue types may appear in the future.
+
+=item addon
+
+If present, it defines the name of a B<dh> sequence add-on that is related to the failure.
+
+=back
+
+=back
+
+This command accepts the following options:
+
+=over 4
+
+=item B<--with> I<addon>, B<--without> I<addon>
+
+These options behave the same as the L<dh(1)> options with the same name.
+
+=back
+
+=head2 log-installed-files (BLD)
+
+B<Synopsis>: B<dh_assistant> B<log-installed-files> B<< -pI<pkg> >> I<[--on-behalf-of-cmd=dh_foo]> B<path ...>
+
+Mark one or more paths as installed for a given package. This is useful for telling L<dh_missing(1)> that the
+paths have been installed manually.
+
+The B<--on-behalf-of-cmd> option can be used by third-party tools to have B<dh_assistant> list them as the
+installer of the provided paths. The convention is to use the basename of the tool itself as its name
+(e.g. B<dh_install>).
+
+Please keep in mind that:
+
+=over 4
+
+=item *
+
+B<No> glob or substitution expansion is done by B<dh_assistant> on the provided paths. If you want to use globs,
+have the shell perform the expansion first.
+
+=item *
+
+Paths must be given as relative to the source root directory (e.g., F<debian/tmp/...>)
+
+=item *
+
+You I<can> provide a directory. If you do, the directory and anything recursively below it will be considered
+as installed. Note that it is fine to provide the directory even if paths inside of it has been excluded as long
+as the directory is fully "covered".
+
+=item *
+
+Do not worry about providing the same filename twice in different invocations to B<dh_assistant> due to B<-arch> /
+B<-indep> overrides. While it will be recorded multiple internally, L<dh_missing(1)> will deduplicate when it
+parses the records.
+
+=back
+
+Note this command only I<marks> paths as installed. It does not actually install them - the caller should ensure
+that the paths are in fact handled (or installed).
+
+=head2 restore-file-on-clean (BLD)
+
+B<Synopsis>: B<dh_assistant> B<restore-file-on-clean> B<FILE ...>
+
+This command will take a backup of listed files and tell L<dh_clean(1)> to restore them when it runs.
+
+Note that generally you do not need to restore modified files on clean. Often you can get away with just
+removing them if they are regenerated anyway (which is the most common case for files being modified during
+builds). Use this command when something taints a file and the build does not cope with the file being
+removed.
+
+The file is stored in B<debian/.debhelper>. If you remove this directory manually without calling
+L<dh_clean(1)> then your B<dh_assistant> provided backup is gone permanently and the restore will never
+occur. At this point, only a version control system or another backup can restore the files.
+
+The command has the following limitations:
+
+=over 4
+
+=item No thread-safety - concurrency will corrupt the restore
+
+The command relies on updating an internal index and concurrent writes will cause it to be corrupt.
+
+While most B<dh_*> commands does not use the underlying function, any of them could do so. Avoid running
+another B<dh_*> command while B<dh_assistant> processes this command (especially running multiple concurrent
+instances of B<dh_assistant restore-file-on-clean> is asking for corruption!).
+
+=item Files only, not directories nor symlinks to files
+
+This command will only restore files; not directories or symlinks to files. It will reject any non-files.
+
+Additionally, if the directory containing the file is removed, the restore will fail (as B<debhelper>
+does not track the directory, it cannot restore it reliably). If this happens, you can do a B<mkdir>
+to restore the directory and run L<dh_clean(1)> again to get the files back. After that, consider
+what went wrong and whether you are using the correct tool(s).
+
+=item Strict file names
+
+All filenames must be relative to the package root (without using the B<./> prefix). No hidden files (that
+is any file starting with a period B<.>) and no version control directories (such as B<CVS>). The checks
+are best effort.
+
+These checks are here to ensure you do not accidentally trash important data that would help you undo
+mistakes.
+
+=item Heavy duty
+
+The command takes a B<full copy> of all files you pass it. This is fine for a handful of small files,
+which is the intended use-case. If you find yourself passing 10+ files or very large files, you might
+be applying a sledgehammer where you needed a different tool.
+
+=back
+
+=head2 supports (CFFA)
+
+B<Synopsis>: B<dh_assistant> B<supports> B<COMMAND>
+
+This command is a scripting aid to programmatically determine whether B<dh_assistant> knows about a given
+subcommand. Pass the name of a subcommand and this command will exit successfully if the subcommand was known
+and unsuccessfully otherwise.
+
+=head1 COMMAND TAGS
+
+Most commands have one or more of the following "tags" associated with them. Their
+meaning is defined here.
+
+=over 4
+
+=item AJSON
+
+The command always provides JSON output. See L</JSON OUTPUT> for details.
+
+=item OJSON
+
+The command *can* provide JSON output via B<--output-format=json>, but does not
+do so by default. See L</JSON OUTPUT> for details when using B<--output-format=json>.
+
+=item LINT
+
+The command is or can be used for linting purposes. This command will exit with code 2 when an important
+issue is found.
+
+Note that commands may have options that redefine what is considered an "important" issue.
+
+=item CRFA
+
+I<Mnemonic "Can be Run From Anywhere">
+
+Most commands must be run inside a source package root directory (a directory
+containing F<debian/control>) because debhelper will need the package metadata
+to lookup the information. Any command with this tag are exempt from this
+requirement and is expected to work regardless of where they are run.
+
+=item BLD
+
+The command is intended to be used as a part of a package build. It may leave
+artifacts behind that will need a L<dh_clean(1)> invocation to remove.
+
+=back
+
+=head1 JSON OUTPUT
+
+Most commands uses JSON format as output. Consumers need to be aware that:
+
+=over 4
+
+=item *
+
+Additional keys may be added at any time. For backwards compatibility, the absence
+of a key should in general be interpreted as null unless another default is documented
+or would be "obvious" for that case.
+
+=item *
+
+Many keys can be null/undefined in special cases. As an example, some information may
+be unavailable when this command is run directly from the debhelper source (git repository).
+
+=back
+
+The output will be prettified when stdout is detected as a terminal. If
+you need to pipe the output to a pager/file (etc.) and still want it
+prettified, please use an external JSON formatter. An example of this:
+
+ dh_assistant supported-compat-levels | json_pp | less
+
+=cut
+
+my $JSON_ENCODER = JSON::PP->new->utf8;
+
+# Prettify if we think the user is reading this.
+$JSON_ENCODER = $JSON_ENCODER->pretty->space_before(0)->canonical if -t STDOUT;
+
+# We never use the log file for this tool
+inhibit_log();
+$Debian::Debhelper::Dh_Lib::PARSE_DH_SEQUENCE_INFO = 1;
+
+my %COMMANDS = (
+ 'help' => \&_do_help,
+ '-h' => \&_do_help,
+ '--help' => \&_do_help,
+ 'active-compat-level' => \&active_compat_level,
+ 'supported-compat-levels' => \&supported_compat_levels,
+ 'which-build-system' => \&which_build_system,
+ 'detect-hook-targets' => \&detect_hook_targets,
+ 'detect-unknown-hook-targets' => \&detect_unknown_hook_targets,
+ 'list-commands' => \&list_commands,
+ 'list-guessed-dh-config-files' => \&list_guessed_dh_config_files,
+ 'log-installed-files' => \&log_installed_files_cmd,
+ 'restore-file-on-clean' => \&dh_assistant_restore_file_on_clean,
+ 'supports' => \&supports,
+);
+
+my ($COMMAND) = shift(@ARGV);
+for my $arg (@ARGV) {
+ if ($arg eq '--help' or $arg eq '-h') {
+ $COMMAND = 'help';
+ last;
+ }
+}
+
+
+sub _do_help {
+ my $me = basename($0);
+ print <<"EOF";
+${me}: Tool for supporting debhelper tools and provide introspection
+
+Usage: ${me} <command> [... addition arguments or options ...]
+
+The following commands are available:
+ help Show this help
+ active-compat-level Output information about which compat level is declared/active (AJSON)
+ supported-compat-levels Output information about supported compat levels (AJSON, CRFA)
+ which-build-system Determine which build system will be used (AJSON)
+ detect-hook-targets Detect and output possible override and hook targets (AJSON)
+ detect-unknown-hook-targets
+ Detect unknown / typos of known hook targets (RJSON)
+ list-commands List all commands across all sequences (RJSON)
+ list-guessed-dh-config-files
+ List guessed "config files" for debhelper commands (AJSON)
+ log-installed-files Mark one or more paths as "installed" so dh_missing is aware (BLD)
+ restore-file-on-clean Mark one or more files as to be restored by dh_clean (BLD)
+ supports Script aid: Test whether dh_assistant knows a particular command (CRFA)
+
+Command tags:
+
+ * AJSON The command always provides JSON output.
+ * RJSON The command *can* provide JSON output via --output-format=json.
+ * LINT The command is or can be used for linting purposes. This command will exit with code 2
+ when an important issue is found.
+ * CRFA Command does not need to be run from a package source directory
+ (Mnemonic "Can be Run From Anywhere")
+ * BLD The command is intended to be used as a part of a package build.
+ It may leave artifacts behind that will need a dh_clean invocation to remove.
+
+
+Its primary purpose is to provide support for third-party debhelper implementations
+not using the debhelper script API or provide introspection for third-party tools
+(e.g., linters). Unless stated otherwise, commands must be run inside a source
+package root directory - that is, the directory containing "debian/control".
+
+Most commands use or can provide JSON output. When stdout is a TTY, the JSON will be
+prettified. See the manpage if you want formatting in other cases.
+EOF
+ return;
+}
+
+sub _assert_debian_control_exists {
+ return if -f 'debian/control';
+ require Cwd;
+ my $cwd = Cwd::getcwd();
+ warning("$cwd does not look like a package source directory (expected $cwd/debian/control to exist and be a file)");
+ error("$COMMAND must be run inside a package source directory");
+ return;
+}
+
+sub _output {
+ my ($kvpairs) = @_;
+ print $JSON_ENCODER->encode($kvpairs);
+ return;
+}
+
+sub active_compat_level {
+ if (@ARGV) {
+ error("$COMMAND: No arguments supported (please remove everything after the command)");
+ }
+ _assert_debian_control_exists();
+ my ($active_compat, $declared_compat, $declared_compat_source) = Debian::Debhelper::Dh_Lib::get_compat_info();
+ if (not defined($declared_compat_source)) {
+ $declared_compat = undef;
+ $active_compat = undef if not exists($ENV{DH_COMPAT});
+ }
+ my %compat_info = (
+ 'active-compat-level' => $active_compat,
+ 'declared-compat-level' => $declared_compat,
+ 'declared-compat-level-source' => $declared_compat_source,
+ );
+ _output(\%compat_info);
+ return;
+}
+
+sub supported_compat_levels {
+ if (@ARGV) {
+ error("$COMMAND: No arguments supported (please remove everything after the command)");
+ }
+ my %compat_levels = (
+ 'MIN_COMPAT_LEVEL' => Debian::Debhelper::Dh_Lib::MIN_COMPAT_LEVEL,
+ 'LOWEST_NON_DEPRECATED_COMPAT_LEVEL' => Debian::Debhelper::Dh_Lib::LOWEST_NON_DEPRECATED_COMPAT_LEVEL,
+ 'LOWEST_VIRTUAL_DEBHELPER_COMPAT_LEVEL' => Debian::Debhelper::Dh_Lib::LOWEST_VIRTUAL_DEBHELPER_COMPAT_LEVEL,
+ 'MAX_COMPAT_LEVEL' => Debian::Debhelper::Dh_Lib::MAX_COMPAT_LEVEL,
+ 'HIGHEST_STABLE_COMPAT_LEVEL' => Debian::Debhelper::Dh_Lib::HIGHEST_STABLE_COMPAT_LEVEL,
+ 'MIN_COMPAT_LEVEL_NOT_SCHEDULED_FOR_REMOVAL' => Debian::Debhelper::Dh_Lib::MIN_COMPAT_LEVEL_NOT_SCHEDULED_FOR_REMOVAL,
+ );
+ _output(\%compat_levels);
+ return;
+}
+
+sub which_build_system {
+ my ($opt_buildsys, $destdir);
+ my $first_argv = @ARGV ? $ARGV[0] : '';
+ my %options = (
+ # Emulate dh_auto_install's --destdir
+ "destdir=s" => \$destdir,
+ );
+ _assert_debian_control_exists();
+ # We never want the build system initialization to modify anything (e.g. create "HOME")
+ $dh{NO_ACT} = 1;
+ require Debian::Debhelper::Dh_Buildsystems;
+ Debian::Debhelper::Dh_Buildsystems::buildsystems_init(options => \%options);
+ my @non_options = grep { !m/^-/ } @ARGV;
+ my $step = @non_options ? $non_options[0] : 'configure';
+ if (@non_options && $first_argv =~ m/^-/) {
+ error("$COMMAND: If the build step is provided, it must be before any options");
+ }
+ if (@non_options > 1) {
+ error("$COMMAND: At most one positional argument is supported");
+ }
+ if (defined($destdir) and $step ne 'install') {
+ warning("$COMMAND: --destdir is not defined for build step \"$step\". Ignoring option")
+ }
+ {
+ no warnings qw(once);
+ $opt_buildsys = $Debian::Debhelper::Dh_Buildsystems::opt_buildsys;
+ }
+ my $build_system = Debian::Debhelper::Dh_Buildsystems::load_buildsystem($opt_buildsys, $step);
+ my %result = (
+ 'build-system' => defined($build_system) ? $build_system->NAME : 'none',
+ 'for-build-step' => $step,
+ 'source-directory' => defined($build_system) ? $build_system->get_sourcedir : undef,
+ 'build-directory' => defined($build_system) ? $build_system->get_builddir : undef,
+ 'dest-directory' => defined($build_system) ? $destdir : undef,
+ 'buildpath' => defined($build_system) ? $build_system->get_buildpath : undef,
+ 'parallel' => defined($build_system) ? $build_system->get_parallel : undef,
+ 'upstream-arguments' => $dh{U_PARAMS},
+ );
+ _output(\%result);
+ return;
+}
+
+sub _in_path {
+ my ($cmd) = @_;
+ for my $dir (split(':', $ENV{PATH})) {
+ return 1 if -x "${dir}/${cmd}";
+ }
+ return 0;
+}
+
+sub _load_hook_targets {
+ require Debian::Debhelper::SequencerUtil;
+ Debian::Debhelper::SequencerUtil::rules_explicit_target('does-not-matter');
+ my ($explicit_targets);
+ {
+ no warnings qw(once);
+ $explicit_targets = \%Debian::Debhelper::SequencerUtil::EXPLICIT_TARGETS;
+ }
+ return $explicit_targets;
+}
+
+sub _hook_target_variants {
+ my ($name) = @_;
+ my @base = (
+ "override_${name}",
+ "execute_before_${name}",
+ "execute_after_${name}",
+ );
+ return map {
+ (
+ $_,
+ "${_}-arch",
+ "${_}-indep",
+ )
+ } @base;
+}
+
+
+sub _load_levenshtein {
+ my @modules = (qw(
+ Text::LevenshteinXS
+ Text::Levenshtein::XS
+ Text::Levenshtein
+ ));
+ my $err;
+ for my $module (@modules) {
+ my $distance_func = eval "use $module (); \\&${module}::distance";
+ $err = $@;
+ if (defined($distance_func)) {
+ return $distance_func;
+ }
+ }
+ my $module_names = join(', ', @modules);
+ warning("Could not load any of the modules ${module_names}");
+ warning("Usually, `apt install libtext-levenshtein-perl` will fix this problem.");
+ error("This subcommand requires one of the following modules to be available: ${module_names}. Last failure was: $@");
+}
+
+
+sub _all_sequence_commands {
+ my ($forgive_errors, @addon_requests) = @_;
+ my ($sequences, @all_commands, @unloadable);
+ Debian::Debhelper::SequencerUtil::load_sequence_addon('root-sequence', 'both');
+ my @addons = Debian::Debhelper::SequencerUtil::compute_selected_addons('binary', @addon_requests);
+ # Load addons, which can modify sequences.
+ foreach my $addon (@addons) {
+ my $addon_name = $addon->{'name'};
+ my $addon_type = $addon->{'addon-type'};
+ eval {
+ Debian::Debhelper::SequencerUtil::load_sequence_addon($addon_name, $addon_type);
+ };
+ if (my $err = $@) {
+ die($err) if not $forgive_errors;
+ push(@unloadable, $addon_name);
+ }
+ }
+ {
+ no warnings qw(once);
+ $sequences = \%Debian::Debhelper::DH::SequenceState::sequences;
+ }
+ my %seen;
+ for my $sequence(values(%{$sequences})) {
+ my @commands = map {$_->[0]} $sequence->flatten_sequence('both', 0);
+ for my $command (@commands) {
+ next if $command =~ m{^(?:debian/rules|create-stamp)};
+ next if exists($seen{$command});
+ $seen{$command} = 1;
+ push(@all_commands, $command);
+ }
+ }
+ return \@all_commands, \@unloadable;
+}
+
+sub list_commands {
+ _assert_debian_control_exists();
+ require Getopt::Long;
+ Getopt::Long::config('no_ignore_case');
+ require Debian::Debhelper::SequencerUtil;
+ my $output_format = "text";
+ my (@addon_requests);
+ my %options=(
+ "output-format=s" => \$output_format,
+ "with=s" => sub {
+ my ($option, $value) = @_;
+ push(@addon_requests, map { "+${_}" } split(",", $value));
+ },
+ "without=s" => sub {
+ my ($option, $value) = @_;
+ push(@addon_requests, map { "-${_}" } split(",", $value));
+ },
+ );
+ Getopt::Long::GetOptionsFromArray(\@ARGV, %options)
+ or error("Could not parse the arguments");
+
+ if (@ARGV) {
+ my $value = $ARGV[0];
+ error("$COMMAND: No non-options supported - please remove ${value}");
+ }
+
+ my ($all_commands, $unloadables) = _all_sequence_commands(1, @addon_requests);
+ if ($output_format eq 'json') {
+ my @commands_json;
+ for my $command (sort(@{$all_commands})) {
+ push(@commands_json, {
+ 'command' => $command,
+ });
+ }
+ my %result = (
+ "commands" => \@commands_json,
+ );
+ if (@{$unloadables}) {
+ my @issues;
+ for my $addon (@{$unloadables}) {
+ push(@issues, {
+ "issue" => "load-addon",
+ "addon" => $addon,
+ });
+ };
+ $result{'issues'} = \@issues;
+ }
+ _output(\%result);
+ } elsif ($output_format eq 'text') {
+ print("Commands present in at least one sequence for this source package (sorted by name):\n");
+ for my $command (sort(@{$all_commands})) {
+ print("\t${command}\n");
+ }
+ if (@{$unloadables}) {
+ my $addon_names = join(" ", @{$unloadables});
+ print("\n");
+ warning("Incomplete result. The following sequence add-ons could not be loaded: $addon_names");
+ }
+ } else {
+ error("Internal error: Missing case for ${output_format}");
+ }
+}
+
+sub _extract_annotations {
+ my ($command) = @_;
+ my @annotations;
+
+ foreach my $dir (split(':', $ENV{PATH})) {
+ if (open (my $h, "<", "$dir/$command")) {
+ while (<$h>) {
+ if (m/PROMISE: DH NOOP( WITHOUT\s+(.*))?\s*$/) {
+ if (defined($2)) {
+ push(@annotations, split(' ', $2));
+ } else {
+ push(@annotations, 'always-skip');
+ }
+ }
+ if (m/INTROSPECTABLE: CONFIG-FILES\s+(.*)\s*$/) {
+ push(@annotations, split(' ', $1));
+ }
+ }
+ close $h;
+ return @annotations;
+ }
+ }
+ return;
+}
+
+sub list_guessed_dh_config_files {
+ _assert_debian_control_exists();
+ require Getopt::Long;
+ Getopt::Long::config('no_ignore_case');
+ require Debian::Debhelper::SequencerUtil;
+ my (@addon_requests);
+ my %options=(
+ "with=s" => sub {
+ my ($option, $value) = @_;
+ push(@addon_requests, map { "+${_}" } split(",", $value));
+ },
+ "without=s" => sub {
+ my ($option, $value) = @_;
+ push(@addon_requests, map { "-${_}" } split(",", $value));
+ },
+ );
+ Getopt::Long::GetOptionsFromArray(\@ARGV, %options)
+ or error("Could not parse the arguments");
+
+ if (@ARGV) {
+ my $value = $ARGV[0];
+ error("$COMMAND: No non-options supported - please remove ${value}");
+ }
+
+ my ($all_commands, $unloadables) = _all_sequence_commands(1, @addon_requests);
+ my $pkg_files = {};
+ my $bug_950723;
+ for my $command (@{$all_commands}) {
+ my @annotations = _extract_annotations($command);
+ next if not @annotations or $annotations[0] eq 'always-skip';
+ for my $annotation (@annotations) {
+ my $type = 'pkgfile';
+ my $need = $annotation;
+ if ($annotation =~ m/^([a-zA-Z0-9-_]+)\((.*)\)$/) {
+ ($type, $need) = ($1, $2);
+ }
+ if ($type eq 'internal') {
+ $bug_950723 = 1 if $need eq 'bug#950723';
+ }
+ next if $type ne 'pkgfile' and $type ne 'pkgfile-logged';
+ my $key = "pkgfile/${need}";
+ my $existing = $pkg_files->{$key};
+ if (defined($existing)) {
+ push(@{$existing->{'commands'}}, {
+ 'command' => $command,
+ });
+ } else {
+ $existing = {
+ 'file-type' => 'pkgfile',
+ 'pkgfile' => $need,
+ 'commands' => [{
+ 'command' => $command,
+ }],
+ };
+ $pkg_files->{$key} = $existing;
+ }
+ if ($bug_950723) {
+ $existing->{"internal"}{"bug#950723"} = JSON::PP::true;
+ }
+ }
+ }
+
+ my @config_files = values(%{$pkg_files});
+ my %result = (
+ "config-files" => \@config_files,
+ );
+ if (@{$unloadables}) {
+ my @issues;
+ for my $addon (@{$unloadables}) {
+ push(@issues, {
+ "issue" => "load-addon",
+ "addon" => $addon,
+ });
+ };
+ $result{'issues'} = \@issues;
+ }
+ _output(\%result);
+}
+
+
+sub detect_unknown_hook_targets {
+ _assert_debian_control_exists();
+ my $distance_func = _load_levenshtein();
+ require Getopt::Long;
+ Getopt::Long::config('no_ignore_case');
+ require Debian::Debhelper::SequencerUtil;
+ my $output_format = "text";
+ my (@addon_requests, %all_overrides, %unknown_hooks);
+ my $lint_exit = 1;
+ my %options=(
+ "output-format=s" => \$output_format,
+ "linter-exit-code!" => \$lint_exit,
+ "with=s" => sub {
+ my ($option, $value) = @_;
+ push(@addon_requests, map { "+${_}" } split(",", $value));
+ },
+ "without=s" => sub {
+ my ($option, $value) = @_;
+ push(@addon_requests, map { "-${_}" } split(",", $value));
+ },
+ );
+
+ Getopt::Long::GetOptionsFromArray(\@ARGV, %options)
+ or error("Could not parse the arguments");
+ if ($output_format ne 'text' and $output_format ne 'json') {
+ error("--output-format must be either text or json\n");
+ }
+ if (@ARGV) {
+ my $value = $ARGV[0];
+ error("$COMMAND: No non-options supported - please remove ${value}");
+ }
+ my $explicit_targets = _load_hook_targets();
+ my %missing_targets = map {
+ $_ => 1
+ } grep {
+ $_ ne 'debhelper-fail-me' and !m{^(?:debian/rules|create-stamp)}
+ } keys(%{$explicit_targets});
+
+ my ($all_commands, $unloadables) = _all_sequence_commands(1, @addon_requests);
+ for my $command (@{$all_commands}) {
+ for my $variant (_hook_target_variants($command)) {
+ $all_overrides{$variant} = 1;
+ delete($missing_targets{$variant});
+ }
+ }
+ my @variants = sort(keys(%all_overrides));
+ for my $target (keys(%missing_targets)) {
+ my @closest_variants;
+ my $closest_variant_distance = 9999;
+ for my $variant (@variants) {
+ next if abs(length($target) - length($variant)) > 3;
+ my $dist = $distance_func->($target, $variant);
+ next if $dist > $closest_variant_distance or $dist > 3;
+ if ($dist < $closest_variant_distance) {
+ $closest_variant_distance = $dist;
+ @closest_variants = ();
+ }
+ push(@closest_variants, $variant);
+ }
+ next if not @closest_variants and $target !~ m{^(?:override|execute_before|execute_after)_};
+ @closest_variants = sort(@closest_variants);
+ $unknown_hooks{$target} = \@closest_variants;
+ }
+
+ if ($output_format eq 'json') {
+ my @hook_target_data;
+ for my $target (sort(keys(%unknown_hooks))) {
+ my $options = $unknown_hooks{$target};
+ my (undef, $filename) = @{$explicit_targets->{$target}};
+ push(@hook_target_data, {
+ 'target-name' => $target,
+ 'filename' => $filename,
+ 'candidates' => $options,
+ });
+ }
+ my %result = (
+ "unknown-hook-targets" => \@hook_target_data,
+ );
+ if (@{$unloadables}) {
+ my @issues;
+ for my $addon (@{$unloadables}) {
+ push(@issues, {
+ "issue" => "load-addon",
+ "addon" => $addon,
+ });
+ };
+ $result{'issues'} = \@issues;
+ }
+ _output(\%result);
+ } elsif ($output_format eq 'text') {
+ for my $target (sort(keys(%unknown_hooks))) {
+ my $options = $unknown_hooks{$target};
+ my (undef, $filename) = @{$explicit_targets->{$target}};
+ my $help = '';
+ if (@{$options}) {
+ if (scalar(@{$options}) == 1) {
+ my $name = $options->[0];
+ $help = " Likely a typo of ${name}";
+ } else {
+ my $names = join(', ', @{$options});
+ $help = " Likely a typo of one of ${names}";
+ }
+ }
+ print("The hook target ${target} in ${filename} does not seem to match any known commands. ${help}\n");
+ }
+ if (@{$unloadables}) {
+ my $addon_names = join(" ", @{$unloadables});
+ print("\n");
+ warning("Incomplete result. The following sequence add-ons could not be loaded: $addon_names");
+ }
+ } else {
+ error("Internal error: Missing case for ${output_format}");
+ }
+ if ($lint_exit && (%unknown_hooks or @{$unloadables})) {
+ exit(EXIT_CODE_LINT_ISSUES_FOUND);
+ }
+ exit(0);
+}
+
+sub detect_hook_targets {
+ if (@ARGV) {
+ error("$COMMAND: No arguments supported (please remove everything after the command)");
+ }
+ _assert_debian_control_exists();
+ my $explicit_targets = _load_hook_targets();
+ my (%result, @targets, @unverifiable_commands, %seen_cmds);
+ while (my ($target, $rule_details) = each(%{$explicit_targets})) {
+ next if $target !~ m{^(?:execute_before_|execute_after_|override_)(\S+?)(-indep|-arch)?$};
+ my ($command, $archness) = ($1, $2);
+ my $param;
+ if ($archness) {
+ $param = ($archness eq '-arch') ? '-a' : '-i' ;
+ }
+ my ($non_empty, $filename) = @{$rule_details};
+
+ my $target_info = {
+ 'target-name' => $target,
+ 'command' => $command,
+ 'package-section-param' => $param,
+ 'is-empty' => $non_empty ? JSON::PP::false : JSON::PP::true,
+ 'filename' => $filename,
+ };
+ push(@targets, $target_info);
+ push(@unverifiable_commands, $command) if not exists($seen_cmds{$command}) and not _in_path($command);
+ $seen_cmds{$command} = 1;
+ }
+ $result{'hook-targets'} = \@targets;
+ $result{'commands-not-in-path'} = \@unverifiable_commands;
+ _output(\%result);
+}
+
+sub dh_assistant_restore_file_on_clean {
+ init(inhibit_log => 1);
+ if (not @ARGV) {
+ error("At least one file name is required");
+ }
+ for my $file (@ARGV) {
+ lstat($file);
+ if ( ! -f _ ) {
+ error("The path ${file} was a symlink. It must be a file; not a symlink to a file") if -l _;
+ error("The path ${file} does not exist") if not -e _;
+ error("The path ${file} was not a file and this command only supports files");
+ }
+ if ($file =~ m{[.][.]}) {
+ # Someone can provide a patch when there is a use-case for "..foo".
+ # Said patch will need to ensure the file is inside the package root dir.
+ error("Files containing \"..\" (which ${file} does) are not supported.");
+ }
+ if ($file =~ m{^/}) {
+ error("Files must be relative to the package root (which ${file} was not)")
+ }
+ if ($file =~ m{^\.} or $file =~ m{/CVS/} or $file =~ m{/\.}) {
+ error("Cowardly refusing to track hidden files / version control files (${file}).");
+ }
+ Debian::Debhelper::Dh_Lib::restore_file_on_clean($file)
+ }
+}
+
+sub log_installed_files_cmd {
+ my $on_behalf_of = 'manually-via-dh_assistant';
+ init(
+ options => {
+ 'on-behalf-of-cmd=s' => \$on_behalf_of,
+ },
+ inhibit_log => 1,
+ );
+ if (index($on_behalf_of, '/') >= 0) {
+ error('The value for --on-behalf-of-cmd must not contain slashes');
+ }
+ if (@{$dh{DOPACKAGES}} != 1) {
+ error('The log-installed-files command must act on exactly one package (use -p<pkg> to define which)');
+ }
+ my $package = $dh{DOPACKAGES}[0];
+ for my $arg (@ARGV) {
+ $arg =~ tr:/:/:s;
+ if (! -e $arg) {
+ warning("The path ${arg} does not exist - double check it is correct. Note: it will recorded anyway.");
+ }
+ }
+ log_installed_files({
+ 'package' => $package,
+ 'tool_name' => $on_behalf_of,
+ }, @ARGV);
+}
+
+sub supports {
+ my ($command, @more) = @ARGV;
+ if (@more or not defined($command)) {
+ error("$COMMAND: Please provide exactly one argument");
+ }
+ exit(0) if exists($COMMANDS{$command});
+ exit(2);
+}
+
+if (not defined($COMMAND)) {
+ error('Usage: ' . basename($0) . ' <command>');
+}
+my $handler = $COMMANDS{$COMMAND};
+if (not defined($handler)) {
+ warning("Arguments/options must not be the first argument (except for --help)")
+ if $COMMAND =~ m/^-/;
+ my $available_cmds = join(' ', sort(grep { $_ ne '-h' and $_ ne '--help' and $_ ne 'help' } keys(%COMMANDS)));
+ error("Unknown command: $COMMAND. Use \"help\" or \"--help\" as first argument for usage. Available commands: ${available_cmds}");
+}
+
+$handler->();
+
+=head1 SEE ALSO
+
+L<debhelper(7)>
+
+This program is a part of debhelper.
+
+=cut
+
+1;
diff --git a/dh_auto_build b/dh_auto_build
new file mode 100755
index 0000000..6321e19
--- /dev/null
+++ b/dh_auto_build
@@ -0,0 +1,63 @@
+#!/usr/bin/perl
+
+=head1 NAME
+
+dh_auto_build - automatically builds a package
+
+=cut
+
+use strict;
+use warnings;
+use Debian::Debhelper::Dh_Lib;
+use Debian::Debhelper::Dh_Buildsystems;
+
+our $VERSION = DH_BUILTIN_VERSION;
+
+=head1 SYNOPSIS
+
+B<dh_auto_build> [S<I<build system options>>] [S<I<debhelper options>>] [S<B<--> I<params>>]
+
+=head1 DESCRIPTION
+
+B<dh_auto_build> is a debhelper program that tries to automatically build a
+package. It does so by running the appropriate command for the build system
+it detects the package uses. For example, if a F<Makefile> is found, this is
+done by running B<make> (or B<MAKE>, if the environment variable is set). If
+there's a F<setup.py>, or F<Build.PL>, it is run to build the package.
+
+This is intended to work for about 90% of packages. If it doesn't work,
+you're encouraged to skip using B<dh_auto_build> at all, and just run the
+build process manually.
+
+=head1 OPTIONS
+
+See L<debhelper(7)/B<BUILD SYSTEM OPTIONS>> for a list of common build
+system selection and control options.
+
+=over 4
+
+=item B<--> I<params>
+
+Pass I<params> to the program that is run, after the parameters that
+B<dh_auto_build> usually passes.
+
+=back
+
+=cut
+
+# PROMISE: DH NOOP WITHOUT cli-options(BUILDSYSTEM) buildsystem(build)
+
+buildsystems_init();
+buildsystems_do();
+
+=head1 SEE ALSO
+
+L<debhelper(7)>
+
+This program is a part of debhelper.
+
+=head1 AUTHOR
+
+Joey Hess <joeyh@debian.org>
+
+=cut
diff --git a/dh_auto_clean b/dh_auto_clean
new file mode 100755
index 0000000..828f2ef
--- /dev/null
+++ b/dh_auto_clean
@@ -0,0 +1,65 @@
+#!/usr/bin/perl
+
+=head1 NAME
+
+dh_auto_clean - automatically cleans up after a build
+
+=cut
+
+use strict;
+use warnings;
+use Debian::Debhelper::Dh_Lib;
+use Debian::Debhelper::Dh_Buildsystems;
+
+our $VERSION = DH_BUILTIN_VERSION;
+
+=head1 SYNOPSIS
+
+B<dh_auto_clean> [S<I<build system options>>] [S<I<debhelper options>>] [S<B<--> I<params>>]
+
+=head1 DESCRIPTION
+
+B<dh_auto_clean> is a debhelper program that tries to automatically clean up
+after a package build. It does so by running the appropriate command for
+the build system it detects the package uses. For example, if there's a
+F<Makefile> and it contains a B<distclean>, B<realclean>, or B<clean> target,
+then this is done by running B<make> (or B<MAKE>, if the environment variable is
+set). If there is a F<setup.py> or F<Build.PL>, it is run to clean the package.
+
+This is intended to work for about 90% of packages. If it doesn't work, or
+tries to use the wrong clean target, you're encouraged to skip using
+B<dh_auto_clean> at all, and just run B<make clean> manually.
+
+=head1 OPTIONS
+
+See L<debhelper(7)/B<BUILD SYSTEM OPTIONS>> for a list of common build
+system selection and control options.
+
+=over 4
+
+=item B<--> I<params>
+
+Pass I<params> to the program that is run, after the parameters that
+B<dh_auto_clean> usually passes.
+
+=back
+
+=cut
+
+# PROMISE: DH NOOP WITHOUT cli-options(BUILDSYSTEM) buildsystem(clean)
+
+inhibit_log();
+buildsystems_init();
+buildsystems_do();
+
+=head1 SEE ALSO
+
+L<debhelper(7)>
+
+This program is a part of debhelper.
+
+=head1 AUTHOR
+
+Joey Hess <joeyh@debian.org>
+
+=cut
diff --git a/dh_auto_configure b/dh_auto_configure
new file mode 100755
index 0000000..5cdcfa0
--- /dev/null
+++ b/dh_auto_configure
@@ -0,0 +1,68 @@
+#!/usr/bin/perl
+
+=head1 NAME
+
+dh_auto_configure - automatically configure a package prior to building
+
+=cut
+
+use strict;
+use warnings;
+use Debian::Debhelper::Dh_Lib;
+use Debian::Debhelper::Dh_Buildsystems;
+
+our $VERSION = DH_BUILTIN_VERSION;
+
+=head1 SYNOPSIS
+
+B<dh_auto_configure> [S<I<build system options>>] [S<I<debhelper options>>] [S<B<--> I<params>>]
+
+=head1 DESCRIPTION
+
+B<dh_auto_configure> is a debhelper program that tries to automatically
+configure a package prior to building. It does so by running the
+appropriate command for the build system it detects the package uses.
+For example, it looks for and runs a F<./configure> script, F<Makefile.PL>,
+F<Build.PL>, or F<cmake>. A standard set of parameters is determined and passed
+to the program that is run. Some build systems, such as make, do not
+need a configure step; for these B<dh_auto_configure> will exit without
+doing anything.
+
+This is intended to work for about 90% of packages. If it doesn't work,
+you're encouraged to skip using B<dh_auto_configure> at all, and just run
+F<./configure> or its equivalent manually.
+
+=head1 OPTIONS
+
+See L<debhelper(7)/B<BUILD SYSTEM OPTIONS>> for a list of common build
+system selection and control options.
+
+=over 4
+
+=item B<--> I<params>
+
+Pass I<params> to the program that is run, after the parameters that
+B<dh_auto_configure> usually passes. For example:
+
+ dh_auto_configure -- --with-foo --enable-bar
+
+=back
+
+=cut
+
+# PROMISE: DH NOOP WITHOUT cli-options(BUILDSYSTEM) buildsystem(configure)
+
+buildsystems_init();
+buildsystems_do();
+
+=head1 SEE ALSO
+
+L<debhelper(7)>
+
+This program is a part of debhelper.
+
+=head1 AUTHOR
+
+Joey Hess <joeyh@debian.org>
+
+=cut
diff --git a/dh_auto_install b/dh_auto_install
new file mode 100755
index 0000000..420fdfd
--- /dev/null
+++ b/dh_auto_install
@@ -0,0 +1,113 @@
+#!/usr/bin/perl
+
+=head1 NAME
+
+dh_auto_install - automatically runs make install or similar
+
+=cut
+
+use strict;
+use warnings;
+use Debian::Debhelper::Dh_Lib;
+use Debian::Debhelper::Dh_Buildsystems;
+use File::Spec;
+use Cwd;
+
+our $VERSION = DH_BUILTIN_VERSION;
+
+=head1 SYNOPSIS
+
+B<dh_auto_install> [S<I<build system options>>] [S<I<debhelper options>>] [S<B<--> I<params>>]
+
+=head1 DESCRIPTION
+
+B<dh_auto_install> is a debhelper program that tries to automatically install
+built files. It does so by running the appropriate command for the build
+system it detects the package uses. For example, if there's a F<Makefile> and
+it contains a B<install> target, then this is done by running B<make> (or B<MAKE>,
+if the environment variable is set). If there is a F<setup.py> or F<Build.PL>,
+it is used. Note that the Ant build system does not support installation,
+so B<dh_auto_install> will not install files built using Ant.
+
+In compat 15 or later, B<dh_auto_install> will use F<debian/tmp> as the default
+B<--destdir> and should be moved from there to the appropriate package build
+directory using L<dh_install(1)> or similar tools. Though if the B<single-binary>
+addon for L<dh(1)> is activated, then it will pass an explicit
+B<< --destdir=debian/I<package>/ >> to B<dh_auto_install>.
+
+For earlier compat levels then unless B<--destdir> option is
+specified, the files are installed into debian/I<package>/ if there is only one
+binary package. In the multiple binary package case, the files are instead
+installed into F<debian/tmp/>, and should be moved from there to the
+appropriate package build directory using L<dh_install(1)> or similar tools.
+
+B<DESTDIR> is used to tell make where to install the files.
+If the Makefile was generated by MakeMaker from a F<Makefile.PL>, it will
+automatically set B<PREFIX=/usr> too, since such Makefiles need that.
+
+This is intended to work for about 90% of packages. If it doesn't work, or
+tries to use the wrong install target, you're encouraged to skip using
+B<dh_auto_install> at all, and just run make install manually.
+
+=head1 OPTIONS
+
+See L<debhelper(7)/B<BUILD SYSTEM OPTIONS>> for a list of common build
+system selection and control options.
+
+=over 4
+
+=item B<--destdir=>I<directory>
+
+Install files into the specified I<directory>. If this option is not specified,
+destination directory is determined automatically as described in the
+L</B<DESCRIPTION>> section.
+
+=item B<--> I<params>
+
+Pass I<params> to the program that is run, after the parameters that
+B<dh_auto_install> usually passes.
+
+=back
+
+=cut
+
+my $destdir;
+
+buildsystems_init(options => {
+ "destdir=s" => \$destdir,
+});
+
+# PROMISE: DH NOOP WITHOUT cli-options(BUILDSYSTEM) buildsystem(install)
+
+# If destdir is not specified, determine it automatically
+if (!$destdir) {
+ my @allpackages=getpackages();
+ if (@allpackages > 1 or not compat(14)) {
+ $destdir="debian/tmp";
+ }
+ else {
+ $destdir=tmpdir($dh{MAINPACKAGE});
+ }
+}
+$destdir = File::Spec->rel2abs($destdir, getcwd());
+
+if (compat(10)) {
+ # Ensure that all debian/<pkg> directories exist
+ install_dir(map { tmpdir($_) } @{$dh{DOPACKAGES}});
+} else {
+ install_dir($destdir);
+}
+
+buildsystems_do("install", $destdir);
+
+=head1 SEE ALSO
+
+L<debhelper(7)>
+
+This program is a part of debhelper.
+
+=head1 AUTHOR
+
+Joey Hess <joeyh@debian.org>
+
+=cut
diff --git a/dh_auto_test b/dh_auto_test
new file mode 100755
index 0000000..05acea8
--- /dev/null
+++ b/dh_auto_test
@@ -0,0 +1,74 @@
+#!/usr/bin/perl
+
+=head1 NAME
+
+dh_auto_test - automatically runs a package's test suites
+
+=cut
+
+use strict;
+use warnings;
+use Debian::Debhelper::Dh_Lib;
+use Debian::Debhelper::Dh_Buildsystems;
+
+our $VERSION = DH_BUILTIN_VERSION;
+
+=head1 SYNOPSIS
+
+B<dh_auto_test> [S<I<build system options>>] [S<I<debhelper options>>] [S<B<--> I<params>>]
+
+=head1 DESCRIPTION
+
+B<dh_auto_test> is a debhelper program that tries to automatically run a
+package's test suite. It does so by running the appropriate command for the
+build system it detects the package uses. For example, if there's a
+Makefile and it contains a B<test> or B<check> target, then this is done by
+running B<make> (or B<MAKE>, if the environment variable is set). If the test
+suite fails, the command will exit nonzero. If there's no test suite, it
+will exit zero without doing anything.
+
+This is intended to work for about 90% of packages with a test suite. If it
+doesn't work, you're encouraged to skip using B<dh_auto_test> at all, and
+just run the test suite manually.
+
+=head1 OPTIONS
+
+See L<debhelper(7)/B<BUILD SYSTEM OPTIONS>> for a list of common build
+system selection and control options.
+
+=over 4
+
+=item B<--> I<params>
+
+Pass I<params> to the program that is run, after the parameters that
+B<dh_auto_test> usually passes.
+
+=back
+
+=head1 NOTES
+
+If the B<DEB_BUILD_OPTIONS> environment variable contains B<nocheck>, no
+tests will be performed.
+
+=cut
+
+# PROMISE: DH NOOP WITHOUT cli-options(BUILDSYSTEM) buildsystem(test)
+
+if (get_buildoption("nocheck")) {
+ exit 0;
+}
+
+buildsystems_init();
+buildsystems_do();
+
+=head1 SEE ALSO
+
+L<debhelper(7)>
+
+This program is a part of debhelper.
+
+=head1 AUTHOR
+
+Joey Hess <joeyh@debian.org>
+
+=cut
diff --git a/dh_bugfiles b/dh_bugfiles
new file mode 100755
index 0000000..6222922
--- /dev/null
+++ b/dh_bugfiles
@@ -0,0 +1,145 @@
+#!/usr/bin/perl
+
+=head1 NAME
+
+dh_bugfiles - install bug reporting customization files into package build directories
+
+=cut
+
+use strict;
+use warnings;
+use Debian::Debhelper::Dh_Lib;
+
+our $VERSION = DH_BUILTIN_VERSION;
+
+=head1 SYNOPSIS
+
+B<dh_bugfiles> [B<-A>] [S<I<debhelper options>>]
+
+=head1 DESCRIPTION
+
+B<dh_bugfiles> is a debhelper program that is responsible for installing
+bug reporting customization files (bug scripts and/or bug control files
+and/or presubj files) into package build directories.
+
+=head1 FILES
+
+=over 4
+
+=item debian/I<package>.bug-script
+
+This is the script to be run by the bug reporting program for generating a bug
+report template. This file is installed as F<usr/share/bug/package> in the
+package build directory if no other types of bug reporting customization
+files are going to be installed for the package in question. Otherwise,
+this file is installed as F<usr/share/bug/package/script>. Finally, the
+installed script is given execute permissions.
+
+=item debian/I<package>.bug-control
+
+It is the bug control file containing some directions for the bug reporting
+tool. This file is installed as F<usr/share/bug/package/control> in the
+package build directory.
+
+=item debian/I<package>.bug-presubj
+
+The contents of this file are displayed to the user by the bug reporting
+tool before allowing the user to write a bug report on the package to the
+Debian Bug Tracking System. This file is installed as
+F<usr/share/bug/package/presubj> in the package build directory.
+
+=back
+
+=head1 OPTIONS
+
+=over 4
+
+=item B<-A>, B<--all>
+
+Install F<debian/bug-*> files to ALL packages acted on when respective
+F<debian/package.bug-*> files do not exist. Normally, F<debian/bug-*> will
+be installed to the first package only.
+
+=back
+
+=cut
+
+init();
+
+# Types of bug files this debhelper program handles.
+# Hash value is the name of the pkgfile of the respective
+# type.
+my %bugfile_types = (
+ "script" => "bug-script",
+ "control" => "bug-control",
+ "presubj" => "bug-presubj",
+);
+# PROMISE: DH NOOP WITHOUT bug-script bug-control bug-presubj cli-options()
+
+foreach my $package (@{$dh{DOPACKAGES}}) {
+ next if is_udeb($package);
+
+ my $tmp=tmpdir($package);
+ my $p_dir="${tmp}/usr/share/bug";
+ my $dir="${p_dir}/$package";
+
+ # Gather information which bug files are available for the
+ # package in question
+ my %bugfiles=();
+ while (my ($type, $pkgfilename) = each(%bugfile_types)) {
+ my $file=pkgfile($package,$pkgfilename);
+ if ($file) {
+ $bugfiles{$type}=$file;
+ }
+ elsif (-f "debian/$pkgfilename" && $dh{PARAMS_ALL}) {
+ $bugfiles{$type}="debian/$pkgfilename";
+ }
+ }
+
+ # If there is only a bug script to install, install it as
+ # usr/share/bug/$package (unless this path is a directory)
+ if (! -d $dir && scalar(keys(%bugfiles)) == 1 && exists $bugfiles{script}) {
+ install_dir($p_dir);
+ install_prog($bugfiles{script}, $dir);
+ }
+ elsif (scalar(keys(%bugfiles)) > 0) {
+ if (-f $dir) {
+ # Move usr/share/bug/$package to usr/share/bug/$package/script
+ rename_path($dir, "${dir}.tmp");
+ install_dir($dir);
+ rename_path("${dir}.tmp", "$dir/script");
+ }
+ else {
+ install_dir($dir);
+ }
+ while (my ($type, $srcfile) = each(%bugfiles)) {
+ if ($type eq 'script') {
+ install_prog($srcfile, "$dir/$type");
+ } else {
+ install_file($srcfile, "$dir/$type");
+ }
+ }
+ }
+
+ # Ensure that the bug script is executable
+ if (-f $dir) {
+ reset_perm_and_owner(0755, $dir);
+ }
+ elsif (-f "$dir/script") {
+ reset_perm_and_owner(0755, "$dir/script");
+ }
+}
+
+=head1 SEE ALSO
+
+F</usr/share/doc/reportbug/README.developers.gz>
+
+L<debhelper(1)>
+
+This program is a part of debhelper.
+
+=head1 AUTHOR
+
+Modestas Vainius <modestas@vainius.eu>
+
+=cut
diff --git a/dh_builddeb b/dh_builddeb
new file mode 100755
index 0000000..aca17f6
--- /dev/null
+++ b/dh_builddeb
@@ -0,0 +1,186 @@
+#!/usr/bin/perl
+
+=encoding UTF-8
+
+=head1 NAME
+
+dh_builddeb - build Debian binary packages
+
+=cut
+
+use strict;
+use warnings;
+use Debian::Debhelper::Dh_Lib;
+
+our $VERSION = DH_BUILTIN_VERSION;
+
+=head1 SYNOPSIS
+
+B<dh_builddeb> [S<I<debhelper options>>] [B<--destdir=>I<directory>] [B<--filename=>I<name>] [S<B<--> I<params>>]
+
+=head1 DESCRIPTION
+
+B<dh_builddeb> simply calls L<dpkg-deb(1)> to build a Debian package
+or packages. It will also build dbgsym packages when L<dh_strip(1)>
+and L<dh_gencontrol(1)> have prepared them.
+
+It supports building multiple binary packages in parallel, when enabled by
+DEB_BUILD_OPTIONS.
+
+When the I<Rules-Requires-Root> field is not (effectively)
+I<binary-targets>, B<dh_builddeb> will pass B<--root-owner-group> to
+L<dpkg-deb(1)>.
+
+=head1 OPTIONS
+
+=over 4
+
+=item B<--destdir=>I<directory>
+
+Use this if you want the generated F<.deb> files to be put in a directory
+other than the default of "F<..>".
+
+=item B<--filename=>I<name>
+
+Use this if you want to force the generated .deb file to have a particular
+file name. Does not work well if more than one .deb is generated!
+
+=item B<--> I<params>
+
+Pass I<params> to L<dpkg-deb(1)> when it is used to build the
+package.
+
+=item B<-u>I<params>
+
+This is another way to pass I<params> to L<dpkg-deb(1)>.
+It is deprecated; use B<--> instead.
+
+=back
+
+=cut
+
+init(options => {
+ "filename=s" => \$dh{FILENAME},
+ "destdir=s" => \$dh{DESTDIR},
+});
+
+# Set the default destination directory.
+if (! defined $dh{DESTDIR}) {
+ $dh{DESTDIR}='..';
+}
+
+if (! defined $dh{FILENAME}) {
+ $dh{FILENAME}='';
+}
+else {
+ $dh{FILENAME}="/$dh{FILENAME}";
+}
+
+sub build_and_rename_deb {
+ my ($package, $destdir, $cmd, $rename_sub) = @_;
+ my $build_dir = "debian/.debhelper/scratch-space/build-${package}";
+ my ($dpkg_filename, $desired_filename);
+ mkdirs($build_dir);
+ doit(@${cmd}, $build_dir);
+ opendir(my $fd, $build_dir) or error("opendir($build_dir) failed: $!");
+ for my $name (readdir($fd)) {
+ next if $name eq '.' or $name eq '..';
+ if ($dpkg_filename) {
+ error("\"@{$cmd} ${build_dir}\" produced two debs: $dpkg_filename and $name");
+ }
+ $dpkg_filename = $name;
+ }
+ closedir($fd);
+ if (not defined($dpkg_filename)) {
+ error("\"@{$cmd} ${build_dir}\" did not produce *any* file but was successful!?");
+ }
+ local $_ = $dpkg_filename;
+ $rename_sub->();
+ $desired_filename = $_;
+ if ($desired_filename ne $dpkg_filename) {
+ print "\tRenaming $dpkg_filename to $desired_filename\n";
+ }
+ rename_path("${build_dir}/${dpkg_filename}",
+ "${destdir}/${desired_filename}");
+}
+
+my @items;
+my @dpkg_options;
+push(@dpkg_options, '--root-owner-group') if not should_use_root();
+my @dbgsym_dpkg_options = ('--root-owner-group');
+
+
+for my $package (@{$dh{DOPACKAGES}}) {
+ push(@items, [$package, 0]);
+ if (not is_udeb($package)) {
+ my $dbgsym_tmpdir = dbgsym_tmpdir($package);
+ my $dbgsym_control = "${dbgsym_tmpdir}/DEBIAN/control";
+ if ( -f $dbgsym_control) {
+ # Only build the dbgsym package if it has a control file.
+ # People might have skipped dh_gencontrol.
+ push(@items, [$package, 1]);
+ } elsif (-d $dbgsym_tmpdir) {
+ warning("Not building dbgsym package for ${package} as it has no control file");
+ warning("Please use dh_gencontrol to avoid this issue");
+ }
+ }
+}
+
+on_items_in_parallel(\@items, sub {
+ foreach my $item (@_) {
+ my ($package, $dbgsym) = @{$item};
+ my $tmp=tmpdir($package);
+
+ if ($dbgsym) {
+ my $dbgsym_tmpdir = dbgsym_tmpdir($package);
+ my @cmd = ("dpkg-deb", @dbgsym_dpkg_options, @{$dh{U_PARAMS}},
+ "--build", $dbgsym_tmpdir);
+ if (DBGSYM_PACKAGE_TYPE eq DEFAULT_PACKAGE_TYPE) {
+ doit(@cmd, $dh{DESTDIR});
+ } else {
+ build_and_rename_deb($package, $dh{DESTDIR}, \@cmd,
+ sub {s/\.\Q${\DEFAULT_PACKAGE_TYPE}\E$/\.\Q${\DBGSYM_PACKAGE_TYPE}\E/g});
+ }
+ next;
+ }
+ if (exists $ENV{DH_ALWAYS_EXCLUDE} && length $ENV{DH_ALWAYS_EXCLUDE}) {
+ complex_doit("find $tmp $dh{EXCLUDE_FIND} | xargs rm -rf");
+ }
+ if (! is_udeb($package)) {
+ doit("dpkg-deb", @dpkg_options, @{$dh{U_PARAMS}}, "--build", $tmp, $dh{DESTDIR}.$dh{FILENAME});
+ }
+ else {
+ my $filename=$dh{FILENAME};
+ my @cmd = qw(dpkg-deb -z6 -Zxz -Sextreme);
+ push(@cmd, @dpkg_options);
+ push(@cmd, @{$dh{U_PARAMS}}) if $dh{U_PARAMS};
+ push(@cmd, '--build', $tmp);
+ if (! $filename) {
+ # dpkg-gencontrol does not include "Package-Type" in the
+ # control file (see #575059, #452273) for political
+ # reasons.
+ #
+ # dh_builddeb used to guess the "correct" filename, but it
+ # fell short when dpkg-gencontrol -V was used. The best
+ # solution so far: Let dpkg-deb build the deb and
+ # have dh_builddeb fix the extension.
+ build_and_rename_deb($package, $dh{DESTDIR}, \@cmd,
+ sub { s/\.deb$/\.udeb/g });
+ } else {
+ doit(@cmd, $dh{DESTDIR}.$filename);
+ }
+ }
+ }
+});
+
+=head1 SEE ALSO
+
+L<debhelper(7)>
+
+This program is a part of debhelper.
+
+=head1 AUTHOR
+
+Joey Hess <joeyh@debian.org>
+
+=cut
diff --git a/dh_clean b/dh_clean
new file mode 100755
index 0000000..a11d09c
--- /dev/null
+++ b/dh_clean
@@ -0,0 +1,199 @@
+#!/usr/bin/perl
+
+=head1 NAME
+
+dh_clean - clean up package build directories
+
+=cut
+
+use strict;
+use warnings;
+use Debian::Debhelper::Dh_Lib;
+
+our $VERSION = DH_BUILTIN_VERSION;
+
+=head1 SYNOPSIS
+
+B<dh_clean> [S<I<debhelper options>>] [B<-k>] [B<-d>] [B<-X>I<item>] [S<I<path> ...>]
+
+=head1 DESCRIPTION
+
+B<dh_clean> is a debhelper program that is responsible for cleaning up. It should
+be the last step of the B<clean> target and other debhelper commands generally
+assume that B<dh_clean> will clean up after them.
+
+It removes the package build directories, and removes some other files including
+F<debian/files>, and any detritus left behind by other debhelper commands. It
+also removes common files and directories that should not appear in a Debian diff:
+ #*# *~ DEADJOE *.orig *.rej *.SUMS __pycache__ TAGS .deps/* *.P *-stamp
+
+It does not run "make clean" to clean up after the build process. Use
+L<dh_auto_clean(1)> to do things like that.
+
+=head1 FILES
+
+=over 4
+
+=item F<debian/clean>
+
+Can list other paths to be removed.
+
+Note that directories listed in this file B<must> end with a trailing
+slash. Any content in these directories will be removed as well.
+
+Supports substitution variables in compat 13 and later as
+documented in L<debhelper(7)>.
+
+=back
+
+=head1 OPTIONS
+
+=over 4
+
+=item B<-k>, B<--keep>
+
+This is deprecated, use L<dh_prep(1)> instead.
+
+The option is removed in compat 12.
+
+=item B<-d>, B<--dirs-only>
+
+Only clean the package build directories, do not clean up any other files
+at all.
+
+=item B<-X>I<item> B<--exclude=>I<item>
+
+Exclude files that contain I<item> anywhere in their filename from being
+deleted, even if they would normally be deleted. You may use this option
+multiple times to build up a list of things to exclude.
+
+=item I<path> ...
+
+Delete these I<path>s too.
+
+Note that directories passed as arguments B<must> end with a trailing
+slash. Any content in these directories will be removed as well.
+
+=back
+
+=cut
+
+init(options => {
+ 'dirs-only' => \$dh{D_FLAG},
+ 'keep|k' => \$dh{K_FLAG},
+ },
+ inhibit_log => 1,
+);
+
+if ($dh{K_FLAG}) {
+ deprecated_functionality('dh_clean -k is deprecated; use dh_prep instead',
+ 12,
+ 'The -k option is not supported in compat 12; use dh_prep instead');
+}
+
+# Remove the debhelper stamp file
+rm_files('debian/debhelper-build-stamp') if not $dh{D_FLAG};
+
+my (@clean_files, @clean_dirs, %seen);
+
+foreach my $package (@{$dh{DOPACKAGES}}) {
+ my $tmp=tmpdir($package);
+ my $ext=pkgext($package);
+ my $source_dir = default_sourcedir($package);
+
+ if (! $dh{D_FLAG}) {
+ push(@clean_files, "debian/${ext}substvars")
+ unless excludefile("debian/${ext}substvars");
+
+ # These are all debhelper temp files, and so it is safe to
+ # wildcard them.
+ my @temp = glob("debian/$ext*.debhelper");
+ push(@clean_files, @temp);
+ }
+
+ push(@clean_dirs , "${tmp}/")
+ unless excludefile($tmp);
+ push(@clean_dirs, "${source_dir}/")
+ if (not $seen{$source_dir}++ and not excludefile($source_dir));
+}
+
+
+if (not $dh{D_FLAG}) {
+ # Restore all files in our bucket (before we delete said bucket)
+ restore_all_files(1);
+
+ # Remove internal state data
+ doit('rm', '-rf', 'debian/.debhelper/');
+}
+
+
+# Remove all debhelper logs.
+if (! $dh{D_FLAG} && ! $dh{K_FLAG}) {
+ my @logs = glob('debian/*.debhelper.log');
+ rm_files(@logs) if @logs;
+}
+
+if (! $dh{D_FLAG}) {
+ if (@ARGV) {
+ push(@clean_files, grep { !m@/$@ } @ARGV);
+ push(@clean_dirs, grep { m@/$@ } @ARGV);
+ }
+
+ if (! $dh{K_FLAG}) {
+ if (!compat(6) && -e "debian/clean") {
+ my @clean=grep {
+ ! excludefile($_)
+ # Silently ignore missing files - for all we know, dh_clean is run before
+ # they have been created.
+ } filearray('debian/clean', ["."], \&glob_expand_error_handler_silently_ignore);
+ push(@clean_files, grep { !m@/$@ } @clean);
+ push(@clean_dirs, grep { m@/$@ } @clean);
+ }
+
+ push(@clean_files, 'debian/files')
+ unless excludefile("debian/files");
+ }
+}
+
+xargs(\@clean_files, 'rm', '-f', '--') if @clean_files;
+xargs(\@clean_dirs, 'rm', '-fr', '--') if @clean_dirs;
+
+if (! $dh{D_FLAG}) {
+ # See if some files that would normally be deleted are excluded.
+ my $find_options='';
+ if (defined($dh{EXCLUDE_FIND}) && $dh{EXCLUDE_FIND} ne '') {
+ $find_options="! \\( $dh{EXCLUDE_FIND} \\) -a";
+ }
+
+ # vcs directories that should not have their contents cleaned
+ # (plus the internal "quilt" directory)
+ my $vcs_dirs=join " -o ", map { "-path .\\*/" . $_ }
+ (".git", ".svn", ".bzr", ".hg", "CVS", '.pc', '_darcs');
+
+ # Remove other temp files.
+ complex_doit("find . $find_options \\( \\( \\
+ \\( $vcs_dirs \\) -prune -o -type f -a \\
+ \\( -name '#*#' -o -name '.*~' -o -name '*~' -o -name DEADJOE \\
+ -o -name '*.orig' -o -name '*.rej' -o -name '*.bak' \\
+ -o -name '.*.orig' -o -name .*.rej -o -name '.SUMS' \\
+ -o -name TAGS -o \\( -path '*/.deps/*' -a -name '*.P' \\) \\
+ \\) -exec rm -f {} + \\) -o \\
+ \\( -type d -a \\( -name autom4te.cache -o -name __pycache__ \\) -prune -exec rm -rf {} + \\) \\)");
+}
+
+if (!compat(6) && !$dh{K_FLAG}) {
+ my @stamp_files = glob('*-stamp');
+ rm_files(@stamp_files) if @stamp_files;
+}
+
+=head1 SEE ALSO
+
+L<debhelper(7)>
+
+This program is a part of debhelper.
+
+=head1 AUTHOR
+
+Joey Hess <joeyh@debian.org>
+
+=cut
diff --git a/dh_compress b/dh_compress
new file mode 100755
index 0000000..ac72687
--- /dev/null
+++ b/dh_compress
@@ -0,0 +1,253 @@
+#!/usr/bin/perl
+
+=head1 NAME
+
+dh_compress - compress files and fix symlinks in package build directories
+
+=cut
+
+use strict;
+use warnings;
+use Cwd qw(getcwd abs_path);
+use File::Spec::Functions qw(abs2rel);
+use Debian::Debhelper::Dh_Lib;
+
+our $VERSION = DH_BUILTIN_VERSION;
+
+=head1 SYNOPSIS
+
+B<dh_compress> [S<I<debhelper options>>] [B<-X>I<item>] [B<-A>] [S<I<file> ...>]
+
+=head1 DESCRIPTION
+
+B<dh_compress> is a debhelper program that is responsible for compressing
+the files in package build directories, and makes sure that any symlinks
+that pointed to the files before they were compressed are updated to point
+to the new files.
+
+By default, B<dh_compress> compresses files that Debian policy mandates should
+be compressed, namely all files in F<usr/share/info>, F<usr/share/man>,
+files in F<usr/share/doc> that are larger than 4k in size,
+(except the F<copyright> file, F<.html> and other web files, image files, and files
+that appear to be already compressed based on their extensions), and all
+F<changelog> files. Plus PCF fonts underneath F<usr/share/fonts/X11/>
+
+=head1 FILES
+
+=over 4
+
+=item debian/I<package>.compress
+
+These files are deprecated.
+
+If this file exists, the default files are not compressed. Instead, the
+file is ran as a shell script, and all filenames that the shell script
+outputs will be compressed. The shell script will be run from inside the
+package build directory. Note though that using B<-X> is a much better idea in
+general; you should only use a F<debian/package.compress> file if you really
+need to.
+
+=back
+
+=head1 OPTIONS
+
+=over 4
+
+=item B<-X>I<item>, B<--exclude=>I<item>
+
+Exclude files that contain F<item> anywhere in their filename from being
+compressed. For example, B<-X.tiff> will exclude TIFF files from compression.
+You may use this option multiple times to build up a list of things to
+exclude.
+
+=item B<-A>, B<--all>
+
+Compress all files specified by command line parameters in ALL packages
+acted on.
+
+=item I<file> ...
+
+Add these files to the list of files to compress.
+
+=back
+
+=head1 CONFORMS TO
+
+Debian policy, version 3.0
+
+=cut
+
+init();
+
+on_pkgs_in_parallel {
+ my $olddir;
+
+ foreach my $package (@_) {
+ my $tmp=tmpdir($package);
+
+ my $compress=pkgfile($package,"compress");
+
+ # Run the file name gathering commands from within the directory
+ # structure that will be effected.
+ next unless -d $tmp;
+ my $ignore_doc_dirs = '-name _sources';
+ if (not compat(11)) {
+ my $target_package = compute_doc_main_package($package);
+ $ignore_doc_dirs .= qq{ -o -path "usr/share/doc/${package}/examples"};
+ $ignore_doc_dirs .= qq{ -o -path "usr/share/doc/${target_package}/examples"}
+ if $target_package and $target_package ne $package;
+ }
+ $olddir = getcwd() if not defined $olddir;
+ verbose_print("cd $tmp");
+ chdir($tmp) || error("Can't cd to $tmp: $!");
+
+ # Figure out what files to compress.
+ my @files;
+ # First of all, deal with any files specified right on the command line.
+ if (($package eq $dh{FIRSTPACKAGE} || $dh{PARAMS_ALL}) && @ARGV) {
+ push @files, map { s{^/+}{}; $_ } @ARGV;
+ }
+ if ($compress) {
+ # The compress file is a sh script that outputs the files to be compressed
+ # (typically using find).
+ warning("$compress is deprecated; use -X or avoid calling dh_compress instead");
+ push @files, split(/\n/,`sh $olddir/$compress 2>/dev/null`);
+ } else {
+ # Note that all the excludes of odd things like _z
+ # are because gzip refuses to compress such files, assuming
+ # they are zip files. I looked at the gzip source to get the
+ # complete list of such extensions: ".gz", ".z", ".taz",
+ # ".tgz", "-gz", "-z", "_z"
+ push @files, split(/\n/,`
+ find usr/share/info usr/share/man -type f ! -iname "*.gz" \\
+ ! -iname "*.gif" ! -iname "*.png" ! -iname "*.jpg" \\
+ ! -iname "*.jpeg" \\
+ 2>/dev/null || true;
+ find usr/share/doc \\
+ \\( -type d \\( $ignore_doc_dirs \\) -prune -false \\) -o \\
+ -type f \\( -size +4k -o -name "changelog*" -o -name "NEWS*" \\) \\
+ \\( -name changelog.html -o ! -iname "*.htm*" \\) \\
+ ! -iname "*.xhtml" \\
+ ! -iname "*.gif" ! -iname "*.png" ! -iname "*.jpg" \\
+ ! -iname "*.jpeg" ! -iname "*.gz" ! -iname "*.taz" \\
+ ! -iname "*.tgz" ! -iname "*.z" ! -iname "*.bz2" \\
+ ! -iname "*-gz" ! -iname "*-z" ! -iname "*_z" \\
+ ! -iname "*.epub" ! -iname "*.jar" ! -iname "*.zip" \\
+ ! -iname "*.odg" ! -iname "*.odp" ! -iname "*.odt" \\
+ ! -iname ".htaccess" ! -iname "*.css" \\
+ ! -iname "*.xz" ! -iname "*.lz" ! -iname "*.lzma" \\
+ ! -iname "*.haddock" ! -iname "*.hs" \\
+ ! -iname "*.woff" ! -iname "*.woff2" \\
+ ! -iname "*.svg" ! -iname "*.svgz" ! -iname "*.js" \\
+ ! -name "index.sgml" ! -name "objects.inv" ! -name "*.map" \\
+ ! -name "*.devhelp2" ! -name "search_index.json" \\
+ ! -name "copyright" 2>/dev/null || true;
+ find usr/share/fonts/X11 -type f -name "*.pcf" 2>/dev/null || true;
+ `);
+ }
+
+ # Exclude files from compression.
+ if (@files && defined($dh{EXCLUDE}) && $dh{EXCLUDE}) {
+ my @new = grep { not excludefile($_) } @files;
+ @files=@new;
+ }
+
+ # Look for files with hard links. If we are going to compress both,
+ # we can preserve the hard link across the compression and save
+ # space in the end.
+ my ($unique_files, $hardlinks) = find_hardlinks(@files);
+ my @f = @{$unique_files};
+
+ # normalize file names and remove duplicates
+ my $norm_from_dir = $tmp;
+ if ($norm_from_dir !~ m{^/}) {
+ $norm_from_dir = "${olddir}/${tmp}";
+ }
+ my $resolved = abs_path($norm_from_dir)
+ or error("Cannot resolve $norm_from_dir: $!");
+ my @normalized = normalize_paths($norm_from_dir, $resolved, $tmp, @f);
+ my %uniq_f; @uniq_f{@normalized} = ();
+ @f = sort keys %uniq_f;
+
+ # do it
+ if (@f) {
+ # Make executables not be anymore.
+ xargs(\@f,"chmod","a-x");
+ xargs(\@f,"gzip","-9nf");
+ }
+
+ # Now change over any files we can that used to be hard links so
+ # they are again.
+ foreach (keys %{$hardlinks}) {
+ # Remove old file.
+ rm_files($_);
+ # Make new hardlink.
+ doit("ln", "-f", "$hardlinks->{$_}.gz", "$_.gz");
+ }
+
+ verbose_print("cd '$olddir'");
+ chdir($olddir);
+
+ # Fix up symlinks that were pointing to the uncompressed files.
+ my %links = map { chomp; $_ => 1 } qx_cmd('find', $tmp, '-type', 'l');
+ my $changed;
+ # Keep looping through looking for broken links until no more
+ # changes are made. This is done in case there are links pointing
+ # to links, pointing to compressed files.
+ do {
+ $changed = 0;
+ foreach my $link (keys %links) {
+ my ($directory) = $link =~ m:(.*)/:;
+ my $linkval = readlink($link);
+ if (! -e "$directory/$linkval" && -e "$directory/$linkval.gz") {
+ # Avoid duplicate ".gz.gz" if the link already has
+ # the .gz extension. This can happen via
+ # dh_installman when the .so is already compressed
+ # and then dh_installman reencodes the target
+ # manpage.
+ my $link_name = $link;
+ $link_name .= '.gz' if $link_name !~ m/[.]gz$/;
+ rm_files($link, "$link.gz");
+ make_symlink_raw_target("$linkval.gz", $link_name);
+ delete $links{$link};
+ $changed++;
+ }
+ }
+ } while $changed;
+ }
+};
+
+sub normalize_paths {
+ my ($cwd, $cwd_resolved, $tmp, @paths) = @_;
+ my @normalized;
+ my $prefix = qr{\Q${tmp}/};
+
+ for my $path (@paths) {
+ my $abs = abs_path($path);
+ if (not defined($abs)) {
+ my $err = $!;
+ my $alt = $path;
+ if ($alt =~ s/^$prefix//) {
+ $abs = abs_path($alt);
+ }
+ error(qq{Cannot resolve "$path": $err (relative to "${cwd}")})
+ if (not defined($abs));
+ warning(qq{Interpreted "$path" as "$alt"});
+ }
+ error("${abs} does not exist") if not -e $abs;
+ push(@normalized, abs2rel($abs, $cwd_resolved));
+ }
+ return @normalized;
+}
+
+=head1 SEE ALSO
+
+L<debhelper(7)>
+
+This program is a part of debhelper.
+
+=head1 AUTHOR
+
+Joey Hess <joeyh@debian.org>
+
+=cut
diff --git a/dh_dwz b/dh_dwz
new file mode 100755
index 0000000..294762d
--- /dev/null
+++ b/dh_dwz
@@ -0,0 +1,185 @@
+#!/usr/bin/perl
+
+=head1 NAME
+
+dh_dwz - optimize DWARF debug information in ELF binaries via dwz
+
+=cut
+
+use strict;
+use warnings;
+use File::Find;
+use Debian::Debhelper::Dh_Lib;
+
+our $VERSION = DH_BUILTIN_VERSION;
+
+=head1 SYNOPSIS
+
+B<dh_dwz> [S<I<debhelper options>>] [B<-X>I<item>] [S<B<--> I<params>>]
+
+=head1 DESCRIPTION
+
+B<dh_dwz> is a debhelper program that will optimize the (uncompressed)
+size of the DWARF debug information in ELF binaries. It does so by
+running L<dwz(1)> on all the ELF binaries in the package.
+
+=head1 OPTIONS
+
+=over 4
+
+=item B<--dwz-multifile>, B<--no-dwz-multifile>
+
+Whether L<dwz(1)> should generate a I<multifile> from the ELF binaries
+in the same package. When enabled, if a package ships at least 2 ELF
+binaries, B<dh_dwz> will instruct L<dwz(1)> to generate a multifile
+for the package.
+
+By default, B<dh_dwz> will attempt to create a multifile but will
+continue without if L<dwz(1)> does not create one (but succeeds anyway).
+This commonly happens when the debug files do not contain debug
+symbols (e.g. a missing -g to the compiler) or when the debug
+symbols are compressed (see Debian bug #931891). If B<--dwz-multifile>
+is passed, then B<dh_dwz> will abort with an error if L<dwz(1)> does
+not create a multifile.
+
+Note this options may not work if a package contains more ELF binaries
+than can fit on a single command line. If this becomes a problem,
+please pass B<--no-dwz-multifile> to work around the issue.
+
+The generated multifile will be compressed with B<objcopy
+--compress-debug-sections>.
+
+Note for B<udeb> packages: B<dh_dwz> will never generate multifiles
+for B<udeb> packages. It will still use B<dwz> to reduce the
+file size of debug files if it finds any.
+
+=item B<-X>I<item>, B<--exclude=>I<item>
+
+Exclude files that contain I<item> anywhere in their filename from being
+stripped. You may use this option multiple times to build up a list of
+things to exclude.
+
+=item B<--> I<params>
+
+Pass I<params> to L<dwz(1)> when it processes ELF binaries. This is
+mostly useful for setting memory related parameters (e.g. -l and -L).
+
+=back
+
+=head1 NOTES
+
+If the B<DEB_BUILD_OPTIONS> environment variable contains B<nostrip>,
+nothing will be stripped, in accordance with Debian policy (section
+10.1 "Binaries").
+
+While this tool technically does not remove debug information from
+binaries, it is still skipped when the B<DEB_BUILD_OPTIONS>
+environment variable contains B<nostrip>. This is because B<nostrip>
+is often used to optimize build times (e.g. for "build and
+test"-cycles) rather than optimizing for size.
+
+=cut
+
+my $create_multifile = 'auto';
+
+init(options => {
+ 'dwz-multifile!' => \$create_multifile,
+});
+
+# This variable can be used to turn off stripping (see Policy).
+exit 0 if get_buildoption('nostrip');
+if (not has_tool('dwz')) {
+ warning("Assuming bootstrap scenario and skipping regular dh_dwz feature, since dwz is not in PATH!");
+ exit 0;
+}
+
+my @elf_files;
+
+sub has_tool {
+ my ($cmd) = @_;
+ for my $p (split(m/:/, $ENV{PATH})) {
+ return 1 if -f -x "${p}/${cmd}";
+ }
+ return 0;
+}
+
+sub testfile {
+ my $fn = $_;
+ return if -l $fn; # Always skip symlinks.
+
+ # See if we were asked to exclude this file.
+ # Note that we have to test on the full filename, including directory.
+ if (excludefile($fn)) {
+ $File::Find::prune = 1 if -d _;
+ return;
+ }
+ return if -d _;
+ # Do not process output files from dwz
+ return if index($fn, '/debug/.dwz/') > -1;
+ if (is_so_or_exec_elf_file($fn)) {
+ push(@elf_files, $fn);
+ }
+ return;
+}
+
+on_items_in_parallel(\@{$dh{DOPACKAGES}}, sub {
+foreach my $package (@_) {
+ my $tmp = tmpdir($package);
+
+ next if not -d $tmp;
+
+ @elf_files = ();
+ find({
+ wanted => \&testfile,
+ no_chdir => 1,
+ }, $tmp);
+ next if not @elf_files;
+ # Consistent order;
+ @elf_files = sort(@elf_files);
+ my ($unique_files, $hardlinks) = find_hardlinks(@elf_files);
+ if ($create_multifile and @{$unique_files} > 1 and not is_udeb($package)) {
+ my $objcopy = cross_command($package, 'objcopy');
+ my $ma_dir = dpkg_architecture_value('DEB_HOST_MULTIARCH');
+ my $dwz_dir = "usr/lib/debug/.dwz/${ma_dir}";
+ my $m = "${dwz_dir}/${package}.debug";
+ my @dwz_options = ("-m${tmp}/${m}", "-M/${m}");
+ install_dir("${tmp}/${dwz_dir}");
+ doit('dwz', @dwz_options, @{$dh{U_PARAMS}}, '--', @{$unique_files});
+ if ( -f "${tmp}/${m}") {
+ doit($objcopy, '--compress-debug-sections', "${tmp}/${m}");
+ reset_perm_and_owner(0644, "${tmp}/${m}");
+ } else {
+ error("dwz failed to create a multifile as requested") if $create_multifile ne 'auto';
+ warning("No dwz multifile created, but not explicitly requested either so ignoring it.");
+ warning("Common issues include no debug information at all (missing -g) and");
+ warning("compressed debug information (#931891).");
+ # Clean up after ourselves to avoid leaving empty directories in packages
+ doit('rmdir', '-p', '--ignore-fail-on-non-empty', "${tmp}/${dwz_dir}");
+ }
+ } else {
+ xargs($unique_files, 'dwz', @{$dh{U_PARAMS}}, '--');
+ }
+
+
+ # Now change over any files we can that used to be hard links so
+ # they are again.
+ for my $hardlink (keys %{$hardlinks}) {
+ my $target = $hardlinks->{$hardlink};
+ # Remove old file.
+ rm_files($hardlink);
+ # Make new hardlink.
+ doit('ln', '-f', $target, $hardlink);
+ }
+}});
+
+=head1 SEE ALSO
+
+L<debhelper(7)>
+
+This program is a part of debhelper.
+
+=head1 AUTHOR
+
+Niels Thykier <niels@thykier.net>
+
+=cut
diff --git a/dh_fixperms b/dh_fixperms
new file mode 100755
index 0000000..8c491bb
--- /dev/null
+++ b/dh_fixperms
@@ -0,0 +1,174 @@
+#!/usr/bin/perl
+
+=head1 NAME
+
+dh_fixperms - fix permissions of files in package build directories
+
+=cut
+
+use strict;
+use warnings;
+use Config;
+use Debian::Debhelper::Dh_Lib;
+
+our $VERSION = DH_BUILTIN_VERSION;
+
+=head1 SYNOPSIS
+
+B<dh_fixperms> [S<I<debhelper options>>] [B<-X>I<item>]
+
+=head1 DESCRIPTION
+
+B<dh_fixperms> is a debhelper program that is responsible for setting the
+permissions of files and directories in package build directories to a
+sane state -- a state that complies with Debian policy.
+
+B<dh_fixperms> makes all files in F<usr/share/doc> in the package
+build directory (excluding files in the F<examples/> directory) be
+mode 644. It also changes the permissions of all man pages to mode
+644. It removes group and other write permission from all files. It
+removes execute permissions from any libraries, headers, Perl modules,
+or desktop files that have it set. It makes all files in the standard
+F<bin> and F<sbin> directories, F<usr/games/> and F<etc/init.d>
+executable (since v4). Finally, it removes the setuid and setgid bits
+from all files in the package.
+
+When the I<Rules-Requires-Root> field has the (effective) value of
+I<binary-targets>, B<dh_fixperms> will also reset the ownership of
+all paths to "root:root".
+
+=head1 OPTIONS
+
+=over 4
+
+=item B<-X>I<item>, B<--exclude> I<item>
+
+Exclude files that contain I<item> anywhere in their filename from having
+their permissions changed. You may use this option multiple times to build
+up a list of things to exclude.
+
+=back
+
+=cut
+
+init();
+
+sub patterns2find_expr {
+ return sprintf('\\( -name %s \\)', join(' -o -name ', map { "'$_'" } @_));
+}
+
+
+my $vendorlib = substr $Config{vendorlib}, 1;
+my $vendorarch = substr $Config{vendorarch}, 1;
+my @executable_files_dirs = (
+ qw{usr/bin bin usr/sbin sbin usr/games usr/libexec etc/init.d},
+);
+my @mode_0644_patterns = (
+ # Libraries and related files
+ '*.so.*', '*.so', '*.la', '*.a',
+ # Web application related files
+ '*.js', '*.css', '*.scss', '*.sass',
+ # Images
+ '*.jpeg', '*.jpg', '*.png', '*.gif',
+ # OCaml native-code shared objects
+ '*.cmxs',
+ # Node bindings
+ '*.node',
+);
+my @mode_0755_patterns = (
+ # None for Debian
+);
+my $find_exclude_options='-true';
+if (defined($dh{EXCLUDE_FIND}) && $dh{EXCLUDE_FIND} ne '') {
+ $find_exclude_options="! \\( $dh{EXCLUDE_FIND} \\)";
+}
+
+sub find_and_reset_perm {
+ my ($in_dirs, $mode, $raw_find_expr, $raw_find_expr_late) = @_;
+ my (@dirs, $dir_string);
+ if (ref ($in_dirs) ) {
+ @dirs = grep { -d } @{$in_dirs};
+ return if not @dirs;
+ } else {
+ return if not -d $in_dirs;
+ @dirs = ($in_dirs);
+ }
+ $dir_string = escape_shell(@dirs);
+ $raw_find_expr //= '';
+ $raw_find_expr_late //= '-true';
+ complex_doit("find ${dir_string} ${raw_find_expr} -a ${find_exclude_options} -a ${raw_find_expr_late} -print0",
+ "2>/dev/null | xargs -0r chmod ${mode}");
+}
+
+on_pkgs_in_parallel {
+ foreach my $package (@_) {
+ my $tmp=tmpdir($package);
+
+ next if not -d $tmp;
+
+ # General permissions fixing.
+ complex_doit("find $tmp ${find_exclude_options} -print0",
+ "2>/dev/null | xargs -0r chown --no-dereference 0:0") if should_use_root();
+ find_and_reset_perm($tmp, 'go=rX,u+rw,a-s', '! -type l');
+
+ # Fix up permissions in usr/share/doc, setting everything to not
+ # executable by default, but leave examples directories alone.
+ find_and_reset_perm("${tmp}/usr/share/doc", '0644', '-type f', "! -regex '$tmp/usr/share/doc/[^/]*/examples/.*'");
+ find_and_reset_perm("${tmp}/usr/share/doc", '0755', '-type d');
+
+ # Manpages, include file, desktop files, etc., shouldn't be executable
+ find_and_reset_perm([
+ "${tmp}/usr/share/man",
+ "${tmp}/usr/include",
+ "${tmp}/usr/share/applications",
+ "${tmp}/usr/share/lintian/overrides",
+ ], '0644', '-type f');
+
+ # nor should perl modules.
+ find_and_reset_perm(["${tmp}/${vendorarch}", "${tmp}/${vendorlib}"],
+ 'a-X', "-type f -perm -5 -name '*.pm'");
+
+ find_and_reset_perm($tmp, '0644', '-type f ' . patterns2find_expr(@mode_0644_patterns)) if @mode_0644_patterns;
+ find_and_reset_perm($tmp, '0755', '-type f ' . patterns2find_expr(@mode_0755_patterns)) if @mode_0755_patterns;
+
+ # Programs in the bin and init.d dirs should be executable..
+ find_and_reset_perm([map { "${tmp}/$_"} @executable_files_dirs], 'a+x', '-type f');
+
+ # ADA ali files should be mode 444 to avoid recompilation
+ find_and_reset_perm("${tmp}/usr/lib", 'uga-w', "-type f -name '*.ali'");
+
+ if ( -d "$tmp/usr/lib/nodejs/") {
+ my @nodejs_exec_patterns = qw(*/cli.js */bin.js);
+ my @exec_files = grep {
+ not excludefile($_) and -f $_;
+ } glob_expand(["$tmp/usr/lib/nodejs"], \&glob_expand_error_handler_silently_ignore, @nodejs_exec_patterns);
+ reset_perm_and_owner(0755, @exec_files)
+ }
+
+ if ( -d "$tmp/usr/share/bug/$package") {
+ complex_doit("find $tmp/usr/share/bug/$package -type f",
+ "! -name 'script' ${find_exclude_options} -print0",
+ "2>/dev/null | xargs -0r chmod 644");
+ if ( -f "$tmp/usr/share/bug/$package/script" ) {
+ reset_perm_and_owner(0755, "$tmp/usr/share/bug/$package/script");
+ }
+ } elsif ( -f "$tmp/usr/share/bug/$package" ) {
+ reset_perm_and_owner(0755, "$tmp/usr/share/bug/$package");
+ }
+
+ # Files in $tmp/etc/sudoers.d/ must be mode 0440.
+ find_and_reset_perm("${tmp}/etc/sudoers.d", '0440', "-type f ! -perm 440");
+ }
+};
+
+=head1 SEE ALSO
+
+L<debhelper(7)>
+
+This program is a part of debhelper.
+
+=head1 AUTHOR
+
+Joey Hess <joeyh@debian.org>
+
+=cut
diff --git a/dh_gencontrol b/dh_gencontrol
new file mode 100755
index 0000000..e06763c
--- /dev/null
+++ b/dh_gencontrol
@@ -0,0 +1,231 @@
+#!/usr/bin/perl
+
+=head1 NAME
+
+dh_gencontrol - generate and install control file
+
+=cut
+
+use strict;
+use warnings;
+use Debian::Debhelper::Dh_Lib;
+
+our $VERSION = DH_BUILTIN_VERSION;
+
+=head1 SYNOPSIS
+
+B<dh_gencontrol> [S<I<debhelper options>>] [S<B<--> I<params>>]
+
+=head1 DESCRIPTION
+
+B<dh_gencontrol> is a debhelper program that is responsible for generating
+control files, and installing them into the I<DEBIAN> directory with the
+proper permissions.
+
+This program is merely a wrapper around L<dpkg-gencontrol(1)>, which
+calls it once for each package being acted on (plus related dbgsym
+packages), and passes in some additional useful flags.
+
+B<Note> that if you use B<dh_gencontrol>, you must also use
+L<dh_builddeb(1)> to build the packages. Otherwise, your build may
+fail to build as B<dh_gencontrol> (via L<dpkg-gencontrol(1)>) declares
+which packages are built. As debhelper automatically generates dbgsym
+packages, it some times adds additional packages, which will be built
+by L<dh_builddeb(1)>.
+
+
+=head1 OPTIONS
+
+=over 4
+
+=item B<--> I<params>
+
+Pass I<params> to L<dpkg-gencontrol(1)>.
+
+=item B<-u>I<params>, B<--dpkg-gencontrol-params=>I<params>
+
+This is another way to pass I<params> to L<dpkg-gencontrol(1)>.
+It is deprecated; use B<--> instead.
+
+=back
+
+=cut
+
+init(options => {
+ "dpkg-gencontrol-params=s", => \$dh{U_PARAMS},
+});
+
+if (not compat(13)) {
+ # Load once early, so each child does not have to load these again (they are expensive
+ # compared to Debian::Debhelper::Dh_Lib).
+ require Dpkg::Control;
+ require Dpkg::Control::Fields;
+}
+
+
+on_pkgs_in_parallel {
+ foreach my $package (@_) {
+ my $tmp=tmpdir($package);
+ my $ext=pkgext($package);
+ my $dbgsym_info_dir = "debian/.debhelper/${package}";
+ my $dbgsym_tmp = dbgsym_tmpdir($package);
+
+ my $substvars="debian/${ext}substvars";
+
+ my $changelog=pkgfile($package, 'changelog', 1);
+
+ install_dir("$tmp/DEBIAN");
+
+ # avoid gratuitous warnings
+ ensure_substvars_are_present($substvars, 'misc:Depends', 'misc:Pre-Depends')
+ if compat(14);
+
+ my (@debug_info_params, $build_ids, @pkg_gencontrol_args);
+ if ( -d $dbgsym_info_dir ) {
+ $build_ids = read_dbgsym_build_ids($dbgsym_info_dir);
+ }
+ my $has_dbgsym = -d $dbgsym_tmp;
+
+ my ($dctrl, $added_dbgsym_version);
+ ($dctrl, $added_dbgsym_version) = Debian::Debhelper::Dh_Lib::dh_gencontrol_automatic_substvars($package, $substvars, $has_dbgsym)
+ if not compat(13);
+ $dctrl //= 'debian/control';
+
+
+ if ($has_dbgsym) {
+ my $dbgsym_package = $added_dbgsym_version ? "${package}-dbgsym" : $package;
+ my $dbgsym_ctrl = $added_dbgsym_version ? $dctrl : 'debian/control';
+ my $dbgsym_substvar = $added_dbgsym_version ? '/dev/null' : $substvars;
+ my $multiarch = package_multiarch($package);
+ my $section = package_section($package);
+ my $replaces = read_dbgsym_migration($dbgsym_info_dir);
+ my $component = '';
+ if ($section =~ m{^(.*)/[^/]+$}) {
+ $component = "${1}/";
+ # This should not happen, but lets not propagate the error
+ # if does.
+ $component = '' if $component eq 'main/';
+ }
+
+ # Remove and override more or less every standard field.
+ my @dbgsym_options = (qw(
+ -UPre-Depends -URecommends -USuggests -UEnhances -UProvides -UEssential
+ -UConflicts -DPriority=optional -UHomepage -UImportant
+ -DAuto-Built-Package=debug-symbols
+ -UProtected -UBuilt-Using -UStatic-Built-Using
+ ),
+ "-DPackage=${package}-dbgsym",
+ "-DDepends=${package} (= \${binary:Version})",
+ "-DDescription=debug symbols for ${package}",
+ "-DBuild-Ids=${build_ids}",
+ "-DSection=${component}debug",
+ );
+ push(@dbgsym_options, "-DPackage-Type=${\DBGSYM_PACKAGE_TYPE}")
+ if DBGSYM_PACKAGE_TYPE ne DEFAULT_PACKAGE_TYPE;
+ # Disable multi-arch unless the original package is an
+ # multi-arch: same package. In all other cases, we do not
+ # need a multi-arch value.
+ if ($multiarch ne 'same') {
+ push(@dbgsym_options, '-UMulti-Arch');
+ }
+ # If the dbgsym package is replacing an existing -dbg package,
+ # then declare the necessary Breaks + Replaces. Otherwise,
+ # clear the fields.
+ if ($replaces) {
+ push(@dbgsym_options, "-DReplaces=${replaces}",
+ "-DBreaks=${replaces}");
+ } else {
+ push(@dbgsym_options, '-UReplaces', '-UBreaks');
+ }
+ install_dir("${dbgsym_tmp}/DEBIAN");
+ eval {
+ doit("dpkg-gencontrol", "-p${dbgsym_package}", "-l$changelog", "-T${dbgsym_substvar}",
+ "-c${dbgsym_ctrl}", "-P${dbgsym_tmp}", @{$dh{U_PARAMS}}, @dbgsym_options);
+ };
+ if (my $err = "$@") {
+ if ($dbgsym_ctrl ne 'debian/control') {
+ warning('The dpkg-control command failed. Here is the content of the rewritten d/control file');
+ warning(' used to add relationship substvars for you (in case that is part of the problem).');
+ print(" --- Content of $dbgsym_ctrl\n");
+ system('cat', $dbgsym_ctrl);
+ print(" --- End of content for $dbgsym_ctrl\n");
+ }
+ error($err);
+ }
+
+ reset_perm_and_owner(0644, "${dbgsym_tmp}/DEBIAN/control");
+ } elsif ($build_ids) {
+ # Only include the build-id if there is no dbgsym package (if
+ # there is a dbgsym package, the build-ids into the control
+ # file of the dbgsym package)
+ push(@debug_info_params, "-DBuild-Ids=${build_ids}");
+ }
+
+ # Remove explicit "Multi-Arch: no" headers to avoid auto-rejects by dak.
+ push (@pkg_gencontrol_args, '-UMulti-Arch')
+ if (package_multiarch($package) eq 'no');
+
+ # Generate and install control file.
+ eval {
+ doit("dpkg-gencontrol", "-p$package", "-l$changelog", "-T$substvars",
+ "-c${dctrl}", "-P$tmp", @debug_info_params, @pkg_gencontrol_args,
+ @{$dh{U_PARAMS}});
+ };
+ if (my $err = "$@") {
+ if ($dctrl ne 'debian/control') {
+ warning('The dpkg-control command failed. Here is the content of the rewritten d/control file');
+ warning(' used to add relationship substvars for you (in case that is part of the problem).');
+ print(" --- Content of $dctrl\n");
+ system('cat', $dctrl);
+ print(" --- End of content for $dctrl\n");
+ }
+ error($err);
+ }
+
+
+ # This chmod is only necessary if the user sets the umask to
+ # something odd.
+ reset_perm_and_owner(0644, "${tmp}/DEBIAN/control");
+ }
+};
+
+sub read_dbgsym_file {
+ my ($dbgsym_info_file, $dbgsym_info_dir) = @_;
+ my $dbgsym_path = "${dbgsym_info_dir}/${dbgsym_info_file}";
+ my $result;
+ if (-f $dbgsym_path) {
+ open(my $fd, '<', $dbgsym_path)
+ or error("open $dbgsym_path failed: $!");
+ chomp($result = <$fd>);
+ $result =~ s/\s++$//;
+ close($fd);
+ }
+ return $result;
+}
+
+sub read_dbgsym_migration {
+ return read_dbgsym_file('dbgsym-migration', @_);
+}
+
+sub read_dbgsym_build_ids {
+ my $res = read_dbgsym_file('dbgsym-build-ids', @_);
+ my (%seen, @unique);
+ return '' if not defined($res);
+ for my $id (split(' ', $res)) {
+ next if $seen{$id}++;
+ push(@unique, $id);
+ }
+ return join(' ', @unique);
+}
+
+=head1 SEE ALSO
+
+L<debhelper(7)>
+
+This program is a part of debhelper.
+
+=head1 AUTHOR
+
+Joey Hess <joeyh@debian.org>
+
+=cut
diff --git a/dh_icons b/dh_icons
new file mode 100755
index 0000000..2af6856
--- /dev/null
+++ b/dh_icons
@@ -0,0 +1,87 @@
+#!/usr/bin/perl
+
+=head1 NAME
+
+dh_icons - Update caches of Freedesktop icons
+
+=cut
+
+use strict;
+use warnings;
+use File::Find;
+use Debian::Debhelper::Dh_Lib;
+
+our $VERSION = DH_BUILTIN_VERSION;
+
+=head1 SYNOPSIS
+
+B<dh_icons> [S<I<debhelper options>>] [B<-n>]
+
+=head1 DESCRIPTION
+
+B<dh_icons> is a debhelper program that updates caches of Freedesktop icons
+when needed, using the B<update-icon-caches> program provided by GTK+2.12.
+Currently this program does not handle installation of the files, though it
+may do so at a later date, so should be run after icons are installed in
+the package build directories.
+
+It takes care of adding maintainer script fragments to call
+B<update-icon-caches> for icon directories. (This is not done for gnome and
+hicolor icons, as those are handled by triggers.)
+These commands are inserted into the maintainer scripts by L<dh_installdeb(1)>.
+
+=head1 OPTIONS
+
+=over 4
+
+=item B<-n>, B<--no-scripts>
+
+Do not modify maintainer scripts.
+
+=back
+
+=cut
+
+init();
+
+# PROMISE: DH NOOP WITHOUT tmp(usr/share/icons) cli-options()
+my $baseicondir="/usr/share/icons";
+
+foreach my $package (@{$dh{DOPACKAGES}}) {
+ my $tmp=tmpdir($package);
+ my $icondir="$tmp$baseicondir";
+ if (-d $icondir) {
+ my @dirlist;
+ opendir(my $dirfd, $icondir) or error("Cannot opendir($icondir): $!");
+ while (my $subdir = readdir($dirfd)) {
+ next if $subdir =~ /^\./;
+ next if $subdir eq "gnome";
+ next if $subdir eq "hicolor";
+ my $needs_cache = 0;
+ find sub {
+ $needs_cache = 1 if -f and (/\.png$/ or /\.svg$/ or /\.xpm$/ or /\.icon$/);
+ }, "$icondir/$subdir" ;
+ push @dirlist, "$baseicondir/$subdir" if $needs_cache;
+ }
+ closedir($dirfd);
+ if (@dirlist and ! $dh{NOSCRIPTS}) {
+ my $list=join(" ", sort @dirlist);
+ autoscript($package, 'postinst', 'postinst-icons', { 'DIRLIST' => $list });
+ autoscript($package, 'postrm', 'postrm-icons', { 'DIRLIST' => $list });
+ }
+ }
+}
+
+=head1 SEE ALSO
+
+L<debhelper>
+
+This program is a part of debhelper.
+
+=head1 AUTHOR
+
+Ross Burton <ross@burtonini.com>
+Jordi Mallach <jordi@debian.org>
+Josselin Mouette <joss@debian.org>
+
+=cut
diff --git a/dh_install b/dh_install
new file mode 100755
index 0000000..665f77c
--- /dev/null
+++ b/dh_install
@@ -0,0 +1,393 @@
+#!/usr/bin/perl
+
+=head1 NAME
+
+dh_install - install files into package build directories
+
+=cut
+
+use strict;
+use warnings;
+use Debian::Debhelper::Dh_Lib;
+
+our $VERSION = DH_BUILTIN_VERSION;
+
+=head1 SYNOPSIS
+
+B<dh_install> [B<-X>I<item>] [B<--autodest>] [B<--sourcedir=>I<dir>] [S<I<debhelper options>>] [S<I<file|dir> ... I<destdir>>]
+
+=head1 DESCRIPTION
+
+B<dh_install> is a debhelper program that handles installing files into package
+build directories. There are many B<dh_install>I<*> commands that handle installing
+specific types of files such as documentation, examples, man pages, and so on,
+and they should be used when possible as they often have extra intelligence for
+those particular tasks. B<dh_install>, then, is useful for installing everything
+else, for which no particular intelligence is needed. It is a replacement for
+the old B<dh_movefiles> command.
+
+This program may be used in one of two ways. If you just have a file or two
+that the upstream Makefile does not install for you, you can run B<dh_install>
+on them to move them into place. On the other hand, maybe you have a large
+package that builds multiple binary packages. You can use the upstream
+F<Makefile> to install it all into F<debian/tmp>, and then use B<dh_install> to copy
+directories and files from there into the proper package build directories.
+
+From debhelper compatibility level 7 on, B<dh_install> will fall back to
+looking in F<debian/tmp> for files, if it does not find them in the current
+directory (or wherever you've told it to look using B<--sourcedir>).
+
+=head1 FILES
+
+=over 4
+
+=item debian/I<package>.install
+
+List the files to install into each package and the directory they should be
+installed to. The format is a set of lines, where each line lists a file or
+files to install, and at the end of the line tells the directory it should be
+installed in. The name of the files (or directories) to install should be given
+relative to the current directory, while the installation directory is given
+relative to the package build directory. You may use wildcards in the names of
+the files to install.
+
+Note that if you list exactly one filename or wildcard-pattern on a line by
+itself, with no explicit destination, then B<dh_install>
+will automatically guess the destination to use, the same as if the
+--autodest option were used.
+
+Supports substitution variables in compat 13 and later as
+documented in L<debhelper(7)>.
+
+=item debian/not-installed
+
+Used with the deprecated B<--list-missing> and B<--fail-missing> options.
+Please refer to L<dh_missing(1)> for the documentation of this file.
+
+=back
+
+=head1 OPTIONS
+
+=over 4
+
+=item B<--list-missing>
+
+B<Deprecated>: Please use B<dh_missing --list-missing> instead. If you use
+this option, B<dh_install> will call B<dh_missing> with that option after it has
+processed all the files. Please see L<dh_missing(1)> for the documentation of
+this option.
+
+This option is removed in compat 12.
+
+=item B<--fail-missing>
+
+B<Deprecated>: Please use B<dh_missing --fail-missing> instead. If you use
+this option, B<dh_install> will call B<dh_missing> with that option after it has
+processed all the files. Please see L<dh_missing(1)> for the documentation of
+this option.
+
+This option is removed in compat 12.
+
+=item B<--sourcedir=>I<dir>
+
+Look in the specified directory for files to be installed.
+
+Note that this is not the same as the B<--sourcedirectory> option used
+by the B<dh_auto_>I<*> commands. You rarely need to use this option, since
+B<dh_install> automatically looks for files in F<debian/tmp> in debhelper
+compatibility level 7 and above.
+
+=item B<--autodest>
+
+Guess as the destination directory to install things to. If this is
+specified, you should not list destination directories in
+F<debian/package.install> files or on the command line. Instead, B<dh_install>
+will guess as follows:
+
+Strip off F<debian/tmp> (or the sourcedir if one is given) from the front of
+the filename, if it is present, and install into the dirname of the
+filename. So if the filename is F<debian/tmp/usr/bin>, then that directory
+will be copied to F<debian/package/usr/>. If the filename is
+F<debian/tmp/etc/passwd>, it will be copied to F<debian/package/etc/>.
+
+=item I<file|dir> ... I<destdir>
+
+Lists files (or directories) to install and where to install them to.
+The files will be installed into the first package F<dh_install> acts on.
+
+=back
+
+=cut
+
+init(options => {
+ "autodest" => \$dh{AUTODEST},
+ "list-missing" => \$dh{LIST_MISSING},
+ "fail-missing" => \$dh{FAIL_MISSING},
+ "sourcedir=s" => \$dh{SOURCEDIR},
+});
+
+my $srcdir = '.';
+if (defined($dh{SOURCEDIR})) {
+ $srcdir = $dh{SOURCEDIR};
+ $srcdir =~ s{/+$}{};
+ error("Invalid --sourcedir - must not be empty nor /") if not $srcdir;
+}
+
+my $missing_files = 0;
+
+if ($dh{LIST_MISSING} || $dh{FAIL_MISSING}) {
+ deprecated_functionality('Please use dh_missing --list-missing/--fail-missing instead', 12);
+}
+
+# Support for -X flag.
+my $exclude = '';
+if ($dh{EXCLUDE_FIND}) {
+ $exclude = '! \( '.$dh{EXCLUDE_FIND}.' \)';
+}
+
+# PROMISE: DH NOOP WITHOUT pkgfile-logged(install) cli-options()
+
+foreach my $package (getpackages()) {
+ my (@installed, %dest2sources);
+ my $default_source_dir = default_sourcedir($package);
+ my @search_dirs = ($srcdir);
+ push(@search_dirs, $default_source_dir) if not compat(6);
+
+ # Look at the install files for all packages to handle
+ # list-missing/fail-missing, but skip really installing for
+ # packages that are not being acted on.
+ my $skip_install = process_pkg($package) ? 0 : 1;
+
+ my $tmp=tmpdir($package);
+ my $file=pkgfile($package,"install");
+
+ my @install;
+ if ($file) {
+ @install=filedoublearray($file); # no globbing here; done below
+ }
+
+
+ # With autodest, we can just pretend every pattern was on its own line
+ @install = map { [$_] } map { @$_ } @install if $dh{AUTODEST};
+
+ if (($package eq $dh{FIRSTPACKAGE} || $dh{PARAMS_ALL}) && @ARGV) {
+ if ($dh{AUTODEST}) {
+ # Same as above, with autodest, we can just isolate each entry
+ # - the split is for bug-backwards compatibility (#867866).
+ push(@install, map { [$_] } map { split } @ARGV);
+ } else {
+ # Bug backwards compatibility (#867866). The new "glob_expand"
+ # interface is smart enough to not split on spaces, but dh_install
+ # used to do that... *except* for the "DEST" since it was never
+ # passed to the glob function.
+ my @a = @ARGV;
+ my $dest = pop(@a) if @a > 1;
+ my @srcs = map { split } @a;
+ push(@srcs, $dest) if defined($dest);
+ push(@install, \@srcs);
+ }
+ }
+
+
+ my $glob_error_handler = sub {
+ # Do not require a match for packages that not acted on
+ # (directly). After all, the files might not have been
+ # generated/compiled.
+ return if $skip_install;
+ ++$missing_files;
+ goto \&glob_expand_error_handler_warn_and_discard;
+ };
+
+ foreach my $set (@install) {
+ my ($dest, @filelist, @patterns);
+
+ if (@$set > 1) {
+ $dest=pop @$set;
+ }
+ # Skip excluded patterns. We will need two exclude checks per pattern;
+ # 1) exclude the entire pattern as people expect this to work (#814856)
+ # 2) exclude files matched by the pattern as people could have just
+ # excluded a single file of a "dir/*"-pattern.
+ # This line below filters entire patterns
+ @patterns = grep { not excludefile($_) } @{$set};
+ next if not @patterns;
+ foreach my $glob (@patterns) {
+ my @found = glob_expand(\@search_dirs, $glob_error_handler, $glob);
+ push(@filelist, map { tr{/}{/}s; $_ } @found);
+ }
+
+ if (! @filelist && ! $skip_install) {
+ warning("$package missing files: @$set");
+ ++$missing_files;
+ next;
+ }
+
+ # Do a quick bulk handling of excluded files and update @installed.
+ # - this is for filtering files matched by the pattern
+ @filelist = grep { not excludefile($_) } @filelist if $exclude;
+ push(@installed, @filelist);
+
+ # ... because then we can short-curcit here.
+ next if $skip_install or $missing_files;
+
+ if (not $exclude) {
+ my @unoptimized;
+ for my $src (@filelist) {
+ my $d = $dest // compute_dest($default_source_dir, $src);
+ my $basename = basename($src);
+ if (exists($dest2sources{$d}{$basename})) {
+ # If there is a clash, silently undo the optimizations.
+ # See #866405 and #868169.
+ my $replaced = delete($dest2sources{$d}{$basename});
+ # Associate the $replaced the destination
+ # directory. We cannot be sure that compute_dest will
+ # get it right nor can we blindly set $dest.
+ #
+ # It is technically unnecessary for $src, but we
+ # might as well do it to possibly save a
+ # compute_dest call.
+ push(@unoptimized, [$replaced, $d], [$src, $d]);
+ next;
+ }
+ $dest2sources{$d}{$basename} = $src;
+ }
+ next if not @unoptimized;
+ @filelist = @unoptimized;
+ }
+
+ foreach my $src (@filelist) {
+
+ my $target_dest;
+
+ if (ref($src)) {
+ # On a failed optimization, we will have the
+ # destination directory.
+ ($src, $target_dest) = @{$src};
+ } else {
+ $target_dest = $dest;
+ if (! defined $target_dest) {
+ # Guess at destination directory.
+ $target_dest = compute_dest($default_source_dir, $src);
+ }
+ }
+
+ # Make sure the destination directory exists.
+ install_dir("$tmp/$target_dest");
+
+ if (-d $src && $exclude) {
+ my $basename = basename($src);
+ my $dir = ($basename eq '.') ? $src : "$src/..";
+ my $pwd=`pwd`;
+ chomp $pwd;
+ complex_doit("cd '$dir' && " .
+ "find '$basename' $exclude \\( -type f -or -type l \\) -print0 | LC_ALL=C sort -z | " .
+ "xargs -0 -I {} cp --reflink=auto --parents -dp {} $pwd/$tmp/$target_dest/");
+ # cp is annoying so I need a separate pass
+ # just for empty directories
+ complex_doit("cd '$dir' && " .
+ "find '$basename' $exclude \\( -type d -and -empty \\) -print0 | LC_ALL=C sort -z | " .
+ "xargs -0 -I {} cp --reflink=auto --parents -a {} $pwd/$tmp/$target_dest/");
+ }
+ else {
+ doit("cp", '--reflink=auto', "-a", $src, "$tmp/$target_dest/");
+ }
+ }
+ }
+
+ for my $dest (sort(keys(%dest2sources))) {
+ my @srcs = sort(values(%{$dest2sources{$dest}}));
+ # Make sure the destination directory exists.
+ install_dir("$tmp/$dest");
+ xargs(\@srcs, "cp", '--reflink=auto', "-a", XARGS_INSERT_PARAMS_HERE, "$tmp/$dest/");
+ }
+ log_installed_files($package, @installed);
+}
+
+if ($missing_files) {
+ # There were files we could not install (e.g. patterns that matched nothing)
+ error("missing files, aborting");
+}
+
+if ($dh{LIST_MISSING} || $dh{FAIL_MISSING}) {
+ my @options;
+ foreach (@{$dh{EXCLUDE}}) {
+ push(@options, '--exclude', $_);
+ }
+ push(@options, '--sourcedir', $dh{SOURCEDIR}) if defined($dh{SOURCEDIR});
+ push @options, "--list-missing" if $dh{LIST_MISSING};
+ push @options, "--fail-missing" if $dh{FAIL_MISSING};
+ doit("dh_missing", @options);
+}
+
+sub compute_dest {
+ my ($source_dir, $dest) = @_;
+
+ $dest =~ s/^(.*\/)?\Q$srcdir\E\///;
+ $dest =~ s/^(.*\/)?\Q$source_dir\E\///;
+ $dest = dirname("/".$dest);
+
+ return $dest;
+}
+
+=head1 EXAMPLES
+
+Here are some small examples of configuration files for dh_install.
+
+ # Install my-prog into usr/bin (as "usr/bin/my-prog")
+ my-prog usr/bin
+
+ # Install a plugins directory into usr/share/my-prog
+ # (as "usr/share/my-prog/plugins/")
+ plugins usr/share/my-prog
+
+ # Install a file with spaces in into usr/share/my-prog/data
+ # (as "usr/share/my-prog/data/my datafile with spaces.txt")
+ # ASSUMES COMPAT 13, where substitution patterns are available
+ my${Space}datafile${Space}with${Space}spaces.txt usr/share/my-prog/data
+
+ # Install a library into the multi-arch lib directory
+ # ASSUMES COMPAT 13, where substitution patterns are available
+ build/output/libfrop*.so.* usr/lib/${DEB_HOST_MULTIARCH}
+
+=head1 LIMITATIONS
+
+B<dh_install> cannot rename files or directories, it can only install them
+with the names they already have into wherever you want in the package
+build tree.
+
+There is also no way to filter out results based on build profiles or
+architecture. For documentation content, consider using B<dh_installdocs> or
+B<dh_installexamples> as those helpers account for the B<nodoc> build
+profile.
+
+However, renaming and filtering can be achieved by using B<dh-exec>
+with compatibility level 9 or later. An example debian/I<package>.install
+file using B<dh-exec> could look like:
+
+ #!/usr/bin/dh-exec
+ debian/default.conf => /etc/my-package/start.conf
+ build/foo /usr/bin <!pkg.bar.nofoo>
+
+Please remember the following three things:
+
+=over 4
+
+=item * The package must be using compatibility level 9 or later (see L<debhelper(7)>)
+
+=item * The package will need a build-dependency on dh-exec.
+
+=item * The install file must be marked as executable.
+
+=back
+
+=head1 SEE ALSO
+
+L<debhelper(7)>
+
+This program is a part of debhelper.
+
+=head1 AUTHOR
+
+Joey Hess <joeyh@debian.org>
+
+=cut
diff --git a/dh_installalternatives b/dh_installalternatives
new file mode 100755
index 0000000..7bfbae5
--- /dev/null
+++ b/dh_installalternatives
@@ -0,0 +1,193 @@
+#!/usr/bin/perl
+
+=head1 NAME
+
+dh_installalternatives - install declarative alternative rules
+
+=cut
+
+use strict;
+use warnings;
+use constant LINE_PREFIX => ' ' . q{\\} . "\n ";
+use Debian::Debhelper::Dh_Lib;
+
+our $VERSION = DH_BUILTIN_VERSION;
+
+=head1 SYNOPSIS
+
+B<dh_installalternatives> [S<I<debhelper options>>]
+
+=head1 DESCRIPTION
+
+B<dh_installalternatives> is a debhelper program that is responsible for
+parsing the declarative alternatives format and insert the relevant
+maintscripts snippets to interface with L<update-alternatives(1)>
+
+=head1 FILES
+
+=over 4
+
+=item debian/I<package>.alternatives
+
+An example of the format:
+
+ Name: editor
+ Link: /usr/bin/editor
+ Alternative: /usr/bin/vim.basic
+ Dependents:
+ /usr/share/man/man1/editor.1.gz editor.1.gz /usr/share/man/man1/vim.1.gz
+ /usr/share/man/fr/man1/editor.1.gz editor.fr.1.gz /usr/share/man/fr/man1/vim.1.gz
+ /usr/share/man/it/man1/editor.1.gz editor.it.1.gz /usr/share/man/it/man1/vim.1.gz
+ /usr/share/man/pl/man1/editor.1.gz editor.pl.1.gz /usr/share/man/pl/man1/vim.1.gz
+ /usr/share/man/ru/man1/editor.1.gz editor.ru.1.gz /usr/share/man/ru/man1/vim.1.gz
+ Priority: 50
+
+The fields B<Link>, B<Name>, B<Alternative>, and B<Priority> are mandatory and correspond
+to the L<update-alternatives(1)> B<--install> parameters B<link>, B<name>, B<path>, and
+B<priority> respectively.
+
+The B<Dependents> field is optional and consists of one or more lines. Each non-empty
+line must contain exactly 3 space separated values that match (in order) the values passed
+to the B<--slave> parameter for L<update-alternatives(1)>.
+
+=back
+
+=head1 OPTIONS
+
+=over 4
+
+=item B<-n>, B<--no-scripts>
+
+Do not modify F<postinst>/F<postrm>/F<prerm> scripts.
+
+=back
+
+=cut
+
+init();
+
+# Explicitly discard attempts to use --name; it does not make sense for
+# this helper.
+if ($dh{NAME}) {
+ warning('Ignoring unsupported --name option');
+}
+$dh{NAME} = undef;
+
+# PROMISE: DH NOOP WITHOUT alternatives cli-options()
+
+foreach my $package (@{$dh{DOPACKAGES}}) {
+ my $tmp = tmpdir($package);
+ my $alternatives = pkgfile($package, 'alternatives');
+
+ if (-f $alternatives) {
+ _parse_alternatives_file_and_generate_maintscripts($package, $tmp, $alternatives);
+ }
+}
+
+sub _parse_alternative_and_generate_maintscript {
+ my ($package, $tmpdir, $alternatives_file, $ctrl) = @_;
+
+ my $link_name = $ctrl->{'Name'} // error("Missing mandatory \"Name\" field in ${alternatives_file}");
+ my $link_path = $ctrl->{'Link'}
+ // error("Missing mandatory \"Link\" field for \"${link_name}\" in ${alternatives_file}");
+ my $impl_path = $ctrl->{'Alternative'}
+ // error("Missing mandatory \"Alternative\" field for \"${link_name}\" in ${alternatives_file}");
+ my $priority = $ctrl->{'Priority'}
+ // error("Missing mandatory \"Priority\" field for \"${link_name}\" in ${alternatives_file}");
+ my %maintscript_options;
+
+ if (index($link_name, '/') > -1) {
+ error(qq{Invalid link name "${link_name}" in "${alternatives_file}": Must not contain slash});
+ }
+ my $actual_impl_path = "${tmpdir}/${impl_path}";
+ if ( ! -l $actual_impl_path && ! -e _) {
+ error(qq{Alternative "${impl_path}" for "${link_name}" in ${alternatives_file} does not exist in ${tmpdir}});
+ }
+ if ( -d $actual_impl_path) {
+ error(qq{Alternative "${impl_path}" for "${link_name}" in ${alternatives_file} is a directory});
+ }
+ if ($link_name eq $impl_path) {
+ error(qq{The link name cannot be the same as the implementation path "${link_name}" (in "${alternatives_file}")});
+ }
+
+ $maintscript_options{'RM_OPTIONS'} = "--remove ${link_name} ${impl_path}";
+ $maintscript_options{'INSTALL_OPTIONS'} = "--install ${link_path} ${link_name} ${impl_path} ${priority}";
+
+ if (defined(my $slave_link_text = $ctrl->{'Dependents'})) {
+ my (%dlink_dup, @dependent_links);
+ for my $line (split(/\n/, $slave_link_text)) {
+ my ($dlink_name, $dlink_path, $dimpl_path, $trailing);
+ my $error_with_def = 0;
+ $line =~ s/^\s++//;
+ $line =~ s/\s++$//;
+ next if $line eq ''; # Ignore empty lines
+ ($dlink_path, $dlink_name, $dimpl_path, $trailing) = split(' ', $line, 4);
+ if (not $dlink_name) {
+ warning(qq{Missing link name value (2nd item) for dependent link "${dlink_name}" for "${link_name}"}
+ . qq{ in "${alternatives_file}"});
+ $error_with_def = 1;
+ } elsif (index($dlink_name, '/') > -1) {
+ warning(qq{Invalid dependent link name "${dlink_name}" for "${link_name}"}
+ . qq{ in "${alternatives_file}": Must not contain slash});
+ $error_with_def = 1;
+ } elsif ($dlink_dup{$dlink_name}) {
+ warning(qq{Dependent link "${dlink_name}" is seen more than once for "${link_name}"}
+ . qq{ in ${alternatives_file}});
+ $error_with_def = 1;
+ }
+ if (not $dimpl_path) {
+ warning(qq{Missing path (alternative) value (3rd item) for dependent link "${dlink_name}"}
+ . qq{ for "${link_name}" in "${alternatives_file}"});
+ $error_with_def = 1;
+ }
+ if ($dlink_name eq $dimpl_path) {
+ warning(qq{The link name cannot be the same as the implementation path for "${dlink_name}"}
+ . qq{ in "${alternatives_file}"});
+ $error_with_def = 1;
+ }
+ if ($trailing) {
+ warning(qq{Trailing information for dependent link "${dlink_name}" for "${link_name}"}
+ . qq{ in "${alternatives_file}"});
+ warning("Dependent links must consist of exactly 3 space-separated values");
+ $error_with_def = 1;
+ }
+ if ($error_with_def) {
+ my $link_id = $dlink_name // ('no ' . (scalar(@dependent_links) + 1));
+ error("Error parsing dependent link ${link_id} for \"${link_name}\" in ${alternatives_file}.");
+ }
+ push(@dependent_links, "--slave $dlink_path $dlink_name $dimpl_path");
+ }
+ error("Empty \"Dependents\" field for \"${link_name}\" in ${alternatives_file} (please remove it or add an entry)")
+ if not @dependent_links;
+ $maintscript_options{'INSTALL_OPTIONS'} .= LINE_PREFIX . join(LINE_PREFIX, @dependent_links);
+ }
+ for my $wrong_name (qw(Slave Slaves Slave-Links)) {
+ if ($ctrl->{$wrong_name}) {
+ error("Please use Dependents instead of ${wrong_name}");
+ }
+ }
+
+ autoscript($package, 'postinst', 'postinst-alternatives', \%maintscript_options);
+ autoscript($package, 'prerm', 'prerm-alternatives', \%maintscript_options);
+ return;
+}
+
+sub _parse_alternatives_file_and_generate_maintscripts {
+ my ($package, $tmpdir, $alternatives_file) = @_;
+ my ($ctrl, $fd);
+ require Dpkg::Control::HashCore;
+ open($fd, '<', $alternatives_file) or error("open $alternatives_file failed: $!");
+ while (defined($ctrl = Dpkg::Control::HashCore->new) and ($ctrl->parse($fd, $alternatives_file))) {
+ _parse_alternative_and_generate_maintscript($package, $tmpdir, $alternatives_file, $ctrl);
+ }
+ close($fd);
+ return;
+}
+
+=head1 SEE ALSO
+
+L<debhelper(7)>
+
+This program is a part of debhelper.
+
+=cut
diff --git a/dh_installcatalogs b/dh_installcatalogs
new file mode 100755
index 0000000..aa5f45d
--- /dev/null
+++ b/dh_installcatalogs
@@ -0,0 +1,138 @@
+#!/usr/bin/perl
+
+=head1 NAME
+
+dh_installcatalogs - install and register SGML Catalogs
+
+=cut
+
+use strict;
+use warnings;
+use Debian::Debhelper::Dh_Lib;
+
+our $VERSION = DH_BUILTIN_VERSION;
+
+my $sgmlbasever = "1.28";
+
+=head1 SYNOPSIS
+
+B<dh_installcatalogs> [S<I<debhelper options>>] [B<-n>]
+
+=head1 DESCRIPTION
+
+B<dh_installcatalogs> is a debhelper program that installs and
+registers SGML catalogs. It complies with the Debian XML/SGML policy.
+
+Catalogs will be registered in a supercatalog, in
+F</etc/sgml/I<package>.cat>.
+
+This command automatically adds maintainer script snippets for
+registering and unregistering the catalogs and supercatalogs (unless
+B<-n> is used). These snippets are inserted into the maintainer
+scripts and the B<triggers> file by B<dh_installdeb>; see
+L<dh_installdeb(1)> for an explanation of Debhelper maintainer script
+snippets.
+
+A dependency on B<sgml-base> will be added to B<${misc:Depends}>, so be
+sure your package uses that variable in F<debian/control>.
+
+=head1 FILES
+
+=over 4
+
+=item debian/I<package>.sgmlcatalogs
+
+Lists the catalogs to be installed per package. Each line in that file
+should be of the form C<I<source> I<dest>>, where I<source> indicates where the
+catalog resides in the source tree, and I<dest> indicates the destination
+location for the catalog under the package build area. I<dest> should
+start with F</usr/share/sgml/>.
+
+Supports substitution variables in compat 13 and later as
+documented in L<debhelper(7)>.
+
+=back
+
+=head1 OPTIONS
+
+=over 4
+
+=item B<-n>, B<--no-scripts>
+
+Do not modify F<postinst>/F<postrm>/F<prerm> scripts nor add an
+activation trigger.
+
+=back
+
+=head1 NOTES
+
+Note that this command is not idempotent. L<dh_prep(1)> should be
+called between invocations of this command. Otherwise, it may cause
+multiple instances of the same text to be added to maintainer scripts.
+
+=cut
+
+init();
+
+# PROMISE: DH NOOP WITHOUT sgmlcatalogs cli-options()
+
+foreach my $package (@{$dh{DOPACKAGES}}) {
+ my $tmpdir = tmpdir($package);
+ my $sgmlcatlistfile = pkgfile($package, "sgmlcatalogs");
+ my @sgmlinstalled; # catalogs we've installed
+ if ($#ARGV >= 0) {
+ error("extra command-line arguments");
+ }
+ if ($sgmlcatlistfile) {
+ foreach my $line (filedoublearray($sgmlcatlistfile)) {
+ my $source = $line->[0];
+ my $dest = $line->[1];
+ my $fulldest = "$tmpdir/$dest";
+ $fulldest =~ s|//|/|g; # beautification
+
+ if (! -d dirname($fulldest)) {
+ # Ensure the parent exist
+ install_dir($tmpdir."/".dirname($dest));
+ }
+
+ install_file($source,$fulldest);
+
+ push(@sgmlinstalled,$dest);
+ }
+ }
+ if (@sgmlinstalled) {
+ addsubstvar($package, "misc:Depends", "sgml-base", ">= $sgmlbasever");
+
+ install_dir("$tmpdir/etc/sgml");
+
+ my $centralcat = "/etc/sgml/$package.cat";
+
+ open(my $fd, ">", "$tmpdir$centralcat") || error("failed to write to $tmpdir$centralcat");
+ foreach my $sgmldest (@sgmlinstalled) {
+ print {$fd} "CATALOG " . $sgmldest . "\n";
+ }
+ close($fd) or error("close $tmpdir$centralcat: $!");
+
+ if (! $dh{NOSCRIPTS}) {
+ autotrigger($package, "activate-await", "update-sgmlcatalog");
+ autoscript($package, "postrm", "postrm-sgmlcatalog",
+ { 'CENTRALCAT' => $centralcat });
+ }
+ }
+ else {
+ # remove the dependency
+ addsubstvar($package, "misc:Depends", "sgml-base", ">= $sgmlbasever", 1);
+ }
+}
+
+=head1 SEE ALSO
+
+L<debhelper(7)>
+
+F</usr/share/doc/sgml-base-doc/>
+
+=head1 AUTHOR
+
+Adam Di Carlo <aph@debian.org>
+
+=cut
diff --git a/dh_installchangelogs b/dh_installchangelogs
new file mode 100755
index 0000000..58273d6
--- /dev/null
+++ b/dh_installchangelogs
@@ -0,0 +1,411 @@
+#!/usr/bin/perl
+
+=head1 NAME
+
+dh_installchangelogs - install changelogs into package build directories
+
+=cut
+
+use strict;
+use warnings;
+use Debian::Debhelper::Dh_Lib;
+use Time::Piece;
+
+our $VERSION = DH_BUILTIN_VERSION;
+
+=head1 SYNOPSIS
+
+B<dh_installchangelogs> [S<I<debhelper options>>] [B<-k>] [B<-X>I<item>] [B<--no-trim>] [I<upstream>]
+
+=head1 DESCRIPTION
+
+B<dh_installchangelogs> is a debhelper program that is responsible for
+installing changelogs into package build directories.
+
+An upstream F<changelog> file may be specified as an option. If none
+is specified, B<dh_installchangelogs> may look for files with names
+that seem likely to be changelogs as described in the next paragraphs.
+
+In non-native packages, B<dh_installchangelogs> will first look for
+changelog files installed by the upstream build system into F<<
+usr/share/doc/I<package> >> (of the package build directory) and
+rename the most likely candidate (if any) to F<<
+usr/share/doc/I<package>/changelog >>. Note that
+B<dh_installchangelogs> does I<not> look into any source directory
+(such as F<debian/tmp>). Otherwise, B<dh_installchangelogs> (at
+compatibility level 7 or any later) will look for changelog files in
+the source directory (e.g. the root or the F<docs> subdirectory). It
+will look for F<changelog>, F<changes> and F<history> optionally with
+common extensions (such as F<.txt>, F<.md> and F<.rst>).
+
+If a changelog file is specified and is an F<html> file (determined by file
+extension), it will be installed as F<usr/share/doc/package/changelog.html>
+instead. If the html changelog is converted to plain text, that variant
+can be specified as a second parameter. When no plain text variant is
+specified, a short F<usr/share/doc/package/changelog> is generated,
+pointing readers at the html changelog file.
+
+The B<debchange>-style Debian changelogs are trimmed to include only
+entries more recent than the release date of I<oldstable>.
+No trimming will be performed if the B<--no-trim> option is passed or
+if the B<DEB_BUILD_OPTIONS> environment variable contains B<notrimdch>.
+
+=head1 FILES
+
+=over 4
+
+=item F<debian/changelog>
+
+=item F<debian/NEWS>
+
+=item debian/I<package>.changelog
+
+=item debian/I<package>.NEWS
+
+Automatically installed into usr/share/doc/I<package>/
+in the package build directory.
+
+Use the package specific name if I<package> needs a different
+F<NEWS> or F<changelog> file.
+
+The F<changelog> file is installed with a name of changelog
+for native packages, and F<changelog.Debian> for non-native packages.
+The F<NEWS> file is always installed with a name of F<NEWS.Debian>.
+
+=back
+
+=head1 OPTIONS
+
+=over 4
+
+=item B<-k>, B<--keep>
+
+Keep the original name of the upstream changelog. This will be accomplished
+by installing the upstream changelog as F<changelog>, and making a symlink from
+that to the original name of the F<changelog> file. This can be useful if the
+upstream changelog has an unusual name, or if other documentation in the
+package refers to the F<changelog> file.
+
+=item B<-X>I<item>, B<--exclude=>I<item>
+
+Exclude upstream F<changelog> files that contain I<item> anywhere in their
+filename from being installed.
+
+Note that directory name of the changelog is also part of the match.
+
+=item B<--no-trim>
+
+Install the full changelog, not its trimmed version that includes only
+recent entries.
+
+=item I<upstream>
+
+Install this file as the upstream changelog.
+
+=back
+
+=cut
+
+init(options => {
+ 'keep|k' => \$dh{K_FLAG},
+ 'no-trim' => \$dh{NO_TRIM},
+});
+
+my $news_name="NEWS.Debian";
+my $changelog_name="changelog.Debian";
+
+use constant CUTOFF_DATE_STR => "2019-07-06"; # oldstable = Debian 10 Buster
+use constant CUTOFF_DATE => Time::Piece->strptime(CUTOFF_DATE_STR, "%Y-%m-%d");
+use constant MIN_NUM_ENTRIES => 4;
+
+my $explicit_changelog = @ARGV ? 1 : 0;
+my $default_upstream = $ARGV[0];
+my $default_upstream_text=$default_upstream;
+my $default_upstream_html;
+if (! defined($default_upstream)) {
+ if (! isnative($dh{MAINPACKAGE}) && !compat(6)) {
+ foreach my $dir (qw{. doc docs}) {
+ my $changelog = find_changelog($dir);
+ if ($changelog) {
+ $default_upstream = $changelog;
+ $default_upstream_text = $default_upstream;
+ last;
+ }
+ }
+ }
+ if (isnative($dh{MAINPACKAGE})) {
+ $changelog_name='changelog';
+ }
+}
+elsif ($default_upstream=~m/\.html?$/i) {
+ $default_upstream_html=$default_upstream;
+ $default_upstream_text=$ARGV[1];
+}
+
+sub find_changelog {
+ my ($dir) = @_;
+ my @files=sort glob("$dir/*");
+ foreach my $suffix ('', qw(.txt .md .rst)) {
+ foreach my $name (qw{changelog changes history}) {
+ my @matches=grep {
+ lc basename($_) eq "$name$suffix" && -f $_ && -s _ && ! excludefile($_)
+ } @files;
+ if (@matches) {
+ return shift(@matches);
+ }
+ }
+ }
+ return;
+}
+
+sub install_debian_changelog {
+ my ($changelog, $package, $arch, $tmp) = @_;
+
+ my $changelog_trimmed = generated_file($package, "dh_installchangelogs.dch.trimmed");
+ my $changelog_binnmu = generated_file($package, "dh_installchangelogs.dch.binnmu");
+
+ my ($error_in_changelog, $has_been_trimmed, $oldest_entry_time) =
+ prepare_changesfile("CHANGELOG", $changelog, $changelog_trimmed, $changelog_binnmu);
+
+ if ($error_in_changelog) {
+ # If the changelog could not be trimmed, fall back to the full changelog.
+ warning("$changelog could not be trimmed. The full changelog will be installed.");
+ $changelog_trimmed = $changelog;
+ } elsif ($has_been_trimmed) {
+ # Otherwise add a comment stating that this changelog has been trimmed.
+ my $note = "\n";
+ $note .= "# Older entries have been removed from this changelog.\n";
+ $note .= "# To read the complete changelog use `apt changelog $package`.\n";
+ open(my $log2, ">>", $changelog_trimmed) or error("Cannot open($changelog_trimmed): $!");
+ print($log2 $note) or error("Cannot write($changelog_trimmed): $!");
+ close($log2) or error("Cannot close($changelog_trimmed): $!");
+ }
+
+ install_file($changelog_trimmed, "$tmp/usr/share/doc/$package/$changelog_name");
+ if (-s $changelog_binnmu) {
+ install_file($changelog_binnmu, "$tmp/usr/share/doc/$package/$changelog_name.$arch");
+ }
+
+ return $oldest_entry_time;
+}
+
+sub install_debian_news {
+ my ($news, $package, $oldest_log_entry_time, $tmp) = @_;
+
+ if ($dh{NO_TRIM} || get_buildoption("notrimdch") || !defined($oldest_log_entry_time)) {
+ # Install the whole NEWS file.
+ install_file($news, "$tmp/usr/share/doc/$package/$news_name");
+ return;
+ }
+
+ my $news_trimmed = generated_file($package, "dh_installchangelogs.news.trimmed");
+
+ my ($error_in_news, $has_been_trimmed, $oldest_entry_time) =
+ prepare_changesfile("NEWS", $news, $news_trimmed, undef, $oldest_log_entry_time);
+
+ if ($error_in_news) {
+ # If the NEWS file could not be trimmed, fall back to the full NEWS file.
+ warning("$news could not be trimmed. The full NEWS file will be installed.");
+ $news_trimmed = $news;
+ }
+
+ # Install NEWS unless there are no recent news.
+ install_file($news_trimmed, "$tmp/usr/share/doc/$package/$news_name")
+ unless (-z $news_trimmed);
+}
+
+sub prepare_changesfile {
+ my ($mode, $changesfile, $changesfile_trimmed, $changelog_binnmu, $oldest_log_entry_time) = @_;
+
+ local $ENV{LC_ALL} = "C.UTF-8";
+
+ my $should_be_trimmed = !$dh{NO_TRIM} && !get_buildoption("notrimdch");
+
+ open(my $log1, "<", $changesfile) or error("Cannot open($changesfile): $!");
+ open(my $log2, ">", $changesfile_trimmed) or error("Cannot open($changesfile_trimmed): $!");
+
+ my $oldest_entry_time;
+ my $error_in_changesfile = 0;
+ my $is_binnmu = 0;
+ my $entry = "";
+ my $entry_num = 0;
+ while (my $line=<$log1>) {
+ $entry .= $line;
+
+ # Identify binNUM packages by binary-only=yes in the first line of the changelog.
+ if (($. == 1) && ($line =~ /\A\S.*;.*\bbinary-only=yes/)) {
+ $is_binnmu = 1;
+ }
+
+ # Get out of binNMU mode once we are in the second entry (and throw away one empty line).
+ if ($is_binnmu && ($entry_num eq 1)) {
+ $is_binnmu = 0;
+ $entry_num = 0;
+ $entry = "";
+ next;
+ }
+
+ if ($line =~ /^\s*--\s+.*?\s+<[^>]*>\s+(?<timestamp>.*)$/) {
+ if ($is_binnmu && ($entry_num eq 0)) {
+ # For binNMUs the first changelog entry is written into an extra file to
+ # keep the packages coinstallable.
+ open(my $log_binnum, ">", $changelog_binnmu) or error("Cannot open($changelog_binnmu): $!");
+ print($log_binnum $entry) or error("Cannot write($changelog_binnmu): $!");
+ close($log_binnum) or error("Cannot close($changelog_binnmu): $!");
+
+ # Continue processing the rest of the changelog.
+ $entry = "";
+ $entry_num++;
+ next;
+ }
+
+ my $timestamp = $+{timestamp};
+ $timestamp =~ s/^[A-Za-z]+, +//;
+
+ my $entry_time;
+ eval { $entry_time = Time::Piece->strptime($timestamp, '%d %b %Y %T %z') };
+ if (! defined $entry_time) {
+ $error_in_changesfile = 1;
+ warning("Could not parse timestamp '$timestamp'. $changesfile will not be trimmed.");
+ truncate($log2, 0) or error("Cannot truncate($changesfile_trimmed): $!");
+ last;
+ }
+
+ # Stop processing the changelog if we reached the cut-off date and
+ # at least MIN_NUM_ENTRIES entries have been added.
+ if ($should_be_trimmed && ($mode eq "CHANGELOG") && ($entry_time < CUTOFF_DATE) && ($entry_num >= MIN_NUM_ENTRIES)) {
+ last;
+ }
+
+ # Stop processing the NEWS file if we reached the oldest date in the changelog.
+ if ($should_be_trimmed && ($mode eq "NEWS") && ($entry_time < $oldest_log_entry_time)) { last; }
+
+ # Record the timestamp of what is currently the oldest entry
+ # in the trimmed changelog.
+ $oldest_entry_time = $entry_time;
+
+ # Append entry to trimmed changelog.
+ print($log2 $entry) or error("Cannot write($changesfile_trimmed): $!");
+ $entry = "";
+ $entry_num++;
+ }
+ }
+ # If the whole changelog has not been read, then it has been trimmed.
+ my $has_been_trimmed = !eof($log1);
+
+ close($log1) or error("Cannot close($changesfile): $!");
+ close($log2) or error("Cannot close($changesfile_trimmed): $!");
+
+ return $error_in_changesfile, $has_been_trimmed, $oldest_entry_time
+}
+
+# INTROSPECTABLE: CONFIG-FILES pkgfile(changelog) pkgfile(NEWS)
+
+on_pkgs_in_parallel {
+ foreach my $package (@_) {
+ next if is_udeb($package);
+
+ my $tmp=tmpdir($package);
+ my $changelog = pkgfile($package, 'changelog', 1);
+ my $news = pkgfile($package, 'NEWS', 1);
+ my $upstream_changelog;
+ my ($upstream_changelog_text, $upstream_changelog_html);
+ my $changelog_from_tmp_dir = 0;
+
+ if ($explicit_changelog) {
+ $upstream_changelog = $default_upstream;
+ $upstream_changelog_text = $default_upstream_text;
+ $upstream_changelog_html = $default_upstream_html;
+ } else {
+ # Check if the upstream build system provided a
+ # changelog
+ $upstream_changelog = find_changelog("${tmp}/usr/share/doc/${package}");
+ if ($upstream_changelog) {
+ $upstream_changelog_text = $upstream_changelog;
+ $changelog_from_tmp_dir = 1;
+ } else {
+ $upstream_changelog = $default_upstream;
+ $upstream_changelog_text = $upstream_changelog;
+ }
+ }
+
+ if (! -e $changelog) {
+ error("could not find changelog $changelog");
+ }
+
+ # If it is a symlink to a documentation directory from the same
+ # source package, then don't do anything. Think multi-binary
+ # packages that depend on each other and want to link doc dirs.
+ if (-l "$tmp/usr/share/doc/$package") {
+ my $linkval=readlink("$tmp/usr/share/doc/$package");
+ my %allpackages=map { $_ => 1 } getpackages();
+ if ($allpackages{basename($linkval)}) {
+ next;
+ }
+ # Even if the target doesn't seem to be a doc dir from the
+ # same source package, don't do anything if it's a dangling
+ # symlink.
+ next unless -d "$tmp/usr/share/doc/$package";
+ }
+
+ install_dir("$tmp/usr/share/doc/$package");
+
+ my $oldest_log_entry_time;
+ if (! $dh{NO_ACT}) {
+ my $arch = package_binary_arch($package);
+ $oldest_log_entry_time = install_debian_changelog($changelog, $package, $arch, $tmp);
+ }
+
+ if (-e $news && ! $dh{NO_ACT}) {
+ install_debian_news($news, $package, $oldest_log_entry_time, $tmp);
+ }
+
+ if (defined($upstream_changelog)) {
+ my $link_to;
+ my $base="$tmp/usr/share/doc/$package";
+ if (defined($upstream_changelog_text)) {
+ if ($changelog_from_tmp_dir and not $dh{K_FLAG}) {
+ # mv (unless if it is the same file)
+ rename_path($upstream_changelog_text, "$base/changelog")
+ if basename($upstream_changelog_text) ne 'changelog';
+ reset_perm_and_owner(0644, "$base/changelog");
+ } else {
+ install_file($upstream_changelog_text, "$base/changelog");
+ }
+ $link_to='changelog';
+ }
+ if (defined($upstream_changelog_html)) {
+ if ($changelog_from_tmp_dir and not $dh{K_FLAG}) {
+ # mv (unless if it is the same file)
+ rename_path($upstream_changelog_html, "$base/changelog.html")
+ if basename($upstream_changelog_text) ne 'changelog.html';
+ reset_perm_and_owner(0644, "$base/changelog.html");
+ } else {
+ install_file($upstream_changelog_html,"$base/changelog.html");
+ }
+ $link_to='changelog.html';
+ if (! defined($upstream_changelog_text)) {
+ complex_doit("echo 'See changelog.html.gz' > $base/changelog");
+ reset_perm_and_owner(0644,"$base/changelog");
+ }
+ }
+ if ($dh{K_FLAG}) {
+ # Install symlink to original name of the upstream changelog file.
+ # Use basename in case original file was in a subdirectory or something.
+ doit('ln', '-sf', $link_to, "$tmp/usr/share/doc/$package/".basename($upstream_changelog));
+ }
+ }
+ }
+};
+
+=head1 SEE ALSO
+
+L<debhelper(7)>
+
+This program is a part of debhelper.
+
+=head1 AUTHOR
+
+Joey Hess <joeyh@debian.org>
+
+=cut
diff --git a/dh_installcron b/dh_installcron
new file mode 100755
index 0000000..9325b0e
--- /dev/null
+++ b/dh_installcron
@@ -0,0 +1,90 @@
+#!/usr/bin/perl
+
+=head1 NAME
+
+dh_installcron - install cron scripts into etc/cron.*
+
+=cut
+
+use strict;
+use warnings;
+use Debian::Debhelper::Dh_Lib;
+
+our $VERSION = DH_BUILTIN_VERSION;
+
+=head1 SYNOPSIS
+
+B<dh_installcron> [S<B<debhelper options>>] [B<--name=>I<name>]
+
+=head1 DESCRIPTION
+
+B<dh_installcron> is a debhelper program that is responsible for installing
+cron scripts.
+
+=head1 FILES
+
+=over 4
+
+=item debian/I<package>.cron.daily
+
+=item debian/I<package>.cron.weekly
+
+=item debian/I<package>.cron.monthly
+
+=item debian/I<package>.cron.yearly
+
+=item debian/I<package>.cron.hourly
+
+=item debian/I<package>.cron.d
+
+Installed into the appropriate F<etc/cron.*/> directory in the package
+build directory.
+
+=back
+
+=head1 OPTIONS
+
+=over 4
+
+=item B<--name=>I<name>
+
+Look for files named F<debian/package.name.cron.*> and install them as
+F<etc/cron.*/name>, instead of using the usual files and installing them
+as the package name.
+
+=back
+
+=cut
+
+init();
+
+# PROMISE: DH NOOP WITHOUT cron.hourly cron.daily cron.weekly cron.monthly cron.yearly cron.d cli-options()
+
+foreach my $package (@{$dh{DOPACKAGES}}) {
+ my $tmp=tmpdir($package);
+ foreach my $type (qw{hourly daily weekly monthly yearly}) {
+ my $cron=pkgfile($package,"cron.$type");
+ if ($cron) {
+ install_dir("$tmp/etc/cron.$type");
+ install_prog($cron,"$tmp/etc/cron.$type/".pkgfilename($package));
+ }
+ }
+ # Separate because this needs to be mode 644.
+ my $cron=pkgfile($package,"cron.d");
+ if ($cron) {
+ install_dir("$tmp/etc/cron.d");
+ install_file($cron,"$tmp/etc/cron.d/".pkgfilename($package));
+ }
+}
+
+=head1 SEE ALSO
+
+L<debhelper(7)>
+
+This program is a part of debhelper.
+
+=head1 AUTHOR
+
+Joey Hess <joeyh@debian.org>
+
+=cut
diff --git a/dh_installdeb b/dh_installdeb
new file mode 100755
index 0000000..f9e2452
--- /dev/null
+++ b/dh_installdeb
@@ -0,0 +1,431 @@
+#!/usr/bin/perl
+
+=head1 NAME
+
+dh_installdeb - install files into the DEBIAN directory
+
+=cut
+
+use strict;
+use warnings;
+use Debian::Debhelper::Dh_Lib;
+
+our $VERSION = DH_BUILTIN_VERSION;
+
+=head1 SYNOPSIS
+
+B<dh_installdeb> [S<I<debhelper options>>]
+
+=head1 DESCRIPTION
+
+B<dh_installdeb> is a debhelper program that is responsible for installing
+files into the F<DEBIAN> directories in package build directories with the
+correct permissions.
+
+=head1 FILES
+
+=over 4
+
+=item I<package>.postinst
+
+=item I<package>.preinst
+
+=item I<package>.postrm
+
+=item I<package>.prerm
+
+These maintainer scripts are installed into the F<DEBIAN> directory.
+
+B<dh_installdeb> will perform substitution of known tokens of
+the pattern B<#TOKEN#>. In generally, scripts will want to
+include the B<#DEBHELPER#> to benefit from the shell scripts
+generated by debhelper commands (including those from
+B<dh_installdeb> when it processes I<package>.maintscript files).
+
+The B<#DEBHELPER#> token should be placed on its own line as it is
+often replaced by a multi-line shell script.
+
+=item I<package>.triggers
+
+=item I<package>.shlibs
+
+These control files are installed into the F<DEBIAN> directory.
+
+Note that I<package>.shlibs is only installed in compat level 9 and
+earlier. In compat 10, please use L<dh_makeshlibs(1)>.
+
+=item I<package>.conffiles
+
+This file will be installed into the F<DEBIAN> directory. The
+provided file will be enriched by debhelper to include all the
+B<conffiles> auto-detected by debhelper (the maintainer should
+not list anything there as debhelper assumes it should handle that part).
+
+This file is primarily useful for using "special" entries such as
+the B<< remove-on-upgrade >> feature from dpkg.
+
+=item I<package>.maintscript
+
+Lines in this file correspond to L<dpkg-maintscript-helper(1)>
+commands and parameters. However, the "maint-script-parameters"
+should I<not> be included as debhelper will add those automatically.
+
+Example:
+
+ # Correct
+ rm_conffile /etc/obsolete.conf 0.2~ foo
+ # INCORRECT
+ rm_conffile /etc/obsolete.conf 0.2~ foo -- "$@"
+
+In compat 10 or later, any shell metacharacters will be escaped, so
+arbitrary shell code cannot be inserted here. For example, a line
+such as C<mv_conffile /etc/oldconffile /etc/newconffile> will insert
+maintainer script snippets into all maintainer scripts sufficient to
+move that conffile.
+
+It was also the intention to escape shell metacharacters in previous
+compat levels. However, it did not work properly and as such it was
+possible to embed arbitrary shell code in earlier compat levels.
+
+The B<dh_installdeb> tool will do some basic validation of some of
+the commands listed in this file to catch common mistakes. The
+validation is enabled as a warning since compat 10 and as a hard
+error in compat 12.
+
+Where possible, B<dh_installdeb> may choose to rewrite some or all
+of the entries into equivalent features supported in dpkg without
+relying on maintainer scripts at its sole discretion (examples
+include rewriting B<rm_conffile> into dpkg's B<remove-on-upgrade>).
+The minimum requirement for activating this feature is that debhelper
+runs in compat 10 or later.
+
+Supports substitution variables in compat 13 and later as
+documented in L<debhelper(7)>.
+
+=back
+
+=head1 OPTIONS
+
+=over 4
+
+=item B<-D>I<TOKEN=VALUE>, B<--define> I<TOKEN=VALUE>
+
+Define tokens to be replaced inside the maintainer scripts when
+it is generated. Please note that the limitations described in
+L</Limitations in token names> also applies to tokens defined
+on the command line. Invalid token names will trigger an error.
+
+In the simple case, this parameter will cause B<< #I<TOKEN># >>
+to be replaced by I<VALUE>. If I<VALUE> starts with a literal
+I<@>-sign, then I<VALUE> is expected to point to a file
+containing the actual value to insert.
+
+An explicit declared token with this parameter will replace built-in
+tokens.
+
+Test examples to aid with the understanding:
+
+ cat >> debian/postinst <<EOF
+ #SIMPLE#
+ #FILEBASED#
+ EOF
+ echo -n "Complex value" > some-file
+ dh_installdeb --define SIMPLE=direct --define FILEBASED=@some-file
+
+In this example, B<#SIMPLE#> will expand to B<direct> and B<#FILEBASED#>
+will expand to B<Complex value>.
+
+It is also possible to set package-specific values for a given
+token. This is useful when B<dh_installdeb> is acting on multiple
+packages that need different values for the same token. This is
+done by prefixing the token name with B<< pkg.I<package-name>. >>.
+
+This can be used as in the following example:
+
+ cat >> debian/foo.postinst <<EOF
+ # Script for #PACKAGE#
+ #TOKEN#
+ EOF
+ cat >> debian/bar.postinst <<EOF
+ # Script for #PACKAGE#
+ #TOKEN#
+ EOF
+ cat >> debian/baz.postinst <<EOF
+ # Script for #PACKAGE#
+ #TOKEN#
+ EOF
+ dh_installdeb -pfoo -pbar -pbaz --define TOKEN=default --define pkg.bar.TOKEN=unique-bar-value \
+ --define pkg.baz.TOKEN=unique-baz-value
+
+In this example, B<#TOKEN#> will expand to B<default> in F<debian/foo.postinst>,
+to B<unique-bar-value> in F<debian/bar.postinst> and to B<unique-baz-value>
+in F<debian/baz.postinst>.
+
+Note that the B<#pkg.*#> tokens will be visible in all scripts acted on. E.g.
+you can refer to B<#pkg.bar.TOKEN#> inside F<debian/foo.postinst> and it will
+be replaced by B<unique-bar-value>.
+
+=back
+
+=head1 SUBSTITUTION IN MAINTAINER SCRIPTS
+
+The B<dh_installdeb> will automatically replace the following tokens
+inside a provided maintainer script (if not replaced via B<-D>/B<--define>):
+
+=over 4
+
+=item #DEBHELPER#
+
+This token is by default replaced with generated shell snippets debhelper
+commands. This includes the snippets generated by
+B<dh_installdeb> from I<package>.maintscript file (if present).
+
+=item #DEB_HOST_I<NAME>#, #DEB_BUILD_I<NAME>#, #DEB_TARGET_I<NAME>#
+
+These tokens are replaced with the respective variable from
+L<dpkg-architecture(1)>. In almost all cases, you will want
+use the B<< #DEB_HOST_I<NAME> >> variant in a script to ensure
+you get the right value when cross-building.
+
+On a best effort, tokens of this pattern that do not match
+a variable in L<dpkg-architecture(1)> will be left as-is.
+
+=item #ENV.I<NAME>#
+
+These tokens of this form will be replaced with value of the
+corresponding environment variable. If the environment
+variable is unset, the token is replaced with the empty
+string.
+
+Note that there are limits on which names can be used (see
+L</Limitations in token names>).
+
+=item #PACKAGE#
+
+This token is by default replaced by the package name, which will contain
+the concrete script.
+
+=back
+
+=head2 Limitations in token names
+
+All tokens intended to be substituted must match the regex: #[A-Za-z0-9_.+]+#
+
+Tokens that do not match that regex will be silently ignored if found in the
+script template. Invalid token names passed to B<-D> or B<--define> will
+cause B<dh_installdeb> to reject the command with an error in most cases.
+
+=cut
+
+my %PROVIDED_SUBST;
+
+init(options => {
+ 'define|D=s%' => \%PROVIDED_SUBST,
+});
+
+# dpkg-maintscript-helper commands with their associated dpkg pre-dependency
+# versions.
+my %maintscript_predeps = (
+ "rm_conffile" => "",
+ "mv_conffile" => "",
+ "symlink_to_dir" => "",
+ "dir_to_symlink" => "",
+);
+my %maintscript_validator = (
+ "rm_conffile" => \&_validate_conffile_args,
+ "mv_conffile" => \&_validate_conffile_args,
+);
+
+# INTROSPECTABLE: CONFIG-FILES pkgfile(maintscript) pkgfile(triggers) pkgfile(postinst) pkgfile(preinst) pkgfile(prerm) pkgfile(postrm) pkgfile(menutest) pkgfile(isinstallable) pkgfile(conffiles)
+
+foreach my $package (@{$dh{DOPACKAGES}}) {
+ my $tmp=tmpdir($package);
+
+ install_dir("$tmp/DEBIAN");
+
+ if (is_udeb($package)) {
+ # For udebs, only do the postinst, and no #DEBHELPER#.
+ # Udebs also support menutest and isinstallable scripts.
+ foreach my $script (qw{postinst menutest isinstallable}) {
+ my $f=pkgfile($package,$script);
+ if ($f) {
+ install_prog($f, "$tmp/DEBIAN/$script");
+ }
+ }
+
+ # stop here for udebs
+ next;
+ }
+
+ my $maintscriptfile=pkgfile($package, "maintscript");
+ my @special_conffiles_entries;
+ if ($maintscriptfile) {
+ if (compat(9)) {
+ foreach my $line (filedoublearray($maintscriptfile)) {
+ my $cmd=$line->[0];
+ error("unknown dpkg-maintscript-helper command: $cmd")
+ unless exists $maintscript_predeps{$cmd};
+ addsubstvar($package, "misc:Pre-Depends", "dpkg",
+ ">= $maintscript_predeps{$cmd}")
+ if length $maintscript_predeps{$cmd};
+ my $params=escape_shell(@$line);
+ foreach my $script (qw{postinst preinst prerm postrm}) {
+ autoscript($package, $script, "maintscript-helper",
+ "s!#PARAMS#!$params!g");
+ }
+ }
+ } else {
+ my @maintscripts = filedoublearray($maintscriptfile);
+ my @params;
+ foreach my $line (@maintscripts) {
+ my $cmd=$line->[0];
+ error("unknown dpkg-maintscript-helper command: $cmd")
+ unless exists $maintscript_predeps{$cmd};
+ addsubstvar($package, "misc:Pre-Depends", "dpkg",
+ ">= $maintscript_predeps{$cmd}")
+ if length $maintscript_predeps{$cmd};
+ if (my $validator = $maintscript_validator{$cmd}) {
+ $validator->($package, @{$line});
+ }
+ if (0) { # Disabled for now: #994919 + #994903
+ my $current_conffile = $line->[1];
+ push(@special_conffiles_entries, "remove-on-upgrade ${current_conffile}");
+ addsubstvar($package, "misc:Pre-Depends", "dpkg (>= 1.20.6~)");
+ next;
+ }
+ push(@params, escape_shell(@{$line}) );
+ }
+ if (@params) {
+ foreach my $script (qw{postinst preinst prerm postrm}) {
+ my $subst = sub {
+ my @res;
+ chomp;
+ for my $param (@params) {
+ my $line = $_;
+ $line =~ s{#PARAMS#}{$param}g;
+ push(@res, $line);
+ }
+ $_ = join("\n", @res) . "\n";
+ };
+ autoscript($package, $script, "maintscript-helper", $subst);
+ }
+ }
+ }
+ }
+
+ # Install debian scripts.
+ my $package_subst = debhelper_script_per_package_subst($package, \%PROVIDED_SUBST);
+ foreach my $script (qw{postinst preinst prerm postrm}) {
+ debhelper_script_subst($package, $script, $package_subst);
+ }
+
+ # Install non-executable files
+ my @non_exec_files;
+ # Removed in compat 12.
+ push(@non_exec_files, 'conffiles');
+ # In compat 10, we let dh_makeshlibs handle "shlibs".
+ push(@non_exec_files, 'shlibs') if compat(9);
+ foreach my $file (@non_exec_files) {
+ my $f=pkgfile($package,$file);
+ if ($f) {
+ install_file($f, "$tmp/DEBIAN/$file");
+ }
+ }
+
+ install_triggers($package, $tmp);
+ if (@special_conffiles_entries) {
+ open(my $fd, '>>', "$tmp/DEBIAN/conffiles");
+ for my $line (@special_conffiles_entries) {
+ print {$fd} "${line}\n";
+ }
+ close($fd);
+ }
+
+ # Automatic conffiles registration: If it is in /etc, it is a
+ # conffile.
+ if ( -d "$tmp/etc") {
+ complex_doit("find $tmp/etc -type f -printf '/etc/%P\n' | LC_ALL=C sort >> $tmp/DEBIAN/conffiles");
+ # Anything found?
+ if (-z "$tmp/DEBIAN/conffiles") {
+ rm_files("$tmp/DEBIAN/conffiles");
+ }
+ }
+
+ if ( -f "$tmp/DEBIAN/conffiles") {
+ reset_perm_and_owner(0644, "$tmp/DEBIAN/conffiles");
+ }
+}
+
+sub install_triggers {
+ my ($package, $tmp) = @_;
+ my $generated = generated_file($package, 'triggers', 0);
+ my @sources = grep { -f $_ } (
+ pkgfile($package, 'triggers'),
+ $generated,
+ );
+ my $target = "$tmp/DEBIAN/triggers";
+ return if not @sources;
+ if (@sources > 1) {
+ my $merged = "${generated}.merged";
+ open(my $ofd, '>', $merged)
+ or error("open ${target} failed: $!");
+ for my $src (@sources) {
+ open(my $ifd, '<', $src)
+ or error("open ${src} failed: $!");
+ print {$ofd} $_ while <$ifd>;
+ close($ifd);
+ }
+ close($ofd) or error("close ${merged} failed: $!");
+ @sources = ($merged);
+ }
+ install_file($sources[0], $target);
+}
+
+sub _validate_conffile_args {
+ my ($package, $cmd, @args) = @_;
+ my ($current_conffile, $new_conffile, $prior_version, $owning_package, $other);
+ for my $arg (@args) {
+ if ($arg eq '--') {
+ _maybe_error("The maintscripts file for $package includes a \"--\" for one of the ${cmd} commands, but it should not");
+ }
+ }
+ if ($cmd eq 'rm_conffile') {
+ ($current_conffile, $prior_version, $owning_package, $other) = @args;
+ } else {
+ ($current_conffile, $new_conffile, $prior_version, $owning_package, $other) = @args;
+ }
+ $current_conffile //= '';
+ _maybe_error("The current conffile path for ${cmd} must be present and absolute, got ${current_conffile}")
+ if not $current_conffile or substr($current_conffile, 0, 1) ne '/';
+ _maybe_error("The new conffile path for ${cmd} must be present and absolute, got ${new_conffile}")
+ if $cmd eq 'mv_conffile' and (not $new_conffile or substr($new_conffile, 0, 1) ne '/');
+
+ _maybe_error("The version for ${cmd} ${current_conffile} is not valid, got ${prior_version}")
+ if $prior_version and $prior_version !~ m{^${Debian::Debhelper::Dh_Lib::PKGVERSION_REGEX}$}o;
+ _maybe_error("The owning package for ${cmd} ${current_conffile} is not valid, got ${owning_package}")
+ if $owning_package and $owning_package !~ m{^${Debian::Debhelper::Dh_Lib::PKGNAME_REGEX}$}o;
+ if (defined($other)) {
+ warning("Too many arguments for ${cmd} ${current_conffile}");
+ }
+}
+
+sub _maybe_error {
+ my ($msg) = @_;
+ if (compat(11)) {
+ warning($msg);
+ } else {
+ error($msg);
+ }
+}
+
+=head1 SEE ALSO
+
+L<debhelper(7)>
+
+This program is a part of debhelper.
+
+=head1 AUTHOR
+
+Joey Hess <joeyh@debian.org>
+
+=cut
diff --git a/dh_installdebconf b/dh_installdebconf
new file mode 100755
index 0000000..79d50d2
--- /dev/null
+++ b/dh_installdebconf
@@ -0,0 +1,243 @@
+#!/usr/bin/perl
+
+=head1 NAME
+
+dh_installdebconf - install files used by debconf in package build directories
+
+=cut
+
+use strict;
+use warnings;
+use Debian::Debhelper::Dh_Lib;
+
+our $VERSION = DH_BUILTIN_VERSION;
+
+=head1 SYNOPSIS
+
+B<dh_installdebconf> [S<I<debhelper options>>] [B<-n>] [S<B<--> I<params>>]
+
+=head1 DESCRIPTION
+
+B<dh_installdebconf> is a debhelper program that is responsible for installing
+files used by debconf into package build directories.
+
+It also automatically generates the F<postrm> commands needed to interface
+with debconf. The commands are added to the maintainer scripts by
+B<dh_installdeb>. See L<dh_installdeb(1)> for an explanation of how that
+works.
+
+Note that if you use debconf, your package probably needs to depend on it
+(it will be added to B<${misc:Depends}> by this program).
+
+Note that for your config script to be called by B<dpkg>, your F<postinst>
+needs to source debconf's confmodule. B<dh_installdebconf> does not
+install this statement into the F<postinst> automatically as it is too
+hard to do it right.
+
+=head1 FILES
+
+=over 4
+
+=item debian/I<package>.config
+
+This is the debconf F<config> script, and is installed into the F<DEBIAN>
+directory in the package build directory.
+
+Inside the script, the token B<#DEBHELPER#> is replaced with
+shell script snippets generated by other debhelper commands.
+
+=item debian/I<package>.templates
+
+This is the debconf F<templates> file, and is installed into the F<DEBIAN>
+directory in the package build directory.
+
+=item F<debian/po/>
+
+If this directory is present, this program will automatically use
+L<po2debconf(1)> to generate merged templates
+files that include the translations from there.
+
+For this to work, your package should build-depend on F<po-debconf>.
+
+=back
+
+=head1 OPTIONS
+
+=over 4
+
+=item B<-n>, B<--no-scripts>
+
+Do not modify F<postrm> script.
+
+=item B<--> I<params>
+
+Pass the params to B<po2debconf>.
+
+=item B<-D>I<TOKEN=VALUE>, B<--define> I<TOKEN=VALUE>
+
+Define tokens to be replaced inside the maintainer scripts when
+it is generated. Please note that the limitations described in
+L</Limitations in token names> also applies to tokens defined
+on the command line. Invalid token names will trigger an error.
+
+In the simple case, this parameter will cause B<< #I<TOKEN># >>
+to be replaced by I<VALUE>. If I<VALUE> starts with a literal
+I<@>-sign, then I<VALUE> is expected to point to a file
+containing the actual value to insert.
+
+An explicit declared token with this parameter will replace built-in
+tokens.
+
+Test examples to aid with the understanding:
+
+ cat >> debian/config <<EOF
+ #SIMPLE#
+ #FILEBASED#
+ EOF
+ echo -n "Complex value" > some-file
+ dh_installdeb --define SIMPLE=direct --define FILEBASED=@some-file
+
+In this example, B<#SIMPLE#> will expand to B<direct> and B<#FILEBASED#>
+will expand to B<Complex value>.
+
+It is also possible to set package-specific values for a given
+token. This is useful when B<dh_installdebconf> is acting on multiple
+packages that need different values for the same token. This is
+done by prefixing the token name with B<< pkg.I<package-name>. >>.
+
+This can be used as in the following example:
+
+ cat >> debian/foo.config <<EOF
+ # Script for #PACKAGE#
+ #TOKEN#
+ EOF
+ cat >> debian/bar.config <<EOF
+ # Script for #PACKAGE#
+ #TOKEN#
+ EOF
+ cat >> debian/baz.config <<EOF
+ # Script for #PACKAGE#
+ #TOKEN#
+ EOF
+ dh_installdebconf -pfoo -pbar -pbaz --define TOKEN=default --define pkg.bar.TOKEN=unique-bar-value \
+ --define pkg.baz.TOKEN=unique-baz-value
+
+In this example, B<#TOKEN#> will expand to B<default> in F<debian/foo.config>,
+to B<unique-bar-value> in F<debian/bar.config> and to B<unique-baz-value>
+in F<debian/baz.config>.
+
+Note that the B<#pkg.*#> tokens will be visible in all scripts acted on. E.g.
+you can refer to B<#pkg.bar.TOKEN#> inside F<debian/foo.config> and it will
+be replaced by B<unique-bar-value>.
+
+=back
+
+=head1 SUBSTITUTION IN MAINTAINER SCRIPTS
+
+The B<dh_installdebconf> will automatically replace the following tokens
+inside a provided maintainer script (if not replaced via B<-D>/B<--define>):
+
+=over 4
+
+=item #DEB_HOST_I<NAME>#, #DEB_BUILD_I<NAME>#, #DEB_TARGET_I<NAME>#
+
+These tokens are replaced with the respective variable from
+L<dpkg-architecture(1)>. In almost all cases, you will want
+use the B<< #DEB_HOST_I<NAME> >> variant in a script to ensure
+you get the right value when cross-building.
+
+On a best effort, tokens of this pattern that do not match
+a variable in L<dpkg-architecture(1)> will be left as-is.
+
+=item #ENV.I<NAME>#
+
+These tokens of this form will be replaced with value of the
+corresponding environment variable. If the environment
+variable is unset, the token is replaced with the empty
+string.
+
+Note that there are limits on which names can be used (see
+L</Limitations in token names>).
+
+=item #PACKAGE#
+
+This token is by default replaced by the package name, which will contain
+the concrete script.
+
+=back
+
+=head2 Limitations in token names
+
+All tokens intended to be substituted must match the regex: #[A-Za-z0-9_.+]+#
+
+Tokens that do not match that regex will be silently ignored if found in the
+script template. Invalid token names passed to B<-D> or B<--define> will
+cause B<dh_installdebconf> to reject the command with an error in most cases.
+
+=cut
+
+my %PROVIDED_SUBST;
+
+init(options => {
+ 'define|D=s%' => \%PROVIDED_SUBST,
+});
+
+my @extraparams;
+if (defined($dh{U_PARAMS})) {
+ @extraparams=@{$dh{U_PARAMS}};
+}
+
+# PROMISE: DH NOOP WITHOUT config templates cli-options()
+
+foreach my $package (@{$dh{DOPACKAGES}}) {
+ my $tmp=tmpdir($package);
+ my $config=pkgfile($package,"config");
+ my $templates=pkgfile($package,"templates");
+
+ install_dir("$tmp/DEBIAN");
+
+ if (! is_udeb($package)) {
+ # Install debian scripts.
+ my $package_subst = debhelper_script_per_package_subst($package, \%PROVIDED_SUBST);
+ debhelper_script_subst($package, "config", $package_subst);
+ }
+
+ if ($templates ne '') {
+ # Are there old-style translated templates?
+ if (glob("$templates.??"), glob("$templates.??_??")) {
+ warning "Ignoring debian/templates.ll files. Switch to po-debconf!";
+ }
+
+ umask(0022); # since I do a redirect below
+
+ if (-d "debian/po") {
+ complex_doit("po2debconf @extraparams $templates > $tmp/DEBIAN/templates");
+ }
+ else {
+ install_file($templates,"$tmp/DEBIAN/templates");
+ }
+ }
+
+ # I'm going with debconf 0.5 because it was the first
+ # "modern" one. udebs just need cdebconf.
+ my $debconfdep=is_udeb($package) ? "cdebconf-udeb" : "debconf (>= 0.5) | debconf-2.0";
+ if ($config ne '' || $templates ne '') {
+ addsubstvar($package, "misc:Depends", $debconfdep);
+ }
+
+ if (($config ne '' || $templates ne '') && ! $dh{NOSCRIPTS}) {
+ autoscript($package,"postrm","postrm-debconf");
+ }
+}
+
+=head1 SEE ALSO
+
+L<debhelper(7)>
+
+This program is a part of debhelper.
+
+=head1 AUTHOR
+
+Joey Hess <joeyh@debian.org>
+
+=cut
diff --git a/dh_installdirs b/dh_installdirs
new file mode 100755
index 0000000..8d33b10
--- /dev/null
+++ b/dh_installdirs
@@ -0,0 +1,141 @@
+#!/usr/bin/perl
+
+=head1 NAME
+
+dh_installdirs - create subdirectories in package build directories
+
+=cut
+
+use strict;
+use warnings;
+use Debian::Debhelper::Dh_Lib;
+
+our $VERSION = DH_BUILTIN_VERSION;
+
+=head1 SYNOPSIS
+
+B<dh_installdirs> [S<I<debhelper options>>] [B<-A>] [B<--sourcedir=>I<dir>] [B<--create-in-sourcedir>] [S<I<dir> ...>]
+
+=head1 DESCRIPTION
+
+B<dh_installdirs> is a debhelper program that is responsible for creating
+subdirectories in package build directories.
+
+Many packages can get away with omitting the call to B<dh_installdirs>
+completely. Notably, other B<dh_*> commands are expected to create
+directories as needed.
+
+=head1 FILES
+
+=over 4
+
+=item debian/I<package>.dirs
+
+Lists directories to be created in I<package>.
+
+Generally, there is no need to list directories created by the
+upstream build system or directories needed by other B<debhelper>
+commands.
+
+Supports substitution variables in compat 13 and later as
+documented in L<debhelper(7)>.
+
+=back
+
+=head1 OPTIONS
+
+=over 4
+
+=item B<-A>, B<--all>
+
+Create any directories specified by command line parameters in ALL packages
+acted on, not just the first.
+
+=item B<--create-in-sourcedir>, B<--no-create-in-sourcedir>
+
+Whether to create the specified directories in the source directory
+(usually F<debian/tmp>) I<in addition to> in the package build directory
+(usually F<< debian/I<package> >>).
+
+The default is B<--no-create-in-sourcedir>.
+
+=item B<--sourcedir=>I<dir>
+
+Consider I<dir> the source directory for the packages acted on instead
+of the default (which is usually F<debian/tmp>).
+
+Please note that this option is dependent on the
+B<--create-in-sourcedir> option (when B<--no-create-in-sourcedir> is
+in effect, this option does nothing in B<dh_installdirs>).
+
+=item I<dir> ...
+
+Create these directories in the package build directory of the first
+package acted on. (Or in all packages if B<-A> is specified.)
+
+=back
+
+=cut
+
+my $create_in_sourcedir = 0;
+
+init(options => {
+ 'sourcedir=s' => \$dh{SOURCEDIR},
+ 'create-in-sourcedir!' => \$create_in_sourcedir,
+});
+
+# PROMISE: DH NOOP WITHOUT dirs cli-options()
+
+foreach my $package (@{$dh{DOPACKAGES}}) {
+ my $tmp=tmpdir($package);
+ my $file=pkgfile($package,"dirs");
+ my $srcdir = $dh{SOURCEDIR} // default_sourcedir($package);
+
+ install_dir($tmp) if compat(10);
+
+ my @dirs;
+
+ if ($file) {
+ @dirs=filearray($file)
+ }
+
+ if (($package eq $dh{FIRSTPACKAGE} || $dh{PARAMS_ALL}) && @ARGV) {
+ push @dirs, @ARGV;
+ }
+
+ if (@dirs) {
+ # Stick the $tmp onto the front of all the dirs.
+ # This is necessary, for 2 reasons, one to make them
+ # be in the right directory, but more importantly, it
+ # protects against the danger of absolute dirs being
+ # specified.
+ my @make_dirs;
+ push(@make_dirs, map {
+ my $dir = "$tmp/$_";
+ $dir =~ tr:/:/:s; # just beautification.
+ $dir;
+ } @dirs);
+ if ($create_in_sourcedir) {
+ push(@make_dirs, map {
+ my $dir = "${srcdir}/$_";
+ $dir =~ tr:/:/:s; # just beautification.
+ $dir;
+ } @dirs);
+ }
+
+ # Create dirs.
+ install_dir(@make_dirs);
+ }
+}
+
+=head1 SEE ALSO
+
+L<debhelper(7)>
+
+This program is a part of debhelper.
+
+=head1 AUTHOR
+
+Joey Hess <joeyh@debian.org>
+
+=cut
diff --git a/dh_installdocs b/dh_installdocs
new file mode 100755
index 0000000..355bc08
--- /dev/null
+++ b/dh_installdocs
@@ -0,0 +1,448 @@
+#!/usr/bin/perl
+
+=encoding UTF-8
+
+=head1 NAME
+
+dh_installdocs - install documentation into package build directories
+
+=cut
+
+use strict;
+use warnings;
+use Debian::Debhelper::Dh_Lib;
+
+our $VERSION = DH_BUILTIN_VERSION;
+
+=head1 SYNOPSIS
+
+B<dh_installdocs> [S<I<debhelper options>>] [B<-A>] [B<-X>I<item>] [S<I<file> ...>]
+
+=head1 DESCRIPTION
+
+B<dh_installdocs> is a debhelper program that is responsible for installing
+documentation into F<usr/share/doc/package> in package build directories.
+
+In compat 10 and earlier, L<dh_install(1)> may be a better tool for handling
+the upstream documentation, when upstream's own build system installs all the desired documentation
+correctly. In this case, B<dh_installdocs> is still useful for installing
+packaging related documentation (e.g. the F<debian/copyright> file).
+
+From debhelper compatibility level 11 on, B<dh_install> will fall back to
+looking in F<debian/tmp> for files, if it does not find them in the current
+directory (or wherever you've told it to look using B<--sourcedir>).
+
+In compat 11 and later, B<dh_installdocs> offers many of the features that
+L<dh_install(1)> also has. Furthermore, B<dh_installdocs> also supports
+the B<nodoc> build profile to exclude documentation (regardless of compat
+level).
+
+=head1 FILES
+
+=over 4
+
+=item debian/I<package>.docs
+
+List documentation files to be installed into I<package>.
+
+Supports substitution variables in compat 13 and later as
+documented in L<debhelper(7)>.
+
+=item F<debian/copyright>
+
+The copyright file is installed into all packages, unless a more
+specific copyright file is available.
+
+=item debian/I<package>.copyright
+
+=item debian/I<package>.README.Debian
+
+=item debian/I<package>.TODO
+
+Each of these files is automatically installed if present for a
+I<package>.
+
+=item F<debian/README.Debian>
+
+=item F<debian/TODO>
+
+These files are installed into the first binary package listed in
+debian/control.
+
+Note that F<README.debian> files are also installed as F<README.Debian>,
+and F<TODO> files will be installed as F<TODO.Debian> in non-native packages.
+
+=item debian/I<package>.doc-base
+
+Installed as doc-base control files. Note that the doc-id will be
+determined from the B<Document:> entry in the doc-base control file in
+question. In the event that multiple doc-base files in a single source
+package share the same doc-id, they will be installed to
+usr/share/doc-base/package instead of usr/share/doc-base/doc-id.
+
+=item debian/I<package>.doc-base.*
+
+If your package needs to register more than one document, you need
+multiple doc-base files, and can name them like this. In the event
+that multiple doc-base files of this style in a single source package
+share the same doc-id, they will be installed to
+usr/share/doc-base/package-* instead of usr/share/doc-base/doc-id.
+
+Please be aware that this deduplication is currently done in memory
+only, so for now it requires B<dh_installdocs> to be called no more
+than once during the package build. Calling B<dh_installdocs
+-p>I<package> in combination with using
+F<debian/>I<package>F<.doc-base.*> files can lead to uninstallable
+packages. See L<https://bugs.debian.org/980903> for details.
+
+=back
+
+=head1 OPTIONS
+
+=over 4
+
+=item B<-A>, B<--all>
+
+Install all files specified by command line parameters in ALL packages
+acted on.
+
+=item B<-X>I<item>, B<--exclude=>I<item>
+
+Exclude files that contain I<item> anywhere in their filename from
+being installed. Note that this includes doc-base files.
+
+=item B<--sourcedir=>I<dir>
+
+Look in the specified directory for files to be installed. This option
+requires compat 11 or later (it is silently ignored in compat 10 or earlier).
+
+Note that this is not the same as the B<--sourcedirectory> option used
+by the B<dh_auto_>I<*> commands. You rarely need to use this option, since
+B<dh_installman> automatically looks for files in F<debian/tmp> in debhelper
+compatibility level 11 and above.
+
+=item B<--doc-main-package=>I<main-package>
+
+Set the main package for a documentation package. This is used to
+install the documentation of the documentation package in F<<
+/usr/share/doc/I<main-package> >> as recommended by the Debian policy
+manual 3.9.7 in §12.3.
+
+In compat 11 (or later), this option is only useful if debhelper's
+auto-detection of the main package is wrong. The option can also be
+used to silence a warning from debhelper when the auto-detection fails
+but the default happens to be correct.
+
+This option cannot be used when B<dh_installdocs> is instructed to act
+on multiple packages. If you need this option, you will generally
+need to combine it with B<-p> to ensure exactly one package is acted
+on.
+
+Please keep in mind that some documentation (the copyright file,
+README.Debian, etc.) will be unaffected by this option.
+
+=item B<--link-doc=>I<package>
+
+Make the documentation directory of all packages acted on be a symlink to
+the documentation directory of I<package>. This has no effect when acting on
+I<package> itself, or if the documentation directory to be created already
+exists when B<dh_installdocs> is run. To comply with policy, I<package> must
+be a binary package that comes from the same source package.
+
+debhelper will try to avoid installing files into linked documentation
+directories that would cause conflicts with the linked package. The B<-A>
+option will have no effect on packages with linked documentation
+directories, and F<copyright>, F<changelog>, F<README.Debian>, and F<TODO> files will
+not be installed.
+
+(An older method to accomplish the same thing, which is still supported,
+is to make the documentation directory of a package be a dangling symlink,
+before calling B<dh_installdocs>.)
+
+Please note that this option only applies to the documentation
+directory for the package itself. When the package ships
+documentation for another package (e.g. see B<--doc-main-package>), it
+will not use a symlink for the documentation of the other package.
+
+
+B<CAVEAT 1>: If a previous version of the package was built without this
+option and is now built with it (or vice-versa), it requires a "dir to
+symlink" (or "symlink to dir") migration. Since debhelper has no
+knowledge of previous versions, you have to enable this migration
+itself.
+
+This can be done by providing a "debian/I<package>.maintscript" file
+and using L<dh_installdeb(1)> to provide the relevant maintainer
+script snippets.
+
+B<CAVEAT 2>: The use of B<--link-doc> should only be done when the
+packages have same "architecture" type. A link from an architecture
+independent package to an architecture dependent package (or vice
+versa) will not work. Since compat 10, debhelper will actively reject
+unsupported combinations.
+
+=item I<file> ...
+
+Install these files as documentation into the first package acted on. (Or
+in all packages if B<-A> is specified).
+
+=back
+
+=head1 EXAMPLES
+
+This is an example of a F<debian/package.docs> file:
+
+ README
+ TODO
+ debian/notes-for-maintainers.txt
+ docs/manual.txt
+ docs/manual.pdf
+ docs/manual-html/
+
+=head1 NOTES
+
+Note that B<dh_installdocs> will happily copy entire directory hierarchies if
+you ask it to (similar to B<cp -a>). If it is asked to install a
+directory, it will install the complete contents of the directory.
+
+=cut
+
+my %docdir_created;
+# Create documentation directories on demand. This allows us to use dangling
+# symlinks for linked documentation directories unless additional files need
+# to be installed.
+sub ensure_docdir {
+ my $package=shift;
+ return if $docdir_created{$package};
+ my $tmp=tmpdir($package);
+
+ my $target;
+ if ($dh{LINK_DOC} && $dh{LINK_DOC} ne $package) {
+ $target="$tmp/usr/share/doc/$dh{LINK_DOC}";
+ }
+ else {
+ $target="$tmp/usr/share/doc/$package";
+ }
+
+ # If this is a symlink, leave it alone.
+ if (! -d $target && ! -l $target) {
+ install_dir($target);
+ }
+ $docdir_created{$package}=1;
+}
+
+init(options => {
+ "link-doc=s" => \$dh{LINK_DOC},
+ "sourcedir=s" => \$dh{SOURCEDIR},
+ 'doc-main-package=s' => \$dh{DOC_MAIN_PACKAGE},
+});
+
+my $called_getpackages = 0;
+my $default_error_handler = compat(10) ? \&glob_expand_error_handler_reject_nomagic_warn_discard : \&glob_expand_error_handler_reject;
+my $nodocs = is_build_profile_active('nodoc') || get_buildoption('nodoc') ? 1 : 0;
+# We cannot assume documentation is built under nodoc, but if it is we must flag it as handled
+# or dh_missing might make noise.
+$default_error_handler = \&glob_expand_error_handler_silently_ignore if $nodocs;
+
+if (@{$dh{DOPACKAGES}} > 1 and $dh{DOC_MAIN_PACKAGE}) {
+ error('--doc-main-package should be used with -p<doc-pkg>');
+}
+
+if ($dh{DOC_MAIN_PACKAGE}) {
+ assert_opt_is_known_package($dh{DOC_MAIN_PACKAGE}, '--doc-main-package');
+}
+assert_opt_is_known_package($dh{LINK_DOC}, '--link-doc') if ($dh{LINK_DOC});
+
+
+# INTROSPECTABLE: CONFIG-FILES pkgfile(docs) pkgfile(copyright) pkgfile(TODO) pkgfile(README.Debian)
+
+foreach my $package (getpackages()) {
+ next if is_udeb($package);
+
+ my $tmp=tmpdir($package);
+ my $file=pkgfile($package,"docs");
+ my $link_doc=($dh{LINK_DOC} && $dh{LINK_DOC} ne $package);
+ my $skip_install = process_pkg($package) ? 0 : 1;
+ my @search_dirs = ('.');
+ my $error_handler = $skip_install ? \&glob_expand_error_handler_silently_ignore : $default_error_handler;
+ @search_dirs = ($dh{SOURCEDIR} // '.', default_sourcedir($package)) if not compat(10);
+
+ if (not $skip_install) {
+ if ($link_doc) {
+ getpackages('both') unless $called_getpackages++;
+
+ if (package_binary_arch($package) ne package_binary_arch($dh{LINK_DOC})) {
+ if (compat(9)) {
+ warning("WARNING: --link-doc between architecture all and not all packages breaks binNMUs");
+ } else {
+ error("--link-doc not allowed between ${package} and $dh{LINK_DOC} (one is arch:all and the other not)");
+ }
+ }
+ # Make sure that the parent directory exists.
+ if (!-d "$tmp/usr/share/doc" && !-l "$tmp/usr/share/doc") {
+ install_dir("$tmp/usr/share/doc");
+ }
+ # Create symlink to another documentation directory if
+ # necessary.
+ if (!-d "$tmp/usr/share/doc/$package" &&
+ !-l "$tmp/usr/share/doc/$package") {
+ make_symlink_raw_target($dh{LINK_DOC}, "$tmp/usr/share/doc/$package");
+ # Policy says that if you make your documentation
+ # directory a symlink, then you have to depend on
+ # the target.
+ addsubstvar($package, 'misc:Depends', "$dh{LINK_DOC} (= \${binary:Version})");
+ }
+ } else {
+ ensure_docdir($package);
+ }
+ }
+
+ my @docs;
+
+ if ($file) {
+ @docs = filearray($file, \@search_dirs, $error_handler);
+ }
+
+ if (($package eq $dh{FIRSTPACKAGE} || ($dh{PARAMS_ALL} && !$link_doc)) && @ARGV) {
+ push @docs, @ARGV;
+ }
+
+ log_installed_files($package, @docs);
+
+ next if $skip_install;
+
+ if (not $nodocs and @docs) {
+ my $exclude = ' -and ! -empty';
+ my $target_package = compute_doc_main_package($package);
+ if (not defined($target_package)) {
+ warning("Cannot auto-detect main package for ${package}. If the default is wrong, please use --doc-main-package");
+ $target_package = $package;
+ } elsif ($dh{PARAMS_ALL} and $package ne $target_package and not $dh{DOC_MAIN_PACKAGE}) {
+ warning("Not using auto-detected $target_package as main doc package for $package: With -A/--all, this would cause file-conflicts.");
+ $target_package = $package;
+ }
+ if ($dh{EXCLUDE_FIND}) {
+ $exclude .= ' -and ! \( '.$dh{EXCLUDE_FIND}.' \)';
+ }
+ my $target_dir = "${tmp}/usr/share/doc/${target_package}";
+ install_dir($target_dir) unless -l $target_dir;
+
+ foreach my $doc (@docs) {
+ next if excludefile($doc);
+ next if -f $doc && ! -s _; # ignore empty files
+ ensure_docdir($package);
+ if (-d $doc && length $exclude) {
+ my $basename = basename($doc);
+ my $dir = ($basename eq '.') ? $doc : "$doc/..";
+ my $pwd=`pwd`;
+ chomp $pwd;
+ # Gracefully handling tmpdir being absolute (-P/...)
+ my $docdir = $target_dir =~ m{^/} ? $target_dir : "${pwd}/${target_dir}";
+ complex_doit("cd '$dir' && " .
+ "find '$basename' \\( -type f -or -type l \\)$exclude -print0 | LC_ALL=C sort -z | " .
+ "xargs -0 -I {} cp --reflink=auto --parents -dp {} $docdir");
+ }
+ else {
+ doit("cp", '--reflink=auto', "-a", $doc, $target_dir);
+ }
+ }
+ doit("chown","-R","0:0","$tmp/usr/share/doc") if should_use_root();
+ doit("chmod","-R","u+rw,go=rX","$tmp/usr/share/doc");
+ }
+
+ # .Debian is correct, according to policy, but I'm easy.
+ my $readme_debian=pkgfile($package,'README.Debian', $package eq $dh{MAINPACKAGE} ? 1 : 0);
+ if (! $readme_debian) {
+ $readme_debian=pkgfile($package,'README.debian', $package eq $dh{MAINPACKAGE} ? 1 : 0);
+ }
+ if (! $link_doc && $readme_debian && ! excludefile($readme_debian)) {
+ ensure_docdir($package);
+ install_file($readme_debian,
+ "$tmp/usr/share/doc/$package/README.Debian");
+ }
+
+ my $todo=pkgfile($package, 'TODO', $package eq $dh{MAINPACKAGE} ? 1 : 0);
+ if (! $link_doc && $todo && ! excludefile($todo)) {
+ ensure_docdir($package);
+ if (isnative($package)) {
+ install_file($todo, "$tmp/usr/share/doc/$package/TODO");
+ }
+ else {
+ install_file($todo,
+ "$tmp/usr/share/doc/$package/TODO.Debian");
+ }
+ }
+
+ # If the "directory" is a dangling symlink, then don't install
+ # the copyright file. This is useful for multibinary packages
+ # that share a doc directory.
+ if (! $link_doc && (! -l "$tmp/usr/share/doc/$package" || -d "$tmp/usr/share/doc/$package")) {
+ # Support debian/package.copyright, but if not present, fall
+ # back on debian/copyright for all packages, not just the
+ # main binary package.
+ my $copyright = pkgfile($package, 'copyright', 1);
+ if ($copyright && ! excludefile($copyright)) {
+ ensure_docdir($package);
+ install_file($copyright,
+ "$tmp/usr/share/doc/$package/copyright");
+ }
+ }
+
+ next if $nodocs;
+
+ # Handle doc-base files. There are two filename formats, the usual
+ # plus an extended format (debian/package.*).
+ my %doc_ids;
+
+ opendir(DEB,"debian/") || error("can't read debian directory: $!");
+ # If this is the main package, we need to handle unprefixed filenames.
+ # For all packages, we must support both the usual filename format plus
+ # that format with a period an something appended.
+ my $regexp="\Q$package\E\.";
+ if ($package eq $dh{MAINPACKAGE}) {
+ $regexp="(|$regexp)";
+ }
+ foreach my $fn (grep {/^${regexp}doc-base(\..*)?$/} readdir(DEB)) {
+ # .EX are example files, generated by eg, dh-make
+ next if $fn=~/\.EX$/;
+ next if excludefile($fn);
+ # Parse the file to get the doc id.
+ open(my $fd, '<', "debian/$fn") || die "Cannot read debian/$fn.";
+ while (<$fd>) {
+ s/\s*$//;
+ if (/^Document\s*:\s*(.*)/) {
+ $doc_ids{$fn}=$1;
+ last;
+ }
+ }
+ if (! exists $doc_ids{$fn}) {
+ warning("Could not parse $fn for doc-base Document id; skipping");
+ }
+ close($fd);
+ }
+ closedir(DEB);
+
+ if (%doc_ids) {
+ install_dir("$tmp/usr/share/doc-base/");
+ }
+ foreach my $fn (keys %doc_ids) {
+ # To avoid issues with duplicated document IDs, we will always
+ # install to usr/share/doc-base/<packagename>.<doc_id> instead
+ # of just usr/share/doc-base/<packagename> or just
+ # usr/share/doc-base/<doc_id>. See #525821 and #980903.
+ install_file("debian/$fn",
+ "$tmp/usr/share/doc-base/$package.$doc_ids{$fn}");
+ }
+}
+
+=head1 SEE ALSO
+
+L<debhelper(7)>
+
+This program is a part of debhelper.
+
+=head1 AUTHOR
+
+Joey Hess <joeyh@debian.org>
+
+=cut
diff --git a/dh_installemacsen b/dh_installemacsen
new file mode 100755
index 0000000..bae900a
--- /dev/null
+++ b/dh_installemacsen
@@ -0,0 +1,149 @@
+#!/usr/bin/perl
+
+=head1 NAME
+
+dh_installemacsen - register an Emacs add on package
+
+=cut
+
+use strict;
+use warnings;
+use Debian::Debhelper::Dh_Lib;
+
+our $VERSION = DH_BUILTIN_VERSION;
+
+=head1 SYNOPSIS
+
+B<dh_installemacsen> [S<I<debhelper options>>] [B<-n>] [B<--priority=>I<n>] [B<--flavor=>I<foo>]
+
+=head1 DESCRIPTION
+
+B<dh_installemacsen> is a debhelper program that is responsible for installing
+files used by the Debian B<emacsen-common> package into package build
+directories.
+
+It also automatically generates the F<preinst> F<postinst> and F<prerm>
+commands needed to register a package as an Emacs add on package. The commands
+are added to the maintainer scripts by B<dh_installdeb>. See
+L<dh_installdeb(1)> for an explanation of how this works.
+
+=head1 FILES
+
+=over 4
+
+=item debian/I<package>.emacsen-compat
+
+Installed into F<usr/lib/emacsen-common/packages/compat/package> in the
+package build directory.
+
+=item debian/I<package>.emacsen-install
+
+Installed into F<usr/lib/emacsen-common/packages/install/package> in the
+package build directory.
+
+=item debian/I<package>.emacsen-remove
+
+Installed into F<usr/lib/emacsen-common/packages/remove/package> in the
+package build directory.
+
+=item debian/I<package>.emacsen-startup
+
+Installed into etc/emacs/site-start.d/50I<package>.el in the package
+build directory. Use B<--priority> to use a different priority than 50.
+
+=back
+
+=head1 OPTIONS
+
+=over 4
+
+=item B<-n>, B<--no-scripts>
+
+Do not modify F<postinst>/F<prerm> scripts.
+
+=item B<--priority=>I<n>
+
+Sets the priority number of a F<site-start.d> file. Default is 50.
+
+=item B<--flavor=>I<foo>
+
+Sets the flavor a F<site-start.d> file will be installed in. Default is
+B<emacs>, alternatives include B<xemacs> and B<emacs20>.
+
+=back
+
+=head1 NOTES
+
+Note that this command is not idempotent. L<dh_prep(1)> should be called
+between invocations of this command. Otherwise, it may cause multiple
+instances of the same text to be added to maintainer scripts.
+
+=cut
+
+init(options => {
+ "flavor=s" => \$dh{FLAVOR},
+ "priority=s" => \$dh{PRIORITY},
+});
+
+if (! defined $dh{PRIORITY}) {
+ $dh{PRIORITY}=50;
+}
+if (! defined $dh{FLAVOR}) {
+ $dh{FLAVOR}='emacs';
+}
+
+# PROMISE: DH NOOP WITHOUT emacsen-common emacsen-install emacsen-remove emacsen-startup cli-options()
+
+foreach my $package (@{$dh{DOPACKAGES}}) {
+ my $tmp=tmpdir($package);
+
+ my $emacsen_compat=pkgfile($package,"emacsen-compat");
+ my $emacsen_install=pkgfile($package,"emacsen-install");
+ my $emacsen_remove=pkgfile($package,"emacsen-remove");
+ my $emacsen_startup=pkgfile($package,"emacsen-startup");
+
+ if ($emacsen_compat ne '') {
+ install_dir("$tmp/usr/lib/emacsen-common/packages/compat");
+ install_file($emacsen_compat,
+ "$tmp/usr/lib/emacsen-common/packages/compat/$package");
+ }
+
+ if ($emacsen_install ne '') {
+ install_dir("$tmp/usr/lib/emacsen-common/packages/install");
+ install_prog($emacsen_install,"$tmp/usr/lib/emacsen-common/packages/install/$package");
+ }
+
+ if ($emacsen_remove ne '') {
+ install_dir("$tmp/usr/lib/emacsen-common/packages/remove");
+ install_prog("$emacsen_remove","$tmp/usr/lib/emacsen-common/packages/remove/$package");
+ }
+
+ if ($emacsen_startup ne '') {
+ install_dir("$tmp/etc/$dh{FLAVOR}/site-start.d/");
+ install_file($emacsen_startup,"$tmp/etc/$dh{FLAVOR}/site-start.d/$dh{PRIORITY}$package.el");
+ }
+
+ if ($emacsen_install ne '' || $emacsen_remove ne '') {
+ if (! $dh{NOSCRIPTS}) {
+ autoscript($package,"preinst","preinst-emacsen",
+ { 'PACKAGE' => $package });
+ autoscript($package,"postinst","postinst-emacsen",
+ { 'PACKAGE' => $package });
+ autoscript($package,"prerm","prerm-emacsen",
+ { 'PACKAGE' => $package });
+ }
+ }
+}
+
+=head1 SEE ALSO
+
+L<debhelper(7)>
+L</usr/share/doc/emacsen-common/debian-emacs-policy.gz>
+
+This program is a part of debhelper.
+
+=head1 AUTHOR
+
+Joey Hess <joeyh@debian.org>
+
+=cut
diff --git a/dh_installexamples b/dh_installexamples
new file mode 100755
index 0000000..ce970ac
--- /dev/null
+++ b/dh_installexamples
@@ -0,0 +1,192 @@
+#!/usr/bin/perl
+
+=encoding UTF-8
+
+=head1 NAME
+
+dh_installexamples - install example files into package build directories
+
+=cut
+
+use strict;
+use warnings;
+use Debian::Debhelper::Dh_Lib;
+
+our $VERSION = DH_BUILTIN_VERSION;
+
+=head1 SYNOPSIS
+
+B<dh_installexamples> [S<I<debhelper options>>] [B<-A>] [B<-X>I<item>] [S<I<file> ...>]
+
+=head1 DESCRIPTION
+
+B<dh_installexamples> is a debhelper program that is responsible for
+installing examples into F<usr/share/doc/package/examples> in package
+build directories.
+
+From debhelper compatibility level 11 on, B<dh_install> will fall back to
+looking in F<debian/tmp> for files, if it does not find them in the current
+directory (or wherever you've told it to look using B<--sourcedir>).
+
+=head1 FILES
+
+=over 4
+
+=item debian/I<package>.examples
+
+Lists example files or directories to be installed.
+
+If upstream provides an F<examples> directory, you will often want to use B<examples/*> rather
+than B<examples> in this file. The latter would create
+F<< /usr/share/doc/I<package>/examples/examples >>, which is rarely what you want.
+
+Supports substitution variables in compat 13 and later as
+documented in L<debhelper(7)>.
+
+=back
+
+=head1 OPTIONS
+
+=over 4
+
+=item B<-A>, B<--all>
+
+Install any files specified by command line parameters in ALL packages
+acted on.
+
+=item B<--sourcedir=>I<dir>
+
+Look in the specified directory for files to be installed. This option
+requires compat 11 or later (it is silently ignored in compat 10 or earlier).
+
+Note that this is not the same as the B<--sourcedirectory> option used
+by the B<dh_auto_>I<*> commands. You rarely need to use this option, since
+B<dh_installexamples> automatically looks for files in F<debian/tmp> in debhelper
+compatibility level 11 and above.
+
+=item B<--doc-main-package=>I<main-package>
+
+Set the main package for a documentation package. This is used to
+install the documentation of the documentation package in F<<
+/usr/share/doc/I<main-package> >> as recommended by the Debian policy
+manual 3.9.7 in §12.3.
+
+In compat 11 (or later), this option is only useful if debhelper's
+auto-detection of the main package is wrong. The option can also be
+used to silence a warning from debhelper when the auto-detection fails
+but the default happens to be correct.
+
+This option cannot be used when B<dh_installexamples> is instructed to act
+on multiple packages. If you need this option, you will generally
+need to combine it with B<-p> to ensure exactly one package is acted
+on.
+
+=item B<-X>I<item>, B<--exclude=>I<item>
+
+Exclude files that contain I<item> anywhere in their filename from
+being installed.
+
+=item I<file> ...
+
+Install these files (or directories) as examples into the first package
+acted on. (Or into all packages if B<-A> is specified.)
+
+=back
+
+=head1 NOTES
+
+Note that B<dh_installexamples> will happily copy entire directory hierarchies
+if you ask it to (similar to B<cp -a>). If it is asked to install a
+directory, it will install the complete contents of the directory.
+
+=cut
+
+init(options => {
+ "sourcedir=s" => \$dh{SOURCEDIR},
+ 'doc-main-package=s' => \$dh{DOC_MAIN_PACKAGE},
+});
+
+# PROMISE: DH NOOP WITHOUT pkgfile-logged(examples) cli-options()
+
+my $pwd;
+my $default_error_handler = compat(10) ? \&glob_expand_error_handler_reject_nomagic_warn_discard : \&glob_expand_error_handler_reject;
+my $nodocs = is_build_profile_active('nodoc') || get_buildoption('nodoc') ? 1 : 0;
+# We cannot assume documentation is built under nodoc, but if it is we must flag it as handled
+# or dh_missing might make noise.
+$default_error_handler = \&glob_expand_error_handler_silently_ignore if $nodocs;
+
+if (@{$dh{DOPACKAGES}} > 1 and $dh{DOC_MAIN_PACKAGE}) {
+ error('--doc-main-package should be used with -p<doc-pkg>');
+}
+if ($dh{DOC_MAIN_PACKAGE}) {
+ assert_opt_is_known_package($dh{DOC_MAIN_PACKAGE}, '--doc-main-package');
+}
+
+foreach my $package (getpackages()) {
+ next if is_udeb($package);
+
+ my $tmp=tmpdir($package);
+ my $file=pkgfile($package,"examples");
+ my @search_dirs = ('.');
+ my $skip_install = process_pkg($package) ? 0 : 1;
+ my $error_handler = $skip_install ? \&glob_expand_error_handler_silently_ignore : $default_error_handler;
+ @search_dirs = ($dh{SOURCEDIR} // '.', default_sourcedir($package)) if not compat(10);
+
+ my @examples;
+
+ if ($file) {
+ @examples = filearray($file, \@search_dirs, $error_handler) if $file;
+ }
+
+ if (($package eq $dh{FIRSTPACKAGE} || $dh{PARAMS_ALL}) && @ARGV) {
+ push @examples, @ARGV;
+ }
+
+ log_installed_files($package, @examples);
+
+ next if $skip_install or $nodocs;
+
+ if (@examples) {
+ my $target_package = compute_doc_main_package($package);
+ if (not defined($target_package)) {
+ warning("Cannot auto-detect main package for ${package}. If the default is wrong, please use --doc-main-package");
+ $target_package = $package;
+ }
+ my $target_dir = "${tmp}/usr/share/doc/${target_package}/examples";
+ install_dir($target_dir);
+
+ my $exclude = '';
+ if ($dh{EXCLUDE_FIND}) {
+ $exclude .= ' -and ! \( '.$dh{EXCLUDE_FIND}.' \)';
+ }
+
+ foreach my $example (@examples) {
+ next if excludefile($example);
+ if (-d $example && $exclude) {
+ my $basename = basename($example);
+ my $dir = ($basename eq '.') ? $example : "$example/..";
+ chomp($pwd=`pwd`) if not defined($pwd);
+ # Gracefully handling tmpdir being absolute (-P/...)
+ my $destdir = $target_dir =~ m{^/} ? $target_dir : "${pwd}/${target_dir}";
+ complex_doit("cd '$dir' && " .
+ "find '$basename' -type f$exclude -print0 | LC_ALL=C sort -z | " .
+ "xargs -0 -I {} cp --reflink=auto --parents -dp {} ${destdir}");
+ }
+ else {
+ doit("cp", '--reflink=auto', "-a", $example, $target_dir);
+ }
+ }
+ }
+}
+
+=head1 SEE ALSO
+
+L<debhelper(7)>
+
+This program is a part of debhelper.
+
+=head1 AUTHOR
+
+Joey Hess <joeyh@debian.org>
+
+=cut
diff --git a/dh_installgsettings b/dh_installgsettings
new file mode 100755
index 0000000..45b228e
--- /dev/null
+++ b/dh_installgsettings
@@ -0,0 +1,106 @@
+#!/usr/bin/perl
+
+=head1 NAME
+
+dh_installgsettings - install GSettings overrides and set dependencies
+
+=cut
+
+use strict;
+use warnings;
+use Debian::Debhelper::Dh_Lib;
+
+our $VERSION = DH_BUILTIN_VERSION;
+
+=head1 SYNOPSIS
+
+B<dh_installgsettings> [S<I<debhelper options>>] [B<--priority=<number>>]
+
+=head1 DESCRIPTION
+
+B<dh_installgsettings> is a debhelper program that is responsible for installing
+GSettings override files and generating appropriate dependencies on the
+GSettings backend.
+
+The dependency on the backend will be generated in B<${misc:Depends}>.
+
+=head1 FILES
+
+=over 4
+
+=item debian/I<package>.gsettings-override
+
+Installed into usr/share/glib-2.0/schemas/10_I<package>.gschema.override in
+the package build directory, with "I<package>" replaced by the package name.
+
+The format of the file is the following:
+
+ [org.gnome.mypackage]
+ boolean-setting=true
+ string-setting='string'
+ ...
+
+=back
+
+=head1 OPTIONS
+
+=over 4
+
+=item B<--priority> I<priority>
+
+Use I<priority> (which should be a 2-digit number) as the override
+priority instead of 10. Higher values than ten can be used by
+derived distributions (20), blend distributions (50), or site-specific
+packages (90).
+
+=cut
+
+init(options => {
+ "priority=s" => \$dh{PRIORITY},
+});
+
+my $priority=10;
+if (defined $dh{PRIORITY}) {
+ $priority=$dh{PRIORITY};
+}
+
+# PROMISE: DH NOOP WITHOUT gsettings-override tmp(usr/share/glib-2.0/schemas) cli-options()
+
+foreach my $package (@{$dh{DOPACKAGES}}) {
+ my $tmp=tmpdir($package);
+
+ my $gsettings_schemas_dir = "$tmp/usr/share/glib-2.0/schemas/";
+
+ my $override = pkgfile($package,"gsettings-override");
+ if ($override ne '') {
+ install_dir($gsettings_schemas_dir);
+ install_file($override,
+ "$gsettings_schemas_dir/${priority}_$package.gschema.override");
+ }
+
+ if (-d "$gsettings_schemas_dir") {
+ # Get a list of the schemas
+ my $schemas = qx_cmd('find', $gsettings_schemas_dir, '-type', 'f',
+ '(', '-name', '*.xml', '-o', '-name', '*.override',
+ ')', '-printf', '%P');
+ if ($schemas ne '') {
+ addsubstvar($package, "misc:Depends", "dconf-gsettings-backend | gsettings-backend");
+ }
+ }
+}
+
+=back
+
+=head1 SEE ALSO
+
+L<debhelper(7)>
+
+This program is a part of debhelper.
+
+=head1 AUTHOR
+
+Laurent Bigonville <bigon@debian.org>,
+Josselin Mouette <joss@debian.org>
+
+=cut
+
diff --git a/dh_installifupdown b/dh_installifupdown
new file mode 100755
index 0000000..3d4cd93
--- /dev/null
+++ b/dh_installifupdown
@@ -0,0 +1,82 @@
+#!/usr/bin/perl
+
+=head1 NAME
+
+dh_installifupdown - install if-up and if-down hooks
+
+=cut
+
+use strict;
+use warnings;
+use Debian::Debhelper::Dh_Lib;
+
+our $VERSION = DH_BUILTIN_VERSION;
+
+=head1 SYNOPSIS
+
+B<dh_installifupdown> [S<I<debhelper options>>] [B<--name=>I<name>]
+
+=head1 DESCRIPTION
+
+B<dh_installifupdown> is a debhelper program that is responsible for installing
+F<if-up>, F<if-down>, F<if-pre-up>, and F<if-post-down> hook scripts into package build
+directories.
+
+=head1 FILES
+
+=over 4
+
+=item debian/I<package>.if-up
+
+=item debian/I<package>.if-down
+
+=item debian/I<package>.if-pre-up
+
+=item debian/I<package>.if-post-down
+
+These files are installed into etc/network/if-*.d/I<package> in
+the package build directory.
+
+=back
+
+=head1 OPTIONS
+
+=over 4
+
+=item B<--name=>I<name>
+
+Look for files named F<debian/package.name.if-*> and install them as
+F<etc/network/if-*/name>, instead of using the usual files and installing them
+as the package name.
+
+=back
+
+=cut
+
+init();
+
+# PROMISE: DH NOOP WITHOUT if-pre-up if-up if-down if-post-down cli-options()
+
+foreach my $package (@{$dh{DOPACKAGES}}) {
+ my $tmp=tmpdir($package);
+
+ foreach my $script (qw(pre-up up down post-down)) {
+ my $file=pkgfile($package, "if-$script");
+ if ($file ne '') {
+ install_dir("$tmp/etc/network/if-$script.d");
+ install_prog($file,"$tmp/etc/network/if-$script.d/".pkgfilename($package));
+ }
+ }
+}
+
+=head1 SEE ALSO
+
+L<debhelper(7)>
+
+This program is a part of debhelper.
+
+=head1 AUTHOR
+
+Joey Hess <joeyh@debian.org>
+
+=cut
diff --git a/dh_installinfo b/dh_installinfo
new file mode 100755
index 0000000..3a25542
--- /dev/null
+++ b/dh_installinfo
@@ -0,0 +1,133 @@
+#!/usr/bin/perl
+
+=head1 NAME
+
+dh_installinfo - install info files
+
+=cut
+
+use strict;
+use warnings;
+use Debian::Debhelper::Dh_Lib;
+
+our $VERSION = DH_BUILTIN_VERSION;
+
+=head1 SYNOPSIS
+
+B<dh_installinfo> [S<I<debhelper options>>] [B<-A>] [S<I<file> ...>]
+
+=head1 DESCRIPTION
+
+B<dh_installinfo> is a debhelper program that is responsible for installing
+info files into F<usr/share/info> in the package build directory.
+
+From debhelper compatibility level 11 on, B<dh_install> will fall back to
+looking in F<debian/tmp> for files, if it does not find them in the current
+directory (or wherever you've told it to look using B<--sourcedir>).
+
+=head1 FILES
+
+=over 4
+
+=item debian/I<package>.info
+
+List info files to be installed.
+
+Supports substitution variables in compat 13 and later as
+documented in L<debhelper(7)>.
+
+=back
+
+=head1 OPTIONS
+
+=over 4
+
+=item B<-A>, B<--all>
+
+Install all files specified by command line parameters in ALL packages
+acted on.
+
+=item B<--sourcedir=>I<dir>
+
+Look in the specified directory for files to be installed. This option
+requires compat 11 or later (it is silently ignored in compat 10 or earlier).
+
+Note that this is not the same as the B<--sourcedirectory> option used
+by the B<dh_auto_>I<*> commands. You rarely need to use this option, since
+B<dh_installinfo> automatically looks for files in F<debian/tmp> in debhelper
+compatibility level 11 and above.
+
+=item I<file> ...
+
+Install these info files into the first package acted on. (Or in
+all packages if B<-A> is specified).
+
+=back
+
+=cut
+
+init(options => {
+ "sourcedir=s" => \$dh{SOURCEDIR},
+});
+
+# PROMISE: DH NOOP WITHOUT pkgfile-logged(info) cli-options() tmp(usr/share/info/dir)
+
+my $default_error_handler = compat(10) ? \&glob_expand_error_handler_reject_nomagic_warn_discard : \&glob_expand_error_handler_reject;
+my $nodocs = is_build_profile_active('nodoc') || get_buildoption('nodoc') ? 1 : 0;
+# We cannot assume documentation is built under nodoc, but if it is we must flag it as handled
+# or dh_missing might make noise.
+$default_error_handler = \&glob_expand_error_handler_silently_ignore if $nodocs;
+
+foreach my $package (getpackages()) {
+ my $tmp=tmpdir($package);
+ my $file=pkgfile($package,"info");
+ my @search_dirs = ('.');
+ my @ignored_files;
+ my $skip_install = process_pkg($package) ? 0 : 1;
+ my $error_handler = $skip_install ? \&glob_expand_error_handler_silently_ignore : $default_error_handler;
+ @search_dirs = ($dh{SOURCEDIR} // '.', default_sourcedir($package)) if not compat(10);
+
+ my @info;
+
+ if ($file) {
+ @info = filearray($file, \@search_dirs, $error_handler) if $file;
+ }
+
+ if (($package eq $dh{FIRSTPACKAGE} || $dh{PARAMS_ALL}) && @ARGV) {
+ push @info, @ARGV;
+ }
+
+ # Pretend that we handled usr/share/info/dir. We do not want to install it as it causes
+ # file conflicts between packages and we also do not want dh_missing to drop a bomb on
+ # the user for it.
+ #
+ # See #971036
+ for my $path (@search_dirs) {
+ if ( -e "${path}/usr/share/info/dir" ) {
+ push(@ignored_files, 'usr/share/info/dir');
+ last;
+ }
+ }
+
+ log_installed_files($package, @info, @ignored_files);
+
+ next if $skip_install or $nodocs;
+
+ if (@info) {
+ install_dir("$tmp/usr/share/info");
+ xargs(\@info, "cp", '--reflink=auto', XARGS_INSERT_PARAMS_HERE, "$tmp/usr/share/info");
+ doit("chmod","-R", "u+rw,go=rX","$tmp/usr/share/info/");
+ }
+}
+
+=head1 SEE ALSO
+
+L<debhelper(7)>
+
+This program is a part of debhelper.
+
+=head1 AUTHOR
+
+Joey Hess <joeyh@debian.org>
+
+=cut
diff --git a/dh_installinit b/dh_installinit
new file mode 100755
index 0000000..2839902
--- /dev/null
+++ b/dh_installinit
@@ -0,0 +1,427 @@
+#!/usr/bin/perl
+
+=head1 NAME
+
+dh_installinit - install service init files into package build directories
+
+=cut
+
+use strict;
+use warnings;
+use Debian::Debhelper::Dh_Lib;
+use File::Find;
+
+our $VERSION = DH_BUILTIN_VERSION;
+
+=head1 SYNOPSIS
+
+B<dh_installinit> [S<I<debhelper options>>] [B<--name=>I<name>] [B<-n>] [B<-R>] [B<-r>] [B<-d>] [S<B<--> I<params>>]
+
+=head1 DESCRIPTION
+
+B<dh_installinit> is a debhelper program that is responsible for
+installing init scripts with associated defaults files. In
+compatibility levels up to and including 10, B<dh_installinit> will
+also install some systemd related files provided by the debian
+packaging (see the L</FILES> section below). In compatibility levels
+up to and including 11, B<dh_installinit> will also handle upstart
+jobs provided in the debian packaging (see the L</FILES> for more
+information on this as well).
+
+It also automatically generates the F<postinst> and F<postrm> and F<prerm>
+commands needed to set up the symlinks in F</etc/rc*.d/> to start and stop
+the init scripts.
+
+In compat 10 or earlier: If a package only ships a systemd service
+file and no sysvinit script is provided, you may want to exclude the
+call to dh_installinit for that package (e.g. via B<-N>). Otherwise,
+you may get warnings from lintian about init.d scripts not being
+included in the package.
+
+=head1 FILES
+
+=over 4
+
+=item debian/I<package>.init
+
+If this exists, it is installed into etc/init.d/I<package> in the package
+build directory.
+
+=item debian/I<package>.default
+
+If this exists, it is installed into etc/default/I<package> in the package
+build directory.
+
+=item debian/I<package>.upstart
+
+In compatibility level 11, this file will trigger an error with a reminder
+about ensuring the proper removal of the upstart file in the previous package
+version. Please consider using the "rm_conffile" feature from
+L<dh_installdeb(1)> to ensure the proper removal of previous upstart files.
+
+In compatibility level 10, if this file exists, it is installed into
+etc/init/I<package>.conf in the package build directory.
+
+=item debian/I<package>.service
+
+If this exists, it is installed into F<< usr/lib/systemd/system/I<package>.service >> in
+the package build directory. Only used in compat levels 10 and below.
+
+=item debian/I<package>.tmpfile
+
+If this exists, it is installed into usr/lib/tmpfiles.d/I<package>.conf in the
+package build directory. Only used in compat levels 10 and below.
+
+=back
+
+=head1 OPTIONS
+
+=over 4
+
+=item B<-n>, B<--no-scripts>
+
+Do not modify F<postinst>/F<postrm>/F<prerm> scripts.
+
+=item B<-o>, B<--only-scripts>
+
+Only modify F<postinst>/F<postrm>/F<prerm> scripts, do not actually install
+any init script, default files, upstart job or systemd service file. May be
+useful if the file is shipped and/or installed by upstream in a way that
+doesn't make it easy to let B<dh_installinit> find it.
+
+B<Caveat>: This will bypass all the regular checks and
+I<unconditionally> modify the scripts. You will almost certainly want
+to use this with B<-p> to limit, which packages are affected by the
+call. Example:
+
+ override_dh_installinit:
+ dh_installinit -pfoo --only-scripts
+ dh_installinit --remaining
+
+=item B<-R>, B<--restart-after-upgrade>
+
+Do not stop the init script until after the package upgrade has been
+completed. This is the default behaviour in compat 10.
+
+In early compat levels, the default was to stop the script in the
+F<prerm>, and starts it again in the F<postinst>.
+
+This can be useful for daemons that should not have a possibly long
+downtime during upgrade. But you should make sure that the daemon will not
+get confused by the package being upgraded while it's running before using
+this option.
+
+=item B<--no-restart-after-upgrade>
+
+Undo a previous B<--restart-after-upgrade> (or the default of compat
+10). If no other options are given, this will cause the service to be
+stopped in the F<prerm> script and started again in the F<postinst>
+script.
+
+=item B<-r>, B<--no-stop-on-upgrade>, B<--no-restart-on-upgrade>
+
+Do not stop init script on upgrade. This has the side-effect of not
+restarting the service as a part of the upgrade.
+
+If you want to restart the service with minimal downtime, please use
+B<--restart-after-upgrade> (default in compat 10 or later). If you want
+the service to be restarted but be stopped during the upgrade, then please
+use B<--no-restart-after-upgrade> (note the "after-upgrade").
+
+Note that the B<--no-restart-on-upgrade> alias is deprecated and will
+be removed in compat 12. This is to avoid confusion with the
+B<--no-restart-after-upgrade> option. The B<--no-stop-on-upgrade>
+variant was introduced in debhelper 10.2 (included in Debian stretch).
+
+=item B<--no-start>
+
+Do not start the init script on install or upgrade, or stop it on removal.
+Only call B<update-rc.d>. Useful for rcS scripts.
+
+=item B<--no-enable>
+
+Disable the init script on purge, but do not enable them on install.
+This implies a versioned dependency on B<< init-system-helpers (E<gt>=
+1.51) >> as it is the first (functional) version that supports
+B<< update-rc.d E<lt>scriptE<gt> defaults-disabled >>.
+
+B<Note> that this option does not affect whether the services are
+started. Please remember to also use B<--no-start> if the service
+should not be started.
+
+Cannot be combined with B<-u>I<params>,
+B<--update-rcd-params=>I<params>, or B<--> I<params>.
+
+=item B<-d>, B<--remove-d>
+
+Remove trailing B<d> from the name of the package, and use the result for the
+filename the upstart job file is installed as in F<etc/init/> , and for the
+filename the init script is installed as in etc/init.d and the default file
+is installed as in F<etc/default/>. This may be useful for daemons with names
+ending in B<d>. (Note: this takes precedence over the B<--init-script> parameter
+described below.)
+
+=item B<-u>I<params> B<--update-rcd-params=>I<params>
+
+=item B<--> I<params>
+
+Pass I<params> to L<update-rc.d(8)>. If not specified, B<defaults> (or
+B<defaults-disabled> with B<--no-enable>) will be passed to
+L<update-rc.d(8)>.
+
+Cannot be combined with B<--no-enable>.
+
+=item B<--name=>I<name>
+
+Install the init script (and default file) as well as upstart job file
+using the filename I<name> instead of the default filename, which is
+the package name. When this parameter is used, B<dh_installinit> looks
+for and installs files named F<debian/package.name.init>,
+F<debian/package.name.default> and F<debian/package.name.upstart>
+instead of the usual F<debian/package.init>, F<debian/package.default> and
+F<debian/package.upstart>.
+
+=item B<--init-script=>I<scriptname>
+
+Use I<scriptname> as the filename the init script is installed as in
+F<etc/init.d/> (and also use it as the filename for the defaults file, if it
+is installed). If you use this parameter, B<dh_installinit> will look to see
+if a file in the F<debian/> directory exists that looks like
+F<package.scriptname> and if so will install it as the init script in
+preference to the files it normally installs.
+
+This parameter is deprecated, use the B<--name> parameter instead. This
+parameter is incompatible with the use of upstart jobs.
+
+=item B<--error-handler=>I<function>
+
+Call the named shell I<function> if running the init script fails. The
+function should be provided in the F<prerm> and F<postinst> scripts, before the
+B<#DEBHELPER#> token.
+
+=back
+
+=head1 NOTES
+
+Note that this command is not idempotent. L<dh_prep(1)> should be called
+between invocations of this command. Otherwise, it may cause multiple
+instances of the same text to be added to maintainer scripts.
+
+=cut
+
+$dh{RESTART_AFTER_UPGRADE} = '';
+$dh{NO_START} = '';
+
+init(options => {
+ "r" => \$dh{R_FLAG},
+ 'no-stop-on-upgrade' => \$dh{R_FLAG},
+ "no-restart-on-upgrade" => sub {
+ $dh{R_FLAG} = 1;
+ deprecated_functionality("--no-restart-on-upgrade has been renamed to --no-stop-on-upgrade",
+ 12);
+ },
+ "no-start" => \$dh{NO_START},
+ "R|restart-after-upgrade!" => \$dh{RESTART_AFTER_UPGRADE},
+ "init-script=s" => \$dh{INIT_SCRIPT},
+ "update-rcd-params=s", => \$dh{U_PARAMS},
+ "remove-d" => \$dh{D_FLAG},
+ "no-enable" => \$dh{NO_ENABLE},
+});
+
+if ($dh{RESTART_AFTER_UPGRADE} eq '') {
+ $dh{RESTART_AFTER_UPGRADE} = 1 if not defined($dh{R_FLAG}) and $dh{NO_START} eq '';
+}
+
+# PROMISE: DH NOOP WITHOUT service tmpfile default upstart init init.d tmp(usr/lib/tmpfiles.d) tmp(etc/tmpfiles.d) cli-options(--init-script|-d|--remove-d|-o|--only-scripts)
+
+my %snippet_options = ('snippet-order' => 'service');
+
+foreach my $package (@{$dh{DOPACKAGES}}) {
+ my $tmp=tmpdir($package);
+
+ # Figure out what filename to install it as.
+ my $script;
+ my $scriptsrc;
+ my $jobfile=$package;
+ if (defined $dh{NAME}) {
+ $jobfile=$script=$scriptsrc=$dh{NAME};
+ }
+ elsif ($dh{D_FLAG}) {
+ # -d on the command line sets D_FLAG. We will
+ # remove a trailing 'd' from the package name and
+ # use that as the name.
+ $script=$package;
+ if ($script=~m/(.*)d$/) {
+ $jobfile=$script=$1;
+ }
+ else {
+ warning("\"$package\" has no final d' in its name, but -d was specified.");
+ }
+ $scriptsrc=$script;
+ }
+ elsif ($dh{INIT_SCRIPT}) {
+ $script=$dh{INIT_SCRIPT};
+ $scriptsrc=$script;
+ }
+ else {
+ $script=$package;
+ if (compat(9)) {
+ $scriptsrc=$script;
+ }
+ else {
+ $scriptsrc="init";
+ }
+ }
+
+ my $service='';
+ $service=pkgfile($package,"service") if compat(10);
+ if ($service ne '' && ! $dh{ONLYSCRIPTS}) {
+ my $path="$tmp/usr/lib/systemd/system";
+ install_dir($path);
+ install_file($service, "$path/$script.service");
+ }
+
+ my $tmpfile='';
+ $tmpfile=pkgfile($package,"tmpfile") if compat(10);
+ if ($tmpfile ne '' && ! $dh{ONLYSCRIPTS}) {
+ my $path="$tmp/usr/lib/tmpfiles.d";
+ install_dir($path);
+ install_file($tmpfile, "$path/$script.conf");
+ }
+
+ my $job=pkgfile($package,"upstart");
+ if ($job ne '' and not compat(11)) {
+ isnative($package); # For the side-effect of setting $dh{VERSION}
+ warning("Detected an upstart file; these are no longer supported by dh_installinit in compat 11");
+ warning("Please ensure a proper removal by adding a \"rm_conffile\" line in debian/<pkg>.maintscript");
+ warning("Example maintscript line:");
+ warning(" rm_conffile /etc/init/${jobfile}.conf $dh{VERSION}");
+ warning("(Note: the example is a best-effort guess and it is not always correct! Please verify before use)");
+ warning("see \"man dh_installdeb\" for more information about the maintscript file");
+ warning("");
+ error("upstart jobs are no longer supported! Please remove $job and check if you need to add a conffile removal");
+ }
+ if ($job ne '' && ! $dh{ONLYSCRIPTS}) {
+ install_dir("$tmp/etc/init");
+ install_file($job, "$tmp/etc/init/$jobfile.conf");
+ }
+
+ my $default=pkgfile($package,'default');
+ if ($default ne '' && ! $dh{ONLYSCRIPTS}) {
+ install_dir("$tmp/etc/default");
+ install_file($default, "$tmp/etc/default/$script");
+ }
+
+ my $init=pkgfile($package,$scriptsrc) || pkgfile($package,"init") ||
+ pkgfile($package,"init.d");
+
+ if ($init ne '' && ! $dh{ONLYSCRIPTS}) {
+ install_dir("$tmp/etc/init.d");
+ install_prog($init,"$tmp/etc/init.d/$script");
+ }
+
+ if ($dh{INIT_SCRIPT} && $job ne '' && $init ne '') {
+ error("Can't use --init-script with an upstart job");
+ }
+
+ if (compat(10) && !$dh{NOSCRIPTS}) {
+ # Include postinst-init-tmpfiles if the package ships any files
+ # in /usr/lib/tmpfiles.d or /etc/tmpfiles.d
+ my @tmpfiles;
+ find({
+ wanted => sub {
+ my $name = $File::Find::name;
+ return unless -f $name;
+ $name =~ s/^\Q$tmp\E//g;
+ if ($name =~ m,^/usr/lib/tmpfiles\.d/, ||
+ $name =~ m,^/etc/tmpfiles\.d/,) {
+ push(@tmpfiles, basename($name));
+ }
+ },
+ no_chdir => 1,
+ }, $tmp);
+ if (@tmpfiles > 0) {
+ # Not migrated to hashref based autoscripts. This will
+ # happen as people migrate to dh_installsystemd.
+ autoscript($package,"postinst", "postinst-init-tmpfiles",
+ "s,#TMPFILES#," . join(" ", sort @tmpfiles).",g");
+ }
+ }
+
+ if ($service ne '' || $job ne '' || $init ne '' || $dh{ONLYSCRIPTS}) {
+ # This is set by the -u "foo" command line switch, it's
+ # the parameters to pass to update-rc.d. If not set,
+ # we have to say "defaults".
+ my $params = 'defaults';
+ my $update_rcd_params = compat(11) ? '' : '--skip-systemd-native ';
+ if ($dh{NO_ENABLE}) {
+ $params = 'defaults-disabled';
+ addsubstvar($package, "misc:Depends", "init-system-helpers (>= 1.51)");
+ }
+
+ if (defined($dh{U_PARAMS}) and @{$dh{U_PARAMS}}) {
+ error("--no-enable and -- params/-u/--update-rcd-params/ are mutually exclusive") if $dh{NO_ENABLE};
+ $params=join(' ',@{$dh{U_PARAMS}});
+ }
+
+ if (! $dh{NOSCRIPTS}) {
+ my $replace = {
+ 'SCRIPT' => $script,
+ 'INITPARMS' => $params,
+ 'ERROR_HANDLER' => $dh{ERROR_HANDLER},
+ 'INVOKE_RCD_PARAMS' => $update_rcd_params,
+ };
+ autoscript($package, 'preinst', 'preinst-init-chmod', $replace, \%snippet_options);
+
+ if (! $dh{NO_START}) {
+ if ($dh{RESTART_AFTER_UPGRADE}) {
+ # update-rc.d, and restart (or
+ # start if new install) script
+ autoscript($package, 'postinst', 'postinst-init-restart', $replace, \%snippet_options);
+ }
+ else {
+ # update-rc.d, and start script
+ autoscript($package, 'postinst', 'postinst-init', $replace, \%snippet_options);
+ }
+
+ autoscript($package, 'preinst', 'preinst-init-stop', $replace, \%snippet_options)
+ unless ($dh{R_FLAG} || $dh{RESTART_AFTER_UPGRADE});
+
+ # stops script only on remove
+ autoscript($package, 'prerm', 'prerm-init-norestart', $replace, \%snippet_options);
+
+ # The --skip-systemd-native option requires
+ # init-system-helpers (>= 1.54) and since we need it
+ # from prerm we need init-system-helpers to have been
+ # unpacked before the package is being unpacked.
+ addsubstvar($package, 'misc:Pre-Depends', 'init-system-helpers (>= 1.54~)')
+ if $update_rcd_params !~ m/^\s*$/;
+ }
+ else {
+ # just update-rc.d
+ autoscript($package,"postinst", "postinst-init-nostart", $replace, \%snippet_options);
+ }
+
+ # removes rc.d links
+ autoscript($package,"postrm","postrm-init",
+ { 'SCRIPT' => $script, 'ERROR_HANDLER' => $dh{ERROR_HANDLER} },
+ \%snippet_options);
+ }
+ }
+}
+
+=head1 SEE ALSO
+
+L<debhelper(7)>, L<dh_installsystemd(1)>
+
+This program is a part of debhelper.
+
+=head1 AUTHORS
+
+Joey Hess <joeyh@debian.org>
+
+Steve Langasek <steve.langasek@canonical.com>
+
+Michael Stapelberg <stapelberg@debian.org>
+
+=cut
diff --git a/dh_installinitramfs b/dh_installinitramfs
new file mode 100755
index 0000000..37fb27f
--- /dev/null
+++ b/dh_installinitramfs
@@ -0,0 +1,103 @@
+#!/usr/bin/perl
+
+=head1 NAME
+
+dh_installinitramfs - install initramfs hooks and setup maintscripts
+
+=cut
+
+use strict;
+use warnings;
+use Debian::Debhelper::Dh_Lib;
+
+our $VERSION = DH_BUILTIN_VERSION;
+
+=head1 SYNOPSIS
+
+B<dh_installinitramfs> [S<B<debhelper options>>] [B<-n>]
+
+=head1 DESCRIPTION
+
+B<dh_installinitramfs> is a debhelper program that is responsible for
+installing Debian package provided initramfs hooks.
+
+If B<dh_installinitramfs> installs or detects one or more initramfs
+hooks in the package, then it also automatically generates the noawait
+trigger B<update-initframfs> command needed to interface with the
+Debian initramfs system. This trigger is inserted into the
+packaging by L<dh_installdeb(1)>.
+
+=head1 FILES
+
+=over 4
+
+=item debian/I<package>.initramfs-hook
+
+Assumed to be an initramfs hook that will be installed into F<<
+usr/share/initramfs-tools/hooks/I<package> >> in the package build
+directory. See B<HOOK SCRIPTS> in L<initramfs-tools(8)> for more
+information about initramfs hooks.
+
+=back
+
+=head1 OPTIONS
+
+=over 4
+
+=item B<-n>, B<--no-scripts>
+
+Do not add the B<update-initramfs> trigger even if it seems like the package
+might need it. The option is called B<--no-scripts> for historical
+reasons as B<dh_installinitramfs> would previously generate maintainer
+scripts that called B<update-initramfs -u>.
+
+Use this option, if you need to interface with the B<update-initramfs>
+system that is not satisfied by the noawait trigger (e.g. because you need
+the extra guarantees and head-aches of a await trigger).
+
+=back
+
+=head1 NOTES
+
+Note that this command is not idempotent. L<dh_prep(1)> should be called
+between invocations of this command. Otherwise, it may cause multiple
+instances of the same text to be added to triggers file.
+
+=cut
+
+init();
+
+# PROMISE: DH NOOP WITHOUT initramfs-hook tmp(usr/share/initramfs-tools/hooks) cli-options()
+
+foreach my $package (@{$dh{DOPACKAGES}}) {
+ my $tmp = tmpdir($package);
+ my $hook_script = pkgfile($package, 'initramfs-hook');
+ my $has_hooks;
+ my $hook_dir = "${tmp}/usr/share/initramfs-tools/hooks";
+
+ if ($hook_script ne '') {
+ install_dir($hook_dir);
+ install_prog($hook_script, "${hook_dir}/${package}");
+ $has_hooks = 1;
+ } elsif (-d $hook_dir and not is_empty_dir($hook_dir)) {
+ $has_hooks = 1;
+ }
+
+ if ($has_hooks && ! $dh{NOSCRIPTS}) {
+ autotrigger($package, 'activate-noawait', 'update-initramfs');
+ }
+}
+
+=head1 SEE ALSO
+
+L<debhelper(7)>
+L<update-initramfs(8)>
+L<initramfs-tools(8)>
+
+This program is a part of debhelper.
+
+=head1 AUTHOR
+
+Niels Thykier <niels@thykier.net>
+
+=cut
diff --git a/dh_installlogcheck b/dh_installlogcheck
new file mode 100755
index 0000000..bf1f779
--- /dev/null
+++ b/dh_installlogcheck
@@ -0,0 +1,91 @@
+#!/usr/bin/perl
+
+=head1 NAME
+
+dh_installlogcheck - install logcheck rulefiles into etc/logcheck/
+
+=cut
+
+use strict;
+use warnings;
+use Debian::Debhelper::Dh_Lib;
+
+our $VERSION = DH_BUILTIN_VERSION;
+
+=head1 SYNOPSIS
+
+B<dh_installlogcheck> [S<I<debhelper options>>]
+
+=head1 DESCRIPTION
+
+B<dh_installlogcheck> is a debhelper program that is responsible for
+installing logcheck rule files.
+
+=head1 FILES
+
+=over 4
+
+=item debian/I<package>.logcheck.cracking
+
+=item debian/I<package>.logcheck.violations
+
+=item debian/I<package>.logcheck.violations.ignore
+
+=item debian/I<package>.logcheck.ignore.workstation
+
+=item debian/I<package>.logcheck.ignore.server
+
+=item debian/I<package>.logcheck.ignore.paranoid
+
+Each of these files, if present, are installed into corresponding
+subdirectories of F<etc/logcheck/> in package build directories.
+
+=back
+
+=head1 OPTIONS
+
+=over 4
+
+=item B<--name=>I<name>
+
+Look for files named F<debian/package.name.logcheck.*> and install
+them into the corresponding subdirectories of F<etc/logcheck/>, but
+use the specified name instead of that of the package.
+
+=back
+
+=cut
+
+init();
+
+# PROMISE: DH NOOP WITHOUT logcheck.cracking logcheck.violations logcheck.violations.ignore logcheck.ignore.workstation logcheck.ignore.server logcheck.ignore.paranoid cli-options()
+
+foreach my $package (@{$dh{DOPACKAGES}}) {
+ my $tmp=tmpdir($package);
+
+ foreach my $type (qw{ignore.d.workstation ignore.d.server
+ ignore.d.paranoid cracking.d
+ violations.d violations.ignore.d}) {
+ my $typenod=$type;
+ $typenod=~s/\.d//;
+ my $logcheck=pkgfile($package,"logcheck.$typenod");
+ if ($logcheck) {
+ my $packagenodot=pkgfilename($package); # run-parts..
+ $packagenodot=~s/\./_/g;
+ install_dir("$tmp/etc/logcheck/$type");
+ install_file($logcheck, "$tmp/etc/logcheck/$type/$packagenodot");
+ }
+ }
+}
+
+=head1 SEE ALSO
+
+L<debhelper(7)>
+
+This program is a part of debhelper.
+
+=head1 AUTHOR
+
+Jon Middleton <jjm@debian.org>
+
+=cut
diff --git a/dh_installlogrotate b/dh_installlogrotate
new file mode 100755
index 0000000..fc3d60a
--- /dev/null
+++ b/dh_installlogrotate
@@ -0,0 +1,63 @@
+#!/usr/bin/perl
+
+=head1 NAME
+
+dh_installlogrotate - install logrotate config files
+
+=cut
+
+use strict;
+use warnings;
+use Debian::Debhelper::Dh_Lib;
+
+our $VERSION = DH_BUILTIN_VERSION;
+
+=head1 SYNOPSIS
+
+B<dh_installlogrotate> [S<I<debhelper options>>] [B<--name=>I<name>]
+
+=head1 DESCRIPTION
+
+B<dh_installlogrotate> is a debhelper program that is responsible for installing
+logrotate config files into F<etc/logrotate.d> in package build directories.
+Files named F<debian/package.logrotate> are installed.
+
+=head1 OPTIONS
+
+=over 4
+
+=item B<--name=>I<name>
+
+Look for files named F<debian/package.name.logrotate> and install them as
+F<etc/logrotate.d/name>, instead of using the usual files and installing them
+as the package name.
+
+=back
+
+=cut
+
+init();
+
+# PROMISE: DH NOOP WITHOUT logrotate cli-options()
+
+foreach my $package (@{$dh{DOPACKAGES}}) {
+ my $tmp=tmpdir($package);
+ my $file=pkgfile($package,"logrotate");
+
+ if ($file) {
+ install_dir("$tmp/etc/logrotate.d");
+ install_file($file,"$tmp/etc/logrotate.d/".pkgfilename($package));
+ }
+}
+
+=head1 SEE ALSO
+
+L<debhelper(7)>
+
+This program is a part of debhelper.
+
+=head1 AUTHOR
+
+Joey Hess <joeyh@debian.org>
+
+=cut
diff --git a/dh_installman b/dh_installman
new file mode 100755
index 0000000..02ed9ad
--- /dev/null
+++ b/dh_installman
@@ -0,0 +1,430 @@
+#!/usr/bin/perl
+
+=head1 NAME
+
+dh_installman - install man pages into package build directories
+
+=cut
+
+use strict;
+use warnings;
+use File::Find;
+use Debian::Debhelper::Dh_Lib;
+
+our $VERSION = DH_BUILTIN_VERSION;
+
+=head1 SYNOPSIS
+
+B<dh_installman> [S<I<debhelper options>>] [S<I<manpage> ...>]
+
+=head1 DESCRIPTION
+
+B<dh_installman> is a debhelper program that handles installing man
+pages into the correct locations in package build directories.
+
+In compat 10 and earlier, this program was primarily for when
+upstream's build system does not properly install them as a part of
+its install step (or it does not have an install step). In compat 11
+and later, it also supports the default searchdir plus --sourcedir
+like dh_install(1) and has the advantage that it respects the nodoc
+build profile (unlike dh_install(1)).
+
+Even if you prefer to use L<dh_install(1)> for installing the manpages,
+B<dh_installman> can still be useful for converting the manpage encoding
+to UTF-8 and for converting F<.so> links (as described below). However,
+that part happens automatically without any explicit configuration.
+
+
+You tell B<dh_installman> what man pages go in your packages, and it figures out
+where to install them based on the section field in their B<.TH> or
+B<.Dt> line. If you have a properly formatted B<.TH> or B<.Dt> line,
+your man page will be installed into the right directory, with the
+right name (this includes proper handling of pages with a subsection,
+like B<3perl>, which are placed in F<man3>, and given an extension of
+F<.3perl>). If your B<.TH> or B<.Dt> line is incorrect or missing, the
+program may guess wrong based on the file extension.
+
+It also supports translated man pages, by looking for extensions
+like F<.ll.8> and F<.ll_LL.8>, or by use of the B<--language> switch.
+
+If B<dh_installman> seems to install a man page into the wrong section or with
+the wrong extension, this is because the man page has the wrong section
+listed in its B<.TH> or B<.Dt> line. Edit the man page and correct the
+section, and B<dh_installman> will follow suit. See L<man(7)> for details
+about the B<.TH> section, and L<mdoc(7)> for the B<.Dt> section. If
+B<dh_installman> seems to install a man page into a directory
+like F</usr/share/man/pl/man1/>, that is because your program has a
+name like F<foo.pl>, and B<dh_installman> assumes that means it is translated
+into Polish. Use B<--language=C> to avoid this.
+
+After the man page installation step, B<dh_installman> will check to see if
+any of the man pages in the temporary directories of any of the packages it
+is acting on contain F<.so> links. If so, it changes them to symlinks.
+
+Also, B<dh_installman> will use man to guess the character encoding of each
+manual page and convert it to UTF-8. If the guesswork fails for some
+reason, you can override it using an encoding declaration. See
+L<manconv(1)> for details.
+
+From debhelper compatibility level 11 on, B<dh_install> will fall back to
+looking in F<debian/tmp> for files, if it does not find them in the current
+directory (or wherever you've told it to look using B<--sourcedir>).
+
+=head1 FILES
+
+=over 4
+
+=item debian/I<package>.manpages
+
+Lists man pages to be installed.
+
+Supports substitution variables in compat 13 and later as
+documented in L<debhelper(7)>.
+
+=back
+
+=head1 OPTIONS
+
+=over 4
+
+=item B<-A>, B<--all>
+
+Install all files specified by command line parameters in ALL packages
+acted on.
+
+=item B<--language=>I<ll>
+
+Use this to specify that the man pages being acted on are written in the
+specified language.
+
+=item B<--sourcedir=>I<dir>
+
+Look in the specified directory for files to be installed. This option
+requires compat 11 or later (it is silently ignored in compat 10 or earlier).
+
+Note that this is not the same as the B<--sourcedirectory> option used
+by the B<dh_auto_>I<*> commands. You rarely need to use this option, since
+B<dh_installman> automatically looks for files in F<debian/tmp> in debhelper
+compatibility level 11 and above.
+
+=item I<manpage> ...
+
+Install these man pages into the first package acted on. (Or in all
+packages if B<-A> is specified).
+
+=back
+
+=head1 EXAMPLES
+
+An example F<< debian/I<package>.manpages >> file could look like this:
+
+ doc/man/foo.1
+ # Translations
+ doc/man/foo.da.1
+ doc/man/foo.de.1
+ doc/man/foo.fr.1
+ # NB: The following line is considered a polish translation
+ # of "foo.1" (and not a manpage written in perl called "foo.pl")
+ doc/man/foo.pl.1
+ # ...
+
+=head1 NOTES
+
+An older version of this program, L<dh_installmanpages(1)>, is still used
+by some packages, and so is still included in debhelper.
+It is, however, deprecated, due to its counterintuitive and inconsistent
+interface. Use this program instead.
+
+=cut
+
+init(options => {
+ "language=s" => \$dh{LANGUAGE},
+ "sourcedir=s" => \$dh{SOURCEDIR},
+});
+
+
+# PROMISE: DH NOOP WITHOUT pkgfile-logged(manpages) tmp(usr/share/man) cli-options()
+
+my (@sofiles, @sodests);
+my @all_packages = getpackages();
+
+my $default_error_handler = compat(10) ? \&glob_expand_error_handler_reject_nomagic_warn_discard : \&glob_expand_error_handler_reject;
+my $nodocs = is_build_profile_active('nodoc') || get_buildoption('nodoc') ? 1 : 0;
+# We cannot assume documentation is built under nodoc, but if it is we must flag it as handled
+# or dh_missing might make noise.
+$default_error_handler = \&glob_expand_error_handler_silently_ignore if $nodocs;
+
+on_items_in_parallel(\@all_packages, sub {
+
+ foreach my $package (@_) {
+ next if is_udeb($package);
+
+ my $tmp = tmpdir($package);
+ my $file = pkgfile($package, "manpages");
+ my @manpages;
+ my @search_dirs = ('.');
+ my $skip_install = process_pkg($package) ? 0 : 1;
+ my $error_handler = $skip_install ? \&glob_expand_error_handler_silently_ignore : $default_error_handler;
+ @search_dirs = ($dh{SOURCEDIR} // '.', default_sourcedir($package)) if not compat(10);
+
+ @manpages = filearray($file, \@search_dirs, $error_handler) if $file;
+
+ if (($package eq $dh{FIRSTPACKAGE} || $dh{PARAMS_ALL}) && @ARGV) {
+ push @manpages, @ARGV;
+ }
+
+ log_installed_files($package, @manpages);
+
+ next if $skip_install or $nodocs;
+
+ foreach my $page (@manpages) {
+ my $basename = basename($page);
+
+ # Support compressed pages.
+ my $gz = '';
+ if ($basename =~ m/(.*)(\.gz)/) {
+ $basename = $1;
+ $gz = $2;
+ }
+
+ my ($fd, $section);
+ # See if there is a .TH or .Dt entry in the man page. If so,
+ # we'll pull the section field from that.
+ if ($gz) {
+ $fd = open_gz($page) or error("open $page failed: $!");
+ }
+ else {
+ open($fd, '<', $page) or error("open $page failed: $!");
+ }
+ while (<$fd>) {
+ if (/^\.TH\s+\S+\s+"?(\d+[^"\s]*)"?/ ||
+ /^\.Dt\s+\S+\s+(\d+[^\s]*)/) {
+ $section = $1;
+ if ($section =~ m/^\d+[.]\d+/) {
+ warning("Ignoring section defined in TH/Dt for ${page} as it looks like a version number: ${section}");
+ $section = undef;
+ }
+ last;
+ }
+ }
+ close($fd);
+ # Failing that, we can try to get it from the filename.
+ if (!$section) {
+ ($section) = $basename =~ m/\.([1-9]\w*)$/;
+ }
+
+ # Now get the numeric component of the section.
+ my ($realsection) = $section =~ m/^(\d+)/ if defined $section;
+ if (!$realsection or ($realsection < 0 or $realsection > 9)) {
+ warning("Section for ${page} is computed as \"${section}\", which is not a valid section")
+ if defined($section);
+ error("Could not determine section for $page");
+ }
+
+ # Get the man page's name -- everything up to the last dot.
+ my ($instname) = $basename =~ m/^(.*)\./;
+
+ my $destdir = "$tmp/usr/share/man/man$realsection/";
+ my $langcode;
+ if (!defined $dh{LANGUAGE} || !exists $dh{LANGUAGE}) {
+ if (not compat(10) and $page =~ m{/man/(?:([a-z][a-z](?:_[A-Z][A-Z])?)(?:\.[^/]+)?)?/man[1-9]/}) {
+ # If it looks like it was installed in a proper man dir, assume the language
+ # from that is correct.
+ $langcode = $1;
+ } else {
+ # Translated man pages are typically specified by adding the
+ # language code to the filename, so detect that and
+ # redirect to appropriate directory, stripping the code.
+ ($langcode) = $basename =~ m/\.([a-z][a-z](?:_[A-Z][A-Z])?)\.(?:[1-9]|man)/;
+ # Avoid false positives such as /usr/share/man/man8/libnss_myhostname.so.2.8
+ if (defined $langcode && $langcode eq 'so' && $basename =~ /^lib.*\.so(\.[0-9]+)*$/) {
+ $langcode = '';
+ }
+ }
+ } elsif ($dh{LANGUAGE} ne 'C') {
+ $langcode = $dh{LANGUAGE};
+ }
+
+ if (defined $langcode && $langcode ne '') {
+ # Strip the language code from the instname.
+ $instname =~ s/\.$langcode$//;
+ }
+
+ if (defined $langcode && $langcode ne '') {
+ $destdir = "$tmp/usr/share/man/$langcode/man$realsection/";
+ }
+ $destdir =~ tr:/:/:s; # just for looks
+ my $instpage = "$destdir$instname.$section";
+
+ next if -l $instpage;
+
+ install_dir($destdir);
+ if ($gz) {
+ doit({ stdout => $instpage }, 'zcat', $page);
+ }
+ else {
+ install_file($page, $instpage);
+ }
+ }
+
+ # Now the .so conversion.
+ @sofiles = @sodests = ();
+ foreach my $dir (qw{usr/share/man}) {
+ if (-e "$tmp/$dir") {
+ find(\&find_so_man, "$tmp/$dir");
+ }
+ }
+ foreach my $sofile (@sofiles) {
+ my $sodest = shift(@sodests);
+ rm_files($sofile);
+ make_symlink_raw_target($sodest, $sofile);
+ }
+ }
+
+});
+
+# Now utf-8 conversion.
+my $has_man_recode = 0;
+$has_man_recode = 1 if has_man_db_tool('man-recode');
+
+if ($has_man_recode || has_man_db_tool('man')) {
+ my (@manpages_to_reencode, @issues);
+ for my $package (@{$dh{DOPACKAGES}}) {
+ next if is_udeb($package);
+ my $tmp = tmpdir($package);
+ foreach my $dir (qw{usr/share/man}) {
+ next unless -e "$tmp/$dir";
+ my %seen;
+ my $wanted = sub {
+ my $path = $File::Find::name;
+ return if -l $path || !-f _;
+ if ($path =~ m/\.dh-new$/) {
+ push(@issues, $path);
+ return;
+ }
+ my $uncompressed_name = $path;
+ $uncompressed_name =~ s/\.(?:gz|Z)$//;
+ if (exists($seen{$uncompressed_name})) {
+ my $msg = "Multiple definitions for manpage ${uncompressed_name} via different compressions.";
+ my @values = sort ($path, $seen{$uncompressed_name});
+ my $warn_msg = $msg . ' Picking ' . $values[0] . ' as the canonical definition.';
+ my $error_msg = $msg . ' Please ensure there is at most one definition.';
+ deprecated_functionality($warn_msg, 13, $error_msg);
+ $path = $values[0];
+ warn("Removing conflicting definition of ${uncompressed_name} (" . $values[1]
+ . ') to ensure deterministic behaviour.');
+ rm_files($values[1]);
+ }
+ $seen{$uncompressed_name} = $path;
+ };
+ find({
+ no_chdir => 1,
+ wanted => $wanted,
+ }, "$tmp/$dir");
+ push(@manpages_to_reencode, sort(values(%seen)));
+ }
+
+ if (@issues) {
+ warning("Removing temporary manpages from another dh_installman instance");
+ rm_files(@issues);
+ warning("Possibly race-condition detected or left-overs from an interrupted dh_installman (e.g. with ^C)");
+ error("Please ensure there are no parallel dh_installman's running (for this pkg) and then re-run dh_installman");
+ }
+ }
+ if (@manpages_to_reencode) {
+ on_items_in_parallel(\@manpages_to_reencode, \&reencode_manpages);
+ }
+} else {
+ # Should only occur during debhelper building itself (to avoid a B-D on man-db).
+ warning("man is not available. Skipping re-encode of UTF-8 manpages")
+}
+
+# Check if a file is a .so man page, for use by File::Find.
+sub find_so_man {
+ # The -s test is because a .so file tends to be small. We don't want
+ # to open every man page. 1024 is arbitrary.
+ if (! -f $_ || -s _ > 1024 || -s _ == 0) {
+ return;
+ }
+
+ # Test first line of file for the .so thing.
+ my $fd;
+ if (/\.gz$/) {
+ $fd = open_gz($_) or error("open $_ failed: $!");
+ }
+ else {
+ open($fd, '<', $_) || error("open $_ failed: $!");
+ }
+ my $l = <$fd>;
+ close($fd);
+
+ if (! defined $l) {
+ error("failed to read $_");
+ }
+
+ if ($l=~m/\.so\s+(.*)\s*/) {
+ my $solink=$1;
+ # This test is here to prevent links like ... man8/../man8/foo.8
+ if (basename($File::Find::dir) eq
+ dirname($solink)) {
+ $solink=basename($solink);
+ }
+ # A so link with a path is relative to the base of the man
+ # page hierarchy, but without a path, is relative to the
+ # current section.
+ elsif ($solink =~ m!/!) {
+ $solink="../$solink";
+ }
+
+ if (-e $solink || -e "$solink.gz") {
+ push @sofiles,"$File::Find::dir/$_";
+ push @sodests,$solink;
+ }
+ }
+}
+
+sub has_man_db_tool {
+ my ($tool) = @_;
+ open(my $old_stderr, '>&', *STDERR) or error("dup(STDERR, tmp_fd): $!");
+ # Ignore the error; it is intended as noise-reduction. As long as we can restore
+ # the stderr later, the log will just be slightly more noisy than planned.
+ open(*STDERR, '>', '/dev/null') or warn("redirect stderr to /dev/null failed: $!");
+
+ my $res = defined(`$tool --version`);
+ open(*STDERR, '>&', $old_stderr) or error("dup(tmp_fd, STDERR): $!");
+ close($old_stderr);
+ return $res;
+}
+
+sub reencode_manpages {
+ my (@manpages) = @_;
+ if ($has_man_recode) {
+ xargs(\@manpages, 'man-recode', '--to-code', 'UTF-8', '--suffix', '.dh-new');
+ }
+ for my $manpage (@manpages) {
+ my $manpage_tmp = "${manpage}.dh-new";
+ $manpage_tmp =~ s/\.(?:gz|Z)\.dh-new$/.dh-new/;
+ if (not $has_man_recode) {
+ my $manpage_cmd = ($manpage =~ m{^/}) ? $manpage : "./${manpage}";
+ doit({ stdout => $manpage_tmp }, 'man', '-l', '--recode', 'UTF-8', $manpage_cmd);
+ }
+ # recode uncompresses compressed pages
+ my $orig = $manpage;
+ rm_files($orig) if $manpage =~ s/\.(gz|Z)$//;
+ rename_path($manpage_tmp, $manpage);
+ }
+ # Bulk reset permissions of all re-encoded files
+ xargs(\@manpages, 'chmod', '0644', '--');
+}
+
+=head1 SEE ALSO
+
+L<debhelper(7)>
+
+This program is a part of debhelper.
+
+=head1 AUTHOR
+
+Joey Hess <joeyh@debian.org>
+
+=cut
diff --git a/dh_installmanpages b/dh_installmanpages
new file mode 100755
index 0000000..22c669e
--- /dev/null
+++ b/dh_installmanpages
@@ -0,0 +1,208 @@
+#!/usr/bin/perl
+
+=head1 NAME
+
+dh_installmanpages - old-style man page installer (deprecated)
+
+=cut
+
+use strict;
+use warnings;
+use File::Find;
+use Debian::Debhelper::Dh_Lib;
+
+our $VERSION = DH_BUILTIN_VERSION;
+
+=head1 SYNOPSIS
+
+B<dh_installmanpages> [S<I<debhelper options>>] [S<I<file> ...>]
+
+=head1 DESCRIPTION
+
+B<dh_installmanpages> is a debhelper program that is responsible for
+automatically installing man pages into F<usr/share/man/>
+in package build directories.
+
+This is a DWIM-style program, with an interface unlike the rest of
+debhelper. It is deprecated, and you are encouraged to use
+L<dh_installman(1)> instead.
+
+B<dh_installmanpages> scans the current directory and all subdirectories for
+filenames that look like man pages. (Note that only real files are looked
+at; symlinks are ignored.) It uses L<file(1)> to verify that the files are
+in the correct format. Then, based on the files' extensions, it installs
+them into the correct man directory.
+
+All filenames specified as parameters will be skipped by B<dh_installmanpages>.
+This is useful if by default it installs some man pages that you do not
+want to be installed.
+
+After the man page installation step, B<dh_installmanpages> will check to see
+if any of the man pages are F<.so> links. If so, it changes them to symlinks.
+
+=head1 OPTIONS
+
+=over 4
+
+=item I<file> ...
+
+Do not install these files as man pages, even if they look like valid man
+pages.
+
+=back
+
+=head1 BUGS
+
+B<dh_installmanpages> will install the man pages it finds into B<all> packages
+you tell it to act on, since it can't tell what package the man
+pages belong in. This is almost never what you really want (use B<-p> to work
+around this, or use the much better L<dh_installman(1)> program instead).
+
+Files ending in F<.man> will be ignored.
+
+Files specified as parameters that contain spaces in their filenames will
+not be processed properly.
+
+=cut
+
+warning("This program is deprecated, switch to dh_installman.");
+
+init();
+
+# Check if a file is a man page, for use by File::Find.
+my @manpages;
+my @allpackages;
+sub find_man {
+ # Does its filename look like a man page?
+ # .ex files are examples installed by deb-make,
+ # we don't want those, or .in files, which are
+ # from configure, nor do we want CVS .#* files.
+ if (! (-f $_ && /^.*\.[1-9].*$/ && ! /\.(ex|in)$/ && ! /^\.#/)) {
+ return;
+ }
+
+ # It's not in a tmp directory is it?
+ if ($File::Find::dir=~m:debian/.*tmp.*:) {
+ return;
+ }
+ foreach my $dir (@allpackages) {
+ if ($File::Find::dir=~m:debian/\Q$dir\E:) {
+ return;
+ }
+ }
+
+ # And file does think it's a real man page?
+ my $type=`file -z $_`;
+ if ($type !~ m/:.*roff/) {
+ return;
+ }
+
+ # Good enough.
+ push @manpages,"$File::Find::dir/$_";
+}
+
+# Check if a file is a .so man page, for use by File::Find.
+my @sofiles;
+my @sodests;
+sub find_so_man {
+ # The -s test is because a .so file tends to be small. We don't want
+ # to open every man page. 1024 is arbitrary.
+ if (! -f $_ || -s $_ > 1024) {
+ return;
+ }
+
+ # Test first line of file for the .so thing.
+ open(my $fd, '<', $_);
+ my $l = <$fd>;
+ close($fd);
+ if ($l=~m/\.so\s+(.*)/) {
+ my $solink=$1;
+ # This test is here to prevent links like ... man8/../man8/foo.8
+ if (basename($File::Find::dir) eq
+ dirname($solink)) {
+ $solink=basename($solink);
+ }
+ else {
+ $solink="../$solink";
+ }
+
+ push @sofiles,"$File::Find::dir/$_";
+ push @sodests,$solink;
+ }
+}
+
+foreach my $package (@{$dh{DOPACKAGES}}) {
+ next if is_udeb($package);
+
+ my $tmp=tmpdir($package);
+
+ # Find all filenames that look like man pages.
+ @manpages=();
+ @allpackages=getpackages() if not @allpackages;
+ find(\&find_man,'.'); # populates @manpages
+
+ foreach my $page (@manpages) {
+ $page=~s:^\./::; # just for looks
+
+ my $basename=basename($page);
+
+ # Skip all files listed on command line.
+ my $install=1;
+ foreach my $skip (@ARGV) {
+ # Look at basename of what's on connect line
+ # for backwards compatibility.
+ if ($basename eq basename($skip)) {
+ $install=undef;
+ last;
+ }
+ }
+
+ if ($install) {
+ my $extdir="share";
+
+ my ($section)=$basename=~m/\.([1-9])/;
+
+ my $destdir="$tmp/usr/$extdir/man/man$section/";
+
+ # Handle translated man pages.
+ my $instname=$basename;
+ my ($langcode)=$basename=~m/\.([a-z][a-z])\.([1-9])/;
+ if (defined $langcode && $langcode ne '') {
+ $destdir="$tmp/usr/$extdir/man/$langcode/man$section/";
+ $instname=~s/\.$langcode\./\./;
+ }
+
+ $destdir=~tr:/:/:s; # just for looks
+
+ if (! -e "$destdir/$basename" && !-l "$destdir/$basename") {
+ install_dir($destdir);
+ install_file($page,$destdir.$instname);
+ }
+ }
+ }
+
+ # Now the .so conversion.
+ @sofiles=@sodests=();
+ foreach my $dir (qw{usr/share/man}) {
+ if (-e "$tmp/$dir") {
+ find(\&find_so_man, "$tmp/$dir");
+ }
+ }
+ foreach my $sofile (@sofiles) {
+ my $sodest=shift(@sodests);
+ doit "rm","-f",$sofile;
+ doit "ln","-sf",$sodest,$sofile;
+ }
+}
+
+=head1 SEE ALSO
+
+L<debhelper(7)>
+
+This program is a part of debhelper.
+
+=head1 AUTHOR
+
+Joey Hess <joeyh@debian.org>
+
+=cut
diff --git a/dh_installmenu b/dh_installmenu
new file mode 100755
index 0000000..c30e90f
--- /dev/null
+++ b/dh_installmenu
@@ -0,0 +1,100 @@
+#!/usr/bin/perl
+
+=head1 NAME
+
+dh_installmenu - install Debian menu files into package build directories
+
+=cut
+
+use strict;
+use warnings;
+use Debian::Debhelper::Dh_Lib;
+
+our $VERSION = DH_BUILTIN_VERSION;
+
+=head1 SYNOPSIS
+
+B<dh_installmenu> [S<B<debhelper options>>] [B<-n>]
+
+=head1 DESCRIPTION
+
+B<dh_installmenu> is a debhelper program that is responsible for installing
+files used by the Debian B<menu> package into package build directories.
+
+It also automatically generates the F<postinst> and F<postrm> commands needed to
+interface with the Debian B<menu> package. These commands are inserted into
+the maintainer scripts by L<dh_installdeb(1)>.
+
+=head1 FILES
+
+=over 4
+
+=item debian/I<package>.menu
+
+Debian menu files, installed into usr/share/menu/I<package> in the package
+build directory. See L<menufile(5)> for its format.
+
+=item debian/I<package>.menu-method
+
+Debian menu method files, installed into etc/menu-methods/I<package>
+in the package build directory.
+
+=back
+
+=head1 OPTIONS
+
+=over 4
+
+=item B<-n>, B<--no-scripts>
+
+Do not modify F<postinst>/F<postrm> scripts.
+
+=back
+
+=cut
+
+init();
+
+# PROMISE: DH NOOP WITHOUT menu menu-method cli-options()
+
+foreach my $package (@{$dh{DOPACKAGES}}) {
+ my $tmp=tmpdir($package);
+ my $menu=pkgfile($package,"menu");
+ my $menu_method=pkgfile($package,"menu-method");
+
+ if ($menu ne '') {
+ install_dir("$tmp/usr/share/menu");
+ install_file($menu,"$tmp/usr/share/menu/$package");
+
+ # Add the scripts if a menu-method file doesn't exist.
+ # The scripts for menu-method handle everything these do, too.
+ if ($menu_method eq "" && ! $dh{NOSCRIPTS}) {
+ autoscript($package,"postinst","postinst-menu");
+ autoscript($package,"postrm","postrm-menu")
+ }
+ }
+
+ if ($menu_method ne '') {
+ install_dir("$tmp/etc/menu-methods");
+ install_file($menu_method,"$tmp/etc/menu-methods/$package");
+
+ if (! $dh{NOSCRIPTS}) {
+ autoscript($package, 'postinst', 'postinst-menu-method', { 'PACKAGE' => $package });
+ autoscript($package, 'postrm', 'postrm-menu-method', { 'PACKAGE' => $package });
+ }
+ }
+}
+
+=head1 SEE ALSO
+
+L<debhelper(7)>
+L<update-menus(1)>
+L<menufile(5)>
+
+This program is a part of debhelper.
+
+=head1 AUTHOR
+
+Joey Hess <joeyh@debian.org>
+
+=cut
diff --git a/dh_installmime b/dh_installmime
new file mode 100755
index 0000000..5ad6190
--- /dev/null
+++ b/dh_installmime
@@ -0,0 +1,73 @@
+#!/usr/bin/perl
+
+=head1 NAME
+
+dh_installmime - install mime files into package build directories
+
+=cut
+
+use strict;
+use warnings;
+use Debian::Debhelper::Dh_Lib;
+
+our $VERSION = DH_BUILTIN_VERSION;
+
+=head1 SYNOPSIS
+
+B<dh_installmime> [S<I<debhelper options>>]
+
+=head1 DESCRIPTION
+
+B<dh_installmime> is a debhelper program that is responsible for installing
+mime files into package build directories.
+
+=head1 FILES
+
+=over 4
+
+=item debian/I<package>.mime
+
+Installed into usr/lib/mime/packages/I<package> in the package build
+directory.
+
+=item debian/I<package>.sharedmimeinfo
+
+Installed into /usr/share/mime/packages/I<package>.xml in the package build
+directory.
+
+=back
+
+=cut
+
+init();
+
+# PROMISE: DH NOOP WITHOUT mime sharedmimeinfo cli-options()
+
+foreach my $package (@{$dh{DOPACKAGES}}) {
+ my $tmp=tmpdir($package);
+
+ my $mime=pkgfile($package,"mime");
+ if ($mime ne '') {
+ install_dir("$tmp/usr/lib/mime/packages");
+ install_file($mime, "$tmp/usr/lib/mime/packages/$package");
+ }
+
+ my $sharedmimeinfo=pkgfile($package,"sharedmimeinfo");
+ if ($sharedmimeinfo ne '') {
+ install_dir("$tmp/usr/share/mime/packages");
+ install_file($sharedmimeinfo,
+ "$tmp/usr/share/mime/packages/$package.xml");
+ }
+}
+
+=head1 SEE ALSO
+
+L<debhelper(7)>
+
+This program is a part of debhelper.
+
+=head1 AUTHOR
+
+Joey Hess <joeyh@debian.org>
+
+=cut
diff --git a/dh_installmodules b/dh_installmodules
new file mode 100755
index 0000000..924a338
--- /dev/null
+++ b/dh_installmodules
@@ -0,0 +1,119 @@
+#!/usr/bin/perl
+
+=head1 NAME
+
+dh_installmodules - register kernel modules
+
+=cut
+
+use strict;
+use warnings;
+use Debian::Debhelper::Dh_Lib;
+use File::Find;
+
+our $VERSION = DH_BUILTIN_VERSION;
+
+=head1 SYNOPSIS
+
+B<dh_installmodules> [S<I<debhelper options>>] [B<-n>] [B<--name=>I<name>]
+
+=head1 DESCRIPTION
+
+B<dh_installmodules> is a debhelper program that is responsible for
+registering kernel modules.
+
+Kernel modules are searched for in the package build directory and if
+found, F<preinst>, F<postinst> and F<postrm> commands are automatically generated to
+run B<depmod> and register the modules when the package is installed.
+These commands are inserted into the maintainer scripts by
+L<dh_installdeb(1)>.
+
+=head1 FILES
+
+=over 4
+
+=item debian/I<package>.modprobe
+
+Installed to etc/modprobe.d/I<package>.conf in the package build directory.
+
+=back
+
+=head1 OPTIONS
+
+=over 4
+
+=item B<-n>, B<--no-scripts>
+
+Do not modify F<preinst>/F<postinst>/F<postrm> scripts.
+
+=item B<--name=>I<name>
+
+When this parameter is used, B<dh_installmodules> looks for and
+installs files named debian/I<package>.I<name>.modprobe instead
+of the usual debian/I<package>.modprobe
+
+=back
+
+=head1 NOTES
+
+Note that this command is not idempotent. L<dh_prep(1)> should be called
+between invocations of this command. Otherwise, it may cause multiple
+instances of the same text to be added to maintainer scripts.
+
+=cut
+
+init();
+
+# Looks for kernel modules in the passed directory. If any are found,
+# returns the kernel version (or versions) that the modules seem to be for.
+sub find_kernel_modules {
+ my $searchdir=shift;
+ my %versions;
+
+ return unless -d $searchdir;
+ find(sub {
+ if (m/ [.]k?o (?:[.](?:[gx]z|bz2))? $/x) {
+ my ($kvers)=$File::Find::dir=~m!lib/modules/([^/]+)/!;
+ if (! defined $kvers || ! length $kvers) {
+ warning("Cannot determine kernel version for module $File::Find::name");
+ }
+ else {
+ $versions{$kvers}=1;
+ }
+ }
+ }, $searchdir);
+
+ return sort(keys(%versions));
+}
+
+# PROMISE: DH NOOP WITHOUT modprobe tmp(lib/modules) cli-options()
+
+foreach my $package (@{$dh{DOPACKAGES}}) {
+ my $tmp=tmpdir($package);
+ my $modprobe_file=pkgfile($package,"modprobe");
+
+ if ($modprobe_file) {
+ my $path = '/etc/modprobe.d/' . pkgfilename($package) . '.conf';
+ install_dir("$tmp/etc/modprobe.d");
+ install_file($modprobe_file, "$tmp/$path");
+ }
+
+ if (! $dh{NOSCRIPTS}) {
+ foreach my $kvers (find_kernel_modules("$tmp/lib/modules")) {
+ autoscript($package, 'postinst', 'postinst-modules', { 'KVERS' => $kvers });
+ autoscript($package, 'postrm', 'postrm-modules', { 'KVERS' => $kvers });
+ }
+ }
+}
+
+=head1 SEE ALSO
+
+L<debhelper(7)>
+
+This program is a part of debhelper.
+
+=head1 AUTHOR
+
+Joey Hess <joeyh@debian.org>
+
+=cut
diff --git a/dh_installpam b/dh_installpam
new file mode 100755
index 0000000..193e334
--- /dev/null
+++ b/dh_installpam
@@ -0,0 +1,81 @@
+#!/usr/bin/perl
+
+=head1 NAME
+
+dh_installpam - install pam support files
+
+=cut
+
+use strict;
+use warnings;
+use Debian::Debhelper::Dh_Lib;
+
+our $VERSION = DH_BUILTIN_VERSION;
+
+=head1 SYNOPSIS
+
+B<dh_installpam> [S<I<debhelper options>>] [B<--name=>I<name>]
+
+=head1 DESCRIPTION
+
+B<dh_installpam> is a debhelper program that is responsible for installing
+files used by PAM into package build directories.
+
+=head1 FILES
+
+=over 4
+
+=item debian/I<package>.pam
+
+Installed into usr/lib/pam.d/I<package> in the package build directory.
+
+Until compatibility level 14 this file was installed under
+etc/pam.d/I<package>. Please consider using the "rm_conffile" feature from
+L<dh_installdeb(1)> to ensure the proper removal of previous PAM files.
+
+=back
+
+=head1 OPTIONS
+
+=over 4
+
+=item B<--name=>I<name>
+
+Look for files named debian/I<package>.I<name>.pam and install them as
+usr/lib/pam.d/I<name>, instead of using the usual files and installing them
+using the package name.
+
+=back
+
+=cut
+
+init();
+
+# PROMISE: DH NOOP WITHOUT pam cli-options()
+
+foreach my $package (@{$dh{DOPACKAGES}}) {
+ my $tmp=tmpdir($package);
+ my $pam=pkgfile($package,"pam");
+
+ my $pamd_dir="/usr/lib/pam.d";
+ if (compat(13)) {
+ $pamd_dir="/etc/pam.d";
+ }
+
+ if ($pam ne '') {
+ install_dir("$tmp/$pamd_dir");
+ install_file($pam,"$tmp/$pamd_dir/".pkgfilename($package));
+ }
+}
+
+=head1 SEE ALSO
+
+L<debhelper(7)>
+
+This program is a part of debhelper.
+
+=head1 AUTHOR
+
+Joey Hess <joeyh@debian.org>
+
+=cut
diff --git a/dh_installppp b/dh_installppp
new file mode 100755
index 0000000..68784a6
--- /dev/null
+++ b/dh_installppp
@@ -0,0 +1,78 @@
+#!/usr/bin/perl
+
+=head1 NAME
+
+dh_installppp - install ppp ip-up and ip-down files
+
+=cut
+
+use strict;
+use warnings;
+use Debian::Debhelper::Dh_Lib;
+
+our $VERSION = DH_BUILTIN_VERSION;
+
+=head1 SYNOPSIS
+
+B<dh_installppp> [S<I<debhelper options>>] [B<--name=>I<name>]
+
+=head1 DESCRIPTION
+
+B<dh_installppp> is a debhelper program that is responsible for installing
+ppp ip-up and ip-down scripts into package build directories.
+
+=head1 FILES
+
+=over 4
+
+=item debian/I<package>.ppp.ip-up
+
+Installed into etc/ppp/ip-up.d/I<package> in the package build directory.
+
+=item debian/I<package>.ppp.ip-down
+
+Installed into etc/ppp/ip-down.d/I<package> in the package build directory.
+
+=back
+
+=head1 OPTIONS
+
+=over 4
+
+=item B<--name=>I<name>
+
+Look for files named F<debian/package.name.ppp.ip-*> and install them as
+F<etc/ppp/ip-*/name>, instead of using the usual files and installing them
+as the package name.
+
+=back
+
+=cut
+
+init();
+
+# PROMISE: DH NOOP WITHOUT ppp.ip-up ppp.ip-down cli-options()
+
+foreach my $package (@{$dh{DOPACKAGES}}) {
+ my $tmp=tmpdir($package);
+
+ foreach my $script (qw(up down)) {
+ my $file=pkgfile($package, "ppp.ip-$script");
+ if ($file ne '') {
+ install_dir("$tmp/etc/ppp/ip-$script.d");
+ install_prog($file,"$tmp/etc/ppp/ip-$script.d/".pkgfilename($package));
+ }
+ }
+}
+
+=head1 SEE ALSO
+
+L<debhelper(7)>
+
+This program is a part of debhelper.
+
+=head1 AUTHOR
+
+Joey Hess <joeyh@debian.org>
+
+=cut
diff --git a/dh_installsystemd b/dh_installsystemd
new file mode 100755
index 0000000..bf95624
--- /dev/null
+++ b/dh_installsystemd
@@ -0,0 +1,456 @@
+#!/usr/bin/perl -w
+
+=head1 NAME
+
+dh_installsystemd - install systemd unit files
+
+=cut
+
+use strict;
+use warnings;
+use Debian::Debhelper::Dh_Lib;
+use File::Find;
+use Cwd qw(getcwd abs_path);
+
+our $VERSION = DH_BUILTIN_VERSION;
+
+=head1 SYNOPSIS
+
+B<dh_installsystemd> [S<I<debhelper options>>] [B<--restart-after-upgrade>] [B<--no-stop-on-upgrade>] [B<--no-enable>] [B<--no-start>] [B<--name=>I<name>] [S<I<unit file> ...>]
+
+=head1 DESCRIPTION
+
+B<dh_installsystemd> is a debhelper program that is responsible for
+installing package maintainer supplied systemd unit files.
+
+It also finds the service files installed by a package and generates
+F<preinst>, F<postinst>, and F<prerm> code blocks for enabling,
+disabling, starting, stopping, and restarting the corresponding
+systemd services, when the package is installed, updated, or
+removed. These snippets are added to the maintainer scripts by
+L<dh_installdeb(1)>.
+
+L<deb-systemd-helper(1)> is used to enable and disable systemd units,
+thus it is not necessary that the machine actually runs systemd during
+package installation time, enabling happens on all machines in order
+to be able to switch from sysvinit to systemd and back.
+
+B<dh_installsystemd> operates on all unit files installed by a
+package. For only generating blocks for specific unit files, pass them
+as arguments, C<dh_installsystemd quota.service>. Specific unit files
+can be excluded from processing using the B<-X> common L<debhelper(1)>
+option.
+
+=head1 FILES
+
+=over 4
+
+=item debian/I<package>.mount,
+ debian/I<package>.path,
+ debian/I<package>@.path,
+ debian/I<package>.service,
+ debian/I<package>@.service,
+ debian/I<package>.socket,
+ debian/I<package>@.socket,
+ debian/I<package>.target,
+ debian/I<package>@.target,
+ debian/I<package>.timer,
+ debian/I<package>@.timer
+
+If any of those files exists, they are installed into
+F<usr/lib/systemd/system/> in the package build directory.
+
+=item debian/I<package>.tmpfile
+
+Only used in compat 12 or earlier. In compat 13+, this file is
+handled by L<dh_installtmpfiles(1)> instead.
+
+If this exists, it is installed into F<usr/lib/tmpfiles.d/> in the
+package build directory. Note that the C<tmpfiles.d> mechanism is
+currently only used by systemd.
+
+=back
+
+=head1 OPTIONS
+
+=over 4
+
+=item B<--no-enable>
+
+Disable the service(s) on purge, but do not enable them on install.
+
+B<Note> that this option does not affect whether the services are
+started. Please remember to also use B<--no-start> if the service
+should not be started.
+
+=item B<--name=>I<name>
+
+This option controls several things.
+
+It changes the name that B<dh_installsystemd> uses when it looks for
+maintainer provided systemd unit files as listed in the L</FILES>
+section. As an example, B<dh_installsystemd --name foo> will look for
+F<<< I<debian/package.>B<< I<foo> >>I<.service> >>> instead of
+F<< I<debian/package.service> >>). These unit files are installed as F<<
+I<name.unit-extension> >> (in the example, it would be installed as
+F<<< B<< I<foo> >>I<.service> >>>).
+
+Furthermore, if no unit files are passed explicitly as command line
+arguments, B<dh_installsystemd> will only act on unit files called
+I<name> (rather than all unit files found in the package).
+
+=item B<--restart-after-upgrade>
+
+Do not stop the unit file until after the package upgrade has been completed.
+This is the default behaviour in compat 10.
+
+In earlier compat levels the default was to stop the unit file in the
+F<prerm>, and start it again in the F<postinst>.
+
+This can be useful for daemons that should not have a possibly long
+downtime during upgrade. But you should make sure that the daemon will not
+get confused by the package being upgraded while it's running before using
+this option.
+
+=item B<--no-restart-after-upgrade>
+
+Undo a previous B<--restart-after-upgrade> (or the default of compat
+10). If no other options are given, this will cause the service to be
+stopped in the F<prerm> script and started again in the F<postinst>
+script.
+
+=item B<-r>, B<--no-stop-on-upgrade>, B<--no-restart-on-upgrade>
+
+Do not stop service on upgrade. This has the side-effect of not
+restarting the service as a part of the upgrade.
+
+If you want to restart the service with minimal downtime, please use
+B<--restart-after-upgrade> (default in compat 10 or later). If you want
+the service to be restarted but be stopped during the upgrade, then please
+use B<--no-restart-after-upgrade> (note the "after-upgrade").
+
+Note that the B<--no-restart-on-upgrade> alias is deprecated and will
+be removed in compat 14. This is to avoid confusion with the
+B<--no-restart-after-upgrade> option.
+
+=item B<--no-start>
+
+Do not start the unit file after upgrades and after initial installation (the
+latter is only relevant for services without a corresponding init script).
+
+B<Note> that this option does not affect whether the services are
+enabled. Please remember to also use B<--no-enable> if the services
+should not be enabled.
+
+=item S<B<unit file> ...>
+
+Only process and generate maintscripts for the installed unit files
+with the (base)name I<unit file>.
+
+Note: B<dh_installsystemd> will still install unit files from
+F<debian/> but it will not generate any maintscripts for them unless
+they are explicitly listed in S<B<unit file> ...>
+
+=back
+
+=head1 NOTES
+
+This command is not idempotent. L<dh_prep(1)> should be called between
+invocations of this command (with the same arguments). Otherwise, it
+may cause multiple instances of the same text to be added to
+maintainer scripts.
+
+=cut
+
+$dh{RESTART_AFTER_UPGRADE} = '';
+$dh{NO_START} = '';
+
+init(options => {
+ "no-enable" => \$dh{NO_ENABLE},
+ "r" => \$dh{R_FLAG},
+ 'no-stop-on-upgrade' => \$dh{R_FLAG},
+ "no-restart-on-upgrade" => \$dh{R_FLAG},
+ "no-start" => \$dh{NO_START},
+ "R|restart-after-upgrade!" => \$dh{RESTART_AFTER_UPGRADE},
+ "no-also" => \$dh{NO_ALSO}, # undocumented option
+});
+
+if ($dh{RESTART_AFTER_UPGRADE} eq '') {
+ $dh{RESTART_AFTER_UPGRADE} = 1 if not defined($dh{R_FLAG}) and $dh{NO_START} eq '';
+}
+
+sub quote {
+ # Add single quotes around the argument.
+ return '\'' . $_[0] . '\'';
+}
+
+sub uniq {
+ my %seen;
+ return grep { !$seen{$_}++ } @_;
+}
+
+sub contains_install_section {
+ my ($unit_path) = @_;
+
+ open(my $fh, '<', $unit_path) or error("Cannot open($unit_path): $!");
+
+ while (my $line = <$fh>) {
+ chomp($line);
+ return 1 if $line =~ /^\s*\[Install\]$/i;
+ }
+ close($fh);
+ return 0;
+}
+
+sub has_sysv_equivalent {
+ my ($tmpdir, $unit) = @_;
+
+ $unit =~ s/\.(?:mount|service|socket|target|path)$//g;
+ return -f "$tmpdir/etc/init.d/$unit";
+}
+
+sub install_unit {
+ my ($package, $script, $pkgsuffix, $path, $installsuffix) = @_;
+ $installsuffix = $installsuffix || $pkgsuffix;
+ my $unit = pkgfile($package, $pkgsuffix);
+ return if $unit eq '';
+ install_dir($path);
+ install_file($unit, "${path}/${script}.${installsuffix}");
+}
+
+# Extracts the directive values from a unit file. Handles repeated
+# directives in the same unit file. Assumes values can only be
+# composed of lists of unit names. This is good enough for the 'Also='
+# and 'Alias=' directives handled here.
+sub extract_key {
+ my ($unit_path, $key) = @_;
+ my @values;
+
+ open(my $fh, '<', $unit_path) or error("Cannot open($unit_path): $!");
+
+ while (my $line = <$fh>) {
+ chomp($line);
+
+ # Since unit names can't have whitespace in systemd, simply
+ # use split and strip any leading/trailing quotes. See
+ # systemd-escape(1) for examples of valid unit names.
+ if ($line =~ /^\s*$key=(.+)$/i) {
+ for my $value (split(/\s+/, $1)) {
+ $value =~ s/^(["'])(.*)\g1$/$2/;
+ push @values, $value;
+ }
+ }
+ }
+
+ close($fh);
+ return @values;
+}
+
+sub list_installed_units {
+ my ($tmpdir, $aliases) = @_;
+
+ my @installed;
+
+ foreach my $unitdir ("$tmpdir/lib/systemd/system", "$tmpdir/usr/lib/systemd/system") {
+ next unless -d $unitdir;
+ opendir(my $dh, "$unitdir") or error("Cannot opendir($unitdir): $!");
+
+ foreach my $name (readdir($dh)) {
+ my $path = "$unitdir/$name";
+ next unless -f $path;
+ if (-l "$path") {
+ my $dest = basename(readlink($path));
+ $aliases->{$dest} //= [ ];
+ push @{$aliases->{$dest}}, $name;
+ } else {
+ push @installed, $name;
+ }
+ }
+
+ closedir($dh);
+ }
+ return @installed;
+}
+
+
+# PROMISE: DH NOOP WITHOUT internal(bug#950723) tmp(lib/systemd/system) tmp(usr/lib/systemd/system) tmp(usr/lib/tmpfiles.d) tmp(etc/tmpfiles.d) mount path service socket target tmpfile timer cli-options()
+
+
+# Install package maintainer supplied unit files
+foreach my $package (@{$dh{DOPACKAGES}}) {
+ my $tmpdir = tmpdir($package);
+
+ # Intall all unit files in the debian/ directory with names in the
+ # form $package.(service|target|socket|path|timer|mount|tmpfile)
+ # and their templated version when relevant.
+
+ # This can be modified with the --name option to look for unit
+ # files with names in the form $package.$name.(service|...) and
+ # $name.(service|target|socket|path|timer|mount|tmpfile) and their
+ # templated version when relevant.
+ my $name = $dh{NAME} // $package;
+
+ for my $type (qw(service target socket path timer)) {
+ install_unit($package, $name, $type, "$tmpdir/usr/lib/systemd/system");
+ install_unit("${package}@", "${name}@", $type, "$tmpdir/usr/lib/systemd/system");
+ }
+
+ install_unit($package, $name, 'mount', "$tmpdir/usr/lib/systemd/system");
+ # In compat 13+, this is handled by dh_installtmpfiles
+ install_unit($package, $name, 'tmpfile', "$tmpdir/usr/lib/tmpfiles.d", 'conf') if compat(12);
+}
+
+
+if (compat(12)) {
+ # In compat 13+, this is handled by dh_installtmpfiles
+ # Add postinst code blocks to handle tmpfiles
+ foreach my $package (@{$dh{DOPACKAGES}}) {
+ my $tmpdir = tmpdir($package);
+ my @tmpfiles;
+
+ my @dirs = grep { -d } map { "${tmpdir}/$_" } qw(usr/lib/tmpfiles.d etc/tmpfiles.d);
+
+ find({
+ wanted => sub {
+ my $name = $File::Find::name;
+ return if not -f $name or not $name =~ m{[.]conf$};
+ push(@tmpfiles, basename($name)); },
+ no_chdir => 1,
+ }, @dirs) if @dirs;
+
+ if (@tmpfiles) {
+ autoscript($package, 'postinst', 'postinst-init-tmpfiles', { 'TMPFILES' => join(' ', sort @tmpfiles) });
+ }
+ }
+}
+
+
+# Add postinst, prerm, and postrm code blocks to handle activation,
+# deactivation, start and stopping of services when the package is
+# installed, upgraded or removed.
+foreach my $package (@{$dh{DOPACKAGES}}) {
+ my $tmpdir = tmpdir($package);
+ my (@args, @start_units, @enable_units, %aliases);
+
+ my @installed_units = list_installed_units($tmpdir, \%aliases);
+
+ # Handle either only the unit files which were passed as arguments
+ # or all unit files that are installed in this package.
+ if (@ARGV) {
+ @args = @ARGV;
+ }
+ elsif ($dh{NAME}) {
+ # Treat --name option as if the corresponding unit names were
+ # passed in the command line.
+ @args = grep /(^|\/)$dh{NAME}\.(mount|path|service|socket|target|timer)$/, @installed_units;
+ }
+ else {
+ @args = @installed_units;
+ }
+
+ # Support excluding units via the -X debhelper common option.
+ foreach my $x (@{$dh{EXCLUDE}}) {
+ @args = grep !/(^|\/)$x$/, @args;
+ }
+
+ # This hash prevents us from looping forever in the following
+ # while loop. An actual real-world example of such a loop is
+ # systemd's systemd-readahead-drop.service, which contains
+ # Also=systemd-readahead-collect.service, and that file in turn
+ # contains Also=systemd-readahead-drop.service, thus forming an
+ # endless loop.
+ my %seen;
+
+ # Must use while and shift because the loop alters the list.
+ while (@args) {
+ my $unit = shift @args;
+ my $path = "${tmpdir}/usr/lib/systemd/system/${unit}";
+ unless (-f $path) {
+ $path = "${tmpdir}/lib/systemd/system/${unit}";
+ error("Package '$package' does not install unit '$unit'.") unless (-f $path);
+ }
+
+ # Skip template service files. Enabling, disabling, starting
+ # or stopping those services without specifying the instance
+ # is not useful.
+ next if ($unit =~ /\@/);
+
+ # Handle all unit files specified via Also= explicitly. This
+ # is not necessary for enabling, but for disabling, as we
+ # cannot read the unit file when disabling as it has already
+ # been deleted. The undocumented --no-also option disables
+ # handling of units linked via Also=. This option is provided
+ # only to suport a very specific use case in network-manager.
+ unless ($dh{NO_ALSO}) {
+ push @args, $_ for grep { !$seen{$_}++ } extract_key($path, 'Also');
+ }
+
+ # Extract unit aliases.
+ push @{$aliases{$unit}}, $_ for extract_key($path, 'Alias');
+
+ # In compat 11 (and earlier), dh_installinit will handle services with
+ # a sysv-equivalent service. In compat 12, dh_installsystemd will
+ # take care of it.
+ if (not compat(11) or not grep { has_sysv_equivalent($tmpdir, $_) } ($unit, @{$aliases{$unit}})) {
+ push @start_units, $unit;
+ }
+
+ if (contains_install_section($path)) {
+ push @enable_units, $unit;
+ }
+ }
+
+ @enable_units = map { quote($_) } uniq sort @enable_units;
+ @start_units = map { quote($_) } uniq sort @start_units;
+
+ my %options = ('snippet-order' => 'service');
+
+ if (@enable_units) {
+ for my $unit (@enable_units) {
+ my $snippet = $dh{NO_ENABLE} ? 'postinst-systemd-dont-enable' : 'postinst-systemd-enable';
+ autoscript($package, 'postinst', $snippet, { 'UNITFILE' => $unit }, \%options);
+ }
+ autoscript($package, 'postrm', 'postrm-systemd', {'UNITFILES' => join(' ', @enable_units) });
+ }
+
+ if (@start_units) {
+ my $replace = { 'UNITFILES' => join(' ', @start_units) };
+
+ if ($dh{RESTART_AFTER_UPGRADE}) {
+ my $snippet;
+ if ($dh{NO_START}) {
+ $snippet = 'postinst-systemd-restartnostart';
+ $replace->{RESTART_ACTION} = 'try-restart';
+ } else {
+ $snippet = 'postinst-systemd-restart';
+ $replace->{RESTART_ACTION} = 'restart';
+ }
+ autoscript($package, 'postinst', $snippet, $replace, \%options);
+ } elsif (!$dh{NO_START}) {
+ # (stop|start) service (before|after) upgrade
+ autoscript($package, 'postinst', 'postinst-systemd-start', $replace, \%options);
+ }
+
+ # stop service before upgrade, if requested
+ autoscript($package, 'preinst', 'preinst-systemd-stop', $replace, \%options)
+ unless ($dh{R_FLAG} || $dh{RESTART_AFTER_UPGRADE});
+
+ # stop service only on remove
+ autoscript($package, 'prerm', 'prerm-systemd-restart', $replace, \%options)
+ unless ($dh{NO_START});
+
+ # Run this with "default" order so it is always after other
+ # service related autosnippets.
+ autoscript($package, 'postrm', 'postrm-systemd-reload-only', $replace);
+ }
+}
+
+=head1 SEE ALSO
+
+L<debhelper(7)>, L<dh_installinit(1)>, L<deb-systemd-helper(1)>
+
+=head1 AUTHORS
+
+pkg-systemd-maintainers@lists.alioth.debian.org
+
+=cut
diff --git a/dh_installsystemduser b/dh_installsystemduser
new file mode 100755
index 0000000..27e96e5
--- /dev/null
+++ b/dh_installsystemduser
@@ -0,0 +1,288 @@
+#!/usr/bin/perl -w
+
+=head1 NAME
+
+dh_installsystemduser - install systemd unit files
+
+=cut
+
+use strict;
+use warnings;
+use Debian::Debhelper::Dh_Lib;
+
+our $VERSION = DH_BUILTIN_VERSION;
+
+=head1 SYNOPSIS
+
+B<dh_installsystemduser> [S<I<debhelper options>>] [B<--no-enable>] [B<--name=>I<name>] [S<I<unit file> ...>]
+
+=head1 DESCRIPTION
+
+B<dh_installsystemduser> finds the systemd user instance service files
+installed by a package and generates F<preinst>, F<postinst>, and F<prerm>
+code blocks for enabling, disabling, starting, stopping, and restarting the
+corresponding systemd user instance services, when the package is installed,
+updated, or removed. These snippets are added to the maintainer scripts by
+L<dh_installdeb(1)>.
+
+L<deb-systemd-helper(1)> is used to enable and disable the systemd
+units, thus it is not necessary that the machine actually runs systemd
+during package installation time, enabling happens on all machines.
+
+B<dh_installsystemduser> operates on all user instance unit files
+installed by a package. For only generating blocks for specific unit
+files, pass them as arguments. Specific unit files can be excluded
+from processing using the B<-X> common L<debhelper(1)> option.
+
+=head1 FILES
+
+=over 4
+
+=item debian/I<package>.user.path,
+ debian/I<package>@.user.path,
+ debian/I<package>.user.service,
+ debian/I<package>@.user.service,
+ debian/I<package>.user.socket,
+ debian/I<package>@.user.socket,
+ debian/I<package>.user.target,
+ debian/I<package>@.user.target,
+ debian/I<package>.user.timer,
+ debian/I<package>@.user.timer
+
+If any of those files exists, they are installed into
+F<usr/lib/systemd/user/> in the package build directory removing the
+F<.user> file name part.
+
+=back
+
+=head1 OPTIONS
+
+=over 4
+
+=item B<--name=>I<name>
+
+Install the service file as I<name.service> instead of the default
+filename I<package.service>. When this parameter is used,
+B<dh_installsystemd> looks for and installs files named
+F<debian/package.name.user.service> instead of the usual
+F<debian/package.user.service>. Moreover, maintainer scripts are only
+generated for units that match the given I<name>.
+
+=item B<--no-enable>
+
+Disable the service(s) on purge, but do not enable them on install.
+
+=back
+
+=head1 NOTES
+
+This command is not idempotent. L<dh_prep(1)> should be called between
+invocations of this command (with the same arguments). Otherwise, it
+may cause multiple instances of the same text to be added to
+maintainer scripts.
+
+=cut
+
+# PROMISE: DH NOOP WITHOUT internal(bug#950723) tmp(usr/lib/systemd/user) user.service user.target user.socket user.path user.timer
+
+init(options => {
+ "no-enable" => \$dh{NO_ENABLE},
+});
+
+sub quote {
+ # Add single quotes around the argument.
+ return '\'' . $_[0] . '\'';
+}
+
+sub uniq {
+ my %seen;
+ return grep { !$seen{$_}++ } @_;
+}
+
+sub contains_install_section {
+ my ($unit_path) = @_;
+
+ open(my $fh, '<', $unit_path) or error("Cannot open($unit_path) to check for [Install]: $!");
+
+ while (my $line = <$fh>) {
+ chomp($line);
+ return 1 if $line =~ /^\s*\[Install\]$/i;
+ }
+ close($fh);
+ return 0;
+}
+
+sub install_user_unit {
+ my ($package, $name, $suffix, $path) = @_;
+ my $unit = pkgfile($package, "user.$suffix");
+ return if $unit eq '';
+
+ install_dir($path);
+ install_file($unit, "$path/$name.$suffix");
+}
+
+# Extracts the directive values from a unit file. Handles repeated
+# directives in the same unit file. Assumes values can only be
+# composed of lists of unit names. This is good enough for the 'Also='
+# and 'Alias=' directives handled here.
+sub extract_key {
+ my ($unit_path, $key) = @_;
+ my @values;
+
+ open(my $fh, '<', $unit_path) or error("Cannot open($unit_path): $!");
+
+ while (my $line = <$fh>) {
+ chomp($line);
+
+ # Since unit names can't have whitespace in systemd, simply
+ # use split and strip any leading/trailing quotes. See
+ # systemd-escape(1) for examples of valid unit names.
+ if ($line =~ /^\s*$key=(.+)$/i) {
+ for my $value (split(/\s+/, $1)) {
+ $value =~ s/^(["'])(.*)\g1$/$2/;
+ push @values, $value;
+ }
+ }
+ }
+
+ close($fh);
+ return @values;
+}
+
+sub list_installed_user_units {
+ my ($tmpdir, $aliases) = @_;
+
+ my $lib_systemd_user = "$tmpdir/usr/lib/systemd/user";
+ my @installed;
+
+ return unless -d $lib_systemd_user;
+ opendir(my $dh, $lib_systemd_user) or error("Cannot opendir($lib_systemd_user): $!");
+
+ foreach my $name (readdir($dh)) {
+ my $path = "$lib_systemd_user/$name";
+ next unless -f $path;
+ if (-l $path) {
+ my $dest = basename(readlink($path));
+ $aliases->{$dest} //= [ ];
+ push @{$aliases->{$dest}}, $name;
+ } else {
+ push @installed, $name;
+ }
+ }
+
+ closedir($dh);
+ return @installed;
+}
+
+# Install package maintainer provided unit files.
+foreach my $package (@{$dh{DOPACKAGES}}) {
+ my $tmpdir = tmpdir($package);
+
+ # unit file name
+ my $name = $dh{NAME} // $package;
+
+ my $path = "$tmpdir/usr/lib/systemd/user";
+ for my $type (qw(service target socket path timer)) {
+ install_user_unit($package, $name, $type, $path);
+ install_user_unit("${package}@", "${name}@", $type, $path);
+ }
+}
+
+# Generate postinst and prerm code blocks to enable and disable units
+foreach my $package (@{$dh{DOPACKAGES}}) {
+ my (@args, @start_units, @enable_units, %aliases);
+
+ my $tmpdir = tmpdir($package);
+ my @units = list_installed_user_units($tmpdir, \%aliases);
+
+ # Handle either only the unit files which were passed as arguments
+ # or all unit files that are installed in this package.
+ if (@ARGV) {
+ @args = @ARGV;
+ }
+ elsif ($dh{NAME}) {
+ # Treat --name flag as if the corresponding units were passed
+ # in the command line.
+ @args = grep /(^|\/)$dh{NAME}\.(service|target|socket|path|timer)$/, @units;
+ }
+ else {
+ @args = @units;
+ }
+
+ # Support excluding units via the -X debhelper common option.
+ foreach my $x (@{$dh{EXCLUDE}}) {
+ @args = grep !/(^|\/)$x$/, @args;
+ }
+
+ # This hash prevents us from looping forever in the following
+ # while loop. An actual real-world example of such a loop is
+ # systemd's systemd-readahead-drop.service, which contains
+ # Also=systemd-readahead-collect.service, and that file in turn
+ # contains Also=systemd-readahead-drop.service, thus forming an
+ # endless loop.
+ my %seen;
+
+ # Must use while and shift because the loop alters the list.
+ while (@args) {
+ my $name = shift @args;
+ my $path = "${tmpdir}/usr/lib/systemd/user/${name}";
+
+ error("User unit file \"$name\" not found in package \"$package\".") if ! -f $path;
+
+ # Skip template service files. Enabling or disabling those
+ # services without specifying the instance is not useful.
+ next if ($name =~ /\@/);
+
+ # Handle all unit files specified via Also= explicitly. This
+ # is not necessary for enabling, but for disabling, as we
+ # cannot read the unit file when disabling as it has already
+ # been deleted.
+ push @args, $_ for grep { !$seen{$_}++ } extract_key($path, 'Also');
+
+ push @enable_units, $name if contains_install_section($path);
+ push @start_units, $name;
+ }
+
+ @enable_units = map { quote($_) } sort(uniq(@enable_units));
+ @start_units = map { quote($_) } sort(uniq(@start_units));
+
+ if (@enable_units) {
+ # The generated maintainer script code blocks use the --user
+ # option that was added to deb-systemd-helper in version 1.52.
+ addsubstvar($package, 'misc:Depends', 'init-system-helpers', ">= 1.52");
+
+ my $postinst = $dh{NO_ENABLE} ? 'postinst-systemd-user-dont-enable' : 'postinst-systemd-user-enable';
+ foreach my $unit (@enable_units) {
+ autoscript($package, 'postinst', $postinst, { 'UNITFILE' => $unit });
+ }
+ autoscript($package, 'postrm', 'postrm-systemd-user', { 'UNITFILES' => join(' ', @enable_units) });
+ }
+
+ if (@start_units and not compat(13)) {
+ # The generated maintainer script code blocks use the --user
+ # option that was added to deb-systemd-invoke in version 1.61 and fixed in 1.66 to really daemon-reload.
+ addsubstvar($package, 'misc:Depends', 'init-system-helpers', ">= 1.66~");
+
+ my %options = ('snippet-order' => 'service');
+
+ # restart service after install/upgrade
+ autoscript($package, 'postinst', 'postinst-systemd-user-restart', { 'UNITFILES' => join(' ', @start_units) }, \%options);
+
+ # stop service after removal
+ autoscript($package, 'prerm', 'prerm-systemd-user-stop', { 'UNITFILES' => join(' ', @start_units) }, \%options);
+
+ # Run this with "default" order so it is always after other
+ # service related autosnippets.
+ autoscript($package, 'postrm', 'postrm-systemd-user-reload-only', { 'UNITFILES' => join(' ', @start_units) });
+ }
+}
+
+=head1 SEE ALSO
+
+L<debhelper(7)>, L<dh_installsystemd(1)>, L<deb-systemd-helper(1)>
+
+=head1 AUTHORS
+
+pkg-systemd-maintainers@lists.alioth.debian.org
+
+=cut
diff --git a/dh_installsysusers b/dh_installsysusers
new file mode 100755
index 0000000..faa4b8e
--- /dev/null
+++ b/dh_installsysusers
@@ -0,0 +1,115 @@
+#!/usr/bin/perl -w
+
+=head1 NAME
+
+dh_installsysusers - install and integrates systemd sysusers files
+
+=cut
+
+use strict;
+use warnings;
+use Debian::Debhelper::Dh_Lib;
+
+our $VERSION = DH_BUILTIN_VERSION;
+
+=head1 SYNOPSIS
+
+B<dh_installsysusers> [S<I<debhelper options>>]
+
+=head1 DESCRIPTION
+
+B<dh_installsysusers> is a debhelper program that is responsible for
+installing package maintainer supplied systemd sysusers files.
+
+It also finds the systemd sysusers files installed in a package and
+generates relevant integration snippets for enabling the users on
+installation. These snippets are added to the package by
+L<dh_installdeb(1)>.
+
+=head1 FILES
+
+=over 4
+
+=item debian/I<package>.sysusers
+
+If the file exist, it will be installed as
+F<< /usr/lib/sysusers.d/I<package>.conf >>.
+
+=back
+
+=head1 OPTIONS
+
+=over 4
+
+=item B<--name=>I<name>
+
+When this parameter is used, B<dh_installsysusers> looks for and
+installs files named debian/I<package>.I<name>.sysusers instead
+of the usual debian/I<package>.sysusers.
+
+Furthermore, the file is installed as F<< /usr/lib/sysusers.d/I<name>.conf >>
+rather than F<< /usr/lib/sysusers.d/I<package>.conf >>.
+
+=back
+
+=head1 NOTES
+
+This command is not idempotent. L<dh_prep(1)> should be called between
+invocations of this command (with the same arguments). Otherwise, it
+may cause multiple instances of the same text to be added to
+maintainer scripts.
+
+=cut
+
+init();
+
+
+# PROMISE: DH NOOP WITHOUT pkgfile(sysusers) tmp(usr/lib/sysusers.d) cli-options()
+
+
+foreach my $package (@{$dh{DOPACKAGES}}) {
+ my $tmpdir = tmpdir($package);
+ my $sysusers = pkgfile($package,"sysusers");
+ my $sysusers_targetdir = "${tmpdir}/usr/lib/sysusers.d";
+ my $target = $dh{NAME} // $package;
+ my $typoed_name = pkgfile($package, "sysuser");
+
+ if ($sysusers eq '' and $typoed_name ne '') {
+ # Warn people in case they typo this as much as I did.
+ my $correct_name = $typoed_name;
+ $correct_name =~ s{^(?:.*[./])\Ksysuser}{sysusers};
+ warning("Possible typo in ${typoed_name} (expected ${correct_name}): File has been ignored");
+ }
+
+ if ($sysusers ne '') {
+ install_dir($sysusers_targetdir);
+ install_file($sysusers, "${sysusers_targetdir}/${target}.conf");
+ }
+
+ if (! $dh{NOSCRIPTS} && ($sysusers ne '' || -d $sysusers_targetdir)) {
+ my @sysusers_files;
+ opendir(my $dir_fd, $sysusers_targetdir) or error("opendir(${sysusers_targetdir}) failed: $!");
+ while (defined(my $entry = readdir($dir_fd))) {
+ next if $entry eq '.' or $entry eq '..' or $entry !~ m{[.]conf$};
+ push @sysusers_files, $entry;
+ }
+ closedir($dir_fd);
+
+ next if @sysusers_files == 0;
+
+ # Sort list of files so postinst content doesn't change if readdir's output is not stable
+ @sysusers_files = sort @sysusers_files;
+ # Generate a single systemd-sysusers invocation and just pass all detected files together
+ autoscript($package, 'postinst', 'postinst-sysusers', { 'CONFILE_BASENAME' => "@sysusers_files" });
+ addsubstvar($package, "misc:Depends", "systemd | systemd-standalone-sysusers | systemd-sysusers");
+ }
+
+}
+
+
+
+=head1 SEE ALSO
+
+L<debhelper(7)>
+
+=cut
diff --git a/dh_installtmpfiles b/dh_installtmpfiles
new file mode 100755
index 0000000..aa5ab72
--- /dev/null
+++ b/dh_installtmpfiles
@@ -0,0 +1,128 @@
+#!/usr/bin/perl -w
+
+=head1 NAME
+
+dh_installtmpfiles - install tmpfiles.d configuration files
+
+=cut
+
+use strict;
+use warnings;
+use Debian::Debhelper::Dh_Lib;
+use File::Find;
+
+our $VERSION = DH_BUILTIN_VERSION;
+
+=head1 SYNOPSIS
+
+B<dh_installtmpfiles> [S<I<debhelper options>>][B<--name=>I<name>]
+
+=head1 DESCRIPTION
+
+B<dh_installtmpfiles> is a debhelper program that is responsible for
+installing package maintainer supplied tmpfiles.d configuration files
+(e.g. for systemd-tmpfiles).
+
+It also finds the tmpfiles.d configuration files installed by a package
+and generates F<postinst> code blocks for activating the tmpfiles.d
+configuration when the package is installed. These snippets are added
+to the maintainer scripts by L<dh_installdeb(1)>.
+
+
+=head1 OPTIONS
+
+=over 4
+
+=item B<--name=>I<name>
+
+This option controls both a prefix used for lookng up maintainer provided
+tmpfiles.d configuration files (those mentioned in the L</FILES> section)
+and also the base name used for the installed version of the file.
+
+=back
+
+=head1 FILES
+
+=over 4
+
+=item debian/I<package>.tmpfiles
+
+If this exists, it is installed into F<usr/lib/tmpfiles.d/> in the
+package build directory. Note that the C<tmpfiles.d> mechanism is
+currently only used by systemd.
+
+=item debian/I<package>.tmpfile
+
+Deprecated name for debian/I<package>.tmpfiles.
+
+=back
+
+=head1 NOTES
+
+This command is not idempotent. L<dh_prep(1)> should be called between
+invocations of this command (with the same arguments). Otherwise, it
+may cause multiple instances of the same text to be added to
+maintainer scripts.
+
+=cut
+
+init();
+
+sub uniq {
+ my %seen;
+ return grep { !$seen{$_}++ } @_;
+}
+
+# PROMISE: DH NOOP WITHOUT tmp(usr/lib/tmpfiles.d) tmp(etc/tmpfiles.d) pkgfile(tmpfiles) pkgfile(tmpfile) cli-options()
+
+# Install package maintainer supplied tmpfiles files
+foreach my $package (@{$dh{DOPACKAGES}}) {
+ my $tmpdir = tmpdir($package);
+ my $tmpfile = pkgfile($package, 'tmpfiles');
+ my $name = $dh{NAME} // $package;
+ my $old_tmpfile = pkgfile($package, 'tmpfile');
+ my $dir;
+ if (not $tmpfile) {
+ my $new_name;
+ next if not $old_tmpfile;
+ $tmpfile = $old_tmpfile;
+ $new_name = $old_tmpfile;
+ $new_name =~ s{^(.+[./])tmpfile(\..+|)$}{$1tmpfiles$2};
+ warning("The name $tmpfile is deprecated; please use $new_name instead");
+ warning(qq{Possible fix: mv -f "${tmpfile}" "${new_name}"});
+ } elsif ($old_tmpfile) {
+ warning("There is both a $tmpfile and a $old_tmpfile that is relevant for this package!?");
+ warning(qq{Possible fix: rm -f "${old_tmpfile}"});
+ error("Aborting; Please resolve the ambiguity between ${tmpfile} and ${old_tmpfile}.");
+ }
+
+ $dir = "$tmpdir/usr/lib/tmpfiles.d";
+ install_dir($dir);
+ install_file($tmpfile, "${dir}/${name}.conf");
+}
+
+# Add postinst code blocks to handle tmpfiles
+foreach my $package (@{$dh{DOPACKAGES}}) {
+ my $tmpdir = tmpdir($package);
+ my @tmpfiles;
+
+ my @dirs = grep { -d } map { "${tmpdir}/$_" } qw(usr/lib/tmpfiles.d etc/tmpfiles.d);
+
+ find({
+ wanted => sub {
+ my $name = $File::Find::name;
+ return if not -f $name or not $name =~ m{[.]conf$};
+ push(@tmpfiles, basename($name)); },
+ no_chdir => 1,
+ }, @dirs) if @dirs;
+
+ if (@tmpfiles) {
+ autoscript($package, 'postinst', 'postinst-init-tmpfiles', { 'TMPFILES' => join(' ', uniq(sort(@tmpfiles))) });
+ }
+}
+
+=head1 SEE ALSO
+
+L<debhelper(7)>
+
+=cut
diff --git a/dh_installudev b/dh_installudev
new file mode 100755
index 0000000..051a9af
--- /dev/null
+++ b/dh_installudev
@@ -0,0 +1,112 @@
+#!/usr/bin/perl
+
+=head1 NAME
+
+dh_installudev - install udev rules files
+
+=cut
+
+use strict;
+use warnings;
+use Debian::Debhelper::Dh_Lib;
+
+our $VERSION = DH_BUILTIN_VERSION;
+
+=head1 SYNOPSIS
+
+B<dh_installudev> [S<I<debhelper options>>] [B<-n>] [B<--name=>I<name>] [B<--priority=>I<priority>]
+
+=head1 DESCRIPTION
+
+B<dh_installudev> is a debhelper program that is responsible for
+installing B<udev> rules files.
+
+=head1 FILES
+
+=over 4
+
+=item debian/I<package>.udev
+
+Installed into F<usr/lib/udev/rules.d/> in the package build directory.
+
+=back
+
+=head1 OPTIONS
+
+=over 4
+
+=item B<--name=>I<name>
+
+When this parameter is used, B<dh_installudev> looks for and
+installs files named debian/I<package>.I<name>.udev instead of the usual
+debian/I<package>.udev.
+
+=item B<--priority=>I<priority>
+
+Sets the priority the file. Default is 60.
+
+=back
+
+=head1 NOTES
+
+Note that this command is not idempotent. L<dh_prep(1)> should be called
+between invocations of this command. Otherwise, it may cause multiple
+instances of the same text to be added to maintainer scripts.
+
+=cut
+
+init(options => {
+ "priority=s" => \$dh{PRIORITY},
+});
+
+# The priority used to look like z60_;
+# we need to calculate that old value to handle
+# conffile moves correctly.
+my $old_priority=$dh{PRIORITY};
+
+# In case a caller still uses the `z` prefix, remove it.
+if (defined $dh{PRIORITY}) {
+ $dh{PRIORITY}=~s/^z//;
+}
+
+if (! defined $dh{PRIORITY}) {
+ $dh{PRIORITY}="60";
+ $old_priority="z60";
+}
+if ($dh{PRIORITY}) {
+ $dh{PRIORITY}.="-";
+ $old_priority.="_";
+}
+
+# PROMISE: DH NOOP WITHOUT udev cli-options()
+
+foreach my $package (@{$dh{DOPACKAGES}}) {
+ my $tmp=tmpdir($package);
+ my $rules_file=pkgfile($package,"udev");
+ my $filename=basename($rules_file);
+ if ($filename eq 'udev') {
+ $filename = "$package.udev";
+ }
+ $filename=~s/\.udev$/.rules/;
+ if (defined $dh{NAME}) {
+ $filename="$dh{NAME}.rules";
+ }
+
+ if ($rules_file) {
+ my $rule="/usr/lib/udev/rules.d/$dh{PRIORITY}$filename";
+ install_dir("$tmp/usr/lib/udev/rules.d");
+ install_file($rules_file, "${tmp}${rule}");
+ }
+}
+
+=head1 SEE ALSO
+
+L<debhelper(7)>
+
+This program is a part of debhelper.
+
+=head1 AUTHOR
+
+Joey Hess <joeyh@debian.org>
+
+=cut
diff --git a/dh_installwm b/dh_installwm
new file mode 100755
index 0000000..d5e0599
--- /dev/null
+++ b/dh_installwm
@@ -0,0 +1,140 @@
+#!/usr/bin/perl
+
+=head1 NAME
+
+dh_installwm - register a window manager
+
+=cut
+
+use strict;
+use warnings;
+use Debian::Debhelper::Dh_Lib;
+
+our $VERSION = DH_BUILTIN_VERSION;
+
+=head1 SYNOPSIS
+
+B<dh_installwm> [S<I<debhelper options>>] [B<-n>] [B<--priority=>I<n>] [S<I<wm> ...>]
+
+=head1 DESCRIPTION
+
+B<dh_installwm> is a debhelper program that is responsible for
+generating the F<postinst> and F<prerm> commands that register a window manager
+with L<update-alternatives(8)>. The window manager's man page is also
+registered as a slave symlink (in v6 mode and up). It must be installed in
+F<usr/share/man/man1/> in the package build directory prior to calling
+B<dh_installwm>. In compat 9 and earlier, the manpage was optional.
+
+=head1 FILES
+
+=over 4
+
+=item debian/I<package>.wm
+
+List window manager programs to register.
+
+Supports substitution variables in compat 13 and later as
+documented in L<debhelper(7)>.
+
+=back
+
+=head1 OPTIONS
+
+=over 4
+
+=item B<--priority=>I<n>
+
+Set the priority of the window manager. Default is 20, which is too low for
+most window managers; see the Debian Policy document for instructions on
+calculating the correct value.
+
+=item B<-n>, B<--no-scripts>
+
+Do not modify F<postinst>/F<prerm> scripts. Turns this command into a no-op.
+
+=item B<-A>, B<--all>
+
+Modify scripts for window managers specified by command line
+parameters in ALL packages acted on, not just the first.
+
+=item I<wm> ...
+
+Window manager programs to register.
+
+=back
+
+=head1 NOTES
+
+Note that this command is not idempotent. L<dh_prep(1)> should be called
+between invocations of this command. Otherwise, it may cause multiple
+instances of the same text to be added to maintainer scripts.
+
+=cut
+
+init(options => {
+ "priority=s" => \$dh{PRIORITY},
+});
+
+if (! defined $dh{PRIORITY}) {
+ $dh{PRIORITY}=20;
+}
+
+if (@ARGV) {
+ # This is here for backwards compatibility. If the filename doesn't
+ # include a path, assume it's in /usr/bin.
+ if ($ARGV[0] !~ m:/:) {
+ $ARGV[0]="/usr/bin/$ARGV[0]";
+ }
+}
+
+my $nodocs = is_build_profile_active('nodoc') || get_buildoption('nodoc') ? 1 : 0;
+
+# PROMISE: DH NOOP WITHOUT wm cli-options()
+
+foreach my $package (@{$dh{DOPACKAGES}}) {
+ my $tmp=tmpdir($package);
+ my $file=pkgfile($package,"wm");
+
+ my @wm;
+ if ($file) {
+ @wm=filearray($file, '.');
+ }
+
+ if (($package eq $dh{FIRSTPACKAGE} || $dh{PARAMS_ALL}) && @ARGV) {
+ push @wm, @ARGV;
+ }
+
+ if (! $dh{NOSCRIPTS}) {
+WM: foreach my $wm (@wm) {
+ autoscript($package,"prerm","prerm-wm", { 'WM' => $wm });
+
+ my $wmman;
+ foreach my $ext (".1", ".1x") {
+ $wmman="/usr/share/man/man1/".basename($wm).$ext;
+ if (-e "$tmp$wmman" || -e "$tmp$wmman.gz") {
+ autoscript($package,"postinst","postinst-wm", { 'WM' => $wm, 'WMMAN' => "${wmman}.gz" , 'PRIORITY' => $dh{PRIORITY} });
+ next WM;
+ }
+ }
+ if (not compat(9) and not $nodocs) {
+ error("no manpage found (creating an x-window-manager alternative requires a slave symlink for the manpage)");
+ } else {
+ warning("no manpage found (creating an x-window-manager alternative requires a slave symlink for the manpage)");
+ }
+ # Reaching this code means a broken package will be produced.
+ autoscript($package,"postinst","postinst-wm-noman", { 'WM' => $wm, 'PRIORITY' => $dh{PRIORITY} });
+ }
+ }
+}
+
+=head1 SEE ALSO
+
+L<debhelper(7)>
+
+This program is a part of debhelper.
+
+=head1 AUTHOR
+
+Joey Hess <joeyh@debian.org>
+
+=cut
diff --git a/dh_installxfonts b/dh_installxfonts
new file mode 100755
index 0000000..c16659f
--- /dev/null
+++ b/dh_installxfonts
@@ -0,0 +1,100 @@
+#!/usr/bin/perl
+
+=head1 NAME
+
+dh_installxfonts - register X fonts
+
+=cut
+
+use strict;
+use warnings;
+use Debian::Debhelper::Dh_Lib;
+
+our $VERSION = DH_BUILTIN_VERSION;
+
+=head1 SYNOPSIS
+
+B<dh_installxfonts> [S<I<debhelper options>>]
+
+=head1 DESCRIPTION
+
+B<dh_installxfonts> is a debhelper program that is responsible for
+registering X fonts, so their corresponding F<fonts.dir>, F<fonts.alias>,
+and F<fonts.scale> be rebuilt properly at install time.
+
+Before calling this program, you should have installed any X fonts provided
+by your package into the appropriate location in the package build
+directory, and if you have F<fonts.alias> or F<fonts.scale> files, you should
+install them into the correct location under F<etc/X11/fonts> in your
+package build directory.
+
+Your package should depend on B<xfonts-utils> so that the
+B<update-fonts->I<*> commands are available. (This program adds that dependency to
+B<${misc:Depends}>.)
+
+This program automatically generates the F<postinst> and F<postrm> commands needed
+to register X fonts. These commands are inserted into the maintainer
+scripts by B<dh_installdeb>. See L<dh_installdeb(1)> for an explanation of how
+this works.
+
+=head1 NOTES
+
+See L<update-fonts-alias(8)>, L<update-fonts-scale(8)>, and
+L<update-fonts-dir(8)> for more information about X font installation.
+
+See Debian policy, section 11.8.5. for details about doing fonts the Debian
+way.
+
+=cut
+
+init();
+
+# PROMISE: DH NOOP WITHOUT tmp(usr/share/fonts/X11) cli-options()
+
+foreach my $package (@{$dh{DOPACKAGES}}) {
+ my $tmp=tmpdir($package);
+
+ # Find all font directories in the package build directory.
+ my @fontdirs;
+ foreach my $parentdir ("$tmp/usr/share/fonts/X11/") {
+ opendir(DIR, $parentdir) || next;
+ @fontdirs = grep { -d "$parentdir/$_" && !/^\./ } (readdir DIR);
+ closedir DIR;
+ }
+
+ if (@fontdirs) {
+ # Figure out what commands the postinst and postrm will need
+ # to call.
+ my (@cmds, @cmds_postinst, @cmds_postrm);
+ # Sort items for reproducible binary package contents.
+ foreach my $f (sort @fontdirs) {
+ # This must come before update-fonts-dir.
+ push @cmds, "update-fonts-scale $f"
+ if -f "$tmp/etc/X11/fonts/$f/$package.scale";
+ push @cmds, "update-fonts-dir --x11r7-layout $f";
+ if (-f "$tmp/etc/X11/fonts/$f/$package.alias") {
+ push @cmds_postinst, "update-fonts-alias --include /etc/X11/fonts/$f/$package.alias $f";
+ push @cmds_postrm, "update-fonts-alias --exclude /etc/X11/fonts/$f/$package.alias $f";
+ }
+ }
+
+ autoscript($package, "postinst", "postinst-xfonts",
+ { 'CMDS' => join(";", @cmds, @cmds_postinst) });
+ autoscript($package, "postrm", "postrm-xfonts",
+ { 'CMDS' => join(";", @cmds, @cmds_postrm) });
+
+ addsubstvar($package, "misc:Depends", "xfonts-utils");
+ }
+}
+
+=head1 SEE ALSO
+
+L<debhelper(7)>
+
+This program is a part of debhelper.
+
+=head1 AUTHOR
+
+Joey Hess <joeyh@debian.org>
+
+=cut
diff --git a/dh_link b/dh_link
new file mode 100755
index 0000000..a0e6517
--- /dev/null
+++ b/dh_link
@@ -0,0 +1,178 @@
+#!/usr/bin/perl
+
+=head1 NAME
+
+dh_link - create symlinks in package build directories
+
+=cut
+
+use strict;
+use warnings;
+use File::Find;
+use Debian::Debhelper::Dh_Lib;
+
+our $VERSION = DH_BUILTIN_VERSION;
+
+=head1 SYNOPSIS
+
+B<dh_link> [S<I<debhelper options>>] [B<-A>] [B<-X>I<item>] [S<I<source destination> ...>]
+
+=head1 DESCRIPTION
+
+B<dh_link> is a debhelper program that creates symlinks in package build
+directories.
+
+B<dh_link> accepts a list of pairs of source and destination
+files. The source files are the already existing files that will be
+symlinked from (called B<target> by L<ln(1)>). The destination files
+are the symlinks that will be created (called B<link name> by
+L<ln(1)>). There B<must> be an equal number of source and destination
+files specified.
+
+Be sure you B<do> specify the absolute path to both the source and
+destination files (unlike you would do if you were using something
+like L<ln(1)>). Please note that the leading slash is optional.
+
+B<dh_link> will generate symlinks that comply with Debian policy - absolute
+when policy says they should be absolute, and relative links with as short
+a path as possible. It will also create any subdirectories it needs to put
+the symlinks in.
+
+Any pre-existing destination files will be replaced with symlinks.
+
+B<dh_link> also scans the package build tree for existing symlinks which do not
+conform to Debian policy, and corrects them (v4 or later).
+
+=head1 FILES
+
+=over 4
+
+=item debian/I<package>.links
+
+Lists pairs of source and destination files to be symlinked. Each pair
+should be put on its own line, with the source and destination separated by
+whitespace.
+
+In each pair the source file (called B<target> by L<ln(1)>) comes
+first and is followed by the destination file (called B<link name> by
+L<ln(1)>). Thus the pairs of source and destination files in each line
+are given in the same order as they would be given to L<ln(1)>.
+
+In contrast to L<ln(1)>, source and destination paths must be absolute
+(the leading slash is optional).
+
+Supports substitution variables in compat 13 and later as
+documented in L<debhelper(7)>.
+
+=back
+
+=head1 OPTIONS
+
+=over 4
+
+=item B<-A>, B<--all>
+
+Create any links specified by command line parameters in ALL packages
+acted on, not just the first.
+
+=item B<-X>I<item>, B<--exclude=>I<item>
+
+Exclude symlinks that contain I<item> anywhere in their filename from
+being corrected to comply with Debian policy.
+
+=item I<source destination> ...
+
+Create a file named I<destination> as a link to a file named I<source>. Do
+this in the package build directory of the first package acted on.
+(Or in all packages if B<-A> is specified.)
+
+=back
+
+=head1 EXAMPLES
+
+ dh_link usr/share/man/man1/foo.1 usr/share/man/man1/bar.1
+
+Make F<bar.1> be a symlink to F<foo.1>
+
+ dh_link var/lib/foo usr/lib/foo \
+ usr/share/man/man1/foo.1 usr/share/man/man1/bar.1
+
+Make F</usr/lib/foo/> be a link to F</var/lib/foo/>, and F<bar.1> be a symlink to
+the F<foo.1>
+
+ var/lib/foo usr/lib/foo
+ usr/share/man/man1/foo.1 usr/share/man/man1/bar.1
+
+Same as above but as content for a debian/I<package>.links file.
+
+=cut
+
+init();
+
+# INTROSPECTABLE: CONFIG-FILES pkgfile(links)
+
+foreach my $package (@{$dh{DOPACKAGES}}) {
+ my $tmp=tmpdir($package);
+ my $file=pkgfile($package,"links");
+
+ my @links;
+ if ($file) {
+ @links=filearray($file);
+ }
+
+ # Make sure it has pairs of symlinks and destinations. If it
+ # doesn't, $#links will be _odd_ (not even, -- it's zero-based).
+ if (int($#links/2) eq $#links/2) {
+ error("$file lists a link without a destination.");
+ }
+
+ if (($package eq $dh{FIRSTPACKAGE} || $dh{PARAMS_ALL}) && @ARGV) {
+ push @links, @ARGV;
+ }
+
+ # Same test as above, including arguments this time.
+ if (int($#links/2) eq $#links/2) {
+ error("parameters list a link without a destination.");
+ }
+
+ # If there is a temp dir already
+ if (-e $tmp) {
+ # Scan for existing links and add them to @links, so they
+ # are recreated policy conformant.
+ find(
+ sub {
+ return unless -l;
+ return if excludefile($_);
+ my $dir=$File::Find::dir;
+ $dir=~s/^\Q$tmp\E//;
+ my $target = readlink($_);
+ if ($target=~/^\//) {
+ push @links, $target;
+ }
+ else {
+ push @links, "$dir/$target";
+ }
+ push @links, "$dir/$_";
+
+ },
+ $tmp);
+ }
+
+ while (@links) {
+ my $dest=pop @links;
+ my $src=pop @links;
+ make_symlink($dest, $src, $tmp);
+ }
+}
+
+=head1 SEE ALSO
+
+L<debhelper(7)>
+
+This program is a part of debhelper.
+
+=head1 AUTHOR
+
+Joey Hess <joeyh@debian.org>
+
+=cut
diff --git a/dh_lintian b/dh_lintian
new file mode 100755
index 0000000..9fb2ed4
--- /dev/null
+++ b/dh_lintian
@@ -0,0 +1,72 @@
+#!/usr/bin/perl
+
+=head1 NAME
+
+dh_lintian - install lintian override files into package build directories
+
+=cut
+
+use strict;
+use warnings;
+use Debian::Debhelper::Dh_Lib;
+
+our $VERSION = DH_BUILTIN_VERSION;
+
+=head1 SYNOPSIS
+
+B<dh_lintian> [S<I<debhelper options>>]
+
+=head1 DESCRIPTION
+
+B<dh_lintian> is a debhelper program that is responsible for installing
+override files used by lintian into package build directories.
+
+=head1 FILES
+
+=over 4
+
+=item debian/I<package>.lintian-overrides
+
+Installed into usr/share/lintian/overrides/I<package> in the package
+build directory. This file is used to suppress erroneous lintian
+diagnostics.
+
+=item F<debian/source/lintian-overrides>
+
+These files are not installed, but will be scanned by lintian to provide
+overrides for the source package.
+
+=back
+
+=cut
+
+init();
+
+# PROMISE: DH NOOP WITHOUT lintian-overrides cli-options()
+
+foreach my $package (@{$dh{DOPACKAGES}}) {
+ next if is_udeb($package);
+
+ my $tmp=tmpdir($package);
+ my $or_dir = "$tmp/usr/share/lintian/overrides";
+ my $overrides=pkgfile($package,"lintian-overrides");
+
+ if ($overrides ne '') {
+ install_dir($or_dir);
+ install_dh_config_file($overrides, "$or_dir/$package");
+ }
+}
+
+=head1 SEE ALSO
+
+L<debhelper(1)>
+
+This program is a part of debhelper.
+
+L<lintian(1)>
+
+=head1 AUTHOR
+
+Steve Robbins <smr@debian.org>
+
+=cut
diff --git a/dh_listpackages b/dh_listpackages
new file mode 100755
index 0000000..e43e2cd
--- /dev/null
+++ b/dh_listpackages
@@ -0,0 +1,44 @@
+#!/usr/bin/perl
+
+=head1 NAME
+
+dh_listpackages - list binary packages debhelper will act on
+
+=cut
+
+use strict;
+use warnings;
+use Debian::Debhelper::Dh_Lib;
+
+our $VERSION = DH_BUILTIN_VERSION;
+
+=head1 SYNOPSIS
+
+B<dh_listpackages> [S<I<debhelper options>>]
+
+=head1 DESCRIPTION
+
+B<dh_listpackages> is a debhelper program that outputs a list of all binary
+packages debhelper commands will act on. If you pass it some options, it
+will change the list to match the packages other debhelper commands would
+act on if passed the same options.
+
+Packages are listed in the order they appear in F<debian/control>.
+
+=cut
+
+$dh{BLOCK_NOOP_WARNINGS}=1;
+init(inhibit_log => 1);
+print join("\n",@{$dh{DOPACKAGES}})."\n";
+
+=head1 SEE ALSO
+
+L<debhelper(7)>
+
+This program is a part of debhelper.
+
+=head1 AUTHOR
+
+Joey Hess <joeyh@debian.org>
+
+=cut
diff --git a/dh_makeshlibs b/dh_makeshlibs
new file mode 100755
index 0000000..32c634d
--- /dev/null
+++ b/dh_makeshlibs
@@ -0,0 +1,511 @@
+#!/usr/bin/perl
+
+=head1 NAME
+
+dh_makeshlibs - automatically create shlibs file and call dpkg-gensymbols
+
+=cut
+
+use strict;
+use warnings;
+use Debian::Debhelper::Dh_Lib;
+
+our $VERSION = DH_BUILTIN_VERSION;
+
+=head1 SYNOPSIS
+
+B<dh_makeshlibs> [S<I<debhelper options>>] [B<-m>I<major>] [B<-V>I<[dependencies]>] [B<-n>] [B<-X>I<item>] [S<B<--> I<params>>]
+
+=head1 DESCRIPTION
+
+B<dh_makeshlibs> is a debhelper program that automatically scans for shared
+libraries, and generates a shlibs file for the libraries it finds.
+
+It will also ensure that ldconfig is invoked during install and removal when
+it finds shared libraries. Since debhelper 9.20151004, this is done via a
+dpkg trigger. In older versions of debhelper, B<dh_makeshlibs> would
+generate a maintainer script for this purpose.
+
+Since debhelper 12.3, B<dh_makeshlibs> will by default add an additional
+I<udeb> line for udebs in the shlibs file, when the udeb has the same
+name as the deb followed by a "-udeb" suffix (e.g. if the deb is called
+"libfoo1", then debhelper will auto-detect the udeb if it is named
+"libfoo1-udeb"). Please use the B<--add-udeb> and B<--no-add-udeb> options
+below when this auto-detection is insufficient.
+
+If you previously used B<--add-udeb> and are considering to migrate to
+using the new auto-detection feature in 12.3, then
+please remember to test that the resulting F<DEBIAN/shlibs> files are
+as expected. There are some known corner cases, where the
+auto-detection is insufficient. These include when the udeb contains
+library files from multiple regular deb packages or when the packages
+do not follow the expected naming convention.
+
+=head1 FILES
+
+=over 4
+
+=item debian/I<package>.shlibs
+
+Installs this file, if present, into the package as DEBIAN/shlibs. If
+omitted, debhelper will generate a shlibs file automatically if it
+detects any libraries.
+
+Note in compat levels 9 and earlier, this file was installed by
+L<dh_installdeb(1)> rather than B<dh_makeshlibs>.
+
+=item debian/I<package>.symbols
+
+=item debian/I<package>.symbols.I<arch>
+
+These symbols files, if present, are passed to L<dpkg-gensymbols(1)> to
+be processed and installed. Use the I<arch> specific names if you need
+to provide different symbols files for different architectures.
+
+=back
+
+=head1 OPTIONS
+
+=over 4
+
+=item B<-m>I<major>, B<--major=>I<major>
+
+Instead of trying to guess the major number of the library with objdump,
+use the major number specified after the -m parameter. This is much less
+useful than it used to be, back in the bad old days when this program
+looked at library filenames rather than using objdump.
+
+=item B<-V>, B<-V>I<dependencies>
+
+=item B<--version-info>, B<--version-info=>I<dependencies>
+
+If a shlibs file is generated by this program, this option controls
+what version will be used in the dependency relation.
+
+In compat 12 and later, B<dh_makeshlibs> defaults to B<-VUpstream-Version>.
+In compat 11 and earlier the default behaved like B<-VNone>.
+
+The B<dh_makeshlibs> tool can generate dependencies in three variants:
+
+=over 4
+
+=item B<-VUpstream-Version>
+
+The dependency will be "I<packagename> B<(E<gt>>= I<packageversion>B<)>".
+Note that I<Upstream-Version> is case-sensitive and must be written
+exactly as shown here.
+
+This is a conservative setting that always ensures that other packages'
+shared library dependencies are at least as tight as they need to be
+(unless the library is prone to changing ABI without updating the
+upstream version number).
+
+The flip side is that packages might end up with dependencies that are
+too tight in some cases (note a symbols file can mitigate this issue).
+This is often of minor temporary inconvenience and usually a lot
+better than the fall out caused by forgetting to bump the dependency
+information.
+
+This explicit form was added in debhelper/11.3. In previous versions,
+a B<-V> without any dependency information was used instead (and that
+form still works)
+
+=item B<-VNone>
+
+The dependency will be "I<packagename>". Note that I<None> is
+case-sensitive and must be written exactly as shown here.
+
+This form is generally unsafe with the only exception being if upstream
+does not extend the ABI in any way. However, most upstreams improve their
+interfaces over time and packagers are recommended to use
+B<-VUpstream-Version> (or one of the other forms of B<-V>I<dependencies>).
+
+Alternatively, this may be sufficient if (and only if) the package uses
+symbol versioning (see L<dpkg-gensymbols(1)>) and does I<not> build any
+udeb packages. Note that symbols are not supported for udeb packages,
+which solely relies on shlibs for dependency handling.
+
+=item B<-V>I<package-relation>
+
+In this case, the value passed to B<-V> will be used as a dependency
+relation. The I<package-relation> should generally be of the form
+"I<some-package-name> B<(E<gt>>= I<some-package-version>B<)>". Remember
+to include the package name.
+
+Note that debhelper will use the value I<as it is> with no sanity
+checking or modification. In I<rare special> cases, this is needed to
+generate a dependency on a different package than the one containing
+the library.
+
+=back
+
+When choosing a value for this option, please keep mind that if the
+package provides a symbols file, then that this is generally preferred over
+the shlibs file for regular .deb packages. See L<dpkg-shlibdeps(1)>
+for more information on this topic.
+
+=item B<-n>, B<--no-scripts>
+
+Do not add the "ldconfig" trigger even if it seems like the package
+might need it. The option is called B<--no-scripts> for historical
+reasons as B<dh_makeshlibs> would previously generate maintainer
+scripts that called B<ldconfig>.
+
+=item B<-X>I<item>, B<--exclude=>I<item>
+
+Exclude files that contain I<item> anywhere in their filename or directory
+from being treated as shared libraries.
+
+=item B<--add-udeb=>I<udeb>
+
+Create an additional line for udebs in the shlibs file and use I<udeb> as the
+package name for udebs to depend on instead of the regular library package.
+
+This option is only useful for special cases such as when debhelper
+cannot auto-detect package name of the udeb package, when the udeb
+will contain libraries from multiple deb packages, or when the udeb
+contains libraries B<not> present in the deb package.
+
+=item B<--no-add-udeb>
+
+Do not add any udeb lines to the shlibs file. This can be used to disable the
+default auto-detection of udebs.
+
+This may be useful in case you do not want a shlibs file at all for the udeb
+because no package will depend on it. E.g. because adding a udeb package
+for the library was "overkill" and the library is embedded in a different
+udeb package.
+
+=item B<--> I<params>
+
+Pass I<params> to L<dpkg-gensymbols(1)>.
+
+=back
+
+=head1 EXAMPLES
+
+=over 4
+
+=item B<dh_makeshlibs -VNone>
+
+Assuming this is a package named F<libfoobar1>, generates a shlibs file that
+looks something like:
+ libfoobar 1 libfoobar1
+
+=item B<dh_makeshlibs -VUpstream-Version>
+
+Assuming the current version of the package is 1.1-3, generates a shlibs
+file that looks something like:
+ libfoobar 1 libfoobar1 (>= 1.1)
+
+=item B<dh_makeshlibs -V 'libfoobar1 (E<gt>= 1.0)'>
+
+Generates a shlibs file that looks something like:
+ libfoobar 1 libfoobar1 (>= 1.0)
+
+=back
+
+=cut
+
+my ($shlibs_udeb, %known_udeb_solibs);
+
+init(options => {
+ "m=s", => \$dh{M_PARAMS},
+ "major=s" => \$dh{M_PARAMS},
+ "version-info:s" => \$dh{V_FLAG},
+ "add-udeb=s" => \$shlibs_udeb,
+ "no-add-udeb" => sub { $shlibs_udeb = ''; },
+});
+
+my $ok=1;
+
+sub _all_so_files {
+ my ($package, $root_dir) = @_;
+ return if not -d $root_dir;
+ my (@all_so_files, @so_file_data);
+ my $objdump = cross_command($package, "objdump");
+ my $ma = package_multiarch($package);
+ my $ma_quoted = quotemeta($ma);
+ my $skip_dir_parent = qr{
+ /usr/lib(?:/${ma_quoted})?/?$
+ }x;
+ # Maybe this should be an allow list instead (#204975)
+ my $skip_dir_basename = qr{
+ jni
+ | python(?:\d+[.][^/]++)?
+ | perl(?:5|-base)?
+ | ruby(?:gems-integration)?
+ }x;
+
+ require File::Find;
+ File::Find::find(sub {
+ # Lazy loading of File::Find makes perl think that File::Find::dir is only used once
+ # and we might have typo'ed something
+ no warnings qw(once);
+ # Only real/regular files
+ -l && return;
+ if ( -d and $File::Find::dir =~ $skip_dir_parent and $_ =~ $skip_dir_basename) {
+ $File::Find::prune = 1;
+ return;
+ }
+ -f _ || return;
+ my $path = "$File::Find::dir/$_";
+ return if excludefile($path);
+ return if not is_so_or_exec_elf_file($_);
+ push(@all_so_files, $path);
+ }, $root_dir);
+
+ @all_so_files = sort(@all_so_files);
+ for my $lib_file (@all_so_files) {
+ my ($library, $major, $ret);
+ if (compat(10)) {
+ # In compat 10, we silently ignored failing exit codes
+ # from objdump. Its horrible, but such was compat 10.
+ $ret = `$objdump -p "$lib_file"`;
+ chomp($ret);
+ } else {
+ $ret = qx_cmd($objdump, '-p', $lib_file);
+ }
+ if ($ret=~m/\s+SONAME\s+(.*)\.so\.(.*)/) {
+ # proper soname format
+ $library=$1;
+ $major=$2;
+ } elsif ($ret=~m/\s+SONAME\s+(.*)-(\d.*)\.so/) {
+ # idiotic crap soname format
+ $library=$1;
+ $major=$2;
+ } elsif ($ret !~ m/\s+SONAME\s+(?:\S)/) {
+ next;
+ }
+ push(@so_file_data, [$lib_file, $library, $major,]);
+ };
+ return @so_file_data;
+}
+
+foreach my $package (@{$dh{DOPACKAGES}}) {
+ next if is_udeb($package);
+
+ my $tmp=tmpdir($package);
+
+ my (%seen, $unversioned_so);
+ my $need_ldconfig = 0;
+ # Note that since each package can have a shlibs file independently of
+ # each other, we need to make these local.
+ my $v_flag_set = $dh{V_FLAG_SET};
+ my $v_flag = $dh{V_FLAG} // '';
+ my $shlibs_file = pkgfile($package, 'shlibs');
+
+ rm_files("$tmp/DEBIAN/shlibs");
+
+ # So, we look for files or links to existing files with names that
+ # match "*.so.*". And we only look at real files not
+ # symlinks, so we don't accidentally add shlibs data to -dev
+ # packages. This may have a few false positives, which is ok,
+ # because only if we can get a library name and a major number from
+ # objdump is anything actually added.
+ my (@udeb_lines, @deb_lines, @lib_files, $udeb_name);
+ if (defined($shlibs_udeb)) {
+ $udeb_name = $shlibs_udeb if $shlibs_udeb ne '';
+ } else {
+ my $guessed_udeb = "${package}-udeb";
+ $udeb_name = $guessed_udeb if is_known_package($guessed_udeb) and is_udeb($guessed_udeb);
+ }
+ # If there is a udeb (which we assume there never is under the "noudeb" build-profile)
+ # then check it for libraries.
+ if (defined($udeb_name) and not is_build_profile_active('noudeb')) {
+ for my $so_data (_all_so_files($udeb_name, tmpdir($udeb_name))) {
+ my (undef, $library, $major) = @{$so_data};
+ $major = $dh{M_PARAMS} if defined($dh{M_PARAMS}) and $dh{M_PARAMS} ne '';
+ next if not defined($library) or not defined($major);
+ $known_udeb_solibs{$udeb_name}{"${library}\x1f${major}"} = 1;
+ }
+ # If the udeb contains no SO files but there was an explicit --add-udeb, then
+ # something is wrong.
+ error("The udeb $shlibs_udeb does not contain any shared libraries but --add-udeb=$shlibs_udeb was passed!?")
+ if defined($shlibs_udeb) and not exists($known_udeb_solibs{$udeb_name});
+ }
+ for my $so_data (_all_so_files($package, $tmp)) {
+ my ($lib_file, $library, $major) = @{$so_data};
+ push(@lib_files, $lib_file) if compat(11);
+ if (not defined($library)) {
+ $unversioned_so = 1;
+ push(@lib_files, $lib_file) if not compat(11);
+ }
+
+ if (defined($dh{M_PARAMS}) && $dh{M_PARAMS} ne '') {
+ $major=$dh{M_PARAMS};
+ }
+
+ my $deps=$package;
+ if ($v_flag_set) {
+ if ($shlibs_file) {
+ warning("The provided ${shlibs_file} file overwrites -V");
+ # Clear the flag to avoid duplicate warnings.
+ $v_flag_set = 0;
+ $v_flag = '';
+ } else {
+ # Set the default "-V" (with no value) is passed.
+ $v_flag = 'Upstream-Version' if $v_flag eq '';
+ }
+ } elsif ($v_flag eq '') {
+ # Set the default if "-V" is omitted.
+ $v_flag = compat(11) ? 'None' : 'Upstream-Version';
+ }
+ if ($v_flag ne '') {
+ if ($v_flag eq 'Upstream-Version') {
+ # Call isnative because it sets $dh{VERSION}
+ # as a side effect.
+ isnative($package);
+ my $version = $dh{VERSION};
+ # Old compatibility levels include the
+ # debian revision, while new do not.
+ # Remove debian version, if any.
+ $version =~ s/-[^-]+$//;
+ $deps = "$package (>= $version)";
+ } elsif ($v_flag ne 'None') {
+ $deps = $v_flag;
+ }
+ }
+ if (defined($library) && defined($major) && defined($deps) &&
+ $library ne '' && $major ne '' && $deps ne '') {
+ $need_ldconfig=1;
+ push(@lib_files, $lib_file) if not compat(11);
+ # Prevent duplicate lines from entering the file.
+ my $line="$library $major $deps";
+ if (! $seen{$line}) {
+ $seen{$line}=1;
+ push(@deb_lines, $line);
+ if (defined($udeb_name)) {
+ my $udeb_deps = $deps;
+ $udeb_deps =~ s/\Q$package\E/$udeb_name/e;
+ $line="udeb: $library $major $udeb_deps";
+ push @udeb_lines, $line;
+ # Track which libraries have been used in the udeb to ensure
+ # we spot missing libraries.
+ delete($known_udeb_solibs{$udeb_name}{"${library}\x1f${major}"})
+ if defined($udeb_name);
+ }
+ }
+ }
+ }
+
+ if (defined($udeb_name) and not $shlibs_udeb) {
+ my $issues = 0;
+ for my $lib_key (sort(keys(%{$known_udeb_solibs{$udeb_name}}))) {
+ my ($library, $major) = split(qr/\x1f/, $lib_key);
+ warning("$udeb_name contains SO library $library (version $major) but $package does not contain a similar library!?");
+ $issues = 1;
+ }
+ if ($issues) {
+ $ok = 0;
+ warning("Rejecting the generated shlibs file for $udeb_name!");
+ warning("Hint: Either add the missing libraries to $package, remove them from $udeb_name, or");
+ warning("Hint: (if this difference is expected) pass \"--add-udeb=$udeb_name\" to dh_makeshlibs.");
+ warning("Hint: In the latter case, you *may* also need to combine it with \"-p$package\"");
+ warning("Hint: Alternatively, if you have merged the shared lib package into $udeb_name and it has no");
+ warning("Hint: other packages need to know of this library, then use \"--no-add-udeb\"");
+ }
+ }
+
+ if ($shlibs_file) {
+ install_dir("$tmp/DEBIAN");
+ install_file($shlibs_file, "$tmp/DEBIAN/shlibs");
+ } elsif (@deb_lines or @udeb_lines) {
+ install_dir("$tmp/DEBIAN");
+ if ($dh{VERBOSE}) {
+ verbose_print('echo ' . escape_shell($_) . ' >> ' . escape_shell("$tmp/DEBIAN/shlibs"))
+ for @deb_lines, @udeb_lines;
+ }
+ if (not $dh{NO_ACT}) {
+ open(my $shlibs_fd, '>', "$tmp/DEBIAN/shlibs") or error("open($tmp/DEBIAN/shlibs): $!");
+ # Write the shlibs file with the udeb: lines last.
+ print {$shlibs_fd} "$_\n" for @deb_lines, @udeb_lines;
+ close($shlibs_fd) or error("close($tmp/DEBIAN/shlibs");
+ }
+ }
+
+ if (-e "$tmp/DEBIAN/shlibs") {
+ reset_perm_and_owner(0644, "$tmp/DEBIAN/shlibs");
+ }
+
+ # dpkg-gensymbols files
+ my $symbols=pkgfile($package, "symbols");
+ if (-e $symbols) {
+ my @liblist;
+ if (! compat(7)) {
+ @liblist=map { "-e$_" } @lib_files;
+ }
+ # -I is used rather than using dpkg-gensymbols
+ # own search for symbols files, since that search
+ # is not 100% compatible with debhelper. (For example,
+ # this supports --ignore being used.)
+ $ok = doit_noerror(
+ "dpkg-gensymbols",
+ "-p$package",
+ "-I$symbols",
+ "-P$tmp",
+ @liblist,
+ @{$dh{U_PARAMS}}
+ ) && $ok;
+
+ if (-f "$tmp/DEBIAN/symbols" and -s _ == 0) {
+ rm_files("$tmp/DEBIAN/symbols");
+ } elsif ($unversioned_so) {
+ # There are a few "special" libraries (e.g. nss/nspr)
+ # which do not have versioned SONAMES. However the
+ # maintainer provides a symbols file for them and we can
+ # then use that to add an ldconfig trigger.
+ $need_ldconfig = 1;
+ }
+ }
+
+ # Historically, --no-scripts would disable the creation of
+ # maintscripts for calling ldconfig.
+ if (! $dh{NOSCRIPTS} && $need_ldconfig) {
+ autotrigger($package, 'activate-noawait', 'ldconfig');
+ }
+
+ next if ! -f "$tmp/DEBIAN/symbols" and ! -f "$tmp/DEBIAN/shlibs";
+
+ my $t64_compat = Debian::Debhelper::Dh_Lib::t64_compat_name($package);
+ # Handle Provides: for the t64 transition
+ next if $t64_compat eq '' and $package !~ /^lib.*t64(?:-nss)?$/;
+ if ($t64_compat eq '') {
+ $t64_compat = $package;
+ $t64_compat =~ s/t64//;
+ if ($t64_compat eq $package) {
+ error("Failed to derive a t64 compat name for ${package}. Please file a bug against debhelper or add"
+ . ' the X-Time64-Compat header to d/control, in which you can provide the compat package name'
+ . ' you want.');
+ }
+ }
+
+ require Dpkg::Arch;
+ require Dpkg::BuildFlags;
+ my $arch = package_binary_arch($package);
+ my $bf = Dpkg::BuildFlags->new();
+ if (Dpkg::Arch::debarch_to_cpubits($arch) != 32 or !$bf->get_feature("abi", "time64")) {
+ addsubstvar($package, "t64:Provides", $t64_compat, '= ${binary:Version}');
+ } else {
+ # Avoid a "unknown" substvar from dpkg-gencontrol.
+ my $ext = pkgext($package);
+ my $substvars = "debian/${ext}substvars";
+ ensure_substvars_are_present($substvars, 't64:Provides');
+ }
+}
+
+unless ($ok) {
+ error "failing due to earlier errors";
+}
+
+=head1 SEE ALSO
+
+L<debhelper(7)>
+
+This program is a part of debhelper.
+
+=head1 AUTHOR
+
+Joey Hess <joeyh@debian.org>
+
+=cut
diff --git a/dh_md5sums b/dh_md5sums
new file mode 100755
index 0000000..8f92843
--- /dev/null
+++ b/dh_md5sums
@@ -0,0 +1,128 @@
+#!/usr/bin/perl
+
+=head1 NAME
+
+dh_md5sums - generate DEBIAN/md5sums file
+
+=cut
+
+use strict;
+use warnings;
+use Cwd;
+use Debian::Debhelper::Dh_Lib;
+
+our $VERSION = DH_BUILTIN_VERSION;
+
+=head1 SYNOPSIS
+
+B<dh_md5sums> [S<I<debhelper options>>] [B<-x>] [B<-X>I<item>] [B<--include-conffiles>]
+
+=head1 DESCRIPTION
+
+B<dh_md5sums> is a debhelper program that is responsible for generating
+a F<DEBIAN/md5sums> file, which lists the md5sums of each file in the package.
+These files are used by B<dpkg --verify> or the L<debsums(1)> program.
+
+All files in F<DEBIAN/> are omitted from the F<md5sums> file, as are all
+conffiles (unless you use the B<--include-conffiles> switch).
+
+The md5sums file is installed with proper permissions and ownerships.
+
+=head1 OPTIONS
+
+=over 4
+
+=item B<-x>, B<--include-conffiles>
+
+Include conffiles in the md5sums list. Note that this information is
+redundant since it is included in F</var/lib/dpkg/status> in Debian packages.
+
+=item B<-X>I<item>, B<--exclude=>I<item>
+
+Exclude files that contain I<item> anywhere in their filename from
+being listed in the md5sums file.
+
+=back
+
+=cut
+
+init(options => {
+ "x" => \$dh{INCLUDE_CONFFILES}, # is -x for some unknown historical reason..
+ "include-conffiles" => \$dh{INCLUDE_CONFFILES},
+});
+
+on_pkgs_in_parallel {
+ foreach my $package (@_) {
+ next if is_udeb($package);
+
+ my $dbgsym_tmp = dbgsym_tmpdir($package);
+ my $tmp=tmpdir($package);
+
+ install_dir("$tmp/DEBIAN");
+
+ # Check if we should exclude conffiles.
+ my %conffiles;
+ if (! $dh{INCLUDE_CONFFILES} && -r "$tmp/DEBIAN/conffiles") {
+ # Generate exclude regexp.
+ open(my $fd, '<', "$tmp/DEBIAN/conffiles")
+ or error("open $tmp/DEBIAN/conffiles failed: $!");
+ while (my $line = <$fd>) {
+ chomp($line);
+ next if $line !~ s{^/+}{};
+ next if $line eq '';
+ $conffiles{$line} = 1;
+ }
+ close($fd);
+ }
+
+ generate_md5sums_file($tmp, \%conffiles);
+ if ( -d $dbgsym_tmp) {
+ install_dir("${dbgsym_tmp}/DEBIAN");
+ generate_md5sums_file($dbgsym_tmp);
+ }
+ }
+};
+
+sub generate_md5sums_file {
+ my ($tmpdir, $conffiles) = @_;
+ my $find_pid = open(my $find_fd, '-|') // error("fork failed: $!");
+ my (@files, $pipeline_pid);
+ if (not $find_pid) {
+ # Child
+ chdir($tmpdir) or error("chdir($tmpdir) failed: $!");
+ exec { 'find' } 'find', '-type', 'f', '!', '-regex', './DEBIAN/.*', '-printf', "%P\\0";
+ }
+ local $/ = "\0"; # NUL-terminated input/"lines"
+ while (my $line = <$find_fd>) {
+ chomp($line);
+ next if excludefile($line);
+ next if $conffiles and %{$conffiles} and exists($conffiles->{$line});
+ push(@files, $line);
+ }
+ close($find_fd) or error_exitcode("find -type f ! -regex './DEBIAN/.*' -printf '%P\\0'");
+ @files = sort(@files);
+ verbose_print("cd $tmpdir >/dev/null && " . q{xargs -r0 md5sum | perl -pe 'if (s@^\\\\@@) { s/\\\\\\\\/\\\\/g; }' > DEBIAN/md5sums});
+ $pipeline_pid = open(my $pipeline_fd, '|-') // error("fork failed: $!");
+ if (not $pipeline_pid) {
+ # Child
+ chdir($tmpdir) or error("chdir($tmpdir) failed: $!");
+ exec { 'sh' } '/bin/sh', '-c', q{xargs -r0 md5sum | perl -pe 'if (s@^\\\\@@) { s/\\\\\\\\/\\\\/g; }' > DEBIAN/md5sums};
+ }
+
+ printf {$pipeline_fd} "%s\0", $_ for @files; # @files include NUL-terminator
+ close($pipeline_fd) or error_exitcode("cd $tmpdir >/dev/null && xargs -r0 md5sum | perl -pe 'if (s@^\\\\@@) { s/\\\\\\\\/\\\\/g; }' > DEBIAN/md5sums");
+ reset_perm_and_owner(0644, "${tmpdir}/DEBIAN/md5sums");
+ return;
+}
+
+=head1 SEE ALSO
+
+L<debhelper(7)>
+
+This program is a part of debhelper.
+
+=head1 AUTHOR
+
+Joey Hess <joeyh@debian.org>
+
+=cut
diff --git a/dh_missing b/dh_missing
new file mode 100755
index 0000000..ef19d00
--- /dev/null
+++ b/dh_missing
@@ -0,0 +1,271 @@
+#!/usr/bin/perl
+
+=head1 NAME
+
+dh_missing - check for missing files
+
+=cut
+
+use v5.24;
+use warnings;
+use Debian::Debhelper::Dh_Lib;
+
+our $VERSION = DH_BUILTIN_VERSION;
+
+=head1 SYNOPSIS
+
+B<dh_missing> [B<-X>I<item>] [B<--sourcedir=>I<dir>] [S<I<debhelper options>>]
+
+=head1 DESCRIPTION
+
+B<dh_missing> compares the list of installed files with the files in
+the source directory. If any of the files (and symlinks) in the source
+directory were not installed to somewhere, it will warn on stderr
+about that (B<--list-missing>) or fail (B<--fail-missing>).
+
+Please note that in compat 11 and earlier without either of these
+options, B<dh_missing> will silently do nothing. In compat 12,
+B<--list-missing> is the default In compat 13 and later,
+B<--fail-missing> is the default.
+
+This may be useful if you have a large package and want to make sure that
+you don't miss installing newly added files in new upstream releases.
+
+Remember to test different kinds of builds (dpkg-buildpackage -A/-B/...) as
+you may experience varying results when only a subset of the packages are
+built.
+
+=head1 FILES
+
+=over 4
+
+=item debian/not-installed
+
+List the files that are deliberately not installed in I<any> binary
+package. Paths listed in this file are ignored by B<dh_missing>.
+However, it is B<not> a method to exclude files from being installed
+by any of the debhelper tool. If you want a tool to not install a
+given file, please use its B<--exclude> option (where available).
+
+B<dh_missing> will expand wildcards in this file (since debhelper 11.1).
+Wildcards without matches will be ignored.
+
+Supports substitution variables in compat 13 and later as
+documented in L<debhelper(7)>.
+
+=back
+
+=head1 OPTIONS
+
+=over 4
+
+=item B<--list-missing>
+
+Warn on stderr about source files not installed to somewhere.
+
+Note that many dh-tools acting on a path will mark the path as
+installed even if it has been excluded via B<-X> or B<--exclude>.
+This is also seen when a dh-tool is acting on a directory and
+exclusion is used to ignore some files in the directory. In either
+case, this will make B<dh_missing> silently assume the excluded files
+have been handled.
+
+This is the default in compat 12.
+
+=item B<--fail-missing>
+
+This option is like B<--list-missing>, except if a file was missed, it will
+not only list the missing files, but also fail with a nonzero exit code.
+
+This is the default in compat 13 and later.
+
+=back
+
+=cut
+
+init(options => {
+ "list-missing" => \$dh{LIST_MISSING},
+ "fail-missing" => \$dh{FAIL_MISSING},
+ "sourcedir=s" => \$dh{SOURCEDIR},
+});
+
+my (@installed, %helpers, %helpers_basename);
+
+my $srcdir = '.';
+if (defined($dh{SOURCEDIR})) {
+ $srcdir = $dh{SOURCEDIR};
+ $srcdir =~ s{/+$}{};
+ error("Invalid --sourcedir - must not be empty nor /") if not $srcdir;
+}
+
+if (!$dh{LIST_MISSING} && !$dh{FAIL_MISSING}) {
+ exit 0 if compat(11);
+ # --list-missing is the default in compat 12 and --fail-missing in compat 13+
+ my $option = compat(12) ? 'LIST_MISSING' : 'FAIL_MISSING';
+ $dh{$option} = 1;
+}
+
+# . as srcdir makes no sense, so this is a special case.
+if ($srcdir eq '.') {
+ $srcdir='debian/tmp';
+}
+
+if (! -d $srcdir) {
+ # If there was no explicit source directory, then we do not care
+ # if it is missing.
+ exit(0) if not defined $dh{SOURCEDIR};
+
+ if (scalar(getpackages()) == 1 && defined($dh{SOURCEDIR})) {
+ warning("$srcdir does not exist and there is only binary package.");
+ warning("Assuming everything is installed directly into the package directory.");
+ exit(0);
+ }
+ if (compat(10)) {
+ # Prevent "dh $@ --list-missing --destdir=... ..." from failing in compat 10.
+ warning("Cannot check if installation is missing files: $srcdir does not exist");
+ exit(0);
+ } else {
+ error("Cannot check if installation is missing files: $srcdir does not exist");
+ }
+}
+
+for my $file (glob('debian/.debhelper/generated/*/installed-by-*')) {
+ my ($target_pkg, $helper) = ('unknown', 'unknown');
+ my $had_files = 0;
+ my %seen;
+ if ($file =~ m@.*/([^/]+)/installed-by-(.*)@) {
+ ($target_pkg, $helper) = ($1, $2);
+ }
+
+ open(my $fh, '<', $file) or error("could not open $file: $!");
+ while (my $line = <$fh>) {
+ chomp($line);
+ next if $line =~ m/^\s*$/;
+ next if $seen{$line}++; # Ignore duplicates
+ $had_files++;
+ push(@installed, $line);
+ }
+ $helpers{$helper}{$target_pkg} = $had_files;
+ close($fh);
+}
+
+my @missing;
+if ( -f 'debian/not-installed') {
+ my @not_installed = filearray('debian/not-installed');
+ for my $pattern (@not_installed) {
+ my @matches;
+ # Add an explicit d/tmp if absent as there is no point in
+ # looking outside the debian staging directory
+ $pattern =~ s:^\s*:debian/tmp/: unless $pattern =~ m:^\s*debian/tmp/:;
+ @matches = glob_expand(['.'], \&glob_expand_error_handler_silently_ignore, $pattern);
+ if (@matches) {
+ # Assume classify them as installed
+ push(@installed, @matches);
+ } else {
+ # Assume it is not a pattern and classify it as installed
+ push(@installed, $pattern);
+ }
+ }
+
+ push(@installed, @not_installed);
+}
+my $installed=join("|", map {
+ # Kill any extra slashes, for robustness.
+ y:/:/:s;
+ s:/+$::;
+ s:^(\./)*::;
+ "\Q$_\E\/.*|\Q$_\E";
+} @installed);
+$installed=qr{^($installed)$};
+
+# Lazy load File::Find
+require File::Find;
+
+File::Find::find(sub {
+ # Lazy loading of File::Find makes perl think that File::Find::dir is only used once
+ # and we might have typo'ed something
+ no warnings qw(once);
+ -f || -l || return;
+ $_="$File::Find::dir/$_";
+ if (! /$installed/ && ! excludefile($_)) {
+ my $file=$_;
+ $file=~s/^\Q$srcdir\E\///;
+ push @missing, $file;
+ }
+}, $srcdir);
+if (@missing) {
+ my $had_related_files;
+ my %seen_basename = map { basename($_) => $_ } @installed;
+ my $multiarch = dpkg_architecture_value("DEB_HOST_MULTIARCH");
+ my $seen_ma_value = 0;
+ for my $file (sort(@missing)) {
+ my $basename = basename($file);
+ if (exists($seen_basename{$basename})) {
+ my $alt_source = $seen_basename{$basename};
+ $had_related_files //= [$file, $alt_source];
+ warning("$file exists in $srcdir but is not installed to anywhere (related file: \"$alt_source\")");
+ } else {
+ warning("$file exists in $srcdir but is not installed to anywhere ");
+ }
+ $seen_ma_value = 1 if index($file, $multiarch) > -1;
+ }
+ if ($had_related_files) {
+ my ($missing, $alt_source) = $had_related_files->@*;
+ my $error = $dh{FAIL_MISSING} ? 'error' : 'warning';
+ nonquiet_print();
+ nonquiet_print('While detecting missing files, dh_missing noted some files with a similar name to those');
+ nonquiet_print("that were missing. This ${error} /might/ be resolved by replacing references to the");
+ nonquiet_print('missing files with the similarly named ones that dh_missing found - assuming the content');
+ nonquiet_print('is identical.');
+ nonquiet_print();
+ nonquiet_print('As an example, you might want to replace:');
+ nonquiet_print(" * ${alt_source}");
+ nonquiet_print('with:');
+ nonquiet_print(" * ${missing}");
+ nonquiet_print('in a file in debian/ or as argument to one of the dh_* tools called from debian/rules.');
+ nonquiet_print('(Note it is possible the paths are not used verbatim but instead directories ');
+ nonquiet_print('containing or globs matching them are used instead)');
+ nonquiet_print();
+ nonquiet_print('Alternatively, add the missing file to debian/not-installed if it cannot and should not');
+ nonquiet_print('be used.');
+ nonquiet_print();
+ }
+ nonquiet_print("The following debhelper tools have reported what they installed (with files per package)");
+ for my $helper (sort(keys(%helpers))) {
+ my $pkg_info = $helpers{$helper};
+ my @results;
+ for my $pkg (sort(keys(%{$pkg_info}))) {
+ my $no = $pkg_info->{$pkg};
+ push(@results, "${pkg} (${no})")
+ }
+ nonquiet_print(" * ${helper}: " . join(', ', @results));
+ }
+ nonquiet_print('If the missing files are installed by another tool, please file a bug against it.');
+ nonquiet_print('When filing the report, if the tool is not part of debhelper itself, please reference the');
+ nonquiet_print('"Logging helpers and dh_missing" section from the "PROGRAMMING" guide for debhelper (10.6.3+).');
+ nonquiet_print(' (in the debhelper package: /usr/share/doc/debhelper/PROGRAMMING.md.gz)');
+ nonquiet_print("Be sure to test with dpkg-buildpackage -A/-B as the results may vary when only a subset is built");
+ nonquiet_print("If the omission is intentional or no other helper can take care of this consider adding the");
+ nonquiet_print("paths to debian/not-installed.");
+ if ($seen_ma_value) {
+ nonquiet_print();
+ nonquiet_print("Remember to be careful with paths containing \"${multiarch}\", where you might need to");
+ nonquiet_print("use a wildcard or (assuming compat 13+) e.g. \${DEB_HOST_MULTIARCH} in debian/not-installed");
+ nonquiet_print("to ensure it works on all architectures (see #961104).");
+ }
+ if ($dh{FAIL_MISSING}) {
+ error("missing files, aborting");
+ }
+}
+
+=head1 SEE ALSO
+
+L<debhelper(7)>
+
+This program is a part of debhelper.
+
+=head1 AUTHOR
+
+Michael Stapelberg <stapelberg@debian.org>
+
+=cut
diff --git a/dh_movefiles b/dh_movefiles
new file mode 100755
index 0000000..7c5c8ff
--- /dev/null
+++ b/dh_movefiles
@@ -0,0 +1,171 @@
+#!/usr/bin/perl
+
+=head1 NAME
+
+dh_movefiles - move files out of debian/tmp into subpackages
+
+=cut
+
+use strict;
+use warnings;
+use Debian::Debhelper::Dh_Lib;
+
+our $VERSION = DH_BUILTIN_VERSION;
+
+=head1 SYNOPSIS
+
+B<dh_movefiles> [S<I<debhelper options>>] [B<--sourcedir=>I<dir>] [B<-X>I<item>] [S<I<file> ...>]
+
+=head1 DESCRIPTION
+
+B<dh_movefiles> is a debhelper program that is responsible for moving files
+out of F<debian/tmp> or some other directory and into other package build
+directories. This may be useful if your package has a F<Makefile> that installs
+everything into F<debian/tmp>, and you need to break that up into subpackages.
+
+Note: B<dh_install> is a much better program, and you are recommended to use
+it instead of B<dh_movefiles>.
+
+=head1 FILES
+
+=over 4
+
+=item debian/I<package>.files
+
+Lists the files to be moved into a package, separated by whitespace. The
+filenames listed should be relative to F<debian/tmp/>. You can also list
+directory names, and the whole directory will be moved.
+
+=back
+
+=head1 OPTIONS
+
+=over 4
+
+=item B<--sourcedir=>I<dir>
+
+Instead of moving files out of F<debian/tmp> (the default), this option makes
+it move files out of some other directory. Since the entire contents of
+the sourcedir is moved, specifying something like B<--sourcedir=/> is very
+unsafe, so to prevent mistakes, the sourcedir must be a relative filename;
+it cannot begin with a `B</>'.
+
+=item B<-Xitem>, B<--exclude=item>
+
+Exclude files that contain B<item> anywhere in their filename from
+being installed.
+
+=item I<file> ...
+
+Lists files to move. The filenames listed should be relative to
+F<debian/tmp/>. You can also list directory names, and the whole directory will
+be moved. It is an error to list files here unless you use B<-p>, B<-i>, or B<-a> to
+tell B<dh_movefiles> which subpackage to put them in.
+
+=back
+
+=head1 NOTES
+
+Note that files are always moved out of F<debian/tmp> by default (even if you
+have instructed debhelper to use a compatibility level higher than one,
+which does not otherwise use debian/tmp for anything at all). The idea
+behind this is that the package that is being built can be told to install
+into F<debian/tmp>, and then files can be moved by B<dh_movefiles> from that
+directory. Any files or directories that remain are ignored, and get
+deleted by B<dh_clean> later.
+
+=cut
+
+init(options => {
+ "sourcedir=s" => \$dh{SOURCEDIR},
+});
+
+my $ret=0;
+
+foreach my $package (@{$dh{DOPACKAGES}}) {
+ my $tmp=tmpdir($package);
+ my $files=pkgfile($package,"files");
+
+ my $sourcedir="debian/tmp";
+ if ($dh{SOURCEDIR}) {
+ if ($dh{SOURCEDIR}=~m:^/:) {
+ error("The sourcedir must be a relative filename, not starting with `/'.");
+ }
+ $sourcedir=$dh{SOURCEDIR};
+ }
+
+ if (! -d $sourcedir) {
+ error("$sourcedir does not exist.");
+ }
+
+ my (@tomove, @tomove_expanded);
+
+ # debian/files has a different purpose, so ignore it.
+ if ($files && $files ne "debian/files" ) {
+ @tomove=filearray($files, $sourcedir);
+ }
+
+ if (($package eq $dh{FIRSTPACKAGE} || $dh{PARAMS_ALL}) && @ARGV) {
+ # Expand these manually similar to filearray
+ push(@tomove_expanded, map { glob("$sourcedir/$_") } @ARGV);
+ }
+
+ if ((@tomove || @tomove_expanded) && $tmp eq $sourcedir) {
+ error("I was asked to move files from $sourcedir to $sourcedir.");
+ }
+
+ # filearray() does not add the sourcedir, which we need.
+ @tomove = map { "$sourcedir/$_" } @tomove;
+
+ push(@tomove, @tomove_expanded);
+
+ if (@tomove) {
+ install_dir($tmp);
+
+ doit("rm","-f","debian/movelist");
+ foreach (@tomove) {
+ my $file=$_;
+ if (! -e $file && ! -l $file && ! $dh{NO_ACT}) {
+ $ret=1;
+ warning("$file not found (supposed to put it in $package)");
+ }
+ else {
+ $file=~s:^\Q$sourcedir\E/+::;
+ my $cmd="(cd $sourcedir >/dev/null ; find $file ! -type d ";
+ if ($dh{EXCLUDE_FIND}) {
+ $cmd.="-a ! \\( $dh{EXCLUDE_FIND} \\) ";
+ }
+ $cmd.="-print || true) >> debian/movelist";
+ complex_doit($cmd);
+ }
+ }
+ my $pwd=`pwd`;
+ chomp $pwd;
+ complex_doit("(cd $sourcedir >/dev/null ; tar --create --files-from=$pwd/debian/movelist --file -) | (cd $tmp >/dev/null ;tar xpf -)");
+ # --remove-files is not used above because tar then doesn't
+ # preserve hard links
+ complex_doit("(cd $sourcedir >/dev/null ; tr '\\n' '\\0' < $pwd/debian/movelist | xargs -0 rm -f)");
+ doit("rm","-f","debian/movelist");
+ }
+}
+
+# If $ret is set, we weren't actually able to find some
+# files that were specified to be moved, and we should
+# exit with the code in $ret. This program puts off
+# exiting with an error until all files have been tried
+# to be moved, because this makes it easier for some
+# packages that aren't always sure exactly which files need
+# to be moved.
+exit $ret;
+
+=head1 SEE ALSO
+
+L<debhelper(7)>
+
+This program is a part of debhelper.
+
+=head1 AUTHOR
+
+Joey Hess <joeyh@debian.org>
+
+=cut
diff --git a/dh_movetousr b/dh_movetousr
new file mode 100755
index 0000000..de24352
--- /dev/null
+++ b/dh_movetousr
@@ -0,0 +1,230 @@
+#!/usr/bin/perl
+
+=head1 NAME
+
+dh_movetousr - canonicalize location according to merged-/usr
+
+=cut
+
+use strict;
+use warnings;
+use Config;
+use File::Find;
+use File::Spec;
+use Debian::Debhelper::Dh_Lib;
+
+our $VERSION = DH_BUILTIN_VERSION;
+
+=head1 SYNOPSIS
+
+B<dh_movetousr> [S<I<debhelper options>>] [B<--fail-noop> | B<--warn-noop>]
+
+=head1 DESCRIPTION
+
+B<dh_movetousr> is a B<debhelper> program that canonicalizes paths inside packages according to merged-/usr.
+Shipping aliased paths is known to cause problems with B<dpkg>, so this helper moves all affected files to F</usr> regardless of how they were installed.
+The compatibility symlinks ensure that converted packages continue to work.
+In the process, absolute symbolic links may become relative or vice versa due to Debian policy section 10.5.
+
+Please keep in mind that moving files in this way is known to cause problems.
+Known problems have been documented at L<https://people.debian.org/~helmutg/dep17.html>.
+For instance, if files have been moved between packages, use of this tool may cause file loss during upgrades (P1).
+Most problems can be detected by L<https://salsa.debian.org/helmutg/dumat>, which uses the Debian bug tracking for feedback.
+Therefore, it is recommended to upload to B<experimental> when moving files (e.g. using this helper) or restructuring packages that earlier moved files.
+A particular problem not being detected is about B<dpkg-statoverride> (P5).
+Please review uses of B<dpkg-statoverride> in maintainer scripts and update them as needed.
+For these reasons, B<dh_movetousr> is not automatically enabled in e.g. a compatibility level.
+
+While we want to move files to F</usr> in B<trixie> and beyond, we do not want to move them in B<bookworm> and earlier.
+This poses challenges to backporting packages, because any such moves have to be reverted during the backport.
+A backport of B<debhelper> to B<bookworm> shall include a stub for this helper doing nothing to achieve this goal.
+For packages that do not need to be backported (e.g. packages targeting B<forky> and beyond), consider updating locations instead of using this helper.
+When the only affected type of file is B<systemd> units, consider using B<dh_installsystemd> or detecting the unit location from C<pkgconf --variable=systemdsystemunitdir systemd> instead of this helper as both will work in backports.
+
+For further information on the state of the transition refer to L<https://wiki.debian.org/UsrMerge>.
+
+B<dh_movetousr> shall be removed from B<debhelper> during B<forky+1> is release cycle.
+
+=head1 OPTIONS
+
+=over 4
+
+=item B<--fail-noop>
+
+Fail if no files were found in aliased locations and therefore no change has
+been performed.
+
+=item B<--warn-noop>
+
+Warn if no files were found in aliased locations and therefore no change has
+been performed.
+
+=back
+
+=head1 EXAMPLES
+
+ Build-Depends: dh-sequence-movetousr
+
+Enable this tool in a package that uses B<dh>.
+
+=cut
+
+init(options => {
+ 'fail-noop!' => sub { $dh{NOOP_LEVEL} = 'error'; },
+ 'warn-noop!' => sub { $dh{NOOP_LEVEL} = 'warning'; },
+});
+
+my @merged_directories = (
+ qw{bin lib lib64 libo32 libx32 sbin},
+);
+my $merged_dir_pattern = join('|', @merged_directories);
+
+sub merge_entry {
+ my ($package, $rootdir, $location) = @_;
+ if (-l "$rootdir/usr/$location") {
+ error("cannot move $location to /usr in $package, because it exists there as a symlink");
+ } elsif (-l "$rootdir/$location") {
+ if (-e "$rootdir/usr/$location") {
+ error("cannot move symlink $location to /usr in $package, because it already exists there");
+ }
+ my $target = readlink("$rootdir/$location");
+ my $recreate = 0; # Whether the link target should be recomputed.
+ my @newtarget; # Represented as split m|/|. Relative to /.
+ if ($target !~ m,^/,) {
+ # In case the original target is relative, prepend the
+ # basename of the link location.
+ @newtarget = split(m|/+|, $location);
+ pop @newtarget;
+ }
+ # Append the original target, resolving any '../', './' and
+ # double-slashes.
+ foreach my $part (split(m|/+|, $target)) {
+ if ($part eq '..') {
+ # If the link goes past /, recreate.
+ $recreate = 1 if (scalar @newtarget <= 1);
+ pop @newtarget if (scalar @newtarget > 0);
+ } elsif ($part ne '' && $part ne '.') {
+ push @newtarget, $part;
+ }
+ }
+ if (grep { $_ eq $newtarget[0] } @merged_directories) {
+ # If the link target is aliased, unalias and recreate.
+ unshift @newtarget, 'usr';
+ $recreate = 1;
+ } elsif ($target =~ m,^/, && $newtarget[0] eq 'usr') {
+ # If the original link is absolute and now points to
+ # /usr, recreate.
+ $recreate = 1;
+ }
+ if ($recreate) {
+ make_symlink("usr/$location", join('/', @newtarget), $rootdir);
+ rm_files("$rootdir/$location");
+ } else {
+ rename_path("$rootdir/$location", "$rootdir/usr/$location");
+ }
+ } elsif (-d "$rootdir/$location") {
+ my $did_mkdir = 0;
+ if (! -d "$rootdir/usr/$location") {
+ if (-e "$rootdir/usr/$location") {
+ error("cannot move directory $location to /usr in $package as it exists in /usr as a non-directory");
+ } else {
+ install_dir("$rootdir/usr/$location");
+ $did_mkdir = 1;
+ }
+ }
+ opendir(my $dh, "$rootdir/$location") or
+ error("cannot open directory $rootdir/$location: $!");
+ while (my $entry = readdir($dh)) {
+ next if ($entry eq "." || $entry eq "..");
+ merge_entry($package, $rootdir, "$location/$entry");
+ }
+ closedir($dh);
+ if ($did_mkdir) {
+ doit("chown", "--reference", "$rootdir/$location", "$rootdir/usr/$location");
+ doit("chmod", "--reference", "$rootdir/$location", "$rootdir/usr/$location");
+ }
+ verbose_print('rmdir ' . escape_shell("$rootdir/$location"))
+ if $dh{VERBOSE};
+ rmdir("$rootdir/$location") or
+ error("rmdir $rootdir/$location failed: $!");
+ } elsif (-e "$rootdir/usr/$location") {
+ error("cannot move $location to /usr in $package, because it already exists there");
+ } else {
+ rename_path("$rootdir/$location", "$rootdir/usr/$location");
+ }
+}
+
+my ($is_noop) = 1;
+sub process_packages {
+ foreach my $package (@_) {
+ my $tmp = tmpdir($package);
+
+ next if ! -d $tmp;
+
+ if (-d "$tmp/usr") {
+ # Reconstruct absolute symlinks pointing from /usr into an
+ # aliased directory.
+ find (
+ {
+ wanted => sub {
+ return unless -l;
+ my $target = readlink($_);
+ return unless ($target =~ m,^/($merged_dir_pattern)(/|$),);
+ s|^\Q$tmp\E||;
+ make_symlink("$_", "usr/$target", $tmp);
+ },
+ no_chdir => 1
+ },
+ "$tmp/usr"
+ );
+ }
+
+ foreach my $dir (@merged_directories) {
+ if (-d "$tmp/$dir" && ! -l "$tmp/$dir") {
+ merge_entry($package, $tmp, $dir);
+ $is_noop = 0;
+ }
+ }
+ }
+}
+
+if (exists $ENV{'DEB_BUILD_PROFILES'} || ! exists $dh{NOOP_LEVEL}) {
+ on_pkgs_in_parallel(\&process_packages);
+} else {
+ process_packages(@{$dh{DOPACKAGES}});
+ if ($is_noop) {
+ warning('dh_movetousr did not move any files to /usr');
+ if (exists $ENV{DH_INTERNAL_MOVETOUSR_IS_ADDON}) {
+ if ($dh{DOARCH} && ! $dh{DOINDEP} && getpackages('indep')) {
+ warning('consider moving dh-sequence-movetousr to Build-Depends-Indep');
+ } elsif ($dh{DOINDEP} && ! $dh{DOARCH} && getpackages('arch')) {
+ warning('consider moving dh-sequence-movetousr to Build-Depends-Arch');
+ } else {
+ warning('consider dropping dh-sequence-movetousr from Build-Depends');
+ }
+ } else {
+ if ($dh{DOARCH} && ! $dh{DOINDEP} && getpackages('indep')) {
+ warning('consider passing -i to dh_movetousr');
+ } elsif ($dh{DOINDEP} && ! $dh{DOARCH} && getpackages('arch')) {
+ warning('consider passing -a to dh_movetousr');
+ } else {
+ warning('consider deleting the dh_movetousr invocation');
+ }
+ }
+ if ($dh{NOOP_LEVEL} eq 'error') {
+ error('failing as requested');
+ }
+ }
+}
+
+=head1 SEE ALSO
+
+L<debhelper(7)>
+
+This program is a part of debhelper.
+
+=head1 AUTHOR
+
+Helmut Grohne <helmut@subdivi.de>
+
+=cut
diff --git a/dh_perl b/dh_perl
new file mode 100755
index 0000000..62ffcaa
--- /dev/null
+++ b/dh_perl
@@ -0,0 +1,198 @@
+#!/usr/bin/perl
+
+=head1 NAME
+
+dh_perl - calculates Perl dependencies and cleans up after MakeMaker
+
+=cut
+
+use strict;
+use warnings;
+use Config;
+use File::Find;
+use Debian::Debhelper::Dh_Lib;
+use constant DISTRO_PERL => $^X;
+
+our $VERSION = DH_BUILTIN_VERSION;
+
+=head1 SYNOPSIS
+
+B<dh_perl> [S<I<debhelper options>>] [B<-d>] [S<I<library dirs> ...>]
+
+=head1 DESCRIPTION
+
+B<dh_perl> is a debhelper program that is responsible for generating
+the B<${perl:Depends}> substitutions and adding them to substvars files.
+
+The program will look at Perl scripts and modules in your package,
+and will use this information to generate a dependency on B<perl> or
+B<perlapi>. The dependency will be substituted into your package's F<control>
+file wherever you place the token B<${perl:Depends}>.
+
+B<dh_perl> also cleans up empty directories that MakeMaker can generate when
+installing Perl modules.
+
+=head1 OPTIONS
+
+=over 4
+
+=item B<-d>
+
+In some specific cases you may want to depend on B<perl-base> rather than the
+full B<perl> package. If so, you can pass the -d option to make B<dh_perl> generate
+a dependency on the correct base package. This is only necessary for some
+packages that are included in the base system.
+
+Note that this flag may cause no dependency on B<perl-base> to be generated at
+all. B<perl-base> is Essential, so its dependency can be left out, unless a
+versioned dependency is needed.
+
+=item B<-V>
+
+By default, scripts and architecture independent modules don't depend
+on any specific version of B<perl>. The B<-V> option causes the current
+version of the B<perl> (or B<perl-base> with B<-d>) package to be specified.
+
+=item I<library dirs>
+
+If your package installs Perl modules in non-standard
+directories, you can make B<dh_perl> check those directories by passing their
+names on the command line. It will only check the F<vendorlib> and F<vendorarch>
+directories by default.
+
+=back
+
+=head1 CONFORMS TO
+
+Debian policy, version 3.8.3
+
+Perl policy, version 1.20
+
+=cut
+
+init();
+
+my $vendorlib = substr $Config{vendorlib}, 1;
+my $vendorarch = substr $Config{vendorarch}, 1;
+if (is_cross_compiling()) {
+ my $incdir = perl_cross_incdir();
+ $vendorarch = substr qx/perl -I$incdir -MConfig -e 'print \$Config{vendorarch}'/, 1
+ if defined $incdir;
+}
+
+# Cleaning the paths given on the command line
+foreach (@ARGV) {
+ s#/$##;
+ s#^/##;
+}
+
+my $perl = 'perl';
+# If -d is given, then the dependency is on perl-base rather than perl.
+$perl .= '-base' if $dh{D_FLAG};
+
+# dependency types
+use constant PROGRAM => 1;
+use constant PM_MODULE => 2;
+use constant XS_MODULE => 4;
+use constant ARCHDEP_MODULE => 8;
+
+use constant MA_ANY_INCOMPATIBLE_TYPES => ~(PROGRAM | PM_MODULE);
+
+
+foreach my $package (@{$dh{DOPACKAGES}}) {
+ my $tmp=tmpdir($package);
+
+ next unless -d $tmp;
+
+ # Check also for alternate locations given on the command line
+ my @dirs = grep -d, map "$tmp/$_", $vendorlib, $vendorarch, @ARGV;
+
+ # Look for perl modules and check where they are installed
+ my $deps = 0;
+ find sub {
+ return unless -f;
+ $deps |= PM_MODULE if /\.pm$/;
+ $deps |= XS_MODULE if /\.so$/;
+ $deps |= ARCHDEP_MODULE
+ if $File::Find::dir =~ /\Q$vendorarch\E/;
+ }, @dirs if @dirs;
+
+ # find scripts
+ $tmp =~ tr:/:/:s;
+ $tmp =~ s{[^/]\K/$}{};
+ my $usd_dir = "$tmp/usr/share/doc";
+ my $check_script = sub {
+ if ($_ eq $usd_dir) {
+ $File::Find::prune = 1 if -d $_;
+ return;
+ }
+ return unless -f and (-x _ or /\.pl$/);
+
+ return unless open(my $fd, '<', $_);
+ my $path = $_;
+ my $rewrite_shebang = 0;
+ if (read($fd, local $_, 32) and m%^#!\s*(/usr/bin/perl|${\DISTRO_PERL}|/usr/bin/env\s+perl)\s%) {
+ my $actual_perl = $1;
+ $deps |= PROGRAM;
+ $rewrite_shebang = 1 if ($actual_perl ne DISTRO_PERL);
+ }
+ close($fd);
+ rewrite_shebang($path) if $rewrite_shebang;
+ };
+ find({
+ wanted => $check_script,
+ no_chdir => 1,
+ }, $tmp);
+
+ if ($deps) {
+ my $version="";
+ if ($deps & XS_MODULE or $dh{V_FLAG_SET}) {
+ ($version) = qx_cmd('dpkg', '-s', $perl) =~ /^Version:\s*(\S+)/m
+ unless $version;
+ $version = ">= $version";
+ }
+
+ my $perlarch = $perl;
+ $perlarch .= ':any' if (($deps & MA_ANY_INCOMPATIBLE_TYPES) == 0) and not $dh{V_FLAG_SET};
+
+ # no need to depend on an un-versioned perl-base -- it's
+ # essential
+ addsubstvar($package, "perl:Depends", $perlarch, $version)
+ if $perl ne 'perl-base' || length($version);
+
+ # add perlapi-<ver> for XS modules and other modules
+ # installed into vendorarch
+ addsubstvar($package, "perl:Depends",
+ "perlapi-" . ($Config{debian_abi} || $Config{version}))
+ if $deps & ( XS_MODULE | ARCHDEP_MODULE );
+ }
+
+ # MakeMaker always makes lib and share dirs, but typically
+ # only one directory is installed into.
+ foreach my $dir ("$tmp/$vendorlib", "$tmp/$vendorarch") {
+ if (-d $dir) {
+ doit("rmdir", "--ignore-fail-on-non-empty", "--parents",
+ "$dir");
+ }
+ }
+}
+
+sub rewrite_shebang {
+ my ($file) = @_;
+ doit($^X, '-p', '-i', '-e',
+ 's{#!\s*(/usr/bin/perl|' . quotemeta(DISTRO_PERL) . '|/usr/bin/env\s+perl)}{#! ' . DISTRO_PERL . '} if ($. == 1);',
+ $file);
+ return;
+}
+
+=head1 SEE ALSO
+
+L<debhelper(7)>
+
+This program is a part of debhelper.
+
+=head1 AUTHOR
+
+Brendan O'Dea <bod@debian.org>
+
+=cut
diff --git a/dh_prep b/dh_prep
new file mode 100755
index 0000000..718d8d9
--- /dev/null
+++ b/dh_prep
@@ -0,0 +1,80 @@
+#!/usr/bin/perl
+
+=head1 NAME
+
+dh_prep - perform cleanups in preparation for building a binary package
+
+=cut
+
+use strict;
+use warnings;
+use Debian::Debhelper::Dh_Lib;
+
+our $VERSION = DH_BUILTIN_VERSION;
+
+=head1 SYNOPSIS
+
+B<dh_prep> [S<I<debhelper options>>] [B<-X>I<item>]
+
+=head1 DESCRIPTION
+
+B<dh_prep> is a debhelper program that performs some file cleanups in
+preparation for building a binary package. (This is what B<dh_clean -k>
+used to do.) It removes the package build directories, F<debian/tmp>,
+and some temp files that are generated when building a binary package.
+
+It is typically run at the top of the B<binary-arch> and B<binary-indep> targets,
+or at the top of a target such as install that they depend on.
+
+=head1 OPTIONS
+
+=over 4
+
+=item B<-X>I<item> B<--exclude=>I<item>
+
+Exclude files that contain F<item> anywhere in their filename from being
+deleted, even if they would normally be deleted. You may use this option
+multiple times to build up a list of things to exclude.
+
+=back
+
+=cut
+
+init();
+
+my (@clean_files, @clean_dirs, %seen);
+
+foreach my $package (@{$dh{DOPACKAGES}}) {
+ my $tmp=tmpdir($package);
+ my $ext=pkgext($package);
+ my $source_dir = default_sourcedir($package);
+
+ push(@clean_files, "debian/${ext}substvars")
+ unless excludefile("debian/${ext}substvars");
+
+ # These are all debhelper temp files, and so it is safe to
+ # wildcard them.
+ my @temp = glob("debian/$ext*.debhelper");
+ push(@clean_files, @temp);
+ push(@clean_dirs, "debian/.debhelper/generated/${package}/");
+ push(@clean_dirs , "${tmp}/")
+ unless excludefile($tmp);
+
+ push(@clean_dirs, "${source_dir}/")
+ if (not $seen{$source_dir}++ and not excludefile($source_dir));
+}
+
+xargs(\@clean_files, 'rm', '-f', '--') if @clean_files;
+xargs(\@clean_dirs, 'rm', '-fr', '--') if @clean_dirs;
+
+=head1 SEE ALSO
+
+L<debhelper(7)>
+
+This program is a part of debhelper.
+
+=head1 AUTHOR
+
+Joey Hess <joeyh@debian.org>
+
+=cut
diff --git a/dh_shlibdeps b/dh_shlibdeps
new file mode 100755
index 0000000..1abacd3
--- /dev/null
+++ b/dh_shlibdeps
@@ -0,0 +1,214 @@
+#!/usr/bin/perl
+
+=head1 NAME
+
+dh_shlibdeps - calculate shared library dependencies
+
+=cut
+
+use strict;
+use warnings;
+use Cwd;
+use File::Find;
+use Debian::Debhelper::Dh_Lib;
+
+our $VERSION = DH_BUILTIN_VERSION;
+
+=head1 SYNOPSIS
+
+B<dh_shlibdeps> [S<I<debhelper options>>] [B<-L>I<package>] [B<-l>I<directory>] [B<-X>I<item>] [S<B<--> I<params>>]
+
+=head1 DESCRIPTION
+
+B<dh_shlibdeps> is a debhelper program that is responsible for calculating
+shared library dependencies for packages.
+
+This program is merely a wrapper around L<dpkg-shlibdeps(1)> that calls it
+once for each package listed in the F<control> file, passing it
+a list of ELF executables and shared libraries it has found.
+
+=head1 OPTIONS
+
+=over 4
+
+=item B<-X>I<item>, B<--exclude=>I<item>
+
+Exclude files that contain F<item> anywhere in their filename from being
+passed to B<dpkg-shlibdeps>. This will make their dependencies be ignored.
+This may be useful in some situations, but use it with caution. This option
+may be used more than once to exclude more than one thing.
+
+=item B<--> I<params>
+
+Pass I<params> to L<dpkg-shlibdeps(1)>.
+
+=item B<-u>I<params>, B<--dpkg-shlibdeps-params=>I<params>
+
+This is another way to pass I<params> to L<dpkg-shlibdeps(1)>.
+It is deprecated; use B<--> instead.
+
+=item B<-l>I<directory>[B<:>I<directory> ...]
+
+With recent versions of B<dpkg-shlibdeps>, this option is generally not
+needed.
+
+It tells B<dpkg-shlibdeps> (via its B<-l> parameter), to look for private
+package libraries in the specified directory (or directories -- separate
+with colons). With recent
+versions of B<dpkg-shlibdeps>, this is mostly only useful for packages that
+build multiple flavors of the same library, or other situations where
+the library is installed into a directory not on the regular library search
+path.
+
+=item B<-L>I<package>, B<--libpackage=>I<package>
+
+With recent versions of B<dpkg-shlibdeps>, this option is generally not
+needed, unless your package builds multiple flavors of the same library
+or is relying on F<debian/shlibs.local> for an internal library.
+
+It tells B<dpkg-shlibdeps> (via its B<-S> parameter) to look first in the package
+build directory for the specified package, when searching for libraries,
+symbol files, and shlibs files.
+
+If needed, this can be passed multiple times with different package
+names.
+
+=back
+
+=head1 EXAMPLES
+
+Suppose that your source package produces libfoo1, libfoo-dev, and
+libfoo-bin binary packages. libfoo-bin links against libfoo1, and should
+depend on it. In your rules file, first run B<dh_makeshlibs>, then B<dh_shlibdeps>:
+
+ dh_makeshlibs
+ dh_shlibdeps
+
+This will have the effect of generating automatically a shlibs file for
+libfoo1, and using that file and the libfoo1 library in the
+F<debian/libfoo1/usr/lib> directory to calculate shared library dependency
+information.
+
+If a libbar1 package is also produced, that is an alternate build of
+libfoo, and is installed into F</usr/lib/bar/>, you can make libfoo-bin depend
+on libbar1 as follows:
+
+ dh_shlibdeps -Llibbar1 -l/usr/lib/bar
+
+=cut
+
+init(options => {
+ "L|libpackage=s@" => \$dh{LIBPACKAGE},
+ "dpkg-shlibdeps-params=s" => \$dh{U_PARAMS},
+ "l=s" => \$dh{L_PARAMS},
+});
+
+if (defined $dh{V_FLAG}) {
+ warning("You probably wanted to pass -V to dh_makeshlibs, it has no effect on dh_shlibdeps");
+}
+
+on_pkgs_in_parallel {
+ my $is_non_statically_linked_elf_file = sub {
+ my ($file) = @_;
+ my @file_args = Debian::Debhelper::Dh_Lib::_internal_optional_file_args();
+ my $ff = qx_cmd('file', @file_args, '--brief', '-e', 'apptype', '-e', 'ascii',
+ '-e', 'encoding', '-e', 'cdf', '-e', 'compress', '-e', 'tar', '--', $file);
+ return 1 if $ff =~ m/ELF/ && $ff !~ /statically linked/;
+ return 0;
+ };
+
+ foreach my $package (@_) {
+ my $tmp=tmpdir($package);
+ my $ext=pkgext($package);
+ my (@filelist);
+
+ # Generate a list of ELF binaries in the package, ignoring any
+ # we were told to exclude.
+ my $find_options='';
+ if (defined($dh{EXCLUDE_FIND}) && $dh{EXCLUDE_FIND} ne '') {
+ $find_options="! \\( $dh{EXCLUDE_FIND} \\)";
+ }
+ next if not -d $tmp;
+ if (compat(10)) {
+ foreach my $file (split(/\n/, `find $tmp -type f \\( -perm /111 -or -name "*.so*" -or -name "*.cmxs" -or -name "*.node" \\) $find_options -print`)) {
+ # Prune directories that contain separated debug symbols.
+ # CAVEAT: There are files in /usr/lib/debug that are not detached debug symbols,
+ # which should be processed. (see #865982)
+ next if $file =~ m!^\Q$tmp\E/usr/lib/debug/(lib|lib64|usr|bin|sbin|opt|dev|emul|\.build-id)/!;
+ # TODO this is slow, optimize. Ie, file can run once on
+ # multiple files..
+ if ($is_non_statically_linked_elf_file->($file)) {
+ push @filelist, $file;
+ }
+ }
+ } else {
+ my $find_elf_files = sub {
+ my $fn = $_;
+ return if -l $fn; # Ignore symlinks
+ # See if we were asked to exclude this file.
+ # Note that we have to test on the full filename, including directory.
+ if (excludefile($fn)) {
+ $File::Find::prune = 1 if -d _;
+ return;
+ }
+ if (-d _) {
+ # Prune directories that contain separated debug symbols.
+ # CAVEAT: There are files in /usr/lib/debug that are not detached debug symbols,
+ # which should be processed. (see #865982)
+ if ($fn =~ m!^\Q$tmp\E/usr/lib/debug/(lib|lib64|usr|bin|sbin|opt|dev|emul|\.build-id)/!) {
+ $File::Find::prune = 1;
+ }
+ return;
+ }
+
+ return if not -f _;
+ return if not is_so_or_exec_elf_file($fn);
+ # TODO this is slow, optimize. Ie, file can run once on
+ # multiple files..
+ if ($is_non_statically_linked_elf_file->($fn)) {
+ push(@filelist, $fn);
+ }
+ };
+ find({
+ wanted => $find_elf_files,
+ no_chdir => 1,
+ }, $tmp);
+ }
+
+ if (@filelist) {
+ my @opts;
+
+ # dpkg-shlibdeps expects this directory to exist
+ install_dir("$tmp/DEBIAN");
+
+ if (defined($dh{LIBPACKAGE})) {
+ @opts = map { '-S' . tmpdir($_) } @{$dh{LIBPACKAGE}};
+ }
+ push(@opts, '-dPre-Depends') if not compat(14) and package_is_essential($package);
+ push @opts, "-tudeb" if is_udeb($package);
+
+ if ($dh{L_PARAMS}) {
+ foreach (split(/:/, $dh{L_PARAMS})) {
+ # Force the path absolute.
+ my $libdir = m:^/: ? $_ : "/$_";
+ push @opts, "-l$libdir";
+ }
+ }
+
+ doit("dpkg-shlibdeps","-Tdebian/${ext}substvars",
+ @opts,@{$dh{U_PARAMS}},@filelist);
+ }
+ }
+};
+
+=head1 SEE ALSO
+
+L<debhelper(7)>, L<dpkg-shlibdeps(1)>
+
+This program is a part of debhelper.
+
+=head1 AUTHOR
+
+Joey Hess <joeyh@debian.org>
+
+=cut
diff --git a/dh_strip b/dh_strip
new file mode 100755
index 0000000..5cd32c1
--- /dev/null
+++ b/dh_strip
@@ -0,0 +1,445 @@
+#!/usr/bin/perl
+
+=head1 NAME
+
+dh_strip - strip executables, shared libraries, and some static libraries
+
+=cut
+
+use strict;
+use warnings;
+use File::Find;
+use Debian::Debhelper::Dh_Lib;
+
+our $VERSION = DH_BUILTIN_VERSION;
+
+=head1 SYNOPSIS
+
+B<dh_strip> [S<I<debhelper options>>] [B<-X>I<item>] [B<--dbg-package=>I<package>] [B<--keep-debug>]
+
+=head1 DESCRIPTION
+
+B<dh_strip> is a debhelper program that is responsible for stripping
+out debug symbols in executables, shared libraries, and static
+libraries that are not needed during execution.
+
+This program examines your package build directories and works out what
+to strip on its own. It uses L<file(1)> and file permissions and filenames
+to figure out what files are shared libraries (F<*.so>), executable binaries,
+and static (F<lib*.a>) and debugging libraries (F<lib*_g.a>, F<debug/*.so>), and
+strips each as much as is possible. (Which is not at all for debugging
+libraries.) In general it seems to make very good guesses, and will do the
+right thing in almost all cases.
+
+Since it is very hard to automatically guess if a file is a
+module, and hard to determine how to strip a module, B<dh_strip> does not
+currently deal with stripping binary modules such as F<.o> files.
+
+=head1 OPTIONS
+
+=over 4
+
+=item B<-X>I<item>, B<--exclude=>I<item>
+
+Exclude files that contain I<item> anywhere in their filename from being
+stripped. You may use this option multiple times to build up a list of
+things to exclude.
+
+=item B<--dbg-package=>I<package>
+
+B<This option is a now special purpose option that you normally do not
+need>. In most cases, there should be little reason to use this
+option for new source packages as debhelper automatically generates
+debug packages ("dbgsym packages"). B<If you have a manual
+--dbg-package> that you want to replace with an automatically
+generated debug symbol package, please see the B<--dbgsym-migration>
+option.
+
+Causes B<dh_strip> to save debug symbols stripped from the packages it acts on
+as independent files in the package build directory of the specified debugging
+package.
+
+For example, if your packages are libfoo and foo and you want to include a
+I<foo-dbg> package with debugging symbols, use B<dh_strip --dbg-package=>I<foo-dbg>.
+
+This option implies B<--no-automatic-dbgsym> and I<cannot> be used
+with B<--automatic-dbgsym> or B<--dbgsym-migration>.
+
+=item B<-k>, B<--keep-debug>
+
+B<This option is a now special purpose option that you normally do not
+need>. In most cases, there should be little reason to use this
+option for new source packages as debhelper automatically generates
+debug packages ("dbgsym packages"). B<If you have a manual
+--dbg-package> that you want to replace with an automatically
+generated debug symbol package, please see the B<--dbgsym-migration>
+option.
+
+Debug symbols will be retained, but split into an independent
+file in F<usr/lib/debug/> in the package build directory. B<--dbg-package>
+is easier to use than this option, but this option is more flexible.
+
+This option implies B<--no-automatic-dbgsym> and I<cannot> be used
+with B<--automatic-dbgsym>.
+
+=item B<--dbgsym-migration=>I<package-relation>
+
+This option is used to migrate from a manual "-dbg" package (created
+with B<--dbg-package>) to an automatic generated debug symbol
+package. This option should describe a valid B<Replaces>- and
+B<Breaks>-relation, which will be added to the debug symbol package to
+avoid file conflicts with the (now obsolete) -dbg package.
+
+This option implies B<--automatic-dbgsym> and I<cannot> be used with
+B<--keep-debug>, B<--dbg-package> or B<--no-automatic-dbgsym>.
+
+Examples:
+
+ dh_strip --dbgsym-migration='libfoo-dbg (<< 2.1-3~)'
+
+ dh_strip --dbgsym-migration='libfoo-tools-dbg (<< 2.1-3~), libfoo2-dbg (<< 2.1-3~)'
+
+=item B<--automatic-dbgsym>, B<--no-automatic-dbgsym>
+
+Control whether B<dh_strip> should be creating debug symbol packages
+when possible.
+
+The default is to create debug symbol packages.
+
+=item B<--ddebs>, B<--no-ddebs>
+
+Historical name for B<--automatic-dbgsym> and B<--no-automatic-dbgsym>.
+
+=item B<--ddeb-migration=>I<package-relation>
+
+Historical name for B<--dbgsym-migration>.
+
+=back
+
+=head1 NOTES
+
+If the B<DEB_BUILD_OPTIONS> environment variable contains B<nostrip>,
+nothing will be stripped, in accordance with Debian policy (section
+10.1 "Binaries"). This will also inhibit the automatic creation of
+debug symbol packages.
+
+The automatic creation of debug symbol packages can also be prevented
+by adding B<noautodbgsym> to the B<DEB_BUILD_OPTIONS> environment
+variable. However, B<dh_strip> will still add debuglinks to ELF
+binaries when this flag is set. This is to ensure that the regular
+deb package will be identical with and without this flag (assuming it
+is otherwise "bit-for-bit" reproducible).
+
+=head1 CONFORMS TO
+
+Debian policy, version 3.0.1
+
+=cut
+
+init(options => {
+ 'keep-debug|keep|k' => \$dh{K_FLAG},
+ 'dbgsym-migration=s' => \$dh{MIGRATE_DBGSYM},
+ 'automatic-dbgsym!' => \$dh{ENABLE_DBGSYM},
+ # Deprecated variants
+ 'ddeb-migration=s' => \$dh{MIGRATE_DBGSYM},
+ 'ddebs!' => \$dh{ENABLE_DBGSYM},
+
+});
+
+if ($dh{MIGRATE_DBGSYM}) {
+ error("--keep-debug and --dbgsym-migration are mutually exclusive") if ($dh{K_FLAG});
+ error("--dbg-package and --dbgsym-migration are mutually exclusive") if ($dh{DEBUGPACKAGE});
+}
+
+if ($dh{ENABLE_DBGSYM}) {
+ error("--keep-debug and explicit --automatic-dbgsym are mutually exclusive") if ($dh{K_FLAG});
+ error("--dbg-package and explicit --automatic-dbgsym are mutually exclusive") if ($dh{DEBUGPACKAGE});
+}
+
+$dh{ENABLE_DBGSYM} = 1 if not defined($dh{ENABLE_DBGSYM});
+
+if ($dh{MIGRATE_DBGSYM} and not $dh{ENABLE_DBGSYM}) {
+ error("--dbgsym-migration and --no-automatic-dbgsym are mutually exclusive");
+}
+
+# This variable can be used to turn off stripping (see Policy).
+exit 0 if (get_buildoption('nostrip'));
+
+my $no_auto_dbgsym = 0;
+$no_auto_dbgsym = 1 if get_buildoption('noautodbgsym') or get_buildoption('noddebs');
+
+# Check if a file is an elf binary, shared library, or static library,
+# for use by File::Find. It'll fill the 3 first arrays with anything
+# it finds. The @build_ids will be the collected build-ids (if any)
+my (@shared_libs, @executables, @static_libs, @build_ids, %file_output);
+sub testfile {
+ my $fn = $_;
+ return if -l $fn; # Always skip symlinks.
+
+ # See if we were asked to exclude this file.
+ # Note that we have to test on the full filename, including directory.
+ if (excludefile($fn)) {
+ $File::Find::prune = 1 if -d _;
+ return;
+ }
+ # Ignore the .../debug/.build-id/ directory. It is not really helpful
+ # to strip debug symbols.
+ $File::Find::prune = 1 if -d _ && index($fn, '/debug/.build-id/') > -1;
+ return if -d _;
+
+ # Is it a debug library in a debug subdir?
+ return if $fn=~m{debug/.*\.so};
+ return if $fn=~m{/guile/.*\.go$};
+
+ # Exploit the previous stat call to get the $mode, so we can check
+ # later if it is executable or not.
+ #
+ # NB: compat() can issue a stat, so we /should/ do this now
+ my (undef, undef, $mode, undef) = stat(_);
+
+ if (compat(10)) {
+ # In compat 10 and earlier, we used filenames and file(1)
+
+ # Does its filename look like a shared library?
+ # - *.cmxs are OCaml native code shared libraries
+ # - *.node are also native ELF binaries (for node-js)
+ if ($fn =~ m/\.(?:so.*?|cmxs|node)$/) {
+ # Ok, do the expensive test.
+ my $type = get_file_type($fn, 1);
+ if ($type =~ m/ELF.*shared/) {
+ push @shared_libs, $fn;
+ return;
+ }
+ }
+
+ # -x is not good enough for this test
+ if ($mode & 0111) {
+ # Ok, expensive test.
+ my $type = get_file_type($fn, 1);
+ if ($type =~ m/ELF.*(executable|shared)/) {
+ push(@executables, $fn);
+ return;
+ }
+ }
+ } else {
+ # In compat 11, we check the ELF header manually (because bulking file(1) is a pain and
+ # it is too slow otherwise)
+
+ if (is_so_or_exec_elf_file($fn)) {
+ # -x is not good enough for this test
+ if ($mode & 0111) {
+ push(@executables, $fn);
+ } else {
+ push(@shared_libs, $fn);
+ }
+ return;
+ }
+ }
+ # Is it a static library, and not a debug library?
+ if ($fn =~ m/\/lib[^\/]*\.a$/ && $fn !~ m/.*_g\.a$/) {
+ # Is it a binary file, or something else (maybe a linker
+ # script on Hurd, for example? I don't use file, because
+ # file returns a variety of things on static libraries.
+ if (-B $fn) {
+ push @static_libs, $fn;
+ return;
+ }
+ }
+}
+
+sub write_buildid_file {
+ my ($package, $build_ids) = @_;
+ my $dir = "debian/.debhelper/${package}";
+ my $path = "${dir}/dbgsym-build-ids";
+ install_dir($dir);
+ open(my $fd, '>>', $path) or error("open $path failed: $!");
+ print {$fd} join(q{ }, sort(@{$build_ids})) . ' ';
+ close($fd) or error("close $path failed: $!");
+}
+
+# I could just use `file $_[0]`, but this is safer
+sub get_file_type {
+ my ($file, $cache_ok) = @_;
+ return $file_output{$file} if $cache_ok && $file_output{$file};
+ my @file_args = Debian::Debhelper::Dh_Lib::_internal_optional_file_args();
+ my @cmdline = ('file', @file_args, '--brief', '-e', 'apptype', '-e', 'ascii', '-e', 'encoding', '-e', 'cdf',
+ '-e', 'compress', '-e', 'tar', '--', $file);
+
+ open(my $fd, '-|', @cmdline) // error("cannot fork+exec file: $!");
+ my $type = <$fd>;
+ close($fd) || error_exitcode(escape_shell(@cmdline));
+
+ error("file(1) gave no result for $file!?") if (not $type) ;
+ return $file_output{$file} = $type;
+}
+
+sub make_debug {
+ my ($objcopy, $file, $tmp, $desttmp, $use_build_id) = @_;
+ my ($debug_path, $debug_build_id);
+
+ # Don't try to copy debug symbols out if the file is already
+ # stripped.
+ #
+ # Disable caching for non-build-id based extractions.
+ # Unfortunately, it breaks when there are hardlinks to the same
+ # ELF files.
+ my $file_info = get_file_type($file, $use_build_id ? 1 : 0);
+ return unless $file_info =~ /not stripped/;
+
+ if ($use_build_id) {
+ if ($file_info =~ m/BuildID\[sha1]\s*=\s*([0-9a-f]{2})([0-9a-f]+)/ or
+ `LC_ALL=C readelf -n $file`=~ /^\s+Build ID: ([0-9a-f]{2})([0-9a-f]+)$/m) {
+ $debug_path=$desttmp."/usr/lib/debug/.build-id/$1/$2.debug";
+ $debug_build_id="${1}${2}";
+ push(@build_ids, $debug_build_id);
+ } else {
+ # For dbgsyms, we need build-id (else it will not be
+ # co-installable).
+ warning("Could not find the BuildID in $file");
+ return if $use_build_id > 1;
+ }
+ }
+ if (not $debug_path) {
+ # Either not using build_id OR no build-id available
+ my ($base_file)=$file=~/^\Q$tmp\E(.*)/;
+ $debug_path=$desttmp."/usr/lib/debug/".$base_file;
+ }
+ install_dir(dirname($debug_path));
+ if (compat(8) && $use_build_id < 2) {
+ doit($objcopy, "--only-keep-debug", $file, $debug_path);
+ }
+ else {
+ # Compat 9 OR a dbgsym package.
+ doit($objcopy, "--only-keep-debug", "--compress-debug-sections", $file, $debug_path) unless -e $debug_path;
+ }
+
+ # No reason for this to be executable.
+ reset_perm_and_owner(0644, $debug_path);
+ return $debug_path;
+}
+
+sub attach_debug {
+ my ($objcopy, $file, $debug_path) = @_;
+ doit($objcopy, "--add-gnu-debuglink", $debug_path, $file);
+}
+
+my %all_packages = map { $_ => 1 } getpackages();
+
+sub process_packages {
+ foreach my $package (@_) {
+ my $tmp=tmpdir($package);
+ my $objcopy = cross_command($package, "objcopy");
+ my $strip = cross_command($package, "strip");
+
+ # Support for keeping the debugging symbols in a detached file.
+ my $keep_debug=$dh{K_FLAG};
+ my $debugtmp=$tmp;
+ my $use_build_id = compat(8) ? 0 : 1;
+ if ($dh{DEBUGPACKAGE}) {
+ $keep_debug=1;
+ my $debugpackage=$dh{DEBUGPACKAGE};
+ error("debug package $debugpackage is not listed in the control file") if (!$all_packages{$debugpackage});
+ $debugtmp=tmpdir($debugpackage);
+ }
+ # Temporary workaround: Do not build dbgsym packages for udebs as
+ # dpkg-gencontrol and dpkg-deb does not agree on the file
+ # extension.
+ if ($dh{ENABLE_DBGSYM} and not $keep_debug and not package_is_arch_all($package) and not is_udeb($package)) {
+ # Avoid creating a dbgsym that would clash with a registered
+ # package or looks like a manual -dbg package.
+ if (not $all_packages{"${package}-dbgsym"} and $package !~ m/-dbg(?:sym)?$/) {
+ $debugtmp = dbgsym_tmpdir($package);
+ $keep_debug = 1;
+ $use_build_id = 2;
+ }
+ }
+ %file_output=@shared_libs=@executables=@static_libs=();
+ find({
+ wanted => \&testfile,
+ no_chdir => 1,
+ }, $tmp);
+
+ foreach (@shared_libs) {
+ my $debug_path = make_debug($objcopy, $_, $tmp, $debugtmp, $use_build_id) if $keep_debug;
+ # Note that all calls to strip on shared libs
+ # *must* include the --strip-unneeded.
+ doit($strip, "--remove-section=.comment", "--remove-section=.note", "--strip-unneeded", $_);
+ attach_debug($objcopy, $_, $debug_path) if defined $debug_path;
+ }
+
+ foreach (@executables) {
+ my $debug_path = make_debug($objcopy, $_, $tmp, $debugtmp, $use_build_id) if $keep_debug;
+ doit($strip, "--remove-section=.comment", "--remove-section=.note", $_);
+ attach_debug($objcopy, $_, $debug_path) if defined $debug_path;
+ }
+
+ foreach (@static_libs) {
+ # NB: The short variant (-D) is broken in Jessie
+ # (binutils/2.25-3)
+ doit($strip, '--strip-debug', '--remove-section=.comment',
+ '--remove-section=.note', '--enable-deterministic-archives',
+ '-R', '.gnu.lto_*', '-R', '.gnu.debuglto_*',
+ '-N', '__gnu_lto_slim', '-N', '__gnu_lto_v1',
+ $_);
+ }
+ if (-d "$tmp/usr/lib/debug/.dwz" and ($use_build_id > 1 or ($dh{DEBUGPACKAGE} and $dh{DEBUGPACKAGE} ne $package))) {
+ my @files = glob_expand(["$tmp/usr/lib/debug/.dwz"], \&glob_expand_error_handler_reject, '*');
+ install_dir("$debugtmp/usr/lib/debug/.dwz");
+ xargs(\@files, 'cp', '--reflink=auto', "-a", XARGS_INSERT_PARAMS_HERE, "$debugtmp/usr/lib/debug/.dwz");
+ doit('rm', '-fr', "$tmp/usr/lib/debug/.dwz");
+ doit('rmdir', '-p', '--ignore-fail-on-non-empty', "$tmp/usr/lib/debug");
+ }
+
+ if ($no_auto_dbgsym and $use_build_id > 1) {
+ # When DEB_BUILD_OPTIONS contains noautodbgsym, remove the
+ # dbgsym dir and clear the build-ids.
+ #
+ # Note we have to extract the dbg symbols as usual, since
+ # attach_debug (objcopy --add-gnu-debuglink) requires the dbg
+ # file to exist.
+ doit('rm', '-fr', $debugtmp);
+ @build_ids = ();
+ }
+ if ($use_build_id > 1 and -d $debugtmp) {
+ my $dbgsym_docdir = "${debugtmp}/usr/share/doc";
+ my $doc_symlink = "${dbgsym_docdir}/${package}-dbgsym";
+ if ( not -l $doc_symlink and not -e _ ) {
+ install_dir($dbgsym_docdir);
+ make_symlink_raw_target($package, $doc_symlink);
+ }
+ if ($dh{MIGRATE_DBGSYM}) {
+ my $path = "debian/.debhelper/${package}/dbgsym-migration";
+ open(my $fd, '>', $path) or error("open $path failed: $!");
+ print {$fd} "$dh{MIGRATE_DBGSYM}\n";
+ close($fd) or error("close $path failed: $!");
+ }
+ }
+ if ($use_build_id > 1 and @build_ids) {
+ write_buildid_file($package, \@build_ids);
+ @build_ids = ();
+ }
+ }
+ if (@build_ids and $dh{DEBUGPACKAGE}) {
+ write_buildid_file($dh{DEBUGPACKAGE}, \@build_ids);
+ }
+}
+
+if ($dh{DEBUGPACKAGE}) {
+ # Non-deterministic issues with --dbg-package and parallelism (see
+ # #872007). Analysis and patches welcome for this case.
+ process_packages(@{$dh{DOPACKAGES}});
+} else {
+ on_pkgs_in_parallel(\&process_packages);
+}
+
+=head1 SEE ALSO
+
+L<debhelper(7)>
+
+This program is a part of debhelper.
+
+=head1 AUTHOR
+
+Joey Hess <joeyh@debian.org>
+
+=cut
diff --git a/dh_systemd_enable b/dh_systemd_enable
new file mode 100755
index 0000000..e7c02ba
--- /dev/null
+++ b/dh_systemd_enable
@@ -0,0 +1,292 @@
+#!/usr/bin/perl -w
+
+=head1 NAME
+
+dh_systemd_enable - enable/disable systemd unit files
+
+=cut
+
+use strict;
+use warnings;
+use Debian::Debhelper::Dh_Lib;
+use File::Find;
+
+our $VERSION = DH_BUILTIN_VERSION;
+
+=head1 SYNOPSIS
+
+B<dh_systemd_enable> [S<I<debhelper options>>] [B<--no-enable>] [B<--name=>I<name>] [S<I<unit file> ...>]
+
+=head1 DESCRIPTION
+
+B<dh_systemd_enable> is a debhelper program that is responsible for enabling
+and disabling systemd unit files.
+
+In the simple case, it finds all unit files installed by a package (e.g.
+bacula-fd.service) and enables them. It is not necessary that the machine
+actually runs systemd during package installation time, enabling happens on all
+machines in order to be able to switch from sysvinit to systemd and back.
+
+In the complex case, you can call B<dh_systemd_enable> and B<dh_systemd_start>
+manually (by overwriting the debian/rules targets) and specify flags per unit
+file. An example is colord, which ships colord.service, a dbus-activated
+service without an [Install] section. This service file cannot be enabled or
+disabled (a state called "static" by systemd) because it has no
+[Install] section. Therefore, running dh_systemd_enable does not make sense.
+
+For only generating blocks for specific service files, you need to pass them as
+arguments, e.g. B<dh_systemd_enable quota.service> and B<dh_systemd_enable
+--name=quotarpc quotarpc.service>.
+
+=head1 FILES
+
+=over 4
+
+=item debian/I<package>.service, debian/I<package>@.service
+
+If this exists, it is installed into F<< usr/lib/systemd/system/I<package>.service >> (or
+F<< usr/lib/systemd/system/I<package>@.service >>) in the package build directory.
+
+=item debian/I<package>.tmpfile
+
+If this exists, it is installed into usr/lib/tmpfiles.d/I<package>.conf in the
+package build directory.
+
+=item debian/I<package>.target, debian/I<package>@.target
+
+If this exists, it is installed into F<< usr/lib/systemd/system/I<package>.target >> (or
+F<< usr/lib/systemd/system/I<package>@.target >>) in the package build directory.
+
+=item debian/I<package>.socket, debian/I<package>@.socket
+
+If this exists, it is installed into F<< usr/lib/systemd/system/I<package>.socket >> (or
+F<< usr/lib/systemd/system/I<package>@.socket >>) in the package build directory.
+
+=item debian/I<package>.mount
+
+If this exists, it is installed into F<< usr/lib/systemd/system/I<package>.mount >>
+in the package build directory.
+
+=item debian/I<package>.path, debian/I<package>@.path
+
+If this exists, it is installed into F<< usr/lib/systemd/system/I<package>.path >> (or
+F<< usr/lib/systemd/system/I<package>@.path >>) in the package build directory.
+
+=item debian/I<package>.timer, debian/I<package>@.timer
+
+If this exists, it is installed into F<< usr/lib/systemd/system/I<package>.timer >> (or
+F<< usr/lib/systemd/system/I<package>@.timer >>) in the package build directory.
+
+=back
+
+=head1 OPTIONS
+
+=over 4
+
+=item B<--no-enable>
+
+Disable the service(s) on purge, but do not enable them on install.
+
+B<Note> that this option does not affect whether the services are
+started. That is controlled by L<dh_systemd_start(1)> (using e.g. its
+B<--no-start> option).
+
+=item B<--name=>I<name>
+
+Install the service file as I<name.service> instead of the default filename,
+which is the I<package.service>. When this parameter is used,
+B<dh_systemd_enable> looks for and installs files named
+F<debian/package.name.service> instead of the usual F<debian/package.service>.
+
+=back
+
+=head1 NOTES
+
+Note that this command is not idempotent. L<dh_prep(1)> should be called
+between invocations of this command (with the same arguments). Otherwise, it
+may cause multiple instances of the same text to be added to maintainer
+scripts.
+
+Note that B<dh_systemd_enable> should be run before B<dh_installinit>.
+The default sequence in B<dh> does the right thing, this note is only relevant
+when you are calling B<dh_systemd_enable> manually.
+
+=cut
+
+if (not compat(10)) {
+ error("dh_systemd_enable is no longer used in compat >= 11, please use dh_installsystemd instead");
+}
+
+init(options => {
+ "no-enable" => \$dh{NO_ENABLE},
+});
+
+sub contains_install_section {
+ my ($unit_path) = @_;
+ open(my $fh, '<', $unit_path) or error("Cannot open($unit_path) to check for [Install]: $!");
+ while (my $line = <$fh>) {
+ chomp($line);
+ return 1 if $line =~ /^\s*\[Install\]$/i;
+ }
+ close($fh);
+ return 0;
+}
+
+sub install_unit {
+ my ($package, $script, $pkgsuffix, $path, $installsuffix) = @_;
+ $installsuffix = $installsuffix || $pkgsuffix;
+ my $unit = pkgfile($package, $pkgsuffix);
+ return if $unit eq '';
+ install_dir($path);
+ install_file($unit, "${path}/${script}.${installsuffix}");
+}
+
+# PROMISE: DH NOOP WITHOUT tmp(lib/systemd/system) tmp(usr/lib/systemd/system) mount path service socket target tmpfile timer
+
+my %requested_files = map { basename($_) => 1 } @ARGV;
+my %installed_files;
+
+foreach my $package (@{$dh{DOPACKAGES}}) {
+ my $tmpdir = tmpdir($package);
+ my @installed_units;
+ my @units;
+
+ # XXX: This is duplicated in dh_installinit, which is unfortunate.
+ # We do need the service files before running dh_installinit though,
+ # every other solution makes things much worse for all the maintainers.
+
+ # Figure out what filename to install it as.
+ my $script;
+ my $jobfile=$package;
+ if (defined $dh{NAME}) {
+ $jobfile=$script=$dh{NAME};
+ }
+ elsif ($dh{D_FLAG}) {
+ # -d on the command line sets D_FLAG. We will
+ # remove a trailing 'd' from the package name and
+ # use that as the name.
+ $script=$package;
+ if ($script=~m/(.*)d$/) {
+ $jobfile=$script=$1;
+ }
+ else {
+ warning("\"$package\" has no final d' in its name, but -d was specified.");
+ }
+ }
+ elsif ($dh{INIT_SCRIPT}) {
+ $script=$dh{INIT_SCRIPT};
+ }
+ else {
+ $script=$package;
+ }
+
+ for my $service_type (qw(service target socket path timer)) {
+ install_unit($package, $script, $service_type, "$tmpdir/usr/lib/systemd/system");
+ install_unit("${package}@", "${script}@", $service_type, "$tmpdir/usr/lib/systemd/system");
+ }
+
+ install_unit($package, $script, 'mount', "$tmpdir/usr/lib/systemd/system");
+ install_unit($package, $script, 'tmpfile', "$tmpdir/usr/lib/tmpfiles.d", 'conf');
+
+ foreach my $unitdir ("${tmpdir}/usr/lib/systemd/system", "${tmpdir}/lib/systemd/system") {
+ find({
+ wanted => sub {
+ my $name = $File::Find::name;
+ return unless -f $name;
+ # Skip symbolic links, their only legitimate
+ # use is for adding an alias, e.g. linking
+ # smartmontools.service -> smartd.service.
+ return if -l $name;
+ return unless $name =~ m,^\Q$unitdir\E/[^/]+$,;
+ error("Unit $name is installed in both the /usr/lib/systemd/system and /lib/systemd/system directories of $package.")
+ if (grep { $_ eq $name } @installed_units);
+ push @installed_units, $name;
+ },
+ no_chdir => 1,
+ }, $unitdir) if -d $unitdir;
+ }
+
+ # Handle either only the unit files which were passed as arguments or
+ # all unit files that are installed in this package.
+ my @args = @ARGV > 0 ? @ARGV : @installed_units;
+
+ # support excluding units via -X
+ foreach my $x (@{$dh{EXCLUDE}}) {
+ @args = grep !/(^|\/)$x$/, @args;
+ }
+
+ for my $name (@args) {
+ my $base = basename($name);
+ # Try to make the path absolute, so that the user can call
+ # dh_installsystemd bacula-fd.service
+ if ($base eq $name) {
+ # NB: This works because each unit in @installed_units
+ # comes from exactly one directory.
+ my ($full) = grep { basename($_) eq $base } @installed_units;
+ if (defined($full)) {
+ $name = $full;
+ } elsif (not exists($requested_files{$base})) {
+ warning(qq|Could not find "$name" in the /lib/systemd/system directory of $package. | .
+ qq|This could be a typo, or using Also= with a service file from another package. | .
+ qq|Please check carefully that this message is harmless.|);
+ } else {
+ # Ignore an explicitly requested file that is missing; happens when we are acting on
+ # multiple packages and only a subset of them have the unit file.
+ next;
+ }
+ }
+
+ $installed_files{$base} = 1 if exists($requested_files{$base});
+
+ # Skip template service files like e.g. getty@.service.
+ # Enabling, disabling, starting or stopping those services
+ # without specifying the instance (e.g. getty@ttyS0.service) is
+ # not useful.
+ if ($name =~ /\@/) {
+ next;
+ }
+
+ # Skip unit files that don’t have an [Install] section.
+ next unless contains_install_section($name);
+
+ push @units, $name;
+ }
+
+ next if @units == 0;
+
+ # Wrap the basenames in '' to preserve \x2d when the shell parses the
+ # name. (#764730)
+ my $unitargs = join(' ', sort map { q{'} . basename($_) . q{'} } @units);
+ for my $unit (sort @units) {
+ # Wrap the basenames in '' to preserve \x2d when the shell parses the
+ # name. (#764730)
+ my $base = q{'} . basename($unit) . q{'};
+ if ($dh{NO_ENABLE}) {
+ autoscript($package, 'postinst', 'postinst-systemd-dont-enable', { 'UNITFILE' => $base });
+ } else {
+ autoscript($package, 'postinst', 'postinst-systemd-enable', { 'UNITFILE' => $base });
+ }
+ }
+ autoscript($package, 'postrm', 'postrm-systemd', {'UNITFILES' => $unitargs });
+}
+
+if (%requested_files) {
+ my $any_missing = 0;
+ for my $name (sort(keys(%requested_files))) {
+ if (not exists($installed_files{$name})) {
+ warning(qq{Requested unit "$name" but it was not found in any package acted on.});
+ $any_missing = 1;
+ }
+ }
+ error("Could not handle all of the requested services") if $any_missing;
+}
+
+=head1 SEE ALSO
+
+L<dh_systemd_start(1)>, L<debhelper(7)>
+
+=head1 AUTHORS
+
+pkg-systemd-maintainers@lists.alioth.debian.org
+
+=cut
diff --git a/dh_systemd_start b/dh_systemd_start
new file mode 100755
index 0000000..a260948
--- /dev/null
+++ b/dh_systemd_start
@@ -0,0 +1,292 @@
+#!/usr/bin/perl -w
+
+=head1 NAME
+
+dh_systemd_start - start/stop/restart systemd unit files
+
+=cut
+
+use strict;
+use warnings;
+use Debian::Debhelper::Dh_Lib;
+use File::Find;
+use Cwd qw(getcwd abs_path);
+
+our $VERSION = DH_BUILTIN_VERSION;
+
+=head1 SYNOPSIS
+
+B<dh_systemd_start> [S<I<debhelper options>>] [B<--restart-after-upgrade>] [B<--no-stop-on-upgrade>] [S<I<unit file> ...>]
+
+=head1 DESCRIPTION
+
+B<dh_systemd_start> is a debhelper program that is responsible for
+starting/stopping or restarting systemd unit files in case no corresponding
+sysv init script is available.
+
+As with B<dh_installinit>, the unit file is stopped before
+upgrades and started afterwards (unless B<--restart-after-upgrade> is
+specified, in which case it will only be restarted after the upgrade).
+This logic is not used when there is a corresponding SysV init script
+because invoke-rc.d performs the stop/start/restart in that case.
+
+=head1 OPTIONS
+
+=over 4
+
+=item B<--restart-after-upgrade>
+
+Do not stop the unit file until after the package upgrade has been completed.
+This is the default behaviour in compat 10.
+
+In earlier compat levels the default was to stop the unit file in the
+F<prerm>, and start it again in the F<postinst>.
+
+This can be useful for daemons that should not have a possibly long
+downtime during upgrade. But you should make sure that the daemon will not
+get confused by the package being upgraded while it's running before using
+this option.
+
+=item B<--no-restart-after-upgrade>
+
+Undo a previous B<--restart-after-upgrade> (or the default of compat
+10). If no other options are given, this will cause the service to be
+stopped in the F<prerm> script and started again in the F<postinst>
+script.
+
+=item B<-r>, B<--no-stop-on-upgrade>, B<--no-restart-on-upgrade>
+
+Do not stop service on upgrade.
+
+=item B<--no-start>
+
+Do not start the unit file after upgrades and after initial installation (the
+latter is only relevant for services without a corresponding init script).
+
+=back
+
+=head1 NOTES
+
+Note that this command is not idempotent. L<dh_prep(1)> should be called
+between invocations of this command (with the same arguments). Otherwise, it
+may cause multiple instances of the same text to be added to maintainer
+scripts.
+
+Note that B<dh_systemd_start> should be run after B<dh_installinit> so that it
+can detect corresponding SysV init scripts. The default sequence in B<dh> does
+the right thing, this note is only relevant when you are calling
+B<dh_systemd_start> manually.
+
+=cut
+
+if (not compat(10)) {
+ error("dh_systemd_start is no longer used in compat >= 11, please use dh_installsystemd instead");
+}
+
+$dh{RESTART_AFTER_UPGRADE} = 1 if not compat(9);
+
+init(options => {
+ "r" => \$dh{R_FLAG},
+ 'no-stop-on-upgrade' => \$dh{R_FLAG},
+ "no-restart-on-upgrade" => \$dh{R_FLAG},
+ "no-start" => \$dh{NO_START},
+ "R|restart-after-upgrade!" => \$dh{RESTART_AFTER_UPGRADE},
+ "no-also" => \$dh{NO_ALSO},
+});
+
+# Extracts the Also= or Alias= line(s) from a unit file.
+# In case this produces horribly wrong results, you can pass --no-also, but
+# that should really not be necessary. Please report bugs to
+# pkg-systemd-maintainers.
+sub extract_key {
+ my ($unit_path, $key) = @_;
+ my @values;
+
+ return if $dh{NO_ALSO};
+
+ open(my $fh, '<', $unit_path) or error("Cannot open($unit_path) for extracting the Also= line(s): $!");
+
+ while (my $line = <$fh>) {
+ chomp($line);
+
+ # The keys parsed from the unit file below can only have
+ # unit names as values. Since unit names can't have
+ # whitespace in systemd, simply use split and strip any
+ # leading/trailing quotes. See systemd-escape(1) for
+ # examples of valid unit names.
+ if ($line =~ /^\s*$key=(.+)$/i) {
+ for my $value (split(/\s+/, $1)) {
+ $value =~ s/^(["'])(.*)\g1$/$2/;
+ push @values, $value;
+ }
+ }
+ }
+ close($fh);
+ return @values;
+}
+
+
+# PROMISE: DH NOOP WITHOUT tmp(lib/systemd/system) tmp(usr/lib/systemd/system)
+
+my %requested_files = map { basename($_) => 1 } @ARGV;
+my %installed_files;
+
+foreach my $package (@{$dh{DOPACKAGES}}) {
+ my $tmpdir = tmpdir($package);
+ my @installed_units;
+ my @units;
+ my %aliases;
+
+ my $oldcwd = getcwd();
+ foreach my $unitdir ("${tmpdir}/usr/lib/systemd/system", "${tmpdir}/lib/systemd/system") {
+ find({
+ wanted => sub {
+ my $name = $File::Find::name;
+ return unless -f;
+ return unless $name =~ m,^\Q$unitdir\E/[^/]+$,;
+ if (-l) {
+ my $target = abs_path(readlink());
+ $target =~ s,^\Q${oldcwd}\E/,,g;
+ $aliases{$target} = [ $_ ];
+ } else {
+ error("Unit $name is installed in both the /usr/lib/systemd/system and /lib/systemd/system directories of $package.")
+ if (grep { $_ eq $name } @installed_units);
+ push @installed_units, $name;
+ }
+ },
+ }, $unitdir) if -d $unitdir;
+ chdir($oldcwd);
+ }
+
+ # Handle either only the unit files which were passed as arguments or
+ # all unit files that are installed in this package.
+ my @args = @ARGV > 0 ? @ARGV : @installed_units;
+
+ # support excluding units via -X
+ foreach my $x (@{$dh{EXCLUDE}}) {
+ @args = grep !/(^|\/)$x$/, @args;
+ }
+
+ # This hash prevents us from looping forever in the following while loop.
+ # An actual real-world example of such a loop is systemd’s
+ # systemd-readahead-drop.service, which contains
+ # Also=systemd-readahead-collect.service, and that file in turn
+ # contains Also=systemd-readahead-drop.service, thus forming an endless
+ # loop.
+ my %seen;
+
+ # We use while/shift because we push to the list in the body.
+ while (@args) {
+ my $name = shift @args;
+ my $base = basename($name);
+
+ # Try to make the path absolute, so that the user can call
+ # dh_installsystemd bacula-fd.service
+ if ($base eq $name) {
+ # NB: This works because each unit in @installed_units
+ # comes from exactly one directory.
+ my ($full) = grep { basename($_) eq $base } @installed_units;
+ if (defined($full)) {
+ $name = $full;
+ } elsif (not exists($requested_files{$base})) {
+ warning(qq|Could not find "$name" in the /lib/systemd/system directory of $package. | .
+ qq|This could be a typo, or using Also= with a service file from another package. | .
+ qq|Please check carefully that this message is harmless.|);
+ } else {
+ # Ignore an explicitly requested file that is missing; happens when we are acting on
+ # multiple packages and only a subset of them have the unit file.
+ next;
+ }
+ }
+
+ $installed_files{$base} = 1 if exists($requested_files{$base});
+
+ # Skip template service files like e.g. getty@.service.
+ # Enabling, disabling, starting or stopping those services
+ # without specifying the instance (e.g. getty@ttyS0.service) is
+ # not useful.
+ if ($name =~ /\@/) {
+ next;
+ }
+
+ # Handle all unit files specified via Also= explicitly.
+ # This is not necessary for enabling, but for disabling, as we
+ # cannot read the unit file when disabling (it was already
+ # deleted).
+ my @also = grep { !exists($seen{$_}) } extract_key($name, 'Also');
+ $seen{$_} = 1 for @also;
+ @args = (@args, @also);
+
+ push @{$aliases{$name}}, $_ for extract_key($name, 'Alias');
+ my @sysv = grep {
+ my $base = $_;
+ $base =~ s/\.(?:mount|service|socket|target|path)$//g;
+ -f "$tmpdir/etc/init.d/$base"
+ } ($base, @{$aliases{$name}});
+ if (@sysv == 0 && !grep { $_ eq $name } @units) {
+ push @units, $name;
+ }
+ }
+
+ next if @units == 0;
+
+ # Wrap the basenames in '' to preserve \x2d when the shell parses the
+ # name. (#764730)
+ my $unitargs = join(' ', sort map { q{'} . basename($_) . q{'} } @units);
+ # The $package and $sed parameters are always the same.
+ # This wrapper function makes the following logic easier to read.
+ my $sd_autoscript = sub {
+ my ($script, $filename, $restart_action) = @_;
+ my $replace = {
+ 'UNITFILES' => $unitargs,
+ 'RESTART_ACTION' => $restart_action // '',
+ };
+ autoscript($package, $script, $filename, $replace);
+ };
+
+ if ($dh{RESTART_AFTER_UPGRADE}) {
+ my ($snippet, $restart_action);
+ if ($dh{NO_START}) {
+ $snippet = 'postinst-systemd-restartnostart';
+ $restart_action = 'try-restart';
+ } else {
+ $snippet = 'postinst-systemd-restart';
+ $restart_action = 'restart';
+ }
+ $sd_autoscript->("postinst", $snippet, $restart_action);
+ } elsif (!$dh{NO_START}) {
+ # We need to stop/start before/after the upgrade.
+ $sd_autoscript->("postinst", "postinst-systemd-start");
+ }
+
+ $sd_autoscript->("postrm", "postrm-systemd-reload-only");
+
+ if ($dh{R_FLAG} || $dh{RESTART_AFTER_UPGRADE}) {
+ # stop service only on remove
+ $sd_autoscript->("prerm", "prerm-systemd-restart");
+ } elsif (!$dh{NO_START}) {
+ # always stop service
+ $sd_autoscript->("prerm", "prerm-systemd");
+ }
+}
+
+if (%requested_files) {
+ my $any_missing = 0;
+ for my $name (sort(keys(%requested_files))) {
+ if (not exists($installed_files{$name})) {
+ warning(qq{Requested unit "$name" but it was not found in any package acted on.});
+ $any_missing = 1;
+ }
+ }
+ error("Could not handle all of the requested services") if $any_missing;
+}
+
+=head1 SEE ALSO
+
+L<debhelper(7)>
+
+=head1 AUTHORS
+
+pkg-systemd-maintainers@lists.alioth.debian.org
+
+=cut
diff --git a/dh_testdir b/dh_testdir
new file mode 100755
index 0000000..db7f7ce
--- /dev/null
+++ b/dh_testdir
@@ -0,0 +1,72 @@
+#!/usr/bin/perl
+
+=head1 NAME
+
+dh_testdir - test directory before building Debian package
+
+=cut
+
+use strict;
+use warnings;
+use Debian::Debhelper::Dh_Lib;
+
+our $VERSION = DH_BUILTIN_VERSION;
+
+=head1 SYNOPSIS
+
+B<dh_testdir> [S<I<debhelper options>>] [S<I<file> ...>]
+
+=head1 DESCRIPTION
+
+B<dh_testdir> tries to make sure that you are in the correct directory when
+building a Debian package. It makes sure that the file F<debian/control>
+exists, as well as any other files you specify. If not,
+it exits with an error.
+
+=head1 OPTIONS
+
+=over 4
+
+=item I<file> ...
+
+Test for the existence of these files too.
+
+=back
+
+=cut
+
+# This command is completely useless when called from dh(1) as dh will
+# have attempted to read d/control before it even constructs the
+# command sequences. Accordingly, there is no doubt that the
+# following is unconditionally true:
+#
+# PROMISE: DH NOOP
+
+# Run before init because init will try to read debian/control and
+# we want a nicer error message.
+checkfile('debian/control');
+
+init(inhibit_log => 1);
+
+foreach my $file (@ARGV) {
+ checkfile($file);
+}
+
+sub checkfile {
+ my $file=shift;
+ if (! -e $file) {
+ error("\"$file\" not found. Are you sure you are in the correct directory?");
+ }
+}
+
+=head1 SEE ALSO
+
+L<debhelper(7)>
+
+This program is a part of debhelper.
+
+=head1 AUTHOR
+
+Joey Hess <joeyh@debian.org>
+
+=cut
diff --git a/dh_testroot b/dh_testroot
new file mode 100755
index 0000000..5dcadc5
--- /dev/null
+++ b/dh_testroot
@@ -0,0 +1,103 @@
+#!/usr/bin/perl
+
+=encoding UTF-8
+
+=head1 NAME
+
+dh_testroot - ensure that a package is built with necessary level of root permissions
+
+=head1 SYNOPSIS
+
+B<dh_testroot> [S<I<debhelper options>>]
+
+=head1 DESCRIPTION
+
+B<dh_testroot> is used to determine if the target is being run with
+suffient access to root(-like) features.
+
+The definition of sufficient access depends on whether the builder
+(the tool invoking the F<debian/rules> target) supports the
+I<Rules-Requires-Root> (R³) field. If the builder supports R³, then
+it will set the environment variable I<DEB_RULES_REQUIRES_ROOT> and
+B<dh_testroot> will validate that the builder followed the minimum
+requirements for the given value of I<DEB_RULES_REQUIRES_ROOT>.
+
+If the builder does not support I<Rules-Requires-Root>, then it will
+not set the I<DEB_RULES_REQUIRES_ROOT> environment variable. This
+will in turn make B<dh_testroot> (and the rest of debhelper) fall back
+to assuming that (fake)root is implied.
+
+The following is a summary of how B<dh_testroot> behaves based on the
+I<DEB_RULES_REQUIRES_ROOT> environment variable (leading and trailing
+whitespace in the variable is ignored).
+
+=over 4
+
+=item -
+
+If unset, or set to C<binary-targets>, then B<dh_testroot> asserts
+that it is run as root or under L<fakeroot(1)>.
+
+=item -
+
+If set to C<no>, then B<dh_testroot> returns successfully (without
+performing any additional checks).
+
+=item -
+
+If set to any other value than the above, then B<dh_testroot> asserts
+that it is either run as root (or under L<fakeroot(1)>) or the builder
+has provided the B<DEB_GAIN_ROOT_CMD> environment variable (e.g. via
+dpkg-buildpackage -r).
+
+=back
+
+Please note that B<dh_testroot> does I<not> read the
+I<Rules-Requires-Root> field. Which implies that B<dh_testroot> may
+produce incorrect result if the builder lies in
+I<DEB_RULES_REQUIRES_ROOT>. On the flip side, it also enables things
+like testing for what will happen when I<DEB_RULES_REQUIRES_ROOT> is
+set to a given value.
+
+=cut
+
+use strict;
+use warnings;
+use Debian::Debhelper::Dh_Lib;
+
+our $VERSION = DH_BUILTIN_VERSION;
+
+inhibit_log();
+
+my $requirements = Debian::Debhelper::Dh_Lib::root_requirements();
+
+if (! -f 'debian/control') {
+ warning('dh_testroot must be called from the source root');
+}
+
+# PROMISE: DH NOOP WITHOUT internal(rrr)
+
+# By declaration; nothing requires root and this command must be a no-op in that case.
+exit 0 if $requirements eq 'none';
+# The builder /can/ choose to ignore the requirements and just call us as root.
+# If so, we do not bother checking the requirements any further.
+exit 0 if $< == 0;
+if ($requirements eq 'legacy-root') {
+ error("You must run this as root (or use fakeroot).");
+} else {
+ my $env = $ENV{DEB_GAIN_ROOT_CMD};
+ error("Package needs targeted root but builder has not provided a gain-root command via \${DEB_GAIN_ROOT_CMD}")
+ if not $env;
+}
+
+=head1 SEE ALSO
+
+L<debhelper(7)>
+
+This program is a part of debhelper.
+
+=head1 AUTHOR
+
+Joey Hess <joeyh@debian.org>
+
+=cut
diff --git a/dh_ucf b/dh_ucf
new file mode 100755
index 0000000..99d5b87
--- /dev/null
+++ b/dh_ucf
@@ -0,0 +1,111 @@
+#!/usr/bin/perl
+
+=head1 NAME
+
+dh_ucf - register configuration files with ucf
+
+=cut
+
+use strict;
+use warnings;
+use Debian::Debhelper::Dh_Lib;
+
+our $VERSION = DH_BUILTIN_VERSION;
+
+=head1 SYNOPSIS
+
+B<dh_ucf> [S<I<debhelper options>>] [B<-A>] [B<-n>] [S<I<file> ...>]
+
+=head1 DESCRIPTION
+
+B<dh_ucf> is a debhelper program that is responsible for generating the
+F<postinst> and F<postrm> commands that register files with ucf(1) and ucfr(1).
+
+=head1 FILES
+
+=over 4
+
+=item debian/I<package>.ucf
+
+List pairs of source and destination files to register with ucf. Each pair
+should be put on its own line, with the source and destination separated by
+whitespace. Both source and destination must be absolute paths. The source
+should be a file that is provided by your package, typically in /usr/share/,
+while the destination is typically a file in /etc/.
+
+A dependency on ucf will be generated in B<${misc:Depends}>.
+
+Supports substitution variables in compat 13 and later as
+documented in L<debhelper(7)>.
+
+=back
+
+=head1 OPTIONS
+
+=over 4
+
+=item B<-A>, B<--all>
+
+Install all files specified by command line parameters in ALL packages
+acted on.
+
+=item B<-n>, B<--no-scripts>
+
+Do not modify F<postinst>/F<postrm> scripts. Turns this command into a no-op.
+
+=item I<file> ...
+
+Install these info files into the first package acted on. (Or in
+all packages if B<-A> is specified).
+
+=back
+
+=head1 NOTES
+
+Note that this command is not idempotent. L<dh_prep(1)> should be called
+between invocations of this command. Otherwise, it may cause multiple
+instances of the same text to be added to maintainer scripts.
+
+=cut
+
+init();
+
+# PROMISE: DH NOOP WITHOUT ucf cli-options()
+
+foreach my $package (@{$dh{DOPACKAGES}}) {
+ my $file=pkgfile($package,"ucf");
+
+ my @ucf;
+ if ($file) {
+ @ucf=filedoublearray($file);
+ }
+
+ if (($package eq $dh{FIRSTPACKAGE} || $dh{PARAMS_ALL}) && @ARGV) {
+ push @ucf, [@ARGV];
+ }
+
+ if (! $dh{NOSCRIPTS}) {
+ if (@ucf) {
+ addsubstvar($package, "misc:Depends", "ucf");
+ }
+ foreach my $set (@ucf) {
+ my $src = $set->[0];
+ my $dest = $set->[1];
+ autoscript($package,"postinst","postinst-ucf","s:#UCFSRC#:$src:g;s:#UCFDEST#:$dest:g;s/#PACKAGE#/$package/g",);
+ autoscript($package,"postrm","postrm-ucf","s:#UCFDEST#:$dest:g;s/#PACKAGE#/$package/g");
+ }
+ }
+}
+
+=head1 SEE ALSO
+
+L<debhelper(7)>
+
+This program is a part of debhelper.
+
+=head1 AUTHOR
+
+Joey Hess <joeyh@debian.org>
+Jeroen Schot <schot@a-eskwadraat.nl>
+
+=cut
diff --git a/dh_update_autotools_config b/dh_update_autotools_config
new file mode 100755
index 0000000..7c28bce
--- /dev/null
+++ b/dh_update_autotools_config
@@ -0,0 +1,82 @@
+#!/usr/bin/perl
+
+=head1 NAME
+
+dh_update_autotools_config - Update autotools config files
+
+=cut
+
+use strict;
+use warnings;
+use Debian::Debhelper::Dh_Lib;
+
+our $VERSION = DH_BUILTIN_VERSION;
+
+=head1 SYNOPSIS
+
+B<dh_update_autotools_config> [S<I<debhelper options>>]
+
+=head1 DESCRIPTION
+
+B<dh_update_autotools_config> replaces all occurrences of B<config.sub>
+and B<config.guess> in the source tree by the up-to-date versions
+found in the autotools-dev package. The original files are backed up
+and restored by B<dh_clean>.
+
+=cut
+
+init();
+
+for my $basename (qw(config.guess config.sub)) {
+ my $new_version = "/usr/share/misc/$basename";
+ open(my $fd, '-|', 'find', '-mindepth', '1',
+ '(', '-type', 'd', '-name', '.*', '-prune', ')',
+ '-o', '-type', 'f', '-name', $basename, '-print')
+ or error("Cannot run find -type f -name $basename: $!");
+ while (my $filename = <$fd>) {
+ chomp($filename);
+ next if not is_autotools_config_file($filename);
+ restore_file_on_clean($filename);
+ doit('cp', '-f', $new_version, $filename);
+ }
+ close($fd);
+}
+
+sub is_autotools_config_file {
+ my ($file) = @_;
+ my ($is_autoconf_file);
+ open(my $fd, '<', $file) or error("open $file for reading failed: $!");
+ while (my $line = <$fd>) {
+ chomp($line);
+ # This is the test lintian uses.
+ if ($line =~ m{^timestamp=['"]\d{4}-\d{2}-\d{2}['"]\s*$}) {
+ $is_autoconf_file = 1;
+ last;
+ }
+ $line =~ s/\s++$//;
+ if ($line eq q{# Attempt to guess a canonical system name.}
+ or $line =~ q{^# Configuration validation subroutine script}) {
+ # Very old scripts do not have that timestamp line, but
+ # they do have these headers (which even new files also
+ # have).
+ $is_autoconf_file = 1;
+ last;
+ }
+ last if $. >= 10;
+ }
+ close($fd);
+ return $is_autoconf_file;
+}
+
+
+=head1 SEE ALSO
+
+L<debhelper(7)>
+
+This program is a part of debhelper.
+
+=head1 AUTHOR
+
+Niels Thykier <niels@thykier.net>
+
+=cut
diff --git a/dh_usrlocal b/dh_usrlocal
new file mode 100755
index 0000000..34be494
--- /dev/null
+++ b/dh_usrlocal
@@ -0,0 +1,146 @@
+#!/usr/bin/perl
+
+=encoding UTF-8
+
+=head1 NAME
+
+dh_usrlocal - migrate usr/local directories to maintainer scripts
+
+=cut
+
+use warnings;
+use strict;
+use Debian::Debhelper::Dh_Lib;
+use File::Find;
+use File::stat;
+
+our $VERSION = DH_BUILTIN_VERSION;
+
+=head1 SYNOPSIS
+
+B<dh_usrlocal> [S<I<debhelper options>>] [B<-n>]
+
+=head1 DESCRIPTION
+
+B<dh_usrlocal> is a debhelper program that can be used for building packages
+that will provide a subdirectory in F</usr/local> when installed.
+
+It finds subdirectories of F<usr/local> in the package build directory, and
+removes them, replacing them with maintainer script snippets (unless B<-n>
+is used) to create the directories at install time, and remove them when
+the package is removed, in a manner compliant with Debian policy. These
+snippets are inserted into the maintainer scripts by B<dh_installdeb>. See
+L<dh_installdeb(1)> for an explanation of debhelper maintainer script
+snippets.
+
+When the I<DEB_RULES_REQUIRES_ROOT> environment variable is not (effectively)
+I<binary-targets>, the directories in F</usr/local> will be handled as if
+they were owned by root:root (see below).
+
+When the I<DEB_RULES_REQUIRES_ROOT> environment variable has an effective value of
+I<binary-targets>, the owners, groups and permissions will be
+preserved with the sole exception where the directory is owned by root:root.
+
+If a directory is owned by root:root, then ownership will be determined
+at install time. The ownership and permission bits will either be root:root
+mode 0755 or root:staff mode 02775. The actual choice depends on whether
+the system has F</etc/staff-group-for-usr-local> (as documented in the Debian
+Policy Manual §9.1.2 since version 4.1.4)
+
+=head1 OPTIONS
+
+=over 4
+
+=item B<-n>, B<--no-scripts>
+
+Do not modify F<postinst>/F<prerm> scripts.
+
+=back
+
+=head1 NOTES
+
+Note that this command is not idempotent. L<dh_prep(1)> should be called
+between invocations of this command. Otherwise, it may cause multiple
+instances of the same text to be added to maintainer scripts.
+
+=head1 CONFORMS TO
+
+Debian policy, version 2.2
+
+=cut
+
+init();
+
+# PROMISE: DH NOOP WITHOUT tmp(usr/local) cli-options()
+
+foreach my $package (@{$dh{DOPACKAGES}}) {
+ my $tmp = tmpdir($package);
+
+ if (-d "$tmp/usr/local") {
+ my (@dirs, @justdirs);
+ find({no_chdir => 1,
+ preprocess => sub {
+ # Ensure a reproducible traversal.
+ return sort @_;
+ },
+ postprocess => sub {
+ # Uninstall, unless a direct child of /usr/local.
+ $_ = $File::Find::dir;
+ s!^\Q$tmp\E!!;
+ push @justdirs, $_ if m!/usr/local/.*/!;
+ # Remove a directory after its childs.
+ doit('rmdir', $File::Find::dir);
+ },
+ wanted => sub {
+ # rmdir would fail later anyways.
+ error("${File::Find::name} is not a directory")
+ if not -d $File::Find::name;
+ # Install directory before its childs.
+ my $fn = $File::Find::name;
+ $fn =~ s!^\Q$tmp\E!!;
+ return if $fn eq '/usr/local';
+ # Detect some obvious cases of "this will not end
+ # well". We rely on what "while read dir ... ; do"
+ # can handle for correctness.
+ if ($fn =~ m{[\s!'"\$()*#;<>?@\[\]\\`|]}) {
+ error("Cannot generate a correct shell script for $fn due to shell metacharacters");
+ }
+ if (should_use_root()) {
+ my $stat = stat $File::Find::dir;
+ if ($stat->uid == 0 && $stat->gid == 0) {
+ # Figure out the ownership and permission at runtime
+ # (required by Policy 9.1.2)
+ push(@dirs, "$fn default");
+ } else {
+ my $user = getpwuid $stat->uid;
+ my $group = getgrgid $stat->gid;
+ my $mode = sprintf "%04lo", ($stat->mode & 07777);
+ push @dirs, "$fn $mode $user $group";
+ }
+ } else {
+ # Figure out the ownership and permission at runtime
+ # (required by Policy 9.1.2)
+ push(@dirs, "$fn default");
+ }
+ }}, "$tmp/usr/local");
+
+ if (! $dh{NOSCRIPTS}) {
+ autoscript($package,"postinst", "postinst-usrlocal",
+ { 'DIRS' => join ("\n", @dirs)}) if @dirs;
+ autoscript($package,"prerm", "prerm-usrlocal",
+ { 'JUSTDIRS' => join ("\n", @justdirs)}) if @justdirs;
+ }
+ }
+}
+
+=head1 SEE ALSO
+
+L<debhelper(7)>
+
+This program is a part of debhelper.
+
+=head1 AUTHOR
+
+Andrew Stribblehill <ads@debian.org>
+
+=cut