You are staring at dh_installxmlcatalogs in an old rules file with no idea what it actually touches. It adds a local XML catalogue to a Debian package, registers its identifiers in the package and system catalogue databases, and generates the maintainer scripts that keep the registration safe to rebuild. This guide follows dh_installxmlcatalogs from xml-core 0.19; the installed manual page is dated 12 December 2023.
Allow about 20 minutes for a small package. You need a Debian source package using debhelper, an XML catalogue in its source tree, and the xml-core package. The examples change packaging files and generated maintainer-script snippets, so work in version control and review the diff before building. Do not run the command against a real package tree merely to experiment.
Run these ordinary, read-only checks from the package source directory:
$ command -v dh_installxmlcatalogs
/usr/bin/dh_installxmlcatalogs
$ dpkg-query -W -f='${Package} ${Version}\n' xml-core
xml-core 0.19
The command is a debhelper helper, not a general-purpose XML catalogue editor. It reads a file named debian/PACKAGE.xmlcatalogs, installs the listed local catalogue, and prepares maintainer-script fragments that call update-xmlcatalog.
On this machine, invoking the installed command directly fails before it can do any work, because the Perl module Debian::Debhelper::Dh_Lib is not available in the current environment. That is a packaging-environment problem, not evidence that the catalogue file is wrong. Run it through a normal debhelper build environment instead.
Choose a path under debian/ or another source directory, then make the catalogue path in the package unambiguous. This example uses a package called example-schema:
$ mkdir -p debian/catalog
$ editor debian/catalog/catalog.xml
Use the XML catalogue syntax required by the software that will read it. A minimal example might contain an entity mapping such as:
<?xml version="1.0"?>
<catalog xmlns="urn:oasis:names:tc:entity:xmlns:xml:catalog">
<public publicId="-//EXAMPLE//DTD EXAMPLE 1.0//EN"
uri="example.dtd"/>
</catalog>
Keep this file in the source package. The helper copies it into the package build area; it does not create a catalogue from an identifier alone.
Checkpoint: confirm the source file exists and is readable before moving on.
$ test -r debian/catalog/catalog.xml && echo 'catalog source is readable'
catalog source is readable
Create debian/example-schema.xmlcatalogs. Its first line uses the three-field local;source;dest form:
local;debian/catalog/catalog.xml;/usr/share/xml/example-schema/catalog.xml
local is literal. The middle field is the source-tree path. The final field is the destination under the package build area and should begin with /usr/share/xml/. This is an installation description, not a shell command, so do not add quotes or a leading ./ unless that is genuinely part of your source path.
$ awk -F';' 'NF != 3 || $1 != "local" || $3 !~ /^\/usr\/share\/xml\// { print "invalid local entry"; exit 1 }' debian/example-schema.xmlcatalogs
$ echo 'local entry shape is valid'
local entry shape is valid
Add entries for the identifiers that consumers should resolve. A package-only registration uses four fields:
package;public;-//EXAMPLE//DTD EXAMPLE 1.0//EN;/usr/share/xml/example-schema/catalog.xml
The fields are the literal word package, an entity type, the identifier, and the local catalogue path. Use public for formal public identifiers, system for a local file or URL identifier, and uri for a non-local URI that is not part of the external document subset, such as a URI used outside an entity or DTD.
If applications on the whole system should find the identifier, use a root registration instead. It has three fields and does not name the local catalogue:
root;public;-//EXAMPLE//DTD EXAMPLE 1.0//EN
When the same mapping belongs in both places, use the four-field combined form:
root-and-package;public;-//EXAMPLE//DTD EXAMPLE 1.0//EN;/usr/share/xml/example-schema/catalog.xml
Do not use root just because it is convenient: it changes the system catalogue used by other packages, so keep the scope as narrow as the software requires. Each identifier should be registered once, in the scope it actually needs. For a package-local public identifier, the complete file is now:
local;debian/catalog/catalog.xml;/usr/share/xml/example-schema/catalog.xml
package;public;-//EXAMPLE//DTD EXAMPLE 1.0//EN;/usr/share/xml/example-schema/catalog.xml
The helper adds a dependency on xml-core to ${misc:Depends}. Ensure the package stanza in debian/control includes that substitution:
Package: example-schema
Architecture: all
Depends: ${misc:Depends}
Description: Example XML schema
Example package containing an XML catalog.
Do not replace the substitution with a guessed fixed version unless the package has a separately justified requirement. Debhelper calculates the generated dependency; if the package already uses ${misc:Depends}, leave the existing dependency line intact.
In a debhelper override or sequence, run dh_installxmlcatalogs after the package files are available. The usual invocation needs no options:
$ dh_installxmlcatalogs
It installs the local catalogue into the package staging directory and adds registration and unregistration snippets to the generated postinst, prerm and postrm scripts. Registration calls update-xmlcatalog during installation; the removal paths clean the package catalogue when the package is purged.
Review the generated package rather than assuming success means the mapping is correct:
$ find debian/example-schema -path '*/usr/share/xml/*' -type f -print
debian/example-schema/usr/share/xml/example-schema/catalog.xml
$ grep -R -n 'update-xmlcatalog' debian/example-schema/DEBIAN
debian/example-schema/DEBIAN/postinst:...
debian/example-schema/DEBIAN/prerm:...
The exact line numbers and generated script formatting vary. Look for the expected identifier, scope and catalogue path in the scripts, and inspect debian/control after the build has generated its substitution variables.
Warning: dh_installxmlcatalogs is not idempotent. Repeating it in the same prepared tree can append another copy of the same text to maintainer scripts.
Before rerunning it, clean the debhelper-generated state with the package's normal clean target:
$ dh_clean -k
$ dh_installxmlcatalogs
The -k form is the cleanup action named by the installed manual page. In a normal package build, prefer the standard debian/rules clean and then rebuild, provided that target invokes the appropriate debhelper cleanup. Check the diff afterwards:
$ git diff --check
$ git diff -- debian/example-schema.xmlcatalogs debian/control debian/example-schema/DEBIAN
If you accidentally generated duplicate blocks, stop before building or installing the package. Restore the generated files from a clean rebuild or your version-control recovery workflow, then run the helper once. Do not hand-delete an uncertain maintainer-script fragment in a live installed package.
--noscripts only deliberatelyThe -n and --noscripts options suppress changes to postinst, postrm and prerm. They do not turn off catalogue installation, and they do not provide a replacement registration mechanism:
$ dh_installxmlcatalogs --noscripts
Use this only when another controlled part of the package build owns the equivalent registration and unregistration work. Otherwise the package may ship a catalogue file that the XML catalogue system never knows about. This option changes package installation behaviour, so review the resulting maintainer scripts and test install, upgrade and removal before release.
debian/PACKAGE.xmlcatalogs has a correct local entry and only the required package, root or combined registrations./usr/share/xml/, and the source catalogue is present in the tree.debian/control contains ${misc:Depends} for the package using the helper.