forked from efabless/OpenLane
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathverify_versions.py
249 lines (219 loc) · 9.51 KB
/
verify_versions.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Copyright 2021 Efabless Corporation
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import os
import re
import io
import sys
import json
import pathlib
import traceback
from os.path import dirname, abspath, join
from typing import Optional
sys.path.append(os.path.dirname(__file__))
import includedyaml as yaml # noqa: E402
openlane_dir = abspath(dirname(dirname(__file__)))
def verify_versions(
no_pdks: bool = False,
no_tools: bool = False,
report_file: io.TextIOBase = sys.stderr,
pdk: Optional[str] = os.getenv("PDK"),
):
# 1. Load Current Flow Script Manifest
manifest = None
try:
flow_script_manifest_path = join(
openlane_dir, "dependencies", "tool_metadata.yml"
)
manifest = yaml.safe_load(open(flow_script_manifest_path))
except FileNotFoundError:
raise Exception(
"Flow script tool manifest not found. This is a fatal error."
) # Is this even possible?
manifest_dict = {element["name"]: element for element in manifest}
mismatches = False
manifest_names_by_SOURCES_name = {
"open_pdks": "open_pdks",
"skywater": "sky130",
"magic": "magic",
}
pdk_manifest_names = set(manifest_names_by_SOURCES_name.values())
if not no_pdks:
try:
# 2. Check if the PDK is compatible with Flow Scripts
pdk_root = os.getenv("PDK_ROOT")
if not os.getenv("PDK_ROOT"):
pdk_root = join(openlane_dir, "pdks")
if pdk.startswith("sky130"):
pdk_dir = join(pdk_root, pdk)
if not pathlib.Path(pdk_dir).is_dir():
raise Exception(f"{pdk_dir} not found.")
tool_versions = []
sources_file = join(pdk_dir, "SOURCES")
config_file = join(pdk_dir, ".config", "nodeinfo.json")
if os.path.isfile(sources_file):
sources_str = None
try:
sources_str = open(sources_file).read()
except FileNotFoundError:
raise Exception(
f"Could not find SOURCES file for the installed {pdk} PDK."
)
sources_str = sources_str.strip()
# Format: {tool} {commit}
#
# This regex also handles an issue where older versions used the
# non-standard echo -ne command, where the format is -ne {tool}\n{commit}\n.
#
name_rx = re.compile(
r"(?:\-ne\s+)?([\w\-]+)\s+([A-Fa-f0-9]+)", re.MULTILINE
)
for tool_match in name_rx.finditer(sources_str):
name = tool_match[1]
commit = tool_match[2]
manifest_name = manifest_names_by_SOURCES_name.get(name)
if manifest_name is None:
continue
tool_versions.append((manifest_name, commit))
elif os.path.isfile(config_file):
config_str = open(config_file).read()
try:
config = json.loads(config_str)
commit_set = config["commit"]
if type(commit_set) == str:
tool_versions.append(("open_pdks", commit_set))
else:
for key, value in commit_set.items():
# Handle bug in some older versions of opdks where the magic commit field is empty.
if value.strip() == "":
continue
tool_versions.append((key, value))
except json.decoder.JSONDecodeError:
raise Exception("Malformed .config/nodeinfo.json.")
else:
raise Exception(
"Neither SOURCES nor .config/nodeinfo.json exist in the PDK."
)
for name, commit in tool_versions:
manifest_commit = manifest_dict[name]["commit"]
if commit != manifest_commit:
mismatches = True
print(
f"The version of {name} used in building the PDK does not match the version OpenLane was tested on (installed: {commit}, tested: {manifest_commit})",
file=report_file,
)
print(
"This may introduce some issues. You may want to re-install the PDK by invoking `make pdk`.",
file=report_file,
)
pdk_manifest_names.add(name)
except Exception as e:
print(e, file=report_file)
print(traceback.format_exc(), file=report_file)
raise Exception("Failed to compare PDKs.")
if not no_tools:
installed = os.getenv("OPENLANE_LOCAL_INSTALL") == "1"
environment_manifest = None
if installed:
# 3a. Compare with installed versions
installed_versions_path = join(
os.environ["OL_INSTALL_DIR"], "build", "versions"
)
environment_manifest = []
for tool in os.listdir(installed_versions_path):
protocol, url, commit = (
open(join(installed_versions_path, tool)).read().split(":")
)
repo = f"{protocol}:{url}"
environment_manifest.append(
{"name": tool, "repo": repo, "commit": commit}
)
else:
# 3b. Compare Container And Installation Manifests
try:
container_manifest_path = join("/", "tool_metadata.yml")
environment_manifest = yaml.safe_load(open(container_manifest_path))
except FileNotFoundError:
raise Exception(
"Container manifest not found. What this likely means is that the container is severely out of date."
)
tool_set_flow = (
set([element["name"] for element in manifest]) - pdk_manifest_names
)
tool_set_container = (
set([element["name"] for element in environment_manifest])
- pdk_manifest_names
)
unmatched_tools_flow = tool_set_flow - tool_set_container
for tool in unmatched_tools_flow:
tool_object = manifest_dict[tool]
if (
tool_object.get("in_container") is not None
and not tool_object["in_container"]
):
continue
if (
installed
and tool_object.get("in_install") is not None
and not tool_object["in_install"]
):
continue
print(
f"Tool {tool} is required by the flow scripts being used, but appears to not be installed in the environment.",
file=report_file,
)
mismatches = True
unmatched_tools_container = tool_set_container - tool_set_flow
for tool in unmatched_tools_container:
print(
f"Tool {tool} is installed in the environment, but has no corresponding entry in the flow scripts.",
file=report_file,
)
mismatches = True
for tool in environment_manifest:
if tool["name"] in pdk_manifest_names:
continue # PDK Stuff Already Checked
flow_script_counterpart = manifest_dict.get(tool["name"])
if flow_script_counterpart is None:
continue
container_commit = tool["commit"]
flow_script_commit = flow_script_counterpart["commit"]
if container_commit != flow_script_commit:
print(
f"The version of {tool['name']} installed in the environment does not match the one required by the OpenLane flow scripts (installed: {container_commit}, expected: {flow_script_commit})",
file=report_file,
)
mismatches = True
return mismatches
if __name__ == "__main__":
try:
no_tools = False
no_pdks = False
mismatches = os.getenv("TEST_MISMATCHES") or "all"
if mismatches == "none":
no_tools = True
no_pdks = True
elif mismatches == "pdk":
no_tools = True
elif mismatches == "tools":
no_pdks = True
elif mismatches != "all":
print(f"Unknown mismatch test '{mismatches}'.", file=sys.stderr)
sys.exit(-1)
mismatches = verify_versions(no_tools=no_tools, no_pdks=no_pdks)
sys.exit(os.EX_CONFIG if mismatches else os.EX_OK)
except Exception as e:
print(f"{e}")
sys.exit(os.EX_UNAVAILABLE)