• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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