Skip to content

Commit c8f9efb

Browse files
authored
Enhance lib install from git url and zip file (#1145)
Previously installing from a zip file would extract the content directly into the user library folder, this could have caused problem if the zip file wasn't structured correctly. To handle this we now extract to a temp directory to verify that the zip is structured correctly and to infer the name of the library being installed. If everything is fine we then copy it over to the user library folder to finalize the installation. Installing from a git repository has been enhanced too, after cloning the repository to the user library folder we delete the .git folder to make it a plain folder. Cloning should be faster too since we now clone using depth of one commit to avoid downloading useless files. Both when installing from a zip file or from a git repository we delete the installed library folder if one with the same name is already installed.
1 parent ee076dd commit c8f9efb

File tree

2 files changed

+136
-7
lines changed

2 files changed

+136
-7
lines changed

arduino/libraries/librariesmanager/install.go

+74-2
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import (
2828
"github.com/arduino/arduino-cli/arduino/utils"
2929
paths "github.com/arduino/go-paths-helper"
3030
"github.com/codeclysm/extract/v3"
31+
"github.com/sirupsen/logrus"
3132
"gopkg.in/src-d/go-git.v4"
3233
)
3334

@@ -99,15 +100,62 @@ func (lm *LibrariesManager) InstallZipLib(ctx context.Context, archivePath strin
99100
return fmt.Errorf("User directory not set")
100101
}
101102

103+
tmpDir, err := paths.MkTempDir(paths.TempDir().String(), "arduino-cli-lib-")
104+
if err != nil {
105+
return err
106+
}
107+
102108
file, err := os.Open(archivePath)
103109
if err != nil {
104110
return err
105111
}
106112
defer file.Close()
107113

108-
if err := extract.Archive(ctx, file, libsDir.String(), nil); err != nil {
109-
return fmt.Errorf("extracting archive: %s", err)
114+
// Extract to a temporary directory so we can check if the zip is structured correctly.
115+
// We also use the top level folder from the archive to infer the library name.
116+
if err := extract.Archive(ctx, file, tmpDir.String(), nil); err != nil {
117+
return fmt.Errorf("extracting archive: %w", err)
118+
}
119+
120+
paths, err := tmpDir.ReadDir()
121+
if err != nil {
122+
return err
123+
}
124+
125+
if len(paths) > 1 {
126+
return fmt.Errorf("archive is not valid: multiple files found in zip file top level")
127+
}
128+
129+
libraryName := paths[0].Base()
130+
installPath := libsDir.Join(libraryName)
131+
132+
// Deletes libraries folder if already installed
133+
if _, ok := lm.Libraries[libraryName]; ok {
134+
logrus.
135+
WithField("library name", libraryName).
136+
WithField("install path", installPath).
137+
Trace("Deleting library")
138+
installPath.RemoveAll()
110139
}
140+
141+
logrus.
142+
WithField("library name", libraryName).
143+
WithField("install path", installPath).
144+
WithField("zip file", archivePath).
145+
Trace("Installing library")
146+
147+
files, err := tmpDir.Join(libraryName).ReadDirRecursive()
148+
files.FilterOutDirs()
149+
for _, f := range files {
150+
finalPath := installPath.Join(f.Base())
151+
if err := finalPath.Parent().MkdirAll(); err != nil {
152+
return fmt.Errorf("creating directory: %w", err)
153+
}
154+
if err := f.CopyTo(finalPath); err != nil {
155+
return fmt.Errorf("copying library: %w", err)
156+
}
157+
}
158+
111159
return nil
112160
}
113161

@@ -120,18 +168,42 @@ func (lm *LibrariesManager) InstallGitLib(gitURL string) error {
120168

121169
libraryName, err := parseGitURL(gitURL)
122170
if err != nil {
171+
logrus.
172+
WithError(err).
173+
Warn("Parsing git URL")
123174
return err
124175
}
125176

126177
installPath := libsDir.Join(libraryName)
127178

179+
// Deletes libraries folder if already installed
180+
if _, ok := lm.Libraries[libraryName]; ok {
181+
logrus.
182+
WithField("library name", libraryName).
183+
WithField("install path", installPath).
184+
Trace("Deleting library")
185+
installPath.RemoveAll()
186+
}
187+
188+
logrus.
189+
WithField("library name", libraryName).
190+
WithField("install path", installPath).
191+
WithField("git url", gitURL).
192+
Trace("Installing library")
193+
128194
_, err = git.PlainClone(installPath.String(), false, &git.CloneOptions{
129195
URL: gitURL,
196+
Depth: 1,
130197
Progress: os.Stdout,
131198
})
132199
if err != nil {
200+
logrus.
201+
WithError(err).
202+
Warn("Cloning git repository")
133203
return err
134204
}
205+
// We don't want the installed library to be a git repository thus we delete this folder
206+
installPath.Join(".git").RemoveAll()
135207
return nil
136208
}
137209

test/test_lib.py

+62-5
Original file line numberDiff line numberDiff line change
@@ -225,15 +225,25 @@ def test_install_with_git_url(run_command, data_dir, downloads_dir):
225225
}
226226
assert run_command("config init --dest-dir .", custom_env=env)
227227

228+
lib_install_dir = Path(data_dir, "libraries", "WiFi101")
229+
# Verifies library is not already installed
230+
assert not lib_install_dir.exists()
231+
232+
git_url = "https://github.com/arduino-libraries/WiFi101.git"
233+
228234
# Test git-url library install
229-
res = run_command("lib install --git-url https://github.com/arduino-libraries/WiFi101.git")
235+
res = run_command(f"lib install --git-url {git_url}")
230236
assert res.ok
231237
assert "--git-url and --zip-path flags allow installing untrusted files, use it at your own risk." in res.stdout
232238

233-
# Test failing-install as repository already exists
234-
res = run_command("lib install --git-url https://github.com/arduino-libraries/WiFi101.git")
235-
assert "--git-url and --zip-path flags allow installing untrusted files, use it at your own risk." in res.stdout
236-
assert "Error installing Git Library: repository already exists" in res.stderr
239+
# Verifies library is installed in expected path
240+
assert lib_install_dir.exists()
241+
242+
# Reinstall library
243+
assert run_command(f"lib install --git-url {git_url}")
244+
245+
# Verifies library remains installed
246+
assert lib_install_dir.exists()
237247

238248

239249
def test_install_with_zip_path(run_command, data_dir, downloads_dir):
@@ -249,12 +259,25 @@ def test_install_with_zip_path(run_command, data_dir, downloads_dir):
249259
# Download a specific lib version
250260
assert run_command("lib download [email protected]")
251261

262+
lib_install_dir = Path(data_dir, "libraries", "AudioZero-1.0.0")
263+
# Verifies library is not already installed
264+
assert not lib_install_dir.exists()
265+
252266
zip_path = Path(downloads_dir, "libraries", "AudioZero-1.0.0.zip")
253267
# Test zip-path install
254268
res = run_command(f"lib install --zip-path {zip_path}")
255269
assert res.ok
256270
assert "--git-url and --zip-path flags allow installing untrusted files, use it at your own risk." in res.stdout
257271

272+
# Verifies library is installed in expected path
273+
assert lib_install_dir.exists()
274+
275+
# Reinstall library
276+
assert run_command(f"lib install --zip-path {zip_path}")
277+
278+
# Verifies library remains installed
279+
assert lib_install_dir.exists()
280+
258281

259282
def test_update_index(run_command):
260283
result = run_command("lib update-index")
@@ -440,6 +463,9 @@ def test_install_with_git_url_local_file_uri(run_command, downloads_dir, data_di
440463

441464
assert run_command(f"lib install --git-url {repo_dir.as_uri()}", custom_env=env)
442465

466+
# Verifies library is installed
467+
assert lib_install_dir.exists()
468+
443469

444470
def test_install_with_git_local_url(run_command, downloads_dir, data_dir):
445471
assert run_command("update")
@@ -462,6 +488,9 @@ def test_install_with_git_local_url(run_command, downloads_dir, data_dir):
462488

463489
assert run_command(f"lib install --git-url {repo_dir}", custom_env=env)
464490

491+
# Verifies library is installed
492+
assert lib_install_dir.exists()
493+
465494

466495
def test_install_with_git_url_relative_path(run_command, downloads_dir, data_dir):
467496
assert run_command("update")
@@ -483,3 +512,31 @@ def test_install_with_git_url_relative_path(run_command, downloads_dir, data_dir
483512
assert Repo.clone_from(git_url, repo_dir)
484513

485514
assert run_command("lib install --git-url ./WiFi101", custom_working_dir=data_dir, custom_env=env)
515+
516+
# Verifies library is installed
517+
assert lib_install_dir.exists()
518+
519+
520+
def test_install_with_git_url_does_not_create_git_repo(run_command, downloads_dir, data_dir):
521+
assert run_command("update")
522+
523+
env = {
524+
"ARDUINO_DATA_DIR": data_dir,
525+
"ARDUINO_DOWNLOADS_DIR": downloads_dir,
526+
"ARDUINO_SKETCHBOOK_DIR": data_dir,
527+
"ARDUINO_ENABLE_UNSAFE_LIBRARY_INSTALL": "true",
528+
}
529+
530+
lib_install_dir = Path(data_dir, "libraries", "WiFi101")
531+
# Verifies library is not installed
532+
assert not lib_install_dir.exists()
533+
534+
# Clone repository locally
535+
git_url = "https://github.com/arduino-libraries/WiFi101.git"
536+
repo_dir = Path(data_dir, "WiFi101")
537+
assert Repo.clone_from(git_url, repo_dir)
538+
539+
assert run_command(f"lib install --git-url {repo_dir}", custom_env=env)
540+
541+
# Verifies installed library is not a git repository
542+
assert not Path(lib_install_dir, ".git").exists()

0 commit comments

Comments
 (0)