Set Reusable Package Values in debian/substvars
Stop hard-coding the same string into five control fields: debian/substvars lets you define it once and reuse it. By the end you will have reusable values for package metadata, a one-off override for a test build, and a way to 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 generate control text or read files. They do not install, remove or upgrade a package, and 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. dpkg-source, dpkg-gencontrol and dpkg-genchanges all 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 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 substituted fields with ${NAME}. For example, a binary stanza can contain:
Package: example-package
Architecture: all
Description: ${BuildNote}
${DescriptionText}
Control fields are parsed before substitution. The continuation line still needs its normal leading space, while the value of ${DescriptionText} can itself contain ${Newline} when you deliberately want 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 checkpoint that matters: the placeholders have gone, and the continuation line is formatted as control data. If your tree has no valid changelog, fix that first, because dpkg-gencontrol needs package version information to produce a normal stanza.
To use another variables file, pass -T. It suits 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 before running the check:
Example
printf '%s\n' 'BuildNote=assembled for the test profile' > /tmp/example-substvars
-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 you do not want committed:
$ dpkg-gencontrol -O -VBuildNote='assembled by CI'
Package: example-package
Description: assembled by CI
This example assumes 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.
Warning
If the same name is defined in a file 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 repeats until no variable references remain. That allows a controlled chain such as ReleaseLabel=${BuildNote} (${Arch}), but a circular definition can stop expansion from ever finishing. Keep references short and acyclic.
5. Handle missing values and literal dollar signs
An undefined variable produces a warning and expands to nothing. Treat that warning as a packaging defect, not 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}, write ${}{NAME} in the input. The empty substitution produces the dollar sign, and the following braces stay ordinary text. That is handy when generating documentation or another template, where you do not want dpkg to expand the inner name.
6. Respect the fields that cannot use variables
Do not put substitutions in Package, Source or Architecture. The build metadata needs these fields while it is being parsed, before ordinary substitution happens. Keep their values literal and stable.
Built-in values cover the common cases:
Archis the host architecture.source:Versionandbinary:Versionexpose package versions.Newline,SpaceandTabcontain those characters.- Field values are available through canonical names such as
S:HomepageandF:Descriptionin binary control generation. Use the exact case the field name requires.
Warning
Avoid the obsolete Source-Version variable. The installed man page says it emits an error because its meaning differs from its old function. Pick 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. If it prints a match, inspect the variable spelling, the file selected by -T, and whether the field is one of the protected ones. Keep the temporary output if you need to compare builds, then remove that specific file when you finish.
Tip
Do not reach for sudo to fix a substitution warning. These are source metadata and syntax problems, and elevated privileges only make it easier to write files with the wrong owner.
Done means
- Tools identified.
dpkg-devanddpkg-gencontrolwere found on the machine. - Values defined. Required names are set with the correct case in
debian/substvarsor an intentional-Tfile. - Output expanded.
dpkg-gencontrol -Oproduced control text with the expected fields filled in. - No leftovers. No unresolved
${...}references remain in the rendered output. - Protected fields literal.
Package,SourceandArchitecturehold plain values. - Temp files handled. Validation output was removed, or kept deliberately outside the source tree.