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.
Files changed (77) hide show
  1. shrinkwrap/__init__.py +1 -0
  2. shrinkwrap/__main__.py +4 -0
  3. shrinkwrap/commands/__init__.py +0 -0
  4. shrinkwrap/commands/build.py +91 -0
  5. shrinkwrap/commands/buildall.py +180 -0
  6. shrinkwrap/commands/clean.py +161 -0
  7. shrinkwrap/commands/inspect.py +235 -0
  8. shrinkwrap/commands/process.py +106 -0
  9. shrinkwrap/commands/run.py +311 -0
  10. shrinkwrap/config/FVP_Base_RevC-2xAEMvA-base.yaml +98 -0
  11. shrinkwrap/config/FVP_Base_RevC-2xAEMvA-rme.yaml +42 -0
  12. shrinkwrap/config/arch/v8.0.yaml +22 -0
  13. shrinkwrap/config/arch/v8.1.yaml +26 -0
  14. shrinkwrap/config/arch/v8.2.yaml +28 -0
  15. shrinkwrap/config/arch/v8.3.yaml +25 -0
  16. shrinkwrap/config/arch/v8.4.yaml +26 -0
  17. shrinkwrap/config/arch/v8.5.yaml +29 -0
  18. shrinkwrap/config/arch/v8.6.yaml +28 -0
  19. shrinkwrap/config/arch/v8.7.yaml +24 -0
  20. shrinkwrap/config/arch/v8.8.yaml +31 -0
  21. shrinkwrap/config/arch/v8.9.yaml +32 -0
  22. shrinkwrap/config/arch/v9.0.yaml +29 -0
  23. shrinkwrap/config/arch/v9.1.yaml +25 -0
  24. shrinkwrap/config/arch/v9.2.yaml +29 -0
  25. shrinkwrap/config/arch/v9.3.yaml +23 -0
  26. shrinkwrap/config/arch/v9.4.yaml +21 -0
  27. shrinkwrap/config/arch/v9.5.yaml +20 -0
  28. shrinkwrap/config/bootwrapper.yaml +76 -0
  29. shrinkwrap/config/buildroot-cca.yaml +113 -0
  30. shrinkwrap/config/buildroot.yaml +54 -0
  31. shrinkwrap/config/cca-3world.yaml +215 -0
  32. shrinkwrap/config/cca-4world.yaml +57 -0
  33. shrinkwrap/config/cca-edk2.yaml +58 -0
  34. shrinkwrap/config/debug/rmm.yaml +15 -0
  35. shrinkwrap/config/debug/tfa.yaml +18 -0
  36. shrinkwrap/config/debug/tftf.yaml +17 -0
  37. shrinkwrap/config/dt-base.yaml +115 -0
  38. shrinkwrap/config/edk2-base.yaml +59 -0
  39. shrinkwrap/config/ffa-hafnium-optee.yaml +45 -0
  40. shrinkwrap/config/ffa-optee.yaml +30 -0
  41. shrinkwrap/config/ffa-tftf.yaml +26 -0
  42. shrinkwrap/config/hafnium-base.yaml +51 -0
  43. shrinkwrap/config/kvm-unit-tests.yaml +32 -0
  44. shrinkwrap/config/kvmtool-base.yaml +33 -0
  45. shrinkwrap/config/linux-base.yaml +80 -0
  46. shrinkwrap/config/ns-edk2-base.yaml +83 -0
  47. shrinkwrap/config/ns-edk2-optee.yaml +41 -0
  48. shrinkwrap/config/ns-edk2.yaml +49 -0
  49. shrinkwrap/config/ns-preload.yaml +98 -0
  50. shrinkwrap/config/optee-base.yaml +37 -0
  51. shrinkwrap/config/rfa-base.yaml +49 -0
  52. shrinkwrap/config/rfa.yaml +47 -0
  53. shrinkwrap/config/rmm-base.yaml +24 -0
  54. shrinkwrap/config/rust.yaml +31 -0
  55. shrinkwrap/config/test/cca.yaml +47 -0
  56. shrinkwrap/config/tfa-base.yaml +45 -0
  57. shrinkwrap/config/tfa-rme.yaml +36 -0
  58. shrinkwrap/config/tftf-base.yaml +32 -0
  59. shrinkwrap/shrinkwrap_main.py +133 -0
  60. shrinkwrap/utils/__init__.py +0 -0
  61. shrinkwrap/utils/clivars.py +16 -0
  62. shrinkwrap/utils/config.py +1166 -0
  63. shrinkwrap/utils/graph.py +263 -0
  64. shrinkwrap/utils/label.py +153 -0
  65. shrinkwrap/utils/logger.py +160 -0
  66. shrinkwrap/utils/process.py +230 -0
  67. shrinkwrap/utils/runtime.py +192 -0
  68. shrinkwrap/utils/ssh_agent.py +98 -0
  69. shrinkwrap/utils/tty.py +46 -0
  70. shrinkwrap/utils/vars.py +14 -0
  71. shrinkwrap/utils/workspace.py +59 -0
  72. shrinkwrap_tool-2026.2.1.dist-info/METADATA +63 -0
  73. shrinkwrap_tool-2026.2.1.dist-info/RECORD +77 -0
  74. shrinkwrap_tool-2026.2.1.dist-info/WHEEL +5 -0
  75. shrinkwrap_tool-2026.2.1.dist-info/entry_points.txt +2 -0
  76. shrinkwrap_tool-2026.2.1.dist-info/licenses/license.rst +41 -0
  77. 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)
@@ -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)
@@ -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
+ ```