Coverage for debputy/architecture_support.py: 89%
100 statements
« prev ^ index » next coverage.py v6.5.0, created at 2023-01-22 14:29 +0100
« prev ^ index » next coverage.py v6.5.0, created at 2023-01-22 14:29 +0100
1import os
2import subprocess
3from functools import lru_cache
4from typing import Dict
7class DpkgArchitectureBuildProcessValuesTable:
8 """Dict-like interface to dpkg-architecture values"""
10 def __init__(self, *, mocked_answers: Dict[str, str] = None):
11 """Create a new dpkg-architecture table; NO INSTANTIATION
13 This object will be created for you; if you need a production instance
14 then call dpkg_architecture_table(). If you need a testing instance,
15 then call mock_arch_table(...)
17 :param mocked_answers: Used for testing purposes. Do not use directly;
18 instead use mock_arch_table(...) to create the table you want.
19 """
20 self._architecture_cache = {}
21 self._has_run_dpkg_architecture = False
22 if mocked_answers is None:
23 self._architecture_cache = {}
24 self._respect_environ = True
25 self._has_run_dpkg_architecture = False
26 else:
27 self._architecture_cache = mocked_answers
28 self._respect_environ = False
29 self._has_run_dpkg_architecture = True
31 def __contains__(self, item: str) -> bool:
32 try:
33 self[item]
34 except KeyError:
35 return False
36 else:
37 return True
39 def __getitem__(self, item: str):
40 if item not in self._architecture_cache:
41 if self._respect_environ: 41 ↛ 42line 41 didn't jump to line 42, because the condition on line 41 was never true
42 value = os.environ.get(item)
43 if value is not None:
44 self._architecture_cache[item] = value
45 return value
46 if not self._has_run_dpkg_architecture: 46 ↛ 47line 46 didn't jump to line 47, because the condition on line 46 was never true
47 self._load_dpkg_architecture_values()
48 # Fall through and look it up in the cache
49 return self._architecture_cache[item]
51 @property
52 def current_host_arch(self):
53 """The architecture we are building for
55 This is the architecture name you need if you are in doubt.
56 """
57 return self['DEB_HOST_ARCH']
59 @property
60 def current_host_multiarch(self):
61 """The multi-arch path basename
63 This is the multi-arch basename name you need if you are in doubt. It
64 goes here:
66 "/usr/lib/{MA}".format(table.current_host_multiarch)
68 """
69 return self['DEB_HOST_MULTIARCH']
71 @property
72 def is_cross_compiling(self):
73 """Whether we are cross-compiling
75 This is defined as DEB_BUILD_GNU_TYPE != DEB_HOST_GNU_TYPE and
76 affects whether we can rely on being able to run the binaries
77 that are compiled.
78 """
79 return self['DEB_BUILD_GNU_TYPE'] != self['DEB_HOST_GNU_TYPE']
81 def _load_dpkg_architecture_values(self):
82 kw_pairs = _parse_dpkg_arch_output(subprocess.check_output(['dpkg-architecture']))
83 for k, v in kw_pairs:
84 self._architecture_cache[k] = os.environ.get(k, v)
85 self._has_run_dpkg_architecture = True
88def _parse_dpkg_arch_output(output: bytes):
89 text = output.decode('utf-8')
90 for line in text.splitlines():
91 k, v = line.strip().split('=', 1)
92 yield k, v
95def _rewrite(value, from_pattern, to_pattern):
96 assert value.startswith(from_pattern)
97 return to_pattern + value[len(from_pattern):]
100def mock_arch_table(host_arch, *, build_arch=None, target_arch=None):
101 """Creates a mocked instance of DpkgArchitectureBuildProcessValuesTable
104 :param host_arch: The dpkg architecture to mock answers for. This affects
105 DEB_HOST_* values and defines the default for DEB_{BUILD,TARGET}_* if
106 not overridden.
107 :param build_arch: If set and has a different value than host_arch, then
108 pretend this is a cross-build. This value affects the DEB_BUILD_* values.
109 :param target_arch: If set and has a different value than host_arch, then
110 pretend this is a build _of_ a cross-compiler. This value affects the
111 DEB_TARGET_* values.
112 """
114 if build_arch is None:
115 build_arch = host_arch
117 if target_arch is None:
118 target_arch = host_arch
119 return _mock_arch_tables(host_arch, build_arch, target_arch)
122@lru_cache
123def _mock_arch_tables(host_arch, build_arch, target_arch):
124 mock_table = {}
126 env = dict(os.environ)
127 # Set CC to /bin/true avoid a warning from dpkg-architecture
128 env['CC'] = '/bin/true'
129 # Clear environ variables that might confuse dpkg-architecture
130 for k in os.environ:
131 if k.startswith('DEB_'):
132 del env[k]
134 if build_arch == host_arch:
135 # easy / common case - we can handle this with a single call
136 kw_pairs = _parse_dpkg_arch_output(subprocess.check_output(
137 ['dpkg-architecture', '-a', host_arch, '-A', target_arch],
138 env=env)
139 )
140 for k, v in kw_pairs:
141 if k.startswith(('DEB_HOST_', 'DEB_TARGET_')):
142 mock_table[k] = v
143 # Clone DEB_HOST_* into DEB_BUILD_* as well
144 if k.startswith('DEB_HOST_'):
145 k2 = _rewrite(k, 'DEB_HOST_', 'DEB_BUILD_')
146 mock_table[k2] = v
147 elif build_arch != host_arch and host_arch != target_arch:
148 # This will need two dpkg-architecture calls because we cannot set
149 # DEB_BUILD_* directly. But we can set DEB_HOST_* and then rewrite
150 # it
151 # First handle the build arch
152 kw_pairs = _parse_dpkg_arch_output(subprocess.check_output(['dpkg-architecture', '-a', build_arch], env=env))
153 for k, v in kw_pairs:
154 if k.startswith('DEB_HOST_'):
155 k = _rewrite(k, 'DEB_HOST_', 'DEB_BUILD_')
156 mock_table[k] = v
158 kw_pairs = _parse_dpkg_arch_output(subprocess.check_output(
159 ['dpkg-architecture', '-a', host_arch, '-A', target_arch],
160 env=env)
161 )
162 for k, v in kw_pairs:
163 if k.startswith(('DEB_HOST_', 'DEB_TARGET_')):
164 mock_table[k] = v
165 else:
166 # This is a fun special case. We know that:
167 # * build_arch != host_arch
168 # * host_arch == target_arch
169 # otherwise we would have hit one of the previous cases.
170 #
171 # We can do this in a single call to dpkg-architecture by
172 # a bit of "cleaver" rewriting.
173 #
174 # - Use -a to set DEB_HOST_* and then rewrite that as
175 # DEB_BUILD_*
176 # - use -A to set DEB_TARGET_* and then use that for both
177 # DEB_HOST_* and DEB_TARGET_*
179 kw_pairs = _parse_dpkg_arch_output(subprocess.check_output(
180 ['dpkg-architecture', '-a', build_arch, '-A', target_arch],
181 env=env)
182 )
183 for k, v in kw_pairs:
184 if k.startswith('DEB_HOST_'):
185 k2 = _rewrite(k, 'DEB_HOST_', 'DEB_BUILD_')
186 mock_table[k2] = v
187 continue
188 if k.startswith('DEB_TARGET_'):
189 mock_table[k] = v
190 k2 = _rewrite(k, 'DEB_TARGET_', 'DEB_HOST_')
191 mock_table[k2] = v
193 table = DpkgArchitectureBuildProcessValuesTable(mocked_answers=mock_table)
194 return table
197_ARCH_TABLE = DpkgArchitectureBuildProcessValuesTable()
200def dpkg_architecture_table():
201 return _ARCH_TABLE