Fix for x86_64 build fail
[platform/upstream/connectedhomeip.git] / third_party / pigweed / repo / pw_arduino_build / py / pw_arduino_build / core_installer.py
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
17 import argparse
18 import logging
19 import operator
20 import os
21 import platform
22 import shutil
23 import stat
24 import subprocess
25 import sys
26 import time
27 from pathlib import Path
28 from typing import Dict, List
29
30 import pw_arduino_build.file_operations as file_operations
31
32 _LOG = logging.getLogger(__name__)
33
34
35 class ArduinoCoreNotSupported(Exception):
36     """Exception raised when a given core can not be installed."""
37
38
39 # yapf: disable
40 _ARDUINO_CORE_ARTIFACTS: Dict[str, Dict] = {
41     # pylint: disable=line-too-long
42     "teensy": {
43         "Linux": {
44             "arduino-ide": {
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",
48             },
49             "teensyduino": {
50                 "url": "https://www.pjrc.com/teensy/td_153/TeensyduinoInstall.linux64",
51                 "sha256": "2e6cd99a757bc80593ea3de006de4cc934bcb0a6ec74cad8ec327f0289d40f0b",
52                 "file_name": "TeensyduinoInstall.linux64",
53             },
54         },
55         # TODO(tonymd): Handle 32-bit Linux Install?
56         "Linux32": {
57             "arduino-ide": {
58                 "url": "https://downloads.arduino.cc/arduino-1.8.13-linux32.tar.xz",
59                 "file_name": "arduino-1.8.13-linux32.tar.xz",
60                 "sha256": "",
61             },
62             "teensyduino": {
63                 "url": "https://www.pjrc.com/teensy/td_153/TeensyduinoInstall.linux32",
64                 "file_name": "TeensyduinoInstall.linux32",
65                 "sha256": "",
66             },
67         },
68         # TODO(tonymd): Handle ARM32 (Raspberry Pi) Install?
69         "LinuxARM32": {
70             "arduino-ide": {
71                 "url": "https://downloads.arduino.cc/arduino-1.8.13-linuxarm.tar.xz",
72                 "file_name": "arduino-1.8.13-linuxarm.tar.xz",
73                 "sha256": "",
74             },
75             "teensyduino": {
76                 "url": "https://www.pjrc.com/teensy/td_153/TeensyduinoInstall.linuxarm",
77                 "file_name": "TeensyduinoInstall.linuxarm",
78                 "sha256": "",
79             },
80         },
81         # TODO(tonymd): Handle ARM64 Install?
82         "LinuxARM64": {
83             "arduino-ide": {
84                 "url": "https://downloads.arduino.cc/arduino-1.8.13-linuxaarch64.tar.xz",
85                 "file_name": "arduino-1.8.13-linuxaarch64.tar.xz",
86                 "sha256": "",
87             },
88             "teensyduino": {
89                 "url": "https://www.pjrc.com/teensy/td_153/TeensyduinoInstall.linuxaarch64",
90                 "file_name": "TeensyduinoInstall.linuxaarch64",
91                 "sha256": "",
92             },
93         },
94         "Darwin": {
95             "teensyduino": {
96                 "url": "https://www.pjrc.com/teensy/td_153/Teensyduino_MacOS_Catalina.zip",
97                 "file_name": "Teensyduino_MacOS_Catalina.zip",
98                 "sha256": "401ef42c6e83e621cdda20191a4ef9b7db8a214bede5a94a9e26b45f79c64fe2",
99             },
100         },
101         "Windows": {
102             "arduino-ide": {
103                 "url": "https://downloads.arduino.cc/arduino-1.8.13-windows.zip",
104                 "file_name": "arduino-1.8.13-windows.zip",
105                 "sha256": "78d3e96827b9e9b31b43e516e601c38d670d29f12483e88cbf6d91a0f89ef524",
106             },
107             "teensyduino": {
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",
113             },
114         }
115     },
116     "adafruit-samd": {
117         "all": {
118             "core": {
119                 "version": "1.6.2",
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",
123             }
124         },
125         "Linux": {},
126         "Darwin": {},
127         "Windows": {},
128     },
129     "arduino-samd": {
130         "all": {
131             "core": {
132                 "version": "1.8.8",
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",
136             }
137         },
138         "Linux": {},
139         "Darwin": {},
140         "Windows": {},
141     },
142     "stm32duino": {
143         "all": {
144             "core": {
145                 "version": "1.9.0",
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",
149             }
150         },
151         "Linux": {},
152         "Darwin": {},
153         "Windows": {},
154     },
155 }
156 # yapf: enable
157
158
159 def install_core_command(args: argparse.Namespace):
160     install_core(args.prefix, args.core_name)
161
162
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)
168
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)
173
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)
188     else:
189         raise ArduinoCoreNotSupported(
190             "Invalid core '{}'. Supported cores: {}".format(
191                 core_name, ", ".join(supported_cores())))
192
193
194 def supported_cores():
195     return _ARDUINO_CORE_ARTIFACTS.keys()
196
197
198 def get_windows_process_names() -> List[str]:
199     result = subprocess.run("wmic process get description",
200                             capture_output=True)
201     output = result.stdout.decode().splitlines()
202     return [line.strip() for line in output if line]
203
204
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()]
208
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"])
215
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"])
222
223     file_operations.extract_archive(arduino_zipfile, install_dir, cache_dir)
224
225     # "teensy" here should match args.core_name
226     teensy_core_dir = os.path.join(install_prefix, "teensy")
227
228     # Change working directory for installation
229     original_working_dir = os.getcwd()
230     os.chdir(install_prefix)
231
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' "
235               "dialog.")
236     _LOG.info("    You should see: 'Verified publisher: PRJC.COM LLC'")
237
238     def wait_for_process(process_name,
239                          timeout=30,
240                          result_operator=operator.truth):
241         start_time = time.time()
242         while result_operator(process_name in get_windows_process_names()):
243             time.sleep(1)
244             if time.time() > start_time + timeout:
245                 _LOG.error(
246                     "Error: Installation Failed.\n"
247                     "Please click yes on the Windows 'User Account Control' "
248                     "dialog.")
249                 sys.exit(1)
250
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
256         "runas",  # operation
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)
261
262     # Wait for teensyduino_installer to start running
263     wait_for_process("TeensyduinoInstall.exe", result_operator=operator.not_)
264
265     _LOG.info("Waiting for TeensyduinoInstall.exe to finish.")
266     # Wait till teensyduino_installer is finished
267     wait_for_process("TeensyduinoInstall.exe", timeout=360)
268
269     if not os.path.exists(os.path.join(teensy_core_dir, "hardware", "teensy")):
270         _LOG.error(
271             "Error: Installation Failed.\n"
272             "Please try again and ensure Teensyduino is installed in "
273             "the folder:\n"
274             "%s", teensy_core_dir)
275         sys.exit(1)
276     else:
277         _LOG.info("Install complete!")
278
279     file_operations.remove_empty_directories(install_dir)
280     os.chdir(original_working_dir)
281
282
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()]
286
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"])
293
294     extracted_files = file_operations.extract_archive(
295         teensyduino_zip,
296         install_dir,
297         cache_dir,
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)
303
304
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()]
308
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"])
315
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"])
322
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)
326
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)
332
333     file_operations.remove_empty_directories(install_dir)
334     os.chdir(original_working_dir)
335
336
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
342     # symbolic link.
343     patch_root_path = (Path(install_dir) /
344                        "hardware/teensy/avr/cores").resolve()
345
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"))
349
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(),
354                                         unsafe_paths=True)
355
356
357 def install_arduino_samd_core(install_prefix: str, install_dir: str,
358                               cache_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"])
365
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)
375     return True
376
377
378 def install_adafruit_samd_core(install_prefix: str, install_dir: str,
379                                cache_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"])
386
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)
391
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
396     # Specifically:
397     #   https://github.com/ARM-software/CMSIS_5/archive/5.4.0.tar.gz
398     os.chdir(original_working_dir)
399     return True
400
401
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"])
409
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)
419     return True