Skip to content

Commit d88f658

Browse files
committed
Add support for installing library dependencies from archive downloads
- Archive downloads are often faster than cloning repositories, especially when the repository contains binaries. - Some libraries are not available from Git repositories. The optional source-path field allows installing libraries which are not in the archive's root folder. The optional destination-name field allows installing libraries to a custom named library folder. This is useful for perfectly emulating the library as it will be installed by Library Manager, which uses the library.properties name field to name the library folder.
1 parent 4d7e6f3 commit d88f658

File tree

3 files changed

+91
-9
lines changed

3 files changed

+91
-9
lines changed

README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,12 @@ Keys:
4646
- `source-path` - path to install as a library. Paths are relative to the root of the repository. The default is to install from the root of the repository.
4747
- `destination-name` - folder name to install the library to. By default, the folder will be named according to the source repository or subfolder name.
4848

49+
##### Archive download
50+
51+
Keys:
52+
- `source-url` - download URL for the archive (e.g., `https://github.com/arduino-libraries/Servo/archive/master.zip`).
53+
- `source-path` - path to install as a library. Paths are relative to the root folder of the archive. If the archive doesn't have a root folder, use `..` as `source-path` to install from the archive root. The default is to install from the root folder of the archive.
54+
- `destination-name` - folder name to install the library to. By default, the folder will be named according to the source archive or subfolder name.
4955

5056
### `sketch-paths`
5157

compilesketches/compilesketches.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,9 @@ def sort_dependency_list(self, dependency_list):
238238
or dependency[self.dependency_source_url_key].startswith("git://")
239239
):
240240
sorted_dependencies.repository.append(dependency)
241+
else:
242+
# All other URLs are assumed to be downloads
243+
sorted_dependencies.download.append(dependency)
241244
elif self.dependency_source_path_key in dependency:
242245
# Libraries with source-path and no source-url are assumed to be paths
243246
sorted_dependencies.path.append(dependency)
@@ -254,6 +257,7 @@ def __init__(self):
254257
self.manager = []
255258
self.path = []
256259
self.repository = []
260+
self.download = []
257261

258262
def install_platforms_from_board_manager(self, platform_list, additional_url_list):
259263
"""Install platform dependencies from the Arduino Board Manager
@@ -460,6 +464,9 @@ def install_libraries(self):
460464
if len(library_list.repository) > 0:
461465
self.install_libraries_from_repository(library_list=library_list.repository)
462466

467+
if len(library_list.download) > 0:
468+
self.install_libraries_from_download(library_list=library_list.download)
469+
463470
def install_libraries_from_library_manager(self, library_list):
464471
"""Install libraries using the Arduino Library Manager
465472
@@ -532,6 +539,29 @@ def install_libraries_from_repository(self, library_list):
532539
destination_parent_path=self.libraries_path,
533540
destination_name=destination_name)
534541

542+
def install_libraries_from_download(self, library_list):
543+
"""Install libraries by downloading them
544+
545+
Keyword arguments:
546+
library_list -- list of dictionaries defining the dependencies
547+
"""
548+
for library in library_list:
549+
self.verbose_print("Installing library from download URL:", library[self.dependency_source_url_key])
550+
if self.dependency_source_path_key in library:
551+
source_path = library[self.dependency_source_path_key]
552+
else:
553+
source_path = "."
554+
555+
if self.dependency_destination_name_key in library:
556+
destination_name = library[self.dependency_destination_name_key]
557+
else:
558+
destination_name = None
559+
560+
install_from_download(url=library[self.dependency_source_url_key],
561+
source_path=source_path,
562+
destination_parent_path=self.libraries_path,
563+
destination_name=destination_name)
564+
535565
def find_sketches(self):
536566
"""Return a list of all sketches under the paths specified in the sketch paths list recursively."""
537567
sketch_list = []

compilesketches/tests/test_compilesketches.py

Lines changed: 55 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -333,8 +333,14 @@ def test_install_platforms(mocker, fqbn_arg, expected_platform, expected_additio
333333
[{compilesketches.CompileSketches.dependency_source_url_key: "https://example.com/foo/bar.git/"}],
334334
["repository"]),
335335
([{compilesketches.CompileSketches.dependency_source_url_key: "git://example.com/foo/bar"}], ["repository"]),
336+
([{compilesketches.CompileSketches.dependency_source_url_key: "https://example.com/foo/bar"}], ["download"]),
336337
([{compilesketches.CompileSketches.dependency_source_path_key: "foo/bar"}], ["path"]),
337338
([{compilesketches.CompileSketches.dependency_name_key: "FooBar"}], ["manager"]),
339+
([{compilesketches.CompileSketches.dependency_source_url_key: "git://example.com/foo/bar"},
340+
{compilesketches.CompileSketches.dependency_source_url_key: "https://example.com/foo/bar"},
341+
{compilesketches.CompileSketches.dependency_source_path_key: "foo/bar"},
342+
{compilesketches.CompileSketches.dependency_name_key: "FooBar"}],
343+
["repository", "download", "path", "manager"]),
338344
([{compilesketches.CompileSketches.dependency_source_url_key: "git://example.com/foo/bar"}], ["repository"]),
339345
]
340346
)
@@ -494,24 +500,27 @@ def test_get_repository_dependency_ref(dependency, expected_ref):
494500

495501

496502
@pytest.mark.parametrize(
497-
"libraries, expected_manager, expected_path, expected_repository",
503+
"libraries, expected_manager, expected_path, expected_repository, expected_download",
498504
[("", [], [{compilesketches.CompileSketches.dependency_source_path_key: pathlib.PurePath("/foo/GitHubWorkspace")}],
499-
[]),
505+
[], []),
500506
("foo bar", [{compilesketches.CompileSketches.dependency_name_key: "foo"},
501507
{compilesketches.CompileSketches.dependency_name_key: "bar"}],
502-
[{compilesketches.CompileSketches.dependency_source_path_key: pathlib.PurePath("/foo/GitHubWorkspace")}], []),
508+
[{compilesketches.CompileSketches.dependency_source_path_key: pathlib.PurePath("/foo/GitHubWorkspace")}], [], []),
503509
("\"foo\" \"bar\"", [{compilesketches.CompileSketches.dependency_name_key: "foo"},
504510
{compilesketches.CompileSketches.dependency_name_key: "bar"}],
505-
[{compilesketches.CompileSketches.dependency_source_path_key: pathlib.PurePath("/foo/GitHubWorkspace")}], []),
506-
("-", [], [], []),
511+
[{compilesketches.CompileSketches.dependency_source_path_key: pathlib.PurePath("/foo/GitHubWorkspace")}], [], []),
512+
("-", [], [], [], []),
507513
("- " + compilesketches.CompileSketches.dependency_name_key + ": foo",
508-
[{compilesketches.CompileSketches.dependency_name_key: "foo"}], [], []),
514+
[{compilesketches.CompileSketches.dependency_name_key: "foo"}], [], [], []),
509515
("- " + compilesketches.CompileSketches.dependency_source_path_key + ": /foo/bar", [],
510-
[{compilesketches.CompileSketches.dependency_source_path_key: "/foo/bar"}], []),
516+
[{compilesketches.CompileSketches.dependency_source_path_key: "/foo/bar"}], [], []),
511517
("- " + compilesketches.CompileSketches.dependency_source_url_key + ": https://example.com/foo.git", [], [],
512-
[{"source-url": "https://example.com/foo.git"}])]
518+
[{"source-url": "https://example.com/foo.git"}], []),
519+
("- " + compilesketches.CompileSketches.dependency_source_url_key + ": https://example.com/foo.zip", [], [], [],
520+
[{"source-url": "https://example.com/foo.zip"}])]
513521
)
514-
def test_install_libraries(monkeypatch, mocker, libraries, expected_manager, expected_path, expected_repository):
522+
def test_install_libraries(monkeypatch, mocker, libraries, expected_manager, expected_path, expected_repository,
523+
expected_download):
515524
libraries_path = pathlib.Path("/foo/LibrariesPath")
516525

517526
monkeypatch.setenv("GITHUB_WORKSPACE", "/foo/GitHubWorkspace")
@@ -523,6 +532,7 @@ def test_install_libraries(monkeypatch, mocker, libraries, expected_manager, exp
523532
mocker.patch("compilesketches.CompileSketches.install_libraries_from_library_manager", autospec=True)
524533
mocker.patch("compilesketches.CompileSketches.install_libraries_from_path", autospec=True)
525534
mocker.patch("compilesketches.CompileSketches.install_libraries_from_repository", autospec=True)
535+
mocker.patch("compilesketches.CompileSketches.install_libraries_from_download", autospec=True)
526536

527537
compile_sketches.install_libraries()
528538

@@ -550,6 +560,13 @@ def test_install_libraries(monkeypatch, mocker, libraries, expected_manager, exp
550560
else:
551561
compile_sketches.install_libraries_from_repository.assert_not_called()
552562

563+
if len(expected_download) > 0:
564+
compile_sketches.install_libraries_from_download.assert_called_once_with(
565+
compile_sketches,
566+
library_list=expected_download)
567+
else:
568+
compile_sketches.install_libraries_from_download.assert_not_called()
569+
553570

554571
def test_install_libraries_from_library_manager(mocker):
555572
run_command_output_level = unittest.mock.sentinel.run_command_output_level
@@ -658,6 +675,35 @@ def test_install_libraries_from_repository(mocker):
658675
compile_sketches.install_from_repository.assert_has_calls(calls=install_from_repository_calls)
659676

660677

678+
def test_install_libraries_from_download(mocker):
679+
library_list = [
680+
{compilesketches.CompileSketches.dependency_source_url_key: unittest.mock.sentinel.source_url1,
681+
compilesketches.CompileSketches.dependency_source_path_key: unittest.mock.sentinel.source_path,
682+
compilesketches.CompileSketches.dependency_destination_name_key: unittest.mock.sentinel.destination_name},
683+
{compilesketches.CompileSketches.dependency_source_url_key: unittest.mock.sentinel.source_url2}
684+
]
685+
686+
expected_source_path_list = [unittest.mock.sentinel.source_path, "."]
687+
expected_destination_name_list = [unittest.mock.sentinel.destination_name, None]
688+
689+
compile_sketches = get_compilesketches_object()
690+
691+
mocker.patch("compilesketches.install_from_download", autospec=True)
692+
693+
compile_sketches.install_libraries_from_download(library_list=library_list)
694+
695+
install_libraries_from_download_calls = []
696+
for library, expected_source_path, expected_destination_name in zip(library_list, expected_source_path_list,
697+
expected_destination_name_list):
698+
install_libraries_from_download_calls.append(
699+
unittest.mock.call(url=library[compilesketches.CompileSketches.dependency_source_url_key],
700+
source_path=expected_source_path,
701+
destination_parent_path=compilesketches.CompileSketches.libraries_path,
702+
destination_name=expected_destination_name)
703+
)
704+
compilesketches.install_from_download.assert_has_calls(calls=install_libraries_from_download_calls)
705+
706+
661707
def test_find_sketches(capsys, monkeypatch):
662708
nonexistent_sketch_path = "/foo/NonexistentSketch"
663709

0 commit comments

Comments
 (0)