Codebase list debspawn / a418df8
Update upstream source from tag 'upstream/0.6.0' Update to upstream version '0.6.0' with Debian dir 47ee958a83844054b2a4b48083e01e71fb6d6650 Matthias Klumpp 1 year, 7 months ago
18 changed file(s) with 280 addition(s) and 84 deletion(s). Raw diff Collapse all Expand all
4040
4141 - name: Install dependencies
4242 run: python -m pip install
43 toml types-toml
43 tomlkit
4444 pkgconfig
4545 flake8
4646 pytest
+25
-10
NEWS less more
0 Version 0.6.0
1 ~~~~~~~~~~~~~
2 Released: 2022-10-02
3
4 Features:
5 * Allow containers that run a custom command to be booted as well
6 * Add configuration option to disable package caching
7 * Add configuration option to set a different bootstrap tool
8 * Make artifact storage in interactive mode an explicit action
9
10 Bugfixes:
11 * Fix pyproject.toml license/author fields
12 * Don't use deprecated Lintian argument
13 * Use tomlkit instead of deprecated toml
14
015 Version 0.5.2
1 ~~~~~~~~~~~~~~
16 ~~~~~~~~~~~~~
217 Released: 2022-02-22
318
419 Features:
1025 * Do not include APT package caches in tarballs
1126
1227 Version 0.5.1
13 ~~~~~~~~~~~~~~
28 ~~~~~~~~~~~~~
1429 Released: 2021-11-08
1530
1631 Notes:
3651 * man: Clarify new image name / suite relations in ds-create manual page
3752
3853 Version 0.5.0
39 ~~~~~~~~~~~~~~
54 ~~~~~~~~~~~~~
4055 Released: 2021-06-04
4156
4257 Features:
5368 * Fix image creation if resolv.conf is a symlink
5469
5570 Version 0.4.2
56 ~~~~~~~~~~~~~~
71 ~~~~~~~~~~~~~
5772 Released: 2021-05-24
5873
5974 Features:
8095 Helmut Grohne, Matthias Klumpp
8196
8297 Version 0.4.1
83 ~~~~~~~~~~~~~~
98 ~~~~~~~~~~~~~
8499 Released: 2020-12-22
85100
86101 Features:
99114 * Disable syscall filter for some groups by default (Matthias Klumpp)
100115
101116 Version 0.4.0
102 ~~~~~~~~~~~~~~
117 ~~~~~~~~~~~~~
103118 Released: 2020-01-20
104119
105120 Features:
118133 * Add MANIFEST file
119134
120135 Version 0.3.0
121 ~~~~~~~~~~~~~~
136 ~~~~~~~~~~~~~
122137 Released: 2020-01-06
123138
124139 Features:
150165 * Ensure all data lands in its intended directories when installing
151166
152167 Version 0.2.1
153 ~~~~~~~~~~~~~~
168 ~~~~~~~~~~~~~
154169 Released: 2019-01-10
155170
156171 Features:
159174 * Allow creating an image with a suite and base-suite
160175
161176 Version 0.2.0
162 ~~~~~~~~~~~~~~
177 ~~~~~~~~~~~~~
163178 Released: 2018-08-28
164179
165180 Features:
179194 * Set environment shell
180195
181196 Version 0.1.0
182 ~~~~~~~~~~~~~~
197 ~~~~~~~~~~~~~
183198 Released: 2018-08-20
184199
185200 Notes:
11
22 1. Write NEWS entries for Debspawn in the same format as usual.
33
4 git shortlog v0.5.1.. | grep -i -v trivial | grep -v Merge > NEWS.new
4 git shortlog v0.5.2.. | grep -i -v trivial | grep -v Merge > NEWS.new
55
66 --------------------------------------------------------------------------------
7 Version 0.5.2
8 ~~~~~~~~~~~~~~
9 Released: 2021-xx-xx
7 Version 0.5.3
8 ~~~~~~~~~~~~~
9 Released: 2022-xx-xx
1010
1111 Notes:
1212
1717
1818 2. Commit changes in Git:
1919
20 git commit -a -m "Release version 0.5.2"
21 git tag -s -f -m "Release 0.5.2" v0.5.2 <gpg password>
20 git commit -a -m "Release version 0.5.3"
21 git tag -s -f -m "Release 0.5.3" v0.5.3 <gpg password>
2222 git push --tags
2323 git push
2424
0 # This file is part of debspawn.
0 # This file is part of debspawn.
11 #
22 # Debspawn is free software: you can redistribute it and/or modify
33 # it under the terms of the GNU Lesser General Public License as published by
1717 # along with this software. If not, see <http://www.gnu.org/licenses/>.
1818
1919 __appname__ = 'debspawn'
20 __version__ = '0.5.2'
20 __version__ = '0.6.0'
3333 get_random_free_uid_gid,
3434 )
3535 from .utils.log import (
36 input_bool,
3637 print_info,
3738 print_warn,
3839 print_error,
40 print_bullet,
3941 print_header,
4042 print_section,
4143 capture_console_output,
6264 aptcache_tmp,
6365 pkginjector,
6466 prev_exitcode,
65 ):
67 ) -> bool:
6668 '''Launch an interactive shell in the build environment'''
6769
6870 # find the right directory to switch to
9597 verbose=True,
9698 )
9799
100 print()
101 copy_artifacts = input_bool(
102 'Should any generated build artifacts (binary/source packages, etc.) be saved?', default=False
103 )
104 if copy_artifacts:
105 print_bullet('Artifacts will be copied to the results directory.')
106 else:
107 print_bullet('Artifacts will not be kept.')
108
98109 if source_pkg_dir:
99 print()
100 while True:
101 try:
102 copy_changes = input(
103 (
104 'Should changes to the debian/ directory be copied back to the host?\n'
105 'This will OVERRIDE all changes made on files on the host. [y/N]: '
106 )
107 )
108 except EOFError:
109 copy_changes = 'n'
110 if copy_changes == 'y' or copy_changes == 'Y':
111 copy_changes = True
112 break
113 elif copy_changes == 'n' or copy_changes == 'N':
114 copy_changes = False
115 break
116 elif not copy_changes:
117 copy_changes = False
118 break
110 copy_changes = input_bool(
111 (
112 'Should changes to the debian/ directory be copied back to the host?\n'
113 'This will OVERRIDE all changes made on files on the host.'
114 ),
115 default=False,
116 )
119117
120118 if copy_changes:
121119 print_info('Cleaning up...')
175173 os.remove(fname)
176174 print()
177175 else:
178 print_info('Discarding build environment.')
176 print_bullet('Discarding build environment.')
179177 else:
180178 print_info('Can not copy back changes as original package directory is unknown.')
179
180 return copy_artifacts
181181
182182
183183 def internal_execute_build(
292292 print() # extra blank line after Lintian output
293293
294294 if interact:
295 interact_with_build_environment(
295 ri = interact_with_build_environment(
296296 osbase,
297297 instance_dir,
298298 machine_name,
302302 pkginjector=pkginjector,
303303 prev_exitcode=r,
304304 )
305 # exit with status of previous exit code
306 if r != 0:
305 # if we exit with a non-True result, we stop here and don't proceed
306 # with the next steps that save artifacts.
307 if not ri:
307308 return False
308309
309310 build_dir_size = get_tree_size(pkg_dir)
277277 custom_command,
278278 options.build_dir,
279279 options.artifacts_dir,
280 boot=options.boot,
280281 init_command=options.init_command,
281282 copy_command=options.external_commad,
282283 header_msg=options.header,
667668 'List one or more additional permissions to grant the container. Takes a comma-separated '
668669 'list of capability names.'
669670 ),
671 )
672 sp.add_argument(
673 '--boot',
674 action='store_true',
675 dest='boot',
676 help='Boot container image (requires the image to contain an init system).',
670677 )
671678 sp.add_argument('command', action='store', nargs='*', default=None, help='The command to run.')
672679
1919 import os
2020 import sys
2121
22 import toml
22 import tomlkit
2323
2424 thisfile = __file__
2525 if not os.path.isabs(thisfile):
4545 if os.path.isfile(fname):
4646 with open(fname) as f:
4747 try:
48 cdata = toml.load(f)
49 except toml.TomlDecodeError as e:
48 cdata = tomlkit.load(f)
49 except tomlkit.exceptions.ParseError as e:
5050 print(
5151 'Unable to parse global configuration (global.toml): {}'.format(str(e)),
5252 file=sys.stderr,
7070 self._temp_dir = cdata.get('TempDir', '/var/tmp/debspawn/')
7171 self._default_bootstrap_variant = cdata.get('DefaultBootstrapVariant', 'buildd')
7272 self._allow_unsafe_perms = cdata.get('AllowUnsafePermissions', False)
73 self._cache_packages = bool(cdata.get('CachePackages', True))
74 self._bootstrap_tool = cdata.get('BootstrapTool', 'debootstrap')
7375
7476 self._syscall_filter = cdata.get('SyscallFilter', 'compat')
7577 if self._syscall_filter == 'compat':
125127
126128 @property
127129 def syscall_filter(self) -> list:
130 """Customize which syscalls should be filtered."""
128131 return self._syscall_filter
129132
130133 @property
131134 def allow_unsafe_perms(self) -> bool:
135 """Whether usage of unsafe permissions is allowed."""
132136 return self._allow_unsafe_perms
137
138 @property
139 def cache_packages(self) -> bool:
140 """Whether APT packages should be cached by debspawn."""
141 return self._cache_packages
142
143 @property
144 def bootstrap_tool(self) -> str:
145 """The chroot bootstrap tool that we should use."""
146 return self._bootstrap_tool
133147
134148 def __init__(self, fname=None):
135149 if not GlobalConfig._instance:
271271 cmd = ['lintian',
272272 '-I', # infos by default
273273 '--pedantic', # pedantic hints by default,
274 '--no-tag-display-limit' # display all tags found (even if that may be a lot occasionally)
274 '--tag-display-limit', '0', # display all tags found (even if that may be a lot occasionally)
275275 ]
276276 run_command(cmd)
277277
194194
195195 from . import __version__
196196 from .nspawn import systemd_version, systemd_detect_virt
197 from .osbase import debootstrap_version, print_container_base_image_info
197 from .osbase import bootstrap_tool_version, print_container_base_image_info
198198
199199 print('Debspawn Status Report', end='')
200200 sys.stdout.flush()
212212 print('Platform:', platform.platform(aliased=True))
213213 print('Virtualization:', systemd_detect_virt())
214214 print('Systemd-nspawn version:', systemd_version())
215 print('Debootstrap version:', debootstrap_version())
215 print('Bootstrap tool:', '{} {}'.format(gconf.bootstrap_tool, bootstrap_tool_version(gconf)))
216216
217217 print_section('Container image list')
218218 print_container_base_image_info(gconf)
1717 # along with this software. If not, see <http://www.gnu.org/licenses/>.
1818
1919 import os
20 import typing as T
2021 import platform
21 from typing import Union
22 import subprocess
2223
2324 from .utils import (
2425 safe_run,
9798 syscall_filter: list[str] = None,
9899 env_vars: dict[str, str] = None,
99100 private_users: bool = False,
100 ):
101 nowait: bool = False,
102 ) -> T.Union[subprocess.CompletedProcess, subprocess.Popen]:
101103 '''
102104 Execute systemd-nspawn with the given parameters.
103105 Mess around with cgroups if necessary.
151153 # if we boot the container, we also register it with machinectl, otherwise
152154 # we run an unregistered container with the command as PID2
153155 cmd.append('-b')
156 cmd.append('--notify-ready=yes')
154157 else:
155158 cmd.append('--register=no')
156159 cmd.append('-a')
186189
187190 cmd.extend(parameters)
188191
189 proc = run_forwarded(cmd)
190 return proc.returncode
192 if nowait:
193 return subprocess.Popen(cmd, shell=False, stdin=subprocess.DEVNULL)
194 else:
195 return run_forwarded(cmd)
191196
192197
193198 def nspawn_run_persist(
195200 base_dir,
196201 machine_name,
197202 chdir,
198 command: Union[list[str], str] = None,
199 flags: Union[list[str], str] = None,
203 command: T.Union[list[str], str] = None,
204 flags: T.Union[list[str], str] = None,
200205 *,
201206 tmp_apt_cache_dir: str = None,
202207 pkginjector: PackageInjector = None,
222227 params = [
223228 '--chdir={}'.format(chdir),
224229 '--link-journal=no',
225 '--bind={}:/var/cache/apt/archives/'.format(aptcache_tmp_dir),
226230 ]
231 if aptcache_tmp_dir:
232 params.append('--bind={}:/var/cache/apt/archives/'.format(aptcache_tmp_dir))
227233 if pkginjector and pkginjector.instance_repo_dir:
228234 params.append('--bind={}:/srv/extra-packages/'.format(pkginjector.instance_repo_dir))
229235
231237 params.append('--personality={}'.format(personality))
232238 params.extend(flags)
233239 params.extend(['-{}D'.format('' if verbose else 'q'), base_dir])
234 params.extend(command)
240
241 # nspawn can not run a command in a booted container on its own
242 if not boot:
243 params.extend(command)
244 sdns_nowait = boot and command
235245
236246 # ensure the temporary apt cache is up-to-date
237 osbase.aptcache.create_instance_cache(aptcache_tmp_dir)
247 if aptcache_tmp_dir:
248 osbase.aptcache.create_instance_cache(aptcache_tmp_dir)
238249
239250 # run command in container
240 ret = _execute_sdnspawn(
251 ns_proc = _execute_sdnspawn(
241252 osbase,
242253 params,
243254 machine_name,
246257 env_vars=env_vars,
247258 private_users=private_users,
248259 boot=boot,
260 nowait=sdns_nowait,
249261 )
250262
251 # archive APT cache, so future runs of this command are faster
252 osbase.aptcache.merge_from_dir(aptcache_tmp_dir)
263 if not sdns_nowait:
264 ret = ns_proc.returncode
265 else:
266 try:
267 import time
268
269 # the container is (hopefully) running now, but let's check for that
270 time_ac_start = time.time()
271 container_booted = False
272 while (time.time() - time_ac_start) < 60:
273 scisr_out, _, _ = run_command(
274 [
275 'systemd-run',
276 '-GP',
277 '--wait',
278 '-qM',
279 machine_name,
280 'systemctl',
281 'is-system-running',
282 ]
283 )
284
285 # check if we are actually running, try again later if not
286 if scisr_out.strip() in ('running', 'degraded'):
287 print()
288 container_booted = True
289 break
290 time.sleep(0.5)
291
292 if container_booted:
293 sdr_cmd = [
294 'systemd-run',
295 '-GP',
296 '--wait',
297 '-qM',
298 machine_name,
299 '--working-directory',
300 chdir,
301 ] + command
302 proc = run_forwarded(sdr_cmd)
303 ret = proc.returncode
304 else:
305 ret = 7
306 print_error('Timed out while waiting for the container to boot.')
307 finally:
308 run_forwarded(['machinectl', 'poweroff', machine_name])
309 try:
310 ns_proc.wait(30)
311 except subprocess.TimeoutExpired:
312 ns_proc.terminate()
313
314 # archive APT cache, so future runs of this command are faster (unless disabled in configuration)
315 if aptcache_tmp_dir:
316 osbase.aptcache.merge_from_dir(aptcache_tmp_dir)
253317
254318 return ret
255319
256 if tmp_apt_cache_dir:
320 if not osbase.cache_packages:
321 # APT package caching was explicitly disabled by the user
322 ret = run_nspawn_with_aptcache(None)
323 elif tmp_apt_cache_dir:
324 # we will be reusing an externally provided temporary APT cache directory
257325 ret = run_nspawn_with_aptcache(tmp_apt_cache_dir)
258326 else:
327 # we will create our own temporary APT cache dir
259328 with temp_dir('aptcache-' + machine_name) as aptcache_tmp:
260329 ret = run_nspawn_with_aptcache(aptcache_tmp)
261330
267336 base_dir,
268337 machine_name,
269338 chdir,
270 command: Union[list[str], str] = None,
271 flags: Union[list[str], str] = None,
339 command: T.Union[list[str], str] = None,
340 flags: T.Union[list[str], str] = None,
272341 allowed: list[str] = None,
273342 syscall_filter: list[str] = None,
274343 env_vars: dict[str, str] = None,
301370 syscall_filter=syscall_filter,
302371 env_vars=env_vars,
303372 private_users=private_users,
304 )
373 ).returncode
305374
306375
307376 def nspawn_make_helper_cmd(flags, build_uid: int):
328397 chdir='/tmp',
329398 *,
330399 build_uid: int,
331 nspawn_flags: Union[list[str], str] = None,
400 nspawn_flags: T.Union[list[str], str] = None,
332401 allowed: list[str] = None,
333402 env_vars: dict[str, str] = None,
334403 private_users: bool = False,
3434 print_section,
3535 format_filesize,
3636 )
37 from .config import GlobalConfig
3738 from .nspawn import nspawn_run_persist, nspawn_run_helper_persist
3839 from .aptcache import APTCache
3940 from .utils.env import ensure_root, get_owner_uid_gid, get_random_free_uid_gid
4243 from .utils.zstd_tar import ensure_tar_zstd, compress_directory, decompress_tarball
4344
4445
45 def debootstrap_version():
46 def bootstrap_tool_version(gconf=None):
47 if not gconf:
48 gconf = GlobalConfig()
4649 ds_version = 'unknown'
4750 try:
48 out, _, _ = safe_run(['debootstrap', '--version'])
51 out, _, _ = safe_run([gconf.bootstrap_tool, '--version'])
4952 parts = out.strip().split(' ', 2)
5053 ds_version = parts[0 if len(parts) < 2 else 1]
5154 except Exception as e:
52 print_warn('Unable to determine debootstrap version: {}'.format(e))
55 print_warn('Unable to determine bootstrap tool version: {}'.format(e))
5356
5457 return ds_version
5558
5962 Describes an OS base registered with debspawn
6063 '''
6164
62 def __init__(self, gconf, suite, arch, variant=None, *, base_suite=None, custom_name=None, cachekey=None):
65 def __init__(
66 self,
67 gconf: GlobalConfig,
68 suite: str,
69 arch: str,
70 variant=None,
71 *,
72 base_suite=None,
73 custom_name=None,
74 cachekey=None,
75 ):
6376 self._gconf = gconf
6477 self._suite = suite
6578 self._base_suite = base_suite
8093
8194 self._parameters_checked = False
8295 self._aptcache = APTCache(self)
96
97 # debootstrap-compatible tools that we know about
98 self._known_bootstrap_tools = ('debootstrap', 'mmdebstrap', 'qemu-debootstrap')
8399
84100 # get a fresh UID to give to our build user within the container
85101 self._builder_uid = get_random_free_uid_gid()[0]
194210 return self._aptcache
195211
196212 @property
213 def cache_packages(self) -> bool:
214 return self._gconf.cache_packages
215
216 @property
197217 def has_base_suite(self) -> bool:
198218 return True if self.base_suite and self.base_suite != self.suite else False
199219
304324 data['AllowRecommends'] = True
305325 if with_init:
306326 data['IncludesInit'] = True
327 data['BootstrapTool'] = self._gconf.bootstrap_tool
307328
308329 with open(self.get_config_location(), 'wt') as f:
309330 f.write(json.dumps(data, sort_keys=True, indent=4))
384405 if not extra_suites:
385406 extra_suites = []
386407
408 bootstrap_tool_exe = self._gconf.bootstrap_tool
409 if not shutil.which(bootstrap_tool_exe):
410 print_error('Unable to find executable for bootstrap tool "{}".'.format(bootstrap_tool_exe))
411 return False
412
387413 # ensure image location exists
388414 Path(self._gconf.osroots_dir).mkdir(parents=True, exist_ok=True)
389415
392418 else:
393419 print_section('Creating new base: {} [{}]'.format(self.suite, self.arch))
394420
421 print('Bootstrap tool:', '{} {}'.format(bootstrap_tool_exe, bootstrap_tool_version(self._gconf)))
422 if bootstrap_tool_exe not in self._known_bootstrap_tools:
423 print_warn('Using unfamiliar bootstrap tool: {}'.format(bootstrap_tool_exe))
395424 if self._custom_name:
396425 print('Custom name: {}'.format(self.name))
397426 print('Using mirror: {}'.format(mirror if mirror else 'default'))
403432 include_pkgs = ['python3-minimal', 'eatmydata']
404433 if with_init:
405434 include_pkgs.append('systemd-sysv')
406 cmd = ['debootstrap', '--arch={}'.format(self.arch), '--include=' + ','.join(include_pkgs)]
435 cmd = [bootstrap_tool_exe, '--arch={}'.format(self.arch), '--include=' + ','.join(include_pkgs)]
407436 if components:
408437 cmd.append('--components={}'.format(','.join(components)))
409438 if self.variant:
851880 command,
852881 build_dir,
853882 artifacts_dir,
883 boot: bool = False,
854884 init_command=None,
855885 copy_command=False,
856886 header_msg=None,
9951025 nspawn_flags.extend(['--bind-ro={}:/srv/build/'.format(build_dir)])
9961026
9971027 r = nspawn_run_persist(
998 self, instance_dir, machine_name, chdir, command, nspawn_flags, allowed=allowed
1028 self, instance_dir, machine_name, chdir, command, nspawn_flags, allowed=allowed, boot=boot
9991029 )
10001030 if r != 0:
10011031 return False
2121 listify,
2222 temp_dir,
2323 rmtree_mntsafe,
24 systemd_escape,
2425 format_filesize,
2526 hardlink_or_copy,
2627 )
4243 'hardlink_or_copy',
4344 'format_filesize',
4445 'rmtree_mntsafe',
46 'systemd_escape',
4547 ]
126126 else:
127127 print(prefix)
128128 sys.stdout.flush()
129
130
131 def input_bool(question_text, default=False) -> bool:
132 """As user a Yes/No question."""
133 if default:
134 default_info = '[Y/n]'
135 else:
136 default_info = '[y/N]'
137 while True:
138 try:
139 in_str = input('{} {}:'.format(question_text, default_info))
140 except EOFError:
141 return default
142 if in_str == 'y' or in_str == 'Y':
143 return True
144 elif in_str == 'n' or in_str == 'N':
145 return False
146 elif not in_str:
147 return default
129148
130149
131150 class TwoStreamLogger:
2121 import stat
2222 import fcntl
2323 import shutil
24 import typing as T
2425 import subprocess
25 from typing import Any, Optional
2626 from pathlib import Path
2727 from contextlib import contextmanager
2828
2929 from ..config import GlobalConfig
3030
3131
32 def listify(item: Any):
32 def listify(item: T.Any):
3333 '''
3434 Return a list of :item, unless :item already is a lit.
3535 '''
4747 os.chdir(ncwd)
4848
4949
50 def random_string(prefix: Optional[str] = None, count: int = 8):
50 def random_string(prefix: T.Optional[str] = None, count: int = 8):
5151 '''
5252 Create a string of random alphanumeric characters of a given length,
5353 separated with a hyphen from an optional prefix.
6262 if prefix:
6363 return '{}-{}'.format(prefix, rdm_id)
6464 return rdm_id
65
66
67 def systemd_escape(name: str) -> T.Optional[str]:
68 '''Escape a string using systemd's escaping rules.'''
69 from .command import run_command
70
71 out, _, ret = run_command(['systemd-escape', name])
72 if ret != 0:
73 return None
74 return out.strip()
6575
6676
6777 @contextmanager
259269 # This can only happen if someone replaces
260270 # a directory with a symlink after the call to
261271 # os.scandir or stat.S_ISDIR above.
262 raise OSError("Cannot call rmtree on a symbolic " "link")
272 raise OSError('Cannot call rmtree on a symbolic link')
263273 except OSError:
264274 onerror(os.path.islink, fullname, sys.exc_info())
265275 finally:
249249 host's <filename>/dev</filename> and <filename>/proc</filename> filesystems
250250 available from within the container. See the <parameter>--allow</parameter> option
251251 of <command>&package; run</command> for more details.
252 (Default: <code>false</code>)
253 </para>
254 </listitem>
255 </varlistentry>
256
257 <varlistentry>
258 <term><option>CachePackages</option></term>
259 <listitem>
260 <para>
261 Boolean option. If set to <literal>false</literal>, <command>&package;</command> will not
262 manage its own local cache of APT packages, but will instead always try to download
263 them. It is only recommended to change this option if you are already running a separate APT
264 package repository mirror or a caching proxy such as apt-cacher-ng(8).
265 (Default: <code>true</code>)
266 </para>
267 </listitem>
268 </varlistentry>
269
270 <varlistentry>
271 <term><option>BootstrapTool</option></term>
272 <listitem>
273 <para>
274 Set the bootstrap tool that should be used for bootstrapping new images.
275 The tool should have an interface compatible with debootstrap(8). This option
276 allows to use alternative tools like mmdebstrap(1) with <command>&package;</command>.
277 (Default: <code>debootstrap</code>)
252278 </para>
253279 </listitem>
254280 </varlistentry>
11 name = "debspawn"
22 description = "Debian package builder and build helper using systemd-nspawn"
33 authors = [
4 "Matthias Klumpp <matthias@tenstral.net>"
4 {name = "Matthias Klumpp", email = "matthias@tenstral.net"},
55 ]
6 license = "LGPL-3.0-or-later"
6 license = {text="LGPL-3.0-or-later"}
77 readme = "README.md"
8 python = "^3.9"
9 homepage = "https://github.com/lkhq/debspawn"
10 repository = "https://github.com/lkhq/debspawn"
8 requires-python = ">=3.9"
9 dynamic = ['version']
10
11 [project.urls]
12 Documentation = "https://github.com/lkhq/debspawn"
13 Source = "https://github.com/lkhq/debspawn"
1114
1215 [build-system]
1316 requires = ["setuptools", "wheel", "pkgconfig"]
9898
9999 scripts = ['debspawn.py']
100100
101 install_requires = ['toml>=0.10']
101 install_requires = ['tomlkit>=0.8']
102102
103103 setup(
104104 name=__appname__,
117117 include_package_data=True,
118118 #
119119 packages=packages,
120 cmdclass=cmdclass, # type: ignore
120 cmdclass=cmdclass,
121121 package_data=package_data,
122122 scripts=scripts,
123123 install_requires=install_requires,