shrinkwrap-tool 2026.2.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- shrinkwrap/__init__.py +1 -0
- shrinkwrap/__main__.py +4 -0
- shrinkwrap/commands/__init__.py +0 -0
- shrinkwrap/commands/build.py +91 -0
- shrinkwrap/commands/buildall.py +180 -0
- shrinkwrap/commands/clean.py +161 -0
- shrinkwrap/commands/inspect.py +235 -0
- shrinkwrap/commands/process.py +106 -0
- shrinkwrap/commands/run.py +311 -0
- shrinkwrap/config/FVP_Base_RevC-2xAEMvA-base.yaml +98 -0
- shrinkwrap/config/FVP_Base_RevC-2xAEMvA-rme.yaml +42 -0
- shrinkwrap/config/arch/v8.0.yaml +22 -0
- shrinkwrap/config/arch/v8.1.yaml +26 -0
- shrinkwrap/config/arch/v8.2.yaml +28 -0
- shrinkwrap/config/arch/v8.3.yaml +25 -0
- shrinkwrap/config/arch/v8.4.yaml +26 -0
- shrinkwrap/config/arch/v8.5.yaml +29 -0
- shrinkwrap/config/arch/v8.6.yaml +28 -0
- shrinkwrap/config/arch/v8.7.yaml +24 -0
- shrinkwrap/config/arch/v8.8.yaml +31 -0
- shrinkwrap/config/arch/v8.9.yaml +32 -0
- shrinkwrap/config/arch/v9.0.yaml +29 -0
- shrinkwrap/config/arch/v9.1.yaml +25 -0
- shrinkwrap/config/arch/v9.2.yaml +29 -0
- shrinkwrap/config/arch/v9.3.yaml +23 -0
- shrinkwrap/config/arch/v9.4.yaml +21 -0
- shrinkwrap/config/arch/v9.5.yaml +20 -0
- shrinkwrap/config/bootwrapper.yaml +76 -0
- shrinkwrap/config/buildroot-cca.yaml +113 -0
- shrinkwrap/config/buildroot.yaml +54 -0
- shrinkwrap/config/cca-3world.yaml +215 -0
- shrinkwrap/config/cca-4world.yaml +57 -0
- shrinkwrap/config/cca-edk2.yaml +58 -0
- shrinkwrap/config/debug/rmm.yaml +15 -0
- shrinkwrap/config/debug/tfa.yaml +18 -0
- shrinkwrap/config/debug/tftf.yaml +17 -0
- shrinkwrap/config/dt-base.yaml +115 -0
- shrinkwrap/config/edk2-base.yaml +59 -0
- shrinkwrap/config/ffa-hafnium-optee.yaml +45 -0
- shrinkwrap/config/ffa-optee.yaml +30 -0
- shrinkwrap/config/ffa-tftf.yaml +26 -0
- shrinkwrap/config/hafnium-base.yaml +51 -0
- shrinkwrap/config/kvm-unit-tests.yaml +32 -0
- shrinkwrap/config/kvmtool-base.yaml +33 -0
- shrinkwrap/config/linux-base.yaml +80 -0
- shrinkwrap/config/ns-edk2-base.yaml +83 -0
- shrinkwrap/config/ns-edk2-optee.yaml +41 -0
- shrinkwrap/config/ns-edk2.yaml +49 -0
- shrinkwrap/config/ns-preload.yaml +98 -0
- shrinkwrap/config/optee-base.yaml +37 -0
- shrinkwrap/config/rfa-base.yaml +49 -0
- shrinkwrap/config/rfa.yaml +47 -0
- shrinkwrap/config/rmm-base.yaml +24 -0
- shrinkwrap/config/rust.yaml +31 -0
- shrinkwrap/config/test/cca.yaml +47 -0
- shrinkwrap/config/tfa-base.yaml +45 -0
- shrinkwrap/config/tfa-rme.yaml +36 -0
- shrinkwrap/config/tftf-base.yaml +32 -0
- shrinkwrap/shrinkwrap_main.py +133 -0
- shrinkwrap/utils/__init__.py +0 -0
- shrinkwrap/utils/clivars.py +16 -0
- shrinkwrap/utils/config.py +1166 -0
- shrinkwrap/utils/graph.py +263 -0
- shrinkwrap/utils/label.py +153 -0
- shrinkwrap/utils/logger.py +160 -0
- shrinkwrap/utils/process.py +230 -0
- shrinkwrap/utils/runtime.py +192 -0
- shrinkwrap/utils/ssh_agent.py +98 -0
- shrinkwrap/utils/tty.py +46 -0
- shrinkwrap/utils/vars.py +14 -0
- shrinkwrap/utils/workspace.py +59 -0
- shrinkwrap_tool-2026.2.1.dist-info/METADATA +63 -0
- shrinkwrap_tool-2026.2.1.dist-info/RECORD +77 -0
- shrinkwrap_tool-2026.2.1.dist-info/WHEEL +5 -0
- shrinkwrap_tool-2026.2.1.dist-info/entry_points.txt +2 -0
- shrinkwrap_tool-2026.2.1.dist-info/licenses/license.rst +41 -0
- shrinkwrap_tool-2026.2.1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
# Copyright (c) 2022, Arm Limited.
|
|
2
|
+
# SPDX-License-Identifier: MIT
|
|
3
|
+
|
|
4
|
+
import fcntl
|
|
5
|
+
import io
|
|
6
|
+
import os
|
|
7
|
+
import pty
|
|
8
|
+
import shlex
|
|
9
|
+
import selectors
|
|
10
|
+
import subprocess
|
|
11
|
+
import sys
|
|
12
|
+
import shrinkwrap.utils.runtime as runtime
|
|
13
|
+
import shrinkwrap.utils.tty as tty
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
STDOUT = 0
|
|
17
|
+
STDERR = 1
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class Process:
|
|
21
|
+
"""
|
|
22
|
+
A wrapper to a process that should be managed by the ProcessManager.
|
|
23
|
+
"""
|
|
24
|
+
def __init__(self, args, interactive, data, run_to_end):
|
|
25
|
+
self.args = shlex.split(args)
|
|
26
|
+
self.interactive = interactive
|
|
27
|
+
self.data = data
|
|
28
|
+
self.run_to_end = run_to_end
|
|
29
|
+
self._popen = None
|
|
30
|
+
self._stdout = None
|
|
31
|
+
self._stdin = None
|
|
32
|
+
self._stderr = None
|
|
33
|
+
self._active = 0
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class ProcessManager:
|
|
37
|
+
"""
|
|
38
|
+
A ProcessManager can manage a set of processes running in parallel such
|
|
39
|
+
that it can capture all their (stdout + stderr) output and pass it to a
|
|
40
|
+
handler delegate.
|
|
41
|
+
"""
|
|
42
|
+
def __init__(self, handler=None, terminate_handler=None):
|
|
43
|
+
self._handler = handler
|
|
44
|
+
self._terminate_handler = terminate_handler
|
|
45
|
+
self._procs = []
|
|
46
|
+
self._active = 0
|
|
47
|
+
self._sel = None
|
|
48
|
+
|
|
49
|
+
def set_handler(self, handler):
|
|
50
|
+
self._handler = handler
|
|
51
|
+
|
|
52
|
+
def add(self, process):
|
|
53
|
+
self._procs.append(process)
|
|
54
|
+
if self._sel:
|
|
55
|
+
self._proc_activate(process)
|
|
56
|
+
|
|
57
|
+
def run(self, forward_stdin=False):
|
|
58
|
+
try:
|
|
59
|
+
self._sel = selectors.DefaultSelector()
|
|
60
|
+
self._active = 0
|
|
61
|
+
|
|
62
|
+
if forward_stdin:
|
|
63
|
+
self._stdin_activate()
|
|
64
|
+
|
|
65
|
+
for proc in self._procs:
|
|
66
|
+
self._proc_activate(proc)
|
|
67
|
+
|
|
68
|
+
while self._active > 0:
|
|
69
|
+
for key, mask in self._sel.select():
|
|
70
|
+
handler = key.data[0]
|
|
71
|
+
handler(key, mask)
|
|
72
|
+
finally:
|
|
73
|
+
for proc in self._procs:
|
|
74
|
+
try:
|
|
75
|
+
self._proc_deactivate(proc, force=True)
|
|
76
|
+
except:
|
|
77
|
+
pass
|
|
78
|
+
|
|
79
|
+
if forward_stdin:
|
|
80
|
+
self._stdin_deactivate()
|
|
81
|
+
|
|
82
|
+
self._sel.close()
|
|
83
|
+
self._sel = None
|
|
84
|
+
|
|
85
|
+
def _read_nonblock(self, fileobj):
|
|
86
|
+
# If stdin and stdout are both connected to a tty, setting stdin
|
|
87
|
+
# to nonblocking will also cause stdout to be set nonblocking
|
|
88
|
+
# and can cause errors when printing to stdout the output buffer
|
|
89
|
+
# is full. So work around that by only making fds nonblocking
|
|
90
|
+
# while we do the read.
|
|
91
|
+
fl = fcntl.fcntl(fileobj, fcntl.F_GETFL)
|
|
92
|
+
fcntl.fcntl(fileobj, fcntl.F_SETFL, fl | os.O_NONBLOCK)
|
|
93
|
+
data = fileobj.read()
|
|
94
|
+
fcntl.fcntl(fileobj, fcntl.F_SETFL, fl)
|
|
95
|
+
return data
|
|
96
|
+
|
|
97
|
+
def _proc_handle(self, key, mask):
|
|
98
|
+
proc = key.data[1]
|
|
99
|
+
streamid = key.data[2]
|
|
100
|
+
data = self._read_nonblock(key.fileobj)
|
|
101
|
+
|
|
102
|
+
if data == '':
|
|
103
|
+
self._proc_stream_deactivate(proc, key.fileobj, streamid)
|
|
104
|
+
elif self._handler:
|
|
105
|
+
self._handler(self, proc, data, streamid)
|
|
106
|
+
|
|
107
|
+
def _proc_activate(self, proc):
|
|
108
|
+
cmd = runtime.mkcmd(proc.args, proc.interactive)
|
|
109
|
+
|
|
110
|
+
if proc.interactive:
|
|
111
|
+
master, slave = pty.openpty()
|
|
112
|
+
|
|
113
|
+
proc._popen = subprocess.Popen(cmd,
|
|
114
|
+
stdin=slave,
|
|
115
|
+
stdout=slave,
|
|
116
|
+
stderr=slave)
|
|
117
|
+
|
|
118
|
+
proc._stdin = io.open(master, 'wb', buffering=0)
|
|
119
|
+
proc._stdout = io.open(master, 'rb', -1, closefd=False)
|
|
120
|
+
# Don't attempt to translate newlines. We need the '\r's
|
|
121
|
+
# to correctly return the carriage for interactive
|
|
122
|
+
# terminals. Telnet sometimes gives '\r\r\n' too, which
|
|
123
|
+
# would be incorrectly translated to '\n\n'.
|
|
124
|
+
proc._stdout = io.TextIOWrapper(proc._stdout,
|
|
125
|
+
newline='',
|
|
126
|
+
errors='replace')
|
|
127
|
+
# stdout and stderr get merged into pty, so can't tell
|
|
128
|
+
# them apart. This isn't a problem for the emit build
|
|
129
|
+
# warnings use case.
|
|
130
|
+
proc._stderr = None
|
|
131
|
+
proc._active = 1
|
|
132
|
+
else:
|
|
133
|
+
proc._popen = subprocess.Popen(cmd,
|
|
134
|
+
stdin=subprocess.DEVNULL,
|
|
135
|
+
stdout=subprocess.PIPE,
|
|
136
|
+
stderr=subprocess.PIPE,
|
|
137
|
+
universal_newlines=True,
|
|
138
|
+
errors='replace')
|
|
139
|
+
|
|
140
|
+
proc._stdin = None
|
|
141
|
+
proc._stdout = proc._popen.stdout
|
|
142
|
+
proc._stderr = proc._popen.stderr
|
|
143
|
+
proc._active = 2
|
|
144
|
+
|
|
145
|
+
if proc._stdout:
|
|
146
|
+
self._sel.register(proc._stdout,
|
|
147
|
+
selectors.EVENT_READ,
|
|
148
|
+
(self._proc_handle, proc, STDOUT))
|
|
149
|
+
|
|
150
|
+
if proc._stderr:
|
|
151
|
+
self._sel.register(proc._stderr,
|
|
152
|
+
selectors.EVENT_READ,
|
|
153
|
+
(self._proc_handle, proc, STDERR))
|
|
154
|
+
|
|
155
|
+
if proc.run_to_end:
|
|
156
|
+
self._active += 1
|
|
157
|
+
|
|
158
|
+
def _proc_stream_deactivate(self, proc, stream, streamid, force=False):
|
|
159
|
+
if not stream:
|
|
160
|
+
return
|
|
161
|
+
|
|
162
|
+
self._sel.unregister(stream)
|
|
163
|
+
|
|
164
|
+
if streamid == STDOUT:
|
|
165
|
+
proc._stdout = None
|
|
166
|
+
if streamid == STDERR:
|
|
167
|
+
proc._stderr = None
|
|
168
|
+
|
|
169
|
+
assert(proc._active > 0)
|
|
170
|
+
proc._active -= 1
|
|
171
|
+
|
|
172
|
+
if not force and proc._active == 0:
|
|
173
|
+
self._proc_deactivate(proc, force)
|
|
174
|
+
|
|
175
|
+
def _proc_deactivate(self, proc, force):
|
|
176
|
+
if not proc._popen:
|
|
177
|
+
return
|
|
178
|
+
|
|
179
|
+
if proc.run_to_end:
|
|
180
|
+
self._active -= 1
|
|
181
|
+
|
|
182
|
+
proc._stdin = None
|
|
183
|
+
self._proc_stream_deactivate(proc, proc._stdout, STDOUT, force)
|
|
184
|
+
self._proc_stream_deactivate(proc, proc._stderr, STDERR, force)
|
|
185
|
+
|
|
186
|
+
proc._popen.kill()
|
|
187
|
+
try:
|
|
188
|
+
proc._popen.communicate()
|
|
189
|
+
except:
|
|
190
|
+
pass
|
|
191
|
+
retcode = None if force else proc._popen.poll()
|
|
192
|
+
proc._popen = None
|
|
193
|
+
|
|
194
|
+
if self._terminate_handler:
|
|
195
|
+
self._terminate_handler(self, proc, retcode)
|
|
196
|
+
|
|
197
|
+
def _stdin_handle(self, key, mask):
|
|
198
|
+
data = self._read_nonblock(key.fileobj)
|
|
199
|
+
for proc in self._procs:
|
|
200
|
+
if proc._stdin:
|
|
201
|
+
proc._stdin.write(data)
|
|
202
|
+
|
|
203
|
+
def _stdin_activate(self):
|
|
204
|
+
# Replace stdin with unbuffered binary stream so we can pass
|
|
205
|
+
# input to the ptys without any modification.
|
|
206
|
+
self._stdin_orig = sys.stdin
|
|
207
|
+
sys.stdin = io.open(self._stdin_orig.fileno(),
|
|
208
|
+
'rb',
|
|
209
|
+
buffering=0,
|
|
210
|
+
closefd=False)
|
|
211
|
+
|
|
212
|
+
# Set the terminal to raw input mode.
|
|
213
|
+
self._tty_orig = tty.configure(sys.stdin)
|
|
214
|
+
|
|
215
|
+
# Register stdin so we get notified when there is data.
|
|
216
|
+
self._sel.register(sys.stdin,
|
|
217
|
+
selectors.EVENT_READ,
|
|
218
|
+
(self._stdin_handle,))
|
|
219
|
+
|
|
220
|
+
def _stdin_deactivate(self):
|
|
221
|
+
# Unregister for notifications.
|
|
222
|
+
self._sel.unregister(sys.stdin)
|
|
223
|
+
|
|
224
|
+
# Restore terminal mode.
|
|
225
|
+
tty.restore(sys.stdin, self._tty_orig)
|
|
226
|
+
|
|
227
|
+
# Restore stdin to being buffered text.
|
|
228
|
+
sys.stdin.close()
|
|
229
|
+
sys.stdin = self._stdin_orig
|
|
230
|
+
self._stdin_orig = None
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
# Copyright (c) 2022, Arm Limited.
|
|
2
|
+
# SPDX-License-Identifier: MIT
|
|
3
|
+
|
|
4
|
+
import os
|
|
5
|
+
import subprocess
|
|
6
|
+
import sys
|
|
7
|
+
import tuxmake.runtime
|
|
8
|
+
import types
|
|
9
|
+
import shrinkwrap.utils.ssh_agent as ssh_agent_lib
|
|
10
|
+
|
|
11
|
+
_SSH_AUTH_SOCK = '/run/host-services/ssh-auth.sock'
|
|
12
|
+
"""Path to ssh-agent socket."""
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
_instance = None
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def get_null_user_opts(self):
|
|
19
|
+
return []
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class Runtime:
|
|
23
|
+
"""
|
|
24
|
+
Wraps tuxmake.runtime to provide an interface for executing commands in
|
|
25
|
+
an abstracted runtime. Multiple runtimes are supported, identified by a
|
|
26
|
+
`name`. The 'null' runtime simply executes the commands on the native
|
|
27
|
+
host. The 'docker', 'docker-local', 'podman' and 'podman-local' runtimes
|
|
28
|
+
execute the commands in a container. `timeout`, if provided, is an
|
|
29
|
+
integer number of days after which the container will be shutdown if
|
|
30
|
+
still running. If None, the default timeout is used.
|
|
31
|
+
"""
|
|
32
|
+
def __init__(self, *, name, image=None, ssh_agent_keys=None, timeout=None):
|
|
33
|
+
self._rt = None
|
|
34
|
+
self._mountpoints = set()
|
|
35
|
+
|
|
36
|
+
ssh_agent_keys = ssh_agent_keys or []
|
|
37
|
+
|
|
38
|
+
self._rt = tuxmake.runtime.Runtime.get(name)
|
|
39
|
+
self._rt.set_image(image)
|
|
40
|
+
self._rt.network = "host"
|
|
41
|
+
|
|
42
|
+
is_mac = sys.platform.startswith('darwin')
|
|
43
|
+
is_docker = name.startswith('docker')
|
|
44
|
+
is_container = is_docker or name.startswith('podman')
|
|
45
|
+
|
|
46
|
+
# MacOS uses GIDs that overlap with already defined GIDs in the
|
|
47
|
+
# container so we can't just bind the macos host UID/GID to the
|
|
48
|
+
# shrinkwrap user in the container. This concept doesn't really
|
|
49
|
+
# work anyway, because on MacOS the container is running on a
|
|
50
|
+
# completely separate (linux) kernel in a VM. Fortunately docker
|
|
51
|
+
# maps the VM to the current MacOS user when touching mapped
|
|
52
|
+
# volumes so it all works out. So on MacOS run as root.
|
|
53
|
+
# Unfortunately, tuxmake tries to be too clever (it assumes a
|
|
54
|
+
# linux host) and tries to map the in-container user to the host
|
|
55
|
+
# UID/GID. This fails when the in-container user is root. So we
|
|
56
|
+
# have this ugly workaround to override the user-opts with
|
|
57
|
+
# nothing. By passing nothing, we implicitly run as root and
|
|
58
|
+
# tuxmake doesn't try to run usermod.
|
|
59
|
+
if is_mac and is_docker:
|
|
60
|
+
self._rt.get_user_opts = \
|
|
61
|
+
types.MethodType(get_null_user_opts, self._rt)
|
|
62
|
+
else:
|
|
63
|
+
self._rt.set_user('shrinkwrap')
|
|
64
|
+
self._rt.set_group('shrinkwrap')
|
|
65
|
+
|
|
66
|
+
# Tuxmake always starts the container with "sleep 1d" so that it
|
|
67
|
+
# automatically shuts down after 1 day if it ends up dangling.
|
|
68
|
+
# This is problematic for some long running FVP test cases, so
|
|
69
|
+
# we allow overriding it on the command line. We add a filter
|
|
70
|
+
# for spawn_container() which modifies the sleep argument which
|
|
71
|
+
# is the last element in the cmd list. Avoid hooking unless the
|
|
72
|
+
# user explicitly specified a timeout that is different from the
|
|
73
|
+
# expected default.
|
|
74
|
+
if is_container and timeout and timeout != 1:
|
|
75
|
+
spawn_container_orig = self._rt.spawn_container
|
|
76
|
+
def spawn_container_new(self, cmd):
|
|
77
|
+
if (cmd[-1] == '1d'):
|
|
78
|
+
cmd[-1] = f'{timeout}d'
|
|
79
|
+
return spawn_container_orig(cmd)
|
|
80
|
+
self._rt.spawn_container = \
|
|
81
|
+
types.MethodType(spawn_container_new, self._rt)
|
|
82
|
+
|
|
83
|
+
for key in ssh_agent_keys:
|
|
84
|
+
ssh_agent_lib.add(key)
|
|
85
|
+
|
|
86
|
+
socket = ssh_agent_lib.socket()
|
|
87
|
+
|
|
88
|
+
if name != 'null' and socket is not None:
|
|
89
|
+
if is_mac:
|
|
90
|
+
socket = _SSH_AUTH_SOCK
|
|
91
|
+
|
|
92
|
+
self._rt.add_volume(socket, _SSH_AUTH_SOCK)
|
|
93
|
+
|
|
94
|
+
def start(self):
|
|
95
|
+
for mp in self._mountpoints:
|
|
96
|
+
self._rt.add_volume(mp)
|
|
97
|
+
|
|
98
|
+
self._rt.prepare()
|
|
99
|
+
|
|
100
|
+
def add_volume(self, src):
|
|
101
|
+
# Podman can't deal with duplicate mount points, so filter out
|
|
102
|
+
# duplicates here, including mount-points that are children of
|
|
103
|
+
# other mount points. Then defer registering the actual final
|
|
104
|
+
# volumes until start() time.
|
|
105
|
+
|
|
106
|
+
if not src:
|
|
107
|
+
return
|
|
108
|
+
|
|
109
|
+
mountpoints = set()
|
|
110
|
+
|
|
111
|
+
for mp in self._mountpoints:
|
|
112
|
+
common = os.path.commonpath([src, mp])
|
|
113
|
+
if common == mp:
|
|
114
|
+
# mp is parent (or duplicate) of src, so src
|
|
115
|
+
# already covered.
|
|
116
|
+
return
|
|
117
|
+
elif common != src:
|
|
118
|
+
# src is not a parent of mp. So include mp in
|
|
119
|
+
# filtered set of mount points.
|
|
120
|
+
mountpoints.add(mp)
|
|
121
|
+
|
|
122
|
+
# If we got here, then src is a unique mountpoint. Add it, and
|
|
123
|
+
# commit the filtered set.
|
|
124
|
+
mountpoints.add(src)
|
|
125
|
+
self._mountpoints = mountpoints
|
|
126
|
+
|
|
127
|
+
def mkcmd(self, cmd, interactive=False):
|
|
128
|
+
return self._rt.get_command_line(cmd, interactive, False)
|
|
129
|
+
|
|
130
|
+
def ip_address(self):
|
|
131
|
+
"""
|
|
132
|
+
Returns the primary ip address of the runtime.
|
|
133
|
+
"""
|
|
134
|
+
|
|
135
|
+
script = """
|
|
136
|
+
import socket
|
|
137
|
+
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
|
138
|
+
s.settimeout(0)
|
|
139
|
+
try:
|
|
140
|
+
s.connect(('10.254.254.254', 1))
|
|
141
|
+
ip = s.getsockname()[0]
|
|
142
|
+
except Exception:
|
|
143
|
+
ip = '127.0.0.1'
|
|
144
|
+
finally:
|
|
145
|
+
s.close()
|
|
146
|
+
print(ip)
|
|
147
|
+
""".replace('\n', '\\n').replace('\t', '\\t')
|
|
148
|
+
|
|
149
|
+
cmd = ['python3', '-c', f'exec("{script}")']
|
|
150
|
+
res = subprocess.run(self.mkcmd(cmd),
|
|
151
|
+
universal_newlines=True,
|
|
152
|
+
stdout=subprocess.PIPE,
|
|
153
|
+
stderr=subprocess.PIPE)
|
|
154
|
+
if res.returncode == 0:
|
|
155
|
+
return res.stdout.strip()
|
|
156
|
+
return '127.0.0.1'
|
|
157
|
+
|
|
158
|
+
def __enter__(self):
|
|
159
|
+
global _instance
|
|
160
|
+
assert _instance is None
|
|
161
|
+
_instance = self
|
|
162
|
+
return self
|
|
163
|
+
|
|
164
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
165
|
+
global _instance
|
|
166
|
+
assert _instance == self
|
|
167
|
+
_instance = None
|
|
168
|
+
if self._rt:
|
|
169
|
+
# Horrible hack alert; I've observed that docker can
|
|
170
|
+
# take over 10 seconds to stop the container on at least
|
|
171
|
+
# 1 system running Ubuntu 24.04. Let's cleanup the
|
|
172
|
+
# container asynchonously in a child process.
|
|
173
|
+
if os.fork() == 0:
|
|
174
|
+
self._rt.cleanup()
|
|
175
|
+
exit()
|
|
176
|
+
self._rt = None
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def get():
|
|
180
|
+
"""
|
|
181
|
+
Returns the current Runtime instance.
|
|
182
|
+
"""
|
|
183
|
+
global _instance
|
|
184
|
+
assert _instance is not None
|
|
185
|
+
return _instance
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def mkcmd(cmd, interactive=False):
|
|
189
|
+
"""
|
|
190
|
+
Shorthand for get().mkcmd().
|
|
191
|
+
"""
|
|
192
|
+
return get().mkcmd(cmd, interactive)
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# Copyright (c) 2024, Arm Limited.
|
|
2
|
+
# SPDX-License-Identifier: MIT
|
|
3
|
+
|
|
4
|
+
import atexit
|
|
5
|
+
import os
|
|
6
|
+
import re
|
|
7
|
+
import subprocess
|
|
8
|
+
from typing import Optional
|
|
9
|
+
|
|
10
|
+
import shrinkwrap.utils.logger as logger
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
_logger = logger.Logger(27)
|
|
14
|
+
"""Logger used within this module."""
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _log(msg):
|
|
18
|
+
"""Print a message to the console."""
|
|
19
|
+
_logger.print(msg, tag='ssh-agent', cont=False)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
_proc = None
|
|
23
|
+
"""ssh-agent process started by this module."""
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _start():
|
|
27
|
+
"""Start an ssh-agent process."""
|
|
28
|
+
global _proc
|
|
29
|
+
|
|
30
|
+
assert _proc is None
|
|
31
|
+
_proc = subprocess.run(
|
|
32
|
+
['ssh-agent', '-s'], stdout=subprocess.PIPE, text=True)
|
|
33
|
+
|
|
34
|
+
# Extract socket path and PID from ssh-agent output
|
|
35
|
+
output = _proc.stdout
|
|
36
|
+
match = re.search(
|
|
37
|
+
r'SSH_AUTH_SOCK=(?P<sock>[^;]+).*SSH_AGENT_PID=(?P<pid>[^;]+)',
|
|
38
|
+
output,
|
|
39
|
+
re.DOTALL | re.MULTILINE)
|
|
40
|
+
|
|
41
|
+
if not match:
|
|
42
|
+
raise Exception(f'Failed to parse ssh-agent output: {output}')
|
|
43
|
+
|
|
44
|
+
values = match.groupdict()
|
|
45
|
+
|
|
46
|
+
os.environ['SSH_AUTH_SOCK'] = values['sock']
|
|
47
|
+
os.environ['SSH_AGENT_PID'] = values['pid']
|
|
48
|
+
|
|
49
|
+
_log(f'Started ssh-agent pid {values["pid"]}')
|
|
50
|
+
|
|
51
|
+
# Register a handler to kill the ssh-agent process
|
|
52
|
+
atexit.register(_stop)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def _stop():
|
|
56
|
+
"""Stop the ssh-agent process."""
|
|
57
|
+
global _proc
|
|
58
|
+
|
|
59
|
+
assert _proc is not None
|
|
60
|
+
|
|
61
|
+
_log('Stopping ssh-agent')
|
|
62
|
+
|
|
63
|
+
subprocess.check_call(['ssh-agent', '-k'], stdout=subprocess.DEVNULL)
|
|
64
|
+
|
|
65
|
+
del os.environ['SSH_AUTH_SOCK']
|
|
66
|
+
del os.environ['SSH_AGENT_PID']
|
|
67
|
+
|
|
68
|
+
_proc = None
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def socket() -> Optional[str]:
|
|
72
|
+
"""Return path to socket."""
|
|
73
|
+
return os.environ.get('SSH_AUTH_SOCK')
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def add(key: Optional[str]=None) -> None:
|
|
77
|
+
"""
|
|
78
|
+
Add specified key, or add default keys if key is None.
|
|
79
|
+
|
|
80
|
+
If no ssh-agent process is running, one is started.
|
|
81
|
+
"""
|
|
82
|
+
if socket() is None:
|
|
83
|
+
_start()
|
|
84
|
+
else:
|
|
85
|
+
global _proc
|
|
86
|
+
if _proc is None:
|
|
87
|
+
raise Exception('Adding a key to an ssh-agent not owned by shrinkwrap is not supported.')
|
|
88
|
+
|
|
89
|
+
if key is None:
|
|
90
|
+
_log('Adding default keys')
|
|
91
|
+
else:
|
|
92
|
+
_log(f'Adding key {key}')
|
|
93
|
+
|
|
94
|
+
args = ['ssh-add']
|
|
95
|
+
if key is not None:
|
|
96
|
+
args.append(key)
|
|
97
|
+
|
|
98
|
+
subprocess.check_call(args)
|
shrinkwrap/utils/tty.py
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# Copyright (c) 2022, Arm Limited.
|
|
2
|
+
# SPDX-License-Identifier: MIT
|
|
3
|
+
|
|
4
|
+
from termios import *
|
|
5
|
+
import copy
|
|
6
|
+
|
|
7
|
+
IFLAG = 0
|
|
8
|
+
OFLAG = 1
|
|
9
|
+
CFLAG = 2
|
|
10
|
+
LFLAG = 3
|
|
11
|
+
ISPEED = 4
|
|
12
|
+
OSPEED = 5
|
|
13
|
+
CC = 6
|
|
14
|
+
|
|
15
|
+
def configure(fd, when=TCSAFLUSH):
|
|
16
|
+
try:
|
|
17
|
+
orig = tcgetattr(fd)
|
|
18
|
+
mode = copy.deepcopy(orig)
|
|
19
|
+
|
|
20
|
+
# Don't remove/convert line feeds to new lines on input. EDK2
|
|
21
|
+
# can't handle only receiving newlines.
|
|
22
|
+
mode[IFLAG] = mode[IFLAG] & ~(INLCR | IGNCR | ICRNL)
|
|
23
|
+
|
|
24
|
+
# Disable input echo and use non-canonical input mode (no
|
|
25
|
+
# buffering).
|
|
26
|
+
mode[LFLAG] = mode[LFLAG] & ~(ECHO | ICANON)
|
|
27
|
+
mode[CC][VMIN] = 1
|
|
28
|
+
mode[CC][VTIME] = 0
|
|
29
|
+
|
|
30
|
+
# Use ^] as the interrupt (instead of ^C), and remove key
|
|
31
|
+
# bindings for quit and suspend signals. This means the usual
|
|
32
|
+
# key bindings are passed through to the child.
|
|
33
|
+
mode[CC][VINTR] = b'\x1d'
|
|
34
|
+
mode[CC][VQUIT] = b'\x00'
|
|
35
|
+
mode[CC][VSUSP] = b'\x00'
|
|
36
|
+
|
|
37
|
+
tcsetattr(fd, when, mode)
|
|
38
|
+
return orig
|
|
39
|
+
|
|
40
|
+
except error as e:
|
|
41
|
+
# Expect failure if we are not connected to a tty. Non-fatal.
|
|
42
|
+
pass
|
|
43
|
+
|
|
44
|
+
def restore(fd, mode, when=TCSAFLUSH):
|
|
45
|
+
if mode:
|
|
46
|
+
tcsetattr(fd, when, mode)
|
shrinkwrap/utils/vars.py
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# Copyright (c) 2022, Arm Limited.
|
|
2
|
+
# SPDX-License-Identifier: MIT
|
|
3
|
+
|
|
4
|
+
def parse(args, type):
|
|
5
|
+
vars = {}
|
|
6
|
+
|
|
7
|
+
for pair in args:
|
|
8
|
+
try:
|
|
9
|
+
key, value = pair.split('=', maxsplit=1)
|
|
10
|
+
vars[key] = value
|
|
11
|
+
except ValueError:
|
|
12
|
+
raise Exception(f'Invalid {type}var {pair}')
|
|
13
|
+
|
|
14
|
+
return vars
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# Copyright (c) 2022, Arm Limited.
|
|
2
|
+
# SPDX-License-Identifier: MIT
|
|
3
|
+
|
|
4
|
+
import os
|
|
5
|
+
|
|
6
|
+
_code_root = os.path.dirname(os.path.dirname(__file__))
|
|
7
|
+
_data_root = os.path.expanduser('~/.shrinkwrap')
|
|
8
|
+
|
|
9
|
+
def _get_loc(var, default=None):
|
|
10
|
+
path = os.environ.get(var, default)
|
|
11
|
+
if not path:
|
|
12
|
+
return None
|
|
13
|
+
path = os.path.abspath(path)
|
|
14
|
+
os.makedirs(path, exist_ok=True)
|
|
15
|
+
return path
|
|
16
|
+
|
|
17
|
+
build = _get_loc('SHRINKWRAP_BUILD', os.path.join(_data_root, 'build'))
|
|
18
|
+
package = _get_loc('SHRINKWRAP_PACKAGE', os.path.join(_data_root, 'package'))
|
|
19
|
+
project_cache = _get_loc("SHRINKWRAP_PROJECT_CACHE")
|
|
20
|
+
|
|
21
|
+
_configs = None
|
|
22
|
+
|
|
23
|
+
def configs():
|
|
24
|
+
global _configs
|
|
25
|
+
|
|
26
|
+
if _configs is None:
|
|
27
|
+
value = os.environ.get('SHRINKWRAP_CONFIG')
|
|
28
|
+
|
|
29
|
+
# Set of paths that we will search for configs in, in priority
|
|
30
|
+
# order. Prefer any user-supplied paths, then fall back to
|
|
31
|
+
# ~/.shrinkwrap/config (which is the "installed" location of the
|
|
32
|
+
# standard config store), then fall back to the in-repo location
|
|
33
|
+
# for the uninstalled case. Then filter out any paths that don't
|
|
34
|
+
# exist.
|
|
35
|
+
paths = value.split(':') if value else []
|
|
36
|
+
paths += [os.path.realpath(os.path.join(_data_root, 'config'))]
|
|
37
|
+
paths += [os.path.realpath(os.path.join(_code_root, 'config'))]
|
|
38
|
+
paths += [os.path.realpath(os.path.join(_code_root, '../../config'))]
|
|
39
|
+
_configs = [p for p in paths if os.path.exists(p)]
|
|
40
|
+
|
|
41
|
+
return _configs
|
|
42
|
+
|
|
43
|
+
def config(path, join=True):
|
|
44
|
+
for config in configs():
|
|
45
|
+
p = os.path.join(config, path) if join else f'{config}{path}'
|
|
46
|
+
if os.path.exists(p):
|
|
47
|
+
return config
|
|
48
|
+
return None
|
|
49
|
+
|
|
50
|
+
def dump():
|
|
51
|
+
print(f'workspace.project_cache:')
|
|
52
|
+
print(f' {project_cache}')
|
|
53
|
+
print(f'workspace.build:')
|
|
54
|
+
print(f' {build}')
|
|
55
|
+
print(f'workspace.package:')
|
|
56
|
+
print(f' {package}')
|
|
57
|
+
print(f'workspace.config:')
|
|
58
|
+
for path in configs():
|
|
59
|
+
print(f' {path}')
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: shrinkwrap-tool
|
|
3
|
+
Version: 2026.2.1
|
|
4
|
+
Summary: Shrinkwrap tool for building and running Arm FVPs
|
|
5
|
+
Author-email: Ryan Roberts <ryan.roberts@arm.com>, Saul Romero D <saul.romerodominguez@arm.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://git.gitlab.arm.com/tooling/shrinkwrap
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: Operating System :: OS Independent
|
|
10
|
+
Requires-Python: >=3.8
|
|
11
|
+
Description-Content-Type: text/markdown
|
|
12
|
+
License-File: license.rst
|
|
13
|
+
Requires-Dist: PyYAML>=6.0.3
|
|
14
|
+
Requires-Dist: tuxmake>=1.34.0
|
|
15
|
+
Requires-Dist: termcolor<3.0,>=2.4.0; python_version < "3.10"
|
|
16
|
+
Requires-Dist: termcolor>=3.2.0; python_version >= "3.10"
|
|
17
|
+
Requires-Dist: graphlib-backport; python_version < "3.9"
|
|
18
|
+
Provides-Extra: dev
|
|
19
|
+
Requires-Dist: cffi>=2.0.0; extra == "dev"
|
|
20
|
+
Requires-Dist: cryptography>=46.0.3; extra == "dev"
|
|
21
|
+
Requires-Dist: cmake>=3.31.6; extra == "dev"
|
|
22
|
+
Requires-Dist: pycparser>=2.23; extra == "dev"
|
|
23
|
+
Requires-Dist: fdt>=0.3.3; extra == "dev"
|
|
24
|
+
Requires-Dist: pyelftools>=0.32; extra == "dev"
|
|
25
|
+
Requires-Dist: build; extra == "dev"
|
|
26
|
+
Requires-Dist: twine; extra == "dev"
|
|
27
|
+
Requires-Dist: pytest; extra == "dev"
|
|
28
|
+
Provides-Extra: docs
|
|
29
|
+
Requires-Dist: Sphinx<10,>=8.2; extra == "docs"
|
|
30
|
+
Requires-Dist: sphinx-rtd-theme<4,>=3.1.0; extra == "docs"
|
|
31
|
+
Requires-Dist: sphinx-copybutton>=0.5.2; extra == "docs"
|
|
32
|
+
Dynamic: license-file
|
|
33
|
+
|
|
34
|
+
# Shrinkwrap
|
|
35
|
+
|
|
36
|
+
Shrinkwrap is a tool to simplify the process of building and running firmware on
|
|
37
|
+
Arm Fixed Virtual Platforms (FVP). Users simply invoke the tool to build the
|
|
38
|
+
required config, then pass their own kernel and rootfs to the tool to boot the
|
|
39
|
+
full system on FVP.
|
|
40
|
+
|
|
41
|
+
- Documentation is available at: [ReadTheDocs](https://shrinkwrap.docs.arm.com)
|
|
42
|
+
- Source Code is available at: [GitLab](https://gitlab.arm.com/tooling/shrinkwrap)
|
|
43
|
+
- Shrinkwrap Container Images are available at: [DockerHub](https://hub.docker.com/u/shrinkwraptool)
|
|
44
|
+
|
|
45
|
+
The documentation (linked above) contains a
|
|
46
|
+
[QuickStart](https://shrinkwrap.docs.arm.com/en/latest/userguide/quickstart.html)
|
|
47
|
+
section, which details how to install and use the tool. However, if you are in a
|
|
48
|
+
hurry, here are the minimal steps:
|
|
49
|
+
|
|
50
|
+
> **NOTE:** This assumes you have Python >=3.9.0 and docker installed. If this
|
|
51
|
+
> is not the case, please refer to the documentation.
|
|
52
|
+
|
|
53
|
+
```
|
|
54
|
+
sudo apt-get install git netcat-openbsd python3 python3-pip telnet
|
|
55
|
+
python3 -m venv .venv
|
|
56
|
+
source .venv/bin/activate
|
|
57
|
+
python -m pip install --upgrade pip
|
|
58
|
+
python -m pip install shrinkwrap
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
```
|
|
62
|
+
shrinkwrap --help
|
|
63
|
+
```
|