Skip to content

Commit a0ac7d4

Browse files
committed
Overwrite previous installations of dependencies
Library dependencies of Library Manager sourced libraries (as defined in its library.properties metadata file) are automatically installed by Arduino CLI. Libraries from Library Manager sources are installed before the libraries from other sources. When the user of the action defined a library dependency that was also automatically installed via the Library Manager dependency system, the workflow step using the action would fail on the installation of the library from the user's definition due to an error caused by the library being already installed. My first instinct for how to fix the failure caused by conflict with an existing installation was to simply change the order the library dependency sources are installed so the Library Manager sourced libraries are installed last. However, there is another factor: If a library with a version lower than the latest version available from Library Manager is already installed, Arduino CLI updates it with the version from Library Manager. The exact version of the library dependencies specified by the user of the script must be retained, so this means the reordering solution won't work. The solution is to leave the installation order so the Library Manager sourced libraries are installed first, then configure the installation of libraries from the other sources to overwrite existing installations. Adding this "force" capability to the `compilesketches.CompileSketches.install_from_path()` function also allowed moving the overwrite code from from its previous unlikely home in `compilesketches.CompileSketches.get_platform_installation_path()`.
1 parent 3f7c2f8 commit a0ac7d4

File tree

2 files changed

+141
-64
lines changed

2 files changed

+141
-64
lines changed

compilesketches/compilesketches.py

Lines changed: 61 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -210,7 +210,8 @@ def install_arduino_cli(self):
210210
url=arduino_cli_archive_download_url_prefix + arduino_cli_archive_file_name,
211211
# The Arduino CLI has no root folder, so just install the arduino-cli executable from the archive root
212212
source_path="arduino-cli",
213-
destination_parent_path=self.arduino_cli_installation_path
213+
destination_parent_path=self.arduino_cli_installation_path,
214+
force=False
214215
)
215216

216217
# Configure the location of the Arduino CLI user directory
@@ -424,20 +425,32 @@ def install_platforms_from_path(self, platform_list):
424425

425426
# Install the platform
426427
install_from_path(source_path=source_path,
427-
destination_parent_path=platform_installation_path.parent,
428-
destination_name=platform_installation_path.name)
428+
destination_parent_path=platform_installation_path.path.parent,
429+
destination_name=platform_installation_path.path.name,
430+
force=platform_installation_path.is_overwrite)
429431

430432
def get_platform_installation_path(self, platform):
431-
"""Return the correct installation path for the given platform
433+
"""Determines the correct installation path for the given platform and returns an object with the attributes:
434+
path -- correct installation path for the platform (pathlib.Path() object)
435+
is_overwrite -- whether there is an existing installation of the platform (True, False)
432436
433437
Keyword arguments:
434438
platform -- dictionary defining the platform dependency
435439
"""
440+
441+
class PlatformInstallationPath:
442+
def __init__(self):
443+
self.path = pathlib.Path()
444+
self.is_overwrite = False
445+
446+
platform_installation_path = PlatformInstallationPath()
447+
448+
# Default to installing to the sketchbook
436449
platform_vendor = platform[self.dependency_name_key].split(sep=":")[0]
437450
platform_architecture = platform[self.dependency_name_key].rsplit(sep=":", maxsplit=1)[1]
438451

439452
# Default to installing to the sketchbook
440-
platform_installation_path = self.user_platforms_path.joinpath(platform_vendor, platform_architecture)
453+
platform_installation_path.path = self.user_platforms_path.joinpath(platform_vendor, platform_architecture)
441454

442455
# I have no clue why this is needed, but arduino-cli core list fails if this isn't done first. The 3rd party
443456
# platforms are still shown in the list even if their index URLs are not specified to the command via the
@@ -449,15 +462,13 @@ def get_platform_installation_path(self, platform):
449462
for installed_platform in installed_platform_list:
450463
if installed_platform["ID"] == platform[self.dependency_name_key]:
451464
# The platform has been installed via Board Manager, so do an overwrite
452-
platform_installation_path = (
465+
platform_installation_path.path = (
453466
self.board_manager_platforms_path.joinpath(platform_vendor,
454467
"hardware",
455468
platform_architecture,
456469
installed_platform["Installed"])
457470
)
458-
459-
# Remove the existing installation so it can be replaced by the installation function
460-
shutil.rmtree(path=platform_installation_path)
471+
platform_installation_path.is_overwrite = True
461472

462473
break
463474

@@ -484,8 +495,9 @@ def install_platforms_from_repository(self, platform_list):
484495
self.install_from_repository(url=platform[self.dependency_source_url_key],
485496
git_ref=git_ref,
486497
source_path=source_path,
487-
destination_parent_path=destination_path.parent,
488-
destination_name=destination_path.name)
498+
destination_parent_path=destination_path.path.parent,
499+
destination_name=destination_path.path.name,
500+
force=destination_path.is_overwrite)
489501

490502
def get_repository_dependency_ref(self, dependency):
491503
"""Return the appropriate git ref value for a repository dependency
@@ -500,7 +512,13 @@ def get_repository_dependency_ref(self, dependency):
500512

501513
return git_ref
502514

503-
def install_from_repository(self, url, git_ref, source_path, destination_parent_path, destination_name=None):
515+
def install_from_repository(self,
516+
url,
517+
git_ref,
518+
source_path,
519+
destination_parent_path,
520+
destination_name=None,
521+
force=False):
504522
"""Install by cloning a repository
505523
506524
Keyword arguments:
@@ -510,6 +528,7 @@ def install_from_repository(self, url, git_ref, source_path, destination_parent_
510528
destination_parent_path -- path under which to install
511529
destination_name -- folder name to use for the installation. Set to None to use the repository name.
512530
(default None)
531+
force -- replace existing destination folder if present. (default False)
513532
"""
514533
if destination_name is None and source_path.rstrip("/") == ".":
515534
# Use the repository name
@@ -521,7 +540,8 @@ def install_from_repository(self, url, git_ref, source_path, destination_parent_
521540
# Install to the final location
522541
install_from_path(source_path=pathlib.Path(clone_folder, source_path),
523542
destination_parent_path=destination_parent_path,
524-
destination_name=destination_name)
543+
destination_name=destination_name,
544+
force=force)
525545

526546
def clone_repository(self, url, git_ref, destination_path):
527547
"""Clone a Git repository to a specified location and check out the specified ref
@@ -567,8 +587,9 @@ def install_platforms_from_download(self, platform_list):
567587

568588
install_from_download(url=platform[self.dependency_source_url_key],
569589
source_path=source_path,
570-
destination_parent_path=destination_path.parent,
571-
destination_name=destination_path.name)
590+
destination_parent_path=destination_path.path.parent,
591+
destination_name=destination_path.path.name,
592+
force=destination_path.is_overwrite)
572593

573594
def install_libraries(self):
574595
"""Install Arduino libraries."""
@@ -587,6 +608,9 @@ def install_libraries(self):
587608
# that behavior is retained when using the old input syntax
588609
library_list.path = [{self.dependency_source_path_key: os.environ["GITHUB_WORKSPACE"]}]
589610

611+
# Dependencies of Library Manager sourced libraries (as defined by the library's metadata file) are
612+
# automatically installed. For this reason, LM-sources must be installed first so the library dependencies from
613+
# other sources which were explicitly defined won't be replaced.
590614
if len(library_list.manager) > 0:
591615
self.install_libraries_from_library_manager(library_list=library_list.manager)
592616

@@ -639,7 +663,8 @@ def install_libraries_from_path(self, library_list):
639663

640664
install_from_path(source_path=source_path,
641665
destination_parent_path=self.libraries_path,
642-
destination_name=destination_name)
666+
destination_name=destination_name,
667+
force=True)
643668

644669
def install_libraries_from_repository(self, library_list):
645670
"""Install libraries by cloning Git repositories
@@ -669,7 +694,8 @@ def install_libraries_from_repository(self, library_list):
669694
git_ref=git_ref,
670695
source_path=source_path,
671696
destination_parent_path=self.libraries_path,
672-
destination_name=destination_name)
697+
destination_name=destination_name,
698+
force=True)
673699

674700
def install_libraries_from_download(self, library_list):
675701
"""Install libraries by downloading them
@@ -692,7 +718,8 @@ def install_libraries_from_download(self, library_list):
692718
install_from_download(url=library[self.dependency_source_url_key],
693719
source_path=source_path,
694720
destination_parent_path=self.libraries_path,
695-
destination_name=destination_name)
721+
destination_name=destination_name,
722+
force=True)
696723

697724
def find_sketches(self):
698725
"""Return a list of all sketches under the paths specified in the sketch paths list recursively."""
@@ -1269,23 +1296,32 @@ def __init__(self):
12691296
return input_list
12701297

12711298

1272-
def install_from_path(source_path, destination_parent_path, destination_name=None):
1299+
def install_from_path(source_path, destination_parent_path, destination_name=None, force=False):
12731300
"""Copy the source path to the destination path.
12741301
12751302
Keyword arguments:
12761303
source_path -- path to install
12771304
destination_parent_path -- path under which to install
12781305
destination_name -- folder or filename name to use for the installation. Set to None to take the name from
12791306
source_path. (default None)
1307+
force -- replace existing destination if present. (default False)
12801308
"""
12811309
if destination_name is None:
12821310
destination_name = source_path.name
12831311

1312+
destination_path = destination_parent_path.joinpath(destination_name)
1313+
1314+
if destination_path.exists():
1315+
if force:
1316+
# Clear existing folder
1317+
shutil.rmtree(path=destination_path)
1318+
else:
1319+
print("::error::Installation already exists:", destination_path)
1320+
sys.exit(1)
1321+
12841322
# Create the parent path if it doesn't already exist
12851323
destination_parent_path.mkdir(parents=True, exist_ok=True)
12861324

1287-
destination_path = destination_parent_path.joinpath(destination_name)
1288-
12891325
if source_path.is_dir():
12901326
shutil.copytree(src=source_path, dst=destination_path)
12911327
else:
@@ -1321,7 +1357,7 @@ def list_to_string(list_input):
13211357
return " ".join([str(item) for item in list_input])
13221358

13231359

1324-
def install_from_download(url, source_path, destination_parent_path, destination_name=None):
1360+
def install_from_download(url, source_path, destination_parent_path, destination_name=None, force=False):
13251361
"""Download an archive, extract, and install.
13261362
13271363
Keyword arguments:
@@ -1330,6 +1366,7 @@ def install_from_download(url, source_path, destination_parent_path, destination
13301366
destination_parent_path -- path under which to install
13311367
destination_name -- folder name to use for the installation. Set to None to take the name from source_path.
13321368
(default None)
1369+
force -- replace existing destination folder if present. (default False)
13331370
"""
13341371
destination_parent_path = pathlib.Path(destination_parent_path)
13351372

@@ -1362,7 +1399,8 @@ def install_from_download(url, source_path, destination_parent_path, destination
13621399

13631400
install_from_path(source_path=absolute_source_path,
13641401
destination_parent_path=destination_parent_path,
1365-
destination_name=destination_name)
1402+
destination_name=destination_name,
1403+
force=force)
13661404

13671405

13681406
def get_archive_root_path(archive_extract_path):

0 commit comments

Comments
 (0)