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
shrinkwrap/__init__.py ADDED
@@ -0,0 +1 @@
1
+ __version__ = "2026.3.0.dev0"
shrinkwrap/__main__.py ADDED
@@ -0,0 +1,4 @@
1
+ from .shrinkwrap_entry import main
2
+
3
+ if __name__ == "__main__":
4
+ main()
File without changes
@@ -0,0 +1,91 @@
1
+ # Copyright (c) 2022, Arm Limited.
2
+ # SPDX-License-Identifier: MIT
3
+
4
+ import os
5
+ import shrinkwrap.commands.buildall as buildall
6
+ import shrinkwrap.utils.vars as vars
7
+
8
+
9
+ cmd_name = os.path.splitext(os.path.basename(__file__))[0]
10
+
11
+
12
+ def add_parser(parser, formatter):
13
+ """
14
+ Part of the command interface expected by shrinkwrap.py. Adds the
15
+ subcommand to the parser, along with all options and documentation.
16
+ Returns the subcommand name.
17
+ """
18
+ cmdp = parser.add_parser(cmd_name,
19
+ formatter_class=formatter,
20
+ help="""Builds a specified config and packages it ready
21
+ to run.""",
22
+ epilog="""Custom config store(s) can be defined at at
23
+ <SHRINKWRAP_CONFIG> as a colon-separated list of
24
+ directories. Building is done at <SHRINKWRAP_BUILD> and
25
+ output is saved to <SHRINKWRAP_PACKAGE>. The package
26
+ includes all FW binaries, a manifest and a build.sh script
27
+ containing all the commands that were executed per config.
28
+ Any pre-existing config package directory is first deleted.
29
+ Shrinkwrap will always search its default config store even
30
+ if <SHRINKWRAP_CONFIG> is not defined. <SHRINKWRAP_BUILD>
31
+ and <SHRINKWRAP_PACKAGE> default to '~/.shrinkwrap/build'
32
+ and '~/.shrinkwrap/package'. The user can override them by
33
+ setting the environment variables.""")
34
+
35
+ cmdp.add_argument('config',
36
+ metavar='config',
37
+ help="""Config to build. If the config exists relative to the
38
+ current directory that config is used. Else if the config
39
+ exists relative to the config store then it is used.""")
40
+
41
+ cmdp.add_argument('-b', '--btvar',
42
+ metavar='key=value', required=False, default=[],
43
+ action='append',
44
+ help="""Override value for a single build-time variable defined
45
+ by the config. Specify option multiple times for multiple
46
+ variables. Overrides for variables that have a default
47
+ specified by the config are optional.""")
48
+
49
+ cmdp.add_argument('-s', '--no-sync',
50
+ metavar='component', required=False, default=[],
51
+ action='append',
52
+ help="""Optionally specify any components whose git repos should
53
+ not be synced. For all other components, Shrinkwrap ensures
54
+ that all repos are clean and checked out at the correct
55
+ revision. Option can be specified multiple times.""")
56
+
57
+ cmdp.add_argument('--no-sync-all',
58
+ required=False, default=False, action='store_true',
59
+ help="""Do not sync repos for any component, as if --no-sync was
60
+ specified for every component in the config.""")
61
+
62
+ cmdp.add_argument('--force-sync',
63
+ metavar='component', required=False, default=[], action='append',
64
+ help="""Synchronize the repo of the given component even if the local
65
+ source directory contains unsaved changes. YOUR CHANGES WILL BE
66
+ LOST! In addition, download updates from remote branches.""")
67
+
68
+ cmdp.add_argument('--force-sync-all',
69
+ required=False, default=False, action='store_true',
70
+ help="""Synchronize all components even if the local source directories
71
+ contain unsaved changes. YOUR CHANGES WILL BE LOST! In addition,
72
+ download all remote branch updates. Note that this will override
73
+ any 'sync: false' config option.""")
74
+
75
+ buildall.add_common_args(cmdp)
76
+
77
+ return cmd_name
78
+
79
+
80
+ def dispatch(args):
81
+ """
82
+ Part of the command interface expected by shrinkwrap.py. Called to
83
+ execute the subcommand, with the arguments the user passed on the
84
+ command line. The arguments comply with those requested in add_parser().
85
+ """
86
+ btvars = vars.parse(args.btvar, type='bt')
87
+ if args.no_sync_all:
88
+ args.no_sync = True
89
+ if args.force_sync_all:
90
+ args.force_sync = True
91
+ buildall.build([args.config], [btvars], args.no_sync, args.force_sync, args)
@@ -0,0 +1,180 @@
1
+ # Copyright (c) 2022, Arm Limited.
2
+ # SPDX-License-Identifier: MIT
3
+
4
+ import os
5
+ import yaml
6
+ import shrinkwrap.utils.config as config
7
+ import shrinkwrap.utils.graph as ugraph
8
+ import shrinkwrap.utils.runtime as runtime
9
+ import shrinkwrap.utils.workspace as workspace
10
+
11
+
12
+ cmd_name = os.path.splitext(os.path.basename(__file__))[0]
13
+
14
+
15
+ def dflt_jobs():
16
+ return min(os.cpu_count() // 2, 32)
17
+
18
+
19
+ def add_parser(parser, formatter):
20
+ """
21
+ Part of the command interface expected by shrinkwrap.py. Adds the
22
+ subcommand to the parser, along with all options and documentation.
23
+ Returns the subcommand name.
24
+ """
25
+ cmdp = parser.add_parser(cmd_name,
26
+ formatter_class=formatter,
27
+ help="""Builds either all concrete standard configs or an
28
+ explicitly specified set of configs and packages them ready
29
+ to run.""",
30
+ epilog="""Custom config store(s) can be defined at at
31
+ <SHRINKWRAP_CONFIG> as a colon-separated list of
32
+ directories. Building is done at <SHRINKWRAP_BUILD> and
33
+ output is saved to <SHRINKWRAP_PACKAGE>. The package
34
+ includes all FW binaries, a manifest and a build.sh script
35
+ containing all the commands that were executed per config.
36
+ Any pre-existing config package directory is first deleted.
37
+ Shrinkwrap will always search its default config store even
38
+ if <SHRINKWRAP_CONFIG> is not defined. <SHRINKWRAP_BUILD>
39
+ and <SHRINKWRAP_PACKAGE> default to '~/.shrinkwrap/build'
40
+ and '~/.shrinkwrap/package'. The user can override them by
41
+ setting the environment variables.""")
42
+
43
+ cmdp.add_argument('configs',
44
+ metavar='yamlfile',
45
+ help="""A yaml file containing all the configs to be built. The
46
+ top level dictionary contains a 'configs' key, whose value
47
+ is a list of dictionaries, each with a 'config' key, whose
48
+ value is a config filename.""")
49
+
50
+ add_common_args(cmdp)
51
+
52
+ return cmd_name
53
+
54
+
55
+ def add_common_args(cmdp):
56
+ """
57
+ Common args shared between build and buildmulti.
58
+ """
59
+ cmdp.add_argument('-o', '--overlay',
60
+ metavar='cfgfile', required=False, default=[],
61
+ action='append',
62
+ help="""Optional config file overlay to override run-time and
63
+ build-time settings. Only entries within the "build",
64
+ "buildex" and "run" sections are used. Applied to all
65
+ configs being built. Can be specified multiple times;
66
+ left-most overlay is the first overlay applied.""")
67
+
68
+ cmdp.add_argument('-t', '--tasks',
69
+ required=False, default=dflt_jobs(), metavar='count', type=int,
70
+ help="""Maximum number of "high-level" tasks that will be
71
+ performed in parallel by Shrinkwrap. Tasks include syncing
72
+ git repositories, building components and copying
73
+ artifacts. Default={}""".format(dflt_jobs()))
74
+
75
+ cmdp.add_argument('-j', '--jobs',
76
+ required=False, default=dflt_jobs(), metavar='count', type=int,
77
+ help="""Maximum number of low-level jobs that will be
78
+ performed in parallel by each component build task.
79
+ Default={}""".format(dflt_jobs()))
80
+
81
+ cmdp.add_argument('-v', '--verbose',
82
+ required=False, default=False, action='store_true',
83
+ help="""If specified, the output from all executed commands will
84
+ be displayed. It is advisable to set tasks to 1 when
85
+ this option is selected.""")
86
+
87
+ cmdp.add_argument('-n', '--dry-run',
88
+ required=False, default=False, action='store_true',
89
+ help="""If specified, <SHRINKWRAP_BUILD> and
90
+ <SHRINKWRAP_PACKAGE> will not be touched and none of the
91
+ build commands will be executed. Instead the set of
92
+ commands that would have been executed are output to stdout
93
+ as a bash script.""")
94
+
95
+ cmdp.add_argument('-c', '--no-color',
96
+ required=False, default=False, action='store_true',
97
+ help="""If specified, logs will not be colorized.""")
98
+
99
+
100
+ def dispatch(args):
101
+ """
102
+ Part of the command interface expected by shrinkwrap.py. Called to
103
+ execute the subcommand, with the arguments the user passed on the
104
+ command line. The arguments comply with those requested in add_parser().
105
+ """
106
+ with open(args.configs) as file:
107
+ cfgs = yaml.safe_load(file)
108
+
109
+ configs = [c['config'] for c in cfgs['configs']]
110
+ btvarss = [c['btvars'] for c in cfgs['configs']]
111
+ build(configs, btvarss, [], [], args)
112
+
113
+
114
+ def build(configs, btvarss, nosync, force_sync, args):
115
+ """
116
+ Concurrently builds a list of configs. Intended to be called as a common
117
+ handler for the build and buildmulti commands.
118
+ """
119
+ clivars = {'jobs': args.jobs}
120
+ configs = config.load_resolveb_all(configs, args.overlay, clivars, btvarss)
121
+ graph = config.build_graph(configs, args.verbose, nosync, force_sync)
122
+
123
+ if args.dry_run:
124
+ script = ugraph.make_script(graph)
125
+ print(script)
126
+ else:
127
+ if args.verbose:
128
+ workspace.dump()
129
+
130
+ # Run under a runtime environment, which may just run commands
131
+ # natively on the host or may execute commands in a container,
132
+ # depending on what the user specified.
133
+ with runtime.Runtime(name=args.runtime, image=config.get_image(configs, args),
134
+ ssh_agent_keys=args.ssh_agent_keys) as rt:
135
+ def add_volume(path, levels_up=0):
136
+ while levels_up:
137
+ path = os.path.dirname(path)
138
+ levels_up -= 1
139
+ os.makedirs(path, exist_ok=True)
140
+ rt.add_volume(path)
141
+
142
+ add_volume(workspace.build)
143
+ add_volume(workspace.package)
144
+ for c in workspace.configs():
145
+ add_volume(c)
146
+
147
+ for conf in configs:
148
+ for comp in conf['build'].values():
149
+ add_volume(comp['sourcedir'], 1)
150
+ add_volume(comp['builddir'])
151
+
152
+ for btvar in conf['buildex']['btvars'].values():
153
+ if btvar['type'] == 'path':
154
+ rt.add_volume(btvar['value'])
155
+
156
+ if workspace.project_cache:
157
+ add_volume(workspace.project_cache)
158
+
159
+ rt.start()
160
+
161
+ ugraph.execute(graph,
162
+ args.tasks,
163
+ args.verbose,
164
+ not args.no_color)
165
+
166
+ for c in configs:
167
+ # Dump the config.
168
+ cfg_name = os.path.join(workspace.package,
169
+ f'{c["name"]}.yaml')
170
+ with open(cfg_name, 'w') as cfg:
171
+ config.dump(c, cfg)
172
+
173
+ # Dump the script to build the config.
174
+ graph = config.build_graph([c], args.verbose, nosync, force_sync)
175
+ script = ugraph.make_script(graph)
176
+ build_name = os.path.join(workspace.package,
177
+ c['name'],
178
+ 'build.sh')
179
+ with open(build_name, 'w') as build:
180
+ build.write(script)
@@ -0,0 +1,161 @@
1
+ # Copyright (c) 2022, Arm Limited.
2
+ # SPDX-License-Identifier: MIT
3
+
4
+ import os
5
+ import shrinkwrap.utils.config as config
6
+ import shrinkwrap.utils.graph as ugraph
7
+ import shrinkwrap.utils.runtime as runtime
8
+ import shrinkwrap.utils.workspace as workspace
9
+
10
+
11
+ cmd_name = os.path.splitext(os.path.basename(__file__))[0]
12
+
13
+
14
+ def dflt_jobs():
15
+ return min(os.cpu_count() // 2, 32)
16
+
17
+
18
+ def add_parser(parser, formatter):
19
+ """
20
+ Part of the command interface expected by shrinkwrap.py. Adds the
21
+ subcommand to the parser, along with all options and documentation.
22
+ Returns the subcommand name.
23
+ """
24
+ cmdp = parser.add_parser(cmd_name,
25
+ formatter_class=formatter,
26
+ help="""Cleans either all concrete standard configs or an
27
+ explicitly specified set of configs, ready to be rebuilt
28
+ from scratch.""")
29
+
30
+ cmdp.add_argument('configs',
31
+ metavar='config', nargs='*',
32
+ help="""0 or more configs to clean. If a config exists relative
33
+ to the current directory that config is used. Else if a
34
+ config exists relative to the config store then it is used.
35
+ If no configs are provided, all concrete configs in the
36
+ config store are cleaned.""")
37
+
38
+ cmdp.add_argument('-o', '--overlay',
39
+ metavar='cfgfile', required=False, default=[],
40
+ action='append',
41
+ help="""Optional config file overlay to override run-time and
42
+ build-time settings. Only entries within the "build",
43
+ "buildex" and "run" sections are used. Applied to all
44
+ configs being built. Can be specified multiple times;
45
+ left-most overlay is the first overlay applied.""")
46
+
47
+ cmdp.add_argument('-t', '--tasks',
48
+ required=False, default=dflt_jobs(), metavar='count', type=int,
49
+ help="""Maximum number of "high-level" tasks that will be
50
+ performed in parallel by Shrinkwrap. Tasks include cleaning
51
+ components. Default={}""".format(dflt_jobs()))
52
+
53
+ cmdp.add_argument('-j', '--jobs',
54
+ required=False, default=dflt_jobs(), metavar='count', type=int,
55
+ help="""Maximum number of low-level jobs that will be
56
+ performed in parallel by each component clean task.
57
+ Default={}""".format(dflt_jobs()))
58
+
59
+ cmdp.add_argument('-v', '--verbose',
60
+ required=False, default=False, action='store_true',
61
+ help="""If specified, the output from all executed commands will
62
+ be displayed. It is advisable to set tasks to 1 when
63
+ this option is selected.""")
64
+
65
+ cmdp.add_argument('-n', '--dry-run',
66
+ required=False, default=False, action='store_true',
67
+ help="""If specified, none of the clean commands will be
68
+ executed. Instead the set of commands that would have been
69
+ executed are output to stdout as a bash script.""")
70
+
71
+ cmdp.add_argument('-c', '--no-color',
72
+ required=False, default=False, action='store_true',
73
+ help="""If specified, logs will not be colorized.""")
74
+
75
+ cmdp.add_argument('-f', '--filter',
76
+ metavar='[config.]component', required=False, default=[],
77
+ action='append',
78
+ help="""Optionally specify the components to clean. Option can
79
+ be specified multiple times to clean multiple components.
80
+ If not specified, all components are cleaned. If 'config.'
81
+ is omitted, component is cleaned for all configs being
82
+ cleaned (component must exist in all configs being
83
+ cleaned).""")
84
+
85
+ return cmd_name
86
+
87
+
88
+ def dispatch(args):
89
+ """
90
+ Part of the command interface expected by shrinkwrap.py. Called to
91
+ execute the subcommand, with the arguments the user passed on the
92
+ command line. The arguments comply with those requested in add_parser().
93
+ """
94
+ clivars = {'jobs': args.jobs}
95
+ configs = config.load_resolveb_all(args.configs, args.overlay, clivars)
96
+ if len(args.configs) == 0:
97
+ configs = [c for c in configs if c['concrete']]
98
+ for conf in configs:
99
+ conf['graph'] = _filter(conf['name'],
100
+ conf['graph'],
101
+ args.filter)
102
+
103
+ graph = config.clean_graph(configs, args.verbose)
104
+
105
+ if args.dry_run:
106
+ script = ugraph.make_script(graph)
107
+ print(script)
108
+ else:
109
+ if args.verbose:
110
+ workspace.dump()
111
+
112
+ # Run under a runtime environment, which may just run commands
113
+ # natively on the host or may execute commands in a container,
114
+ # depending on what the user specified.
115
+ with runtime.Runtime(name=args.runtime, image=config.get_image(configs, args),
116
+ ssh_agent_keys=args.ssh_agent_keys) as rt:
117
+ def add_volume(path, levels_up=0):
118
+ while levels_up:
119
+ path = os.path.dirname(path)
120
+ levels_up -= 1
121
+ os.makedirs(path, exist_ok=True)
122
+ rt.add_volume(path)
123
+
124
+ add_volume(workspace.build)
125
+ add_volume(workspace.package)
126
+
127
+ for conf in configs:
128
+ for comp in conf['build'].values():
129
+ add_volume(comp['sourcedir'], 1)
130
+ add_volume(comp['builddir'], 1)
131
+
132
+ rt.start()
133
+
134
+ ugraph.execute(graph,
135
+ args.tasks,
136
+ args.verbose,
137
+ not args.no_color)
138
+
139
+
140
+ def _filter(name, graph, args):
141
+ components = []
142
+ pruned = {}
143
+
144
+ for arg in args:
145
+ try:
146
+ conf, comp = arg.split('.', maxsplit=1)
147
+ if conf == name:
148
+ components.append(comp)
149
+ except ValueError:
150
+ components.append(arg)
151
+
152
+ if len(components) == 0:
153
+ return graph
154
+
155
+ for c in components:
156
+ if c not in graph:
157
+ raise Exception(f'Bad filter: {c} not a '
158
+ f'component of {name}')
159
+ pruned[c] = []
160
+
161
+ return pruned
@@ -0,0 +1,235 @@
1
+ # Copyright (c) 2022, Arm Limited.
2
+ # SPDX-License-Identifier: MIT
3
+
4
+ import json
5
+ import io
6
+ import os
7
+ import re
8
+ import textwrap
9
+ import shrinkwrap.utils.config as config
10
+
11
+
12
+ cmd_name = os.path.splitext(os.path.basename(__file__))[0]
13
+
14
+
15
+ def add_parser(parser, formatter):
16
+ """
17
+ Part of the command interface expected by shrinkwrap.py. Adds the
18
+ subcommand to the parser, along with all options and documentation.
19
+ Returns the subcommand name.
20
+ """
21
+ cmdp = parser.add_parser(cmd_name,
22
+ formatter_class=formatter,
23
+ help="""Outputs to stdout info about either all concrete
24
+ standard configs or an explicitly specified set of configs.
25
+ Info includes name, description and runtime variables with
26
+ their default values.""",
27
+ epilog="""Custom config store(s) can be defined at at
28
+ <SHRINKWRAP_CONFIG> as a colon-separated list of
29
+ directories. Shrinkwrap will always search its default
30
+ config store even if <SHRINKWRAP_CONFIG> is not
31
+ defined.""")
32
+
33
+ cmdp.add_argument('configs',
34
+ metavar='config', nargs='*',
35
+ help="""0 or more configs to inspect. If a config exists
36
+ relative to the current directory that config is used. Else
37
+ if a config exists relative to the config store then it is
38
+ used. If no configs are provided, all concrete configs
39
+ in the config store are built.""")
40
+
41
+ cmdp.add_argument('-a', '--all',
42
+ required=False, default=False, action='store_true',
43
+ help="""If specified, and no configs were explicitly provided,
44
+ lists all standard configs rather than just the concrete
45
+ ones.""")
46
+
47
+ cmdp.add_argument('-j', '--json',
48
+ required=False, default=False, action='store_true',
49
+ help="""If specified, output is in json.""")
50
+
51
+ return cmd_name
52
+
53
+
54
+ def dispatch(args):
55
+ """
56
+ Part of the command interface expected by shrinkwrap.py. Called to
57
+ execute the subcommand, with the arguments the user passed on the
58
+ command line. The arguments comply with those requested in add_parser().
59
+ """
60
+ configs = config.load_all(args.configs)
61
+
62
+ cfgs = []
63
+ for c in sorted(configs, key=lambda c: c['fullname']):
64
+ if len(args.configs) == 0 and not args.all and not c['concrete']:
65
+ continue
66
+
67
+ cfgs.append({
68
+ 'name': c['fullname'],
69
+ 'description': c['description'],
70
+ 'image': c['image'] if c['image'] is not None else '<none>',
71
+ 'concrete': c['concrete'],
72
+ 'btvars': {
73
+ k: _var_value(v['value'])
74
+ for k,v in c['buildex']['btvars'].items()
75
+ },
76
+ 'rtvars': {
77
+ k: _var_value(v['value'])
78
+ for k,v in c['run']['rtvars'].items()
79
+ },
80
+ 'components': _comp_revisions(c['build']),
81
+ })
82
+
83
+ if args.json:
84
+ print(json.dumps(cfgs, indent=4))
85
+ return
86
+
87
+ width = 80
88
+ indent = 21
89
+ vindent = 24
90
+
91
+ descs = []
92
+ for c in cfgs:
93
+ buf = io.StringIO()
94
+
95
+ buf.write(_text_wrap('name',
96
+ c['name'],
97
+ width=width,
98
+ indent=indent,
99
+ paraspace=1))
100
+ buf.write('\n')
101
+ buf.write(_text_wrap('description',
102
+ c['description'],
103
+ width=width,
104
+ indent=indent,
105
+ paraspace=1))
106
+ buf.write('\n')
107
+ buf.write(_text_wrap('image',
108
+ c['image'],
109
+ width=width,
110
+ indent=indent,
111
+ paraspace=1))
112
+ buf.write('\n')
113
+ buf.write(_text_wrap('concrete',
114
+ c['concrete'],
115
+ width=width,
116
+ indent=indent,
117
+ paraspace=1))
118
+ buf.write('\n')
119
+ buf.write(_dict_wrap('build-time vars',
120
+ c['btvars'],
121
+ width=width,
122
+ kindent=indent,
123
+ vindent=vindent))
124
+ buf.write('\n')
125
+ buf.write(_dict_wrap('run-time vars',
126
+ c['rtvars'],
127
+ width=width,
128
+ kindent=indent,
129
+ vindent=vindent))
130
+ buf.write('\n')
131
+ buf.write(_repo_wrap('components',
132
+ c['components'],
133
+ width=width,
134
+ kindent=indent,
135
+ vindent=vindent))
136
+
137
+ descs.append(buf.getvalue())
138
+
139
+ separator = '\n' + ('-' * width) + '\n\n'
140
+ all = separator.join(descs)
141
+ print(all)
142
+
143
+
144
+ def _var_value(value):
145
+ if value is None:
146
+ return '<null>'
147
+ if value == '':
148
+ return '<empty>'
149
+ return str(value)
150
+
151
+
152
+ def _comp_revisions(components):
153
+ revs = {}
154
+ for comp in sorted(components.keys()):
155
+ for repo in sorted(components[comp]['repo'].keys()):
156
+ name = comp if repo == '.' else f"{comp} ({repo})"
157
+ revision = components[comp]['repo'][repo]['revision']
158
+ remote = components[comp]['repo'][repo]['remote']
159
+ revs[name] = {
160
+ 'repository': remote,
161
+ 'revision': revision,
162
+ }
163
+ return revs
164
+
165
+
166
+ def _text_wrap(tag, text, width=80, indent=0, paraspace=1, end='\n'):
167
+ text = str(text)
168
+ tag = str(tag)
169
+ indent_pattern = ' ' * indent
170
+
171
+ lines = [textwrap.fill(l,
172
+ width=width,
173
+ initial_indent=indent_pattern,
174
+ subsequent_indent=indent_pattern)
175
+ for l in text.splitlines() if l]
176
+
177
+ wrapped = ('\n' * (paraspace + 1)).join(lines)
178
+
179
+ if tag:
180
+ if len(tag) > indent - 2:
181
+ wrapped = f'{tag}:\n' + wrapped
182
+ else:
183
+ wrapped = f'{tag}:' + wrapped[len(tag) + 1:]
184
+
185
+ return wrapped + end
186
+
187
+
188
+ def _dict_wrap(tag, dictionary, width=80, kindent=0, vindent=0, end='\n'):
189
+ if len(dictionary) == 0:
190
+ lines = [str(None)]
191
+ else:
192
+ dwidth = width - kindent
193
+ lines = []
194
+
195
+ for k, v in dictionary.items():
196
+ line = _text_wrap(k,
197
+ v,
198
+ width=dwidth,
199
+ indent=vindent,
200
+ paraspace=0,
201
+ end='')
202
+ lines.append(line)
203
+
204
+ dtext = '\n'.join(lines)
205
+
206
+ return _text_wrap(tag,
207
+ dtext,
208
+ width=width,
209
+ indent=kindent,
210
+ paraspace=0,
211
+ end=end)
212
+
213
+
214
+ def _repo_wrap(tag, components, width=80, kindent=0, vindent=0, end='\n'):
215
+ def is_git_sha(s):
216
+ return bool(re.fullmatch(r"[0-9a-f]{40}", s))
217
+
218
+ repo_indent = 0
219
+ for info in components.values():
220
+ rev = info['revision']
221
+ if is_git_sha(rev):
222
+ rev = rev[:12]
223
+ repo_indent = max(repo_indent, len(rev))
224
+ repo_indent += 2
225
+
226
+ dictionary = {}
227
+ for comp, info in components.items():
228
+ repo = info['repository']
229
+ rev = info['revision']
230
+ if is_git_sha(rev):
231
+ rev = rev[:12]
232
+ value = f"{rev}{' ' * (repo_indent - len(rev))}{repo}"
233
+ dictionary[comp] = value
234
+
235
+ return _dict_wrap(tag, dictionary, 10000, kindent, vindent, end)