Control Debian Package Metadata with debian/substvars
You will finish with a small, repeatable way to define reusable values for Debian package metadata, override one value for a test build, and inspect the generated control fields before packaging. The examples match dpkg 1.22.6, provided here by dpkg-dev version 1.22.6ubuntu6.6.
The route
Jump straight to the step you need, or tick off Done means at the end.
Allow about fifteen minutes. You need an unpacked Debian source tree with a usable debian/control and changelog, plus the dpkg-dev package. The commands below generate control text or read files; they do not install, remove or upgrade a package. No elevated privileges are required.
1. Check the installed toolchain
Start in the root of the source tree. Confirm the package version and the command that will consume your variables:
$ dpkg-query -W -f='${Package} ${Version}\n' dpkg-dev
dpkg-dev 1.22.6ubuntu6.6
$ command -v dpkg-gencontrol
/usr/bin/dpkg-gencontrol
Your version and path may differ. The important boundary is that dpkg-source, dpkg-gencontrol and dpkg-genchanges substitute variables while writing their control information. This guide uses dpkg-gencontrol -O because it prints the result without replacing a control file in the package build directory.
Checkpoint: run pwd and confirm that the current directory contains debian/control. If it does not, stop and move to the source-tree root before creating a variables file.
2. Add a value to debian/substvars
Create or edit debian/substvars with one assignment per line. A normal assignment uses =; comments, blank lines and trailing whitespace are ignored:
# Values used by the package metadata
BuildNote=assembled by the local build
DescriptionText=This description came from debian/substvars.
Reference those names in fields that are substituted, using ${NAME}. For example, a binary stanza can contain:
Package: example-package
Architecture: all
Description: ${BuildNote}
${DescriptionText}
Control fields are parsed before substitution. That means the continuation line still needs the normal leading space, while the value of ${DescriptionText} can itself contain ${Newline} when you deliberately need more generated lines.
Checkpoint: run sed -n '1,120p' debian/substvars and check the spelling and case of every name. Variable names are case-sensitive and may contain letters, digits, hyphens and colons, but must start with an alphanumeric character.
3. Generate control output without changing the package
Use dpkg-gencontrol -O to print the binary control stanza. It reads debian/substvars by default:
$ dpkg-gencontrol -O
Package: example-package
Version: 0.1-1
Architecture: all
Description: assembled by the local build
This description came from debian/substvars.
The complete output also includes fields such as maintainer, section and the calculated installed size. The two description lines are the useful checkpoint here: the placeholders have gone, and the continuation line is formatted as control data. If your tree has no valid changelog, fix that source-tree problem first; dpkg-gencontrol needs package version information to produce a normal stanza.
To use another variables file, pass -T. This is useful for a temporary build profile without replacing the project file:
$ dpkg-gencontrol -T /tmp/example-substvars -O
Package: example-package
Description: assembled for the test profile
For that exact result, the temporary file contains BuildNote=assembled for the test profile. Create it with printf '%s\n' 'BuildNote=assembled for the test profile' > /tmp/example-substvars before running the check. -T can be used more than once. Keep temporary files outside the source tree and remove them after the check. That cleanup is safe because it does not alter package metadata.
4. Override a value for one command
The common -Vname=value option supplies a substitution variable on the command line. Use it for a one-off value or a build-system name that should not be committed:
$ dpkg-gencontrol -O -VBuildNote='assembled by CI'
Package: example-package
Description: assembled by CI
Use this example when BuildNote is not also defined in a variables file. Quote values containing spaces, dollar signs or shell punctuation. The option sets the variable for this invocation only; it does not edit debian/substvars. If the same name is defined in a file that is read during the command, do not assume the command-line value wins: use a distinct name or make the input order explicit and verify the rendered result.
Substitution is repeated until no variable references remain. This permits a controlled chain such as ReleaseLabel=${BuildNote} (${Arch}), but it also means a circular definition can keep expansion from completing. Keep references short and acyclic.
5. Handle missing values and literal dollar signs
An undefined variable produces a warning and expands to an empty value. Treat that warning as a packaging defect, not as harmless noise:
$ dpkg-gencontrol -O
dpkg-gencontrol: warning: package example-package: substitution variable ${MissingText} used, but is not defined
The exact warning context depends on the field and dpkg version. If a variable is intentionally optional, use the question-mark assignment introduced in dpkg 1.21.8:
OptionalNote?=
An optional variable does not warn when it is unused. It still expands to its value when you reference it. Do not use ?= to hide a required release or dependency value.
To emit a literal ${NAME} in the result, write ${}{NAME} in the input. The empty substitution produces the dollar sign, and the following braces remain ordinary text. This is useful when generating documentation or another template rather than asking dpkg to expand the inner name.
6. Respect the fields that cannot use variables
Do not put substitutions in Package, Source or Architecture. These fields are needed while the build metadata is being parsed, before ordinary substitution takes place. Keep their values literal and stable.
Built-in values cover common metadata: Arch is the host architecture, source:Version and binary:Version expose package versions, and Newline, Space and Tab contain those characters. Source and output field values are available through canonical names such as S:Homepage and F:Description in binary control generation. Use the exact case required by the field name.
Avoid the obsolete Source-Version variable. The installed manpage says it emits an error because its meaning differs from its old function; choose source:Version or binary:Version according to what the package metadata needs.
7. Verify the final change
Before a real build, render the relevant fields and search for unresolved substitutions:
$ dpkg-gencontrol -O > /tmp/example-control
$ rg '\$\{[^}]+\}' /tmp/example-control
$ printf 'control status: %s\n' "$?"
control status: 1
Status 1 from rg means it found no remaining placeholder in this check. If it prints a match, inspect the variable spelling, the file selected by -T, and whether the field is one of the protected fields. Keep the temporary output if you need to compare builds, then remove that specific file when finished.
Do not use sudo to solve a substitution warning. These are source metadata and syntax problems. Elevated privileges would only make it easier to write files with the wrong owner.
Done means
dpkg-devanddpkg-gencontrolwere identified on the machine.- Required values are defined with the correct case in
debian/substvarsor an intentional-Tfile. dpkg-gencontrol -Oproduced control text with the expected expanded fields.- No unresolved
${...}references remain in the rendered output. Package,SourceandArchitectureremain literal.- Temporary validation output was removed or retained deliberately outside the source tree.