Skip to content

Commit 3f7c2f8

Browse files
committed
Use shared function to install from local path
There was redundant code used to symlink/move files from a local path to the installation. It tends to share the same requirements, which turns out to be somewhat complex. Moving the code to a dedicated function makes the code less complex and easier to maintain and expand.
1 parent 7c2fbc7 commit 3f7c2f8

File tree

2 files changed

+162
-88
lines changed

2 files changed

+162
-88
lines changed

compilesketches/compilesketches.py

Lines changed: 45 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -422,11 +422,10 @@ def install_platforms_from_path(self, platform_list):
422422

423423
platform_installation_path = self.get_platform_installation_path(platform=platform)
424424

425-
# Create the parent path if it doesn't exist already.
426-
platform_installation_path.parent.mkdir(parents=True, exist_ok=True)
427-
428-
# Install the platform by creating a symlink
429-
platform_installation_path.symlink_to(target=source_path, target_is_directory=True)
425+
# Install the platform
426+
install_from_path(source_path=source_path,
427+
destination_parent_path=platform_installation_path.parent,
428+
destination_name=platform_installation_path.name)
430429

431430
def get_platform_installation_path(self, platform):
432431
"""Return the correct installation path for the given platform
@@ -512,25 +511,17 @@ def install_from_repository(self, url, git_ref, source_path, destination_parent_
512511
destination_name -- folder name to use for the installation. Set to None to use the repository name.
513512
(default None)
514513
"""
515-
if destination_name is None:
516-
if source_path.rstrip("/") == ".":
517-
# Use the repository name
518-
destination_name = url.rstrip("/").rsplit(sep="/", maxsplit=1)[1].rsplit(sep=".", maxsplit=1)[0]
519-
else:
520-
# Use the source path folder name
521-
destination_name = pathlib.PurePath(source_path).name
522-
523-
if source_path.rstrip("/") == ".":
524-
# Clone directly to the target path
525-
self.clone_repository(url=url, git_ref=git_ref,
526-
destination_path=pathlib.PurePath(destination_parent_path, destination_name))
527-
else:
528-
# Clone to a temporary folder
529-
with tempfile.TemporaryDirectory() as clone_folder:
530-
self.clone_repository(url=url, git_ref=git_ref, destination_path=clone_folder)
531-
# Install by moving the source folder
532-
shutil.move(src=str(pathlib.PurePath(clone_folder, source_path)),
533-
dst=str(pathlib.PurePath(destination_parent_path, destination_name)))
514+
if destination_name is None and source_path.rstrip("/") == ".":
515+
# Use the repository name
516+
destination_name = url.rstrip("/").rsplit(sep="/", maxsplit=1)[1].rsplit(sep=".", maxsplit=1)[0]
517+
518+
# Clone to a temporary folder to allow installing from subfolders of repos
519+
with tempfile.TemporaryDirectory() as clone_folder:
520+
self.clone_repository(url=url, git_ref=git_ref, destination_path=clone_folder)
521+
# Install to the final location
522+
install_from_path(source_path=pathlib.Path(clone_folder, source_path),
523+
destination_parent_path=destination_parent_path,
524+
destination_name=destination_name)
534525

535526
def clone_repository(self, url, git_ref, destination_path):
536527
"""Clone a Git repository to a specified location and check out the specified ref
@@ -644,14 +635,11 @@ def install_libraries_from_path(self, library_list):
644635
destination_name = os.environ["GITHUB_REPOSITORY"].split(sep="/")[1]
645636
else:
646637
# Use the existing folder name
647-
destination_name = source_path.name
648-
649-
# Create the parent path if it doesn't exist already
650-
self.libraries_path.mkdir(parents=True, exist_ok=True)
638+
destination_name = None
651639

652-
# Install the library by creating a symlink in the sketchbook
653-
library_symlink_path = self.libraries_path.joinpath(destination_name)
654-
library_symlink_path.symlink_to(target=source_path, target_is_directory=True)
640+
install_from_path(source_path=source_path,
641+
destination_parent_path=self.libraries_path,
642+
destination_name=destination_name)
655643

656644
def install_libraries_from_repository(self, library_list):
657645
"""Install libraries by cloning Git repositories
@@ -1281,6 +1269,29 @@ def __init__(self):
12811269
return input_list
12821270

12831271

1272+
def install_from_path(source_path, destination_parent_path, destination_name=None):
1273+
"""Copy the source path to the destination path.
1274+
1275+
Keyword arguments:
1276+
source_path -- path to install
1277+
destination_parent_path -- path under which to install
1278+
destination_name -- folder or filename name to use for the installation. Set to None to take the name from
1279+
source_path. (default None)
1280+
"""
1281+
if destination_name is None:
1282+
destination_name = source_path.name
1283+
1284+
# Create the parent path if it doesn't already exist
1285+
destination_parent_path.mkdir(parents=True, exist_ok=True)
1286+
1287+
destination_path = destination_parent_path.joinpath(destination_name)
1288+
1289+
if source_path.is_dir():
1290+
shutil.copytree(src=source_path, dst=destination_path)
1291+
else:
1292+
shutil.copy(src=source_path, dst=destination_path)
1293+
1294+
12841295
def path_is_sketch(path):
12851296
"""Return whether the specified path is an Arduino sketch.
12861297
@@ -1349,15 +1360,9 @@ def install_from_download(url, source_path, destination_parent_path, destination
13491360
print("::error::Archive source path:", source_path, "not found")
13501361
sys.exit(1)
13511362

1352-
if destination_name is None:
1353-
destination_name = absolute_source_path.name
1354-
1355-
# Create the parent path if it doesn't already exist
1356-
destination_parent_path.mkdir(parents=True, exist_ok=True)
1357-
1358-
# Install by moving the source folder
1359-
shutil.move(src=str(absolute_source_path),
1360-
dst=str(destination_parent_path.joinpath(destination_name)))
1363+
install_from_path(source_path=absolute_source_path,
1364+
destination_parent_path=destination_parent_path,
1365+
destination_name=destination_name)
13611366

13621367

13631368
def get_archive_root_path(archive_extract_path):

compilesketches/tests/test_compilesketches.py

Lines changed: 117 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -638,8 +638,7 @@ def test_install_platforms_from_path(capsys, mocker, path_exists, platform_list)
638638
mocker.patch("compilesketches.CompileSketches.get_platform_installation_path",
639639
autospec=True,
640640
return_value=platform_installation_path)
641-
mocker.patch.object(pathlib.Path, "mkdir", autospec=True)
642-
mocker.patch.object(pathlib.Path, "symlink_to", autospec=True)
641+
mocker.patch("compilesketches.install_from_path", autospec=True)
643642

644643
if not path_exists:
645644
with pytest.raises(expected_exception=SystemExit, match="1"):
@@ -655,24 +654,23 @@ def test_install_platforms_from_path(capsys, mocker, path_exists, platform_list)
655654
compile_sketches.install_platforms_from_path(platform_list=platform_list)
656655

657656
get_platform_installation_path_calls = []
658-
mkdir_calls = []
659-
symlink_to_calls = []
657+
install_from_path_calls = []
660658
for platform in platform_list:
661659
get_platform_installation_path_calls.append(unittest.mock.call(compile_sketches, platform=platform))
662-
mkdir_calls.append(unittest.mock.call(platform_installation_path.parent, parents=True, exist_ok=True))
663-
symlink_to_calls.append(
660+
install_from_path_calls.append(
664661
unittest.mock.call(
665-
platform_installation_path,
666-
target=compilesketches.absolute_path(
662+
source_path=compilesketches.absolute_path(
667663
platform[compilesketches.CompileSketches.dependency_source_path_key]
668664
),
669-
target_is_directory=True
665+
destination_parent_path=platform_installation_path.parent,
666+
destination_name=platform_installation_path.name
670667
)
671668
)
672669

673670
# noinspection PyUnresolvedReferences
674-
pathlib.Path.mkdir.assert_has_calls(calls=mkdir_calls)
675-
pathlib.Path.symlink_to.assert_has_calls(calls=symlink_to_calls)
671+
compile_sketches.get_platform_installation_path.assert_has_calls(calls=get_platform_installation_path_calls)
672+
# noinspection PyUnresolvedReferences
673+
compilesketches.install_from_path.assert_has_calls(calls=install_from_path_calls)
676674

677675

678676
@pytest.mark.parametrize(
@@ -937,22 +935,19 @@ def test_install_libraries_from_library_manager(mocker):
937935
["FooRepoName"]),
938936
(True,
939937
[{compilesketches.CompileSketches.dependency_source_path_key: os.environ["GITHUB_WORKSPACE"] + "/Bar"}],
940-
["Bar"])]
938+
[None])]
941939
)
942940
def test_install_libraries_from_path(capsys, monkeypatch, mocker, path_exists, library_list,
943941
expected_destination_name_list):
944942
libraries_path = pathlib.Path("/foo/LibrariesPath")
945-
symlink_source_path = pathlib.Path("/foo/SymlinkSourcePath")
946943

947944
monkeypatch.setenv("GITHUB_REPOSITORY", "foo/FooRepoName")
948945

949946
compile_sketches = get_compilesketches_object()
950947
compile_sketches.libraries_path = libraries_path
951948

952949
mocker.patch.object(pathlib.Path, "exists", autospec=True, return_value=path_exists)
953-
mocker.patch.object(pathlib.Path, "mkdir", autospec=True)
954-
mocker.patch.object(pathlib.Path, "joinpath", autospec=True, return_value=symlink_source_path)
955-
mocker.patch.object(pathlib.Path, "symlink_to", autospec=True)
950+
mocker.patch("compilesketches.install_from_path", autospec=True)
956951

957952
if not path_exists:
958953
with pytest.raises(expected_exception=SystemExit, match="1"):
@@ -966,23 +961,20 @@ def test_install_libraries_from_path(capsys, monkeypatch, mocker, path_exists, l
966961
else:
967962
compile_sketches.install_libraries_from_path(library_list=library_list)
968963

969-
mkdir_calls = []
970-
joinpath_calls = []
971-
symlink_to_calls = []
964+
install_from_path_calls = []
972965
for library, expected_destination_name in zip(library_list, expected_destination_name_list):
973-
mkdir_calls.append(unittest.mock.call(libraries_path, parents=True, exist_ok=True))
974-
joinpath_calls.append(unittest.mock.call(libraries_path, expected_destination_name))
975-
symlink_to_calls.append(
976-
unittest.mock.call(symlink_source_path,
977-
target=compilesketches.absolute_path(
978-
library[compilesketches.CompileSketches.dependency_source_path_key]),
979-
target_is_directory=True))
966+
install_from_path_calls.append(
967+
unittest.mock.call(
968+
source_path=compilesketches.absolute_path(
969+
library[compilesketches.CompileSketches.dependency_source_path_key]
970+
),
971+
destination_parent_path=libraries_path,
972+
destination_name=expected_destination_name
973+
)
974+
)
980975

981976
# noinspection PyUnresolvedReferences
982-
pathlib.Path.mkdir.assert_has_calls(calls=mkdir_calls)
983-
# noinspection PyUnresolvedReferences
984-
pathlib.Path.joinpath.assert_has_calls(calls=joinpath_calls)
985-
pathlib.Path.symlink_to.assert_has_calls(calls=symlink_to_calls)
977+
compilesketches.install_from_path.assert_has_calls(calls=install_from_path_calls)
986978

987979

988980
def test_install_libraries_from_repository(mocker):
@@ -1121,6 +1113,87 @@ def test_get_list_from_multiformat_input(input_value, expected_list, expected_wa
11211113
assert input_list.was_yaml_list == expected_was_yaml_list
11221114

11231115

1116+
# noinspection PyUnresolvedReferences
1117+
@pytest.mark.parametrize(
1118+
"source_path, destination_parent_path, destination_name, expected_destination_path",
1119+
[(pathlib.Path("foo/source-path"),
1120+
pathlib.Path("bar/destination-parent-path"),
1121+
None,
1122+
pathlib.Path("bar/destination-parent-path/source-path")),
1123+
(pathlib.Path("foo/source-path"),
1124+
pathlib.Path("bar/destination-parent-path"),
1125+
"destination-name",
1126+
pathlib.Path("bar/destination-parent-path/destination-name"))])
1127+
@pytest.mark.parametrize("is_dir", [True, False])
1128+
def test_install_from_path(mocker,
1129+
source_path,
1130+
destination_parent_path,
1131+
destination_name,
1132+
expected_destination_path,
1133+
is_dir):
1134+
mocker.patch.object(pathlib.Path, "mkdir", autospec=True)
1135+
mocker.patch.object(pathlib.Path, "is_dir", autospec=True, return_value=is_dir)
1136+
mocker.patch("shutil.copytree", autospec=True)
1137+
mocker.patch("shutil.copy", autospec=True)
1138+
1139+
compilesketches.install_from_path(source_path=source_path, destination_parent_path=destination_parent_path,
1140+
destination_name=destination_name)
1141+
if is_dir:
1142+
shutil.copytree.assert_called_once_with(src=source_path, dst=expected_destination_path)
1143+
else:
1144+
shutil.copy.assert_called_once_with(src=source_path, dst=expected_destination_path)
1145+
1146+
1147+
def test_install_from_path_functional(tmp_path):
1148+
source_path = tmp_path.joinpath("foo-source")
1149+
test_file_path = source_path.joinpath("foo-test-file")
1150+
destination_parent_path = tmp_path.joinpath("foo-destination-parent")
1151+
1152+
def prep_test_folders():
1153+
shutil.rmtree(path=source_path, ignore_errors=True)
1154+
source_path.mkdir(parents=True)
1155+
test_file_path.write_text("foo test file contents")
1156+
shutil.rmtree(path=destination_parent_path, ignore_errors=True)
1157+
# destination_parent_path is created by install_from_path()
1158+
1159+
# Test install of folder
1160+
# Test naming according to source
1161+
# Test existing destination_parent_path
1162+
prep_test_folders()
1163+
destination_parent_path.mkdir(parents=True)
1164+
compilesketches.install_from_path(source_path=source_path, destination_parent_path=destination_parent_path,
1165+
destination_name=None)
1166+
assert directories_are_same(left_directory=source_path,
1167+
right_directory=destination_parent_path.joinpath(source_path.name))
1168+
1169+
# Test custom folder name
1170+
prep_test_folders()
1171+
destination_name = "foo-destination-name"
1172+
compilesketches.install_from_path(source_path=source_path,
1173+
destination_parent_path=destination_parent_path,
1174+
destination_name=destination_name)
1175+
assert directories_are_same(left_directory=source_path,
1176+
right_directory=destination_parent_path.joinpath(destination_name))
1177+
1178+
# Test install of file
1179+
# Test naming according to source
1180+
prep_test_folders()
1181+
compilesketches.install_from_path(source_path=test_file_path,
1182+
destination_parent_path=destination_parent_path,
1183+
destination_name=None)
1184+
assert filecmp.cmp(f1=test_file_path,
1185+
f2=destination_parent_path.joinpath(test_file_path.name))
1186+
1187+
# Test custom folder name
1188+
prep_test_folders()
1189+
destination_name = "foo-destination-name"
1190+
compilesketches.install_from_path(source_path=test_file_path,
1191+
destination_parent_path=destination_parent_path,
1192+
destination_name=destination_name)
1193+
assert filecmp.cmp(f1=test_file_path,
1194+
f2=destination_parent_path.joinpath(destination_name))
1195+
1196+
11241197
def test_path_is_sketch():
11251198
# Sketch file
11261199
assert compilesketches.path_is_sketch(path=test_data_path.joinpath("HasSketches", "Sketch1", "Sketch1.ino")) is True
@@ -2156,10 +2229,10 @@ def test_get_archive_root_path(archive_extract_path, expected_archive_root_path)
21562229
[("https://example.com/foo/FooRepositoryName.git", ".", None, "FooRepositoryName"),
21572230
("https://example.com/foo/FooRepositoryName.git/", "./examples", "FooDestinationName",
21582231
"FooDestinationName"),
2159-
("git://example.com/foo/FooRepositoryName", "examples", None, "examples")])
2232+
("git://example.com/foo/FooRepositoryName", "examples", None, None)])
21602233
def test_install_from_repository(mocker, url, source_path, destination_name, expected_destination_name):
21612234
git_ref = unittest.mock.sentinel.git_ref
2162-
destination_parent_path = pathlib.PurePath("/foo/DestinationParentPath")
2235+
destination_parent_path = unittest.mock.sentinel.destination_parent_path
21632236
clone_path = pathlib.PurePath("/foo/ClonePath")
21642237

21652238
# Stub
@@ -2176,7 +2249,7 @@ def __exit__(self, *exc):
21762249
mocker.patch("tempfile.TemporaryDirectory", autospec=True,
21772250
return_value=TemporaryDirectory(temporary_directory=clone_path))
21782251
mocker.patch("compilesketches.CompileSketches.clone_repository", autospec=True)
2179-
mocker.patch("shutil.move", autospec=True)
2252+
mocker.patch("compilesketches.install_from_path", autospec=True)
21802253

21812254
compile_sketches = get_compilesketches_object()
21822255

@@ -2186,21 +2259,17 @@ def __exit__(self, *exc):
21862259
destination_parent_path=destination_parent_path,
21872260
destination_name=destination_name)
21882261

2189-
if source_path.rstrip("/") == ".":
2190-
compile_sketches.clone_repository.assert_called_once_with(compile_sketches,
2191-
url=url,
2192-
git_ref=git_ref,
2193-
destination_path=destination_parent_path.joinpath(
2194-
expected_destination_name))
2195-
else:
2196-
tempfile.TemporaryDirectory.assert_called_once()
2197-
compile_sketches.clone_repository.assert_called_once_with(compile_sketches,
2198-
url=url,
2199-
git_ref=git_ref,
2200-
destination_path=clone_path)
2201-
# noinspection PyUnresolvedReferences
2202-
shutil.move.assert_called_once_with(src=str(clone_path.joinpath(source_path)),
2203-
dst=str(destination_parent_path.joinpath(expected_destination_name)))
2262+
tempfile.TemporaryDirectory.assert_called_once()
2263+
compile_sketches.clone_repository.assert_called_once_with(compile_sketches,
2264+
url=url,
2265+
git_ref=git_ref,
2266+
destination_path=clone_path)
2267+
# noinspection PyUnresolvedReferences
2268+
compilesketches.install_from_path.assert_called_once_with(
2269+
source_path=clone_path.joinpath(source_path),
2270+
destination_parent_path=destination_parent_path,
2271+
destination_name=expected_destination_name
2272+
)
22042273

22052274

22062275
@pytest.mark.parametrize("git_ref", ["v1.0.2", "latest", None])

0 commit comments

Comments
 (0)