Migration to AGL gerrit (update go import)
[src/xds/xds-server.git] / lib / xdsserver / sdks.go
1 /*
2  * Copyright (C) 2017-2018 "IoT.bzh"
3  * Author Sebastien Douheret <sebastien@iot.bzh>
4  *
5  * Licensed under the Apache License, Version 2.0 (the "License");
6  * you may not use this file except in compliance with the License.
7  * You may obtain a copy of the License at
8  *
9  *   http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  */
17
18 package xdsserver
19
20 import (
21         "fmt"
22         "path"
23         "path/filepath"
24         "strings"
25         "sync"
26
27         common "gerrit.automotivelinux.org/gerrit/src/xds/xds-common.git/golib"
28         "gerrit.automotivelinux.org/gerrit/src/xds/xds-server/lib/xsapiv1"
29 )
30
31 // SDKs List of installed SDK
32 type SDKs struct {
33         *Context
34         Sdks         map[string]*CrossSDK
35         SdksFamilies map[string]*xsapiv1.SDKFamilyConfig
36
37         mutex sync.Mutex
38         stop  chan struct{} // signals intentional stop
39 }
40
41 // NewSDKs creates a new instance of SDKs
42 func NewSDKs(ctx *Context) (*SDKs, error) {
43         s := SDKs{
44                 Context:      ctx,
45                 Sdks:         make(map[string]*CrossSDK),
46                 SdksFamilies: make(map[string]*xsapiv1.SDKFamilyConfig),
47                 stop:         make(chan struct{}),
48         }
49
50         scriptsDir := ctx.Config.FileConf.SdkScriptsDir
51         if !common.Exists(scriptsDir) {
52                 // allow to use scripts/sdk in debug mode
53                 scriptsDir = filepath.Join(filepath.Dir(ctx.Config.FileConf.SdkScriptsDir), "scripts", "sdks")
54                 if !common.Exists(scriptsDir) {
55                         return &s, fmt.Errorf("scripts directory doesn't exist (%v)", scriptsDir)
56                 }
57         }
58         s.Log.Infof("SDK scripts dir: %s", scriptsDir)
59
60         dirs, err := filepath.Glob(path.Join(scriptsDir, "*"))
61         if err != nil {
62                 s.Log.Errorf("Error while retrieving SDK scripts: dir=%s, error=%s", scriptsDir, err.Error())
63                 return &s, err
64         }
65
66         s.mutex.Lock()
67         defer s.mutex.Unlock()
68
69         // Foreach directories in scripts/sdk
70         nbInstalled := 0
71         for _, d := range dirs {
72                 if !common.IsDir(d) {
73                         continue
74                 }
75
76                 sdksList, err := ListCrossSDK(d, s.Log)
77                 if err != nil {
78                         // allow to use XDS even if error on list
79                         s.Log.Errorf("Cannot retrieve SDK list: %v", err)
80                 }
81                 s.LogSillyf("'%s' SDKs list: %v", d, sdksList)
82
83                 for _, sdk := range sdksList {
84                         cSdk, err := s._createNewCrossSDK(sdk, d, false, false)
85                         if err != nil {
86                                 s.Log.Debugf("Error while processing SDK sdk=%v\n err=%s", sdk, err.Error())
87                                 continue
88                         }
89
90                         if cSdk.sdk.Status == xsapiv1.SdkStatusInstalled {
91                                 nbInstalled++
92                         }
93
94                         s.SdksFamilies[cSdk.sdk.FamilyConf.FamilyName] = &cSdk.sdk.FamilyConf
95                 }
96         }
97
98         ctx.Log.Debugf("Cross SDKs: %d defined, %d installed", len(s.Sdks), nbInstalled)
99
100         // Start monitor thread to detect new SDKs
101         sdksDirs := []string{}
102         for _, sf := range s.SdksFamilies {
103                 sdksDirs = append(sdksDirs, sf.RootDir)
104         }
105
106         if len(s.SdksFamilies) == 0 {
107                 s.Log.Warningf("No cross SDKs definition found")
108                 /* TODO: used it or cleanup
109                 } else {
110                         go s.monitorSDKInstallation(sdksDirs)
111                 */
112         }
113
114         return &s, nil
115 }
116
117 // _createNewCrossSDK Private function to create a new Cross SDK
118 func (s *SDKs) _createNewCrossSDK(sdk xsapiv1.SDK, scriptDir string, installing bool, force bool) (*CrossSDK, error) {
119
120         cSdk, err := NewCrossSDK(s.Context, sdk, scriptDir)
121         if err != nil {
122                 return cSdk, err
123         }
124
125         // Allow to overwrite not installed SDK or when force is set
126         if _, exist := s.Sdks[cSdk.sdk.ID]; exist {
127                 if !force && cSdk.sdk.Path != "" && common.Exists(cSdk.sdk.Path) {
128                         return cSdk, fmt.Errorf("SDK ID %s already installed in %s", cSdk.sdk.ID, cSdk.sdk.Path)
129                 }
130                 if !force && cSdk.sdk.Status != xsapiv1.SdkStatusNotInstalled {
131                         return cSdk, fmt.Errorf("Duplicate SDK ID %s (use force to overwrite)", cSdk.sdk.ID)
132                 }
133         }
134
135         // Sanity check
136         errMsg := "Invalid SDK definition "
137         if installing && cSdk.sdk.Path == "" {
138                 return cSdk, fmt.Errorf(errMsg + "(path not set)")
139         }
140         if installing && cSdk.sdk.URL == "" {
141                 return cSdk, fmt.Errorf(errMsg + "(url not set)")
142         }
143
144         // Add to list
145         s.Sdks[cSdk.sdk.ID] = cSdk
146
147         return cSdk, err
148 }
149
150 // Stop SDKs management
151 func (s *SDKs) Stop() {
152         close(s.stop)
153 }
154
155 // monitorSDKInstallation
156 /* TODO: used it or cleanup
157 import  "github.com/zillode/notify"
158
159 func (s *SDKs) monitorSDKInstallation(watchingDirs []string) {
160
161         // Set up a watchpoint listening for inotify-specific events
162         c := make(chan notify.EventInfo, 1)
163
164         addWatcher := func(rootDir string) error {
165                 s.Log.Debugf("SDK Register watcher: rootDir=%s", rootDir)
166
167                 if err := notify.Watch(rootDir+"/...", c, notify.Create, notify.Remove); err != nil {
168                         return fmt.Errorf("SDK monitor: rootDir=%v err=%v", rootDir, err)
169                 }
170                 return nil
171         }
172
173         // Add directory watchers
174         for _, dir := range watchingDirs {
175                 if err := addWatcher(dir); err != nil {
176                         s.Log.Errorln(err.Error())
177                 }
178         }
179
180         // Wait inotify or stop events
181         for {
182                 select {
183                 case <-s.stop:
184                         s.Log.Debugln("Stop monitorSDKInstallation")
185                         notify.Stop(c)
186                         return
187                 case ei := <-c:
188                         s.LogSillyf("monitorSDKInstallation SDKs event %v, path %v\n", ei.Event(), ei.Path())
189
190                         // Filter out all event that doesn't match environment file
191                         if !strings.Contains(ei.Path(), "environment-setup-") {
192                                 continue
193                         }
194                         dir := path.Dir(ei.Path())
195
196                         sdk, err := s.GetByPath(dir)
197                         if err != nil {
198                                 s.Log.Warningf("Cannot find SDK path to notify creation")
199                                 s.LogSillyf("event: %v", ei.Event())
200                                 continue
201                         }
202
203                         switch ei.Event() {
204                         case notify.Create:
205                                 sdkDef, err := GetSDKInfo(scriptDir, sdk.URL, "", "", s.Log)
206                                 if err != nil {
207                                         s.Log.Warningf("Cannot get sdk info: %v", err)
208                                         continue
209                                 }
210                                 sdk.Path = sdkDef.Path
211                                 sdk.Path = sdkDef.SetupFile
212
213                                 // Emit Folder state change event
214                                 if err := s.events.Emit(xsapiv1.EVTSDKInstall, sdk, ""); err != nil {
215                                         s.Log.Warningf("Cannot notify SDK install: %v", err)
216                                 }
217
218                         case notify.Remove, notify.InMovedFrom:
219                                 // Emit Folder state change event
220                                 if err := s.events.Emit(xsapiv1.EVTSDKRemove, sdk, ""); err != nil {
221                                         s.Log.Warningf("Cannot notify SDK remove: %v", err)
222                                 }
223                         }
224                 }
225         }
226 }
227 */
228
229 // ResolveID Complete an SDK ID (helper for user that can use partial ID value)
230 func (s *SDKs) ResolveID(id string) (string, error) {
231         if id == "" {
232                 return "", nil
233         }
234
235         match := []string{}
236         for iid := range s.Sdks {
237                 if strings.HasPrefix(iid, id) {
238                         match = append(match, iid)
239                 }
240         }
241
242         if len(match) == 1 {
243                 return match[0], nil
244         } else if len(match) == 0 {
245                 return id, fmt.Errorf("Unknown sdk id")
246         }
247         return id, fmt.Errorf("Multiple sdk IDs found: %v", match)
248 }
249
250 // Get returns an SDK from id
251 func (s *SDKs) Get(id string) *xsapiv1.SDK {
252         s.mutex.Lock()
253         defer s.mutex.Unlock()
254
255         sc, exist := s.Sdks[id]
256         if !exist {
257                 return nil
258         }
259         return (*sc).Get()
260 }
261
262 // GetByPath Find a SDK from path
263 func (s *SDKs) GetByPath(path string) (*xsapiv1.SDK, error) {
264         if path == "" {
265                 return nil, fmt.Errorf("can't found sdk (empty path)")
266         }
267         for _, ss := range s.Sdks {
268                 if ss.sdk.Path == path {
269                         return ss.Get(), nil
270                 }
271         }
272         return nil, fmt.Errorf("not found")
273 }
274
275 // GetAll returns all existing SDKs
276 func (s *SDKs) GetAll() []xsapiv1.SDK {
277         s.mutex.Lock()
278         defer s.mutex.Unlock()
279         res := []xsapiv1.SDK{}
280         for _, v := range s.Sdks {
281                 res = append(res, *(*v).Get())
282         }
283         return res
284 }
285
286 // GetEnvCmd returns the command used to initialized the environment for an SDK
287 func (s *SDKs) GetEnvCmd(id string, defaultID string) []string {
288         if id == "" && defaultID == "" {
289                 // no env cmd
290                 return []string{}
291         }
292
293         s.mutex.Lock()
294         defer s.mutex.Unlock()
295
296         if iid, err := s.ResolveID(id); err == nil {
297                 if sdk, exist := s.Sdks[iid]; exist {
298                         return sdk.GetEnvCmd()
299                 }
300         }
301
302         if sdk, exist := s.Sdks[defaultID]; defaultID != "" && exist {
303                 return sdk.GetEnvCmd()
304         }
305
306         // Return default env that may be empty
307         return []string{}
308 }
309
310 // Install Used to install a new SDK
311 func (s *SDKs) Install(id, filepath string, force bool, timeout int, args []string, sess *ClientSession) (*xsapiv1.SDK, error) {
312
313         var sdk *xsapiv1.SDK
314         var err error
315         scriptDir := ""
316         sdkFilename := ""
317
318         if id != "" && filepath != "" {
319                 return nil, fmt.Errorf("invalid parameter, both id and filepath are set")
320         }
321
322         s.mutex.Lock()
323         defer s.mutex.Unlock()
324
325         if id != "" {
326                 curSdk, exist := s.Sdks[id]
327                 if !exist {
328                         return nil, fmt.Errorf("unknown id")
329                 }
330
331                 sdk = &curSdk.sdk
332                 scriptDir = sdk.FamilyConf.ScriptsDir
333
334                 // Update path when not set
335                 if sdk.Path == "" {
336                         sdkDef, err := GetSDKInfo(scriptDir, sdk.URL, "", "", s.Log)
337                         if err != nil || sdkDef.Path == "" {
338                                 return nil, fmt.Errorf("cannot retrieve sdk path %v", err)
339                         }
340                         sdk.Path = sdkDef.Path
341                 }
342
343         } else if filepath != "" {
344                 // FIXME support any location and also sharing either by pathmap or Syncthing
345                 baseDir := "${HOME}/xds-workspace/sdks"
346                 sdkFilename, _ = common.ResolveEnvVar(path.Join(baseDir, path.Base(filepath)))
347                 if !common.Exists(sdkFilename) {
348                         return nil, fmt.Errorf("SDK file not accessible, must be in %s", baseDir)
349                 }
350
351                 for _, sf := range s.SdksFamilies {
352                         sdkDef, err := GetSDKInfo(sf.ScriptsDir, "", sdkFilename, "", s.Log)
353                         if err == nil {
354                                 // OK, sdk found
355                                 sdk = &sdkDef
356                                 scriptDir = sf.ScriptsDir
357                                 break
358                         }
359
360                         s.Log.Debugf("GetSDKInfo error: family=%s, sdkFilename=%s, err=%v", sf.FamilyName, path.Base(sdkFilename), err)
361                 }
362                 if sdk == nil {
363                         return nil, fmt.Errorf("Cannot identify SDK family for %s", path.Base(filepath))
364                 }
365
366         } else {
367                 return nil, fmt.Errorf("invalid parameter, id or filepath must be set")
368         }
369
370         cSdk, err := s._createNewCrossSDK(*sdk, scriptDir, true, force)
371         if err != nil {
372                 return nil, err
373         }
374
375         // Launch script to install
376         // (note that add event will be generated by monitoring thread)
377         if err := cSdk.Install(sdkFilename, force, timeout, args, sess); err != nil {
378                 return &cSdk.sdk, err
379         }
380
381         return &cSdk.sdk, nil
382 }
383
384 // AbortInstall Used to abort SDK installation
385 func (s *SDKs) AbortInstall(id string, timeout int) (*xsapiv1.SDK, error) {
386
387         if id == "" {
388                 return nil, fmt.Errorf("invalid parameter")
389         }
390         cSdk, exist := s.Sdks[id]
391         if !exist {
392                 return nil, fmt.Errorf("unknown id")
393         }
394
395         s.mutex.Lock()
396         defer s.mutex.Unlock()
397
398         err := cSdk.AbortInstallRemove(timeout)
399
400         return &cSdk.sdk, err
401 }
402
403 // Remove Used to uninstall a SDK
404 func (s *SDKs) Remove(id string, timeout int, sess *ClientSession) (*xsapiv1.SDK, error) {
405
406         cSdk, exist := s.Sdks[id]
407         if !exist {
408                 return nil, fmt.Errorf("unknown id")
409         }
410
411         s.mutex.Lock()
412         defer s.mutex.Unlock()
413
414         // Launch script to remove/uninstall
415         // (note that remove event will be generated by monitoring thread)
416         if err := cSdk.Remove(timeout, sess); err != nil {
417                 return &cSdk.sdk, err
418         }
419
420         sdk := cSdk.sdk
421
422         // Don't delete it from s.Sdks
423         // (always keep sdk reference to allow for example re-install)
424
425         return &sdk, nil
426 }