mirror of
https://github.com/efabless/openlane2.git
synced 2026-05-30 00:03:47 +08:00
## CLI
* Overhauled how the PDK commandline options work, using a decorator instead of doing everything in a callback
* `--smoke-test/--run-example` are now no longer callbacks, and `--run-example` now supports more options (e.g. another PDK, another flow, etc.)
## Steps
* Created `OpenROAD.DEFtoODB`
* Useful for custom flows, where the DEF is modified but the ODB needs to be updated to reflect these modifications
## Flows
* `VHDLClassic` is now `Classic` with appropriate Substitutions, see Misc.
## Misc Enhancements/Updates
* `SequentialFlow`
* Substitutions can now
* be done at the class level by assigning to `Substitutions`
* be done in `config.json` files using a dictionary in the field `.meta.substituting_steps`
* emplace steps before or after existing steps, e.g. `+STEP`, `-STEP`
* Step names for `from`, `to`, `skip` and `only` are now fuzzy-matched using `rapidfuzz` to give suggestions in error messages
* If the environment variable `_i_want_openlane_to_fuzzy_match_steps_and_im_willing_to_accept_the_risks` is set to `1`, the suggestions are used automatically (not recommended)
* Gating config vars are now simply removed if they do not target a valid step (so removed steps in a substituted flow do not cause a FlowException)
## Documentation
* Updated the architecture document to reflect changes and clarify some elements.
* Updated Usage/Writing Custom Flows to document step substitution
* Created a new document on writing plugins
## Tool Updates
* Upgrade `nix-eda`
* `forAllSystems` now composes overlays for nixpkgs based on the `withInputs` field, allowing for easier overriding
* `nixpkgs` -> 24.05
* `klayout` -> `0.29.1`
* `ioplace_parser` -> `0.3.0`
* Python build tool changed from `setuptools` to `poetry`, which properly verifies that all version ranges are within constraints
* Updated wrong Python package version ranges that all happen to work
* Nix devshells now use [numtide/devshell](https://github.com/numtide/devshell), which creates an executable to enter the environment, allowing for easy repacking
214 lines
6.4 KiB
Python
214 lines
6.4 KiB
Python
#!/usr/bin/env python3
|
|
# -*- coding: utf-8 -*-
|
|
# Copyright 2024 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
|
|
#
|
|
# https://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.
|
|
#
|
|
# SPDX-License-Identifier: Apache-2.0
|
|
|
|
import os
|
|
import inspect
|
|
import traceback
|
|
import importlib
|
|
from types import ModuleType
|
|
from typing import List, Tuple, Dict, Optional, Set
|
|
|
|
import jinja2
|
|
from sphinx.config import Config
|
|
from sphinx.application import Sphinx
|
|
from docstring_parser import parse, Docstring
|
|
|
|
from util import debug, rimraf, mkdirp
|
|
|
|
|
|
def setup(app: Sphinx):
|
|
app.connect("config-inited", generate_module_docs)
|
|
app.add_config_value("generate_module_autodocs", [], True)
|
|
return {"version": "1.0"}
|
|
|
|
|
|
def generate_docs_for_module(
|
|
processed: Set[ModuleType],
|
|
build_path: str,
|
|
templates: Dict[str, jinja2.Template],
|
|
top_level_name: Optional[str],
|
|
top_level_dir: Optional[str],
|
|
module: ModuleType,
|
|
docstring: Docstring,
|
|
):
|
|
if module in processed:
|
|
return
|
|
debug(f"Processing {module.__name__}…")
|
|
processed.add(module)
|
|
|
|
template = templates["module"]
|
|
|
|
assert module.__file__ is not None
|
|
|
|
full_name = module.__name__
|
|
module_path = full_name.split(".")
|
|
module_parents = ".".join(module_path[:-1])
|
|
module_name = module_path[-1]
|
|
|
|
module_file = os.path.abspath(inspect.getfile(module))
|
|
|
|
top_level_name = top_level_name or full_name
|
|
top_level_dir = top_level_dir or os.path.dirname(module_file)
|
|
|
|
module_file_rel = os.path.relpath(module_file, top_level_dir)
|
|
|
|
if module_file_rel.endswith("__init__.py"):
|
|
module_file_rel = module_file_rel[: -len("__init__.py")]
|
|
elif module_file_rel.endswith(".py"):
|
|
module_file_rel = module_file_rel[:-3]
|
|
|
|
module_doc_dir = os.path.abspath(os.path.join(build_path, module_file_rel))
|
|
module_doc_path = os.path.join(module_doc_dir, "index")
|
|
module_rst_path = os.path.join(module_doc_dir, "index.rst")
|
|
|
|
submodules = []
|
|
# Process Submodules
|
|
for key in dir(module):
|
|
attr = getattr(module, key)
|
|
try:
|
|
if attr in processed:
|
|
continue
|
|
object_full_name = attr.__name__
|
|
object_path = object_full_name.split(".")
|
|
object_name = object_path[-1]
|
|
object_file = os.path.abspath(inspect.getfile(attr))
|
|
except (TypeError, AttributeError):
|
|
continue
|
|
|
|
if not object_file.startswith(top_level_dir):
|
|
continue
|
|
|
|
docstring_raw = None
|
|
try:
|
|
docstring_raw = getattr(attr, "__doc__")
|
|
except AttributeError:
|
|
pass
|
|
|
|
if docstring_raw is None:
|
|
debug(f"Skipping {object_full_name}: no docstring")
|
|
continue
|
|
|
|
object_docstring = parse(docstring_raw)
|
|
|
|
if inspect.ismodule(attr):
|
|
submodule = attr
|
|
if submodule.__doc__ is None:
|
|
continue
|
|
|
|
submodule_doc_path = generate_docs_for_module(
|
|
processed=processed,
|
|
module=submodule,
|
|
docstring=object_docstring,
|
|
build_path=build_path,
|
|
top_level_name=top_level_name,
|
|
top_level_dir=top_level_dir,
|
|
templates=templates,
|
|
)
|
|
|
|
submodule_relative_path = os.path.relpath(
|
|
submodule_doc_path,
|
|
module_doc_dir,
|
|
)
|
|
|
|
submodules.append(
|
|
(
|
|
object_name,
|
|
object_docstring.short_description,
|
|
submodule_relative_path,
|
|
)
|
|
)
|
|
|
|
short_desc = docstring.short_description
|
|
long_desc = docstring.long_description
|
|
include_imported_members = True
|
|
if "no-imported-members" in long_desc:
|
|
include_imported_members = False
|
|
|
|
# Process Current Module
|
|
kwargs = {
|
|
"parent_name": module_parents,
|
|
"full_name": full_name,
|
|
"module_name": module_name,
|
|
"short_desc": short_desc,
|
|
"long_desc": long_desc,
|
|
"submodules": submodules,
|
|
"include_imported_members": include_imported_members,
|
|
}
|
|
|
|
mkdirp(os.path.dirname(module_rst_path))
|
|
with open(module_rst_path, "w") as f:
|
|
f.write(template.render(**kwargs))
|
|
|
|
return module_doc_path
|
|
|
|
|
|
def generate_module_docs(app: Sphinx, conf: Config):
|
|
try:
|
|
generate_module_autodocs_conf: List[Tuple[str, str]] = (
|
|
conf.generate_module_autodocs
|
|
)
|
|
|
|
conf_py_path: str = conf._raw_config["__file__"]
|
|
doc_root_dir: str = os.path.dirname(conf_py_path)
|
|
|
|
template_relpath: str = conf.templates_path[0]
|
|
all_templates_path = os.path.abspath(template_relpath)
|
|
template_path = os.path.join(all_templates_path, "generate_module_autodocs")
|
|
|
|
lookup = jinja2.FileSystemLoader(searchpath=template_path)
|
|
|
|
# Mako-like environment
|
|
env = jinja2.Environment(
|
|
"<%",
|
|
"%>",
|
|
"${",
|
|
"}",
|
|
"<%doc>",
|
|
"</%doc>",
|
|
"%",
|
|
"##",
|
|
loader=lookup,
|
|
)
|
|
|
|
templates = {k: env.get_template(f"{k}.rst") for k in ["module"]}
|
|
for module_name, build_path in generate_module_autodocs_conf:
|
|
rimraf(build_path)
|
|
|
|
build_path_resolved = os.path.join(doc_root_dir, build_path)
|
|
top_level_module = importlib.import_module(module_name)
|
|
|
|
try:
|
|
docstring_raw = getattr(top_level_module, "__doc__")
|
|
except AttributeError:
|
|
raise ValueError("Top level module lacks a docstring.")
|
|
|
|
docstring = parse(docstring_raw)
|
|
|
|
generate_docs_for_module(
|
|
processed=set(),
|
|
module=top_level_module,
|
|
docstring=docstring,
|
|
build_path=build_path_resolved,
|
|
templates=templates,
|
|
top_level_name=None,
|
|
top_level_dir=None,
|
|
)
|
|
except Exception:
|
|
print(traceback.format_exc())
|
|
exit(-1)
|