Add 'pw doctor'.
Add 'pw doctor' command to check environment is correctly set up
for pigweed. For now contains minimal checks.
Change-Id: Icb132c89bb272b52c57dae0451b24966b48d4e90
Bug: 28
diff --git a/docs/BUILD.gn b/docs/BUILD.gn
index 0003609..d4c3767 100644
--- a/docs/BUILD.gn
+++ b/docs/BUILD.gn
@@ -45,6 +45,7 @@
"$dir_pw_cpu_exception:docs",
"$dir_pw_cpu_exception_armv7m:docs",
"$dir_pw_docgen:docs",
+ "$dir_pw_doctor:docs",
"$dir_pw_dumb_io:docs",
"$dir_pw_dumb_io_baremetal_stm32f429:docs",
"$dir_pw_dumb_io_stdio:docs",
diff --git a/modules.gni b/modules.gni
index fe04edf..3d3e534 100644
--- a/modules.gni
+++ b/modules.gni
@@ -27,6 +27,7 @@
dir_pw_cpu_exception = "$dir_pigweed/pw_cpu_exception"
dir_pw_cpu_exception_armv7m = "$dir_pigweed/pw_cpu_exception_armv7m"
dir_pw_docgen = "$dir_pigweed/pw_docgen"
+dir_pw_doctor = "$dir_pigweed/pw_doctor"
dir_pw_dumb_io = "$dir_pigweed/pw_dumb_io"
dir_pw_dumb_io_baremetal_stm32f429 =
"$dir_pigweed/pw_dumb_io_baremetal_stm32f429"
diff --git a/pw_doctor/BUILD b/pw_doctor/BUILD
new file mode 100644
index 0000000..85a5ca0
--- /dev/null
+++ b/pw_doctor/BUILD
@@ -0,0 +1,17 @@
+# Copyright 2019 The Pigweed Authors
+#
+# 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.
+
+package(default_visibility = ["//visibility:public"])
+
+licenses(["notice"]) # Apache License 2.0
diff --git a/pw_doctor/BUILD.gn b/pw_doctor/BUILD.gn
new file mode 100644
index 0000000..9eed4a2
--- /dev/null
+++ b/pw_doctor/BUILD.gn
@@ -0,0 +1,21 @@
+# Copyright 2019 The Pigweed Authors
+#
+# 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.
+
+import("$dir_pw_docgen/docs.gni")
+
+pw_doc_group("docs") {
+ sources = [
+ "docs.rst",
+ ]
+}
diff --git a/pw_doctor/README.md b/pw_doctor/README.md
new file mode 100644
index 0000000..f09b31c
--- /dev/null
+++ b/pw_doctor/README.md
@@ -0,0 +1 @@
+Check the environment for compatibility with Pigweed.
diff --git a/pw_doctor/docs.rst b/pw_doctor/docs.rst
new file mode 100644
index 0000000..15f0eb3
--- /dev/null
+++ b/pw_doctor/docs.rst
@@ -0,0 +1,8 @@
+.. _chapter-pw-doctor:
+
+---------
+pw_doctor
+---------
+``pw doctor`` confirms the environment is set up correctly. With ``--strict``
+it checks that things exactly match what is expected and it checks that things
+look compatible without.
diff --git a/pw_doctor/py/pw_doctor/__init__.py b/pw_doctor/py/pw_doctor/__init__.py
new file mode 100644
index 0000000..7f440f8
--- /dev/null
+++ b/pw_doctor/py/pw_doctor/__init__.py
@@ -0,0 +1,17 @@
+# Copyright 2019 The Pigweed Authors
+#
+# 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.
+"""Pigweed doctor module."""
+
+# Import pw_plugin to register the pw_cli plugin.
+import pw_doctor.pw_plugin
diff --git a/pw_doctor/py/pw_doctor/doctor.py b/pw_doctor/py/pw_doctor/doctor.py
new file mode 100755
index 0000000..266352b
--- /dev/null
+++ b/pw_doctor/py/pw_doctor/doctor.py
@@ -0,0 +1,183 @@
+#!/usr/bin/env python3
+# Copyright 2019 The Pigweed Authors
+#
+# 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.
+"""Checks if the environment is set up correctly for Pigweed."""
+
+import argparse
+import logging
+import json
+import os
+import shutil
+import subprocess
+import sys
+import tempfile
+
+
+def call_stdout(*args, **kwargs):
+ kwargs.update(stdout=subprocess.PIPE)
+ proc = subprocess.run(*args, **kwargs)
+ return proc.stdout.decode('utf-8')
+
+
+class _Fatal(Exception):
+ pass
+
+
+class DoctorContext:
+ """Base class for other checks."""
+ def __init__(self, *, strict=False, log=None):
+ self.name = self.__class__.__name__
+ self._strict = strict
+ self._log = log or logging.getLogger(__name__)
+ self.failures = set()
+ self.curr_checker = None
+
+ def fatal(self, fmt, *args, **kwargs):
+ """Same as error() but terminates the checkearly."""
+ self.error(fmt, *args, **kwargs)
+ raise _Fatal()
+
+ def error(self, fmt, *args, **kwargs):
+ self._log.error(fmt, *args, **kwargs)
+ self.failures.add(self.curr_checker)
+
+ def warning(self, fmt, *args, **kwargs):
+ if self._strict:
+ self.error(fmt, *args, **kwargs)
+ else:
+ self._log.warning(fmt, *args, **kwargs)
+
+ def info(self, fmt, *args, **kwargs):
+ self._log.info(fmt, *args, **kwargs)
+
+ def debug(self, fmt, *args, **kwargs):
+ self._log.debug(fmt, *args, **kwargs)
+
+
+def register_into(dest):
+ def decorate(func):
+ dest.append(func)
+ return func
+
+ return decorate
+
+
+CHECKS = []
+
+
+@register_into(CHECKS)
+def pw_root(ctx: DoctorContext):
+ """Check that environment variable PW_ROOT is set and makes sense."""
+ root = os.environ.get('PW_ROOT', None)
+
+ if root is None:
+ ctx.fatal('PW_ROOT not set')
+
+ git_root = call_stdout(['git', 'rev-parse', '--show-toplevel'],
+ cwd=root).strip()
+ if root != git_root:
+ ctx.error('PW_ROOT (%s) != `git rev-parse --show-toplevel` (%s)', root,
+ git_root)
+
+
+@register_into(CHECKS)
+def python_version(ctx: DoctorContext):
+ """Check the Python version is correct."""
+ actual = sys.version_info
+ expected = (3, 8)
+ if actual[0:2] < expected or actual[0] != expected[0]:
+ ctx.error('Python %d.%d.x required, got Python %d.%d.%d', *expected,
+ *actual[0:3])
+ elif actual[0:2] > expected:
+ ctx.warning('Python %d.%d.x required, got Python %d.%d.%d', *expected,
+ *actual[0:3])
+
+
+@register_into(CHECKS)
+def cipd(ctx: DoctorContext):
+ """Check cipd is set up correctly and in use."""
+ cipd_path = 'pigweed'
+
+ temp = tempfile.NamedTemporaryFile(prefix='cipd', delete=False)
+ subprocess.run(['cipd', 'acl-check', '-json-output', temp.name, cipd_path],
+ stdout=subprocess.PIPE)
+ if not json.load(temp)['result']:
+ ctx.fatal(
+ "can't access %s CIPD directory, have you run "
+ "'cipd auth-login'?", cipd_path)
+
+ commands_expected_from_cipd = [
+ 'arm-none-eabi-gcc',
+ 'bazel',
+ 'bloaty',
+ 'clang++',
+ 'gn',
+ 'ninja',
+ 'protoc',
+ ]
+
+ for command in commands_expected_from_cipd:
+ path = shutil.which(command)
+ if 'cipd' not in path:
+ ctx.warning('not using %s from cipd, got %s', command, path)
+
+
+def main(strict=False, checks=None):
+ """Run all the Check subclasses defined in this file."""
+
+ ctx = DoctorContext(strict=strict)
+
+ if checks is None:
+ checks = tuple(CHECKS)
+
+ ctx.info('Doctor running %d checks...', len(checks))
+ for check in checks:
+ try:
+ ctx.info('Running %s...', check.__name__)
+ ctx.curr_checker = check.__name__
+ check(ctx)
+
+ except _Fatal:
+ pass
+
+ finally:
+ ctx.curr_checker = None
+
+ if ctx.failures:
+ ctx.info('Failed checks: %s', ', '.join(ctx.failures))
+ else:
+ ctx.info('All checks passed!')
+ return len(ctx.failures)
+
+
+def argument_parser(
+ parser: argparse.ArgumentParser = None) -> argparse.ArgumentParser:
+ """Create argument parser."""
+
+ if parser is None:
+ parser = argparse.ArgumentParser(description=__doc__)
+
+ parser.add_argument(
+ '--strict',
+ action='store_true',
+ help='Run additional checks.',
+ )
+
+ return parser
+
+
+if __name__ == '__main__':
+ # By default, display log messages like a simple print statement.
+ logging.basicConfig(format='%(message)s', level=logging.INFO)
+ sys.exit(main(**vars(argument_parser().parse_args())))
diff --git a/pw_doctor/py/pw_doctor/pw_plugin.py b/pw_doctor/py/pw_doctor/pw_plugin.py
new file mode 100644
index 0000000..5893033
--- /dev/null
+++ b/pw_doctor/py/pw_doctor/pw_plugin.py
@@ -0,0 +1,28 @@
+# Copyright 2019 The Pigweed Authors
+#
+# 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.
+"""Registers the check plugin for Pigweed."""
+
+try:
+ import pw_cli.plugins
+ from pw_doctor import doctor
+
+ pw_cli.plugins.register(
+ 'doctor',
+ doctor.main,
+ doctor.__doc__.splitlines()[0].rstrip('.'),
+ doctor.argument_parser,
+ )
+
+except ImportError:
+ pass
diff --git a/pw_doctor/py/setup.py b/pw_doctor/py/setup.py
new file mode 100644
index 0000000..b870a42
--- /dev/null
+++ b/pw_doctor/py/setup.py
@@ -0,0 +1,33 @@
+# Copyright 2019 The Pigweed Authors
+#
+# 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.
+"""The pw_doctor package."""
+
+import unittest
+import setuptools
+
+
+def test_suite():
+ """Test suite for pw_doctor module."""
+ return unittest.TestLoader().discover('./', pattern='*_test.py')
+
+
+setuptools.setup(
+ name='pw_doctor',
+ version='0.0.1',
+ author='Pigweed Authors',
+ author_email='pigweed-developers@googlegroups.com',
+ description='Environment check script for Pigweed',
+ packages=setuptools.find_packages(),
+ test_suite='setup.test_suite',
+)