Added target and terminal support.
[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 // SDKsConstructor creates a new instance of SDKs
42 func SDKsConstructor(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         // Update SDK DB on startup by default (can be disable using config file)
67         update := true
68         if s.Config.FileConf.SdkDbUpdate != "startup" {
69                 update = false
70         }
71
72         s.mutex.Lock()
73         defer s.mutex.Unlock()
74
75         // Foreach directories in scripts/sdk
76         nbInstalled := 0
77         for _, d := range dirs {
78                 if !common.IsDir(d) {
79                         continue
80                 }
81
82                 sdksList, err := ListCrossSDK(d, update, s.Log)
83                 if err != nil {
84                         // allow to use XDS even if error on list
85                         s.Log.Errorf("Cannot retrieve SDK list: %v", err)
86                         sdksList, _ = ListCrossSDK(d, false, s.Log)
87                 }
88                 s.LogSillyf("'%s' SDKs list: %v", d, sdksList)
89
90                 for _, sdk := range sdksList {
91                         cSdk, err := s._createNewCrossSDK(sdk, d, false, false)
92                         if err != nil {
93                                 s.Log.Debugf("Error while processing SDK sdk=%v\n err=%s", sdk, err.Error())
94                                 continue
95                         }
96
97                         if cSdk.sdk.Status == xsapiv1.SdkStatusInstalled {
98                                 nbInstalled++
99                         }
100
101                         s.SdksFamilies[cSdk.sdk.FamilyConf.FamilyName] = &cSdk.sdk.FamilyConf
102                 }
103         }
104
105         ctx.Log.Debugf("Cross SDKs: %d defined, %d installed", len(s.Sdks), nbInstalled)
106
107         // Start monitor thread to detect new SDKs
108         sdksDirs := []string{}
109         for _, sf := range s.SdksFamilies {
110                 sdksDirs = append(sdksDirs, sf.RootDir)
111         }
112
113         if len(s.SdksFamilies) == 0 {
114                 s.Log.Warningf("No cross SDKs definition found")
115                 /* TODO: used it or cleanup
116                 } else {
117                         go s.monitorSDKInstallation(sdksDirs)
118                 */
119         }
120
121         return &s, nil
122 }
123
124 // _createNewCrossSDK Private function to create a new Cross SDK
125 func (s *SDKs) _createNewCrossSDK(sdk xsapiv1.SDK, scriptDir string, installing bool, force bool) (*CrossSDK, error) {
126
127         cSdk, err := NewCrossSDK(s.Context, sdk, scriptDir)
128         if err != nil {
129                 return cSdk, err
130         }
131
132         // Allow to overwrite not installed SDK or when force is set
133         if _, exist := s.Sdks[cSdk.sdk.ID]; exist {
134                 if !force && cSdk.sdk.Path != "" && common.Exists(cSdk.sdk.Path) {
135                         return cSdk, fmt.Errorf("SDK ID %s already installed in %s", cSdk.sdk.ID, cSdk.sdk.Path)
136                 }
137                 if !force && cSdk.sdk.Status != xsapiv1.SdkStatusNotInstalled {
138                         return cSdk, fmt.Errorf("Duplicate SDK ID %s (use force to overwrite)", cSdk.sdk.ID)
139                 }
140         }
141
142         // Sanity check
143         errMsg := "Invalid SDK definition "
144         if installing && cSdk.sdk.Path == "" {
145                 return cSdk, fmt.Errorf(errMsg + "(path not set)")
146         }
147         if installing && cSdk.sdk.URL == "" {
148                 return cSdk, fmt.Errorf(errMsg + "(url not set)")
149         }
150
151         // Add to list
152         s.Sdks[cSdk.sdk.ID] = cSdk
153
154         return cSdk, err
155 }
156
157 // Stop SDKs management
158 func (s *SDKs) Stop() {
159         close(s.stop)
160 }
161
162 // monitorSDKInstallation
163 /* TODO: used it or cleanup
164 import  "github.com/zillode/notify"
165
166 func (s *SDKs) monitorSDKInstallation(watchingDirs []string) {
167
168         // Set up a watchpoint listening for inotify-specific events
169         c := make(chan notify.EventInfo, 1)
170
171         addWatcher := func(rootDir string) error {
172                 s.Log.Debugf("SDK Register watcher: rootDir=%s", rootDir)
173
174                 if err := notify.Watch(rootDir+"/...", c, notify.Create, notify.Remove); err != nil {
175                         return fmt.Errorf("SDK monitor: rootDir=%v err=%v", rootDir, err)
176                 }
177                 return nil
178         }
179
180         // Add directory watchers
181         for _, dir := range watchingDirs {
182                 if err := addWatcher(dir); err != nil {
183                         s.Log.Errorln(err.Error())
184                 }
185         }
186
187         // Wait inotify or stop events
188         for {
189                 select {
190                 case <-s.stop:
191                         s.Log.Debugln("Stop monitorSDKInstallation")
192                         notify.Stop(c)
193                         return
194                 case ei := <-c:
195                         s.LogSillyf("monitorSDKInstallation SDKs event %v, path %v\n", ei.Event(), ei.Path())
196
197                         // Filter out all event that doesn't match environment file
198                         if !strings.Contains(ei.Path(), "environment-setup-") {
199                                 continue
200                         }
201                         dir := path.Dir(ei.Path())
202
203                         sdk, err := s.GetByPath(dir)
204                         if err != nil {
205                                 s.Log.Warningf("Cannot find SDK path to notify creation")
206                                 s.LogSillyf("event: %v", ei.Event())
207                                 continue
208                         }
209
210                         switch ei.Event() {
211                         case notify.Create:
212                                 sdkDef, err := GetSDKInfo(scriptDir, sdk.URL, "", "", s.Log)
213                                 if err != nil {
214                                         s.Log.Warningf("Cannot get sdk info: %v", err)
215                                         continue
216                                 }
217                                 sdk.Path = sdkDef.Path
218                                 sdk.Path = sdkDef.SetupFile
219
220                                 // Emit Folder state change event
221                                 if err := s.events.Emit(xsapiv1.EVTSDKAdd, sdk, ""); err != nil {
222                                         s.Log.Warningf("Cannot notify SDK install: %v", err)
223                                 }
224
225                         case notify.Remove, notify.InMovedFrom:
226                                 // Emit Folder state change event
227                                 if err := s.events.Emit(xsapiv1.EVTSDKRemove, sdk, ""); err != nil {
228                                         s.Log.Warningf("Cannot notify SDK remove: %v", err)
229                                 }
230                         }
231                 }
232         }
233 }
234 */
235
236 // ResolveID Complete an SDK ID (helper for user that can use partial ID value)
237 func (s *SDKs) ResolveID(id string) (string, error) {
238         if id == "" {
239                 return "", nil
240         }
241
242         match := []string{}
243         for iid := range s.Sdks {
244                 if strings.HasPrefix(iid, id) {
245                         match = append(match, iid)
246                 }
247         }
248
249         if len(match) == 1 {
250                 return match[0], nil
251         } else if len(match) == 0 {
252                 return id, fmt.Errorf("Unknown sdk id")
253         }
254         return id, fmt.Errorf("Multiple sdk IDs found: %v", match)
255 }
256
257 // Get returns an SDK from id
258 func (s *SDKs) Get(id string) *xsapiv1.SDK {
259         s.mutex.Lock()
260         defer s.mutex.Unlock()
261
262         sc, exist := s.Sdks[id]
263         if !exist {
264                 return nil
265         }
266         return (*sc).Get()
267 }
268
269 // GetByPath Find a SDK from path
270 func (s *SDKs) GetByPath(path string) (*xsapiv1.SDK, error) {
271         if path == "" {
272                 return nil, fmt.Errorf("can't found sdk (empty path)")
273         }
274         for _, ss := range s.Sdks {
275                 if ss.sdk.Path == path {
276                         return ss.Get(), nil
277                 }
278         }
279         return nil, fmt.Errorf("not found")
280 }
281
282 // GetAll returns all existing SDKs
283 func (s *SDKs) GetAll() []xsapiv1.SDK {
284         s.mutex.Lock()
285         defer s.mutex.Unlock()
286         res := []xsapiv1.SDK{}
287         for _, v := range s.Sdks {
288                 res = append(res, *(*v).Get())
289         }
290         return res
291 }
292
293 // GetEnvCmd returns the command used to initialized the environment for an SDK
294 func (s *SDKs) GetEnvCmd(id string, defaultID string) []string {
295         if id == "" && defaultID == "" {
296                 // no env cmd
297                 return []string{}
298         }
299
300         s.mutex.Lock()
301         defer s.mutex.Unlock()
302
303         if iid, err := s.ResolveID(id); err == nil {
304                 if sdk, exist := s.Sdks[iid]; exist {
305                         return sdk.GetEnvCmd()
306                 }
307         }
308
309         if sdk, exist := s.Sdks[defaultID]; defaultID != "" && exist {
310                 return sdk.GetEnvCmd()
311         }
312
313         // Return default env that may be empty
314         return []string{}
315 }
316
317 // Install Used to install a new SDK
318 func (s *SDKs) Install(id, filepath string, force bool, timeout int, args []string, sess *ClientSession) (*xsapiv1.SDK, error) {
319
320         var sdk *xsapiv1.SDK
321         var err error
322         scriptDir := ""
323         sdkFilename := ""
324
325         if id != "" && filepath != "" {
326                 return nil, fmt.Errorf("invalid parameter, both id and filepath are set")
327         }
328
329         s.mutex.Lock()
330         defer s.mutex.Unlock()
331
332         if id != "" {
333                 curSdk, exist := s.Sdks[id]
334                 if !exist {
335                         return nil, fmt.Errorf("unknown id")
336                 }
337
338                 sdk = &curSdk.sdk
339                 scriptDir = sdk.FamilyConf.ScriptsDir
340
341                 // Update path when not set
342                 if sdk.Path == "" {
343                         sdkDef, err := GetSDKInfo(scriptDir, sdk.URL, "", "", s.Log)
344                         if err != nil || sdkDef.Path == "" {
345                                 return nil, fmt.Errorf("cannot retrieve sdk path %v", err)
346                         }
347                         sdk.Path = sdkDef.Path
348                 }
349
350         } else if filepath != "" {
351                 // FIXME support any location and also sharing either by pathmap or Syncthing
352                 baseDir := "${HOME}/xds-workspace/sdks"
353                 sdkFilename, _ = common.ResolveEnvVar(path.Join(baseDir, path.Base(filepath)))
354                 if !common.Exists(sdkFilename) {
355                         return nil, fmt.Errorf("SDK file not accessible, must be in %s", baseDir)
356                 }
357
358                 for _, sf := range s.SdksFamilies {
359                         sdkDef, err := GetSDKInfo(sf.ScriptsDir, "", sdkFilename, "", s.Log)
360                         if err == nil {
361                                 // OK, sdk found
362                                 sdk = &sdkDef
363                                 scriptDir = sf.ScriptsDir
364                                 break
365                         }
366
367                         s.Log.Debugf("GetSDKInfo error: family=%s, sdkFilename=%s, err=%v", sf.FamilyName, path.Base(sdkFilename), err)
368                 }
369                 if sdk == nil {
370                         return nil, fmt.Errorf("Cannot identify SDK family for %s", path.Base(filepath))
371                 }
372
373         } else {
374                 return nil, fmt.Errorf("invalid parameter, id or filepath must be set")
375         }
376
377         cSdk, err := s._createNewCrossSDK(*sdk, scriptDir, true, force)
378         if err != nil {
379                 return nil, err
380         }
381
382         // Launch script to install
383         // (note that add event will be generated by monitoring thread)
384         if err := cSdk.Install(sdkFilename, force, timeout, args, sess); err != nil {
385                 return &cSdk.sdk, err
386         }
387
388         return &cSdk.sdk, nil
389 }
390
391 // AbortInstall Used to abort SDK installation
392 func (s *SDKs) AbortInstall(id string, timeout int) (*xsapiv1.SDK, error) {
393
394         if id == "" {
395                 return nil, fmt.Errorf("invalid parameter")
396         }
397         cSdk, exist := s.Sdks[id]
398         if !exist {
399                 return nil, fmt.Errorf("unknown id")
400         }
401
402         s.mutex.Lock()
403         defer s.mutex.Unlock()
404
405         err := cSdk.AbortInstallRemove(timeout)
406
407         return &cSdk.sdk, err
408 }
409
410 // Remove Used to uninstall a SDK
411 func (s *SDKs) Remove(id string, timeout int, sess *ClientSession) (*xsapiv1.SDK, error) {
412
413         cSdk, exist := s.Sdks[id]
414         if !exist {
415                 return nil, fmt.Errorf("unknown id")
416         }
417
418         s.mutex.Lock()
419         defer s.mutex.Unlock()
420
421         // Launch script to remove/uninstall
422         // (note that remove event will be generated by monitoring thread)
423         if err := cSdk.Remove(timeout, sess); err != nil {
424                 return &cSdk.sdk, err
425         }
426
427         sdk := cSdk.sdk
428
429         // Don't delete it from s.Sdks
430         // (always keep sdk reference to allow for example re-install)
431
432         return &sdk, nil
433 }