1#!/usr/bin/env python3 2# Copyright 2020 The Pigweed Authors 3# 4# Licensed under the Apache License, Version 2.0 (the "License"); you may not 5# use this file except in compliance with the License. You may obtain a copy of 6# the License at 7# 8# https://www.apache.org/licenses/LICENSE-2.0 9# 10# Unless required by applicable law or agreed to in writing, software 11# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13# License for the specific language governing permissions and limitations under 14# the License. 15"""Arduino Core Installer.""" 16 17import argparse 18import importlib.resources 19import logging 20import os 21from pathlib import Path 22import platform 23import shutil 24 25try: 26 from pw_arduino_build import file_operations 27 28except ImportError: 29 # Load this directory if pw_arduino_build is not available. 30 import file_operations # type: ignore 31 32_LOG = logging.getLogger(__name__) 33 34 35class ArduinoCoreNotSupported(Exception): 36 """Exception raised when a given core can not be installed.""" 37 38 39class ArduinoCoreInstallationFailed(Exception): 40 """Exception raised when a given core failed to be installed.""" 41 42 43_ARDUINO_CORE_ARTIFACTS: dict[str, dict] = { 44 # pylint: disable=line-too-long 45 "teensy": { 46 "all": { 47 "core": { 48 "version": "1.58.1", 49 "url": "https://www.pjrc.com/teensy/td_158-1/teensy-package.tar.bz2", 50 "file_name": "teensy-package.tar.bz2", 51 "sha256": "3a3f3728045621d25068c5b5dfc24bf171550127e9fae4d0e8be2574c6636cff", 52 } 53 }, 54 "Linux": { 55 "teensy-tools": { 56 "url": "https://www.pjrc.com/teensy/td_158/teensy-tools-linux64.tar.bz2", 57 "file_name": "teensy-tools-linux64.tar.bz2", 58 "sha256": "0a272575ca42b4532ab89516df160e1d68e449fe1538c0bd71dbb768f1b3c0b6", 59 "version": "1.58.0", 60 }, 61 }, 62 # TODO(tonymd): Handle 32-bit Linux Install? 63 "Linux32": { 64 "teensy-tools": { 65 "url": "https://www.pjrc.com/teensy/td_158/teensy-tools-linux32.tar.bz2", 66 "file_name": "teensy-tools-linux32.tar.bz2", 67 "sha256": "995d974935c8118ad6d4c191206453dd8f57c1e299264bb4cffcc62c96c6d077", 68 "version": "1.58.0", 69 }, 70 }, 71 # TODO(tonymd): Handle ARM32 (Raspberry Pi) Install? 72 "LinuxARM32": { 73 "teensy-tools": { 74 "url": "https://www.pjrc.com/teensy/td_158/teensy-tools-linuxarm.tar.bz2", 75 "file_name": "teensy-tools-linuxarm.tar.bz2", 76 "sha256": "88cf8e55549f5d5937fa7dbc763cad49bd3680d4e5185b318c667f541035e633", 77 "version": "1.58.0", 78 }, 79 }, 80 # TODO(tonymd): Handle ARM64 Install? 81 "LinuxARM64": { 82 "teensy-tools": { 83 "url": "https://www.pjrc.com/teensy/td_158/teensy-tools-linuxaarch64.tar.bz2", 84 "file_name": "teensy-tools-linuxaarch64.tar.bz2", 85 "sha256": "a20b1c5e91fe51c3b6591e4cfcf711d4a4c0a0bb5120c59d1c8dd8d32ae44e31", 86 "version": "1.58.0", 87 }, 88 }, 89 "Darwin": { 90 "teensy-tools": { 91 "url": "https://www.pjrc.com/teensy/td_158/teensy-tools-macos.tar.bz2", 92 "file_name": "teensy-tools-macos.tar.bz2", 93 "sha256": "d386412e38fe6dd6c5d849c2b1f8eea00cbf7bc3659fb6ba9f83cebfb736924b", 94 "version": "1.58.0", 95 }, 96 }, 97 "Windows": { 98 "teensy-tools": { 99 "url": "https://www.pjrc.com/teensy/td_158/teensy-tools-windows.tar.bz2", 100 "file_name": "teensy-tools-windows.tar.bz2", 101 "sha256": "206315ddc82381d2da92da9f633a1719e00c0e8f5432acfed434573409a48de1", 102 "version": "1.58.0", 103 }, 104 }, 105 }, 106 "adafruit-samd": { 107 "all": { 108 "core": { 109 "version": "1.6.2", 110 "url": "https://github.com/adafruit/ArduinoCore-samd/archive/1.6.2.tar.gz", 111 "file_name": "adafruit-samd-1.6.2.tar.gz", 112 "sha256": "5875f5bc05904c10e6313f02653f28f2f716db639d3d43f5a1d8a83d15339d64", 113 } 114 }, 115 "Linux": {}, 116 "Darwin": {}, 117 "Windows": {}, 118 }, 119 "arduino-samd": { 120 "all": { 121 "core": { 122 "version": "1.8.8", 123 "url": "http://downloads.arduino.cc/cores/samd-1.8.8.tar.bz2", 124 "file_name": "arduino-samd-1.8.8.tar.bz2", 125 "sha256": "7b93eb705cba9125d9ee52eba09b51fb5fe34520ada351508f4253abbc9f27fa", 126 } 127 }, 128 "Linux": {}, 129 "Darwin": {}, 130 "Windows": {}, 131 }, 132 "stm32duino": { 133 "all": { 134 "core": { 135 "version": "2.6.0", 136 "url": "https://github.com/stm32duino/Arduino_Core_STM32/archive/2.6.0.tar.gz", 137 "file_name": "stm32duino-2.6.0.tar.gz", 138 "sha256": "53f37df1202b1bccfb353e4775200f63b36d487fe734fdb8ca9bfa00c2f3429f", 139 } 140 }, 141 "Linux": {}, 142 "Darwin": {}, 143 "Windows": {}, 144 }, 145} 146 147 148def install_core_command(args: argparse.Namespace): 149 install_core(args.prefix, args.core_name) 150 151 152def install_core(prefix, core_name): 153 install_prefix = os.path.realpath( 154 os.path.expanduser(os.path.expandvars(prefix)) 155 ) 156 install_dir = os.path.join(install_prefix, core_name) 157 cache_dir = os.path.join(install_prefix, ".cache", core_name) 158 159 if core_name in supported_cores(): 160 shutil.rmtree(install_dir, ignore_errors=True) 161 os.makedirs(install_dir, exist_ok=True) 162 os.makedirs(cache_dir, exist_ok=True) 163 164 if core_name == "teensy": 165 install_teensy_core(install_prefix, install_dir, cache_dir) 166 apply_teensy_patches(install_dir) 167 elif core_name == "adafruit-samd": 168 install_adafruit_samd_core(install_prefix, install_dir, cache_dir) 169 elif core_name == "stm32duino": 170 install_stm32duino_core(install_prefix, install_dir, cache_dir) 171 elif core_name == "arduino-samd": 172 install_arduino_samd_core(install_prefix, install_dir, cache_dir) 173 else: 174 raise ArduinoCoreNotSupported( 175 "Invalid core '{}'. Supported cores: {}".format( 176 core_name, ", ".join(supported_cores()) 177 ) 178 ) 179 180 181def supported_cores(): 182 return _ARDUINO_CORE_ARTIFACTS.keys() 183 184 185def install_teensy_core(_install_prefix: str, install_dir: str, cache_dir: str): 186 """Install teensy core files and tools.""" 187 # Install the Teensy core source files 188 artifacts = _ARDUINO_CORE_ARTIFACTS["teensy"]["all"]["core"] 189 core_tarfile = file_operations.download_to_cache( 190 url=artifacts["url"], 191 expected_sha256sum=artifacts["sha256"], 192 cache_directory=cache_dir, 193 downloaded_file_name=artifacts["file_name"], 194 ) 195 196 package_path = os.path.join( 197 install_dir, "hardware", "avr", artifacts["version"] 198 ) 199 os.makedirs(package_path, exist_ok=True) 200 file_operations.extract_archive(core_tarfile, package_path, cache_dir) 201 202 expected_files = [ 203 Path(package_path) / 'boards.txt', 204 Path(package_path) / 'platform.txt', 205 ] 206 207 if any(not expected_file.is_file() for expected_file in expected_files): 208 expected_files_str = "".join( 209 list(f" {expected_file}\n" for expected_file in expected_files) 210 ) 211 212 raise ArduinoCoreInstallationFailed( 213 "\n\nError: Installation Failed.\n" 214 "Please remove the package:\n\n" 215 " pw package remove teensy\n\n" 216 "Try again and ensure the following files exist:\n\n" 217 + expected_files_str 218 ) 219 220 teensy_tools = _ARDUINO_CORE_ARTIFACTS["teensy"][platform.system()] 221 222 for tool_name, artifacts in teensy_tools.items(): 223 tool_tarfile = file_operations.download_to_cache( 224 url=artifacts["url"], 225 expected_sha256sum=artifacts["sha256"], 226 cache_directory=cache_dir, 227 downloaded_file_name=artifacts["file_name"], 228 ) 229 230 tool_path = os.path.join( 231 install_dir, "tools", tool_name, artifacts["version"] 232 ) 233 234 os.makedirs(tool_path, exist_ok=True) 235 file_operations.extract_archive(tool_tarfile, tool_path, cache_dir) 236 237 return True 238 239 240def apply_teensy_patches(install_dir): 241 # On Mac the "hardware" directory is a symlink: 242 # ls -l third_party/arduino/cores/teensy/ 243 # hardware -> Teensyduino.app/Contents/Java/hardware 244 # Resolve paths since `git apply` doesn't work if a path is beyond a 245 # symbolic link. 246 patch_root_path = ( 247 Path(install_dir) / "hardware/avr/1.58.1/cores" 248 ).resolve() 249 250 # Get all *.diff files for the teensy core. 251 patches_python_package = 'pw_arduino_build.core_patches.teensy' 252 253 patch_file_names = sorted( 254 patch 255 for patch in importlib.resources.contents(patches_python_package) 256 if Path(patch).suffix in ['.diff'] 257 ) 258 259 # Apply each patch file. 260 for diff_name in patch_file_names: 261 with importlib.resources.path( 262 patches_python_package, diff_name 263 ) as diff_path: 264 file_operations.git_apply_patch( 265 patch_root_path.as_posix(), 266 diff_path.as_posix(), 267 unsafe_paths=True, 268 ) 269 270 271def install_arduino_samd_core( 272 install_prefix: str, install_dir: str, cache_dir: str 273): 274 artifacts = _ARDUINO_CORE_ARTIFACTS["arduino-samd"]["all"]["core"] 275 core_tarfile = file_operations.download_to_cache( 276 url=artifacts["url"], 277 expected_sha256sum=artifacts["sha256"], 278 cache_directory=cache_dir, 279 downloaded_file_name=artifacts["file_name"], 280 ) 281 282 package_path = os.path.join( 283 install_dir, "hardware", "samd", artifacts["version"] 284 ) 285 os.makedirs(package_path, exist_ok=True) 286 file_operations.extract_archive(core_tarfile, package_path, cache_dir) 287 original_working_dir = os.getcwd() 288 os.chdir(install_prefix) 289 # TODO(tonymd): Fetch core/tools as specified by: 290 # http://downloads.arduino.cc/packages/package_index.json 291 os.chdir(original_working_dir) 292 return True 293 294 295def install_adafruit_samd_core( 296 install_prefix: str, install_dir: str, cache_dir: str 297): 298 artifacts = _ARDUINO_CORE_ARTIFACTS["adafruit-samd"]["all"]["core"] 299 core_tarfile = file_operations.download_to_cache( 300 url=artifacts["url"], 301 expected_sha256sum=artifacts["sha256"], 302 cache_directory=cache_dir, 303 downloaded_file_name=artifacts["file_name"], 304 ) 305 306 package_path = os.path.join( 307 install_dir, "hardware", "samd", artifacts["version"] 308 ) 309 os.makedirs(package_path, exist_ok=True) 310 file_operations.extract_archive(core_tarfile, package_path, cache_dir) 311 312 original_working_dir = os.getcwd() 313 os.chdir(install_prefix) 314 # TODO(tonymd): Fetch platform specific tools as specified by: 315 # https://adafruit.github.io/arduino-board-index/package_adafruit_index.json 316 # Specifically: 317 # https://github.com/ARM-software/CMSIS_5/archive/5.4.0.tar.gz 318 os.chdir(original_working_dir) 319 return True 320 321 322def install_stm32duino_core(install_prefix, install_dir, cache_dir): 323 artifacts = _ARDUINO_CORE_ARTIFACTS["stm32duino"]["all"]["core"] 324 core_tarfile = file_operations.download_to_cache( 325 url=artifacts["url"], 326 expected_sha256sum=artifacts["sha256"], 327 cache_directory=cache_dir, 328 downloaded_file_name=artifacts["file_name"], 329 ) 330 331 package_path = os.path.join( 332 install_dir, "hardware", "stm32", artifacts["version"] 333 ) 334 os.makedirs(package_path, exist_ok=True) 335 file_operations.extract_archive(core_tarfile, package_path, cache_dir) 336 original_working_dir = os.getcwd() 337 os.chdir(install_prefix) 338 # TODO(tonymd): Fetch platform specific tools as specified by: 339 # https://github.com/stm32duino/BoardManagerFiles/raw/HEAD/STM32/package_stm_index.json 340 os.chdir(original_working_dir) 341 return True 342