west: Add a boards command

Add a new "boards" command that is able to list all the boards in the
upstream tree. There is currently no support for out-of-tree boards.

The command executes cmake to use the built-in CMake script,
boards.cmake, to list the boards, and then stores the information
retrieved and allows the user to present it in a user-definable format.

Fixes https://github.com/zephyrproject-rtos/west/issues/53

Signed-off-by: Carles Cufi <carles.cufi@nordicsemi.no>
diff --git a/doc/guides/west/build-flash-debug.rst b/doc/guides/west/build-flash-debug.rst
index afc6723..b04448e 100644
--- a/doc/guides/west/build-flash-debug.rst
+++ b/doc/guides/west/build-flash-debug.rst
@@ -3,9 +3,9 @@
 Building, Flashing and Debugging
 ################################
 
-West provides 5 commands for building, flashing, and interacting with Zephyr
-programs running on a board: ``build``, ``flash``, ``debug``, ``debugserver``
-and ``attach``.
+Zephyr provides several :ref:`west extension commands <west-extensions>` for
+building, flashing, and interacting with Zephyr programs running on a board:
+``build``, ``flash``, ``debug``, ``debugserver`` and ``attach``.
 
 These use information stored in the CMake cache [#cmakecache]_ to
 flash or attach a debugger to a board supported by Zephyr. The exception is
@@ -61,6 +61,10 @@
   whether ``-b`` is required, just try leaving it out. West will print an
   error if the option is required and was not given.
 
+.. tip::
+  You can use the :ref:`west boards <west-boards>` command to list all
+  supported boards.
+
 Specify the source directory path as the first positional argument::
 
   west build -b <BOARD> path/to/source/directory
diff --git a/doc/guides/west/extensions.rst b/doc/guides/west/extensions.rst
index f770c66..dd35f56 100644
--- a/doc/guides/west/extensions.rst
+++ b/doc/guides/west/extensions.rst
@@ -11,12 +11,14 @@
 own.
 
 Some commands you can run when using west with Zephyr, like the ones used to
-:ref:`build, flash, and debug <west-build-flash-debug>`, are extensions. That's
-why help for them shows up like this in ``west --help``:
+:ref:`build, flash, and debug <west-build-flash-debug>` and the
+:ref:`ones described here <west-zephyr-ext-cmds>` , are extensions. That's why
+help for them shows up like this in ``west --help``:
 
 .. code-block:: none
 
    commands from project at "zephyr":
+     boards:               display information about supported boards
      build:                compile a Zephyr application
      sign:                 sign a Zephyr binary for bootloader chain-loading
      flash:                flash and run a binary on a board
diff --git a/doc/guides/west/index.rst b/doc/guides/west/index.rst
index 91cf5f8..b17931e 100644
--- a/doc/guides/west/index.rst
+++ b/doc/guides/west/index.rst
@@ -38,6 +38,7 @@
    config.rst
    extensions.rst
    build-flash-debug.rst
+   zephyr-cmds.rst
    sign.rst
    why.rst
    without-west.rst
diff --git a/doc/guides/west/zephyr-cmds.rst b/doc/guides/west/zephyr-cmds.rst
new file mode 100644
index 0000000..92f0b6a
--- /dev/null
+++ b/doc/guides/west/zephyr-cmds.rst
@@ -0,0 +1,39 @@
+.. _west-zephyr-ext-cmds:
+
+Additional Zephyr extension commands
+####################################
+
+Aside from the :ref:`build, flash, and debug commands <west-build-flash-debug>`,
+the zephyr tree extends the west command set with additional zephyr-specific
+commands.
+
+.. Add a per-page contents at the top of the page. This page is nested
+   deeply enough that it doesn't have any subheadings in the main nav.
+
+.. only:: html
+
+   .. contents::
+      :local:
+
+.. _west-boards:
+
+Listing boards: ``west boards``
+*******************************
+
+The ``boards`` command can be used to list the boards that are supported by
+Zephyr without having to resort to additional sources of information.
+
+It can be run by typing::
+
+  west boards
+
+This command lists all supported boards in a default format. If you prefer to
+specify the display format yourself you can use the ``--format`` (or ``-f``)
+flag::
+
+  west boards -f "{arch}:{name}"
+
+Additional help about the formatting options can be found by running::
+
+  west boards -h
+
diff --git a/scripts/west-commands.yml b/scripts/west-commands.yml
index e86fe7f..807489b 100644
--- a/scripts/west-commands.yml
+++ b/scripts/west-commands.yml
@@ -1,5 +1,10 @@
 # Keep the help strings in sync with the values in the .py files!
 west-commands:
+  - file: scripts/west_commands/boards.py
+    commands:
+      - name: boards
+        class: Boards
+        help: display information about supported boards
   - file: scripts/west_commands/build.py
     commands:
       - name: build
diff --git a/scripts/west_commands/boards.py b/scripts/west_commands/boards.py
new file mode 100644
index 0000000..e324959
--- /dev/null
+++ b/scripts/west_commands/boards.py
@@ -0,0 +1,94 @@
+# Copyright (c) 2019 Nordic Semiconductor ASA
+#
+# SPDX-License-Identifier: Apache-2.0
+
+import argparse
+import collections
+import os
+import re
+import textwrap
+
+from west import log
+from west.commands import WestCommand
+from cmake import run_cmake
+
+class Boards(WestCommand):
+
+    def __init__(self):
+        super().__init__(
+            'boards',
+            # Keep this in sync with the string in west-commands.yml.
+            'display information about supported boards',
+            'Display information about boards',
+            accepts_unknown_args=False)
+
+    def do_add_parser(self, parser_adder):
+        default_fmt = '{name} ({arch})'
+        parser = parser_adder.add_parser(
+            self.name,
+            help=self.help,
+            formatter_class=argparse.RawDescriptionHelpFormatter,
+            description=self.description,
+            epilog=textwrap.dedent('''\
+            FORMAT STRINGS
+
+            Boards are listed using a Python 3 format string. Arguments
+            to the format string are accessed by name.
+
+            The default format string is:
+
+            "{}"
+
+            The following arguments are available:
+
+            - name: board name
+            - arch: board architecture
+            '''.format(default_fmt)))
+
+        # Remember to update scripts/west-completion.bash if you add or remove
+        # flags
+        parser.add_argument('-f', '--format', default=default_fmt,
+                            help='''Format string to use to list each board;
+                                    see FORMAT STRINGS below.'''),
+
+        return parser
+
+    def do_run(self, args, unknown_args):
+        zb = os.environ.get('ZEPHYR_BASE')
+        if not zb:
+            log.die('Internal error: ZEPHYR_BASE not set in the environment, '
+                    'and should have been by the main script')
+
+        cmake_args = ['-DBOARD_ROOT_SPACE_SEPARATED={}'.format(zb),
+                      '-P', '{}/cmake/boards.cmake'.format(zb)]
+        lines = run_cmake(cmake_args, capture_output=True)
+        arch_re = re.compile(r'\s*([\w-]+)\:')
+        board_re = re.compile(r'\s*([\w-]+)\s*')
+        arch = None
+        boards = collections.OrderedDict()
+        for line in lines:
+            match = arch_re.match(line)
+            if match:
+                arch = match.group(1)
+                boards[arch] = []
+                continue
+            match = board_re.match(line)
+            if match:
+                if not arch:
+                    log.die('Invalid board output from CMake: {}'.format(lines))
+                board = match.group(1)
+                boards[arch].append(board)
+
+        for arch in boards:
+            for board in boards[arch]:
+                try:
+                    result = args.format.format(
+                        name=board,
+                        arch=arch)
+                    print(result)
+                except KeyError as e:
+                    # The raised KeyError seems to just put the first
+                    # invalid argument in the args tuple, regardless of
+                    # how many unrecognizable keys there were.
+                    log.die('unknown key "{}" in format string "{}"'.
+                            format(e.args[0], args.format))