aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSebastien Douheret <sebastien.douheret@iot.bzh>2017-12-22 21:26:40 +0100
committerSebastien Douheret <sebastien.douheret@iot.bzh>2017-12-22 21:29:59 +0100
commitf1c182ede3c4aed0d6196d05b0a64ff93372e755 (patch)
tree2cf95732a06808aac8325bccb5199346b33165a2
parent285332c351777b74abca638b8b2a2cde3c68edc6 (diff)
Added SDKs management support.
Signed-off-by: Sebastien Douheret <sebastien.douheret@iot.bzh>
-rw-r--r--.gitignore1
-rw-r--r--.vscode/launch.json6
-rw-r--r--.vscode/settings.json3
-rw-r--r--Makefile2
-rw-r--r--conf.d/etc/xds/server/server-config.json2
-rw-r--r--lib/syncthing/st.go2
-rw-r--r--lib/xdsconfig/config.go4
-rw-r--r--lib/xdsconfig/fileconfig.go18
-rw-r--r--lib/xdsserver/apiv1-events.go2
-rw-r--r--lib/xdsserver/apiv1-sdks.go79
-rw-r--r--lib/xdsserver/apiv1.go3
-rw-r--r--lib/xdsserver/sdk.go370
-rw-r--r--lib/xdsserver/sdks.go222
-rw-r--r--lib/xdsserver/webserver.go1
-rw-r--r--lib/xsapiv1/events.go12
-rw-r--r--lib/xsapiv1/sdks.go61
-rw-r--r--scripts/sdks/README.md78
-rwxr-xr-xscripts/sdks/agl/_build-sdks-json.sh121
-rwxr-xr-xscripts/sdks/agl/_env-init.sh29
-rwxr-xr-xscripts/sdks/agl/add103
-rwxr-xr-xscripts/sdks/agl/get-config33
-rwxr-xr-xscripts/sdks/agl/list137
-rwxr-xr-xscripts/sdks/agl/remove32
-rwxr-xr-xscripts/sdks/agl/update22
-rwxr-xr-xscripts/xds-utils/install-agl-sdks.sh2
-rw-r--r--webapp/src/index.html2
26 files changed, 1283 insertions, 64 deletions
diff --git a/.gitignore b/.gitignore
index a5007eb..debbc5d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -6,6 +6,7 @@ package
debug
cmd/*/debug
*.zip
+scripts/sdks/**/sdks*.json
webapp/dist
webapp/node_modules
diff --git a/.vscode/launch.json b/.vscode/launch.json
index 245c2ee..f976fb6 100644
--- a/.vscode/launch.json
+++ b/.vscode/launch.json
@@ -32,6 +32,12 @@
},
"args": ["-log", "debug", "-c", "__config_local_dev.json"],
"showLog": false
+ },
+ {
+ "type": "node",
+ "request": "launch",
+ "name": "Script sdk list",
+ "program": "${workspaceFolder}/scripts/sdks/agl/list"
}
]
}
diff --git a/.vscode/settings.json b/.vscode/settings.json
index 24aa012..6415d9e 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -29,6 +29,7 @@
"inotify", "Inot", "pname", "pkill", "sdkid", "CLOUDSYNC", "xdsagent",
"gdbserver", "golib", "eows", "mfolders", "IFOLDER", "flds", "dflt",
"stconfig", "reflectme", "franciscocpg", "crosssdk", "urfave", "EXEPATH",
- "conv", "Sillyf", "xsapiv"
+ "conv", "Sillyf", "xsapiv",
+ "EVTSDK"
]
}
diff --git a/Makefile b/Makefile
index 86107f1..adefb65 100644
--- a/Makefile
+++ b/Makefile
@@ -149,7 +149,7 @@ webapp/install:
.PHONY: scripts
scripts:
- @mkdir -p $(LOCAL_BINDIR) && cp -rf scripts/xds-utils $(LOCAL_BINDIR)
+ @mkdir -p $(LOCAL_BINDIR) && cp -rf scripts/xds-utils scripts/sdks $(LOCAL_BINDIR)
.PHONY: conffile
conffile:
diff --git a/conf.d/etc/xds/server/server-config.json b/conf.d/etc/xds/server/server-config.json
index 5562bb3..a27d4f5 100644
--- a/conf.d/etc/xds/server/server-config.json
+++ b/conf.d/etc/xds/server/server-config.json
@@ -2,7 +2,7 @@
"webAppDir": "www",
"httpPort": "8000",
"shareRootDir": "${HOME}/.xds/server/projects",
- "sdkRootDir": "/xdt/sdk",
+ "sdkScriptsDir": "${EXEPATH}/scripts/sdks",
"syncthing": {
"binDir": "",
"home": "${HOME}/.xds/server/syncthing-config",
diff --git a/lib/syncthing/st.go b/lib/syncthing/st.go
index a52e1fe..bb50ea8 100644
--- a/lib/syncthing/st.go
+++ b/lib/syncthing/st.go
@@ -191,7 +191,7 @@ func (s *SyncThing) startProc(exeName string, args []string, env []string, eChan
cmdOut, err := cmd.StdoutPipe()
if err != nil {
- return nil, fmt.Errorf("Pipe stdout error for : %s", err)
+ return nil, fmt.Errorf("Pipe stdout error for : %v", err)
}
go io.Copy(outfile, cmdOut)
diff --git a/lib/xdsconfig/config.go b/lib/xdsconfig/config.go
index 59cf394..74ce21e 100644
--- a/lib/xdsconfig/config.go
+++ b/lib/xdsconfig/config.go
@@ -55,7 +55,7 @@ const (
DefaultPort = "8000"
DefaultShareDir = "${HOME}/.xds/server/projects"
DefaultSTHomeDir = "${HOME}/.xds/server/syncthing-config"
- DefaultSdkRootDir = "/xdt/sdk"
+ DefaultSdkScriptsDir = "${EXEPATH}/scripts/sdks"
)
// Init loads the configuration on start-up
@@ -97,7 +97,7 @@ func Init(cliCtx *cli.Context, log *logrus.Logger) (*Config, error) {
FileConf: FileConfig{
WebAppDir: "webapp/dist",
ShareRootDir: dfltShareDir,
- SdkRootDir: DefaultSdkRootDir,
+ SdkScriptsDir: DefaultSdkScriptsDir,
HTTPPort: DefaultPort,
SThgConf: &SyncThingConf{Home: dfltSTHomeDir},
LogsDir: "",
diff --git a/lib/xdsconfig/fileconfig.go b/lib/xdsconfig/fileconfig.go
index 8e77de7..bf8aa25 100644
--- a/lib/xdsconfig/fileconfig.go
+++ b/lib/xdsconfig/fileconfig.go
@@ -51,12 +51,12 @@ type SyncThingConf struct {
// FileConfig is the JSON structure of xds-server config file (server-config.json)
type FileConfig struct {
- WebAppDir string `json:"webAppDir"`
- ShareRootDir string `json:"shareRootDir"`
- SdkRootDir string `json:"sdkRootDir"`
- HTTPPort string `json:"httpPort"`
- SThgConf *SyncThingConf `json:"syncthing"`
- LogsDir string `json:"logsDir"`
+ WebAppDir string `json:"webAppDir"`
+ ShareRootDir string `json:"shareRootDir"`
+ SdkScriptsDir string `json:"sdkScriptsDir"`
+ HTTPPort string `json:"httpPort"`
+ SThgConf *SyncThingConf `json:"syncthing"`
+ LogsDir string `json:"logsDir"`
}
// readGlobalConfig reads configuration from a config file.
@@ -117,7 +117,7 @@ func readGlobalConfig(c *Config, confFile string) error {
vars := []*string{
&fCfg.WebAppDir,
&fCfg.ShareRootDir,
- &fCfg.SdkRootDir,
+ &fCfg.SdkScriptsDir,
&fCfg.LogsDir}
if fCfg.SThgConf != nil {
vars = append(vars, &fCfg.SThgConf.Home, &fCfg.SThgConf.BinDir)
@@ -136,8 +136,8 @@ func readGlobalConfig(c *Config, confFile string) error {
if fCfg.ShareRootDir == "" {
fCfg.ShareRootDir = c.FileConf.ShareRootDir
}
- if fCfg.SdkRootDir == "" {
- fCfg.SdkRootDir = c.FileConf.SdkRootDir
+ if fCfg.SdkScriptsDir == "" {
+ fCfg.SdkScriptsDir = c.FileConf.SdkScriptsDir
}
if fCfg.HTTPPort == "" {
fCfg.HTTPPort = c.FileConf.HTTPPort
diff --git a/lib/xdsserver/apiv1-events.go b/lib/xdsserver/apiv1-events.go
index 0942753..eedd747 100644
--- a/lib/xdsserver/apiv1-events.go
+++ b/lib/xdsserver/apiv1-events.go
@@ -39,6 +39,8 @@ func (s *APIService) eventsRegister(c *gin.Context) {
return
}
+ // TODO: add args.Filter support
+
sess := s.sessions.Get(c)
if sess == nil {
common.APIError(c, "Unknown sessions")
diff --git a/lib/xdsserver/apiv1-sdks.go b/lib/xdsserver/apiv1-sdks.go
index bcb293b..b38c418 100644
--- a/lib/xdsserver/apiv1-sdks.go
+++ b/lib/xdsserver/apiv1-sdks.go
@@ -22,6 +22,7 @@ import (
"github.com/gin-gonic/gin"
common "github.com/iotbzh/xds-common/golib"
+ "github.com/iotbzh/xds-server/lib/xsapiv1"
)
// getSdks returns all SDKs configuration
@@ -44,3 +45,81 @@ func (s *APIService) getSdk(c *gin.Context) {
c.JSON(http.StatusOK, sdk)
}
+
+// installSdk Install a new Sdk
+func (s *APIService) installSdk(c *gin.Context) {
+ var args xsapiv1.SDKInstallArgs
+
+ if err := c.BindJSON(&args); err != nil {
+ common.APIError(c, "Invalid arguments")
+ return
+ }
+ id, err := s.sdks.ResolveID(args.ID)
+ if err != nil {
+ common.APIError(c, err.Error())
+ return
+ }
+
+ // Support install from ID->URL or from local file
+ if id != "" {
+ s.Log.Debugf("Installing SDK id %s (force %v)", id, args.Force)
+ } else if args.Filename != "" {
+ s.Log.Debugf("Installing SDK filename %s (force %v)", args.Filename, args.Force)
+ }
+
+ // Retrieve session info
+ sess := s.sessions.Get(c)
+ if sess == nil {
+ common.APIError(c, "Unknown sessions")
+ return
+ }
+
+ sdk, err := s.sdks.Install(id, args.Filename, args.Force, args.Timeout, sess)
+ if err != nil {
+ common.APIError(c, err.Error())
+ return
+ }
+
+ c.JSON(http.StatusOK, sdk)
+}
+
+// abortInstallSdk Abort a SDK installation
+func (s *APIService) abortInstallSdk(c *gin.Context) {
+ var args xsapiv1.SDKInstallArgs
+
+ if err := c.BindJSON(&args); err != nil {
+ common.APIError(c, "Invalid arguments")
+ return
+ }
+ id, err := s.sdks.ResolveID(args.ID)
+ if err != nil {
+ common.APIError(c, err.Error())
+ return
+ }
+
+ sdk, err := s.sdks.AbortInstall(id, args.Timeout)
+ if err != nil {
+ common.APIError(c, err.Error())
+ return
+ }
+
+ c.JSON(http.StatusOK, sdk)
+}
+
+// removeSdk Uninstall a Sdk
+func (s *APIService) removeSdk(c *gin.Context) {
+ id, err := s.sdks.ResolveID(c.Param("id"))
+ if err != nil {
+ common.APIError(c, err.Error())
+ return
+ }
+
+ s.Log.Debugln("Remove SDK id ", id)
+
+ delEntry, err := s.sdks.Remove(id)
+ if err != nil {
+ common.APIError(c, err.Error())
+ return
+ }
+ c.JSON(http.StatusOK, delEntry)
+}
diff --git a/lib/xdsserver/apiv1.go b/lib/xdsserver/apiv1.go
index 143c25f..f9d5948 100644
--- a/lib/xdsserver/apiv1.go
+++ b/lib/xdsserver/apiv1.go
@@ -48,6 +48,9 @@ func NewAPIV1(ctx *Context) *APIService {
s.apiRouter.GET("/sdks", s.getSdks)
s.apiRouter.GET("/sdks/:id", s.getSdk)
+ s.apiRouter.POST("/sdks", s.installSdk)
+ s.apiRouter.POST("/sdks/abortinstall", s.abortInstallSdk)
+ s.apiRouter.DELETE("/sdks/:id", s.removeSdk)
s.apiRouter.POST("/make", s.buildMake)
s.apiRouter.POST("/make/:id", s.buildMake)
diff --git a/lib/xdsserver/sdk.go b/lib/xdsserver/sdk.go
index 7a90f6b..c011d09 100644
--- a/lib/xdsserver/sdk.go
+++ b/lib/xdsserver/sdk.go
@@ -18,50 +18,378 @@
package xdsserver
import (
+ "encoding/json"
"fmt"
- "path/filepath"
+ "os"
+ "os/exec"
+ "path"
+ "strconv"
+ "strings"
+ "time"
+ "github.com/Sirupsen/logrus"
+ common "github.com/iotbzh/xds-common/golib"
+ "github.com/iotbzh/xds-common/golib/eows"
"github.com/iotbzh/xds-server/lib/xsapiv1"
uuid "github.com/satori/go.uuid"
)
+// Definition of scripts used to managed SDKs
+const (
+ scriptAdd = "add"
+ scriptGetConfig = "get-config"
+ scriptList = "list"
+ scriptRemove = "remove"
+ scriptUpdate = "update"
+)
+
+var scriptsAll = []string{
+ scriptAdd,
+ scriptGetConfig,
+ scriptList,
+ scriptRemove,
+ scriptUpdate,
+}
+
+var sdkCmdID = 0
+
// CrossSDK Hold SDK config
type CrossSDK struct {
- sdk xsapiv1.SDK
+ *Context
+ sdk xsapiv1.SDK
+ scripts map[string]string
+ installCmd *eows.ExecOverWS
+ removeCmd *eows.ExecOverWS
+
+ bufStdout string
+ bufStderr string
+}
+
+// ListCrossSDK List all available and installed SDK (call "list" script)
+func ListCrossSDK(scriptDir string, log *logrus.Logger) ([]xsapiv1.SDK, error) {
+ sdksList := []xsapiv1.SDK{}
+
+ // Retrieve SDKs list and info
+ cmd := exec.Command(path.Join(scriptDir, scriptList))
+ stdout, err := cmd.CombinedOutput()
+ if err != nil {
+ return sdksList, fmt.Errorf("Cannot get sdks list: %v", err)
+ }
+
+ if err = json.Unmarshal(stdout, &sdksList); err != nil {
+ log.Errorf("SDK list script output:\n%v\n", string(stdout))
+ return sdksList, fmt.Errorf("Cannot decode sdk list %v", err)
+ }
+
+ return sdksList, nil
}
// NewCrossSDK creates a new instance of Syncthing
-func NewCrossSDK(path string) (*CrossSDK, error) {
- // Assume that we have .../<profile>/<version>/<arch>
+func NewCrossSDK(ctx *Context, sdk xsapiv1.SDK, scriptDir string) (*CrossSDK, error) {
s := CrossSDK{
- sdk: xsapiv1.SDK{Path: path},
+ Context: ctx,
+ sdk: sdk,
+ scripts: make(map[string]string),
}
- s.sdk.Arch = filepath.Base(path)
+ // Execute get-config script to retrieve SDK configuration
+ getConfFile := path.Join(scriptDir, scriptGetConfig)
+ if !common.Exists(getConfFile) {
+ return &s, fmt.Errorf("'%s' script file not found in %s", scriptGetConfig, scriptDir)
+ }
- d := filepath.Dir(path)
- s.sdk.Version = filepath.Base(d)
+ cmd := exec.Command(getConfFile)
+ stdout, err := cmd.CombinedOutput()
+ if err != nil {
+ return &s, fmt.Errorf("Cannot get sdk config using %s: %v", getConfFile, err)
+ }
- d = filepath.Dir(d)
- s.sdk.Profile = filepath.Base(d)
+ err = json.Unmarshal(stdout, &s.sdk.FamilyConf)
+ if err != nil {
+ s.Log.Errorf("SDK config script output:\n%v\n", string(stdout))
+ return &s, fmt.Errorf("Cannot decode sdk config %v", err)
+ }
+ famName := s.sdk.FamilyConf.FamilyName
- // Use V3 to ensure that we get same uuid on restart
- s.sdk.ID = uuid.NewV3(uuid.FromStringOrNil("sdks"), s.sdk.Profile+"_"+s.sdk.Arch+"_"+s.sdk.Version).String()
- s.sdk.Name = s.sdk.Arch + " (" + s.sdk.Version + ")"
+ // Sanity check
+ if s.sdk.FamilyConf.RootDir == "" {
+ return &s, fmt.Errorf("SDK config not valid (rootDir not set)")
+ }
+ if s.sdk.FamilyConf.EnvSetupFile == "" {
+ return &s, fmt.Errorf("SDK config not valid (envSetupFile not set)")
+ }
- envFile := filepath.Join(path, "environment-setup*")
- ef, err := filepath.Glob(envFile)
- if err != nil {
- return nil, fmt.Errorf("Cannot retrieve environment setup file: %v", err)
+ // Check that other mandatory scripts are present
+ for _, scr := range scriptsAll {
+ s.scripts[scr] = path.Join(scriptDir, scr)
+ if !common.Exists(s.scripts[scr]) {
+ return &s, fmt.Errorf("Script named '%s' missing in SDK family '%s'", scr, famName)
+ }
+ }
+
+ // Fixed default fields value
+ sdk.LastError = ""
+ if sdk.Status == "" {
+ sdk.Status = xsapiv1.SdkStatusNotInstalled
}
- if len(ef) != 1 {
- return nil, fmt.Errorf("No environment setup file found match %s", envFile)
+
+ // Sanity check
+ errMsg := "Invalid SDK definition "
+ if sdk.Name == "" {
+ return &s, fmt.Errorf(errMsg + "(name not set)")
+ } else if sdk.Profile == "" {
+ return &s, fmt.Errorf(errMsg + "(profile not set)")
+ } else if sdk.Version == "" {
+ return &s, fmt.Errorf(errMsg + "(version not set)")
+ } else if sdk.Arch == "" {
+ return &s, fmt.Errorf(errMsg + "(arch not set)")
+ }
+ if sdk.Status == xsapiv1.SdkStatusInstalled {
+ if sdk.SetupFile == "" {
+ return &s, fmt.Errorf(errMsg + "(setupFile not set)")
+ } else if !common.Exists(sdk.SetupFile) {
+ return &s, fmt.Errorf(errMsg + "(setupFile not accessible)")
+ }
+ if sdk.Path == "" {
+ return &s, fmt.Errorf(errMsg + "(path not set)")
+ } else if !common.Exists(sdk.Path) {
+ return &s, fmt.Errorf(errMsg + "(path not accessible)")
+ }
+ }
+
+ // Use V3 to ensure that we get same uuid on restart
+ nm := s.sdk.Name
+ if nm == "" {
+ nm = s.sdk.Profile + "_" + s.sdk.Arch + "_" + s.sdk.Version
}
- s.sdk.EnvFile = ef[0]
+ s.sdk.ID = uuid.NewV3(uuid.FromStringOrNil("sdks"), nm).String()
+
+ s.LogSillyf("New SDK: ID=%v, Family=%s, Name=%v", s.sdk.ID[:8], s.sdk.FamilyConf.FamilyName, s.sdk.Name)
return &s, nil
}
+// Install a SDK (non blocking command, IOW run in background)
+func (s *CrossSDK) Install(file string, force bool, timeout int, sess *ClientSession) error {
+
+ if s.sdk.Status == xsapiv1.SdkStatusInstalled {
+ return fmt.Errorf("already installed")
+ }
+ if s.sdk.Status == xsapiv1.SdkStatusInstalling {
+ return fmt.Errorf("installation in progress")
+ }
+
+ // Compute command args
+ cmdArgs := []string{}
+ if file != "" {
+ cmdArgs = append(cmdArgs, "--file", file)
+ } else {
+ cmdArgs = append(cmdArgs, "--url", s.sdk.URL)
+ }
+ if force {
+ cmdArgs = append(cmdArgs, "--force")
+ }
+
+ // Unique command id
+ sdkCmdID++
+ cmdID := "sdk-install-" + strconv.Itoa(sdkCmdID)
+
+ // Create new instance to execute command and sent output over WS
+ s.installCmd = eows.New(s.scripts[scriptAdd], cmdArgs, sess.IOSocket, sess.ID, cmdID)
+ s.installCmd.Log = s.Log
+ if timeout > 0 {
+ s.installCmd.CmdExecTimeout = timeout
+ } else {
+ s.installCmd.CmdExecTimeout = 30 * 60 // default 30min
+ }
+
+ // FIXME: temporary hack
+ s.bufStdout = ""
+ s.bufStderr = ""
+ SizeBufStdout := 10
+ SizeBufStderr := 2000
+ if valS, ok := os.LookupEnv("XDS_SDK_BUF_STDOUT"); ok {
+ if valI, err := strconv.Atoi(valS); err == nil {
+ SizeBufStdout = valI
+ }
+ }
+ if valS, ok := os.LookupEnv("XDS_SDK_BUF_STDERR"); ok {
+ if valI, err := strconv.Atoi(valS); err == nil {
+ SizeBufStderr = valI
+ }
+ }
+
+ // Define callback for output (stdout+stderr)
+ s.installCmd.OutputCB = func(e *eows.ExecOverWS, stdout, stderr string) {
+ // paranoia
+ data := e.UserData
+ sdkID := (*data)["SDKID"].(string)
+ if sdkID != s.sdk.ID {
+ s.Log.Errorln("BUG: sdk ID differs: %v != %v", sdkID, s.sdk.ID)
+ }
+
+ // IO socket can be nil when disconnected
+ so := s.sessions.IOSocketGet(e.Sid)
+ if so == nil {
+ s.Log.Infof("%s not emitted: WS closed (sid:%s, msgid:%s)", xsapiv1.EVTSDKInstall, e.Sid, e.CmdID)
+ return
+ }
+
+ if s.LogLevelSilly {
+ s.Log.Debugf("%s emitted - WS sid[4:] %s - id:%s - SDK ID:%s:", xsapiv1.EVTSDKInstall, e.Sid[4:], e.CmdID, sdkID[:16])
+ if stdout != "" {
+ s.Log.Debugf("STDOUT <<%v>>", strings.Replace(stdout, "\n", "\\n", -1))
+ }
+ if stderr != "" {
+ s.Log.Debugf("STDERR <<%v>>", strings.Replace(stderr, "\n", "\\n", -1))
+ }
+ }
+
+ // Temporary "Hack": Buffered sent data to avoid freeze in web Browser
+ // FIXME: remove bufStdout & bufStderr and implement better algorithm
+ s.bufStdout += stdout
+ s.bufStderr += stderr
+ if len(s.bufStdout) > SizeBufStdout || len(s.bufStderr) > SizeBufStderr {
+ // Emit event
+ err := (*so).Emit(xsapiv1.EVTSDKInstall, xsapiv1.SDKManagementMsg{
+ CmdID: e.CmdID,
+ Timestamp: time.Now().String(),
+ Sdk: s.sdk,
+ Progress: 0, // TODO add progress
+ Exited: false,
+ Stdout: s.bufStdout,
+ Stderr: s.bufStderr,
+ })
+ if err != nil {
+ s.Log.Errorf("WS Emit : %v", err)
+ }
+ s.bufStdout = ""
+ s.bufStderr = ""
+ }
+ }
+
+ // Define callback for output
+ s.installCmd.ExitCB = func(e *eows.ExecOverWS, code int, exitError error) {
+ // paranoia
+ data := e.UserData
+ sdkID := (*data)["SDKID"].(string)
+ if sdkID != s.sdk.ID {
+ s.Log.Errorln("BUG: sdk ID differs: %v != %v", sdkID, s.sdk.ID)
+ }
+
+ s.Log.Debugf("Command SDK ID %s [Cmd ID %s] exited: code %d, exitError: %v", sdkID[:16], e.CmdID, code, exitError)
+
+ // IO socket can be nil when disconnected
+ so := s.sessions.IOSocketGet(e.Sid)
+ if so == nil {
+ s.Log.Infof("%s (exit) not emitted - WS closed (id:%s)", xsapiv1.EVTSDKInstall, e.CmdID)
+ return
+ }
+
+ // Emit event remaining data in bufStdout/err
+ if len(s.bufStderr) > 0 || len(s.bufStdout) > 0 {
+ err := (*so).Emit(xsapiv1.EVTSDKInstall, xsapiv1.SDKManagementMsg{
+ CmdID: e.CmdID,
+ Timestamp: time.Now().String(),
+ Sdk: s.sdk,
+ Progress: 50, // TODO add progress
+ Exited: false,
+ Stdout: s.bufStdout,
+ Stderr: s.bufStderr,
+ })
+ if err != nil {
+ s.Log.Errorf("WS Emit : %v", err)
+ }
+ s.bufStdout = ""
+ s.bufStderr = ""
+ }
+
+ // Update SDK status
+ if code == 0 && exitError == nil {
+ s.sdk.LastError = ""
+ s.sdk.Status = xsapiv1.SdkStatusInstalled
+ } else {
+ s.sdk.LastError = "Installation failed (code " + strconv.Itoa(code) +
+ ")"
+ if exitError != nil {
+ s.sdk.LastError = ". Error: " + exitError.Error()
+ }
+ s.sdk.Status = xsapiv1.SdkStatusNotInstalled
+ }
+
+ emitErr := ""
+ if exitError != nil {
+ emitErr = exitError.Error()
+ }
+ if emitErr == "" && s.sdk.LastError != "" {
+ emitErr = s.sdk.LastError
+ }
+
+ // Emit event
+ errSoEmit := (*so).Emit(xsapiv1.EVTSDKInstall, xsapiv1.SDKManagementMsg{
+ CmdID: e.CmdID,
+ Timestamp: time.Now().String(),
+ Sdk: s.sdk,
+ Progress: 100,
+ Exited: true,
+ Code: code,
+ Error: emitErr,
+ })
+ if errSoEmit != nil {
+ s.Log.Errorf("WS Emit : %v", errSoEmit)
+ }
+
+ // Cleanup command for the next time
+ s.installCmd = nil
+ }
+
+ // User data (used within callbacks)
+ data := make(map[string]interface{})
+ data["SDKID"] = s.sdk.ID
+ s.installCmd.UserData = &data
+
+ // Start command execution
+ s.Log.Infof("Install SDK %s: cmdID=%v, cmd=%v, args=%v", s.sdk.Name, s.installCmd.CmdID, s.installCmd.Cmd, s.installCmd.Args)
+
+ s.sdk.Status = xsapiv1.SdkStatusInstalling
+ s.sdk.LastError = ""
+
+ err := s.installCmd.Start()
+
+ return err
+}
+
+// AbortInstallRemove abort an install or remove command
+func (s *CrossSDK) AbortInstallRemove(timeout int) error {
+
+ if s.installCmd == nil {
+ return fmt.Errorf("no installation in progress for this sdk")
+ }
+
+ s.sdk.Status = xsapiv1.SdkStatusNotInstalled
+ return s.installCmd.Signal("SIGKILL")
+}
+
+// Remove Used to remove/uninstall a SDK
+func (s *CrossSDK) Remove() error {
+
+ if s.sdk.Status != xsapiv1.SdkStatusInstalled {
+ return fmt.Errorf("this sdk is not installed")
+ }
+
+ s.sdk.Status = xsapiv1.SdkStatusUninstalling
+
+ cmdline := s.scripts[scriptRemove] + " " + s.sdk.Path
+ cmd := exec.Command(cmdline)
+ stdout, err := cmd.CombinedOutput()
+ if err != nil {
+ return fmt.Errorf("Error while uninstalling sdk: %v", err)
+ }
+ s.Log.Debugf("SDK uninstall output:\n %v", stdout)
+
+ return nil
+}
+
// Get Return SDK definition
func (s *CrossSDK) Get() *xsapiv1.SDK {
return &s.sdk
@@ -69,5 +397,5 @@ func (s *CrossSDK) Get() *xsapiv1.SDK {
// GetEnvCmd returns the command used to initialized the environment
func (s *CrossSDK) GetEnvCmd() []string {
- return []string{"source", s.sdk.EnvFile}
+ return []string{"source", s.sdk.SetupFile}
}
diff --git a/lib/xdsserver/sdks.go b/lib/xdsserver/sdks.go
index 35aa0ba..38e380d 100644
--- a/lib/xdsserver/sdks.go
+++ b/lib/xdsserver/sdks.go
@@ -26,6 +26,7 @@ import (
common "github.com/iotbzh/xds-common/golib"
"github.com/iotbzh/xds-server/lib/xsapiv1"
+ uuid "github.com/satori/go.uuid"
)
// SDKs List of installed SDK
@@ -34,6 +35,7 @@ type SDKs struct {
Sdks map[string]*CrossSDK
mutex sync.Mutex
+ stop chan struct{} // signals intentional stop
}
// NewSDKs creates a new instance of SDKs
@@ -41,40 +43,140 @@ func NewSDKs(ctx *Context) (*SDKs, error) {
s := SDKs{
Context: ctx,
Sdks: make(map[string]*CrossSDK),
+ stop: make(chan struct{}),
}
- // Retrieve installed sdks
- sdkRD := ctx.Config.FileConf.SdkRootDir
+ scriptsDir := ctx.Config.FileConf.SdkScriptsDir
+ if !common.Exists(scriptsDir) {
+ // allow to use scripts/sdk in debug mode
+ scriptsDir = filepath.Join(filepath.Dir(ctx.Config.FileConf.SdkScriptsDir), "scripts", "sdks")
+ if !common.Exists(scriptsDir) {
+ return &s, fmt.Errorf("scripts directory doesn't exist (%v)", scriptsDir)
+ }
+ }
+ s.Log.Infof("SDK scripts dir: %s", scriptsDir)
+
+ dirs, err := filepath.Glob(path.Join(scriptsDir, "*"))
+ if err != nil {
+ s.Log.Errorf("Error while retrieving SDK scripts: dir=%s, error=%s", scriptsDir, err.Error())
+ return &s, err
+ }
- if common.Exists(sdkRD) {
+ s.mutex.Lock()
+ defer s.mutex.Unlock()
- // Assume that SDK install tree is <rootdir>/<profile>/<version>/<arch>
- dirs, err := filepath.Glob(path.Join(sdkRD, "*", "*", "*"))
+ // Foreach directories in scripts/sdk
+ nbInstalled := 0
+ monSdksPath := make(map[string]*xsapiv1.SDKFamilyConfig)
+ for _, d := range dirs {
+ if !common.IsDir(d) {
+ continue
+ }
+
+ sdksList, err := ListCrossSDK(d, s.Log)
if err != nil {
- ctx.Log.Debugf("Error while retrieving SDKs: dir=%s, error=%s", sdkRD, err.Error())
return &s, err
}
- s.mutex.Lock()
- defer s.mutex.Unlock()
+ s.LogSillyf("'%s' SDKs list: %v", d, sdksList)
- for _, d := range dirs {
- if !common.IsDir(d) {
- continue
- }
- cSdk, err := NewCrossSDK(d)
+ for _, sdk := range sdksList {
+ cSdk, err := NewCrossSDK(ctx, sdk, d)
if err != nil {
- ctx.Log.Debugf("Error while processing SDK dir=%s, err=%s", d, err.Error())
+ s.Log.Debugf("Error while processing SDK sdk=%v\n err=%s", sdk, err.Error())
continue
}
+ if _, exist := s.Sdks[cSdk.sdk.ID]; exist {
+ s.Log.Warningf("Duplicate SDK ID : %v", cSdk.sdk.ID)
+ cSdk.sdk.ID += "_DUPLICATE_" + uuid.NewV1().String()
+ }
s.Sdks[cSdk.sdk.ID] = cSdk
+ if cSdk.sdk.Status == xsapiv1.SdkStatusInstalled {
+ nbInstalled++
+ }
+
+ monSdksPath[cSdk.sdk.FamilyConf.RootDir] = &cSdk.sdk.FamilyConf
}
}
- ctx.Log.Debugf("SDKs: %d cross sdks found", len(s.Sdks))
+ ctx.Log.Debugf("Cross SDKs: %d defined, %d installed", len(s.Sdks), nbInstalled)
+
+ // Start monitor thread to detect new SDKs
+ if len(monSdksPath) == 0 {
+ s.Log.Warningf("No cross SDKs definition found")
+ }
return &s, nil
}
+// Stop SDKs management
+func (s *SDKs) Stop() {
+ close(s.stop)
+}
+
+// monitorSDKInstallation
+/* TODO: cleanup
+func (s *SDKs) monitorSDKInstallation(monSDKs map[string]*xsapiv1.SDKFamilyConfig) {
+
+ // Set up a watchpoint listening for inotify-specific events
+ c := make(chan notify.EventInfo, 1)
+
+ addWatcher := func(rootDir string) error {
+ s.Log.Debugf("SDK Register watcher: rootDir=%s", rootDir)
+
+ if err := notify.Watch(rootDir+"/...", c, notify.Create, notify.Remove); err != nil {
+ return fmt.Errorf("SDK monitor: rootDir=%v err=%v", rootDir, err)
+ }
+ return nil
+ }
+
+ // Add directory watchers
+ for dir := range monSDKs {
+ if err := addWatcher(dir); err != nil {
+ s.Log.Errorln(err.Error())
+ }
+ }
+
+ // Wait inotify or stop events
+ for {
+ select {
+ case <-s.stop:
+ s.Log.Debugln("Stop monitorSDKInstallation")
+ notify.Stop(c)
+ return
+ case ei := <-c:
+ s.LogSillyf("monitorSDKInstallation SDKs event %v, path %v\n", ei.Event(), ei.Path())
+
+ // Filter out all event that doesn't match environment file
+ if !strings.Contains(ei.Path(), "environment-setup-") {
+ continue
+ }
+ dir := path.Dir(ei.Path())
+
+ sdk, err := s.GetByPath(dir)
+ if err != nil {
+ s.Log.Warningf("Cannot find SDK path to notify creation")
+ s.LogSillyf("event: %v", ei.Event())
+ continue
+ }
+
+ switch ei.Event() {
+ case notify.Create:
+ // Emit Folder state change event
+ if err := s.events.Emit(xsapiv1.EVTSDKInstall, sdk, ""); err != nil {
+ s.Log.Warningf("Cannot notify SDK install: %v", err)
+ }
+
+ case notify.Remove, notify.InMovedFrom:
+ // Emit Folder state change event
+ if err := s.events.Emit(xsapiv1.EVTSDKRemove, sdk, ""); err != nil {
+ s.Log.Warningf("Cannot notify SDK remove: %v", err)
+ }
+ }
+ }
+ }
+}
+*/
+
// ResolveID Complete an SDK ID (helper for user that can use partial ID value)
func (s *SDKs) ResolveID(id string) (string, error) {
if id == "" {
@@ -108,6 +210,19 @@ func (s *SDKs) Get(id string) *xsapiv1.SDK {
return (*sc).Get()
}
+// GetByPath Find a SDK from path
+func (s *SDKs) GetByPath(path string) (*xsapiv1.SDK, error) {
+ if path == "" {
+ return nil, fmt.Errorf("can't found sdk (empty path)")
+ }
+ for _, ss := range s.Sdks {
+ if ss.sdk.Path == path {
+ return ss.Get(), nil
+ }
+ }
+ return nil, fmt.Errorf("not found")
+}
+
// GetAll returns all existing SDKs
func (s *SDKs) GetAll() []xsapiv1.SDK {
s.mutex.Lock()
@@ -142,3 +257,80 @@ func (s *SDKs) GetEnvCmd(id string, defaultID string) []string {
// Return default env that may be empty
return []string{}
}
+
+// Install Used to install a new SDK
+func (s *SDKs) Install(id, filepath string, force bool, timeout int, sess *ClientSession) (*xsapiv1.SDK, error) {
+ var cSdk *CrossSDK
+ if id != "" && filepath != "" {
+ return nil, fmt.Errorf("invalid parameter, both id and filepath are set")
+ }
+ if id != "" {
+ var exist bool
+ cSdk, exist = s.Sdks[id]
+ if !exist {
+ return nil, fmt.Errorf("unknown id")
+ }
+ } else if filepath != "" {
+ // TODO check that file is accessible
+
+ } else {
+ return nil, fmt.Errorf("invalid parameter, id or filepath must be set")
+ }
+
+ s.mutex.Lock()
+ defer s.mutex.Unlock()
+
+ // Launch script to install
+ // (note that add event will be generated by monitoring thread)
+ if err := cSdk.Install(filepath, force, timeout, sess); err != nil {
+ return &cSdk.sdk, err
+ }
+
+ return &cSdk.sdk, nil
+}
+
+// AbortInstall Used to abort SDK installation
+func (s *SDKs) AbortInstall(id string, timeout int) (*xsapiv1.SDK, error) {
+
+ if id == "" {
+ return nil, fmt.Errorf("invalid parameter")
+ }
+ cSdk, exist := s.Sdks[id]
+ if !exist {
+ return nil, fmt.Errorf("unknown id")
+ }
+
+ s.mutex.Lock()
+ defer s.mutex.Unlock()
+
+ err := cSdk.AbortInstallRemove(timeout)
+
+ return &cSdk.sdk, err
+}
+
+// Remove Used to uninstall a SDK
+func (s *SDKs) Remove(id string) (*xsapiv1.SDK, error) {
+ s.mutex.Lock()
+ defer s.mutex.Unlock()
+
+ cSdk, exist := s.Sdks[id]
+ if !exist {
+ return nil, fmt.Errorf("unknown id")
+ }
+
+ s.mutex.Lock()
+ defer s.mutex.Unlock()
+
+ // Launch script to remove/uninstall
+ // (note that remove event will be generated by monitoring thread)
+ if err := cSdk.Remove(); err != nil {
+ return &cSdk.sdk, err
+ }
+
+ sdk := cSdk.sdk
+
+ // Don't delete it from s.Sdks
+ // (always keep sdk reference to allow for example re-install)
+
+ return &sdk, nil
+}
diff --git a/lib/xdsserver/webserver.go b/lib/xdsserver/webserver.go
index 3b5b239..27b212b 100644
--- a/lib/xdsserver/webserver.go
+++ b/lib/xdsserver/webserver.go
@@ -127,6 +127,7 @@ func (s *WebServer) Serve() error {
case <-s.stop:
// Shutting down permanently
s.sessions.Stop()
+ s.sdks.Stop()
s.Log.Infoln("shutting down (stop)")
case err = <-serveError:
// Error due to listen/serve failure
diff --git a/lib/xsapiv1/events.go b/lib/xsapiv1/events.go
index 1552579..84e62c1 100644
--- a/lib/xsapiv1/events.go
+++ b/lib/xsapiv1/events.go
@@ -24,8 +24,8 @@ import (
// EventRegisterArgs Parameters (json format) of /events/register command
type EventRegisterArgs struct {
- Name string `json:"name"`
- ProjectID string `json:"filterProjectID"`
+ Name string `json:"name"`
+ Filter string `json:"filter"`
}
// EventUnRegisterArgs Parameters of /events/unregister command
@@ -49,14 +49,18 @@ const (
// Supported Events type
EVTAll = EventTypePrefix + "all"
- EVTFolderChange = EventTypePrefix + "folder-change" // type EventMsg with Data type xsapiv1.???
- EVTFolderStateChange = EventTypePrefix + "folder-state-change" // type EventMsg with Data type xsapiv1.???
+ EVTFolderChange = EventTypePrefix + "folder-change" // type EventMsg with Data type xsapiv1.FolderConfig
+ EVTFolderStateChange = EventTypePrefix + "folder-state-change" // type EventMsg with Data type xsapiv1.FolderConfig
+ EVTSDKInstall = EventTypePrefix + "sdk-install" // type EventMsg with Data type xsapiv1.SDKManagementMsg
+ EVTSDKRemove = EventTypePrefix + "sdk-remove" // type EventMsg with Data type xsapiv1.SDKManagementMsg
)
// EVTAllList List of all supported events
var EVTAllList = []string{
EVTFolderChange,
EVTFolderStateChange,
+ EVTSDKInstall,
+ EVTSDKRemove,
}
// DecodeFolderConfig Helper to decode Data field type FolderConfig
diff --git a/lib/xsapiv1/sdks.go b/lib/xsapiv1/sdks.go
index 4bc390a..3a79c99 100644
--- a/lib/xsapiv1/sdks.go
+++ b/lib/xsapiv1/sdks.go
@@ -17,15 +17,62 @@
package xsapiv1
+// SDK status definition
+const (
+ SdkStatusDisable = "Disable"
+ SdkStatusNotInstalled = "Not Installed"
+ SdkStatusInstalling = "Installing"
+ SdkStatusUninstalling = "Un-installing"
+ SdkStatusInstalled = "Installed"
+)
+
// SDK Define a cross tool chain used to build application
type SDK struct {
- ID string `json:"id" binding:"required"`
- Name string `json:"name"`
- Profile string `json:"profile"`
- Version string `json:"version"`
- Arch string `json:"arch"`
- Path string `json:"path"`
+ ID string `json:"id" binding:"required"`
+ Name string `json:"name"`
+ Description string `json:"description"`
+ Profile string `json:"profile"`
+ Version string `json:"version"`
+ Arch string `json:"arch"`
+ Path string `json:"path"`
+ URL string `json:"url"`
+ Status string `json:"status"`
+ Date string `json:"date"`
+ Size string `json:"size"`
+ Md5sum string `json:"md5sum"`
+ SetupFile string `json:"setupFile"`
+ LastError string `json:"lastError"`
// Not exported fields
- EnvFile string `json:"-"`
+ FamilyConf SDKFamilyConfig `json:"-"`
+}
+
+// SDKFamilyConfig Configuration structure to define a SDKs family
+type SDKFamilyConfig struct {
+ FamilyName string `json:"familyName"`
+ Description string `json:"description"`
+ RootDir string `json:"rootDir"`
+ EnvSetupFile string `json:"envSetupFilename"`
+ ScriptsDir string `json:"scriptsDir"`
+}
+
+// SDKInstallArgs JSON parameters of POST /sdks or /sdks/abortinstall commands
+type SDKInstallArgs struct {
+ ID string `json:"id" binding:"required"` // install by ID (must be part of GET /sdks result)
+ Filename string `json:"filename"` // install by using a file
+ Force bool `json:"force"` // force SDK install when already existing
+ Timeout int `json:"timeout"` // 1800 == default 30 minutes
+}
+
+// SDKManagementMsg Message send during SDK installation or when installation is complete
+type SDKManagementMsg struct {
+ CmdID string `json:"cmdID"`
+ Timestamp string `json:"timestamp"`
+ Sdk SDK `json:"sdk"`
+ Stdout string `json:"stdout"`
+ Stderr string `json:"stderr"`
+ Progress int `json:"progress"` // 0 = not started to 100% = complete
+ Exited bool `json:"exited"`
+ Code int `json:"code"`
+ Error string `json:"error"`
}
diff --git a/scripts/sdks/README.md b/scripts/sdks/README.md
new file mode 100644
index 0000000..8b741ec
--- /dev/null
+++ b/scripts/sdks/README.md
@@ -0,0 +1,78 @@
+# SDKs management scripts
+
+To support a new SDK family, you must create a new directory under
+`scripts/sdk/xxx` where xxx is the new SDK family.
+
+Then you must create the following scripts (or executable) :
+
+- `get-config`: returned SDK configuration structure
+- `list`: returned the list of installed SDKs
+- `add`: add a new SDK
+- `remove`: remove an existing SDK
+
+## `get-config`
+
+Returned SDK configuration as json format:
+
+```json
+{
+ "familyName": "xxx",
+ "description": "bla bla",
+ "rootDir": "/yyy/zzz",
+ "envSetupFilename": "my-envfilename*",
+ "scriptsDir": "scripts_path"
+}
+```
+
+where:
+
+- `familyName` : sdk familyName (usually same name used as xxx directory)
+- `rootDir` : root directory where SDK are/will be installed
+- `envSetupFilename` : sdk files (present in each sdk) that will be sourced to
+ setup sdk environment
+
+## `list`
+
+Returned the list all SDKs (available and installed)
+
+```json
+[
+ {
+ "name": "My SDK name",
+ "description": "A description",
+ "profile": "profile",
+ "version": "version",
+ "arch": "architecture",
+ "path": "path where sdk installed locally",
+ "url": "https://website.url.to.download.sdk",
+ "status": "Not Installed | Installed",
+ "date": "2017-12-25 00:00",
+ "size": "123 MB",
+ "md5sum": "123456789",
+ "setupFile": "path to file to setup SDK environment"
+ }, {
+ "name": "My SDK name 2",
+ "description": "A description 2",
+ ...
+ }
+ ...
+]
+```
+
+## `add`
+
+add a new SDK
+
+List of parameters to implement:
+
+- `-f|--file <filepath>` : install a SDK using a file
+- `--force`: force SDK install when a SDK already in the same destination directory
+- `-u|--url <url>` : download SDK using this URL and then install it
+- `-no-clean` : don't cleanup temporary files
+- `-h|--help` : display help
+
+## `remove`
+
+Remove an existing SDK
+
+The first argument is the full path of the directory of the SDK to removed.
diff --git a/scripts/sdks/agl/_build-sdks-json.sh b/scripts/sdks/agl/_build-sdks-json.sh
new file mode 100755
index 0000000..82cb2f3
--- /dev/null
+++ b/scripts/sdks/agl/_build-sdks-json.sh
@@ -0,0 +1,121 @@
+#!/bin/bash
+###########################################################################
+# Copyright 2017 IoT.bzh
+#
+# author: Sebastien Douheret <sebastien@iot.bzh>
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+###########################################################################
+
+SDK_AGL_BASEURL="https://download.automotivelinux.org/AGL"
+SDK_AGL_IOTBZH_BASEURL="http://iot.bzh/download/public/XDS/sdk"
+
+# Define urls where SDKs can be downloaded
+DOWNLOADABLE_URLS="
+ ${SDK_AGL_BASEURL}/snapshots/master/latest/*/deploy/sdk
+
+ ${SDK_AGL_BASEURL}/release/dab/3.99.3/m3ulcb-nogfx/deploy/sdk
+ ${SDK_AGL_BASEURL}/release/dab/4.0.2/*/deploy/sdk
+
+ ${SDK_AGL_BASEURL}/release/eel/4.99.4/*/deploy/sdk
+ ${SDK_AGL_BASEURL}/release/eel/latest/*/deploy/sdk
+
+ ${SDK_AGL_IOTBZH_BASEURL}
+"
+
+###
+
+
+# Compute full urls list (parse '*' characters)
+urls=""
+for url in $(echo $DOWNLOADABLE_URLS); do
+ if [[ "$url" = *"*"* ]]; then
+ bUrl=$(echo $url | cut -d'*' -f 1)
+ eUrl=$(echo $url | cut -d'*' -f 2)
+ dirs=$(curl -s ${bUrl} | grep '\[DIR\]' | grep -oP 'href="[^"]*"' | cut -d'"' -f 2)
+ for dir in $(echo $dirs); do
+ urls="$urls ${bUrl::-1}/${dir::-1}/${eUrl:1}"
+ done
+ else
+ urls="$urls $url"
+ fi
+done
+
+# Compute list of available/installable SDKs
+sdksList=" "
+for url in $(echo $urls); do
+ htmlPage=$(curl -s --connect-timeout 10 "${url}/")
+ files=$(echo ${htmlPage} | egrep -o 'href="[^"]*.sh"' | cut -d '"' -f 2)
+ if [ "$?" != "0" ] || [ "${files}" = "" ]; then
+ echo " IGNORED ${url}: no valid files found"
+ continue
+ fi
+
+ for sdkFile in $(echo ${files}); do
+
+ # assume that sdk name follow this format :
+ # _PROFILE_-_COMPILER_ARCH_-_TARGET_-crosssdk-_ARCH_-toolchain-_VERSION_.sh
+ # for example:
+ # poky-agl-glibc-x86_64-agl-demo-platform-crosssdk-corei7-64-toolchain-4.0.1.sh
+
+ [[ "${sdkFile}" != *"crosssdk"* ]] && { echo " IGNORED ${sdkFile}, not a valid sdk file"; continue; }
+
+ echo "Processing ${sdkFile}"
+ profile=$(echo "${sdkFile}" | sed -r 's/(.*)-glibc.*/\1/')
+ version=$(echo "${sdkFile}" | sed -r 's/.*toolchain-(.*).sh/\1/')
+ arch=$(echo "${sdkFile}" | sed -r 's/.*crosssdk-(.*)-toolchain.*/\1/')
+
+ endUrl=${url#$SDK_AGL_BASEURL}
+ if [ "${endUrl::4}" = "http" ]; then
+ name=${profile}_${arch}_${version}
+ else
+ name=$(echo "AGL-$(echo ${endUrl} | cut -d'/' -f2,3,4,5)" | sed s:/:-:g)
+ fi
+
+ [ "${profile}" = "" ] && { echo " ERROR: profile not set" continue; }
+ [ "${version}" = "" ] && { echo " ERROR: version not set" continue; }
+ [ "${arch}" = "" ] && { echo " ERROR: arch not set" continue; }
+ [ "${name}" = "" ] && { name=${profile}_${arch}_${version}; }
+
+ sdkDate="$(echo "${htmlPage}" |egrep -o ${sdkFile/+/\\+}'</a>.*[0-9\-]+ [0-9]+:[0-9]+' |cut -d'>' -f 4|cut -d' ' -f1,2)"
+ sdkSize="$(echo "${htmlPage}" |egrep -o "${sdkFile/+/\\+}.*${sdkDate}.*[0-9\.MG]+</td>" |cut -d'>' -f7 |cut -d'<' -f1)"
+ md5sum="$(wget -q -O - ${url}/${sdkFile/.sh/.md5} |cut -d' ' -f1)"
+
+ read -r -d '' res <<- EndOfMessage
+{
+ "name": "${name}",
+ "description": "AGL SDK ${arch} (version ${version})",
+ "profile": "${profile}",
+ "version": "${version}",
+ "arch": "${arch}",
+ "path": "",
+ "url": "${url}/${sdkFile}",
+ "status": "Not Installed",
+ "date": "${sdkDate}",
+ "size": "${sdkSize}",
+ "md5sum": "${md5sum}",
+ "setupFile": ""
+},
+EndOfMessage
+
+ sdksList="${sdksList}${res}"
+ done
+done
+
+OUT_FILE=$(dirname "$0")/sdks_$(date +"%F_%H%m").json
+
+echo "[" > ${OUT_FILE}
+echo "${sdksList::-1}" >> ${OUT_FILE}
+echo "]" >> ${OUT_FILE}
+
+echo "SDKs list successfully saved in ${OUT_FILE}"
diff --git a/scripts/sdks/agl/_env-init.sh b/scripts/sdks/agl/_env-init.sh
new file mode 100755
index 0000000..0f423c4
--- /dev/null
+++ b/scripts/sdks/agl/_env-init.sh
@@ -0,0 +1,29 @@
+#!/bin/bash
+ ###########################################################################
+# Copyright 2017 IoT.bzh
+#
+# author: Sebastien Douheret <sebastien@iot.bzh>
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+###########################################################################
+
+. /etc/xdtrc
+
+[ -z "$XDT_SDK" ] && XDT_SDK=/xdt/sdk
+
+export SDK_FAMILY_NAME="agl"
+export SDK_ROOT_DIR="$XDT_SDK"
+export SDK_ENV_SETUP_FILENAME="environment-setup-*"
+export SDK_DATABASE="http://iot.bzh/download/public/XDS/sdk/sdks_latest.json"
+
+[ "$1" = "-print" ] && { env; }
diff --git a/scripts/sdks/agl/add b/scripts/sdks/agl/add
new file mode 100755
index 0000000..dcb3833
--- /dev/null
+++ b/scripts/sdks/agl/add
@@ -0,0 +1,103 @@
+#!/bin/bash
+ ###########################################################################
+# Copyright 2017 IoT.bzh
+#
+# author: Sebastien Douheret <sebastien@iot.bzh>
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+###########################################################################
+
+. $(dirname "$0")/_env-init.sh
+
+usage() {
+ echo "Usage: $(basename $0) [-h|--help] [-f|--file <sdk-filename>] [-u|--url <https_url>] [--force] [--no-clean]"
+ exit 1
+}
+
+TMPDIR=""
+SDK_FILE=""
+URL=""
+do_cleanup=true
+do_force=false
+while [ $# -ne 0 ]; do
+ case $1 in
+ -f|--file)
+ shift
+ SDK_FILE=$1
+ ;;
+ --force)
+ do_force=true
+ ;;
+ -u|--url)
+ shift
+ URL=$1
+ ;;
+ -no-clean)
+ do_cleanup=false
+ ;;
+ -h|--help)
+ usage
+ ;;
+ *)
+ echo "Invalid argument: $1"
+ usage
+ ;;
+ esac
+ shift
+done
+
+[ "$SDK_FILE" = "" ] && [ "$URL" = "" ] && { echo "--file or --url option must be set"; exit 1; }
+
+# Create SDK root dir if needed
+[ ! -d ${SDK_ROOT_DIR} ] && mkdir -p ${SDK_ROOT_DIR}
+cd ${SDK_ROOT_DIR} || exit 1
+
+# Cleanup
+trap "cleanExit" 0 1 2 15
+cleanExit ()
+{
+ if ($do_cleanup); then
+ [[ -d ${TMPDIR} ]] && rm -rf ${TMPDIR}
+ fi
+}
+
+# Download sdk
+if [ "$URL" != "" ]; then
+ TMPDIR=$(mktemp -d)
+ SDK_FILE=${TMPDIR}/$(basename ${URL})
+ echo "Downloading $(basename ${SDK_FILE}) ..."
+ wget "$URL" -O "${SDK_FILE}" || exit 1
+fi
+
+# Retreive default install dir to extract version
+offset=$(grep -na -m1 "^MARKER:$" "${SDK_FILE}" | cut -d':' -f1)
+eval $(head -n $offset "${SDK_FILE}" | grep ^DEFAULT_INSTALL_DIR= )
+
+PROFILE=$(basename $(dirname $DEFAULT_INSTALL_DIR))
+VERSION=$(basename $DEFAULT_INSTALL_DIR)
+ARCH=$(echo "$SDK_FILE" | sed -r 's/.*crosssdk-(.*)-toolchain.*/\1/')
+
+[ "$PROFILE" = "" ] && { echo "PROFILE is not set"; exit 1; }
+[ "$VERSION" = "" ] && { echo "VERSION is not set"; exit 1; }
+[ "$ARCH" = "" ] && { echo "ARCH is not set"; exit 1; }
+
+DESTDIR=${SDK_ROOT_DIR}/${PROFILE}/${VERSION}/${ARCH}
+
+[ -d ${DESTDIR} ] && [ "$do_force" != "true" ] && { echo "SDK already installed in $DESTDIR"; exit 1; }
+
+# Cleanup previous install
+rm -rf ${DESTDIR} && mkdir -p ${DESTDIR} || exit 1
+
+# Install sdk
+chmod +x ${SDK_FILE}
+${SDK_FILE} -y -d ${DESTDIR}
diff --git a/scripts/sdks/agl/get-config b/scripts/sdks/agl/get-config
new file mode 100755
index 0000000..8d370b8
--- /dev/null
+++ b/scripts/sdks/agl/get-config
@@ -0,0 +1,33 @@
+#!/bin/bash
+ ###########################################################################
+# Copyright 2017 IoT.bzh
+#
+# author: Sebastien Douheret <sebastien@iot.bzh>
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+###########################################################################
+
+SCRIPTS_DIR=$(dirname "$0")
+. ${SCRIPTS_DIR}/_env-init.sh
+
+read -r -d '' res <<- EndOfMessage
+{
+ "familyName": "${SDK_FAMILY_NAME}",
+ "description": "Automotive Grade Linux SDK",
+ "rootDir": "${SDK_ROOT_DIR}",
+ "envSetupFilename": "${SDK_ENV_SETUP_FILENAME}",
+ "scriptsDir": "${SCRIPTS_DIR}"
+}
+EndOfMessage
+
+echo "$res"
diff --git a/scripts/sdks/agl/list b/scripts/sdks/agl/list
new file mode 100755
index 0000000..dc748a4
--- /dev/null
+++ b/scripts/sdks/agl/list
@@ -0,0 +1,137 @@
+#! /usr/bin/env node
+
+/**************************************************************************
+ * Copyright 2017 IoT.bzh
+ *
+ * author: Sebastien Douheret <sebastien@iot.bzh>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ **************************************************************************/
+
+const fs = require('fs');
+const process = require('process');
+const execSync = require('child_process').execSync;
+const path = require('path');
+
+
+// Only used for debug purpose
+const DEBUG = false || (process.argv.length > 2 && process.argv[2] == '-debug');
+dbgPrint = function () {
+ if (DEBUG) console.log.apply(console, arguments);
+}
+// Get env vars
+var envMap = {};
+envData = execSync(path.join(__dirname, '_env-init.sh -print'));
+envData.toString().split('\n').forEach(e => envMap[e.split('=')[0]] = e.split('=')[1]);
+const opts = {
+ cwd: __dirname,
+ env: envMap
+};
+
+// Get list of available SDKs
+sdksDBFile = path.join(__dirname, "sdks_latest.json")
+try {
+ // Fetch SDK Json database file when not existing
+ if (!fs.existsSync(sdksDBFile)) {
+ var data = execSync(path.join(__dirname, 'update'), opts);
+ }
+ // Read SDK Json database file
+ var data = fs.readFileSync(sdksDBFile);
+ var sdks = JSON.parse(data);
+
+ // Force some default fields value
+ sdks.forEach(sdk => {
+ sdk.status = 'Not Installed';
+ });
+} catch (err) {
+ dbgPrint('ERROR: ', err);
+ process.exit(-1)
+}
+
+// Get list of installed SDKs
+try {
+ const cmd = 'find "${SDK_ROOT_DIR}" -maxdepth 4 -name "${SDK_ENV_SETUP_FILENAME}"';
+ var data = execSync(cmd, opts);
+ data.toString().split('\n').forEach(envFile => {
+ if (envFile == '') return;
+
+ dbgPrint('Processing ', envFile);
+ const profile = envFile.split('/')[3];
+ const version = envFile.split('/')[4];
+ const arch = envFile.split('/')[5];
+ const dir = path.dirname(envFile);
+ if (profile == '' || version == '' || arch == '' || dir == '') {
+ return;
+ }
+
+ sdkDate = ''
+ versionFile = path.join(path.dirname(envFile), 'version-*')
+ try {
+ cmdVer = "[ -f " + versionFile + " ] && grep Timestamp " + versionFile + " |cut -d' ' -f2"
+ var data = execSync(cmdVer);
+ } catch (err) {
+ dbgPrint('IGNORING SDK ', dir);
+ dbgPrint(err.toString());
+ if (DEBUG) {
+ process.exit(-1);
+ } else {
+ return;
+ }
+ }
+ d = data.toString()
+ if (d != "") {
+ sdkDate = d.substring(0, 4) + "-" + d.substring(4, 6) + "-" + d.substring(6, 8)
+ sdkDate += " " + d.substring(8, 10) + ":" + d.substring(10, 12)
+ }
+
+ var found = false;
+ sdks.forEach(sdk => {
+ // Update sdk with local info when found
+ if (profile == sdk.profile && version == sdk.version && arch == sdk.arch) {
+ found = true;
+ dbgPrint(" OK found, updating...");
+ sdk.path = dir;
+ sdk.status = 'Installed';
+ sdk.data = sdkDate;
+ sdk.setupFile = envFile;
+ return
+ }
+ });
+ if (found == false) {
+ dbgPrint(" NOT found in database, adding it...");
+ sdks.push({
+ name: profile + '-' + arch + '-' + version,
+ description: 'AGL SDK ' + arch + ' (version ' + version + ')',
+ profile: profile,
+ version: version,
+ arch: arch,
+ path: dir,
+ url: "",
+ status: "Installed",
+ date: sdkDate,
+ size: "",
+ md5sum: "",
+ setupFile: envFile
+ });
+ }
+ });
+
+} catch (err) {
+ dbgPrint('ERROR: ', err);
+ process.exit(-1)
+}
+
+// Print result
+console.log(JSON.stringify(sdks));
+
+process.exit(0)
diff --git a/scripts/sdks/agl/remove b/scripts/sdks/agl/remove
new file mode 100755
index 0000000..99a4022
--- /dev/null
+++ b/scripts/sdks/agl/remove
@@ -0,0 +1,32 @@
+#!/bin/bash
+ ###########################################################################
+# Copyright 2017 IoT.bzh
+#
+# author: Sebastien Douheret <sebastien@iot.bzh>
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+###########################################################################
+
+. $(dirname "$0")/_env-init.sh
+
+if [[ "${1}" == "" || "${1}" != "${SDK_ROOT_DIR}"* ]]; then
+ echo "Invalid sdk root directory"
+ exit 1
+fi
+
+if [ ! -d "${1}" ]; then
+ echo "sdk directory doesn't exist"
+ exit 1
+fi
+
+rm -rf "${1}"
diff --git a/scripts/sdks/agl/update b/scripts/sdks/agl/update
new file mode 100755
index 0000000..e59c8fa
--- /dev/null
+++ b/scripts/sdks/agl/update
@@ -0,0 +1,22 @@
+#!/bin/bash
+ ###########################################################################
+# Copyright 2017 IoT.bzh
+#
+# author: Sebastien Douheret <sebastien@iot.bzh>
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+###########################################################################
+
+. $(dirname "$0")/_env-init.sh
+
+wget -q --connect-timeout=30 ${SDK_DATABASE}
diff --git a/scripts/xds-utils/install-agl-sdks.sh b/scripts/xds-utils/install-agl-sdks.sh
index 4d9eadf..0491b24 100755
--- a/scripts/xds-utils/install-agl-sdks.sh
+++ b/scripts/xds-utils/install-agl-sdks.sh
@@ -19,7 +19,7 @@
. /etc/xdtrc
-[ -z "$SDK_BASEURL" ] && SDK_BASEURL="http://iot.bzh/download/public/2017/XDS/sdk/"
+[ -z "$SDK_BASEURL" ] && SDK_BASEURL="http://iot.bzh/download/public/XDS/sdk/"
[ -z "$XDT_SDK" ] && XDT_SDK=/xdt/sdk
# Support only poky_agl profile for now
diff --git a/webapp/src/index.html b/webapp/src/index.html
index 3109d2e..68b622f 100644
--- a/webapp/src/index.html
+++ b/webapp/src/index.html
@@ -92,7 +92,7 @@
</a>
</li>
<li>
- <a href="http://iot.bzh/download/public/2017/XDS/docs/XDS_UsersGuide.pdf">
+ <a href="http://iot.bzh/download/public/XDS/docs/XDS_UsersGuide.pdf">
User's guide (PDF file)
<i class="fa fa-external-link" aria-hidden="true"></i>
</a>