diff options
author | Anthony DiGirolamo <tonymd@google.com> | 2020-09-27 10:03:16 -0700 |
---|---|---|
committer | CQ Bot Account <pigweed-scoped@luci-project-accounts.iam.gserviceaccount.com> | 2020-10-06 19:50:35 +0000 |
commit | 50a196f8dbbde3da4f01de0c511c61fdee21dbdb (patch) | |
tree | f673785e5ccd720679824e56ec678cd7a9b3138c /pw_arduino_build | |
parent | 0ab4c0ac5a5ef5eef0cc225e84f6544e073223f5 (diff) | |
download | pigweed-50a196f8dbbde3da4f01de0c511c61fdee21dbdb.tar.gz |
Arduino: python cleanup
- Move code in arduinobuilder.py to their own modules:
- __main__.py
- builder.py
- core_installer.py
- file_operations.py
- New log.py module
- os.system -> subprocess.run
- Move --run-* args in the 'show' subcommand under the 'run' subcommand.
This allows running multiple steps in the same invocation. For
example, this will execute 3 steps:
arduino_builder run \
--run-objcopy \
--run-postbuild \
--run-upload-command teensyloader
Change-Id: Ibe5493689f39aa6e6aaeecf5bc7065929111499d
Reviewed-on: https://pigweed-review.googlesource.com/c/pigweed/pigweed/+/19143
Reviewed-by: Wyatt Hepler <hepler@google.com>
Commit-Queue: Anthony DiGirolamo <tonymd@google.com>
Diffstat (limited to 'pw_arduino_build')
-rw-r--r-- | pw_arduino_build/arduino.gni | 16 | ||||
-rw-r--r-- | pw_arduino_build/py/builder_test.py (renamed from pw_arduino_build/py/pw_arduino_build/arduinobuilder_test.py) | 0 | ||||
-rw-r--r-- | pw_arduino_build/py/file_operations_test.py | 103 | ||||
-rw-r--r-- | pw_arduino_build/py/pw_arduino_build/__main__.py | 457 | ||||
-rwxr-xr-x | pw_arduino_build/py/pw_arduino_build/builder.py (renamed from pw_arduino_build/py/pw_arduino_build/arduinobuilder.py) | 881 | ||||
-rw-r--r-- | pw_arduino_build/py/pw_arduino_build/core_installer.py | 346 | ||||
-rw-r--r-- | pw_arduino_build/py/pw_arduino_build/file_operations.py | 190 | ||||
-rw-r--r-- | pw_arduino_build/py/pw_arduino_build/log.py | 39 | ||||
-rw-r--r-- | pw_arduino_build/py/setup.py | 3 |
9 files changed, 1161 insertions, 874 deletions
diff --git a/pw_arduino_build/arduino.gni b/pw_arduino_build/arduino.gni index bf30cd458..c21463251 100644 --- a/pw_arduino_build/arduino.gni +++ b/pw_arduino_build/arduino.gni @@ -32,7 +32,7 @@ declare_args() { } arduino_builder_script = - get_path_info("py/pw_arduino_build/arduinobuilder.py", "abspath") + get_path_info("py/pw_arduino_build/__main__.py", "abspath") _arduino_core_path = rebase_path("../third_party/arduino/cores/$arduino_core_name") @@ -46,12 +46,20 @@ arduino_global_args = [ arduino_package_name, "--compiler-path-override", _compiler_path_override, - "show", - "--delimit-with-newlines", +] + +arduino_board_args = [ "--build-path", rebase_path(root_build_dir), "--board", arduino_board, "--menu-options", ] -arduino_global_args += arduino_menu_options +arduino_board_args += arduino_menu_options + +arduino_show_command_args = arduino_global_args + [ + "show", + "--delimit-with-newlines", + ] + arduino_board_args + +arduino_run_command_args = arduino_global_args + [ "run" ] + arduino_board_args diff --git a/pw_arduino_build/py/pw_arduino_build/arduinobuilder_test.py b/pw_arduino_build/py/builder_test.py index 60eb1441f..60eb1441f 100644 --- a/pw_arduino_build/py/pw_arduino_build/arduinobuilder_test.py +++ b/pw_arduino_build/py/builder_test.py diff --git a/pw_arduino_build/py/file_operations_test.py b/pw_arduino_build/py/file_operations_test.py new file mode 100644 index 000000000..7306fbbba --- /dev/null +++ b/pw_arduino_build/py/file_operations_test.py @@ -0,0 +1,103 @@ +# Copyright 2020 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. +"""Tests for file_operations module.""" + +import os +import shutil +import tempfile +import unittest +from pathlib import Path +from parameterized import parameterized + +import pw_arduino_build.file_operations as file_operations + + +def file_set(): + return [ + "app.ino", + "core/asm.S", + "core/asm.s", + "core/pwm/pulse.c", + "core/pwm/pulse.h", + "libraries/a.c", + "libraries/b.cpp", + "libraries/c.cc", + "libraries/c.h", + ] + + +def create_files(root_dir, file_names): + for file_name in file_names: + folder_path = Path(root_dir) / Path(os.path.dirname(file_name)) + folder_path.mkdir(parents=True, exist_ok=True) + file_path = Path(root_dir) / Path(file_name) + file_path.touch(exist_ok=True) + + +class TestFileOperations(unittest.TestCase): + """Tests to ensure arduino core library source files can be found.""" + def setUp(self): + self.test_dir = tempfile.mkdtemp() + + def tearDown(self): + shutil.rmtree(self.test_dir) + + @parameterized.expand([ + ( + "sources recursive", file_set(), ["**/*.ino", "**/*.h", "**/*.cpp"], + [ + "app.ino", + "core/pwm/pulse.h", + "libraries/b.cpp", + "libraries/c.h", + ] + ), + ( + "directories recursive", file_set(), ["**"], + [ + "core", + "core/pwm", + "libraries", + ] + ), + ( + "directories one level deep", file_set(), ["*"], + [ + "core", + "libraries", + ] + ), + ( + "items one level deep", file_set(), ["*"], + [ + "app.ino", + "core", + "libraries", + ] + ) + ]) # yapf: disable + def test_find_files(self, test_case, base_fileset, patterns, + expected_results): + """Test find_files on source files and directories. + """ + create_files(self.test_dir, base_fileset) + result = file_operations.find_files(self.test_dir, + patterns, + directories_only=("directories" + in test_case)) + self.assertSequenceEqual(expected_results, result) + + +if __name__ == '__main__': + unittest.main() diff --git a/pw_arduino_build/py/pw_arduino_build/__main__.py b/pw_arduino_build/py/pw_arduino_build/__main__.py new file mode 100644 index 000000000..0bdbcf124 --- /dev/null +++ b/pw_arduino_build/py/pw_arduino_build/__main__.py @@ -0,0 +1,457 @@ +#!/usr/bin/env python3 +# Copyright 2020 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. +"""Command line interface for arduino_builder.""" + +import argparse +import glob +import logging +import os +import pprint +import shlex +import subprocess +import sys +from typing import List + +from pw_arduino_build import core_installer, log +from pw_arduino_build.builder import ArduinoBuilder + +_LOG = logging.getLogger(__name__) + +_pretty_print = pprint.PrettyPrinter(indent=1, width=120).pprint +_pretty_format = pprint.PrettyPrinter(indent=1, width=120).pformat + + +def list_boards_command(unused_args, builder): + # list-boards subcommand + # (does not need a selected board or default menu options) + + # TODO(tonymd): Print this sorted with auto-ljust columns + longest_name_length = 0 + for board_name, board_dict in builder.board.items(): + if len(board_name) > longest_name_length: + longest_name_length = len(board_name) + longest_name_length += 2 + + print("Board Name".ljust(longest_name_length), "Description") + for board_name, board_dict in builder.board.items(): + print(board_name.ljust(longest_name_length), board_dict['name']) + sys.exit(0) + + +def list_menu_options_command(args, builder): + # List all menu options for the selected board. + builder.select_board(args.board) + + print("All Options") + all_options, all_column_widths = builder.get_menu_options() + separator = "-" * (all_column_widths[0] + all_column_widths[1] + 2) + print(separator) + + for name, description in all_options: + print(name.ljust(all_column_widths[0] + 1), description) + + print("\nDefault Options") + print(separator) + + default_options, unused_column_widths = builder.get_default_menu_options() + for name, description in default_options: + print(name.ljust(all_column_widths[0] + 1), description) + + +def show_command_print_string_list(args, string_list: List[str]): + join_token = " " + if args.delimit_with_newlines: + join_token = "\n" + print(join_token.join(string_list)) + + +def show_command_print_flag_string(args, flag_string): + if args.delimit_with_newlines: + flag_string_with_newlines = shlex.split(flag_string) + print("\n".join(flag_string_with_newlines)) + else: + print(flag_string) + + +def run_command_lines(args, command_lines: List[str]): + for command_line in command_lines: + if not args.quiet: + print(command_line) + # TODO(tonymd): Exit with sub command exit code. + command_line_args = shlex.split(command_line) + process = subprocess.run(command_line_args, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT) + if process.returncode != 0: + _LOG.error('Command failed with exit code %d.', process.returncode) + _LOG.error('Full command:') + _LOG.error('') + _LOG.error(' %s', command_line) + _LOG.error('') + _LOG.error('Process output:') + print(flush=True) + sys.stdout.buffer.write(process.stdout) + print(flush=True) + _LOG.error('') + + +def run_command(args, builder): + """Run sub command function. + + Runs Arduino recipes. + """ + + if args.run_prebuilds: + run_command_lines(args, builder.get_prebuild_steps()) + + if args.run_link: + line = builder.get_link_line() + archive_file_path = args.run_link[0] # pylint: disable=unused-variable + object_files = args.run_link[1:] + line = line.replace("{object_files}", " ".join(object_files), 1) + run_command_lines(args, [line]) + + if args.run_objcopy: + run_command_lines(args, builder.get_objcopy_steps()) + + if args.run_postbuilds: + run_command_lines(args, builder.get_postbuild_steps()) + + if args.run_upload_command: + command = builder.get_upload_line(args.run_upload_command, + args.serial_port) + run_command_lines(args, [command]) + + +# pylint: disable=too-many-branches +def show_command(args, builder): + """Show sub command function. + + Prints compiler info and flags. + """ + if args.cc_binary: + print(builder.get_cc_binary()) + + elif args.cxx_binary: + print(builder.get_cxx_binary()) + + elif args.objcopy_binary: + print(builder.get_objcopy_binary()) + + elif args.ar_binary: + print(builder.get_ar_binary()) + + elif args.size_binary: + print(builder.get_size_binary()) + + elif args.c_compile: + print(builder.get_c_compile_line()) + + elif args.cpp_compile: + print(builder.get_cpp_compile_line()) + + elif args.link: + print(builder.get_link_line()) + + elif args.objcopy: + print(builder.get_objcopy(args.objcopy)) + + elif args.objcopy_flags: + objcopy_flags = builder.get_objcopy_flags(args.objcopy_flags) + show_command_print_flag_string(args, objcopy_flags) + + elif args.c_flags: + cflags = builder.get_c_flags() + show_command_print_flag_string(args, cflags) + + elif args.s_flags: + sflags = builder.get_s_flags() + show_command_print_flag_string(args, sflags) + + elif args.cpp_flags: + cppflags = builder.get_cpp_flags() + show_command_print_flag_string(args, cppflags) + + elif args.ld_flags: + ldflags = builder.get_ld_flags() + show_command_print_flag_string(args, ldflags) + + elif args.ld_libs: + print(builder.get_ld_libs()) + + elif args.ar_flags: + ar_flags = builder.get_ar_flags() + show_command_print_flag_string(args, ar_flags) + + elif args.core_path: + print(builder.get_core_path()) + + elif args.postbuild: + print(builder.get_postbuild_line(args.postbuild)) + + elif args.upload_command: + print(builder.get_upload_line(args.upload_command, args.serial_port)) + + elif args.upload_tools: + tools = builder.get_upload_tool_names() + for tool_name in tools: + print(tool_name) + + elif args.library_includes: + show_command_print_string_list(args, builder.library_includes()) + + elif args.library_c_files: + show_command_print_string_list(args, builder.library_c_files()) + + elif args.library_cpp_files: + show_command_print_string_list(args, builder.library_cpp_files()) + + elif args.core_c_files: + show_command_print_string_list(args, builder.core_c_files()) + + elif args.core_s_files: + show_command_print_string_list(args, builder.core_s_files()) + + elif args.core_cpp_files: + show_command_print_string_list(args, builder.core_cpp_files()) + + elif args.variant_c_files: + vfiles = builder.variant_c_files() + if vfiles: + show_command_print_string_list(args, vfiles) + + elif args.variant_s_files: + vfiles = builder.variant_s_files() + if vfiles: + show_command_print_string_list(args, vfiles) + + elif args.variant_cpp_files: + vfiles = builder.variant_cpp_files() + if vfiles: + show_command_print_string_list(args, vfiles) + + +def add_common_options(parser, serial_port, build_path, build_project_name, + project_path, project_source_path): + """Add command line options common to the run and show commands.""" + parser.add_argument( + "--serial-port", + default=serial_port, + help="Serial port for flashing flash. Default: '{}'".format( + serial_port)) + parser.add_argument( + "--build-path", + default=build_path, + help="Build directory. Default: '{}'".format(build_path)) + parser.add_argument( + "--project-path", + default=project_path, + help="Project directory. Default: '{}'".format(project_path)) + parser.add_argument( + "--project-source-path", + default=project_source_path, + help="Project directory. Default: '{}'".format(project_source_path)) + parser.add_argument( + "--build-project-name", + default=build_project_name, + help="Project name. Default: '{}'".format(build_project_name)) + parser.add_argument("--board", + required=True, + help="Name of the board to use.") + # nargs="+" is one or more args, e.g: + # --menu-options menu.usb.serialhid menu.speed.150 + parser.add_argument("--menu-options", nargs="+", type=str) + + +def main(): + """Main command line function. + + Parses command line args and dispatches to sub `*_command()` functions. + """ + def log_level(arg: str) -> int: + try: + return getattr(logging, arg.upper()) + except AttributeError: + raise argparse.ArgumentTypeError( + f'{arg.upper()} is not a valid log level') + + parser = argparse.ArgumentParser() + parser.add_argument("-q", + "--quiet", + help="hide run command output", + action="store_true") + parser.add_argument('-l', + '--loglevel', + type=log_level, + default=logging.INFO, + help='Set the log level ' + '(debug, info, warning, error, critical)') + + build_path = os.path.realpath( + os.path.expanduser(os.path.expandvars("./build"))) + project_path = os.path.realpath( + os.path.expanduser(os.path.expandvars("./"))) + project_source_path = os.path.join(project_path, "src") + build_project_name = os.path.basename(project_path) + + serial_port = "UNKNOWN" + # TODO(tonymd): Temp solution to passing in serial port. It should use + # arduino core discovery tools. + possible_serial_ports = glob.glob("/dev/ttyACM*") + glob.glob( + "/dev/ttyUSB*") + if possible_serial_ports: + serial_port = possible_serial_ports[0] + + # Global command line options + parser.add_argument("--arduino-package-path", + help="Path to the arduino IDE install location.") + parser.add_argument("--arduino-package-name", + help="Name of the Arduino board package to use.") + parser.add_argument("--compiler-path-override", + help="Path to arm-none-eabi-gcc bin folder. " + "Default: Arduino core specified gcc") + + # Subcommands + subparsers = parser.add_subparsers(title="subcommand", + description="valid subcommands", + help="sub-command help", + dest="subcommand", + required=True) + + # install-core command + install_core_parser = subparsers.add_parser( + "install-core", help="Download and install arduino cores") + install_core_parser.set_defaults(func=core_installer.install_core_command) + install_core_parser.add_argument("--prefix", + required=True, + help="Path to install core files.") + install_core_parser.add_argument( + "--core-name", + required=True, + choices=core_installer.supported_cores(), + help="Name of the arduino core to install.") + + # list-boards command + list_boards_parser = subparsers.add_parser("list-boards", + help="show supported boards") + list_boards_parser.set_defaults(func=list_boards_command) + + # list-menu-options command + list_menu_options_parser = subparsers.add_parser( + "list-menu-options", + help="show available menu options for selected board") + list_menu_options_parser.set_defaults(func=list_menu_options_command) + list_menu_options_parser.add_argument("--board", + required=True, + help="Name of the board to use.") + + # show command + show_parser = subparsers.add_parser("show", + help="Return compiler information.") + add_common_options(show_parser, serial_port, build_path, + build_project_name, project_path, project_source_path) + show_parser.add_argument("--delimit-with-newlines", + help="Separate flag output with newlines.", + action="store_true") + + output_group = show_parser.add_mutually_exclusive_group(required=True) + output_group.add_argument("--c-compile", action="store_true") + output_group.add_argument("--cpp-compile", action="store_true") + output_group.add_argument("--link", action="store_true") + output_group.add_argument("--c-flags", action="store_true") + output_group.add_argument("--s-flags", action="store_true") + output_group.add_argument("--cpp-flags", action="store_true") + output_group.add_argument("--ld-flags", action="store_true") + output_group.add_argument("--ar-flags", action="store_true") + output_group.add_argument("--ld-libs", action="store_true") + output_group.add_argument("--objcopy", help="objcopy step for SUFFIX") + output_group.add_argument("--objcopy-flags", + help="objcopy flags for SUFFIX") + output_group.add_argument("--core-path", action="store_true") + output_group.add_argument("--cc-binary", action="store_true") + output_group.add_argument("--cxx-binary", action="store_true") + output_group.add_argument("--ar-binary", action="store_true") + output_group.add_argument("--objcopy-binary", action="store_true") + output_group.add_argument("--size-binary", action="store_true") + output_group.add_argument("--postbuild", + help="Show recipe.hooks.postbuild.*.pattern") + output_group.add_argument("--upload-tools", action="store_true") + output_group.add_argument("--upload-command") + output_group.add_argument("--library-includes", action="store_true") + output_group.add_argument("--library-c-files", action="store_true") + output_group.add_argument("--library-cpp-files", action="store_true") + output_group.add_argument("--core-c-files", action="store_true") + output_group.add_argument("--core-s-files", action="store_true") + output_group.add_argument("--core-cpp-files", action="store_true") + output_group.add_argument("--variant-c-files", action="store_true") + output_group.add_argument("--variant-s-files", action="store_true") + output_group.add_argument("--variant-cpp-files", action="store_true") + + show_parser.set_defaults(func=show_command) + + # run command + run_parser = subparsers.add_parser("run", help="Run Arduino recipes.") + add_common_options(run_parser, serial_port, build_path, build_project_name, + project_path, project_source_path) + run_parser.add_argument("--run-link", + nargs="+", + type=str, + help="Run the link command. Expected arguments: " + "the archive file followed by all obj files.") + run_parser.add_argument("--run-objcopy", action="store_true") + run_parser.add_argument("--run-prebuilds", action="store_true") + run_parser.add_argument("--run-postbuilds", action="store_true") + run_parser.add_argument("--run-upload-command") + + run_parser.set_defaults(func=run_command) + + args = parser.parse_args() + + log.install() + log.set_level(args.loglevel) + + _LOG.debug(_pretty_format(args)) + + # Check for and set alternate compiler path. + compiler_path_override = False + if args.compiler_path_override: + compiler_path_override = os.path.realpath( + os.path.expanduser(os.path.expandvars( + args.compiler_path_override))) + + if args.subcommand == "install-core": + args.func(args) + elif args.subcommand in ["list-boards", "list-menu-options"]: + builder = ArduinoBuilder(args.arduino_package_path, + args.arduino_package_name) + builder.load_board_definitions() + args.func(args, builder) + else: + builder = ArduinoBuilder(args.arduino_package_path, + args.arduino_package_name, + build_path=args.build_path, + build_project_name=args.build_project_name, + project_path=args.project_path, + project_source_path=args.project_source_path, + compiler_path_override=compiler_path_override) + builder.load_board_definitions() + builder.select_board(args.board, args.menu_options) + args.func(args, builder) + + sys.exit(0) + + +if __name__ == '__main__': + main() diff --git a/pw_arduino_build/py/pw_arduino_build/arduinobuilder.py b/pw_arduino_build/py/pw_arduino_build/builder.py index 239517d64..06c4117cb 100755 --- a/pw_arduino_build/py/pw_arduino_build/arduinobuilder.py +++ b/pw_arduino_build/py/pw_arduino_build/builder.py @@ -14,130 +14,23 @@ # the License. """Extracts build information from Arduino cores.""" -import argparse import glob -import hashlib import logging import os import platform import pprint import re -import shutil -import shlex -import stat import sys -import tarfile import time -import urllib.request -import zipfile from collections import OrderedDict -from pathlib import Path -from typing import List, Dict +from typing import List + +from pw_arduino_build import file_operations _LOG = logging.getLogger(__name__) -_STDERR_HANDLER = logging.StreamHandler() _pretty_print = pprint.PrettyPrinter(indent=1, width=120).pprint _pretty_format = pprint.PrettyPrinter(indent=1, width=120).pformat -# yapf: disable -_ARDUINO_CORE_ARTIFACTS = { - # pylint: disable=line-too-long - "teensy": { - "Linux": { - "arduino-ide": { - "url": "https://downloads.arduino.cc/arduino-1.8.13-linux64.tar.xz", - "file_name": "arduino-1.8.13-linux64.tar.xz", - "sha256": "1b20d0ec850a2a63488009518725f058668bb6cb48c321f82dcf47dc4299b4ad", - }, - "teensyduino": { - "url": "https://www.pjrc.com/teensy/td_153/TeensyduinoInstall.linux64", - "file_name": "TeensyduinoInstall.linux64", - "sha256": "2e6cd99a757bc80593ea3de006de4cc934bcb0a6ec74cad8ec327f0289d40f0b", - }, - }, - # TODO(tonymd): Handle 32-bit Linux Install? - "Linux32": { - "arduino-ide": { - "url": "https://downloads.arduino.cc/arduino-1.8.13-linux32.tar.xz", - "file_name": "arduino-1.8.13-linux32.tar.xz", - "sha256": "", - }, - "teensyduino": { - "url": "https://www.pjrc.com/teensy/td_153/TeensyduinoInstall.linux32", - "file_name": "TeensyduinoInstall.linux32", - "sha256": "", - }, - }, - # TODO(tonymd): Handle ARM32 (Raspberry Pi) Install? - "LinuxARM32": { - "arduino-ide": { - "url": "https://downloads.arduino.cc/arduino-1.8.13-linuxarm.tar.xz", - "file_name": "arduino-1.8.13-linuxarm.tar.xz", - "sha256": "", - }, - "teensyduino": { - "url": "https://www.pjrc.com/teensy/td_153/TeensyduinoInstall.linuxarm", - "file_name": "TeensyduinoInstall.linuxarm", - "sha256": "", - }, - }, - # TODO(tonymd): Handle ARM64 Install? - "LinuxARM64": { - "arduino-ide": { - "url": "https://downloads.arduino.cc/arduino-1.8.13-linuxaarch64.tar.xz", - "file_name": "arduino-1.8.13-linuxaarch64.tar.xz", - "sha256": "", - }, - "teensyduino": { - "url": "https://www.pjrc.com/teensy/td_153/TeensyduinoInstall.linuxaarch64", - "file_name": "TeensyduinoInstall.linuxaarch64", - "sha256": "", - }, - }, - "Darwin": { - "teensyduino": { - "url": "https://www.pjrc.com/teensy/td_153/Teensyduino_MacOS_Catalina.zip", - "file_name": "Teensyduino_MacOS_Catalina.zip", - "sha256": "401ef42c6e83e621cdda20191a4ef9b7db8a214bede5a94a9e26b45f79c64fe2", - }, - }, - "Windows": { - "arduino-ide": { - "url": "https://downloads.arduino.cc/arduino-1.8.13-windows.zip", - "file_name": "arduino-1.8.13-windows.zip", - "sha256": "78d3e96827b9e9b31b43e516e601c38d670d29f12483e88cbf6d91a0f89ef524", - }, - "teensyduino": { - "url": "https://www.pjrc.com/teensy/td_153/TeensyduinoInstall.exe", - "file_name": "TeensyduinoInstall.exe", - "sha256": "88f58681e5c4772c54e462bc88280320e4276e5b316dcab592fe38d96db990a1", - }, - } - }, - "adafruit-samd": { - "all": { - "core": { - "url": "https://github.com/adafruit/ArduinoCore-samd/archive/1.6.2.tar.gz", - "sha256": "5875f5bc05904c10e6313f02653f28f2f716db639d3d43f5a1d8a83d15339d64", - } - }, - "Linux": {}, - "Darwin": {}, - "Windows": {}, - }, - "stm32duino": { - "all": { - "core": { - "url": "https://github.com/stm32duino/Arduino_Core_STM32/archive/1.9.0.tar.gz", - "sha256": "4f75ba7a117d90392e8f67c58d31d22393749b9cdd3279bc21e7261ec06c62bf", - } - }, - "Linux": {}, - "Darwin": {}, - "Windows": {}, - }, -} # type: Dict[str, Dict] -# yapf: enable def arduino_runtime_os_string(): @@ -149,327 +42,6 @@ def arduino_runtime_os_string(): return arduno_platform[platform.system()] -class FileOperations: - """File helper functions.""" - @staticmethod - def find_files(starting_dir: str, - patterns: List[str], - directories_only=False) -> List[str]: - # ["**/*.S", "**/*.ino", "**/*.h", "**/*.c", "**/*.cpp"] - - original_working_dir = os.getcwd() - if not (os.path.exists(starting_dir) and os.path.isdir(starting_dir)): - _LOG.error("Directory '%s' does not exist.", starting_dir) - raise FileNotFoundError - - os.chdir(starting_dir) - files = [] - for pattern in patterns: - for file_path in glob.glob(pattern, recursive=True): - if not directories_only or (directories_only - and os.path.isdir(file_path)): - files.append(file_path) - os.chdir(original_working_dir) - return sorted(files) - - @staticmethod - def sha256_sum(file_name): - hash_sha256 = hashlib.sha256() - with open(file_name, "rb") as file_handle: - for chunk in iter(lambda: file_handle.read(4096), b""): - hash_sha256.update(chunk) - return hash_sha256.hexdigest() - - @staticmethod - def md5_sum(file_name): - hash_md5 = hashlib.md5() - with open(file_name, "rb") as file_handle: - for chunk in iter(lambda: file_handle.read(4096), b""): - hash_md5.update(chunk) - return hash_md5.hexdigest() - - @staticmethod - def verify_file_checksum(file_path, - expected_checksum, - sum_function=sha256_sum): - downloaded_checksum = sum_function(file_path) - if downloaded_checksum != expected_checksum: - _LOG.error("Error: Invalid %s", sum_function.__name__) - _LOG.error("%s %s", downloaded_checksum, - os.path.basename(file_path)) - _LOG.error("%s (expected)", expected_checksum) - return sys.exit(1) - - _LOG.info(" %s:", sum_function.__name__) - _LOG.info(" %s %s", downloaded_checksum, os.path.basename(file_path)) - return True - - @staticmethod - def download_to_cache(url: str, - expected_md5sum=None, - expected_sha256sum=None, - cache_directory=".cache") -> str: - - cache_dir = os.path.realpath( - os.path.expanduser(os.path.expandvars(cache_directory))) - downloaded_file = os.path.join(cache_dir, url.split("/")[-1]) - - if not os.path.exists(downloaded_file): - _LOG.info("Downloading: %s", url) - urllib.request.urlretrieve(url, filename=downloaded_file) - - if os.path.exists(downloaded_file): - _LOG.info("Downloaded: %s", downloaded_file) - if expected_sha256sum: - FileOperations.verify_file_checksum( - downloaded_file, - expected_sha256sum, - sum_function=FileOperations.sha256_sum) - elif expected_md5sum: - FileOperations.verify_file_checksum( - downloaded_file, - expected_md5sum, - sum_function=FileOperations.md5_sum) - - return downloaded_file - - @staticmethod - def extract_zipfile(archive_file: str, dest_dir: str): - with zipfile.ZipFile(archive_file) as archive: - archive.extractall(path=dest_dir) - - @staticmethod - def extract_tarfile(archive_file: str, dest_dir: str): - with tarfile.open(archive_file, 'r') as archive: - archive.extractall(path=dest_dir) - - @staticmethod - def extract_archive(archive_file: str, - dest_dir: str, - cache_dir: str, - remove_single_toplevel_folder=True): - """Extract a tar or zip file. - - Args: - archive_file (str): Absolute path to the archive file. - dest_dir (str): Extraction destination directory. - cache_dir (str): Directory where temp files can be created. - remove_single_toplevel_folder (bool): If the archive contains only a - single folder move the contents of that into the destination - directory. - """ - # Make a temporary directory to extract files into - temp_extract_dir = os.path.join(cache_dir, - "." + os.path.basename(archive_file)) - os.makedirs(temp_extract_dir, exist_ok=True) - - _LOG.info("Extracting: %s", archive_file) - if zipfile.is_zipfile(archive_file): - FileOperations.extract_zipfile(archive_file, temp_extract_dir) - elif tarfile.is_tarfile(archive_file): - FileOperations.extract_tarfile(archive_file, temp_extract_dir) - else: - _LOG.error("Unknown archive format: %s", archive_file) - return sys.exit(1) - - _LOG.info("Installing into: %s", dest_dir) - path_to_extracted_files = temp_extract_dir - - extracted_top_level_files = os.listdir(temp_extract_dir) - # Check if tarfile has only one folder - # If yes, make that the new path_to_extracted_files - if remove_single_toplevel_folder and len( - extracted_top_level_files) == 1: - path_to_extracted_files = os.path.join( - temp_extract_dir, extracted_top_level_files[0]) - - # Move extracted files to dest_dir - extracted_files = os.listdir(path_to_extracted_files) - for file_name in extracted_files: - source_file = os.path.join(path_to_extracted_files, file_name) - dest_file = os.path.join(dest_dir, file_name) - shutil.move(source_file, dest_file) - - # rm -rf temp_extract_dir - shutil.rmtree(temp_extract_dir, ignore_errors=True) - - # Return List of extracted files - return list(Path(dest_dir).rglob("*")) - - @staticmethod - def remove_empty_directories(directory): - """Recursively remove empty directories.""" - - for path in sorted(Path(directory).rglob("*"), reverse=True): - # If broken symlink - if path.is_symlink() and not path.exists(): - path.unlink() - # if empty directory - elif path.is_dir() and len(os.listdir(path)) == 0: - path.rmdir() - - -class ArduinoCoreInstaller: - """Simple Arduino core installer.""" - @staticmethod - def install_teensy_core_windows(install_prefix, install_dir, cache_dir): - """Download and install Teensyduino artifacts for Windows.""" - teensy_artifacts = _ARDUINO_CORE_ARTIFACTS["teensy"][platform.system()] - - arduino_artifact = teensy_artifacts["arduino-ide"] - arduino_zipfile = FileOperations.download_to_cache( - url=arduino_artifact["url"], - expected_sha256sum=arduino_artifact["sha256"], - cache_directory=cache_dir) - - teensyduino_artifact = teensy_artifacts["teensyduino"] - teensyduino_installer = FileOperations.download_to_cache( - url=teensyduino_artifact["url"], - expected_sha256sum=teensyduino_artifact["sha256"], - cache_directory=cache_dir) - - FileOperations.extract_archive(arduino_zipfile, install_dir, cache_dir) - - # "teensy" here should match args.core_name - teensy_core_dir = os.path.join(install_prefix, "teensy") - - # Change working directory for installation - original_working_dir = os.getcwd() - os.chdir(install_prefix) - - _LOG.info("Installing Teensyduino to: %s", teensy_core_dir) - - install_command = "{} \"--dir={}\"".format(teensyduino_installer, - "teensy") - _LOG.info(" Running: %s", install_command) - _LOG.info(" Please click yes on the Windows 'User Account Control' " - "dialog.") - _LOG.info(" You should see: 'Verified publisher: PRJC.COM LLC'") - - os.system(install_command) - if not os.path.exists( - os.path.join(teensy_core_dir, "hardware", "teensy")): - _LOG.error( - "Error: Installation Failed.\n" - "Please try again and ensure Teensyduino is installed in " - "the folder:\n" - "%s", teensy_core_dir) - sys.exit(1) - else: - _LOG.info(" Install complete!") - - FileOperations.remove_empty_directories(install_dir) - os.chdir(original_working_dir) - - @staticmethod - def install_teensy_core_mac(unused_install_prefix, install_dir, cache_dir): - """Download and install Teensyduino artifacts for Mac.""" - teensy_artifacts = _ARDUINO_CORE_ARTIFACTS["teensy"][platform.system()] - - teensyduino_artifact = teensy_artifacts["teensyduino"] - teensyduino_zip = FileOperations.download_to_cache( - url=teensyduino_artifact["url"], - expected_sha256sum=teensyduino_artifact["sha256"], - cache_directory=cache_dir) - - extracted_files = FileOperations.extract_archive( - teensyduino_zip, - install_dir, - cache_dir, - remove_single_toplevel_folder=False) - toplevel_folder = sorted(extracted_files)[0] - os.symlink(os.path.join(toplevel_folder, "Contents", "Java", - "hardware"), - os.path.join(install_dir, "hardware"), - target_is_directory=True) - - @staticmethod - def install_teensy_core_linux(install_prefix, install_dir, cache_dir): - """Download and install Teensyduino artifacts for Windows.""" - teensy_artifacts = _ARDUINO_CORE_ARTIFACTS["teensy"][platform.system()] - - arduino_artifact = teensy_artifacts["arduino-ide"] - arduino_tarfile = FileOperations.download_to_cache( - url=arduino_artifact["url"], - expected_sha256sum=arduino_artifact["sha256"], - cache_directory=cache_dir) - - teensyduino_artifact = teensy_artifacts["teensyduino"] - teensyduino_installer = FileOperations.download_to_cache( - url=teensyduino_artifact["url"], - expected_sha256sum=teensyduino_artifact["sha256"], - cache_directory=cache_dir) - - extracted_files = FileOperations.extract_archive( - arduino_tarfile, install_dir, cache_dir) - os.chmod(teensyduino_installer, - os.stat(teensyduino_installer).st_mode | stat.S_IEXEC) - - original_working_dir = os.getcwd() - os.chdir(install_prefix) - # "teensy" here should match args.core_name - os.system("{} --dir={}".format(teensyduino_installer, "teensy")) - - # Remove original arduino IDE files - for efile in extracted_files: - if efile.is_file(): - efile.unlink() - - FileOperations.remove_empty_directories(install_dir) - os.chdir(original_working_dir) - - @staticmethod - def install_arduino_samd_core(install_prefix: str, install_dir: str, - cache_dir: str): - # TODO(tonymd): Fetch core/tools as specified by: - # http://downloads.arduino.cc/packages/package_index.json - pass - - @staticmethod - def install_adafruit_samd_core(install_prefix: str, install_dir: str, - cache_dir: str): - artifacts = _ARDUINO_CORE_ARTIFACTS["adafruit-samd"]["all"]["core"] - core_tarfile = FileOperations.download_to_cache( - url=artifacts["url"], - expected_sha256sum=artifacts["sha256"], - cache_directory=cache_dir) - - package_path = os.path.join( - install_dir, "hardware", "samd", - os.path.basename(core_tarfile).replace(".tar.gz", "")) - os.makedirs(package_path, exist_ok=True) - FileOperations.extract_archive(core_tarfile, package_path, cache_dir) - - original_working_dir = os.getcwd() - os.chdir(install_prefix) - # TODO(tonymd): Fetch platform specific tools as specified by: - # https://adafruit.github.io/arduino-board-index/package_adafruit_index.json - # Specifically: - # https://github.com/ARM-software/CMSIS_5/archive/5.4.0.tar.gz - os.chdir(original_working_dir) - return True - - @staticmethod - def install_stm32duino_core(install_prefix, install_dir, cache_dir): - artifacts = _ARDUINO_CORE_ARTIFACTS["stm32duino"]["all"]["core"] - core_tarfile = FileOperations.download_to_cache( - url=artifacts["url"], - expected_sha256sum=artifacts["sha256"], - cache_directory=cache_dir) - - package_path = os.path.join( - install_dir, "hardware", "stm32", - os.path.basename(core_tarfile).replace(".tar.gz", "")) - os.makedirs(package_path, exist_ok=True) - FileOperations.extract_archive(core_tarfile, package_path, cache_dir) - original_working_dir = os.getcwd() - os.chdir(install_prefix) - # TODO(tonymd): Fetch platform specific tools as specified by: - # https://github.com/stm32duino/BoardManagerFiles/raw/master/STM32/package_stm_index.json - os.chdir(original_working_dir) - return True - - class ArduinoBuilder: """Used to interpret arduino boards.txt and platform.txt files.""" # pylint: disable=too-many-instance-attributes,too-many-public-methods @@ -517,7 +89,7 @@ class ArduinoBuilder: self.compiler_path_override_binaries = [] if self.compiler_path_override: - self.compiler_path_override_binaries = FileOperations.find_files( + self.compiler_path_override_binaries = file_operations.find_files( self.compiler_path_override, "*") # Container dicts for boards.txt and platform.txt file data. @@ -1372,8 +944,8 @@ class ArduinoBuilder: library_path = os.path.join(self.project_path, "libraries") - library_folders = FileOperations.find_files(library_path, ["*"], - directories_only=True) + library_folders = file_operations.find_files(library_path, ["*"], + directories_only=True) library_source_root_folders = [] for lib in library_folders: lib_dir = os.path.join(library_path, lib) @@ -1396,7 +968,7 @@ class ArduinoBuilder: sources = [] library_folders = self.library_folders() for lib_dir in library_folders: - for file_path in FileOperations.find_files(lib_dir, [pattern]): + for file_path in file_operations.find_files(lib_dir, [pattern]): if not file_path.startswith("examples"): sources.append( os.path.relpath(os.path.join(lib_dir, file_path))) @@ -1413,8 +985,8 @@ class ArduinoBuilder: def core_files(self, pattern): sources = [] - for file_path in FileOperations.find_files(self.get_core_path(), - [pattern]): + for file_path in file_operations.find_files(self.get_core_path(), + [pattern]): sources.append(os.path.join(self.get_core_path(), file_path)) return sources @@ -1433,8 +1005,8 @@ class ArduinoBuilder: def variant_files(self, pattern): sources = [] if self.build_variant_path: - for file_path in FileOperations.find_files(self.get_variant_path(), - [pattern]): + for file_path in file_operations.find_files( + self.get_variant_path(), [pattern]): sources.append(os.path.join(self.get_variant_path(), file_path)) return sources @@ -1450,8 +1022,8 @@ class ArduinoBuilder: def project_files(self, pattern): sources = [] - for file_path in FileOperations.find_files(self.project_path, - [pattern]): + for file_path in file_operations.find_files(self.project_path, + [pattern]): if not file_path.startswith( "examples") and not file_path.startswith("libraries"): sources.append(file_path) @@ -1465,430 +1037,3 @@ class ArduinoBuilder: def project_ino_files(self): return self.project_files("**/*.ino") - - -def install_core_command(args): - install_prefix = os.path.realpath( - os.path.expanduser(os.path.expandvars(args.prefix))) - install_dir = os.path.join(install_prefix, args.core_name) - cache_dir = os.path.join(install_prefix, ".cache", args.core_name) - - shutil.rmtree(install_dir, ignore_errors=True) - os.makedirs(install_dir, exist_ok=True) - os.makedirs(cache_dir, exist_ok=True) - - if args.core_name == "teensy": - if platform.system() == "Linux": - ArduinoCoreInstaller.install_teensy_core_linux( - install_prefix, install_dir, cache_dir) - elif platform.system() == "Darwin": - ArduinoCoreInstaller.install_teensy_core_mac( - install_prefix, install_dir, cache_dir) - elif platform.system() == "Windows": - ArduinoCoreInstaller.install_teensy_core_windows( - install_prefix, install_dir, cache_dir) - elif args.core_name == "adafruit-samd": - ArduinoCoreInstaller.install_adafruit_samd_core( - install_prefix, install_dir, cache_dir) - elif args.core_name == "stm32duino": - ArduinoCoreInstaller.install_stm32duino_core(install_prefix, - install_dir, cache_dir) - - sys.exit(0) - - -def list_boards_command(unused_args, builder): - # list-boards subcommand - # (does not need a selected board or default menu options) - - # TODO(tonymd): Print this sorted with auto-ljust columns - longest_name_length = 0 - for board_name, board_dict in builder.board.items(): - if len(board_name) > longest_name_length: - longest_name_length = len(board_name) - longest_name_length += 2 - - print("Board Name".ljust(longest_name_length), "Description") - for board_name, board_dict in builder.board.items(): - print(board_name.ljust(longest_name_length), board_dict['name']) - sys.exit(0) - - -def list_menu_options_command(args, builder): - # List all menu options for the selected board. - builder.select_board(args.board) - - print("All Options") - all_options, all_column_widths = builder.get_menu_options() - separator = "-" * (all_column_widths[0] + all_column_widths[1] + 2) - print(separator) - - for name, description in all_options: - print(name.ljust(all_column_widths[0] + 1), description) - - print("\nDefault Options") - print(separator) - - default_options, unused_column_widths = builder.get_default_menu_options() - for name, description in default_options: - print(name.ljust(all_column_widths[0] + 1), description) - - -def show_command_print_string_list(args, string_list: List[str]): - join_token = " " - if args.delimit_with_newlines: - join_token = "\n" - print(join_token.join(string_list)) - - -def show_command_print_flag_string(args, flag_string): - if args.delimit_with_newlines: - flag_string_with_newlines = shlex.split(flag_string) - print("\n".join(flag_string_with_newlines)) - else: - print(flag_string) - - -def run_command_lines(args, command_lines: List[str]): - for command_line in command_lines: - if not args.quiet: - print(command_line) - # TODO(tonymd): Exit with sub command exit code. - os.system(command_line) - - -# pylint: disable=too-many-branches -def show_command(args, builder): - """Show sub command function. - - Prints compile flags and runs Arduino recipes. - """ - builder.select_board(args.board, args.menu_options) - - if args.cc_binary: - print(builder.get_cc_binary()) - - elif args.cxx_binary: - print(builder.get_cxx_binary()) - - elif args.objcopy_binary: - print(builder.get_objcopy_binary()) - - elif args.ar_binary: - print(builder.get_ar_binary()) - - elif args.size_binary: - print(builder.get_size_binary()) - - elif args.run_c_compile: - print(builder.get_c_compile_line()) - - elif args.run_cpp_compile: - print(builder.get_cpp_compile_line()) - - elif args.run_link: - line = builder.get_link_line() - archive_file_path = args.run_link[0] # pylint: disable=unused-variable - object_files = args.run_link[1:] - line = line.replace("{object_files}", " ".join(object_files), 1) - run_command_lines(args, [line]) - - elif args.run_objcopy: - run_command_lines(args, builder.get_objcopy_steps()) - - elif args.objcopy: - print(builder.get_objcopy(args.objcopy)) - - elif args.objcopy_flags: - objcopy_flags = builder.get_objcopy_flags(args.objcopy_flags) - show_command_print_flag_string(args, objcopy_flags) - - elif args.c_flags: - cflags = builder.get_c_flags() - show_command_print_flag_string(args, cflags) - - elif args.s_flags: - sflags = builder.get_s_flags() - show_command_print_flag_string(args, sflags) - - elif args.cpp_flags: - cppflags = builder.get_cpp_flags() - show_command_print_flag_string(args, cppflags) - - elif args.ld_flags: - ldflags = builder.get_ld_flags() - show_command_print_flag_string(args, ldflags) - - elif args.ld_libs: - print(builder.get_ld_libs()) - - elif args.ar_flags: - ar_flags = builder.get_ar_flags() - show_command_print_flag_string(args, ar_flags) - - elif args.core_path: - print(builder.get_core_path()) - - elif args.run_prebuilds: - run_command_lines(args, builder.get_prebuild_steps()) - - elif args.run_postbuilds: - run_command_lines(args, builder.get_postbuild_steps()) - - elif args.postbuild: - print(builder.get_postbuild_line(args.postbuild)) - - elif args.run_upload_command: - command = builder.get_upload_line(args.run_upload_command, - args.serial_port) - run_command_lines(args, [command]) - - elif args.upload_command: - print(builder.get_upload_line(args.upload_command, args.serial_port)) - - elif args.upload_tools: - tools = builder.get_upload_tool_names() - for tool_name in tools: - print(tool_name) - - elif args.library_includes: - show_command_print_string_list(args, builder.library_includes()) - - elif args.library_c_files: - show_command_print_string_list(args, builder.library_c_files()) - - elif args.library_cpp_files: - show_command_print_string_list(args, builder.library_cpp_files()) - - elif args.core_c_files: - show_command_print_string_list(args, builder.core_c_files()) - - elif args.core_s_files: - show_command_print_string_list(args, builder.core_s_files()) - - elif args.core_cpp_files: - show_command_print_string_list(args, builder.core_cpp_files()) - - elif args.variant_c_files: - vfiles = builder.variant_c_files() - if vfiles: - show_command_print_string_list(args, vfiles) - - elif args.variant_s_files: - vfiles = builder.variant_s_files() - if vfiles: - show_command_print_string_list(args, vfiles) - - elif args.variant_cpp_files: - vfiles = builder.variant_cpp_files() - if vfiles: - show_command_print_string_list(args, vfiles) - - -def main(): - """Main command line function. - - Parses command line args and dispatches to sub `*_command()` functions. - """ - def log_level(arg: str) -> int: - try: - return getattr(logging, arg.upper()) - except AttributeError: - raise argparse.ArgumentTypeError( - f'{arg.upper()} is not a valid log level') - - parser = argparse.ArgumentParser() - parser.add_argument("-q", - "--quiet", - help="hide run command output", - action="store_true") - parser.add_argument('-l', - '--loglevel', - type=log_level, - default=logging.INFO, - help='Set the log level ' - '(debug, info, warning, error, critical)') - - build_path = os.path.realpath( - os.path.expanduser(os.path.expandvars("./build"))) - project_path = os.path.realpath( - os.path.expanduser(os.path.expandvars("./"))) - project_source_path = os.path.join(project_path, "src") - build_project_name = os.path.basename(project_path) - - serial_port = "UNKNOWN" - # TODO(tonymd): Temp solution to passing in serial port. It should use - # arduino core discovery tools. - possible_serial_ports = glob.glob("/dev/ttyACM*") + glob.glob( - "/dev/ttyUSB*") - if possible_serial_ports: - serial_port = possible_serial_ports[0] - - project_name = os.path.basename( - os.path.realpath(os.path.expanduser(os.path.expandvars("./")))) - - # Global command line options - parser.add_argument("--arduino-package-path", - help="Path to the arduino IDE install location.") - parser.add_argument("--arduino-package-name", - help="Name of the Arduino board package to use.") - parser.add_argument("--compiler-path-override", - help="Path to arm-none-eabi-gcc bin folder. " - "Default: Arduino core specified gcc") - - # Subcommands - subparsers = parser.add_subparsers(title="subcommand", - description="valid subcommands", - help="sub-command help", - dest="subcommand", - required=True) - - # install-core command - install_core_parser = subparsers.add_parser( - "install-core", help="Download and install arduino cores") - install_core_parser.set_defaults(func=install_core_command) - install_core_parser.add_argument("--prefix", - required=True, - help="Path to install core files.") - install_core_parser.add_argument( - "--core-name", - required=True, - choices=[ - "teensy", - "stm32duino", - "adafruit-samd", - ], - help="Name of the arduino core to install.") - - # list-boards command - list_boards_parser = subparsers.add_parser("list-boards", - help="show supported boards") - list_boards_parser.set_defaults(func=list_boards_command) - - # list-menu-options command - list_menu_options_parser = subparsers.add_parser( - "list-menu-options", - help="show available menu options for selected board") - list_menu_options_parser.set_defaults(func=list_menu_options_command) - list_menu_options_parser.add_argument("--board", - required=True, - help="Name of the board to use.") - - # show command - show_parser = subparsers.add_parser("show", - help="Return compiler information.") - show_parser.add_argument( - "--serial-port", - default=serial_port, - help="Serial port for flashing flash. Default: '{}'".format( - serial_port)) - show_parser.add_argument( - "--build-path", - default=build_path, - help="Build directory. Default: '{}'".format(build_path)) - show_parser.add_argument( - "--project-path", - default=build_path, - help="Project directory. Default: '{}'".format(project_path)) - show_parser.add_argument( - "--project-source-path", - default=project_source_path, - help="Project directory. Default: '{}'".format(project_source_path)) - show_parser.add_argument( - "--build-project-name", - default=project_name, - help="Project name. Default: '{}'".format(build_project_name)) - show_parser.add_argument("--board", - required=True, - help="Name of the board to use.") - show_parser.add_argument("--delimit-with-newlines", - help="Separate flags output with newlines.", - action="store_true") - - output_group = show_parser.add_mutually_exclusive_group(required=True) - output_group.add_argument("--run-c-compile", action="store_true") - output_group.add_argument("--run-cpp-compile", action="store_true") - output_group.add_argument("--run-link", - nargs="+", - type=str, - help="Run the link command. Expected arguments: " - "the archive file followed by all obj files.") - output_group.add_argument("--run-objcopy", action="store_true") - output_group.add_argument("--run-prebuilds", action="store_true") - output_group.add_argument("--run-postbuilds", action="store_true") - output_group.add_argument("--c-flags", action="store_true") - output_group.add_argument("--s-flags", action="store_true") - output_group.add_argument("--cpp-flags", action="store_true") - output_group.add_argument("--ld-flags", action="store_true") - output_group.add_argument("--ar-flags", action="store_true") - output_group.add_argument("--ld-libs", action="store_true") - output_group.add_argument("--objcopy", help="objcopy step for SUFFIX") - output_group.add_argument("--objcopy-flags", - help="objcopy flags for SUFFIX") - output_group.add_argument("--core-path", action="store_true") - output_group.add_argument("--cc-binary", action="store_true") - output_group.add_argument("--cxx-binary", action="store_true") - output_group.add_argument("--ar-binary", action="store_true") - output_group.add_argument("--objcopy-binary", action="store_true") - output_group.add_argument("--size-binary", action="store_true") - output_group.add_argument("--postbuild", - help="Show recipe.hooks.postbuild.*.pattern") - output_group.add_argument("--upload-tools", action="store_true") - output_group.add_argument("--upload-command") - output_group.add_argument("--run-upload-command") - output_group.add_argument("--library-includes", action="store_true") - output_group.add_argument("--library-c-files", action="store_true") - output_group.add_argument("--library-cpp-files", action="store_true") - output_group.add_argument("--core-c-files", action="store_true") - output_group.add_argument("--core-s-files", action="store_true") - output_group.add_argument("--core-cpp-files", action="store_true") - output_group.add_argument("--variant-c-files", action="store_true") - output_group.add_argument("--variant-s-files", action="store_true") - output_group.add_argument("--variant-cpp-files", action="store_true") - - # nargs="+" is one or more args, e.g: - # --menu-options menu.usb.serialhid menu.speed.150 - show_parser.add_argument("--menu-options", nargs="+", type=str) - show_parser.set_defaults(func=show_command) - - args = parser.parse_args() - - # Set logging level and output handler - _LOG.setLevel(args.loglevel) - _STDERR_HANDLER.setLevel(args.loglevel) - _STDERR_HANDLER.setFormatter( - logging.Formatter("[%(asctime)s] " - "%(levelname)s %(message)s", "%Y%m%d %H:%M:%S")) - _LOG.addHandler(_STDERR_HANDLER) - - _LOG.debug(_pretty_format(args)) - - compiler_path_override = False - if args.compiler_path_override: - compiler_path_override = os.path.realpath( - os.path.expanduser(os.path.expandvars( - args.compiler_path_override))) - - if args.subcommand == "install-core": - args.func(args) - elif args.subcommand in ["list-boards", "list-menu-options"]: - builder = ArduinoBuilder(args.arduino_package_path, - args.arduino_package_name) - builder.load_board_definitions() - args.func(args, builder) - else: - builder = ArduinoBuilder(args.arduino_package_path, - args.arduino_package_name, - build_path=args.build_path, - build_project_name=args.build_project_name, - project_path=args.project_path, - project_source_path=args.project_source_path, - compiler_path_override=compiler_path_override) - builder.load_board_definitions() - args.func(args, builder) - - sys.exit(0) - - -if __name__ == '__main__': - main() diff --git a/pw_arduino_build/py/pw_arduino_build/core_installer.py b/pw_arduino_build/py/pw_arduino_build/core_installer.py new file mode 100644 index 000000000..a1e3d3c74 --- /dev/null +++ b/pw_arduino_build/py/pw_arduino_build/core_installer.py @@ -0,0 +1,346 @@ +#!/usr/bin/env python3 +# Copyright 2020 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. +"""Arduino Core Installer.""" + +import argparse +import logging +import os +import platform +import stat +import sys +import shutil +import subprocess +from typing import Dict + +import pw_arduino_build.file_operations as file_operations + +_LOG = logging.getLogger(__name__) + +# yapf: disable +_ARDUINO_CORE_ARTIFACTS: Dict[str, Dict] = { + # pylint: disable=line-too-long + "teensy": { + "Linux": { + "arduino-ide": { + "url": "https://downloads.arduino.cc/arduino-1.8.13-linux64.tar.xz", + "file_name": "arduino-1.8.13-linux64.tar.xz", + "sha256": "1b20d0ec850a2a63488009518725f058668bb6cb48c321f82dcf47dc4299b4ad", + }, + "teensyduino": { + "url": "https://www.pjrc.com/teensy/td_153/TeensyduinoInstall.linux64", + "file_name": "TeensyduinoInstall.linux64", + "sha256": "2e6cd99a757bc80593ea3de006de4cc934bcb0a6ec74cad8ec327f0289d40f0b", + }, + }, + # TODO(tonymd): Handle 32-bit Linux Install? + "Linux32": { + "arduino-ide": { + "url": "https://downloads.arduino.cc/arduino-1.8.13-linux32.tar.xz", + "file_name": "arduino-1.8.13-linux32.tar.xz", + "sha256": "", + }, + "teensyduino": { + "url": "https://www.pjrc.com/teensy/td_153/TeensyduinoInstall.linux32", + "file_name": "TeensyduinoInstall.linux32", + "sha256": "", + }, + }, + # TODO(tonymd): Handle ARM32 (Raspberry Pi) Install? + "LinuxARM32": { + "arduino-ide": { + "url": "https://downloads.arduino.cc/arduino-1.8.13-linuxarm.tar.xz", + "file_name": "arduino-1.8.13-linuxarm.tar.xz", + "sha256": "", + }, + "teensyduino": { + "url": "https://www.pjrc.com/teensy/td_153/TeensyduinoInstall.linuxarm", + "file_name": "TeensyduinoInstall.linuxarm", + "sha256": "", + }, + }, + # TODO(tonymd): Handle ARM64 Install? + "LinuxARM64": { + "arduino-ide": { + "url": "https://downloads.arduino.cc/arduino-1.8.13-linuxaarch64.tar.xz", + "file_name": "arduino-1.8.13-linuxaarch64.tar.xz", + "sha256": "", + }, + "teensyduino": { + "url": "https://www.pjrc.com/teensy/td_153/TeensyduinoInstall.linuxaarch64", + "file_name": "TeensyduinoInstall.linuxaarch64", + "sha256": "", + }, + }, + "Darwin": { + "teensyduino": { + "url": "https://www.pjrc.com/teensy/td_153/Teensyduino_MacOS_Catalina.zip", + "file_name": "Teensyduino_MacOS_Catalina.zip", + "sha256": "401ef42c6e83e621cdda20191a4ef9b7db8a214bede5a94a9e26b45f79c64fe2", + }, + }, + "Windows": { + "arduino-ide": { + "url": "https://downloads.arduino.cc/arduino-1.8.13-windows.zip", + "file_name": "arduino-1.8.13-windows.zip", + "sha256": "78d3e96827b9e9b31b43e516e601c38d670d29f12483e88cbf6d91a0f89ef524", + }, + "teensyduino": { + "url": "https://www.pjrc.com/teensy/td_153/TeensyduinoInstall.exe", + "file_name": "TeensyduinoInstall.exe", + "sha256": "88f58681e5c4772c54e462bc88280320e4276e5b316dcab592fe38d96db990a1", + }, + } + }, + "adafruit-samd": { + "all": { + "core": { + "version": "1.6.2", + "url": "https://github.com/adafruit/ArduinoCore-samd/archive/1.6.2.tar.gz", + "sha256": "5875f5bc05904c10e6313f02653f28f2f716db639d3d43f5a1d8a83d15339d64", + } + }, + "Linux": {}, + "Darwin": {}, + "Windows": {}, + }, + "arduino-samd": { + "all": { + "core": { + "version": "1.8.8", + "url": "http://downloads.arduino.cc/cores/samd-1.8.8.tar.bz2", + "file_name": "samd-1.8.8.tar.bz2", + "sha256": "7b93eb705cba9125d9ee52eba09b51fb5fe34520ada351508f4253abbc9f27fa", + } + }, + "Linux": {}, + "Darwin": {}, + "Windows": {}, + }, + "stm32duino": { + "all": { + "core": { + "version": "1.9.0", + "url": "https://github.com/stm32duino/Arduino_Core_STM32/archive/1.9.0.tar.gz", + "sha256": "4f75ba7a117d90392e8f67c58d31d22393749b9cdd3279bc21e7261ec06c62bf", + } + }, + "Linux": {}, + "Darwin": {}, + "Windows": {}, + }, +} +# yapf: enable + + +def install_core_command(args: argparse.Namespace): + install_prefix = os.path.realpath( + os.path.expanduser(os.path.expandvars(args.prefix))) + install_dir = os.path.join(install_prefix, args.core_name) + cache_dir = os.path.join(install_prefix, ".cache", args.core_name) + + if args.core_name in supported_cores(): + shutil.rmtree(install_dir, ignore_errors=True) + os.makedirs(install_dir, exist_ok=True) + os.makedirs(cache_dir, exist_ok=True) + + if args.core_name == "teensy": + if platform.system() == "Linux": + install_teensy_core_linux(install_prefix, install_dir, cache_dir) + elif platform.system() == "Darwin": + install_teensy_core_mac(install_prefix, install_dir, cache_dir) + elif platform.system() == "Windows": + install_teensy_core_windows(install_prefix, install_dir, cache_dir) + elif args.core_name == "adafruit-samd": + install_adafruit_samd_core(install_prefix, install_dir, cache_dir) + elif args.core_name == "stm32duino": + install_stm32duino_core(install_prefix, install_dir, cache_dir) + elif args.core_name == "arduino-samd": + install_arduino_samd_core(install_prefix, install_dir, cache_dir) + else: + _LOG.error("Invalid core '%s'. Supported cores: %s", args.core_name, + ", ".join(supported_cores())) + sys.exit(1) + + +def supported_cores(): + return _ARDUINO_CORE_ARTIFACTS.keys() + + +def install_teensy_core_windows(install_prefix, install_dir, cache_dir): + """Download and install Teensyduino artifacts for Windows.""" + teensy_artifacts = _ARDUINO_CORE_ARTIFACTS["teensy"][platform.system()] + + arduino_artifact = teensy_artifacts["arduino-ide"] + arduino_zipfile = file_operations.download_to_cache( + url=arduino_artifact["url"], + expected_sha256sum=arduino_artifact["sha256"], + cache_directory=cache_dir) + + teensyduino_artifact = teensy_artifacts["teensyduino"] + teensyduino_installer = file_operations.download_to_cache( + url=teensyduino_artifact["url"], + expected_sha256sum=teensyduino_artifact["sha256"], + cache_directory=cache_dir) + + file_operations.extract_archive(arduino_zipfile, install_dir, cache_dir) + + # "teensy" here should match args.core_name + teensy_core_dir = os.path.join(install_prefix, "teensy") + + # Change working directory for installation + original_working_dir = os.getcwd() + os.chdir(install_prefix) + + _LOG.info("Installing Teensyduino to: %s", teensy_core_dir) + + install_command = [teensyduino_installer, "--dir=teensy"] + _LOG.info(" Running: %s", " ".join(install_command)) + _LOG.info(" Please click yes on the Windows 'User Account Control' " + "dialog.") + _LOG.info(" You should see: 'Verified publisher: PRJC.COM LLC'") + + install_command = [teensyduino_installer, "--dir=teensy"] + subprocess.run(install_command) + if not os.path.exists(os.path.join(teensy_core_dir, "hardware", "teensy")): + _LOG.error( + "Error: Installation Failed.\n" + "Please try again and ensure Teensyduino is installed in " + "the folder:\n" + "%s", teensy_core_dir) + sys.exit(1) + else: + _LOG.info(" Install complete!") + + file_operations.remove_empty_directories(install_dir) + os.chdir(original_working_dir) + + +def install_teensy_core_mac(unused_install_prefix, install_dir, cache_dir): + """Download and install Teensyduino artifacts for Mac.""" + teensy_artifacts = _ARDUINO_CORE_ARTIFACTS["teensy"][platform.system()] + + teensyduino_artifact = teensy_artifacts["teensyduino"] + teensyduino_zip = file_operations.download_to_cache( + url=teensyduino_artifact["url"], + expected_sha256sum=teensyduino_artifact["sha256"], + cache_directory=cache_dir) + + extracted_files = file_operations.extract_archive( + teensyduino_zip, + install_dir, + cache_dir, + remove_single_toplevel_folder=False) + toplevel_folder = sorted(extracted_files)[0] + os.symlink(os.path.join(toplevel_folder, "Contents", "Java", "hardware"), + os.path.join(install_dir, "hardware"), + target_is_directory=True) + + +def install_teensy_core_linux(install_prefix, install_dir, cache_dir): + """Download and install Teensyduino artifacts for Windows.""" + teensy_artifacts = _ARDUINO_CORE_ARTIFACTS["teensy"][platform.system()] + + arduino_artifact = teensy_artifacts["arduino-ide"] + arduino_tarfile = file_operations.download_to_cache( + url=arduino_artifact["url"], + expected_sha256sum=arduino_artifact["sha256"], + cache_directory=cache_dir) + + teensyduino_artifact = teensy_artifacts["teensyduino"] + teensyduino_installer = file_operations.download_to_cache( + url=teensyduino_artifact["url"], + expected_sha256sum=teensyduino_artifact["sha256"], + cache_directory=cache_dir) + + extracted_files = file_operations.extract_archive(arduino_tarfile, + install_dir, cache_dir) + os.chmod(teensyduino_installer, + os.stat(teensyduino_installer).st_mode | stat.S_IEXEC) + + original_working_dir = os.getcwd() + os.chdir(install_prefix) + # "teensy" here should match args.core_name + install_command = [teensyduino_installer, "--dir=teensy"] + subprocess.run(install_command) + + # Remove original arduino IDE files + for efile in extracted_files: + if efile.is_file(): + efile.unlink() + + file_operations.remove_empty_directories(install_dir) + os.chdir(original_working_dir) + + +def install_arduino_samd_core(install_prefix: str, install_dir: str, + cache_dir: str): + artifacts = _ARDUINO_CORE_ARTIFACTS["arduino-samd"]["all"]["core"] + core_tarfile = file_operations.download_to_cache( + url=artifacts["url"], + expected_sha256sum=artifacts["sha256"], + cache_directory=cache_dir) + + package_path = os.path.join(install_dir, "hardware", "samd", + artifacts["version"]) + os.makedirs(package_path, exist_ok=True) + file_operations.extract_archive(core_tarfile, package_path, cache_dir) + original_working_dir = os.getcwd() + os.chdir(install_prefix) + # TODO(tonymd): Fetch core/tools as specified by: + # http://downloads.arduino.cc/packages/package_index.json + os.chdir(original_working_dir) + return True + + +def install_adafruit_samd_core(install_prefix: str, install_dir: str, + cache_dir: str): + artifacts = _ARDUINO_CORE_ARTIFACTS["adafruit-samd"]["all"]["core"] + core_tarfile = file_operations.download_to_cache( + url=artifacts["url"], + expected_sha256sum=artifacts["sha256"], + cache_directory=cache_dir) + + package_path = os.path.join(install_dir, "hardware", "samd", + artifacts["version"]) + os.makedirs(package_path, exist_ok=True) + file_operations.extract_archive(core_tarfile, package_path, cache_dir) + + original_working_dir = os.getcwd() + os.chdir(install_prefix) + # TODO(tonymd): Fetch platform specific tools as specified by: + # https://adafruit.github.io/arduino-board-index/package_adafruit_index.json + # Specifically: + # https://github.com/ARM-software/CMSIS_5/archive/5.4.0.tar.gz + os.chdir(original_working_dir) + return True + + +def install_stm32duino_core(install_prefix, install_dir, cache_dir): + artifacts = _ARDUINO_CORE_ARTIFACTS["stm32duino"]["all"]["core"] + core_tarfile = file_operations.download_to_cache( + url=artifacts["url"], + expected_sha256sum=artifacts["sha256"], + cache_directory=cache_dir) + + package_path = os.path.join(install_dir, "hardware", "stm32", + artifacts["version"]) + os.makedirs(package_path, exist_ok=True) + file_operations.extract_archive(core_tarfile, package_path, cache_dir) + original_working_dir = os.getcwd() + os.chdir(install_prefix) + # TODO(tonymd): Fetch platform specific tools as specified by: + # https://github.com/stm32duino/BoardManagerFiles/raw/master/STM32/package_stm_index.json + os.chdir(original_working_dir) + return True diff --git a/pw_arduino_build/py/pw_arduino_build/file_operations.py b/pw_arduino_build/py/pw_arduino_build/file_operations.py new file mode 100644 index 000000000..2bcdb69ca --- /dev/null +++ b/pw_arduino_build/py/pw_arduino_build/file_operations.py @@ -0,0 +1,190 @@ +#!/usr/bin/env python3 +# Copyright 2020 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. +"""File Helper Functions.""" + +import glob +import hashlib +import logging +import os +import shutil +import sys +import tarfile +import urllib.request +import zipfile +from pathlib import Path +from typing import List + +_LOG = logging.getLogger(__name__) + + +class InvalidChecksumError(Exception): + pass + + +def find_files(starting_dir: str, + patterns: List[str], + directories_only=False) -> List[str]: + original_working_dir = os.getcwd() + if not (os.path.exists(starting_dir) and os.path.isdir(starting_dir)): + _LOG.error("Directory '%s' does not exist.", starting_dir) + raise FileNotFoundError + + os.chdir(starting_dir) + files = [] + for pattern in patterns: + for file_path in glob.glob(pattern, recursive=True): + if not directories_only or (directories_only + and os.path.isdir(file_path)): + files.append(file_path) + os.chdir(original_working_dir) + return sorted(files) + + +def sha256_sum(file_name): + hash_sha256 = hashlib.sha256() + with open(file_name, "rb") as file_handle: + for chunk in iter(lambda: file_handle.read(4096), b""): + hash_sha256.update(chunk) + return hash_sha256.hexdigest() + + +def md5_sum(file_name): + hash_md5 = hashlib.md5() + with open(file_name, "rb") as file_handle: + for chunk in iter(lambda: file_handle.read(4096), b""): + hash_md5.update(chunk) + return hash_md5.hexdigest() + + +def verify_file_checksum(file_path, + expected_checksum, + sum_function=sha256_sum): + downloaded_checksum = sum_function(file_path) + try: + if downloaded_checksum != expected_checksum: + raise InvalidChecksumError + except InvalidChecksumError: + _LOG.exception("Invalid %s\n" + "%s %s\n" + "%s (expected)", + sum_function.__name__, downloaded_checksum, + os.path.basename(file_path), expected_checksum) + # Exit to stop installation + return sys.exit(1) + + _LOG.info(" %s:", sum_function.__name__) + _LOG.info(" %s %s", downloaded_checksum, os.path.basename(file_path)) + return True + + +def download_to_cache(url: str, + expected_md5sum=None, + expected_sha256sum=None, + cache_directory=".cache") -> str: + + cache_dir = os.path.realpath( + os.path.expanduser(os.path.expandvars(cache_directory))) + downloaded_file = os.path.join(cache_dir, url.split("/")[-1]) + + if not os.path.exists(downloaded_file): + _LOG.info("Downloading: %s", url) + urllib.request.urlretrieve(url, filename=downloaded_file) + + if os.path.exists(downloaded_file): + _LOG.info("Downloaded: %s", downloaded_file) + if expected_sha256sum: + verify_file_checksum(downloaded_file, + expected_sha256sum, + sum_function=sha256_sum) + elif expected_md5sum: + verify_file_checksum(downloaded_file, + expected_md5sum, + sum_function=md5_sum) + + return downloaded_file + + +def extract_zipfile(archive_file: str, dest_dir: str): + with zipfile.ZipFile(archive_file) as archive: + archive.extractall(path=dest_dir) + + +def extract_tarfile(archive_file: str, dest_dir: str): + with tarfile.open(archive_file, 'r') as archive: + archive.extractall(path=dest_dir) + + +def extract_archive(archive_file: str, + dest_dir: str, + cache_dir: str, + remove_single_toplevel_folder=True): + """Extract a tar or zip file. + + Args: + archive_file (str): Absolute path to the archive file. + dest_dir (str): Extraction destination directory. + cache_dir (str): Directory where temp files can be created. + remove_single_toplevel_folder (bool): If the archive contains only a + single folder move the contents of that into the destination + directory. + """ + # Make a temporary directory to extract files into + temp_extract_dir = os.path.join(cache_dir, + "." + os.path.basename(archive_file)) + os.makedirs(temp_extract_dir, exist_ok=True) + + _LOG.info("Extracting: %s", archive_file) + if zipfile.is_zipfile(archive_file): + extract_zipfile(archive_file, temp_extract_dir) + elif tarfile.is_tarfile(archive_file): + extract_tarfile(archive_file, temp_extract_dir) + else: + _LOG.error("Unknown archive format: %s", archive_file) + return sys.exit(1) + + _LOG.info("Installing into: %s", dest_dir) + path_to_extracted_files = temp_extract_dir + + extracted_top_level_files = os.listdir(temp_extract_dir) + # Check if tarfile has only one folder + # If yes, make that the new path_to_extracted_files + if remove_single_toplevel_folder and len(extracted_top_level_files) == 1: + path_to_extracted_files = os.path.join(temp_extract_dir, + extracted_top_level_files[0]) + + # Move extracted files to dest_dir + extracted_files = os.listdir(path_to_extracted_files) + for file_name in extracted_files: + source_file = os.path.join(path_to_extracted_files, file_name) + dest_file = os.path.join(dest_dir, file_name) + shutil.move(source_file, dest_file) + + # rm -rf temp_extract_dir + shutil.rmtree(temp_extract_dir, ignore_errors=True) + + # Return List of extracted files + return list(Path(dest_dir).rglob("*")) + + +def remove_empty_directories(directory): + """Recursively remove empty directories.""" + + for path in sorted(Path(directory).rglob("*"), reverse=True): + # If broken symlink + if path.is_symlink() and not path.exists(): + path.unlink() + # if empty directory + elif path.is_dir() and len(os.listdir(path)) == 0: + path.rmdir() diff --git a/pw_arduino_build/py/pw_arduino_build/log.py b/pw_arduino_build/py/pw_arduino_build/log.py new file mode 100644 index 000000000..d5ed9f795 --- /dev/null +++ b/pw_arduino_build/py/pw_arduino_build/log.py @@ -0,0 +1,39 @@ +# Copyright 2020 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. +"""Configure the system logger for the default pw command log format.""" + +import logging + +_LOG = logging.getLogger(__name__) +_STDERR_HANDLER = logging.StreamHandler() + + +def install(level: int = logging.INFO) -> None: + """Configure the system logger for the arduino_builder log format.""" + + # Set log level on root logger to debug, otherwise any higher levels + # elsewhere are ignored. + root = logging.getLogger() + root.setLevel(logging.DEBUG) + + _STDERR_HANDLER.setLevel(level) + _STDERR_HANDLER.setFormatter( + logging.Formatter("[%(asctime)s] " + "%(levelname)s %(message)s", "%Y%m%d %H:%M:%S")) + root.addHandler(_STDERR_HANDLER) + + +def set_level(log_level: int): + """Sets the log level for logs to stderr.""" + _STDERR_HANDLER.setLevel(log_level) diff --git a/pw_arduino_build/py/setup.py b/pw_arduino_build/py/setup.py index 5c472957b..1cb074f75 100644 --- a/pw_arduino_build/py/setup.py +++ b/pw_arduino_build/py/setup.py @@ -24,8 +24,7 @@ setuptools.setup( packages=setuptools.find_packages(), entry_points={ 'console_scripts': - ['arduino_builder = ' - ' pw_arduino_build.arduinobuilder:main'] + ['arduino_builder = pw_arduino_build.__main__:main'] }, install_requires=[ 'pyserial', |