@@ -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