aboutsummaryrefslogtreecommitdiff
path: root/pw_arduino_build
diff options
context:
space:
mode:
authorAnthony DiGirolamo <tonymd@google.com>2020-09-27 10:03:16 -0700
committerCQ Bot Account <pigweed-scoped@luci-project-accounts.iam.gserviceaccount.com>2020-10-06 19:50:35 +0000
commit50a196f8dbbde3da4f01de0c511c61fdee21dbdb (patch)
treef673785e5ccd720679824e56ec678cd7a9b3138c /pw_arduino_build
parent0ab4c0ac5a5ef5eef0cc225e84f6544e073223f5 (diff)
downloadpigweed-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.gni16
-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.py103
-rw-r--r--pw_arduino_build/py/pw_arduino_build/__main__.py457
-rwxr-xr-xpw_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.py346
-rw-r--r--pw_arduino_build/py/pw_arduino_build/file_operations.py190
-rw-r--r--pw_arduino_build/py/pw_arduino_build/log.py39
-rw-r--r--pw_arduino_build/py/setup.py3
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',