@@ -29,72 +29,121 @@ import (
2929 "github.com/arduino/arduino-cli/arduino/utils"
3030 paths "github.com/arduino/go-paths-helper"
3131 "github.com/codeclysm/extract/v3"
32- "github.com/sirupsen/logrus "
32+ semver "go.bug.st/relaxed-semver "
3333 "gopkg.in/src-d/go-git.v4"
3434 "gopkg.in/src-d/go-git.v4/plumbing"
3535)
3636
37- type alreadyInstalledError struct {}
37+ // LibraryInstallPlan contains the main information required to perform a library
38+ // install, like the path where the library should be installed and the library
39+ // that is going to be replaced by the new one.
40+ // This is the result of a call to InstallPrerequisiteCheck.
41+ type LibraryInstallPlan struct {
42+ // Name of the library to install
43+ Name string
3844
39- func (e * alreadyInstalledError ) Error () string {
40- return tr ("library already installed" )
41- }
45+ // Version of the library to install
46+ Version * semver.Version
4247
43- var (
44- // ErrAlreadyInstalled is returned when a library is already installed and task
45- // cannot proceed.
46- ErrAlreadyInstalled = & alreadyInstalledError {}
47- )
48+ // TargetPath is the path where the library should be installed.
49+ TargetPath * paths.Path
50+
51+ // ReplacedLib is the library that is going to be replaced by the new one.
52+ ReplacedLib * libraries.Library
53+
54+ // UpToDate is true if the library to install has the same version of the library we are going to replace.
55+ UpToDate bool
56+ }
4857
4958// InstallPrerequisiteCheck performs prequisite checks to install a library. It returns the
5059// install path, where the library should be installed and the possible library that is already
5160// installed on the same folder and it's going to be replaced by the new one.
52- func (lm * LibrariesManager ) InstallPrerequisiteCheck (indexLibrary * librariesindex.Release , installLocation libraries.LibraryLocation ) (* paths.Path , * libraries.Library , error ) {
53- installDir := lm .getLibrariesDir (installLocation )
54- if installDir == nil {
55- if installLocation == libraries .User {
56- return nil , nil , fmt .Errorf (tr ("User directory not set" ))
57- }
58- return nil , nil , fmt .Errorf (tr ("Builtin libraries directory not set" ))
61+ func (lm * LibrariesManager ) InstallPrerequisiteCheck (name string , version * semver.Version , installLocation libraries.LibraryLocation ) (* LibraryInstallPlan , error ) {
62+ installDir , err := lm .getLibrariesDir (installLocation )
63+ if err != nil {
64+ return nil , err
5965 }
6066
61- name := indexLibrary .Library .Name
6267 libs := lm .FindByReference (& librariesindex.Reference {Name : name }, installLocation )
63- for _ , lib := range libs {
64- if lib .Version != nil && lib .Version .Equal (indexLibrary .Version ) {
65- return lib .InstallDir , nil , ErrAlreadyInstalled
66- }
67- }
6868
6969 if len (libs ) > 1 {
7070 libsDir := paths .NewPathList ()
7171 for _ , lib := range libs {
7272 libsDir .Add (lib .InstallDir )
7373 }
74- return nil , nil , & arduino.MultipleLibraryInstallDetected {
74+ return nil , & arduino.MultipleLibraryInstallDetected {
7575 LibName : name ,
7676 LibsDir : libsDir ,
7777 Message : tr ("Automatic library install can't be performed in this case, please manually remove all duplicates and retry." ),
7878 }
7979 }
8080
8181 var replaced * libraries.Library
82+ var upToDate bool
8283 if len (libs ) == 1 {
83- replaced = libs [0 ]
84+ lib := libs [0 ]
85+ replaced = lib
86+ upToDate = lib .Version != nil && lib .Version .Equal (version )
8487 }
8588
86- libPath := installDir .Join (utils .SanitizeName (indexLibrary . Library . Name ))
87- if replaced != nil && replaced . InstallDir . EquivalentTo ( libPath ) {
88- return libPath , replaced , nil
89- } else if libPath . IsDir () {
90- return nil , nil , fmt . Errorf ( tr ( "destination dir %s already exists, cannot install" ), libPath )
89+ libPath := installDir .Join (utils .SanitizeName (name ))
90+ if libPath . IsDir ( ) {
91+ if replaced == nil || ! replaced . InstallDir . EquivalentTo ( libPath ) {
92+ return nil , fmt . Errorf ( tr ( "destination dir %s already exists, cannot install" ), libPath )
93+ }
9194 }
92- return libPath , replaced , nil
95+
96+ return & LibraryInstallPlan {
97+ Name : name ,
98+ Version : version ,
99+ TargetPath : libPath ,
100+ ReplacedLib : replaced ,
101+ UpToDate : upToDate ,
102+ }, nil
93103}
94104
95105// Install installs a library on the specified path.
96- func (lm * LibrariesManager ) Install (indexLibrary * librariesindex.Release , libPath * paths.Path ) error {
97- return indexLibrary .Resource .Install (lm .DownloadsDir , libPath .Parent (), libPath )
106+ func (lm * LibrariesManager ) Install (indexLibrary * librariesindex.Release , installPath * paths.Path ) error {
107+ return indexLibrary .Resource .Install (lm .DownloadsDir , installPath .Parent (), installPath )
108+ }
109+
110+ // importLibraryFromDirectory installs a library by copying it from the given directory.
111+ func (lm * LibrariesManager ) importLibraryFromDirectory (libPath * paths.Path , overwrite bool ) error {
112+ // Check if the library is valid and load metatada
113+ if err := validateLibrary (libPath ); err != nil {
114+ return err
115+ }
116+ library , err := libraries .Load (libPath , libraries .User )
117+ if err != nil {
118+ return err
119+ }
120+
121+ // Check if the library is already installed and determine install path
122+ installPlan , err := lm .InstallPrerequisiteCheck (library .Name , library .Version , libraries .User )
123+ if err != nil {
124+ return err
125+ }
126+
127+ if installPlan .UpToDate {
128+ if ! overwrite {
129+ return fmt .Errorf (tr ("library %s already installed" ), installPlan .Name )
130+ }
131+ }
132+ if installPlan .ReplacedLib != nil {
133+ if ! overwrite {
134+ return fmt .Errorf (tr ("Library %[1]s is already installed, but with a different version: %[2]s" , installPlan .Name , installPlan .ReplacedLib ))
135+ }
136+ if err := lm .Uninstall (installPlan .ReplacedLib ); err != nil {
137+ return err
138+ }
139+ }
140+ if installPlan .TargetPath .Exist () {
141+ return fmt .Errorf ("%s: %s" , tr ("destination directory already exists" ), installPlan .TargetPath )
142+ }
143+ if err := libPath .CopyDirTo (installPlan .TargetPath ); err != nil {
144+ return fmt .Errorf ("%s: %w" , tr ("copying library to destination directory:" ), err )
145+ }
146+ return nil
98147}
99148
100149// Uninstall removes a Library
@@ -103,7 +152,7 @@ func (lm *LibrariesManager) Uninstall(lib *libraries.Library) error {
103152 return fmt .Errorf (tr ("install directory not set" ))
104153 }
105154 if err := lib .InstallDir .RemoveAll (); err != nil {
106- return fmt .Errorf (tr ("removing lib directory: %s" ), err )
155+ return fmt .Errorf (tr ("removing library directory: %s" ), err )
107156 }
108157
109158 alternatives := lm .Libraries [lib .Name ]
@@ -113,20 +162,15 @@ func (lm *LibrariesManager) Uninstall(lib *libraries.Library) error {
113162}
114163
115164// InstallZipLib installs a Zip library on the specified path.
116- func (lm * LibrariesManager ) InstallZipLib (ctx context.Context , archivePath string , overwrite bool ) error {
117- libsDir := lm .getLibrariesDir (libraries .User )
118- if libsDir == nil {
119- return fmt .Errorf (tr ("User directory not set" ))
120- }
121-
122- tmpDir , err := paths .MkTempDir (paths .TempDir ().String (), "arduino-cli-lib-" )
165+ func (lm * LibrariesManager ) InstallZipLib (ctx context.Context , archivePath * paths.Path , overwrite bool ) error {
166+ // Clone library in a temporary directory
167+ tmpDir , err := paths .MkTempDir ("" , "" )
123168 if err != nil {
124169 return err
125170 }
126- // Deletes temp dir used to extract archive when finished
127171 defer tmpDir .RemoveAll ()
128172
129- file , err := os .Open (archivePath )
173+ file , err := archivePath .Open ()
130174 if err != nil {
131175 return err
132176 }
@@ -138,58 +182,21 @@ func (lm *LibrariesManager) InstallZipLib(ctx context.Context, archivePath strin
138182 return fmt .Errorf (tr ("extracting archive: %w" ), err )
139183 }
140184
141- paths , err := tmpDir .ReadDir ()
185+ libRootFiles , err := tmpDir .ReadDir ()
142186 if err != nil {
143187 return err
144188 }
145-
146- // Ignores metadata from Mac OS X
147- paths .FilterOutPrefix ("__MACOSX" )
148-
149- if len (paths ) > 1 {
189+ libRootFiles .FilterOutPrefix ("__MACOSX" ) // Ignores metadata from Mac OS X
190+ if len (libRootFiles ) > 1 {
150191 return fmt .Errorf (tr ("archive is not valid: multiple files found in zip file top level" ))
151192 }
152-
153- extractionPath := paths [0 ]
154- libraryName := extractionPath .Base ()
155-
156- if err := validateLibrary (extractionPath ); err != nil {
157- return err
158- }
159-
160- installPath := libsDir .Join (libraryName )
161-
162- if err := libsDir .MkdirAll (); err != nil {
163- return err
164- }
165- defer func () {
166- // Clean up install dir if installation failed
167- files , err := installPath .ReadDir ()
168- if err == nil && len (files ) == 0 {
169- installPath .RemoveAll ()
170- }
171- }()
172-
173- // Delete library folder if already installed
174- if installPath .IsDir () {
175- if ! overwrite {
176- return fmt .Errorf (tr ("library %s already installed" ), libraryName )
177- }
178- logrus .
179- WithField ("library name" , libraryName ).
180- WithField ("install path" , installPath ).
181- Trace ("Deleting library" )
182- installPath .RemoveAll ()
193+ if len (libRootFiles ) == 0 {
194+ return fmt .Errorf (tr ("archive is not valid: no files found in zip file top level" ))
183195 }
196+ tmpInstallPath := libRootFiles [0 ]
184197
185- logrus .
186- WithField ("library name" , libraryName ).
187- WithField ("install path" , installPath ).
188- WithField ("zip file" , archivePath ).
189- Trace ("Installing library" )
190-
191- // Copy extracted library in the destination directory
192- if err := extractionPath .CopyDirTo (installPath ); err != nil {
198+ // Install extracted library in the destination directory
199+ if err := lm .importLibraryFromDirectory (tmpInstallPath , overwrite ); err != nil {
193200 return fmt .Errorf (tr ("moving extracted archive to destination dir: %s" ), err )
194201 }
195202
@@ -198,84 +205,50 @@ func (lm *LibrariesManager) InstallZipLib(ctx context.Context, archivePath strin
198205
199206// InstallGitLib installs a library hosted on a git repository on the specified path.
200207func (lm * LibrariesManager ) InstallGitLib (gitURL string , overwrite bool ) error {
201- installDir := lm .getLibrariesDir (libraries .User )
202- if installDir == nil {
203- return fmt .Errorf (tr ("User directory not set" ))
204- }
205-
206- libraryName , ref , err := parseGitURL (gitURL )
208+ gitLibraryName , ref , err := parseGitURL (gitURL )
207209 if err != nil {
208- logrus .
209- WithError (err ).
210- Warn ("Parsing git URL" )
211210 return err
212211 }
213212
214- // Deletes libraries folder if already installed
215- installPath := installDir .Join (libraryName )
216- if installPath .IsDir () {
217- if ! overwrite {
218- return fmt .Errorf (tr ("library %s already installed" ), libraryName )
219- }
220- logrus .
221- WithField ("library name" , libraryName ).
222- WithField ("install path" , installPath ).
223- Trace ("Deleting library" )
224- installPath .RemoveAll ()
225- }
226- if installPath .Exist () {
227- return fmt .Errorf (tr ("could not create directory %s: a file with the same name exists!" , installPath ))
213+ // Clone library in a temporary directory
214+ tmp , err := paths .MkTempDir ("" , "" )
215+ if err != nil {
216+ return err
228217 }
229-
230- logrus .
231- WithField ("library name" , libraryName ).
232- WithField ("install path" , installPath ).
233- WithField ("git url" , gitURL ).
234- Trace ("Installing library" )
218+ defer tmp .RemoveAll ()
219+ tmpInstallPath := tmp .Join (gitLibraryName )
235220
236221 depth := 1
237222 if ref != "" {
238223 depth = 0
239224 }
240- repo , err := git .PlainClone (installPath .String (), false , & git.CloneOptions {
225+ repo , err := git .PlainClone (tmpInstallPath .String (), false , & git.CloneOptions {
241226 URL : gitURL ,
242227 Depth : depth ,
243228 Progress : os .Stdout ,
244229 })
245230 if err != nil {
246- logrus .
247- WithError (err ).
248- Warn ("Cloning git repository" )
249231 return err
250232 }
251233
252234 if ref != "" {
253235 if h , err := repo .ResolveRevision (ref ); err != nil {
254- logrus .
255- WithError (err ).
256- Warnf ("Resolving revision %s" , ref )
257236 return err
258237 } else if w , err := repo .Worktree (); err != nil {
259- logrus .
260- WithError (err ).
261- Warn ("Finding worktree" )
262238 return err
263239 } else if err := w .Checkout (& git.CheckoutOptions {Hash : plumbing .NewHash (h .String ())}); err != nil {
264- logrus .
265- WithError (err ).
266- Warnf ("Checking out %s" , h )
267240 return err
268241 }
269242 }
270243
271- if err := validateLibrary (installPath ); err != nil {
272- // Clean up installation directory since this is not a valid library
273- installPath .RemoveAll ()
274- return err
244+ // We don't want the installed library to be a git repository thus we delete this folder
245+ tmpInstallPath .Join (".git" ).RemoveAll ()
246+
247+ // Install extracted library in the destination directory
248+ if err := lm .importLibraryFromDirectory (tmpInstallPath , overwrite ); err != nil {
249+ return fmt .Errorf (tr ("moving extracted archive to destination dir: %s" ), err )
275250 }
276251
277- // We don't want the installed library to be a git repository thus we delete this folder
278- installPath .Join (".git" ).RemoveAll ()
279252 return nil
280253}
281254
0 commit comments