2 # Copyright 2020 The Pigweed Authors
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
8 # https://www.apache.org/licenses/LICENSE-2.0
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
15 """Arduino Core Installer."""
27 from pathlib import Path
28 from typing import Dict, List
30 import pw_arduino_build.file_operations as file_operations
32 _LOG = logging.getLogger(__name__)
35 class ArduinoCoreNotSupported(Exception):
36 """Exception raised when a given core can not be installed."""
40 _ARDUINO_CORE_ARTIFACTS: Dict[str, Dict] = {
41 # pylint: disable=line-too-long
45 "url": "https://downloads.arduino.cc/arduino-1.8.13-linux64.tar.xz",
46 "file_name": "arduino-1.8.13-linux64.tar.xz",
47 "sha256": "1b20d0ec850a2a63488009518725f058668bb6cb48c321f82dcf47dc4299b4ad",
50 "url": "https://www.pjrc.com/teensy/td_153/TeensyduinoInstall.linux64",
51 "sha256": "2e6cd99a757bc80593ea3de006de4cc934bcb0a6ec74cad8ec327f0289d40f0b",
52 "file_name": "TeensyduinoInstall.linux64",
55 # TODO(tonymd): Handle 32-bit Linux Install?
58 "url": "https://downloads.arduino.cc/arduino-1.8.13-linux32.tar.xz",
59 "file_name": "arduino-1.8.13-linux32.tar.xz",
63 "url": "https://www.pjrc.com/teensy/td_153/TeensyduinoInstall.linux32",
64 "file_name": "TeensyduinoInstall.linux32",
68 # TODO(tonymd): Handle ARM32 (Raspberry Pi) Install?
71 "url": "https://downloads.arduino.cc/arduino-1.8.13-linuxarm.tar.xz",
72 "file_name": "arduino-1.8.13-linuxarm.tar.xz",
76 "url": "https://www.pjrc.com/teensy/td_153/TeensyduinoInstall.linuxarm",
77 "file_name": "TeensyduinoInstall.linuxarm",
81 # TODO(tonymd): Handle ARM64 Install?
84 "url": "https://downloads.arduino.cc/arduino-1.8.13-linuxaarch64.tar.xz",
85 "file_name": "arduino-1.8.13-linuxaarch64.tar.xz",
89 "url": "https://www.pjrc.com/teensy/td_153/TeensyduinoInstall.linuxaarch64",
90 "file_name": "TeensyduinoInstall.linuxaarch64",
96 "url": "https://www.pjrc.com/teensy/td_153/Teensyduino_MacOS_Catalina.zip",
97 "file_name": "Teensyduino_MacOS_Catalina.zip",
98 "sha256": "401ef42c6e83e621cdda20191a4ef9b7db8a214bede5a94a9e26b45f79c64fe2",
103 "url": "https://downloads.arduino.cc/arduino-1.8.13-windows.zip",
104 "file_name": "arduino-1.8.13-windows.zip",
105 "sha256": "78d3e96827b9e9b31b43e516e601c38d670d29f12483e88cbf6d91a0f89ef524",
108 "url": "https://www.pjrc.com/teensy/td_153/TeensyduinoInstall.exe",
109 # The installer should be named 'Teensyduino.exe' instead of
110 # 'TeensyduinoInstall.exe' to trigger a non-admin installation.
111 "file_name": "Teensyduino.exe",
112 "sha256": "88f58681e5c4772c54e462bc88280320e4276e5b316dcab592fe38d96db990a1",
120 "url": "https://github.com/adafruit/ArduinoCore-samd/archive/1.6.2.tar.gz",
121 "file_name": "adafruit-samd-1.6.2.tar.gz",
122 "sha256": "5875f5bc05904c10e6313f02653f28f2f716db639d3d43f5a1d8a83d15339d64",
133 "url": "http://downloads.arduino.cc/cores/samd-1.8.8.tar.bz2",
134 "file_name": "arduino-samd-1.8.8.tar.bz2",
135 "sha256": "7b93eb705cba9125d9ee52eba09b51fb5fe34520ada351508f4253abbc9f27fa",
146 "url": "https://github.com/stm32duino/Arduino_Core_STM32/archive/1.9.0.tar.gz",
147 "file_name": "stm32duino-1.9.0.tar.gz",
148 "sha256": "4f75ba7a117d90392e8f67c58d31d22393749b9cdd3279bc21e7261ec06c62bf",
159 def install_core_command(args: argparse.Namespace):
160 install_core(args.prefix, args.core_name)
163 def install_core(prefix, core_name):
164 install_prefix = os.path.realpath(
165 os.path.expanduser(os.path.expandvars(prefix)))
166 install_dir = os.path.join(install_prefix, core_name)
167 cache_dir = os.path.join(install_prefix, ".cache", core_name)
169 if core_name in supported_cores():
170 shutil.rmtree(install_dir, ignore_errors=True)
171 os.makedirs(install_dir, exist_ok=True)
172 os.makedirs(cache_dir, exist_ok=True)
174 if core_name == "teensy":
175 if platform.system() == "Linux":
176 install_teensy_core_linux(install_prefix, install_dir, cache_dir)
177 elif platform.system() == "Darwin":
178 install_teensy_core_mac(install_prefix, install_dir, cache_dir)
179 elif platform.system() == "Windows":
180 install_teensy_core_windows(install_prefix, install_dir, cache_dir)
181 apply_teensy_patches(install_dir)
182 elif core_name == "adafruit-samd":
183 install_adafruit_samd_core(install_prefix, install_dir, cache_dir)
184 elif core_name == "stm32duino":
185 install_stm32duino_core(install_prefix, install_dir, cache_dir)
186 elif core_name == "arduino-samd":
187 install_arduino_samd_core(install_prefix, install_dir, cache_dir)
189 raise ArduinoCoreNotSupported(
190 "Invalid core '{}'. Supported cores: {}".format(
191 core_name, ", ".join(supported_cores())))
194 def supported_cores():
195 return _ARDUINO_CORE_ARTIFACTS.keys()
198 def get_windows_process_names() -> List[str]:
199 result = subprocess.run("wmic process get description",
201 output = result.stdout.decode().splitlines()
202 return [line.strip() for line in output if line]
205 def install_teensy_core_windows(install_prefix, install_dir, cache_dir):
206 """Download and install Teensyduino artifacts for Windows."""
207 teensy_artifacts = _ARDUINO_CORE_ARTIFACTS["teensy"][platform.system()]
209 arduino_artifact = teensy_artifacts["arduino-ide"]
210 arduino_zipfile = file_operations.download_to_cache(
211 url=arduino_artifact["url"],
212 expected_sha256sum=arduino_artifact["sha256"],
213 cache_directory=cache_dir,
214 downloaded_file_name=arduino_artifact["file_name"])
216 teensyduino_artifact = teensy_artifacts["teensyduino"]
217 teensyduino_installer = file_operations.download_to_cache(
218 url=teensyduino_artifact["url"],
219 expected_sha256sum=teensyduino_artifact["sha256"],
220 cache_directory=cache_dir,
221 downloaded_file_name=teensyduino_artifact["file_name"])
223 file_operations.extract_archive(arduino_zipfile, install_dir, cache_dir)
225 # "teensy" here should match args.core_name
226 teensy_core_dir = os.path.join(install_prefix, "teensy")
228 # Change working directory for installation
229 original_working_dir = os.getcwd()
230 os.chdir(install_prefix)
232 install_command = [teensyduino_installer, "--dir=teensy"]
233 _LOG.info(" Running: %s", " ".join(install_command))
234 _LOG.info(" Please click yes on the Windows 'User Account Control' "
236 _LOG.info(" You should see: 'Verified publisher: PRJC.COM LLC'")
238 def wait_for_process(process_name,
240 result_operator=operator.truth):
241 start_time = time.time()
242 while result_operator(process_name in get_windows_process_names()):
244 if time.time() > start_time + timeout:
246 "Error: Installation Failed.\n"
247 "Please click yes on the Windows 'User Account Control' "
251 # Run Teensyduino installer with admin rights (non-blocking)
252 # User Account Control (UAC) will prompt the user for consent
253 import ctypes # pylint: disable=import-outside-toplevel
254 ctypes.windll.shell32.ShellExecuteW(
255 None, # parent window handle
257 teensyduino_installer, # file to run
258 subprocess.list2cmdline(install_command), # command parameters
259 install_prefix, # working directory
260 1) # Display mode (SW_SHOWNORMAL: Activates and displays a window)
262 # Wait for teensyduino_installer to start running
263 wait_for_process("TeensyduinoInstall.exe", result_operator=operator.not_)
265 _LOG.info("Waiting for TeensyduinoInstall.exe to finish.")
266 # Wait till teensyduino_installer is finished
267 wait_for_process("TeensyduinoInstall.exe", timeout=360)
269 if not os.path.exists(os.path.join(teensy_core_dir, "hardware", "teensy")):
271 "Error: Installation Failed.\n"
272 "Please try again and ensure Teensyduino is installed in "
274 "%s", teensy_core_dir)
277 _LOG.info("Install complete!")
279 file_operations.remove_empty_directories(install_dir)
280 os.chdir(original_working_dir)
283 def install_teensy_core_mac(unused_install_prefix, install_dir, cache_dir):
284 """Download and install Teensyduino artifacts for Mac."""
285 teensy_artifacts = _ARDUINO_CORE_ARTIFACTS["teensy"][platform.system()]
287 teensyduino_artifact = teensy_artifacts["teensyduino"]
288 teensyduino_zip = file_operations.download_to_cache(
289 url=teensyduino_artifact["url"],
290 expected_sha256sum=teensyduino_artifact["sha256"],
291 cache_directory=cache_dir,
292 downloaded_file_name=teensyduino_artifact["file_name"])
294 extracted_files = file_operations.extract_archive(
298 remove_single_toplevel_folder=False)
299 toplevel_folder = sorted(extracted_files)[0]
300 os.symlink(os.path.join(toplevel_folder, "Contents", "Java", "hardware"),
301 os.path.join(install_dir, "hardware"),
302 target_is_directory=True)
305 def install_teensy_core_linux(install_prefix, install_dir, cache_dir):
306 """Download and install Teensyduino artifacts for Windows."""
307 teensy_artifacts = _ARDUINO_CORE_ARTIFACTS["teensy"][platform.system()]
309 arduino_artifact = teensy_artifacts["arduino-ide"]
310 arduino_tarfile = file_operations.download_to_cache(
311 url=arduino_artifact["url"],
312 expected_sha256sum=arduino_artifact["sha256"],
313 cache_directory=cache_dir,
314 downloaded_file_name=arduino_artifact["file_name"])
316 teensyduino_artifact = teensy_artifacts["teensyduino"]
317 teensyduino_installer = file_operations.download_to_cache(
318 url=teensyduino_artifact["url"],
319 expected_sha256sum=teensyduino_artifact["sha256"],
320 cache_directory=cache_dir,
321 downloaded_file_name=teensyduino_artifact["file_name"])
323 file_operations.extract_archive(arduino_tarfile, install_dir, cache_dir)
324 os.chmod(teensyduino_installer,
325 os.stat(teensyduino_installer).st_mode | stat.S_IEXEC)
327 original_working_dir = os.getcwd()
328 os.chdir(install_prefix)
329 # "teensy" here should match args.core_name
330 install_command = [teensyduino_installer, "--dir=teensy"]
331 subprocess.run(install_command)
333 file_operations.remove_empty_directories(install_dir)
334 os.chdir(original_working_dir)
337 def apply_teensy_patches(install_dir):
338 # On Mac the "hardware" directory is a symlink:
339 # ls -l third_party/arduino/cores/teensy/
340 # hardware -> Teensyduino.app/Contents/Java/hardware
341 # Resolve paths since `git apply` doesn't work if a path is beyond a
343 patch_root_path = (Path(install_dir) /
344 "hardware/teensy/avr/cores").resolve()
346 # Get all *.diff files relative to this python file's parent directory.
347 patch_file_paths = sorted(
348 (Path(__file__).parent / "core_patches/teensy").glob("*.diff"))
350 # Apply each patch file.
351 for diff_path in patch_file_paths:
352 file_operations.git_apply_patch(patch_root_path.as_posix(),
353 diff_path.as_posix(),
357 def install_arduino_samd_core(install_prefix: str, install_dir: str,
359 artifacts = _ARDUINO_CORE_ARTIFACTS["arduino-samd"]["all"]["core"]
360 core_tarfile = file_operations.download_to_cache(
361 url=artifacts["url"],
362 expected_sha256sum=artifacts["sha256"],
363 cache_directory=cache_dir,
364 downloaded_file_name=artifacts["file_name"])
366 package_path = os.path.join(install_dir, "hardware", "samd",
367 artifacts["version"])
368 os.makedirs(package_path, exist_ok=True)
369 file_operations.extract_archive(core_tarfile, package_path, cache_dir)
370 original_working_dir = os.getcwd()
371 os.chdir(install_prefix)
372 # TODO(tonymd): Fetch core/tools as specified by:
373 # http://downloads.arduino.cc/packages/package_index.json
374 os.chdir(original_working_dir)
378 def install_adafruit_samd_core(install_prefix: str, install_dir: str,
380 artifacts = _ARDUINO_CORE_ARTIFACTS["adafruit-samd"]["all"]["core"]
381 core_tarfile = file_operations.download_to_cache(
382 url=artifacts["url"],
383 expected_sha256sum=artifacts["sha256"],
384 cache_directory=cache_dir,
385 downloaded_file_name=artifacts["file_name"])
387 package_path = os.path.join(install_dir, "hardware", "samd",
388 artifacts["version"])
389 os.makedirs(package_path, exist_ok=True)
390 file_operations.extract_archive(core_tarfile, package_path, cache_dir)
392 original_working_dir = os.getcwd()
393 os.chdir(install_prefix)
394 # TODO(tonymd): Fetch platform specific tools as specified by:
395 # https://adafruit.github.io/arduino-board-index/package_adafruit_index.json
397 # https://github.com/ARM-software/CMSIS_5/archive/5.4.0.tar.gz
398 os.chdir(original_working_dir)
402 def install_stm32duino_core(install_prefix, install_dir, cache_dir):
403 artifacts = _ARDUINO_CORE_ARTIFACTS["stm32duino"]["all"]["core"]
404 core_tarfile = file_operations.download_to_cache(
405 url=artifacts["url"],
406 expected_sha256sum=artifacts["sha256"],
407 cache_directory=cache_dir,
408 downloaded_file_name=artifacts["file_name"])
410 package_path = os.path.join(install_dir, "hardware", "stm32",
411 artifacts["version"])
412 os.makedirs(package_path, exist_ok=True)
413 file_operations.extract_archive(core_tarfile, package_path, cache_dir)
414 original_working_dir = os.getcwd()
415 os.chdir(install_prefix)
416 # TODO(tonymd): Fetch platform specific tools as specified by:
417 # https://github.com/stm32duino/BoardManagerFiles/raw/master/STM32/package_stm_index.json
418 os.chdir(original_working_dir)