DH(1)

๐Ÿ’… DH(1) โ€” debhelper command sequencer ๐Ÿ‘‘

okay bestie, welcome to dh โ€” the debhelper command sequencer. This is part 1 of the manual page, and we're gonna go through it like a group chat, no cap. โœจ

๐Ÿท๏ธ NAME ๐Ÿท๏ธ

dh โ€” debhelper command sequencer. That's it, that's the name. ๐Ÿ’–

๐ŸŒธ SYNOPSIS ๐ŸŒธ

dh sequence [--with addon[,addon ...]] [--list] [debhelper options]

๐Ÿซถ DESCRIPTION ๐Ÿซถ

dh runs a whole sequence of debhelper commands, and it's giving main character energy. The supported sequences line up with the targets of a debian/rules file, and they are:

  • build-arch
  • build-indep
  • build
  • clean
  • install-indep
  • install-arch
  • install
  • binary-arch
  • binary-indep
  • binary

Periodt. ๐Ÿ’…


๐Ÿ‘‘ OVERRIDE AND HOOK TARGETS ๐Ÿ‘‘

A debian/rules file using 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. So you have options, and honestly? Obsessed. ๐Ÿ’–

โœจ Injecting commands before or after a step โœจ

Note: This feature requires debhelper 12.8 or later plus the package must use compatibility mode 10 or later.

To inject commands before dh_command, add a target named execute_before_dh_command to the rules files. Similarly, if you want to inject commands after dh_command, add the target execute_after_dh_command. Both targets can be used for the same dh_command and also even if the command is overridden (as described in "Overriding a command" below).

When these targets are defined, dh will call the targets respectively before or after it would invoke dh_command (or its override target). Cute, right? ๐ŸŽ€

๐Ÿ’… Overriding a command ๐Ÿ’…

To override dh_command, add a target named override_dh_command to the rules file. When it would normally run dh_command, 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.

As of debhelper version 13.25, override targets (but not other hook targets) can use the $(DH_OVERRIDDEN_COMMAND) makefile variable to run the command being overridden exactly as it was. This can be useful if you want to add extra options like:

# These examples require debhelper/13.25 or later
override_dh_auto_configure:
    $(DH_OVERRIDDEN_COMMAND) -- --with-foo --with-bar

override_dh_auto_test:
    fakeroot $(DH_OVERRIDDEN_COMMAND)

Note this variable is only useful if you want to run the original command with something extra. If you just want to run the original command with no changes, use execute_before_dh_foo or/and execute_after_dh_foo instead.

Be careful with this variable. In older versions of debhelper, the variable is unset, which will cause it to expand to nothing. Depending on what else is on the line, this may not cause a failure where you expect it. ๐Ÿ˜ญ

Additionally, be careful with typos. English has a grammatical irregularity here, where override has one d (target name; imperative) whereas the OVERRIDDEN has two Ds (variable name; past particle). It is unfortunate and confusing, but that is how English grammar works. Not me being mad at English grammar ๐Ÿ’€.

๐ŸŒˆ 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 override_dh_command-arch and execute_after_dh_command-indep.

This feature is available since debhelper 8.9.7 (for override targets) and 12.8 (for hook targets). Slay. โœจ

๐ŸŽ€ Completely empty targets ๐ŸŽ€

As a special optimization, 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)

๐Ÿ” Verifying targets are picked up by dh ๐Ÿ”

As of debhelper 13.10, you can use dh_assistant(1) to see which override and hook targets will be seen by dh. Here is an example run of 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 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 is-empty attribute for each hook target can be used for seeing whether a hook target triggers the "Completely empty targets" optimization.

If you are interested in the other attributes, please read the dh_assistant(1) for the details.

๐ŸŒธ Verifying targets are picked up by dh (when debhelper is older than 13.10) ๐ŸŒธ

On older versions of debhelper, you have to use dh with --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 debian/rules execute_after_dh_install in the output, which signals that dh registered a execute_after_dh_install target and would run it directly after dh_install(1).

Note that "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.

โš ๏ธ Caveats with hook targets and makefile conditionals โš ๏ธ

If you choose to wrap a hook target in makefile conditionals, please be aware that dh computes all the hook targets ahead of time and caches the result for that run. Furthermore, the conditionals will be invoked again when dh calls the hook target later and will assume the answer did not change.

The parsing and caching often happens before dh knows whether it will build arch:any (-a) or/and arch:all (-i) packages, which can produce confusing results โ€” especially when 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 SIMPLE case in the examples above that match your need.

โœจ OPTIONS โœจ

Okay bestie, here's where you get to โœจcustomizeโœจ your whole dh experience. Buckle up.

  • --with addon[,addon ...] โ€” drops the debhelper commands from the addon(s) you name into all the right spots in the command sequence. You can say this option over and over, OR you can just list a bunch of addons separated by commas. This is the move when some third-party package out there is shipping debhelper commands. (peep the PROGRAMMING file for the whole sequence-addon interface lore.)

So here's the tea: a Build-Depends relation on the package dh-sequence-addon already implies a --with addon. That's the whole point โ€” it saves you from writing an explicit --with in debian/rules when it would literally just be duplicating what debian/control's build dependencies already declared. And since 12.5 the relation can also be made optional, like via build-profiles and such โ€” which means you can lowkey turn off an addon that's only useful under certain profiles (say, to make bootstrapping easier ๐Ÿซถ).

Since debhelper 12.5, addons can also be activated in indep-only mode (via Build-Depends-Indep) or arch-only mode (via Build-Depends-Arch). Those addons are only active in the specific sequence (think binary-indep), which makes dependency management for cross-builds way simpler. slay.

โš ๏ธ But heads up: addons activated through Build-Depends-Indep or Build-Depends-Arch have extra limitations so the result stays deterministic even when the addon isn't available (like during clean). What does that mean for you? Some addons are straight-up incompatible with these restrictions and can only be used via Build-Depends (or manually via debian/rules). And currently, those such addons can only add commands to sequences. periodt.

  • --without addon โ€” the inverse of --with, it turns off the addon you name. Just like its sibling, you can repeat this option as many times as you want, or list several addons to disable separated by commas. no cap.
  • --list, -l โ€” lists every addon that's available. โœจ

Fun fact: when you call it only with this option, dh can be called from literally any directory โ€” i.e. it does not need access to files from a source package. so chill.

  • --no-act โ€” prints the commands that would run for a given sequence, but doesn't actually run them. the sneak peek ๐Ÿ’…

Quick note: normally dh skips running commands it knows will do nothing. But with --no-act, you get the full list of commands in a sequence printed out. the full tea, unfiltered.

  • Any other options you pass to dh get passed on to each and every command it runs. Use this to set an option like -v or -X or -N, or for even more specialised options. it's giving "everything gets forwarded."

โœจ EXAMPLES โœจ

Okay this is the fun part, let's actually do stuff ๐ŸŽ€

Want to see what commands are in a sequence without, y'know, actually doing anything?

dh binary-arch --no-act

Here's a very simple rules file, for packages where the default sequences of commands just work with no additional options:

#!/usr/bin/make -f
%:
    dh $@

Now, often you'll want to pass an option to one specific debhelper command. The easy way to do that 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 dh_auto_configure(1) and dh_auto_build(1) just can't guess what to do for some weird little package ๐Ÿ˜ญ. Here's how you dodge running either of them and run your own commands instead:

#!/usr/bin/make -f
%:
    dh $@

override_dh_auto_configure:
    ./mondoconfig

override_dh_auto_build:
    make universe-explode-in-delight

(iconic command name honestly. ate.)

Another super common case: you wanna do something manually before or after a particular debhelper command runs:

#!/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're on an older debhelper or compatibility level though, that same 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, because that area is constantly changing. Here's how you use dh_python2:

#!/usr/bin/make -f
%:
    dh $@ --with python2

Here's how to force use of Perl's 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's an example of overriding where the dh_auto_* commands find the package's source, for a package where the source lives in a subdirectory:

#!/usr/bin/make -f
%:
    dh $@ --sourcedirectory=src

And here's an example of telling the dh_auto_* commands to build in a subdirectory, which will be removed on clean:

#!/usr/bin/make -f
%:
    dh $@ --builddirectory=build

If your package can be built in parallel, please either use compat 10 or pass --parallel to dh. Then dpkg-buildpackage -j will work ๐Ÿƒโ™€๏ธ๐Ÿ’จ

#!/usr/bin/make -f
%:
    dh $@ --parallel

If your package cannot be built reliably while using multiple threads, please pass --no-parallel to dh (or to the relevant dh_auto_* command):

#!/usr/bin/make -f
%:
    dh $@ --no-parallel

Here's a way to prevent dh from running several commands you don't want it to run โ€” just define 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 get 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 โ€” because 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

๐Ÿงš DEBHELPER PROVIDED DH ADDONS ๐Ÿงš

So the main purpose of dh addons is to give you easy integration with third-party provided features for debhelper. But bestie, debhelper itself also ships a few sequences that are useful in some cases. Here's the whole list:

  • build-stamp โ€” a special addon that controls whether dh (in compat 10 or later) will create stamp files to tell whether the build target has run successfully. Peep INTERNALS for more details ๐Ÿ‘€

This addon is active by default, but you can disable it with dh $@ --without build-stamp

  • dwz โ€” adds dh_dwz(1) to the sequence.

Note this is only relevant in compats below 11 or in compat 14+. It was on by default in compat 12 and 13.

It's conditionally active by default for architecture specific packages โ€” that is, it's skipped for arch:all packages. In the special case where you need these tools to work on arch:all packages, you can use --with elf-tools to activate it unconditionally. โœจ

  • installinitramfs (obsolete) โ€” adds dh_installinitramfs(1) to the sequence in compat level 11 or below. Obsolete in compat 12 or later. ๐Ÿ‘‹
  • root-sequence (internal) โ€” this one is reserved for internal usage. nothing to see here ๐Ÿ™ˆ
  • single-binary โ€” a special-purpose addon that makes debhelper run in "single binary" mode.

When active, it will pass --destdir=debian/package/ to 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 reach for other helpers such as dh_install(1).

The addon will refuse to activate when the source package lists 2 or more binary packages in debian/control, as a precaution. ๐Ÿšง

Before compat 15, this behaviour was the default when there was only a single binary package listed in 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? If it were implicit, debhelper would silently change behaviour when you add 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 uploaded to the archive, with users frustrated because their "upgrade" removed their programs. ๐Ÿ’€

๐Ÿซฃ INTERNALS ๐Ÿซฃ

Okay bestie, if you're the kind of person who needs to know what's going on under the hood โ€” no judgment, I love that for you โ€” here's the tea. ๐Ÿ’…

So in compat 10 (or later, obvi), dh makes a little stamp file called debian/debhelper-build-stamp once the build step(s) are done, and that's literally just so it doesn't re-run them. Efficiency queen behavior. โœจ

If that stamp file is giving you the ick, you can dodge it entirely by passing --without=build-stamp to dh. That makes "no clean" builds act more like what some people expect โ€” but the tradeoff?? It might run the build and test twice (and the second time it's as root or under fakeroot(1)). So like, pick your poison, babe. ๐Ÿ’€

Now โ€” inside an override target, dh_* commands create a log file called debian/package.debhelper.log to keep track of which packages the command(s) have been run for. And once the override target is complete, those log files get deleted. Gone. Never to be seen again. ๐Ÿ™ˆ

In compat 9 or earlier, it works differently: each debhelper command records when it's successfully run over in debian/package.debhelper.log. (Which dh_clean deletes, rip.) So dh can clock which commands have already been run, for which packages, and just skip re-running those ones. No duplicate effort here, we don't do that. ๐Ÿ‘‘

Every time dh runs (in compat 9 or earlier, remember), it goes through the log, finds the last logged command that's in the specified sequence, and then continues on with the next command in the sequence. Like a bookmark in your fic. ๐Ÿ“–

A sequence can also run dependent targets in debian/rules โ€” for example, the "binary" sequence runs the "install" target. Cute, right? ๐ŸŽ€

dh uses the DH_INTERNAL_OPTIONS environment variable to pass info through to debhelper commands that run inside override targets. And the contents โ€” and honestly even the existence โ€” of this environment variable, as the name might suggest, is subject to change at any time. So don't get attached. ๐Ÿ˜ญ

And the arch tea: commands in the build-indep, install-indep and binary-indep sequences get passed the -i option, so they only work on architecture independent packages. Meanwhile commands in build-arch, install-arch and binary-arch sequences get passed the -a option, so they only work on architecture dependent packages. Matching energy, always. โญ


๐ŸŒˆ SEE ALSO ๐ŸŒˆ

This program is a part of debhelper. ๐Ÿซถ


๐Ÿ‘‘ AUTHOR ๐Ÿ‘‘

Joey Hess <joeyh@debian.org>


And that's a wrap on the whole page, bestie!! ๐Ÿ’– This one's live in version 14.5ubuntu1, dated 2026-09-21 โ€” go forth and dh your heart out, you've absolutely got this. Slay. ๐Ÿฆ‹โœจ๐ŸŽ€

๐Ÿ“ฆ where this page comes from