summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJack Rosenthal <jrosenth@chromium.org>2022-01-11 21:37:15 -0700
committerCommit Bot <commit-bot@chromium.org>2022-01-12 19:31:47 +0000
commit24fd126ada05966fdb4a6100a0d6cca453317ad5 (patch)
treed87b42cb22308fdbd170577d1205e6f847c3a3b8
parent2aeac4973de8c4965dfced77d9c3be5325675337 (diff)
downloadchrome-ec-24fd126ada05966fdb4a6100a0d6cca453317ad5.tar.gz
zephyr: zmake: Add a README.md file generated from command structure
Generate a readme file from the command structure and arguments. This file gets checked that it matches the generated contents in the commit queue using a new command "zmake generate-readme --diff". Right now this file only has the commands, but could possibly be extended in the future, for example, to document `BUILD.py` functions. BUG=b:180609783 BRANCH=none TEST=View readme in gitiles TEST=Added unit tests Signed-off-by: Jack Rosenthal <jrosenth@chromium.org> Change-Id: Ie8ed39b30ce2a58c91b2cf8e48b0426fb9b4f2e5 Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/ec/+/3382480 Reviewed-by: Jeremy Bettis <jbettis@chromium.org>
-rw-r--r--zephyr/zmake/README.md144
-rwxr-xr-xzephyr/zmake/run_tests.sh3
-rw-r--r--zephyr/zmake/tests/test_generate_readme.py55
-rw-r--r--zephyr/zmake/zmake/__main__.py25
-rw-r--r--zephyr/zmake/zmake/generate_readme.py123
-rw-r--r--zephyr/zmake/zmake/zmake.py37
6 files changed, 384 insertions, 3 deletions
diff --git a/zephyr/zmake/README.md b/zephyr/zmake/README.md
new file mode 100644
index 0000000000..986cc8a506
--- /dev/null
+++ b/zephyr/zmake/README.md
@@ -0,0 +1,144 @@
+# Zmake
+
+<!-- Auto-generated contents! Run "zmake generate-readme" to update. -->
+
+[TOC]
+
+## Usage
+
+**Usage:** `zmake [-h] [--checkout CHECKOUT] [-D] [-j JOBS] [-l {DEBUG,INFO,WARNING,ERROR,CRITICAL}] [-L] [--log-label] [--modules-dir MODULES_DIR] [--zephyr-base ZEPHYR_BASE] subcommand ...`
+
+Chromium OS's meta-build tool for Zephyr
+
+#### Positional Arguments
+
+| | |
+|---|---|
+| `subcommand` | Subcommand to run |
+
+#### Optional Arguments
+
+| | |
+|---|---|
+| `-h`, `--help` | show this help message and exit |
+| `--checkout CHECKOUT` | Path to ChromiumOS checkout |
+| `-D`, `--debug` | Turn on debug features (e.g., stack trace, verbose logging) |
+| `-j JOBS`, `--jobs JOBS` | Degree of multiprogramming to use |
+| `-l LOG_LEVEL`, `--log-level LOG_LEVEL` | Set the logging level (default=INFO) |
+| `-L`, `--no-log-label` | Turn off logging labels |
+| `--log-label` | Turn on logging labels |
+| `--modules-dir MODULES_DIR` | The path to a directory containing all modules needed. If unspecified, zmake will assume you have a Chrome OS checkout and try locating them in the checkout. |
+| `--zephyr-base ZEPHYR_BASE` | Path to Zephyr OS repository |
+
+## Subcommands
+
+### zmake configure
+
+**Usage:** `zmake configure [-h] [-t TOOLCHAIN] [--bringup] [--allow-warnings] [-B BUILD_DIR] [-b] [--test] project_name_or_dir [-c]`
+
+#### Positional Arguments
+
+| | |
+|---|---|
+| `project_name_or_dir` | Path to the project to build |
+
+#### Optional Arguments
+
+| | |
+|---|---|
+| `-h`, `--help` | show this help message and exit |
+| `-t TOOLCHAIN`, `--toolchain TOOLCHAIN` | Name of toolchain to use |
+| `--bringup` | Enable bringup debugging features |
+| `--allow-warnings` | Do not treat warnings as errors |
+| `-B BUILD_DIR`, `--build-dir BUILD_DIR` | Build directory |
+| `-b`, `--build` | Run the build after configuration |
+| `--test` | Test the .elf file after configuration |
+| `-c`, `--coverage` | Enable CONFIG_COVERAGE Kconfig. |
+
+### zmake build
+
+**Usage:** `zmake build [-h] build_dir [-w]`
+
+#### Positional Arguments
+
+| | |
+|---|---|
+| `build_dir` | The build directory used during configuration |
+
+#### Optional Arguments
+
+| | |
+|---|---|
+| `-h`, `--help` | show this help message and exit |
+| `-w`, `--fail-on-warnings` | Exit with code 2 if warnings are detected |
+
+### zmake list-projects
+
+**Usage:** `zmake list-projects [-h] [--format FORMAT] [search_dir]`
+
+#### Positional Arguments
+
+| | |
+|---|---|
+| `search_dir` | Optional directory to search for BUILD.py files in. |
+
+#### Optional Arguments
+
+| | |
+|---|---|
+| `-h`, `--help` | show this help message and exit |
+| `--format FORMAT` | Output format to print projects (str.format(config=project.config) is called on this for each project). |
+
+### zmake test
+
+**Usage:** `zmake test [-h] build_dir`
+
+#### Positional Arguments
+
+| | |
+|---|---|
+| `build_dir` | The build directory used during configuration |
+
+#### Optional Arguments
+
+| | |
+|---|---|
+| `-h`, `--help` | show this help message and exit |
+
+### zmake testall
+
+**Usage:** `zmake testall [-h]`
+
+#### Optional Arguments
+
+| | |
+|---|---|
+| `-h`, `--help` | show this help message and exit |
+
+### zmake coverage
+
+**Usage:** `zmake coverage [-h] build_dir`
+
+#### Positional Arguments
+
+| | |
+|---|---|
+| `build_dir` | The build directory used during configuration |
+
+#### Optional Arguments
+
+| | |
+|---|---|
+| `-h`, `--help` | show this help message and exit |
+
+### zmake generate-readme
+
+**Usage:** `zmake generate-readme [-h] [-o OUTPUT_FILE] [--diff]`
+
+#### Optional Arguments
+
+| | |
+|---|---|
+| `-h`, `--help` | show this help message and exit |
+| `-o OUTPUT_FILE`, `--output-file OUTPUT_FILE` | File to write to. It will only be written if changed. |
+| `--diff` | If specified, diff the README with the expected contents instead of writing out. |
diff --git a/zephyr/zmake/run_tests.sh b/zephyr/zmake/run_tests.sh
index 60e93cdf1a..4796704440 100755
--- a/zephyr/zmake/run_tests.sh
+++ b/zephyr/zmake/run_tests.sh
@@ -33,3 +33,6 @@ black --check --diff .
# Check flake8 reports no issues.
flake8 .
+
+# Check auto-generated README.md is as expected.
+python -m zmake generate-readme --diff
diff --git a/zephyr/zmake/tests/test_generate_readme.py b/zephyr/zmake/tests/test_generate_readme.py
new file mode 100644
index 0000000000..2a05de9cce
--- /dev/null
+++ b/zephyr/zmake/tests/test_generate_readme.py
@@ -0,0 +1,55 @@
+# Copyright 2022 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import pytest
+
+import zmake.generate_readme as gen_readme
+import zmake.zmake as zm
+
+
+def test_generate_readme_contents():
+ readme = gen_readme.generate_readme()
+
+ # Look for a string we know should appear in the README.
+ assert "### zmake testall\n" in readme
+
+
+@pytest.mark.parametrize(
+ ["expected_contents", "actual_contents", "return_code"],
+ [
+ ("abc\ndef\nghi\n", "abc\nghi\n", 1),
+ ("abc\ndef\nghi\n", "abc\ndef\nghi\n", 0),
+ ("abc\ndef\nghi\n", None, 1),
+ ],
+)
+def test_generate_readme_diff(
+ monkeypatch, tmp_path, expected_contents, actual_contents, return_code
+):
+ def generate_readme():
+ return expected_contents
+
+ monkeypatch.setattr(gen_readme, "generate_readme", generate_readme)
+
+ readme_file = tmp_path / "README.md"
+ if actual_contents is not None:
+ readme_file.write_text(actual_contents)
+
+ zmk = zm.Zmake()
+ assert zmk.generate_readme(readme_file, diff=True) == return_code
+
+
+@pytest.mark.parametrize("exist", [False, True])
+def test_generate_readme_file(monkeypatch, tmp_path, exist):
+ def generate_readme():
+ return "hello\n"
+
+ monkeypatch.setattr(gen_readme, "generate_readme", generate_readme)
+
+ readme_file = tmp_path / "README.md"
+ if exist:
+ readme_file.write_text("some existing contents\n")
+
+ zmk = zm.Zmake()
+ assert zmk.generate_readme(readme_file) == 0
+ assert readme_file.read_text() == "hello\n"
diff --git a/zephyr/zmake/zmake/__main__.py b/zephyr/zmake/zmake/__main__.py
index 136831a319..28ec232148 100644
--- a/zephyr/zmake/zmake/__main__.py
+++ b/zephyr/zmake/zmake/__main__.py
@@ -97,7 +97,7 @@ def get_argparser():
"""Get the argument parser.
Returns:
- An argparse.ArgumentParser.
+ A two tuple, the argument parser, and the subcommand action.
"""
parser = argparse.ArgumentParser(
prog="zmake",
@@ -269,7 +269,26 @@ def get_argparser():
help="The build directory used during configuration",
)
- return parser
+ generate_readme = sub.add_parser(
+ "generate-readme",
+ help="Update the auto-generated markdown documentation",
+ )
+ generate_readme.add_argument(
+ "-o",
+ "--output-file",
+ default=pathlib.Path(__file__).parent.parent / "README.md",
+ help="File to write to. It will only be written if changed.",
+ )
+ generate_readme.add_argument(
+ "--diff",
+ action="store_true",
+ help=(
+ "If specified, diff the README with the expected contents instead of "
+ "writing out."
+ ),
+ )
+
+ return parser, sub
def main(argv=None):
@@ -286,7 +305,7 @@ def main(argv=None):
maybe_reexec(argv)
- parser = get_argparser()
+ parser, _ = get_argparser()
opts = parser.parse_args(argv)
# Default logging
diff --git a/zephyr/zmake/zmake/generate_readme.py b/zephyr/zmake/zmake/generate_readme.py
new file mode 100644
index 0000000000..d21fd19f7c
--- /dev/null
+++ b/zephyr/zmake/zmake/generate_readme.py
@@ -0,0 +1,123 @@
+# Copyright 2022 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+"""Code for auto-generating README.md."""
+
+import argparse
+import io
+
+
+class MarkdownHelpFormatter(argparse.HelpFormatter):
+ """Callbacks to format help output as Markdown."""
+
+ def __init__(self, prog):
+ self._prog = prog
+ self._section_title = None
+ self._section_contents = []
+ self._paragraphs = []
+ super().__init__(prog=prog)
+
+ def add_text(self, text):
+ if text and text is not argparse.SUPPRESS:
+ lst = self._paragraphs
+ if self._section_title:
+ lst = self._section_contents
+ lst.append(text)
+
+ def start_section(self, title):
+ self._section_title = title.title()
+ self._section_contents = []
+
+ def end_section(self):
+ if self._section_contents:
+ self._paragraphs.append(f"#### {self._section_title}")
+ self._paragraphs.extend(self._section_contents)
+ self._section_title = None
+
+ def add_usage(self, usage, actions, groups):
+ if not usage:
+ usage = self._prog
+ self.add_text(
+ f"**Usage:** `{usage} {self._format_actions_usage(actions, groups)}`"
+ )
+
+ def add_arguments(self, actions):
+ def _get_metavar(action):
+ return action.metavar or action.dest
+
+ def _format_invocation(action):
+ if action.option_strings:
+ parts = []
+ for option_string in action.option_strings:
+ if action.nargs == 0:
+ parts.append(option_string)
+ else:
+ parts.append(f"{option_string} {_get_metavar(action).upper()}")
+ return ", ".join(f"`{part}`" for part in parts)
+ else:
+ return f"`{_get_metavar(action)}`"
+
+ def _get_table_line(action):
+ return f"| {_format_invocation(action)} | {action.help} |"
+
+ table_lines = [
+ "| | |",
+ "|---|---|",
+ *(
+ _get_table_line(action)
+ for action in actions
+ if action.help is not argparse.SUPPRESS
+ ),
+ ]
+
+ # Don't want a table with no rows.
+ if len(table_lines) > 2:
+ self.add_text("\n".join(table_lines))
+
+ def format_help(self):
+ return "\n\n".join(self._paragraphs)
+
+
+def generate_readme():
+ """Generate the README.md file.
+
+ Returns:
+ A string with the README contents.
+ """
+ # Deferred import position to avoid circular dependency.
+ # Normally, this would not be required, since we don't use from
+ # imports. But runpy's import machinery essentially does the
+ # equivalent of a from import on __main__.py.
+ import zmake.__main__
+
+ output = io.StringIO()
+ parser, sub_action = zmake.__main__.get_argparser()
+
+ def _append(*args, **kwargs):
+ kwargs.setdefault("file", output)
+ print(*args, **kwargs)
+
+ def _append_argparse_help(parser):
+ parser.formatter_class = MarkdownHelpFormatter
+ _append(parser.format_help())
+
+ _append("# Zmake")
+ _append()
+ _append('<!-- Auto-generated contents! Run "zmake generate-readme" to update. -->')
+ _append()
+ _append("[TOC]")
+ _append()
+ _append("## Usage")
+ _append()
+ _append_argparse_help(parser)
+ _append()
+ _append("## Subcommands")
+
+ for sub_name, sub_parser in sub_action.choices.items():
+ _append()
+ _append(f"### zmake {sub_name}")
+ _append()
+ _append_argparse_help(sub_parser)
+
+ return output.getvalue()
diff --git a/zephyr/zmake/zmake/zmake.py b/zephyr/zmake/zmake/zmake.py
index a906181c47..abc41de855 100644
--- a/zephyr/zmake/zmake/zmake.py
+++ b/zephyr/zmake/zmake/zmake.py
@@ -3,6 +3,7 @@
# found in the LICENSE file.
"""Module encapsulating Zmake wrapper object."""
+import difflib
import logging
import os
import pathlib
@@ -12,6 +13,7 @@ import subprocess
import tempfile
import zmake.build_config
+import zmake.generate_readme
import zmake.jobserver
import zmake.modules
import zmake.multiproc
@@ -828,3 +830,38 @@ class Zmake:
print(format.format(config=project.config), end="")
return 0
+
+ def generate_readme(self, output_file, diff=False):
+ """Re-generate the auto-generated README file.
+
+ Args:
+ output_file: A pathlib.Path; to be written only if changed.
+ diff: Instead of writing out, report the diff.
+ """
+ expected_contents = zmake.generate_readme.generate_readme()
+
+ if output_file.is_file():
+ current_contents = output_file.read_text()
+ if expected_contents == current_contents:
+ return 0
+ if diff:
+ self.logger.error(
+ "The auto-generated README.md differs from the expected contents:"
+ )
+ for line in difflib.unified_diff(
+ current_contents.splitlines(keepends=True),
+ expected_contents.splitlines(keepends=True),
+ str(output_file),
+ ):
+ self.logger.error(line.rstrip())
+ self.logger.error('Run "zmake generate-readme" to fix this.')
+ return 1
+
+ if diff:
+ self.logger.error(
+ 'The README.md file does not exist. Run "zmake generate-readme".'
+ )
+ return 1
+
+ output_file.write_text(expected_contents)
+ return 0