blob: ea5224237a44dcd22913fe6560ba673854068afb [file] [log] [blame]
#!/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 is not None:
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)