librelane 2.4.0__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.

Potentially problematic release.


This version of librelane might be problematic. Click here for more details.

Files changed (170) hide show
  1. librelane/__init__.py +38 -0
  2. librelane/__main__.py +479 -0
  3. librelane/__version__.py +43 -0
  4. librelane/common/__init__.py +63 -0
  5. librelane/common/cli.py +75 -0
  6. librelane/common/drc.py +246 -0
  7. librelane/common/generic_dict.py +319 -0
  8. librelane/common/metrics/__init__.py +35 -0
  9. librelane/common/metrics/__main__.py +413 -0
  10. librelane/common/metrics/library.py +354 -0
  11. librelane/common/metrics/metric.py +186 -0
  12. librelane/common/metrics/util.py +279 -0
  13. librelane/common/misc.py +456 -0
  14. librelane/common/ring_buffer.py +63 -0
  15. librelane/common/tcl.py +80 -0
  16. librelane/common/toolbox.py +549 -0
  17. librelane/common/tpe.py +41 -0
  18. librelane/common/types.py +116 -0
  19. librelane/config/__init__.py +32 -0
  20. librelane/config/__main__.py +155 -0
  21. librelane/config/config.py +1025 -0
  22. librelane/config/flow.py +490 -0
  23. librelane/config/pdk_compat.py +255 -0
  24. librelane/config/preprocessor.py +464 -0
  25. librelane/config/removals.py +45 -0
  26. librelane/config/variable.py +743 -0
  27. librelane/container.py +285 -0
  28. librelane/env_info.py +320 -0
  29. librelane/examples/spm/config.yaml +33 -0
  30. librelane/examples/spm/pin_order.cfg +14 -0
  31. librelane/examples/spm/src/impl.sdc +73 -0
  32. librelane/examples/spm/src/signoff.sdc +68 -0
  33. librelane/examples/spm/src/spm.v +73 -0
  34. librelane/examples/spm/verify/spm_tb.v +106 -0
  35. librelane/examples/spm-user_project_wrapper/SPM_example.v +286 -0
  36. librelane/examples/spm-user_project_wrapper/base_sdc_file.sdc +145 -0
  37. librelane/examples/spm-user_project_wrapper/config-tut.json +12 -0
  38. librelane/examples/spm-user_project_wrapper/config.json +13 -0
  39. librelane/examples/spm-user_project_wrapper/defines.v +66 -0
  40. librelane/examples/spm-user_project_wrapper/template.def +7656 -0
  41. librelane/examples/spm-user_project_wrapper/user_project_wrapper.v +123 -0
  42. librelane/flows/__init__.py +24 -0
  43. librelane/flows/builtins.py +18 -0
  44. librelane/flows/classic.py +327 -0
  45. librelane/flows/cli.py +463 -0
  46. librelane/flows/flow.py +1049 -0
  47. librelane/flows/misc.py +71 -0
  48. librelane/flows/optimizing.py +179 -0
  49. librelane/flows/sequential.py +367 -0
  50. librelane/flows/synth_explore.py +173 -0
  51. librelane/help/__main__.py +39 -0
  52. librelane/logging/__init__.py +40 -0
  53. librelane/logging/logger.py +323 -0
  54. librelane/open_pdks_rev +1 -0
  55. librelane/plugins.py +21 -0
  56. librelane/py.typed +0 -0
  57. librelane/scripts/base.sdc +80 -0
  58. librelane/scripts/klayout/Readme.md +2 -0
  59. librelane/scripts/klayout/open_design.py +63 -0
  60. librelane/scripts/klayout/render.py +121 -0
  61. librelane/scripts/klayout/stream_out.py +176 -0
  62. librelane/scripts/klayout/xml_drc_report_to_json.py +45 -0
  63. librelane/scripts/klayout/xor.drc +120 -0
  64. librelane/scripts/magic/Readme.md +1 -0
  65. librelane/scripts/magic/common/read.tcl +114 -0
  66. librelane/scripts/magic/def/antenna_check.tcl +35 -0
  67. librelane/scripts/magic/def/mag.tcl +19 -0
  68. librelane/scripts/magic/def/mag_gds.tcl +79 -0
  69. librelane/scripts/magic/drc.tcl +78 -0
  70. librelane/scripts/magic/extract_spice.tcl +98 -0
  71. librelane/scripts/magic/gds/drc_batch.tcl +74 -0
  72. librelane/scripts/magic/gds/erase_box.tcl +32 -0
  73. librelane/scripts/magic/gds/extras_mag.tcl +45 -0
  74. librelane/scripts/magic/gds/mag_with_pointers.tcl +31 -0
  75. librelane/scripts/magic/get_bbox.tcl +11 -0
  76. librelane/scripts/magic/lef/extras_maglef.tcl +61 -0
  77. librelane/scripts/magic/lef/maglef.tcl +26 -0
  78. librelane/scripts/magic/lef.tcl +57 -0
  79. librelane/scripts/magic/open.tcl +28 -0
  80. librelane/scripts/magic/wrapper.tcl +21 -0
  81. librelane/scripts/netgen/setup.tcl +28 -0
  82. librelane/scripts/odbpy/apply_def_template.py +49 -0
  83. librelane/scripts/odbpy/cell_frequency.py +107 -0
  84. librelane/scripts/odbpy/check_antenna_properties.py +116 -0
  85. librelane/scripts/odbpy/contextualize.py +109 -0
  86. librelane/scripts/odbpy/defutil.py +573 -0
  87. librelane/scripts/odbpy/diodes.py +373 -0
  88. librelane/scripts/odbpy/disconnected_pins.py +305 -0
  89. librelane/scripts/odbpy/eco_buffer.py +181 -0
  90. librelane/scripts/odbpy/eco_diode.py +139 -0
  91. librelane/scripts/odbpy/filter_unannotated.py +100 -0
  92. librelane/scripts/odbpy/io_place.py +482 -0
  93. librelane/scripts/odbpy/ioplace_parser/__init__.py +23 -0
  94. librelane/scripts/odbpy/ioplace_parser/parse.py +147 -0
  95. librelane/scripts/odbpy/label_macro_pins.py +277 -0
  96. librelane/scripts/odbpy/lefutil.py +97 -0
  97. librelane/scripts/odbpy/placers.py +162 -0
  98. librelane/scripts/odbpy/power_utils.py +397 -0
  99. librelane/scripts/odbpy/random_place.py +57 -0
  100. librelane/scripts/odbpy/reader.py +250 -0
  101. librelane/scripts/odbpy/remove_buffers.py +173 -0
  102. librelane/scripts/odbpy/snap_to_grid.py +57 -0
  103. librelane/scripts/odbpy/wire_lengths.py +93 -0
  104. librelane/scripts/openroad/antenna_check.tcl +20 -0
  105. librelane/scripts/openroad/antenna_repair.tcl +31 -0
  106. librelane/scripts/openroad/basic_mp.tcl +24 -0
  107. librelane/scripts/openroad/buffer_list.tcl +10 -0
  108. librelane/scripts/openroad/common/dpl.tcl +24 -0
  109. librelane/scripts/openroad/common/dpl_cell_pad.tcl +26 -0
  110. librelane/scripts/openroad/common/grt.tcl +32 -0
  111. librelane/scripts/openroad/common/io.tcl +540 -0
  112. librelane/scripts/openroad/common/pdn_cfg.tcl +135 -0
  113. librelane/scripts/openroad/common/resizer.tcl +103 -0
  114. librelane/scripts/openroad/common/set_global_connections.tcl +78 -0
  115. librelane/scripts/openroad/common/set_layer_adjustments.tcl +31 -0
  116. librelane/scripts/openroad/common/set_power_nets.tcl +30 -0
  117. librelane/scripts/openroad/common/set_rc.tcl +75 -0
  118. librelane/scripts/openroad/common/set_routing_layers.tcl +30 -0
  119. librelane/scripts/openroad/cts.tcl +80 -0
  120. librelane/scripts/openroad/cut_rows.tcl +24 -0
  121. librelane/scripts/openroad/dpl.tcl +24 -0
  122. librelane/scripts/openroad/drt.tcl +37 -0
  123. librelane/scripts/openroad/fill.tcl +30 -0
  124. librelane/scripts/openroad/floorplan.tcl +145 -0
  125. librelane/scripts/openroad/gpl.tcl +88 -0
  126. librelane/scripts/openroad/grt.tcl +30 -0
  127. librelane/scripts/openroad/gui.tcl +37 -0
  128. librelane/scripts/openroad/insert_buffer.tcl +127 -0
  129. librelane/scripts/openroad/ioplacer.tcl +67 -0
  130. librelane/scripts/openroad/irdrop.tcl +51 -0
  131. librelane/scripts/openroad/pdn.tcl +52 -0
  132. librelane/scripts/openroad/rcx.tcl +32 -0
  133. librelane/scripts/openroad/repair_design.tcl +70 -0
  134. librelane/scripts/openroad/repair_design_postgrt.tcl +48 -0
  135. librelane/scripts/openroad/rsz_timing_postcts.tcl +68 -0
  136. librelane/scripts/openroad/rsz_timing_postgrt.tcl +70 -0
  137. librelane/scripts/openroad/sta/check_macro_instances.tcl +53 -0
  138. librelane/scripts/openroad/sta/corner.tcl +393 -0
  139. librelane/scripts/openroad/tapcell.tcl +25 -0
  140. librelane/scripts/openroad/write_views.tcl +27 -0
  141. librelane/scripts/pyosys/construct_abc_script.py +177 -0
  142. librelane/scripts/pyosys/json_header.py +84 -0
  143. librelane/scripts/pyosys/synthesize.py +493 -0
  144. librelane/scripts/pyosys/ys_common.py +153 -0
  145. librelane/scripts/tclsh/hello.tcl +1 -0
  146. librelane/state/__init__.py +24 -0
  147. librelane/state/__main__.py +61 -0
  148. librelane/state/design_format.py +195 -0
  149. librelane/state/state.py +359 -0
  150. librelane/steps/__init__.py +61 -0
  151. librelane/steps/__main__.py +510 -0
  152. librelane/steps/checker.py +637 -0
  153. librelane/steps/common_variables.py +340 -0
  154. librelane/steps/cvc_rv.py +169 -0
  155. librelane/steps/klayout.py +509 -0
  156. librelane/steps/magic.py +576 -0
  157. librelane/steps/misc.py +160 -0
  158. librelane/steps/netgen.py +253 -0
  159. librelane/steps/odb.py +1088 -0
  160. librelane/steps/openroad.py +2460 -0
  161. librelane/steps/openroad_alerts.py +102 -0
  162. librelane/steps/pyosys.py +640 -0
  163. librelane/steps/step.py +1571 -0
  164. librelane/steps/tclstep.py +288 -0
  165. librelane/steps/verilator.py +222 -0
  166. librelane/steps/yosys.py +371 -0
  167. librelane-2.4.0.dist-info/METADATA +169 -0
  168. librelane-2.4.0.dist-info/RECORD +170 -0
  169. librelane-2.4.0.dist-info/WHEEL +4 -0
  170. librelane-2.4.0.dist-info/entry_points.txt +9 -0
@@ -0,0 +1,71 @@
1
+ # Copyright 2023 Efabless Corporation
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+ from __future__ import annotations
15
+
16
+ from .flow import Flow
17
+ from .sequential import SequentialFlow
18
+ from ..steps import KLayout, OpenROAD, Magic
19
+
20
+
21
+ @Flow.factory.register()
22
+ class OpenInKLayout(SequentialFlow):
23
+ """
24
+ This 'flow' actually just has one step that opens the LEF/DEF from the
25
+ initial state object in KLayout. Fancy that.
26
+
27
+ Intended for use with run tags that have already been run with
28
+ another flow, i.e.: ::
29
+
30
+ librelane [...]
31
+ librelane --last-run --flow OpenInKLayout [...]
32
+ """
33
+
34
+ name = "Opening in KLayout"
35
+ Steps = [KLayout.OpenGUI]
36
+
37
+
38
+ @Flow.factory.register()
39
+ class OpenInOpenROAD(SequentialFlow):
40
+ """
41
+ This 'flow' actually just has one step that opens the ODB from
42
+ the initial state object in OpenROAD.
43
+
44
+ Intended for use with run tags that have already been run with
45
+ another flow, i.e. ::
46
+
47
+ librelane [...]
48
+ librelane --last-run --flow OpenInOpenROAD [...]
49
+ """
50
+
51
+ name = "Opening in OpenROAD"
52
+
53
+ Steps = [OpenROAD.OpenGUI]
54
+
55
+
56
+ @Flow.factory.register()
57
+ class OpenInMagic(SequentialFlow):
58
+ """
59
+ This 'flow' actually just has one step that opens the GDS or DEF from
60
+ the initial state object in Magic.
61
+
62
+ Intended for use with run tags that have already been run with
63
+ another flow, i.e. ::
64
+
65
+ librelane [...]
66
+ librelane --last-run --flow OpenInMagic [...]
67
+ """
68
+
69
+ name = "Opening in Magic"
70
+
71
+ Steps = [Magic.OpenGUI]
@@ -0,0 +1,179 @@
1
+ # Copyright 2023 Efabless Corporation
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+ from __future__ import annotations
15
+
16
+ from typing import List, Tuple
17
+ from concurrent.futures import Future
18
+
19
+ from .flow import Flow
20
+ from ..state import State
21
+ from ..config import Config
22
+ from ..steps import Step, Yosys, OpenROAD, StepError
23
+ from ..logging import get_log_level, set_log_level, LogLevels, success, info
24
+
25
+
26
+ # "Optimizing" is a custom demo flow to show what's possible with non-sequential Flows in LibreLan
27
+ # It works across two steps:
28
+ # * The Synthesis Exploration - tries multiple synthesis strategies in *parallel*.
29
+ # The best-performing strategy in terms of minimizing the area makes it to the next stage.
30
+ # * Floorplanning and Placement - tries FP and placement with a high utilization.
31
+ # If the high utilization fails, a lower is fallen back to as a suggestion.
32
+ @Flow.factory.register()
33
+ class Optimizing(Flow):
34
+ Steps = [
35
+ Yosys.Synthesis,
36
+ OpenROAD.CheckSDCFiles,
37
+ OpenROAD.STAPrePNR,
38
+ OpenROAD.Floorplan,
39
+ OpenROAD.IOPlacement,
40
+ OpenROAD.GlobalPlacement,
41
+ ]
42
+
43
+ def run(
44
+ self,
45
+ initial_state: State,
46
+ **kwargs,
47
+ ) -> Tuple[State, List[Step]]:
48
+ step_list: List[Step] = []
49
+
50
+ self.set_max_stage_count(2)
51
+
52
+ synthesis_futures: List[Tuple[Config, Future[State]]] = []
53
+ self.start_stage("Synthesis Exploration")
54
+
55
+ log_level_bk = get_log_level()
56
+ set_log_level(LogLevels.ERROR)
57
+
58
+ for strategy in ["AREA 0", "AREA 2", "DELAY 1"]:
59
+ config = self.config.copy(SYNTH_STRATEGY=strategy)
60
+
61
+ synth_step = Yosys.Synthesis(
62
+ config,
63
+ id=f"synthesis-{strategy}",
64
+ state_in=initial_state,
65
+ flow=self,
66
+ )
67
+ synth_future = self.start_step_async(synth_step)
68
+ step_list.append(synth_step)
69
+
70
+ sdc_step = OpenROAD.CheckSDCFiles(
71
+ config,
72
+ id=f"sdc-{strategy}",
73
+ state_in=synth_future,
74
+ flow=self,
75
+ )
76
+ sdc_future = self.start_step_async(sdc_step)
77
+ step_list.append(sdc_step)
78
+
79
+ sta_step = OpenROAD.STAPrePNR(
80
+ config,
81
+ state_in=sdc_future,
82
+ id=f"sta-{strategy}",
83
+ flow=self,
84
+ )
85
+
86
+ step_list.append(sta_step)
87
+ sta_future = self.start_step_async(sta_step)
88
+
89
+ synthesis_futures.append((config, sta_future))
90
+
91
+ synthesis_states: List[Tuple[Config, State]] = [
92
+ (config, future.result()) for config, future in synthesis_futures
93
+ ]
94
+
95
+ self.end_stage()
96
+ set_log_level(log_level_bk)
97
+
98
+ min_strat = synthesis_states[0][0]["SYNTH_STRATEGY"]
99
+ min_config = synthesis_states[0][0]
100
+ min_area_state = synthesis_states[0][1]
101
+ for config, state in synthesis_states[1:]:
102
+ strategy = config["SYNTH_STRATEGY"]
103
+ if (
104
+ state.metrics["design__instance__area"]
105
+ < min_area_state.metrics["design__instance__area"]
106
+ ):
107
+ min_area_state = state
108
+ min_strat = strategy
109
+ min_config = config
110
+
111
+ info(f"Using result from '{min_strat}…")
112
+
113
+ self.start_stage("Floorplanning and Placement")
114
+
115
+ fp_config = min_config.copy(FP_CORE_UTIL=99)
116
+ fp = OpenROAD.Floorplan(
117
+ fp_config,
118
+ state_in=min_area_state,
119
+ id="fp_highutl",
120
+ long_name="Floorplanning (High Util)",
121
+ flow=self,
122
+ )
123
+ self.start_step(fp)
124
+ step_list.append(fp)
125
+ try:
126
+ io = OpenROAD.IOPlacement(
127
+ fp_config,
128
+ state_in=fp.state_out,
129
+ id="io-highutl",
130
+ long_name="I/O Placement (High Util)",
131
+ flow=self,
132
+ )
133
+ self.start_step(io)
134
+ step_list.append(io)
135
+ gpl = OpenROAD.GlobalPlacement(
136
+ fp_config,
137
+ state_in=io.state_out,
138
+ id="gpl-highutil",
139
+ long_name="Global Placement (High Util)",
140
+ flow=self,
141
+ )
142
+ self.start_step(gpl)
143
+ step_list.append(gpl)
144
+ except StepError:
145
+ info("High utilization failed- attempting low utilization…")
146
+ fp_config = min_config.copy(FP_CORE_UTIL=40)
147
+ fp = OpenROAD.Floorplan(
148
+ fp_config,
149
+ state_in=min_area_state,
150
+ id="fp-lowutl",
151
+ long_name="Floorplanning (Low Util)",
152
+ flow=self,
153
+ )
154
+ self.start_step(fp)
155
+ step_list.append(fp)
156
+ io = OpenROAD.IOPlacement(
157
+ fp_config,
158
+ state_in=fp.state_out,
159
+ id="io-lowutl",
160
+ long_name="I/O Placement (Low Util)",
161
+ flow=self,
162
+ )
163
+ self.start_step(io)
164
+ step_list.append(io)
165
+ gpl = OpenROAD.GlobalPlacement(
166
+ fp_config,
167
+ state_in=io.state_out,
168
+ id="gpl-lowutl",
169
+ long_name="Global Placement (Low Util)",
170
+ flow=self,
171
+ )
172
+ self.start_step(gpl)
173
+ step_list.append(gpl)
174
+
175
+ self.end_stage()
176
+
177
+ success("Flow complete.")
178
+ assert gpl.state_out is not None # We should be done with the execution by now
179
+ return (gpl.state_out, step_list)
@@ -0,0 +1,367 @@
1
+ # Copyright 2023 Efabless Corporation
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+ from __future__ import annotations
15
+
16
+ import os
17
+ import fnmatch
18
+ from typing import (
19
+ Iterable,
20
+ List,
21
+ Set,
22
+ Tuple,
23
+ Optional,
24
+ Type,
25
+ Dict,
26
+ Union,
27
+ )
28
+
29
+ from rapidfuzz import process, fuzz, utils
30
+
31
+ from .flow import Flow, FlowException, FlowError
32
+ from ..common import Filter
33
+ from ..state import State
34
+ from ..logging import info, success, debug
35
+ from ..steps import (
36
+ Step,
37
+ StepError,
38
+ StepException,
39
+ DeferredStepError,
40
+ )
41
+
42
+ Substitution = Union[str, Type[Step], None]
43
+
44
+
45
+ class SequentialFlow(Flow):
46
+ """
47
+ The simplest Flow, running each Step as a stage, serially,
48
+ with nothing happening in parallel and no significant inter-step
49
+ processing.
50
+
51
+ All subclasses of this flow have to do is override the :attr:`.Steps` abstract property
52
+ and it would automatically handle the rest. See `Classic` in Built-in Flows for an example.
53
+
54
+ It should be noted, for Steps with duplicate IDs, all Steps other than the
55
+ first one will technically be subclassed with no change other than to simply
56
+ set the ID to the previous step's ID with a suffix: i.e. the second instance
57
+ of ``Test.MyStep`` will have an ID of ``Test.MyStep1``, and so on.
58
+
59
+ :param Substitute: Substitute all instances of one `Step` type by another `Step`
60
+ type in the :attr:`.Steps` attribute for this instance only.
61
+
62
+ You may also use the string Step IDs in place of a `Step` type object.
63
+
64
+ Duplicate ID normalization is re-run after substitutions.
65
+
66
+ :param args: Arguments for :class:`Flow`.
67
+ :param kwargs: Keyword arguments for :class:`Flow`.
68
+
69
+ :cvar gating_config_vars: A mapping from step ID (wildcards) to lists of
70
+ Boolean variable names. All Boolean variables must be True for a step with
71
+ a specific ID to execute.
72
+ """
73
+
74
+ Substitutions: Optional[Dict[str, Union[str, Type[Step], None]]] = None
75
+ gating_config_vars: Dict[str, List[str]] = {}
76
+
77
+ def __init_subclass__(Self, scm_type=None, name=None, **kwargs):
78
+ Self.Steps = Self.Steps.copy() # Break global reference
79
+ Self.config_vars = Self.config_vars.copy()
80
+ Self.gating_config_vars = Self.gating_config_vars.copy()
81
+ if substitute := Self.Substitutions:
82
+ for key, item in substitute.items():
83
+ Self.__substitute_step(Self, key, item)
84
+
85
+ Self.__normalize_step_ids(Self)
86
+
87
+ # Validate Gating Config Vars
88
+ variables_by_name = {}
89
+ for variable in Self.config_vars:
90
+ variables_by_name[variable.name] = variable
91
+
92
+ step_id_set = set()
93
+ for step in Self.Steps:
94
+ step_id_set.add(step.id)
95
+
96
+ for id, variable_names in Self.gating_config_vars.items():
97
+ matching_steps = list(Filter([id]).filter(step_id_set))
98
+ if id not in step_id_set and len(matching_steps) < 1:
99
+ continue
100
+ for var_name in variable_names:
101
+ if var_name not in variables_by_name:
102
+ raise TypeError(
103
+ f"Gating variable '{var_name}' for Step '{id}' does not match any declared config_vars in Flow '{Self.__qualname__}'"
104
+ )
105
+ if variables_by_name[var_name].type != bool:
106
+ raise TypeError(
107
+ f"Gating variable '{var_name}' in Flow '{Self.__qualname__}' is not a Boolean"
108
+ )
109
+
110
+ @classmethod
111
+ def make(Self, step_ids: List[str]) -> Type[SequentialFlow]:
112
+ Step_list = []
113
+ for name in step_ids:
114
+ step = Step.factory.get(name)
115
+ if step is None:
116
+ raise TypeError(f"No step found with id '{name}'")
117
+ Step_list.append(step)
118
+
119
+ class CustomSequentialFlow(SequentialFlow):
120
+ name = "Custom Sequential Flow"
121
+ Steps = Step_list
122
+
123
+ return CustomSequentialFlow
124
+
125
+ @classmethod
126
+ def Substitute(
127
+ Self, Substitutions: Dict[str, Substitution]
128
+ ) -> Type[SequentialFlow]:
129
+ """
130
+ Convenience method to quickly subclass a sequential flow and add
131
+ Substitutions to it.
132
+
133
+ The new flow shall be named ``{previous_flow_name}'``.
134
+
135
+ :param Substitutions: The substitutions to use for the new subclass.
136
+ """
137
+ return type(Self.__name__ + "'", (Self,), {"Substitutions": Substitutions})
138
+
139
+ def __init__(
140
+ self,
141
+ *args,
142
+ Substitute: Optional[Dict[str, Union[str, Type[Step], None]]] = None,
143
+ **kwargs,
144
+ ):
145
+ self.Steps = self.Steps.copy() # Break global reference
146
+
147
+ if substitute := Substitute:
148
+ for key, item in substitute.items():
149
+ self.__substitute_step(self, key, item)
150
+ self.__normalize_step_ids(self)
151
+
152
+ super().__init__(*args, **kwargs)
153
+
154
+ @staticmethod
155
+ def __substitute_step(
156
+ target: Union[SequentialFlow, Type[SequentialFlow]],
157
+ id: str,
158
+ with_step: Union[str, Type[Step], None],
159
+ ):
160
+ step_indices: List[int] = []
161
+ mode = "replace"
162
+ if id.startswith("+"):
163
+ id = id[1:]
164
+ mode = "append"
165
+ if with_step is None:
166
+ raise FlowException("Cannot prepend or append None.")
167
+ elif id.startswith("-"):
168
+ id = id[1:]
169
+ mode = "prepend"
170
+ if with_step is None:
171
+ raise FlowException("Cannot prepend or append None.")
172
+
173
+ for i, step in enumerate(target.Steps):
174
+ if (
175
+ step.id
176
+ != NotImplemented # Will be validated later by initialization: ignore for now
177
+ and fnmatch.fnmatch(step.id.lower(), id.lower())
178
+ ):
179
+ step_indices.append(i)
180
+ if len(step_indices) == 0:
181
+ if with_step is None:
182
+ raise FlowException(
183
+ f"Could not remove '{id}': no steps with ID '{id}' found in flow"
184
+ )
185
+ raise FlowException(
186
+ f"Could not {mode} '{id}' with '{with_step}': no steps with ID '{id}' found in flow."
187
+ )
188
+
189
+ if with_step is None:
190
+ for index in reversed(step_indices):
191
+ del target.Steps[index]
192
+ return
193
+
194
+ if isinstance(with_step, str):
195
+ with_step_opt = Step.factory.get(with_step)
196
+ if with_step_opt is None:
197
+ raise FlowException(
198
+ f"Could not {mode} '{id}' with '{with_step}': no replacement step with ID '{with_step}' found."
199
+ )
200
+ with_step = with_step_opt
201
+
202
+ for i in step_indices:
203
+ if mode == "replace":
204
+ target.Steps[i] = with_step
205
+ elif mode == "append":
206
+ target.Steps.insert(i + 1, with_step)
207
+ elif mode == "prepend":
208
+ target.Steps.insert(i, with_step)
209
+
210
+ @staticmethod
211
+ def __normalize_step_ids(target: Union[SequentialFlow, Type[SequentialFlow]]):
212
+ ids_used: Set[str] = set()
213
+
214
+ for i, step in enumerate(target.Steps):
215
+ counter = 0
216
+ id = step.id
217
+ if (
218
+ id == NotImplemented
219
+ ): # Will be validated later by initialization: ignore for now
220
+ continue
221
+ while id in ids_used:
222
+ counter += 1
223
+ id = f"{step.id}-{counter}"
224
+ if id != step.id:
225
+ target.Steps[i] = step.with_id(id)
226
+ ids_used.add(id)
227
+
228
+ def run(
229
+ self,
230
+ initial_state: State,
231
+ frm: Optional[str] = None,
232
+ to: Optional[str] = None,
233
+ skip: Optional[Iterable[str]] = None,
234
+ reproducible: Optional[str] = None,
235
+ **kwargs,
236
+ ) -> Tuple[State, List[Step]]:
237
+ debug(f"Starting run ▶ '{self.run_dir}'")
238
+ step_ids = {cls.id.lower(): cls.id for cls in reversed(self.Steps)}
239
+ skipped_ids: List[str] = []
240
+
241
+ def resolve_step(matchable: Optional[str], multiple_ok: bool = False):
242
+ nonlocal step_ids
243
+ dangerous_fuzzy_matching = (
244
+ os.getenv(
245
+ "_i_want_librelane_to_fuzzy_match_steps_and_im_willing_to_accept_the_risks",
246
+ None,
247
+ )
248
+ == "1"
249
+ )
250
+ if matchable is None:
251
+ return None
252
+ ids = list(Filter([matchable.lower()]).filter(step_ids))
253
+ if len(ids) > 0:
254
+ if multiple_ok:
255
+ return [step_ids[id] for id in ids]
256
+ if len(ids) > 1:
257
+ raise FlowException(f"{matchable} matched multiple steps.")
258
+ if len(ids) == 1:
259
+ return step_ids[ids[0]]
260
+ else:
261
+ matchTuple = process.extractOne(
262
+ matchable,
263
+ step_ids,
264
+ scorer=fuzz.partial_ratio,
265
+ score_cutoff=80,
266
+ processor=utils.default_process,
267
+ )
268
+ suggestion = ""
269
+ if matchTuple is not None:
270
+ match, _, _ = matchTuple
271
+ if dangerous_fuzzy_matching:
272
+ return [match] if multiple_ok else match
273
+ else:
274
+ suggestion = f" Did you mean: '{match}'?"
275
+ raise FlowException(
276
+ f"Failed to process '{matchable}': no step(s) with ID '{matchable}' found in flow.{suggestion}"
277
+ )
278
+
279
+ frm_resolved = resolve_step(frm)
280
+
281
+ to_resolved = resolve_step(to)
282
+
283
+ reproducible_resolved = resolve_step(reproducible)
284
+
285
+ if skipped_steps := skip:
286
+ for skipped_step in skipped_steps:
287
+ skipped_ids += resolve_step(skipped_step, multiple_ok=True)
288
+
289
+ step_count = len(self.Steps)
290
+ self.progress_bar.set_max_stage_count(step_count)
291
+
292
+ step_list = []
293
+
294
+ info("Starting…")
295
+
296
+ executing = frm is None
297
+ deferred_errors = []
298
+
299
+ gating_cvars_expanded: Dict[str, List[str]] = {}
300
+ for key, value in self.gating_config_vars.items():
301
+ if key in step_ids.values():
302
+ gating_cvars_expanded[key] = value
303
+ continue
304
+ for id in Filter([key]).filter(step_ids.values()):
305
+ gating_cvars_expanded[id] = value
306
+
307
+ current_state = initial_state
308
+ for cls in self.Steps:
309
+ step = cls(config=self.config, state_in=current_state)
310
+ if frm_resolved is not None and frm_resolved == step.id:
311
+ executing = True
312
+
313
+ gated = False
314
+ if gating_cvars := gating_cvars_expanded.get(step.id):
315
+ for variable in gating_cvars:
316
+ if not self.config[variable]:
317
+ info(
318
+ f"Gating variable for step '{step.id}' set to 'False'- the step will be skipped."
319
+ )
320
+ gated = True
321
+
322
+ self.progress_bar.start_stage(step.name)
323
+ increment_ordinal = True
324
+ if not executing or cls.id in skipped_ids or gated:
325
+ info(f"Skipping step '{step.name}'…")
326
+ increment_ordinal = False
327
+ elif cls.id == reproducible_resolved:
328
+ step.create_reproducible(
329
+ os.path.join(
330
+ self.dir_for_step(step),
331
+ "reproducible",
332
+ )
333
+ )
334
+ break
335
+ else:
336
+ step_list.append(step)
337
+ try:
338
+ current_state = step.start(
339
+ toolbox=self.toolbox,
340
+ step_dir=self.dir_for_step(step),
341
+ )
342
+ except StepException as e:
343
+ raise FlowException(str(e)) from None
344
+ except DeferredStepError as e:
345
+ deferred_errors.append(str(e))
346
+ except StepError as e:
347
+ raise FlowError(str(e)) from None
348
+
349
+ self.progress_bar.end_stage(increment_ordinal=increment_ordinal)
350
+
351
+ if to_resolved and to_resolved == step.id:
352
+ executing = False
353
+ if len(deferred_errors) != 0:
354
+ raise FlowError(
355
+ "One or more deferred errors were encountered:\n"
356
+ + "\n".join(deferred_errors)
357
+ )
358
+
359
+ assert self.run_dir is not None
360
+ debug(f"Run concluded ▶ '{self.run_dir}'")
361
+ final_views_path = os.path.join(self.run_dir, "final")
362
+ try:
363
+ current_state.save_snapshot(final_views_path)
364
+ except Exception as e:
365
+ raise FlowException(f"Failed to save final views: {e}")
366
+ success("Flow complete.")
367
+ return (current_state, step_list)