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.
- librelane/__init__.py +38 -0
- librelane/__main__.py +479 -0
- librelane/__version__.py +43 -0
- librelane/common/__init__.py +63 -0
- librelane/common/cli.py +75 -0
- librelane/common/drc.py +246 -0
- librelane/common/generic_dict.py +319 -0
- librelane/common/metrics/__init__.py +35 -0
- librelane/common/metrics/__main__.py +413 -0
- librelane/common/metrics/library.py +354 -0
- librelane/common/metrics/metric.py +186 -0
- librelane/common/metrics/util.py +279 -0
- librelane/common/misc.py +456 -0
- librelane/common/ring_buffer.py +63 -0
- librelane/common/tcl.py +80 -0
- librelane/common/toolbox.py +549 -0
- librelane/common/tpe.py +41 -0
- librelane/common/types.py +116 -0
- librelane/config/__init__.py +32 -0
- librelane/config/__main__.py +155 -0
- librelane/config/config.py +1025 -0
- librelane/config/flow.py +490 -0
- librelane/config/pdk_compat.py +255 -0
- librelane/config/preprocessor.py +464 -0
- librelane/config/removals.py +45 -0
- librelane/config/variable.py +743 -0
- librelane/container.py +285 -0
- librelane/env_info.py +320 -0
- librelane/examples/spm/config.yaml +33 -0
- librelane/examples/spm/pin_order.cfg +14 -0
- librelane/examples/spm/src/impl.sdc +73 -0
- librelane/examples/spm/src/signoff.sdc +68 -0
- librelane/examples/spm/src/spm.v +73 -0
- librelane/examples/spm/verify/spm_tb.v +106 -0
- librelane/examples/spm-user_project_wrapper/SPM_example.v +286 -0
- librelane/examples/spm-user_project_wrapper/base_sdc_file.sdc +145 -0
- librelane/examples/spm-user_project_wrapper/config-tut.json +12 -0
- librelane/examples/spm-user_project_wrapper/config.json +13 -0
- librelane/examples/spm-user_project_wrapper/defines.v +66 -0
- librelane/examples/spm-user_project_wrapper/template.def +7656 -0
- librelane/examples/spm-user_project_wrapper/user_project_wrapper.v +123 -0
- librelane/flows/__init__.py +24 -0
- librelane/flows/builtins.py +18 -0
- librelane/flows/classic.py +327 -0
- librelane/flows/cli.py +463 -0
- librelane/flows/flow.py +1049 -0
- librelane/flows/misc.py +71 -0
- librelane/flows/optimizing.py +179 -0
- librelane/flows/sequential.py +367 -0
- librelane/flows/synth_explore.py +173 -0
- librelane/help/__main__.py +39 -0
- librelane/logging/__init__.py +40 -0
- librelane/logging/logger.py +323 -0
- librelane/open_pdks_rev +1 -0
- librelane/plugins.py +21 -0
- librelane/py.typed +0 -0
- librelane/scripts/base.sdc +80 -0
- librelane/scripts/klayout/Readme.md +2 -0
- librelane/scripts/klayout/open_design.py +63 -0
- librelane/scripts/klayout/render.py +121 -0
- librelane/scripts/klayout/stream_out.py +176 -0
- librelane/scripts/klayout/xml_drc_report_to_json.py +45 -0
- librelane/scripts/klayout/xor.drc +120 -0
- librelane/scripts/magic/Readme.md +1 -0
- librelane/scripts/magic/common/read.tcl +114 -0
- librelane/scripts/magic/def/antenna_check.tcl +35 -0
- librelane/scripts/magic/def/mag.tcl +19 -0
- librelane/scripts/magic/def/mag_gds.tcl +79 -0
- librelane/scripts/magic/drc.tcl +78 -0
- librelane/scripts/magic/extract_spice.tcl +98 -0
- librelane/scripts/magic/gds/drc_batch.tcl +74 -0
- librelane/scripts/magic/gds/erase_box.tcl +32 -0
- librelane/scripts/magic/gds/extras_mag.tcl +45 -0
- librelane/scripts/magic/gds/mag_with_pointers.tcl +31 -0
- librelane/scripts/magic/get_bbox.tcl +11 -0
- librelane/scripts/magic/lef/extras_maglef.tcl +61 -0
- librelane/scripts/magic/lef/maglef.tcl +26 -0
- librelane/scripts/magic/lef.tcl +57 -0
- librelane/scripts/magic/open.tcl +28 -0
- librelane/scripts/magic/wrapper.tcl +21 -0
- librelane/scripts/netgen/setup.tcl +28 -0
- librelane/scripts/odbpy/apply_def_template.py +49 -0
- librelane/scripts/odbpy/cell_frequency.py +107 -0
- librelane/scripts/odbpy/check_antenna_properties.py +116 -0
- librelane/scripts/odbpy/contextualize.py +109 -0
- librelane/scripts/odbpy/defutil.py +573 -0
- librelane/scripts/odbpy/diodes.py +373 -0
- librelane/scripts/odbpy/disconnected_pins.py +305 -0
- librelane/scripts/odbpy/eco_buffer.py +181 -0
- librelane/scripts/odbpy/eco_diode.py +139 -0
- librelane/scripts/odbpy/filter_unannotated.py +100 -0
- librelane/scripts/odbpy/io_place.py +482 -0
- librelane/scripts/odbpy/ioplace_parser/__init__.py +23 -0
- librelane/scripts/odbpy/ioplace_parser/parse.py +147 -0
- librelane/scripts/odbpy/label_macro_pins.py +277 -0
- librelane/scripts/odbpy/lefutil.py +97 -0
- librelane/scripts/odbpy/placers.py +162 -0
- librelane/scripts/odbpy/power_utils.py +397 -0
- librelane/scripts/odbpy/random_place.py +57 -0
- librelane/scripts/odbpy/reader.py +250 -0
- librelane/scripts/odbpy/remove_buffers.py +173 -0
- librelane/scripts/odbpy/snap_to_grid.py +57 -0
- librelane/scripts/odbpy/wire_lengths.py +93 -0
- librelane/scripts/openroad/antenna_check.tcl +20 -0
- librelane/scripts/openroad/antenna_repair.tcl +31 -0
- librelane/scripts/openroad/basic_mp.tcl +24 -0
- librelane/scripts/openroad/buffer_list.tcl +10 -0
- librelane/scripts/openroad/common/dpl.tcl +24 -0
- librelane/scripts/openroad/common/dpl_cell_pad.tcl +26 -0
- librelane/scripts/openroad/common/grt.tcl +32 -0
- librelane/scripts/openroad/common/io.tcl +540 -0
- librelane/scripts/openroad/common/pdn_cfg.tcl +135 -0
- librelane/scripts/openroad/common/resizer.tcl +103 -0
- librelane/scripts/openroad/common/set_global_connections.tcl +78 -0
- librelane/scripts/openroad/common/set_layer_adjustments.tcl +31 -0
- librelane/scripts/openroad/common/set_power_nets.tcl +30 -0
- librelane/scripts/openroad/common/set_rc.tcl +75 -0
- librelane/scripts/openroad/common/set_routing_layers.tcl +30 -0
- librelane/scripts/openroad/cts.tcl +80 -0
- librelane/scripts/openroad/cut_rows.tcl +24 -0
- librelane/scripts/openroad/dpl.tcl +24 -0
- librelane/scripts/openroad/drt.tcl +37 -0
- librelane/scripts/openroad/fill.tcl +30 -0
- librelane/scripts/openroad/floorplan.tcl +145 -0
- librelane/scripts/openroad/gpl.tcl +88 -0
- librelane/scripts/openroad/grt.tcl +30 -0
- librelane/scripts/openroad/gui.tcl +37 -0
- librelane/scripts/openroad/insert_buffer.tcl +127 -0
- librelane/scripts/openroad/ioplacer.tcl +67 -0
- librelane/scripts/openroad/irdrop.tcl +51 -0
- librelane/scripts/openroad/pdn.tcl +52 -0
- librelane/scripts/openroad/rcx.tcl +32 -0
- librelane/scripts/openroad/repair_design.tcl +70 -0
- librelane/scripts/openroad/repair_design_postgrt.tcl +48 -0
- librelane/scripts/openroad/rsz_timing_postcts.tcl +68 -0
- librelane/scripts/openroad/rsz_timing_postgrt.tcl +70 -0
- librelane/scripts/openroad/sta/check_macro_instances.tcl +53 -0
- librelane/scripts/openroad/sta/corner.tcl +393 -0
- librelane/scripts/openroad/tapcell.tcl +25 -0
- librelane/scripts/openroad/write_views.tcl +27 -0
- librelane/scripts/pyosys/construct_abc_script.py +177 -0
- librelane/scripts/pyosys/json_header.py +84 -0
- librelane/scripts/pyosys/synthesize.py +493 -0
- librelane/scripts/pyosys/ys_common.py +153 -0
- librelane/scripts/tclsh/hello.tcl +1 -0
- librelane/state/__init__.py +24 -0
- librelane/state/__main__.py +61 -0
- librelane/state/design_format.py +195 -0
- librelane/state/state.py +359 -0
- librelane/steps/__init__.py +61 -0
- librelane/steps/__main__.py +510 -0
- librelane/steps/checker.py +637 -0
- librelane/steps/common_variables.py +340 -0
- librelane/steps/cvc_rv.py +169 -0
- librelane/steps/klayout.py +509 -0
- librelane/steps/magic.py +576 -0
- librelane/steps/misc.py +160 -0
- librelane/steps/netgen.py +253 -0
- librelane/steps/odb.py +1088 -0
- librelane/steps/openroad.py +2460 -0
- librelane/steps/openroad_alerts.py +102 -0
- librelane/steps/pyosys.py +640 -0
- librelane/steps/step.py +1571 -0
- librelane/steps/tclstep.py +288 -0
- librelane/steps/verilator.py +222 -0
- librelane/steps/yosys.py +371 -0
- librelane-2.4.0.dist-info/METADATA +169 -0
- librelane-2.4.0.dist-info/RECORD +170 -0
- librelane-2.4.0.dist-info/WHEEL +4 -0
- librelane-2.4.0.dist-info/entry_points.txt +9 -0
librelane/flows/misc.py
ADDED
|
@@ -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)
|