From c5cd770ea1e26362ee8480ab2a8501e9c230fee4 Mon Sep 17 00:00:00 2001 From: Andrei Litvin Date: Wed, 28 Jul 2021 10:04:43 -0400 Subject: [PATCH] Add nrf support for scripts/build/build_examples.py (#8642) * Add nrf builds to the example_build.py * Added a bunch of licenses * Detect dry run and skip validation of environment variables * Fix typo in runner naming for dry run detect * Add validation for nrf version in the compile script * Fix unit tests * Remove the double-generate output in the dry run of commands --- scripts/build/build/factory.py | 22 ++++ scripts/build/build/targets.py | 20 +++ scripts/build/build_examples.py | 14 ++ scripts/build/builders/builder.py | 14 +- scripts/build/builders/efr32.py | 14 ++ scripts/build/builders/esp32.py | 15 ++- scripts/build/builders/gn.py | 15 ++- scripts/build/builders/linux.py | 14 ++ scripts/build/builders/nrf.py | 121 ++++++++++++++++++ scripts/build/builders/qpg.py | 14 +- .../build/expected_all_platform_commands.txt | 76 ++++++----- scripts/build/runner/printonly.py | 15 +++ scripts/build/runner/shell.py | 17 +++ scripts/build/test.py | 14 ++ 14 files changed, 352 insertions(+), 33 deletions(-) create mode 100644 scripts/build/builders/nrf.py diff --git a/scripts/build/build/factory.py b/scripts/build/build/factory.py index 5231ff79a3ede9..93799cd323cb1c 100644 --- a/scripts/build/build/factory.py +++ b/scripts/build/build/factory.py @@ -1,3 +1,17 @@ +# Copyright (c) 2021 Project CHIP 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 +# +# 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 from typing import Set @@ -7,6 +21,7 @@ from builders.qpg import QpgBuilder from builders.esp32 import Esp32Builder, Esp32Board, Esp32App from builders.efr32 import Efr32Builder, Efr32App, Efr32Board +from builders.nrf import NrfApp, NrfBoard, NrfConnectBuilder from .targets import Application, Board, Platform @@ -68,6 +83,7 @@ def Create(self, runner, __board_key: Board, __app_key: Application, Platform.ESP32: Matcher(Esp32Builder), Platform.QPG: Matcher(QpgBuilder), Platform.EFR32: Matcher(Efr32Builder), + Platform.NRF: Matcher(NrfConnectBuilder), } # Matrix of what can be compiled and what build options are required @@ -93,6 +109,12 @@ def Create(self, runner, __board_key: Board, __app_key: Application, Application.WINDOW_COVERING, app=Efr32App.WINDOW_COVERING) +_MATCHERS[Platform.NRF].AcceptBoard(Board.NRF5340, board=NrfBoard.NRF5340) +_MATCHERS[Platform.NRF].AcceptBoard(Board.NRF52840, board=NrfBoard.NRF52840) +_MATCHERS[Platform.NRF].AcceptApplication(Application.LOCK, app=NrfApp.LOCK) +_MATCHERS[Platform.NRF].AcceptApplication(Application.LIGHT, app=NrfApp.LIGHT) +_MATCHERS[Platform.NRF].AcceptApplication(Application.SHELL, app=NrfApp.SHELL) + class BuilderFactory: """Creates application builders.""" diff --git a/scripts/build/build/targets.py b/scripts/build/build/targets.py index b2f40cb6f8aeb8..406146dedd8345 100644 --- a/scripts/build/build/targets.py +++ b/scripts/build/build/targets.py @@ -1,3 +1,17 @@ +# Copyright (c) 2021 Project CHIP 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 +# +# 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 logging import os import shutil @@ -11,6 +25,7 @@ class Platform(IntEnum): QPG = auto() ESP32 = auto() EFR32 = auto() + NRF = auto() @property def ArgName(self): @@ -39,6 +54,10 @@ class Board(IntEnum): # EFR32 platform BRD4161A = auto() + # NRF platform + NRF52840 = auto() + NRF5340 = auto() + @property def ArgName(self): return self.name.lower() @@ -57,6 +76,7 @@ class Application(IntEnum): LIGHT = auto() LOCK = auto() WINDOW_COVERING = auto() + SHELL = auto() @property def ArgName(self): diff --git a/scripts/build/build_examples.py b/scripts/build/build_examples.py index a9ef2eee89b282..74109d0b3bf56b 100755 --- a/scripts/build/build_examples.py +++ b/scripts/build/build_examples.py @@ -1,5 +1,19 @@ #!/usr/bin/env -S python3 -B +# Copyright (c) 2021 Project CHIP 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 +# +# 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 coloredlogs import click import logging diff --git a/scripts/build/builders/builder.py b/scripts/build/builders/builder.py index f2942a611f7f9b..873e36d75b2b82 100644 --- a/scripts/build/builders/builder.py +++ b/scripts/build/builders/builder.py @@ -1,4 +1,16 @@ -#!/usr/bin/env python3 +# Copyright (c) 2021 Project CHIP 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 +# +# 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 logging import os diff --git a/scripts/build/builders/efr32.py b/scripts/build/builders/efr32.py index 5cc44f5544f343..54b42b8e489b5d 100644 --- a/scripts/build/builders/efr32.py +++ b/scripts/build/builders/efr32.py @@ -1,3 +1,17 @@ +# Copyright (c) 2021 Project CHIP 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 +# +# 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 logging import os from enum import Enum, auto diff --git a/scripts/build/builders/esp32.py b/scripts/build/builders/esp32.py index 26fd34beabb240..e667c3d83c2cf1 100644 --- a/scripts/build/builders/esp32.py +++ b/scripts/build/builders/esp32.py @@ -1,3 +1,17 @@ +# Copyright (c) 2021 Project CHIP 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 +# +# 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 logging import os import shlex @@ -86,7 +100,6 @@ def generate(self): def build(self): logging.info('Compiling Esp32 at %s', self.output_dir) - self.generate() self._IdfEnvExecute( "ninja -C '%s'" % self.output_dir, title='Building ' + self.identifier) diff --git a/scripts/build/builders/gn.py b/scripts/build/builders/gn.py index 591610265da54c..2b4315c760ef89 100644 --- a/scripts/build/builders/gn.py +++ b/scripts/build/builders/gn.py @@ -1,3 +1,17 @@ +# Copyright (c) 2021 Project CHIP 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 +# +# 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 logging import os @@ -33,6 +47,5 @@ def generate(self): self._Execute(cmd, title='Generating ' + self.identifier) def build(self): - self.generate() self._Execute(['ninja', '-C', self.output_dir], title='Building ' + self.identifier) diff --git a/scripts/build/builders/linux.py b/scripts/build/builders/linux.py index b37e32078b0c43..85f146ff271882 100644 --- a/scripts/build/builders/linux.py +++ b/scripts/build/builders/linux.py @@ -1,3 +1,17 @@ +# Copyright (c) 2021 Project CHIP 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 +# +# 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 logging import os diff --git a/scripts/build/builders/nrf.py b/scripts/build/builders/nrf.py new file mode 100644 index 00000000000000..2e4352351595e7 --- /dev/null +++ b/scripts/build/builders/nrf.py @@ -0,0 +1,121 @@ +# Copyright (c) 2021 Project CHIP 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 +# +# 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 logging +import os +import shlex + +from enum import Enum, auto + +from .builder import Builder + + +class NrfApp(Enum): + LIGHT = auto() + LOCK = auto() + SHELL=auto() + + def ExampleName(self): + if self == NrfApp.LIGHT: + return 'lighting-app' + elif self == NrfApp.LOCK: + return 'lock-app' + elif self == NrfApp.SHELL: + return 'shell' + else: + raise Exception('Unknown app type: %r' % self) + + def AppNamePrefix(self): + if self == NrfApp.LIGHT: + return 'chip-nrf-lighting-example' + elif self == NrfApp.LOCK: + return 'chip-nrf-lock-example' + elif self == NrfApp.SHELL: + return 'chip-nrf-shell' + else: + raise Exception('Unknown app type: %r' % self) + + +class NrfBoard(Enum): + NRF52840 = auto() + NRF5340 = auto() + + def GnArgName(self): + if self == NrfBoard.NRF52840: + return 'nrf52840dk_nrf52840' + elif self == NrfBoard.NRF5340: + return 'nrf5340dk_nrf5340_cpuapp' + else: + raise Exception('Unknown board type: %r' % self) + + +class NrfConnectBuilder(Builder): + + def __init__(self, + root, + runner, + output_dir: str, + app: NrfApp = NrfApp.LIGHT, + board: NrfBoard = NrfBoard.NRF52840): + super(NrfConnectBuilder, self).__init__(root, runner, output_dir) + self.app = app + self.board = board + + def generate(self): + if not os.path.exists(self.output_dir): + # NRF does a in-place update of SDK tools + if not self._runner.dry_run: + if 'ZEPHYR_BASE' not in os.environ: + raise Exception("NRF builds require ZEPHYR_BASE to be set") + + zephyr_base = os.environ['ZEPHYR_BASE'] + nrfconnect_sdk = os.path.dirname(zephyr_base) + + # NRF builds will both try to change .west/config in nrfconnect and + # overall perform a git fetch on that location + if not os.access(nrfconnect_sdk, os.W_OK): + raise Exception("Directory %s not writable. NRFConnect builds require updates to this directory." % nrfconnect_sdk) + + # validate the the ZEPHYR_BASE is up to date (generally the case in docker images) + try: + self._Execute(['python3', 'scripts/setup/nrfconnect/update_ncs.py', '--check']) + except Exception as e: + logging.exception('Failed to validate ZEPHYR_BASE status') + logging.error('To update $ZEPHYR_BASE run: python3 scripts/setup/nrfconnect/update_ncs.py --update --shallow') + + raise Exception('ZEPHYR_BASE validation failed') + + cmd = ''' +source "$ZEPHYR_BASE/zephyr-env.sh"; +export GNUARMEMB_TOOLCHAIN_PATH="$PW_PIGWEED_CIPD_INSTALL_DIR"; +west build --cmake-only -d {outdir} -b {board} {sourcedir} + '''.format( + outdir = shlex.quote(self.output_dir), + board = self.board.GnArgName(), + sourcedir=shlex.quote(os.path.join(self.root, 'examples', self.app.ExampleName(), 'nrfconnect')) + ).strip() + + self._Execute(['bash', '-c', cmd], title='Generating ' + self.identifier) + + + def build(self): + logging.info('Compiling NrfConnect at %s', self.output_dir) + + self._Execute(['ninja', '-C', self.output_dir], title='Building ' + self.identifier) + + def outputs(self): + return { + '%s.elf' % self.app.AppNamePrefix(): os.path.join(self.output_dir, 'zephyr', 'zephyr.elf'), + '%s.map' % self.app.AppNamePrefix(): os.path.join(self.output_dir, 'zephyr', 'zephyr.map'), + } diff --git a/scripts/build/builders/qpg.py b/scripts/build/builders/qpg.py index e11fbb0fa5210c..69c60b3cb68967 100644 --- a/scripts/build/builders/qpg.py +++ b/scripts/build/builders/qpg.py @@ -1,4 +1,16 @@ -#!/usr/bin/env python3 +# Copyright (c) 2021 Project CHIP 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 +# +# 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 logging import os diff --git a/scripts/build/expected_all_platform_commands.txt b/scripts/build/expected_all_platform_commands.txt index f113de90a66c80..56e3cdd9a9bbb8 100644 --- a/scripts/build/expected_all_platform_commands.txt +++ b/scripts/build/expected_all_platform_commands.txt @@ -28,58 +28,76 @@ gn gen --check --fail-on-unused-args --root={root}/examples/lock-app/efr32 '--ar # Generating efr32-brd4161a-window_covering gn gen --check --fail-on-unused-args --root={root}/examples/window-app/efr32 '--args=efr32_board="BRD4161A"' {out}/efr32-brd4161a-window_covering -# Generating linux-native-all_clusters -gn gen --check --fail-on-unused-args --root={root}/examples/all-clusters-app/linux {out}/linux-native-all_clusters +# Generating nrf-nrf52840-light +bash -c 'source "$ZEPHYR_BASE/zephyr-env.sh"; +export GNUARMEMB_TOOLCHAIN_PATH="$PW_PIGWEED_CIPD_INSTALL_DIR"; +west build --cmake-only -d {out}/nrf-nrf52840-light -b nrf52840dk_nrf52840 {root}/examples/lighting-app/nrfconnect' + +# Generating nrf-nrf52840-lock +bash -c 'source "$ZEPHYR_BASE/zephyr-env.sh"; +export GNUARMEMB_TOOLCHAIN_PATH="$PW_PIGWEED_CIPD_INSTALL_DIR"; +west build --cmake-only -d {out}/nrf-nrf52840-lock -b nrf52840dk_nrf52840 {root}/examples/lock-app/nrfconnect' + +# Generating nrf-nrf52840-shell +bash -c 'source "$ZEPHYR_BASE/zephyr-env.sh"; +export GNUARMEMB_TOOLCHAIN_PATH="$PW_PIGWEED_CIPD_INSTALL_DIR"; +west build --cmake-only -d {out}/nrf-nrf52840-shell -b nrf52840dk_nrf52840 {root}/examples/shell/nrfconnect' + +# Generating nrf-nrf5340-light +bash -c 'source "$ZEPHYR_BASE/zephyr-env.sh"; +export GNUARMEMB_TOOLCHAIN_PATH="$PW_PIGWEED_CIPD_INSTALL_DIR"; +west build --cmake-only -d {out}/nrf-nrf5340-light -b nrf5340dk_nrf5340_cpuapp {root}/examples/lighting-app/nrfconnect' + +# Generating nrf-nrf5340-lock +bash -c 'source "$ZEPHYR_BASE/zephyr-env.sh"; +export GNUARMEMB_TOOLCHAIN_PATH="$PW_PIGWEED_CIPD_INSTALL_DIR"; +west build --cmake-only -d {out}/nrf-nrf5340-lock -b nrf5340dk_nrf5340_cpuapp {root}/examples/lock-app/nrfconnect' + +# Generating nrf-nrf5340-shell +bash -c 'source "$ZEPHYR_BASE/zephyr-env.sh"; +export GNUARMEMB_TOOLCHAIN_PATH="$PW_PIGWEED_CIPD_INSTALL_DIR"; +west build --cmake-only -d {out}/nrf-nrf5340-shell -b nrf5340dk_nrf5340_cpuapp {root}/examples/shell/nrfconnect' # Building linux-native-all_clusters ninja -C {out}/linux-native-all_clusters -# Generating qpg-qpg6100-lock -gn gen --check --fail-on-unused-args --root={root}/examples/lock-app/qpg {out}/qpg-qpg6100-lock - # Building qpg-qpg6100-lock ninja -C {out}/qpg-qpg6100-lock -# Generating esp32-m5stack-all_clusters -cd "{root}" -bash -c 'source $IDF_PATH/export.sh; idf.py -D SDKCONFIG_DEFAULTS='"'"'sdkconfig_m5stack.defaults'"'"' -C examples/all-clusters-app/esp32 -B {out}/esp32-m5stack-all_clusters reconfigure' -cd - - # Building esp32-m5stack-all_clusters bash -c 'source $IDF_PATH/export.sh; ninja -C '"'"'{out}/esp32-m5stack-all_clusters'"'"'' -# Generating esp32-devkitc-all_clusters -cd "{root}" -bash -c 'source $IDF_PATH/export.sh; idf.py -D SDKCONFIG_DEFAULTS='"'"'sdkconfig.defaults'"'"' -C examples/all-clusters-app/esp32 -B {out}/esp32-devkitc-all_clusters reconfigure' -cd - - # Building esp32-devkitc-all_clusters bash -c 'source $IDF_PATH/export.sh; ninja -C '"'"'{out}/esp32-devkitc-all_clusters'"'"'' -# Generating esp32-devkitc-lock -cd "{root}" -bash -c 'source $IDF_PATH/export.sh; idf.py -C examples/lock-app/esp32 -B {out}/esp32-devkitc-lock reconfigure' -cd - - # Building esp32-devkitc-lock bash -c 'source $IDF_PATH/export.sh; ninja -C '"'"'{out}/esp32-devkitc-lock'"'"'' -# Generating efr32-brd4161a-light -gn gen --check --fail-on-unused-args --root={root}/examples/lighting-app/efr32 '--args=efr32_board="BRD4161A"' {out}/efr32-brd4161a-light - # Building efr32-brd4161a-light ninja -C {out}/efr32-brd4161a-light -# Generating efr32-brd4161a-lock -gn gen --check --fail-on-unused-args --root={root}/examples/lock-app/efr32 '--args=efr32_board="BRD4161A"' {out}/efr32-brd4161a-lock - # Building efr32-brd4161a-lock ninja -C {out}/efr32-brd4161a-lock -# Generating efr32-brd4161a-window_covering -gn gen --check --fail-on-unused-args --root={root}/examples/window-app/efr32 '--args=efr32_board="BRD4161A"' {out}/efr32-brd4161a-window_covering - # Building efr32-brd4161a-window_covering ninja -C {out}/efr32-brd4161a-window_covering +# Building nrf-nrf52840-light +ninja -C {out}/nrf-nrf52840-light + +# Building nrf-nrf52840-lock +ninja -C {out}/nrf-nrf52840-lock + +# Building nrf-nrf52840-shell +ninja -C {out}/nrf-nrf52840-shell + +# Building nrf-nrf5340-light +ninja -C {out}/nrf-nrf5340-light + +# Building nrf-nrf5340-lock +ninja -C {out}/nrf-nrf5340-lock + +# Building nrf-nrf5340-shell +ninja -C {out}/nrf-nrf5340-shell + diff --git a/scripts/build/runner/printonly.py b/scripts/build/runner/printonly.py index 7f6f70f8e8b448..92dfa7129f3408 100644 --- a/scripts/build/runner/printonly.py +++ b/scripts/build/runner/printonly.py @@ -1,9 +1,24 @@ +# Copyright (c) 2021 Project CHIP 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 +# +# 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 shlex class PrintOnlyRunner: def __init__(self, output_file): self.output_file = output_file + self.dry_run = True def Run(self, cmd, cwd=None, title=None): if title: diff --git a/scripts/build/runner/shell.py b/scripts/build/runner/shell.py index 13bc5e4d36d386..5d9df3dfa9fa7f 100644 --- a/scripts/build/runner/shell.py +++ b/scripts/build/runner/shell.py @@ -1,3 +1,17 @@ +# Copyright (c) 2021 Project CHIP 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 +# +# 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 logging import os import subprocess @@ -36,6 +50,9 @@ def close(self): class ShellRunner: + def __init__(self): + self.dry_run = False + def Run(self, cmd, cwd=None, title=None): outpipe = LogPipe(logging.INFO) errpipe = LogPipe(logging.WARN) diff --git a/scripts/build/test.py b/scripts/build/test.py index ff5c9317715022..0ed467a4db49d3 100644 --- a/scripts/build/test.py +++ b/scripts/build/test.py @@ -1,5 +1,19 @@ #!/usr/bin/env python3 +# Copyright (c) 2021 Project CHIP 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 +# +# 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 click import coloredlogs import difflib