@@ -29,72 +29,121 @@ import (
29
29
"github.com/arduino/arduino-cli/arduino/utils"
30
30
paths "github.com/arduino/go-paths-helper"
31
31
"github.com/codeclysm/extract/v3"
32
- "github.com/sirupsen/logrus "
32
+ semver "go.bug.st/relaxed-semver "
33
33
"gopkg.in/src-d/go-git.v4"
34
34
"gopkg.in/src-d/go-git.v4/plumbing"
35
35
)
36
36
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
38
44
39
- func (e * alreadyInstalledError ) Error () string {
40
- return tr ("library already installed" )
41
- }
45
+ // Version of the library to install
46
+ Version * semver.Version
42
47
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
+ }
48
57
49
58
// InstallPrerequisiteCheck performs prequisite checks to install a library. It returns the
50
59
// install path, where the library should be installed and the possible library that is already
51
60
// 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
59
65
}
60
66
61
- name := indexLibrary .Library .Name
62
67
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
- }
68
68
69
69
if len (libs ) > 1 {
70
70
libsDir := paths .NewPathList ()
71
71
for _ , lib := range libs {
72
72
libsDir .Add (lib .InstallDir )
73
73
}
74
- return nil , nil , & arduino.MultipleLibraryInstallDetected {
74
+ return nil , & arduino.MultipleLibraryInstallDetected {
75
75
LibName : name ,
76
76
LibsDir : libsDir ,
77
77
Message : tr ("Automatic library install can't be performed in this case, please manually remove all duplicates and retry." ),
78
78
}
79
79
}
80
80
81
81
var replaced * libraries.Library
82
+ var upToDate bool
82
83
if len (libs ) == 1 {
83
- replaced = libs [0 ]
84
+ lib := libs [0 ]
85
+ replaced = lib
86
+ upToDate = lib .Version != nil && lib .Version .Equal (version )
84
87
}
85
88
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
+ }
91
94
}
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
93
103
}
94
104
95
105
// 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
98
147
}
99
148
100
149
// Uninstall removes a Library
@@ -103,7 +152,7 @@ func (lm *LibrariesManager) Uninstall(lib *libraries.Library) error {
103
152
return fmt .Errorf (tr ("install directory not set" ))
104
153
}
105
154
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 )
107
156
}
108
157
109
158
alternatives := lm .Libraries [lib .Name ]
@@ -113,20 +162,15 @@ func (lm *LibrariesManager) Uninstall(lib *libraries.Library) error {
113
162
}
114
163
115
164
// 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 ("" , "" )
123
168
if err != nil {
124
169
return err
125
170
}
126
- // Deletes temp dir used to extract archive when finished
127
171
defer tmpDir .RemoveAll ()
128
172
129
- file , err := os .Open (archivePath )
173
+ file , err := archivePath .Open ()
130
174
if err != nil {
131
175
return err
132
176
}
@@ -138,58 +182,21 @@ func (lm *LibrariesManager) InstallZipLib(ctx context.Context, archivePath strin
138
182
return fmt .Errorf (tr ("extracting archive: %w" ), err )
139
183
}
140
184
141
- paths , err := tmpDir .ReadDir ()
185
+ libRootFiles , err := tmpDir .ReadDir ()
142
186
if err != nil {
143
187
return err
144
188
}
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 {
150
191
return fmt .Errorf (tr ("archive is not valid: multiple files found in zip file top level" ))
151
192
}
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" ))
183
195
}
196
+ tmpInstallPath := libRootFiles [0 ]
184
197
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 {
193
200
return fmt .Errorf (tr ("moving extracted archive to destination dir: %s" ), err )
194
201
}
195
202
@@ -198,84 +205,50 @@ func (lm *LibrariesManager) InstallZipLib(ctx context.Context, archivePath strin
198
205
199
206
// InstallGitLib installs a library hosted on a git repository on the specified path.
200
207
func (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 )
207
209
if err != nil {
208
- logrus .
209
- WithError (err ).
210
- Warn ("Parsing git URL" )
211
210
return err
212
211
}
213
212
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
228
217
}
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 )
235
220
236
221
depth := 1
237
222
if ref != "" {
238
223
depth = 0
239
224
}
240
- repo , err := git .PlainClone (installPath .String (), false , & git.CloneOptions {
225
+ repo , err := git .PlainClone (tmpInstallPath .String (), false , & git.CloneOptions {
241
226
URL : gitURL ,
242
227
Depth : depth ,
243
228
Progress : os .Stdout ,
244
229
})
245
230
if err != nil {
246
- logrus .
247
- WithError (err ).
248
- Warn ("Cloning git repository" )
249
231
return err
250
232
}
251
233
252
234
if ref != "" {
253
235
if h , err := repo .ResolveRevision (ref ); err != nil {
254
- logrus .
255
- WithError (err ).
256
- Warnf ("Resolving revision %s" , ref )
257
236
return err
258
237
} else if w , err := repo .Worktree (); err != nil {
259
- logrus .
260
- WithError (err ).
261
- Warn ("Finding worktree" )
262
238
return err
263
239
} 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 )
267
240
return err
268
241
}
269
242
}
270
243
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 )
275
250
}
276
251
277
- // We don't want the installed library to be a git repository thus we delete this folder
278
- installPath .Join (".git" ).RemoveAll ()
279
252
return nil
280
253
}
281
254
0 commit comments