diff options
Diffstat (limited to '')
-rwxr-xr-x | dh | 882 | ||||
-rwxr-xr-x | dh_assistant | 1449 | ||||
-rwxr-xr-x | dh_auto_build | 63 | ||||
-rwxr-xr-x | dh_auto_clean | 65 | ||||
-rwxr-xr-x | dh_auto_configure | 68 | ||||
-rwxr-xr-x | dh_auto_install | 113 | ||||
-rwxr-xr-x | dh_auto_test | 74 | ||||
-rwxr-xr-x | dh_bugfiles | 145 | ||||
-rwxr-xr-x | dh_builddeb | 186 | ||||
-rwxr-xr-x | dh_clean | 199 | ||||
-rwxr-xr-x | dh_compress | 253 | ||||
-rwxr-xr-x | dh_dwz | 185 | ||||
-rwxr-xr-x | dh_fixperms | 174 | ||||
-rwxr-xr-x | dh_gencontrol | 231 | ||||
-rwxr-xr-x | dh_icons | 87 | ||||
-rwxr-xr-x | dh_install | 393 | ||||
-rwxr-xr-x | dh_installalternatives | 193 | ||||
-rwxr-xr-x | dh_installcatalogs | 138 | ||||
-rwxr-xr-x | dh_installchangelogs | 411 | ||||
-rwxr-xr-x | dh_installcron | 90 | ||||
-rwxr-xr-x | dh_installdeb | 431 | ||||
-rwxr-xr-x | dh_installdebconf | 243 | ||||
-rwxr-xr-x | dh_installdirs | 141 | ||||
-rwxr-xr-x | dh_installdocs | 448 | ||||
-rwxr-xr-x | dh_installemacsen | 149 | ||||
-rwxr-xr-x | dh_installexamples | 192 | ||||
-rwxr-xr-x | dh_installgsettings | 106 | ||||
-rwxr-xr-x | dh_installifupdown | 82 | ||||
-rwxr-xr-x | dh_installinfo | 133 | ||||
-rwxr-xr-x | dh_installinit | 427 | ||||
-rwxr-xr-x | dh_installinitramfs | 103 | ||||
-rwxr-xr-x | dh_installlogcheck | 91 | ||||
-rwxr-xr-x | dh_installlogrotate | 63 | ||||
-rwxr-xr-x | dh_installman | 430 | ||||
-rwxr-xr-x | dh_installmanpages | 208 | ||||
-rwxr-xr-x | dh_installmenu | 100 | ||||
-rwxr-xr-x | dh_installmime | 73 | ||||
-rwxr-xr-x | dh_installmodules | 119 | ||||
-rwxr-xr-x | dh_installpam | 81 | ||||
-rwxr-xr-x | dh_installppp | 78 | ||||
-rwxr-xr-x | dh_installsystemd | 456 | ||||
-rwxr-xr-x | dh_installsystemduser | 288 | ||||
-rwxr-xr-x | dh_installsysusers | 115 | ||||
-rwxr-xr-x | dh_installtmpfiles | 128 | ||||
-rwxr-xr-x | dh_installudev | 112 | ||||
-rwxr-xr-x | dh_installwm | 140 | ||||
-rwxr-xr-x | dh_installxfonts | 100 | ||||
-rwxr-xr-x | dh_link | 178 | ||||
-rwxr-xr-x | dh_lintian | 72 | ||||
-rwxr-xr-x | dh_listpackages | 44 | ||||
-rwxr-xr-x | dh_makeshlibs | 511 | ||||
-rwxr-xr-x | dh_md5sums | 128 | ||||
-rwxr-xr-x | dh_missing | 271 | ||||
-rwxr-xr-x | dh_movefiles | 171 | ||||
-rwxr-xr-x | dh_movetousr | 230 | ||||
-rwxr-xr-x | dh_perl | 198 | ||||
-rwxr-xr-x | dh_prep | 80 | ||||
-rwxr-xr-x | dh_shlibdeps | 214 | ||||
-rwxr-xr-x | dh_strip | 445 | ||||
-rwxr-xr-x | dh_systemd_enable | 292 | ||||
-rwxr-xr-x | dh_systemd_start | 292 | ||||
-rwxr-xr-x | dh_testdir | 72 | ||||
-rwxr-xr-x | dh_testroot | 103 | ||||
-rwxr-xr-x | dh_ucf | 111 | ||||
-rwxr-xr-x | dh_update_autotools_config | 82 | ||||
-rwxr-xr-x | dh_usrlocal | 146 |
66 files changed, 14076 insertions, 0 deletions
@@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 |