pw_arduino_build: Teensy and stm32duino fixes

- Switch teensy core to 1.53 release
- Add patching mechanism
- Add patch to fix teensy core cpp17 compatibility.
  Contains these commits:
  - https://github.com/PaulStoffregen/cores/commit/a8046bb
  - https://github.com/PaulStoffregen/cores/commit/914219e
  - https://github.com/PaulStoffregen/cores/commit/1f3f914
  - a patch to make flash_* functions non-static for teensy4
- Add delete message for downloads that fail checksums
- stm32duino: Always include built-in SrcWrapper library

Change-Id: Ib9e98de1bafbb81473cc7a350d8abb60a654e64f
Reviewed-on: https://pigweed-review.googlesource.com/c/pigweed/pigweed/+/27620
Commit-Queue: Auto-Submit <auto-submit@pigweed.google.com.iam.gserviceaccount.com>
Pigweed-Auto-Submit: Anthony DiGirolamo <tonymd@google.com>
Reviewed-by: Keir Mierle <keir@google.com>
diff --git a/pw_arduino_build/py/pw_arduino_build/__main__.py b/pw_arduino_build/py/pw_arduino_build/__main__.py
index 9efed18..f06506e 100644
--- a/pw_arduino_build/py/pw_arduino_build/__main__.py
+++ b/pw_arduino_build/py/pw_arduino_build/__main__.py
@@ -300,7 +300,7 @@
         default=project_source_path,
         help="Project directory. Default: '{}'".format(project_source_path))
     parser.add_argument("--library-path",
-                        default=["libraries"],
+                        default=[],
                         nargs="+",
                         type=str,
                         help="Path to Arduino Library directory.")
diff --git a/pw_arduino_build/py/pw_arduino_build/builder.py b/pw_arduino_build/py/pw_arduino_build/builder.py
index b8c5221..b19946e 100755
--- a/pw_arduino_build/py/pw_arduino_build/builder.py
+++ b/pw_arduino_build/py/pw_arduino_build/builder.py
@@ -91,11 +91,6 @@
         self.build_variant_path = False
         self.library_names = library_names
         self.library_path = library_path
-        if library_path:
-            self.library_path = [
-                os.path.realpath(os.path.expanduser(
-                    os.path.expandvars(l_path))) for l_path in library_path
-            ]
 
         self.compiler_path_override_binaries = []
         if self.compiler_path_override:
@@ -138,6 +133,19 @@
             _LOG.error("\n".join(possible_alternatives))
             sys.exit(1)
 
+        # Populate library paths.
+        if not library_path:
+            self.library_path = []
+        # Append core libraries directory.
+        core_lib_path = Path(self.package_path) / "libraries"
+        if core_lib_path.is_dir():
+            self.library_path.append(Path(self.package_path) / "libraries")
+        if library_path:
+            self.library_path = [
+                os.path.realpath(os.path.expanduser(
+                    os.path.expandvars(l_path))) for l_path in library_path
+            ]
+
         # Grab all folder names in the cores directory. These are typically
         # sub-core source files.
         self.sub_core_folders = os.listdir(
@@ -183,7 +191,7 @@
     def _apply_recipe_overrides(self):
         # Override link recipes with per-core exceptions
         # Teensyduino cores
-        if self.build_arch == 'TEENSY':
+        if self.build_arch == "TEENSY":
             # Change {build.path}/{archive_file}
             # To {archive_file_path} (which should contain the core.a file)
             new_link_line = self.platform["recipe.c.combine.pattern"].replace(
@@ -203,7 +211,7 @@
 
         # Adafruit-samd core
         # TODO(tonymd): This build_arch may clash with Arduino-SAMD core
-        elif self.build_arch == 'SAMD':
+        elif self.build_arch == "SAMD":
             new_link_line = self.platform["recipe.c.combine.pattern"].replace(
                 "\"{build.path}/{archive_file}\" -Wl,--end-group",
                 "{archive_file_path} -Wl,--end-group", 1)
@@ -211,7 +219,7 @@
 
         # STM32L4 Core:
         # https://github.com/GrumpyOldPizza/arduino-STM32L4
-        elif self.build_arch == 'STM32L4':
+        elif self.build_arch == "STM32L4":
             # TODO(tonymd): {build.path}/{archive_file} for the link step always
             # seems to be core.a (except STM32 core)
             line_to_delete = "-Wl,--start-group \"{build.path}/{archive_file}\""
@@ -220,8 +228,11 @@
             self.platform["recipe.c.combine.pattern"] = new_link_line
 
         # stm32duino core
-        elif self.build_arch == 'STM32':
-            pass
+        elif self.build_arch == "STM32":
+            # Must link in SrcWrapper for all projects.
+            if not self.library_names:
+                self.library_names = []
+            self.library_names.append("SrcWrapper")
 
     def _copy_default_menu_options_to_build_variables(self):
         # Clear existing options
@@ -1000,9 +1011,13 @@
             include_args.append("-I{}".format(os.path.relpath(lib_dir)))
         return include_args
 
-    def library_files(self, pattern):
+    def library_files(self, pattern, only_library_name=None):
         sources = []
         library_folders = self.library_folders()
+        if only_library_name:
+            library_folders = [
+                lf for lf in self.library_folders() if only_library_name in lf
+            ]
         for lib_dir in library_folders:
             for file_path in file_operations.find_files(lib_dir, [pattern]):
                 if not file_path.startswith("examples"):
diff --git a/pw_arduino_build/py/pw_arduino_build/core_installer.py b/pw_arduino_build/py/pw_arduino_build/core_installer.py
index 5f26834..2b48195 100644
--- a/pw_arduino_build/py/pw_arduino_build/core_installer.py
+++ b/pw_arduino_build/py/pw_arduino_build/core_installer.py
@@ -24,6 +24,7 @@
 import subprocess
 import sys
 import time
+from pathlib import Path
 from typing import Dict, List
 
 import pw_arduino_build.file_operations as file_operations
@@ -46,9 +47,9 @@
                 "sha256": "1b20d0ec850a2a63488009518725f058668bb6cb48c321f82dcf47dc4299b4ad",
             },
             "teensyduino": {
-                "url": "https://www.pjrc.com/teensy/td_154-beta4/TeensyduinoInstall.linux64",
+                "url": "https://www.pjrc.com/teensy/td_153/TeensyduinoInstall.linux64",
+                "sha256": "2e6cd99a757bc80593ea3de006de4cc934bcb0a6ec74cad8ec327f0289d40f0b",
                 "file_name": "TeensyduinoInstall.linux64",
-                "sha256": "76c58babb7253b65a33d73d53f3f239c2e2ccf8602c771d69300a67d82723730",
             },
         },
         # TODO(tonymd): Handle 32-bit Linux Install?
@@ -92,9 +93,9 @@
         },
         "Darwin": {
             "teensyduino": {
-                "url": "https://www.pjrc.com/teensy/td_154-beta4/Teensyduino_MacOS_Catalina.zip",
+                "url": "https://www.pjrc.com/teensy/td_153/Teensyduino_MacOS_Catalina.zip",
                 "file_name": "Teensyduino_MacOS_Catalina.zip",
-                "sha256": "7ca579c12d8f3a8949dbeec812b8dbef13242d575baa707dc7f02bc452c1f4a1",
+                "sha256": "401ef42c6e83e621cdda20191a4ef9b7db8a214bede5a94a9e26b45f79c64fe2",
             },
         },
         "Windows": {
@@ -104,9 +105,9 @@
                 "sha256": "78d3e96827b9e9b31b43e516e601c38d670d29f12483e88cbf6d91a0f89ef524",
             },
             "teensyduino": {
-                "url": "https://www.pjrc.com/teensy/td_154-beta4/TeensyduinoInstall.exe",
+                "url": "https://www.pjrc.com/teensy/td_153/TeensyduinoInstall.exe",
                 "file_name": "TeensyduinoInstall.exe",
-                "sha256": "f7bcc2ed45e10a5d7b003bedabcde12fb1b8cf7ef9081e2503cd668569642a90",
+                "sha256": "88f58681e5c4772c54e462bc88280320e4276e5b316dcab592fe38d96db990a1",
             },
         }
     },
@@ -169,6 +170,7 @@
             install_teensy_core_mac(install_prefix, install_dir, cache_dir)
         elif platform.system() == "Windows":
             install_teensy_core_windows(install_prefix, install_dir, cache_dir)
+        apply_teensy_patches(install_dir)
     elif args.core_name == "adafruit-samd":
         install_adafruit_samd_core(install_prefix, install_dir, cache_dir)
     elif args.core_name == "stm32duino":
@@ -319,6 +321,29 @@
     os.chdir(original_working_dir)
 
 
+def apply_teensy_patches(install_dir):
+    # Remember where we are to construct relative paths for running `git apply`
+    working_directory_path = Path(os.getcwd())
+
+    # On Mac the "hardware" directory is a symlink:
+    #   ls -l third_party/arduino/cores/teensy/
+    #   hardware -> Teensyduino.app/Contents/Java/hardware
+    # Resolve paths since `git apply` doesn't work if a path is beyond a
+    # symbolic link.
+    patch_root_path = (Path(install_dir) /
+                       "hardware/teensy/avr/cores").resolve()
+
+    # Get all *.diff files relative to this python file's parent directory.
+    patch_file_paths = sorted(
+        (Path(__file__).parent / "core_patches/teensy").glob("*.diff"))
+
+    # Apply each patch file.
+    for diff_path in patch_file_paths:
+        file_operations.git_apply_patch(
+            patch_root_path.relative_to(working_directory_path).as_posix(),
+            diff_path.as_posix())
+
+
 def install_arduino_samd_core(install_prefix: str, install_dir: str,
                               cache_dir: str):
     artifacts = _ARDUINO_CORE_ARTIFACTS["arduino-samd"]["all"]["core"]
diff --git a/pw_arduino_build/py/pw_arduino_build/core_patches/teensy/01-teensyduino_1.53-cpp17.diff b/pw_arduino_build/py/pw_arduino_build/core_patches/teensy/01-teensyduino_1.53-cpp17.diff
new file mode 100644
index 0000000..0487eaa
--- /dev/null
+++ b/pw_arduino_build/py/pw_arduino_build/core_patches/teensy/01-teensyduino_1.53-cpp17.diff
@@ -0,0 +1,70 @@
+diff --git a/teensy3/WCharacter.h b/teensy3/WCharacter.h
+index 5bfe697..7c500c1 100644
+--- a/teensy3/WCharacter.h
++++ b/teensy3/WCharacter.h
+@@ -61,7 +61,7 @@ inline boolean isAlpha(int c)
+ // that fits into the ASCII character set.
+ inline boolean isAscii(int c)
+ {
+-  return ( isascii (c) == 0 ? false : true);
++  return ((c & ~0x7F) != 0 ? false : true);
+ }
+ 
+ 
+@@ -143,7 +143,7 @@ inline boolean isHexadecimalDigit(int c)
+ // ASCII character set, by clearing the high-order bits.
+ inline int toAscii(int c)
+ {
+-  return toascii (c);
++  return (c & 0x7F);
+ }
+ 
+ 
+diff --git a/teensy3/avr_functions.h b/teensy3/avr_functions.h
+index 977c5e9..55c426c 100644
+--- a/teensy3/avr_functions.h
++++ b/teensy3/avr_functions.h
+@@ -95,7 +95,7 @@ static inline void eeprom_update_block(const void *buf, void *addr, uint32_t len
+ char * ultoa(unsigned long val, char *buf, int radix);
+ char * ltoa(long val, char *buf, int radix);
+ 
+-#if defined(_NEWLIB_VERSION) && (__NEWLIB__ < 2 || __NEWLIB__ == 2 && __NEWLIB_MINOR__ < 2)
++#if defined(__STRICT_ANSI__) || (defined(_NEWLIB_VERSION) && (__NEWLIB__ < 2 || __NEWLIB__ == 2 && __NEWLIB_MINOR__ < 2))
+ static inline char * utoa(unsigned int val, char *buf, int radix) __attribute__((always_inline, unused));
+ static inline char * utoa(unsigned int val, char *buf, int radix) { return ultoa(val, buf, radix); }
+ static inline char * itoa(int val, char *buf, int radix) __attribute__((always_inline, unused));
+diff --git a/teensy4/WCharacter.h b/teensy4/WCharacter.h
+index 5bfe697..7c500c1 100644
+--- a/teensy4/WCharacter.h
++++ b/teensy4/WCharacter.h
+@@ -61,7 +61,7 @@ inline boolean isAlpha(int c)
+ // that fits into the ASCII character set.
+ inline boolean isAscii(int c)
+ {
+-  return ( isascii (c) == 0 ? false : true);
++  return ((c & ~0x7F) != 0 ? false : true);
+ }
+ 
+ 
+@@ -143,7 +143,7 @@ inline boolean isHexadecimalDigit(int c)
+ // ASCII character set, by clearing the high-order bits.
+ inline int toAscii(int c)
+ {
+-  return toascii (c);
++  return (c & 0x7F);
+ }
+ 
+ 
+diff --git a/teensy4/avr_functions.h b/teensy4/avr_functions.h
+index fb6ca11..3b34391 100644
+--- a/teensy4/avr_functions.h
++++ b/teensy4/avr_functions.h
+@@ -97,7 +97,7 @@ static inline void eeprom_update_block(const void *buf, void *addr, uint32_t len
+ char * ultoa(unsigned long val, char *buf, int radix);
+ char * ltoa(long val, char *buf, int radix);
+ 
+-#if defined(_NEWLIB_VERSION) && (__NEWLIB__ < 2 || __NEWLIB__ == 2 && __NEWLIB_MINOR__ < 2)
++#if defined(__STRICT_ANSI__) || (defined(_NEWLIB_VERSION) && (__NEWLIB__ < 2 || __NEWLIB__ == 2 && __NEWLIB_MINOR__ < 2))
+ static inline char * utoa(unsigned int val, char *buf, int radix) __attribute__((always_inline, unused));
+ static inline char * utoa(unsigned int val, char *buf, int radix) { return ultoa(val, buf, radix); }
+ static inline char * itoa(int val, char *buf, int radix) __attribute__((always_inline, unused));
diff --git a/pw_arduino_build/py/pw_arduino_build/core_patches/teensy/02-teensy4_nonstatic_flash_functions.diff b/pw_arduino_build/py/pw_arduino_build/core_patches/teensy/02-teensy4_nonstatic_flash_functions.diff
new file mode 100644
index 0000000..251f550
--- /dev/null
+++ b/pw_arduino_build/py/pw_arduino_build/core_patches/teensy/02-teensy4_nonstatic_flash_functions.diff
@@ -0,0 +1,42 @@
+diff --git a/teensy4/eeprom.c b/teensy4/eeprom.c
+index dde1809..9cdfcd0 100644
+--- a/teensy4/eeprom.c
++++ b/teensy4/eeprom.c
+@@ -54,8 +54,8 @@
+ // Conversation about how this code works & what the upper limits are
+ // https://forum.pjrc.com/threads/57377?p=214566&viewfull=1#post214566
+ 
+-static void flash_write(void *addr, const void *data, uint32_t len);
+-static void flash_erase_sector(void *addr);
++void flash_write(void *addr, const void *data, uint32_t len);
++void flash_erase_sector(void *addr);
+ 
+ static uint8_t initialized=0;
+ static uint16_t sector_index[FLASH_SECTORS];
+@@ -217,7 +217,7 @@ void eeprom_write_block(const void *buf, void *addr, uint32_t len)
+ #define PINS1           FLEXSPI_LUT_NUM_PADS_1
+ #define PINS4           FLEXSPI_LUT_NUM_PADS_4
+ 
+-static void flash_wait()
++void flash_wait()
+ {
+ 	FLEXSPI_LUT60 = LUT0(CMD_SDR, PINS1, 0x05) | LUT1(READ_SDR, PINS1, 1); // 05 = read status
+ 	FLEXSPI_LUT61 = 0;
+@@ -239,7 +239,7 @@ static void flash_wait()
+ }
+ 
+ // write bytes into flash memory (which is already erased to 0xFF)
+-static void flash_write(void *addr, const void *data, uint32_t len)
++void flash_write(void *addr, const void *data, uint32_t len)
+ {
+ 	__disable_irq();
+ 	FLEXSPI_LUTKEY = FLEXSPI_LUTKEY_VALUE;
+@@ -279,7 +279,7 @@ static void flash_write(void *addr, const void *data, uint32_t len)
+ }
+ 
+ // erase a 4K sector
+-static void flash_erase_sector(void *addr)
++void flash_erase_sector(void *addr)
+ {
+ 	__disable_irq();
+ 	FLEXSPI_LUTKEY = FLEXSPI_LUTKEY_VALUE;
diff --git a/pw_arduino_build/py/pw_arduino_build/file_operations.py b/pw_arduino_build/py/pw_arduino_build/file_operations.py
index df68331..8158a99 100644
--- a/pw_arduino_build/py/pw_arduino_build/file_operations.py
+++ b/pw_arduino_build/py/pw_arduino_build/file_operations.py
@@ -21,6 +21,7 @@
 import os
 import shutil
 import sys
+import subprocess
 import tarfile
 import urllib.request
 import zipfile
@@ -77,7 +78,9 @@
         raise InvalidChecksumError(
             f"Invalid {sum_function.__name__}\n"
             f"{downloaded_checksum} {os.path.basename(file_path)}\n"
-            f"{expected_checksum} (expected)")
+            f"{expected_checksum} (expected)\n\n"
+            "Please delete this file and try again:\n"
+            f"{file_path}")
 
     _LOG.debug("  %s:", sum_function.__name__)
     _LOG.debug("  %s %s", downloaded_checksum, os.path.basename(file_path))
@@ -218,3 +221,14 @@
         _LOG.warning("Unable to read file '%s'", file_path)
 
     return json_file_options, file_path
+
+
+def git_apply_patch(root_directory, patch_file, ignore_whitespace=True):
+    """Use `git apply` to apply a diff file."""
+
+    _LOG.info("Applying Patch: %s", patch_file)
+    git_apply_command = ["git", "apply"]
+    if ignore_whitespace:
+        git_apply_command.append("--ignore-whitespace")
+    git_apply_command += ["--directory", root_directory, patch_file]
+    subprocess.run(git_apply_command)
diff --git a/pw_arduino_build/py/setup.py b/pw_arduino_build/py/setup.py
index 13af5fc..796a617 100644
--- a/pw_arduino_build/py/setup.py
+++ b/pw_arduino_build/py/setup.py
@@ -22,7 +22,10 @@
     author_email='pigweed-developers@googlegroups.com',
     description='Target-specific python scripts for the arduino target',
     packages=setuptools.find_packages(),
-    package_data={'pw_arduino_build': ['py.typed']},
+    package_data={
+        'pw_arduino_build':
+        ['py.typed', 'core_patches/teensy/01-teensyduino_1.53-cpp17.diff']
+    },
     zip_safe=False,
     entry_points={
         'console_scripts': [
diff --git a/targets/arduino/BUILD.gn b/targets/arduino/BUILD.gn
index 146e784..1949252 100644
--- a/targets/arduino/BUILD.gn
+++ b/targets/arduino/BUILD.gn
@@ -79,9 +79,12 @@
 
       # TODO(tonymd): Determine if libs are needed.
       #   Teensy4 core recipe uses: '-larm_cortexM7lfsp_math -lm -lstdc++'
-      # libs = exec_script(arduino_builder_script,
-      #     arduino_show_command_args + [ "--ld-lib-names" ],
-      #     "list lines")
+      libs = filter_exclude(
+              exec_script(arduino_builder_script,
+                          arduino_show_command_args + [ "--ld-lib-names" ],
+                          "list lines"),
+              # Exclude stdc++ which causes linking errors for teensy cores.
+              [ "\bstdc++\b" ])
     }
 
     pw_source_set("pre_init") {
diff --git a/third_party/arduino/BUILD.gn b/third_party/arduino/BUILD.gn
index f7a6f3d..da82742 100644
--- a/third_party/arduino/BUILD.gn
+++ b/third_party/arduino/BUILD.gn
@@ -51,8 +51,24 @@
                     arduino_show_command_args + [ "--variant-cpp-files" ],
                     "list lines")
 
+    # Some cores have required built in libraries:
+    # - stm32duino requires SrcWrapper.
+    _library_c_files =
+        exec_script(arduino_builder_script,
+                    arduino_show_command_args + [ "--library-c-files" ],
+                    "list lines")
+    _library_s_files =
+        exec_script(arduino_builder_script,
+                    arduino_show_command_args + [ "--library-s-files" ],
+                    "list lines")
+    _library_cpp_files =
+        exec_script(arduino_builder_script,
+                    arduino_show_command_args + [ "--library-cpp-files" ],
+                    "list lines")
+
     sources = _core_c_files + _core_s_files + _core_cpp_files +
-              _variant_c_files + _variant_s_files + _variant_cpp_files
+              _variant_c_files + _variant_s_files + _variant_cpp_files +
+              _library_c_files + _library_s_files + _library_cpp_files
 
     # Rename main() to ArduinoMain()
     # See //pw_arduino_build/docs.rst for details on this approach.