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
|
@@ -0,0 +1,413 @@
|
|
|
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
|
+
import os
|
|
15
|
+
import sys
|
|
16
|
+
import json
|
|
17
|
+
import gzip
|
|
18
|
+
import click
|
|
19
|
+
import tarfile
|
|
20
|
+
import tempfile
|
|
21
|
+
from io import BytesIO
|
|
22
|
+
from decimal import Decimal
|
|
23
|
+
from typing import Optional, Set, Tuple
|
|
24
|
+
|
|
25
|
+
import cloup
|
|
26
|
+
import httpx
|
|
27
|
+
|
|
28
|
+
from .util import MetricDiff, TableVerbosity
|
|
29
|
+
from ..misc import Filter, get_httpx_session, mkdirp
|
|
30
|
+
from ..cli import formatter_settings, IntEnumChoice
|
|
31
|
+
|
|
32
|
+
default_filter_set = [
|
|
33
|
+
"design__*__area",
|
|
34
|
+
"design__max_*",
|
|
35
|
+
"design__lvs_error__count",
|
|
36
|
+
"antenna__violating*",
|
|
37
|
+
"clock__*",
|
|
38
|
+
"ir__*",
|
|
39
|
+
"power__*",
|
|
40
|
+
"timing__*_vio__*",
|
|
41
|
+
"timing*wns*",
|
|
42
|
+
"timing*tns*",
|
|
43
|
+
"*error*",
|
|
44
|
+
"!*__iter:*",
|
|
45
|
+
]
|
|
46
|
+
|
|
47
|
+
# passing_filter_set = [
|
|
48
|
+
# "design__*__area",
|
|
49
|
+
# "route__wirelength__max",
|
|
50
|
+
# "design__instance__utilization",
|
|
51
|
+
# "antenna__violating*",
|
|
52
|
+
# "timing__*__ws",
|
|
53
|
+
# "clock__skew__*",
|
|
54
|
+
# "ir__*",
|
|
55
|
+
# "power__*",
|
|
56
|
+
# "!*__iter:*",
|
|
57
|
+
# ]
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
@cloup.group(
|
|
61
|
+
no_args_is_help=True,
|
|
62
|
+
formatter_settings=formatter_settings,
|
|
63
|
+
)
|
|
64
|
+
def cli():
|
|
65
|
+
pass
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def common_opts(f):
|
|
69
|
+
f = cloup.option(
|
|
70
|
+
"-f",
|
|
71
|
+
"--filter",
|
|
72
|
+
"filter_wildcards",
|
|
73
|
+
multiple=True,
|
|
74
|
+
default=("DEFAULT",),
|
|
75
|
+
help="A list of wildcards to filter by. Wildcards prefixed with ! exclude rather than include and take priority. 'DEFAULT' is replaced by a set of default wildcards.",
|
|
76
|
+
)(f)
|
|
77
|
+
f = cloup.option(
|
|
78
|
+
"--table-verbosity",
|
|
79
|
+
type=IntEnumChoice(TableVerbosity),
|
|
80
|
+
default="ALL",
|
|
81
|
+
help=TableVerbosity.__doc__,
|
|
82
|
+
)(f)
|
|
83
|
+
f = cloup.option(
|
|
84
|
+
"--table-out",
|
|
85
|
+
type=click.Path(file_okay=True, dir_okay=False, writable=True),
|
|
86
|
+
help="The place to write the table to.",
|
|
87
|
+
default=None,
|
|
88
|
+
)(f)
|
|
89
|
+
f = cloup.option(
|
|
90
|
+
"--significant-figures",
|
|
91
|
+
type=int,
|
|
92
|
+
help="Number of significant figures.",
|
|
93
|
+
default=4,
|
|
94
|
+
)(f)
|
|
95
|
+
return f
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
@cloup.command(no_args_is_help=True)
|
|
99
|
+
@common_opts
|
|
100
|
+
@cloup.argument("metric_files", nargs=2)
|
|
101
|
+
def compare(
|
|
102
|
+
metric_files: Tuple[str, str],
|
|
103
|
+
table_verbosity: TableVerbosity,
|
|
104
|
+
filter_wildcards: Tuple[str, ...],
|
|
105
|
+
table_out: Optional[str],
|
|
106
|
+
significant_figures: int,
|
|
107
|
+
):
|
|
108
|
+
"""
|
|
109
|
+
Creates a small summary of the differences between two ``metrics.json`` files.
|
|
110
|
+
"""
|
|
111
|
+
if table_verbosity == "NONE":
|
|
112
|
+
print("Table is empty.", file=sys.stderr)
|
|
113
|
+
exit(0)
|
|
114
|
+
|
|
115
|
+
a_path, b_path = metric_files
|
|
116
|
+
a = json.load(open(a_path, encoding="utf8"), parse_float=Decimal)
|
|
117
|
+
b = json.load(open(b_path, encoding="utf8"), parse_float=Decimal)
|
|
118
|
+
|
|
119
|
+
final_filters = []
|
|
120
|
+
for wildcard in filter_wildcards:
|
|
121
|
+
if wildcard == "DEFAULT":
|
|
122
|
+
final_filters += default_filter_set
|
|
123
|
+
else:
|
|
124
|
+
final_filters.append(wildcard)
|
|
125
|
+
|
|
126
|
+
diff = MetricDiff.from_metrics(
|
|
127
|
+
a, b, significant_figures, filter=Filter(final_filters)
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
md_str = diff.render_md(sort_by=("corner", ""), table_verbosity=table_verbosity)
|
|
131
|
+
|
|
132
|
+
table_file = sys.stdout
|
|
133
|
+
if table_out is not None:
|
|
134
|
+
table_file = open(table_out, "w", encoding="utf8")
|
|
135
|
+
print(md_str, file=table_file)
|
|
136
|
+
|
|
137
|
+
# When we upgrade to rich 13 (when NixOS 23.11 comes out,
|
|
138
|
+
# it has a proper markdown table renderer, but until then, this will have to do)
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
cli.add_command(compare)
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def _compare_metric_folders(
|
|
145
|
+
filter_wildcards: Tuple[str, ...],
|
|
146
|
+
table_verbosity: TableVerbosity,
|
|
147
|
+
path_a: str,
|
|
148
|
+
path_b: str,
|
|
149
|
+
significant_figures: int,
|
|
150
|
+
) -> Tuple[str, str]: # (summary, table)
|
|
151
|
+
a: Set[Tuple[str, str, str]] = set()
|
|
152
|
+
b: Set[Tuple[str, str, str]] = set()
|
|
153
|
+
|
|
154
|
+
def add_designs(in_dir: str, to_set: Set[Tuple[str, str, str]]):
|
|
155
|
+
for file in os.listdir(in_dir):
|
|
156
|
+
basename = os.path.basename(file)
|
|
157
|
+
if not basename.endswith(".metrics.json"):
|
|
158
|
+
continue
|
|
159
|
+
basename = basename[: -len(".metrics.json")]
|
|
160
|
+
|
|
161
|
+
parts = basename.split("-", maxsplit=2)
|
|
162
|
+
if len(parts) != 3:
|
|
163
|
+
raise ValueError(
|
|
164
|
+
f"Invalid filename {basename}: not in the format {{pdk}}-{{scl}}-{{design_name}}"
|
|
165
|
+
)
|
|
166
|
+
pdk, scl, design = parts
|
|
167
|
+
to_set.add((pdk, scl, design))
|
|
168
|
+
|
|
169
|
+
add_designs(path_a, a)
|
|
170
|
+
add_designs(path_b, b)
|
|
171
|
+
|
|
172
|
+
not_in_a = b - a
|
|
173
|
+
not_in_b = a - b
|
|
174
|
+
common = a.intersection(b)
|
|
175
|
+
difference_report = ""
|
|
176
|
+
for tup in not_in_a:
|
|
177
|
+
pdk, scl, design = tup
|
|
178
|
+
difference_report += f"* Results for a new test, `{'/'.join(tup)}`, detected.\n"
|
|
179
|
+
for tup in not_in_b:
|
|
180
|
+
pdk, scl, design = tup
|
|
181
|
+
difference_report += (
|
|
182
|
+
f"* ‼️ Results for `{'/'.join(tup)}` appear to be missing!\n"
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
final_filters = []
|
|
186
|
+
for wildcard in filter_wildcards:
|
|
187
|
+
if wildcard == "DEFAULT":
|
|
188
|
+
final_filters += default_filter_set
|
|
189
|
+
else:
|
|
190
|
+
final_filters.append(wildcard)
|
|
191
|
+
|
|
192
|
+
filter = Filter(final_filters)
|
|
193
|
+
critical_change_report = ""
|
|
194
|
+
tables = ""
|
|
195
|
+
total_critical = 0
|
|
196
|
+
for pdk, scl, design in sorted(common):
|
|
197
|
+
metrics_a = json.load(
|
|
198
|
+
open(
|
|
199
|
+
os.path.join(path_a, f"{pdk}-{scl}-{design}.metrics.json"),
|
|
200
|
+
encoding="utf8",
|
|
201
|
+
),
|
|
202
|
+
parse_float=Decimal,
|
|
203
|
+
)
|
|
204
|
+
|
|
205
|
+
metrics_b = json.load(
|
|
206
|
+
open(
|
|
207
|
+
os.path.join(path_b, f"{pdk}-{scl}-{design}.metrics.json"),
|
|
208
|
+
encoding="utf8",
|
|
209
|
+
),
|
|
210
|
+
parse_float=Decimal,
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
diff = MetricDiff.from_metrics(
|
|
214
|
+
metrics_a,
|
|
215
|
+
metrics_b,
|
|
216
|
+
significant_figures,
|
|
217
|
+
filter=filter,
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
stats = diff.stats()
|
|
221
|
+
|
|
222
|
+
total_critical += stats.critical
|
|
223
|
+
if stats.critical > 0:
|
|
224
|
+
critical_change_report += f" * `{pdk}/{scl}/{design}` \n"
|
|
225
|
+
if table_verbosity != "NONE":
|
|
226
|
+
rendered = diff.render_md(("corner", ""), table_verbosity)
|
|
227
|
+
if rendered.strip() != "":
|
|
228
|
+
tables += f"<details><summary><code>{pdk}/{scl}/{design}</code></summary>\n{rendered}\n</details>\n\n"
|
|
229
|
+
|
|
230
|
+
if total_critical == 0:
|
|
231
|
+
critical_change_report = (
|
|
232
|
+
"* No changes to critical metrics were detected in analyzed designs.\n"
|
|
233
|
+
+ critical_change_report
|
|
234
|
+
)
|
|
235
|
+
else:
|
|
236
|
+
critical_change_report = (
|
|
237
|
+
"* **Changes to critical metrics were detected in the following designs:**\n"
|
|
238
|
+
+ critical_change_report
|
|
239
|
+
)
|
|
240
|
+
|
|
241
|
+
report = ""
|
|
242
|
+
report += difference_report
|
|
243
|
+
report += critical_change_report
|
|
244
|
+
|
|
245
|
+
return report, tables.strip()
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
@cloup.command(no_args_is_help=True)
|
|
249
|
+
@common_opts
|
|
250
|
+
@cloup.argument("metric_folders", nargs=2)
|
|
251
|
+
def compare_multiple(
|
|
252
|
+
filter_wildcards: Tuple[str, ...],
|
|
253
|
+
table_verbosity: TableVerbosity,
|
|
254
|
+
metric_folders: Tuple[str, str],
|
|
255
|
+
table_out: Optional[str],
|
|
256
|
+
significant_figures: int,
|
|
257
|
+
):
|
|
258
|
+
"""
|
|
259
|
+
Creates a small summary/report of the differences between two folders with
|
|
260
|
+
metrics files.
|
|
261
|
+
|
|
262
|
+
The metrics files must be named in the format ``{pdk}-{scl}-{design}.metrics.json``.
|
|
263
|
+
All other files are ignored.
|
|
264
|
+
"""
|
|
265
|
+
path_a, path_b = metric_folders
|
|
266
|
+
summary, tables = _compare_metric_folders(
|
|
267
|
+
filter_wildcards, table_verbosity, path_a, path_b, significant_figures
|
|
268
|
+
)
|
|
269
|
+
print(summary)
|
|
270
|
+
table_file = sys.stdout
|
|
271
|
+
if table_out is not None:
|
|
272
|
+
table_file = open(table_out, "w", encoding="utf8")
|
|
273
|
+
print(tables, file=table_file)
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
cli.add_command(compare_multiple)
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
@cloup.command(hidden=True)
|
|
280
|
+
@cloup.option(
|
|
281
|
+
"-r",
|
|
282
|
+
"--repo",
|
|
283
|
+
default="librelane/librelane",
|
|
284
|
+
help="The GitHub repository for LibreLane",
|
|
285
|
+
)
|
|
286
|
+
@cloup.option(
|
|
287
|
+
"-m",
|
|
288
|
+
"--metric-repo",
|
|
289
|
+
default="librelane/librelane-metrics",
|
|
290
|
+
help="The repository storing metrics for --repo",
|
|
291
|
+
)
|
|
292
|
+
@cloup.option(
|
|
293
|
+
"-b",
|
|
294
|
+
"--branch",
|
|
295
|
+
default="main",
|
|
296
|
+
help="The branch to compare to",
|
|
297
|
+
)
|
|
298
|
+
@cloup.option(
|
|
299
|
+
"-c",
|
|
300
|
+
"--commit",
|
|
301
|
+
default=None,
|
|
302
|
+
help="The commit of --repo to fetch the metrics for. By default, that's the latest commit in the chosen branch.",
|
|
303
|
+
)
|
|
304
|
+
@cloup.option(
|
|
305
|
+
"-t",
|
|
306
|
+
"--token",
|
|
307
|
+
default=None,
|
|
308
|
+
help="A GitHub token to use to query the API and fetch the metrics. Not strictly required, but helps avoid rate-limiting.",
|
|
309
|
+
)
|
|
310
|
+
@common_opts
|
|
311
|
+
@cloup.argument("metric_folder", nargs=1)
|
|
312
|
+
def compare_remote(
|
|
313
|
+
filter_wildcards: Tuple[str, ...],
|
|
314
|
+
table_verbosity: TableVerbosity,
|
|
315
|
+
repo: str,
|
|
316
|
+
metric_repo: str,
|
|
317
|
+
commit: Optional[str],
|
|
318
|
+
token: str,
|
|
319
|
+
metric_folder: str,
|
|
320
|
+
table_out: Optional[str],
|
|
321
|
+
significant_figures: int,
|
|
322
|
+
branch: str,
|
|
323
|
+
):
|
|
324
|
+
"""
|
|
325
|
+
Creates a small summary/report of the differences between a folder and
|
|
326
|
+
a set of metrics stored in --metric-repo. Requires Internet access and
|
|
327
|
+
access to GitHub.
|
|
328
|
+
|
|
329
|
+
The metrics files must be named in the format ``{pdk}-{scl}-{design}.metrics.json``.
|
|
330
|
+
All other files are ignored.
|
|
331
|
+
"""
|
|
332
|
+
session = get_httpx_session(token)
|
|
333
|
+
|
|
334
|
+
if commit is None:
|
|
335
|
+
try:
|
|
336
|
+
result = session.get(
|
|
337
|
+
f"https://api.github.com/repos/{repo}/branches/{branch}"
|
|
338
|
+
)
|
|
339
|
+
except httpx.HTTPStatusError as e:
|
|
340
|
+
if e.response is not None and e.response.status_code == 404:
|
|
341
|
+
print(f"'{branch}' branch of repo {repo} not found.", file=sys.stderr)
|
|
342
|
+
else:
|
|
343
|
+
print(
|
|
344
|
+
f"failed to get info from github API: {e.response.status_code}",
|
|
345
|
+
file=sys.stderr,
|
|
346
|
+
)
|
|
347
|
+
sys.exit(-1)
|
|
348
|
+
result.raise_for_status()
|
|
349
|
+
commit = str(result.json()["commit"]["sha"])
|
|
350
|
+
url = f"https://github.com/{metric_repo}/tarball/commit-{commit}"
|
|
351
|
+
|
|
352
|
+
try:
|
|
353
|
+
with tempfile.TemporaryDirectory(prefix="librelane_metrics_tmpdir_") as d:
|
|
354
|
+
bio_gz = BytesIO()
|
|
355
|
+
with session.stream("GET", url) as r:
|
|
356
|
+
r.raise_for_status()
|
|
357
|
+
for chunk in r.iter_bytes(chunk_size=8192):
|
|
358
|
+
bio_gz.write(chunk)
|
|
359
|
+
bio_gz.seek(0)
|
|
360
|
+
with gzip.GzipFile(fileobj=bio_gz) as bio, tarfile.TarFile(
|
|
361
|
+
fileobj=bio, mode="r"
|
|
362
|
+
) as tf:
|
|
363
|
+
for file in tf:
|
|
364
|
+
if file.isdir():
|
|
365
|
+
continue
|
|
366
|
+
stripped = os.path.sep.join(file.name.split(os.path.sep)[1:])
|
|
367
|
+
final_path = os.path.join(d, stripped)
|
|
368
|
+
final_dir = os.path.dirname(final_path)
|
|
369
|
+
mkdirp(final_dir)
|
|
370
|
+
io = tf.extractfile(file)
|
|
371
|
+
if io is None:
|
|
372
|
+
print(
|
|
373
|
+
f"Failed to unpack file in tarball: {file.name}.",
|
|
374
|
+
file=sys.stderr,
|
|
375
|
+
)
|
|
376
|
+
else:
|
|
377
|
+
with open(final_path, "wb") as f:
|
|
378
|
+
f.write(io.read())
|
|
379
|
+
|
|
380
|
+
summary, tables = _compare_metric_folders(
|
|
381
|
+
filter_wildcards,
|
|
382
|
+
table_verbosity,
|
|
383
|
+
d,
|
|
384
|
+
metric_folder,
|
|
385
|
+
significant_figures,
|
|
386
|
+
)
|
|
387
|
+
print(summary)
|
|
388
|
+
table_file = sys.stdout
|
|
389
|
+
if table_out is not None:
|
|
390
|
+
table_file = open(table_out, "w", encoding="utf8")
|
|
391
|
+
print(tables, file=table_file)
|
|
392
|
+
except httpx.HTTPStatusError as e:
|
|
393
|
+
if e.response is not None and e.response.status_code == 404:
|
|
394
|
+
print(f"Metrics not found for commit: {commit}.", file=sys.stderr)
|
|
395
|
+
else:
|
|
396
|
+
if e.response is not None:
|
|
397
|
+
print(
|
|
398
|
+
f"Failed to obtain metrics for {commit} remotely: {e.response}.",
|
|
399
|
+
file=sys.stderr,
|
|
400
|
+
)
|
|
401
|
+
else:
|
|
402
|
+
print(
|
|
403
|
+
f"Failed to request metrics for {commit} from server: {e}.",
|
|
404
|
+
file=sys.stderr,
|
|
405
|
+
)
|
|
406
|
+
sys.exit(-1)
|
|
407
|
+
|
|
408
|
+
|
|
409
|
+
cli.add_command(compare_remote)
|
|
410
|
+
|
|
411
|
+
|
|
412
|
+
if __name__ == "__main__":
|
|
413
|
+
cli()
|