Last post update: 2024-03-30 11:21:43
If you have binary files that you want to bundle into a Debian package to make use of existing package management tools, then this blog post could be beneficial to you as it describes all the necessary steps required to create an automated binary package building system.
First, ensure that the required tools are installed:
apt install dpkg dpkg-dev python
Create package building script file build.py with the following code:
import os, tempfile, subprocess, shutil, stat
class Package:
def __init__(self, name, key_values):
self.name = name
self.key_values = key_values
def apply(self, common):
for key, value in common.items():
if not key in self.key_values:
self.key_values[key] = value
def prepare(self):
if not "Description" in self.key_values:
self.key_values["Description"] = "Package for " + self.name + "\n ."
def getControl(self):
lines = []
for key, value in self.key_values.items():
lines.append(key + ": " + value)
return "\n".join(lines) + "\n\n"
control = property(getControl)
def __getattr__(self, name):
return self.key_values[name]
def set_installed_size(self, size):
self.key_values["Installed-Size"] = size
def set(self, key, value):
self.key_values[key] = value
def parseOptions(options):
result = {}
for option in options:
key, value = option.split(": ", 1)
result[key] = value
return result
def parseDescription(description):
result = []
for line in description.split("\n"):
if len(line) == 0:
continue
if line.startswith(" "):
result[-1] += "\n" + line
else:
result.append(line)
return result
def loadCommon():
result = {}
with open("common", "r") as file:
options = parseDescription(file.read())
result = parseOptions(options)
return result
def loadPackages():
result = []
with open("packages", "r") as file:
descriptions = file.read().split("\n\n")
for description in descriptions:
options = parseDescription(description)
key_values = parseOptions(options)
result.append(Package(key_values["Package"], key_values))
return result
def isExecutalbeOrLibrary(name, stats):
return stats.st_mode & (stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH) or name.startswith("lib")
run_dir = os.getcwd()
common = loadCommon()
packages = loadPackages()
for package in packages:
package.apply(common)
package.prepare()
with tempfile.TemporaryDirectory(prefix = "build-deb-") as temp_dir:
for package in packages:
package_dir = os.path.join(temp_dir, package.name)
os.mkdir(package_dir)
debian_dir = os.path.join(package_dir, "DEBIAN")
os.mkdir(debian_dir)
shlibs_debian_dir = os.path.join(package_dir, "debian")
os.mkdir(shlibs_debian_dir)
installed_size = 0
created_dirs = []
copied_files = []
executables = []
if os.path.isdir(package.name):
for root, subdirs, files in os.walk(package.name):
for name in subdirs:
path = os.path.join(temp_dir, root, name)
created_dirs.append(path)
os.mkdir(path)
for name in files:
path = os.path.join(temp_dir, root, name)
copied_files.append(path)
shutil.copy(os.path.join(root, name), path)
stats = os.stat(path)
installed_size += stats.st_size
if isExecutalbeOrLibrary(name, stats):
executables.append(path)
package.set_installed_size(str(installed_size // 1024))
if len(executables) > 0:
with open(os.path.join(shlibs_debian_dir, "control"), "w") as file:
file.write("Source: " + package.name + "\n")
file.write(package.control)
os.chdir(package_dir)
subprocess.run(["dpkg-shlibdeps"] + executables, check = True)
os.chdir(run_dir)
shlibdeps_output = None
if os.path.isfile(os.path.join(shlibs_debian_dir, "substvars")):
with open(os.path.join(shlibs_debian_dir, "substvars"), "r") as file:
shlibdeps_output = file.read()
for root, subdirs, files in os.walk(shlibs_debian_dir):
for name in files:
os.remove(os.path.join(shlibs_debian_dir, name))
os.rmdir(shlibs_debian_dir)
if shlibdeps_output != None:
for line in shlibdeps_output.split("\n"):
try:
name, value = line.split("shlibs:", 1)[1].split("=", 1)
package.set(name, value)
except IndexError:
pass
with open(os.path.join(debian_dir, "control"), "w") as file:
file.write(package.control)
subprocess.run(["dpkg-deb", "--root-owner-group", "--build", package_dir, "."], check = True)
for path in copied_files:
os.remove(os.path.join(package_dir, path))
created_dirs.reverse()
for path in created_dirs:
os.rmdir(os.path.join(package_dir, path))
Create package description file packages with Debian control statements (Debian documentation) for each package separated by empty line:
Package: test-package-1
Version: 1.5.1
Package: test-package-2
Version: 1.9.2
Package: test-package-3
Version: 1.0
Create common package description file common with Debian control statements that apply to all packages:
Section: misc
Priority: optional
Architecture: amd64
Maintainer: User <user@example.com>
Create directories for each described package and place your binary files in them. Directory names must match package names specified with Package parameter in package description file. Each directory is treated as a root directory for package, so place executable files in usr/bin/ subdirectory.
Finally, run build script to create described packages in current directory:
python build.py