Skip to content

Commit d8c807f

Browse files
committed
Allow the libraries input to take YAML-format lists of dependencies
As more library sources are added, it will be necessary to use data structures to fully define the dependencies. Since the workflow is already written in YAML, it makes sense to use it for this purpose as well. Support for the old space-separated list format is retained, with the identical behavior, for backwards compatibility.
1 parent 0492047 commit d8c807f

File tree

5 files changed

+130
-23
lines changed

5 files changed

+130
-23
lines changed

README.md

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,18 @@ For 3rd party boards, also specify the Boards Manager URL:
1818
1919
### `libraries`
2020

21-
List of library dependencies to install (space separated). Default `""`.
21+
YAML-format list of library dependencies to install.
22+
23+
Note: the original space-separated list format is also supported. When this syntax is used, the repository under test will always be installed as a library.
24+
25+
#### Sources:
26+
27+
##### Library Manager
28+
29+
Keys:
30+
- `name` - name of the library.
31+
- `version` - version of the library to install. Default is the latest version.
32+
2233

2334
### `sketch-paths`
2435

@@ -106,6 +117,10 @@ Only compiling examples:
106117
- uses: arduino/actions/libraries/compile-examples@master
107118
with:
108119
fqbn: 'arduino:avr:uno'
120+
libraries: |
121+
- name: Servo
122+
- name: Stepper
123+
version: 1.1.3
109124
```
110125

111126
Storing the memory usage change report as a [workflow artifact](https://help.github.com/en/actions/configuring-and-managing-workflows/persisting-workflow-data-using-artifacts):

action.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ inputs:
88
description: 'Full qualified board name, with Boards Manager URL if needed'
99
default: 'arduino:avr:uno'
1010
libraries:
11-
description: 'List of library dependencies to install (space separated)'
1211
default: ''
12+
description: 'YAML-format list of library dependencies to install'
1313
sketch-paths:
1414
description: 'List of paths containing sketches to compile.'
1515
default: 'examples'

compilesketches/compilesketches.py

Lines changed: 61 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414

1515
import git
1616
import github
17+
import yaml
18+
import yaml.parser
1719

1820
import reportsizetrends
1921

@@ -84,6 +86,11 @@ class RunCommandOutput(enum.Enum):
8486
report_previous_ram_key = "previous_ram"
8587
report_ram_delta_key = "ram_delta"
8688

89+
dependency_name_key = "name"
90+
dependency_version_key = "version"
91+
92+
latest_release_indicator = "latest"
93+
8794
def __init__(self, cli_version, fqbn_arg, libraries, sketch_paths, verbose, github_token, report_sketch,
8895
enable_size_deltas_report, sketches_report_path, enable_size_trends_report, google_key_file,
8996
size_trends_report_spreadsheet_id, size_trends_report_sheet_name):
@@ -225,6 +232,25 @@ def install_platforms(self):
225232
self.install_platforms_from_board_manager(platform_list=[fqbn_platform_name],
226233
additional_url_list=additional_url_list)
227234

235+
def sort_dependency_list(self, dependency_list):
236+
"""Sort a list of sketch dependencies by source type
237+
238+
Keyword arguments:
239+
dependency_list -- a list of dictionaries defining dependencies
240+
"""
241+
sorted_dependencies = self.Dependencies()
242+
for dependency in dependency_list:
243+
if dependency is not None:
244+
sorted_dependencies.manager.append(dependency)
245+
246+
return sorted_dependencies
247+
248+
class Dependencies:
249+
"""Container for sorted sketch dependencies"""
250+
251+
def __init__(self):
252+
self.manager = []
253+
228254
def install_platforms_from_board_manager(self, platform_list, additional_url_list):
229255
"""Install platform dependencies from the Arduino Board Manager
230256
@@ -250,6 +276,20 @@ def install_platforms_from_board_manager(self, platform_list, additional_url_lis
250276
# Install the platforms
251277
self.run_arduino_cli_command(command=core_install_command, enable_output=self.get_run_command_output_level())
252278

279+
def get_manager_dependency_name(self, dependency):
280+
"""Return the appropriate name value for a repository dependency
281+
282+
Keyword arguments:
283+
dependency -- dictionary defining the Library/Board Manger dependency
284+
"""
285+
name = dependency[self.dependency_name_key]
286+
if self.dependency_version_key in dependency:
287+
# If "latest" special version name is used, just don't add a version to cause LM to use the latest release
288+
if dependency[self.dependency_version_key] != self.latest_release_indicator:
289+
name = name + "@" + dependency[self.dependency_version_key]
290+
291+
return name
292+
253293
def get_run_command_output_level(self):
254294
"""Determine and return the appropriate output setting for the run_command function."""
255295
if self.verbose:
@@ -316,23 +356,38 @@ def install_libraries(self):
316356
"""Install Arduino libraries."""
317357
self.libraries_path.mkdir(parents=True, exist_ok=True)
318358

319-
libraries_list = parse_list_input(self.libraries)
320-
if len(libraries_list) > 0:
321-
self.install_libraries_from_library_manager(library_name_list=libraries_list)
359+
libraries = yaml.load(stream="", Loader=yaml.SafeLoader)
360+
try:
361+
libraries = yaml.load(stream=self.libraries, Loader=yaml.SafeLoader)
362+
except yaml.parser.ParserError:
363+
# This exception occurs when the space separated list items are individually quoted (e.g., '"Foo" "Bar"')
364+
pass
365+
366+
library_list = self.Dependencies()
367+
if type(libraries) is list:
368+
# libraries input is YAML
369+
library_list = self.sort_dependency_list(libraries)
370+
else:
371+
# libraries input uses the old space-separated list syntax
372+
library_list.manager = [{self.dependency_name_key: library_name}
373+
for library_name in parse_list_input(self.libraries)]
374+
375+
if len(library_list.manager) > 0:
376+
self.install_libraries_from_library_manager(library_list=library_list.manager)
322377

323378
# TODO: this is in anticipation of allowing arbitrary paths to be specified by the user, rather than assuming
324379
# there is a library in the root of the repo (not the case when testing platforms and sketches)
325380
# Install the repository as a library by creating a symlink in the sketchbook
326381
self.install_libraries_from_path(library_path_list=[pathlib.Path(os.environ["GITHUB_WORKSPACE"])])
327382

328-
def install_libraries_from_library_manager(self, library_name_list):
383+
def install_libraries_from_library_manager(self, library_list):
329384
"""Install libraries using the Arduino Library Manager
330385
331386
Keyword arguments:
332-
library_name_list -- list of library names
387+
library_list -- list of dictionaries defining the dependencies
333388
"""
334389
lib_install_command = ["lib", "install"]
335-
lib_install_command.extend(library_name_list)
390+
lib_install_command.extend([self.get_manager_dependency_name(library) for library in library_list])
336391
self.run_arduino_cli_command(command=lib_install_command, enable_output=self.get_run_command_output_level())
337392

338393
def install_libraries_from_path(self, library_path_list):

compilesketches/requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@ gitpython==3.1.1
22
PyGithub==1.47
33
pytest==5.4.1
44
pytest-mock==3.1.0
5+
pyyaml==5.3.1

compilesketches/tests/test_compilesketches.py

Lines changed: 51 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -305,6 +305,21 @@ def test_install_platforms(mocker, fqbn_arg, expected_platform, expected_additio
305305
)
306306

307307

308+
@pytest.mark.parametrize(
309+
"dependency_list, expected_dependency_type_list",
310+
[([None], []),
311+
([{compilesketches.CompileSketches.dependency_name_key: "FooBar"}], ["manager"])]
312+
)
313+
def test_sort_dependency_list(monkeypatch, dependency_list, expected_dependency_type_list):
314+
monkeypatch.setenv("GITHUB_WORKSPACE", "/foo/GitHubWorkspace")
315+
316+
compile_sketches = get_compilesketches_object()
317+
318+
for dependency, expected_dependency_type in zip(dependency_list, expected_dependency_type_list):
319+
assert dependency in getattr(compile_sketches.sort_dependency_list(dependency_list=[dependency]),
320+
expected_dependency_type)
321+
322+
308323
@pytest.mark.parametrize("additional_url_list",
309324
[["https://example.com/package_foo_index.json", "https://example.com/package_bar_index.json"],
310325
[]])
@@ -427,11 +442,35 @@ class CommandData:
427442
subprocess.run.assert_called_once_with(command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True)
428443

429444

430-
@pytest.mark.parametrize("libraries", ['"foo" "bar"', ""])
431-
def test_install_libraries(monkeypatch, mocker, libraries):
432-
monkeypatch.setenv("GITHUB_WORKSPACE", "/foo/bar")
445+
@pytest.mark.parametrize(
446+
"dependency, expected_name",
447+
[({compilesketches.CompileSketches.dependency_name_key: "Foo",
448+
compilesketches.CompileSketches.dependency_version_key: "1.2.3"}, "[email protected]"),
449+
({compilesketches.CompileSketches.dependency_name_key: "Foo",
450+
compilesketches.CompileSketches.dependency_version_key: "latest"}, "Foo"),
451+
({compilesketches.CompileSketches.dependency_name_key: "[email protected]"}, "[email protected]"),
452+
({compilesketches.CompileSketches.dependency_name_key: "Foo"}, "Foo")])
453+
def test_get_manager_dependency_name(dependency, expected_name):
454+
compile_sketches = get_compilesketches_object()
455+
assert compile_sketches.get_manager_dependency_name(dependency=dependency) == expected_name
456+
457+
458+
@pytest.mark.parametrize("libraries, expected_manager",
459+
[("", []),
460+
("foo bar", [{compilesketches.CompileSketches.dependency_name_key: "foo"},
461+
{compilesketches.CompileSketches.dependency_name_key: "bar"}]),
462+
("\"foo\" \"bar\"", [{compilesketches.CompileSketches.dependency_name_key: "foo"},
463+
{compilesketches.CompileSketches.dependency_name_key: "bar"}]),
464+
("-", []),
465+
("- " + compilesketches.CompileSketches.dependency_name_key + ": foo",
466+
[{compilesketches.CompileSketches.dependency_name_key: "foo"}])])
467+
def test_install_libraries(monkeypatch, mocker, libraries, expected_manager):
468+
libraries_path = pathlib.Path("/foo/LibrariesPath")
469+
470+
monkeypatch.setenv("GITHUB_WORKSPACE", "/foo/GitHubWorkspace")
433471

434472
compile_sketches = get_compilesketches_object(libraries=libraries)
473+
compile_sketches.libraries_path = libraries_path
435474

436475
mocker.patch.object(pathlib.Path, "mkdir", autospec=True)
437476
mocker.patch("compilesketches.CompileSketches.install_libraries_from_library_manager", autospec=True)
@@ -440,35 +479,31 @@ def test_install_libraries(monkeypatch, mocker, libraries):
440479
compile_sketches.install_libraries()
441480

442481
# noinspection PyUnresolvedReferences
443-
pathlib.Path.mkdir.assert_called()
482+
pathlib.Path.mkdir.assert_called_with(libraries_path, parents=True, exist_ok=True)
444483

445-
if len(compile_sketches.libraries) > 0:
484+
if len(expected_manager) > 0:
446485
compile_sketches.install_libraries_from_library_manager.assert_called_once_with(
447486
compile_sketches,
448-
library_name_list=compilesketches.parse_list_input(compile_sketches.libraries)
449-
)
487+
library_list=expected_manager)
450488
else:
451489
compile_sketches.install_libraries_from_library_manager.assert_not_called()
452490

453-
compile_sketches.install_libraries_from_path.assert_called_once_with(
454-
compile_sketches,
455-
library_path_list=[pathlib.Path(os.environ["GITHUB_WORKSPACE"])]
456-
)
491+
compile_sketches.install_libraries_from_path.assert_called_once()
457492

458493

459494
def test_install_libraries_from_library_manager(mocker):
460495
run_command_output_level = unittest.mock.sentinel.run_command_output_level
461-
library_name_list = [unittest.mock.sentinel.library_name1, unittest.mock.sentinel.library_name2]
462-
463496
compile_sketches = get_compilesketches_object()
464497

498+
library_list = [{compile_sketches.dependency_name_key: "foo"}, {compile_sketches.dependency_name_key: "bar"}]
499+
465500
mocker.patch("compilesketches.CompileSketches.get_run_command_output_level", autospec=True,
466501
return_value=run_command_output_level)
467502
mocker.patch("compilesketches.CompileSketches.run_arduino_cli_command", autospec=True)
468503

469-
compile_sketches.install_libraries_from_library_manager(library_name_list=library_name_list)
504+
compile_sketches.install_libraries_from_library_manager(library_list=library_list)
470505

471-
lib_install_command = ["lib", "install"] + library_name_list
506+
lib_install_command = ["lib", "install"] + [library["name"] for library in library_list]
472507
compile_sketches.run_arduino_cli_command.assert_called_once_with(compile_sketches,
473508
command=lib_install_command,
474509
enable_output=run_command_output_level)
@@ -1064,6 +1099,7 @@ def test_verbose_print(capsys, verbose):
10641099
@pytest.mark.parametrize("list_argument, expected_list",
10651100
[("", []),
10661101
("foobar", ["foobar"]),
1102+
("foo bar", ["foo", "bar"]),
10671103
("\"foo bar\"", ["foo bar"]),
10681104
("\'foo bar\'", ["foo bar"]),
10691105
("\'\"foo bar\" \"baz\"\'", ["foo bar", "baz"]),

0 commit comments

Comments
 (0)