diff --git a/Dockerfile b/Dockerfile index fbfd507..dc8f45c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM golang:latest +FROM golang:cross MAINTAINER Liang Ding ADD . /wide/gogogo/src/github.com/b3log/wide diff --git a/file/exporter.go b/file/exporter.go index 64e972e..5699a3c 100644 --- a/file/exporter.go +++ b/file/exporter.go @@ -63,8 +63,18 @@ func CreateZipHandler(w http.ResponseWriter, r *http.Request) { } path := args["path"].(string) + var name string + base := filepath.Base(path) + if nil != args["name"] { + name = args["name"].(string) + } else { + name = base + } + + dir := filepath.Dir(path) + if !util.File.IsExist(path) { data["succ"] = false data["msg"] = "Can't find file [" + path + "]" @@ -72,7 +82,8 @@ func CreateZipHandler(w http.ResponseWriter, r *http.Request) { return } - zipFile, err := util.Zip.Create(path + ".zip") + zipPath := filepath.Join(dir, name) + zipFile, err := util.Zip.Create(zipPath + ".zip") if nil != err { logger.Error(err) data["succ"] = false @@ -86,4 +97,6 @@ func CreateZipHandler(w http.ResponseWriter, r *http.Request) { } else { zipFile.AddEntry(base, path) } + + data["path"] = zipPath } diff --git a/main.go b/main.go index 95d456d..b8ca490 100644 --- a/main.go +++ b/main.go @@ -87,6 +87,8 @@ func init() { if *confStat { session.FixedTimeReport() } + + logger.Debug("cross-compilation ", util.Go.GetCrossPlatforms()) } // Main. @@ -125,6 +127,9 @@ func main() { http.HandleFunc(conf.Wide.Context+"/go/install", handlerWrapper(output.GoInstallHandler)) http.HandleFunc(conf.Wide.Context+"/output/ws", handlerWrapper(output.WSHandler)) + // cross compilation + http.HandleFunc(conf.Wide.Context+"/cross", handlerWrapper(output.CrossCompilationHandler)) + // file tree http.HandleFunc(conf.Wide.Context+"/files", handlerWrapper(file.GetFilesHandler)) http.HandleFunc(conf.Wide.Context+"/file/refresh", handlerWrapper(file.RefreshDirectoryHandler)) @@ -237,7 +242,7 @@ func indexHandler(w http.ResponseWriter, r *http.Request) { model := map[string]interface{}{"conf": conf.Wide, "i18n": i18n.GetAll(locale), "locale": locale, "session": wideSession, "latestSessionContent": user.LatestSessionContent, "pathSeparator": conf.PathSeparator, "codeMirrorVer": conf.CodeMirrorVer, - "user": user, "editorThemes": conf.GetEditorThemes()} + "user": user, "editorThemes": conf.GetEditorThemes(), "crossPlatforms": util.Go.GetCrossPlatforms()} logger.Debugf("User [%s] has [%d] sessions", username, len(wideSessions)) diff --git a/output/cross.go b/output/cross.go new file mode 100644 index 0000000..365ef15 --- /dev/null +++ b/output/cross.go @@ -0,0 +1,245 @@ +// Copyright (c) 2014-2015, b3log.org +// +// 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. + +package output + +import ( + "bufio" + "encoding/json" + "io" + "io/ioutil" + "math/rand" + "net/http" + "os/exec" + "path/filepath" + "strconv" + "strings" + + "github.com/b3log/wide/conf" + "github.com/b3log/wide/i18n" + "github.com/b3log/wide/session" + "github.com/b3log/wide/util" +) + +// CrossCompilationHandler handles request of cross compilation. +func CrossCompilationHandler(w http.ResponseWriter, r *http.Request) { + data := map[string]interface{}{"succ": true} + defer util.RetJSON(w, r, data) + + httpSession, _ := session.HTTPSession.Get(r, "wide-session") + if httpSession.IsNew { + http.Error(w, "Forbidden", http.StatusForbidden) + + return + } + username := httpSession.Values["username"].(string) + locale := conf.GetUser(username).Locale + + var args map[string]interface{} + + if err := json.NewDecoder(r.Body).Decode(&args); err != nil { + logger.Error(err) + data["succ"] = false + + return + } + + sid := args["sid"].(string) + filePath := args["path"].(string) + + if util.Go.IsAPI(filePath) || !session.CanAccess(username, filePath) { + http.Error(w, "Forbidden", http.StatusForbidden) + + return + } + + platform := args["platform"].(string) + goos := strings.Split(platform, "_")[0] + goarch := strings.Split(platform, "_")[1] + + curDir := filepath.Dir(filePath) + + suffix := "" + if "windows" == goos { + suffix = ".exe" + } + + cmd := exec.Command("go", "build") + cmd.Dir = curDir + + setCmdEnv(cmd, username) + + for i, env := range cmd.Env { + if strings.HasPrefix(env, "GOOS=") { + cmd.Env[i] = "GOOS=" + goos + + continue + } + + if strings.HasPrefix(env, "GOARCH=") { + cmd.Env[i] = "GOARCH=" + goarch + + continue + } + } + + executable := filepath.Base(curDir) + suffix + executable = filepath.Join(curDir, executable) + name := filepath.Base(curDir) + "-" + goos + "-" + goarch + + stdout, err := cmd.StdoutPipe() + if nil != err { + logger.Error(err) + data["succ"] = false + + return + } + + stderr, err := cmd.StderrPipe() + if nil != err { + logger.Error(err) + data["succ"] = false + + return + } + + if !data["succ"].(bool) { + return + } + + channelRet := map[string]interface{}{} + + if nil != session.OutputWS[sid] { + // display "START [go build]" in front-end browser + + channelRet["output"] = "" + i18n.Get(locale, "start-build").(string) + "\n" + channelRet["cmd"] = "start-build" + + wsChannel := session.OutputWS[sid] + + err := wsChannel.WriteJSON(&channelRet) + if nil != err { + logger.Error(err) + return + } + + wsChannel.Refresh() + } + + reader := bufio.NewReader(io.MultiReader(stdout, stderr)) + + if err := cmd.Start(); nil != err { + logger.Error(err) + data["succ"] = false + + return + } + + go func(runningId int) { + defer util.Recover() + defer cmd.Wait() + + // logger.Debugf("User [%s, %s] is building [id=%d, dir=%s]", username, sid, runningId, curDir) + + // read all + buf, _ := ioutil.ReadAll(reader) + + channelRet := map[string]interface{}{} + channelRet["cmd"] = "cross-build" + channelRet["executable"] = executable + channelRet["name"] = name + + if 0 == len(buf) { // build success + channelRet["output"] = "" + i18n.Get(locale, "build-succ").(string) + "\n" + } else { // build error + // build gutter lint + + errOut := string(buf) + lines := strings.Split(errOut, "\n") + + // path process + var errOutWithPath string + for _, line := range lines { + errOutWithPath += parsePath(curDir, line) + "\n" + } + + channelRet["output"] = "" + i18n.Get(locale, "build-error").(string) + "\n" + + "" + errOutWithPath + "" + + // lint process + + if lines[0][0] == '#' { + lines = lines[1:] // skip the first line + } + + lints := []*Lint{} + + for _, line := range lines { + if len(line) < 1 { + continue + } + + if line[0] == '\t' { + // append to the last lint + last := len(lints) + msg := lints[last-1].Msg + msg += line + + lints[last-1].Msg = msg + + continue + } + + file := line[:strings.Index(line, ":")] + left := line[strings.Index(line, ":")+1:] + index := strings.Index(left, ":") + lineNo := 0 + msg := left + if index >= 0 { + lineNo, err = strconv.Atoi(left[:index]) + + if nil != err { + continue + } + + msg = left[index+2:] + } + + lint := &Lint{ + File: filepath.Join(curDir, file), + LineNo: lineNo - 1, + Severity: lintSeverityError, + Msg: msg, + } + + lints = append(lints, lint) + } + + channelRet["lints"] = lints + } + + if nil != session.OutputWS[sid] { + // logger.Debugf("User [%s, %s] 's build [id=%d, dir=%s] has done", username, sid, runningId, curDir) + + wsChannel := session.OutputWS[sid] + err := wsChannel.WriteJSON(&channelRet) + if nil != err { + logger.Warn(err) + } + + wsChannel.Refresh() + } + + }(rand.Int()) +} diff --git a/static/js/tree.js b/static/js/tree.js index ba749ca..813d6f2 100644 --- a/static/js/tree.js +++ b/static/js/tree.js @@ -171,6 +171,26 @@ var tree = { window.open(config.context + '/file/zip?path=' + wide.curNode.path + ".zip"); } }, + crossCompile: function (platform) { + var request = newWideRequest(); + request.path = wide.curNode.path; + request.platform = platform; + + $.ajax({ + async: false, + type: 'POST', + url: config.context + '/cross', + data: JSON.stringify(request), + dataType: "json", + success: function (data) { + if (!data.succ) { + $("#dialogAlert").dialog("open", data.msg); + + return false; + } + } + }); + }, decompress: function () { var request = newWideRequest(); request.path = wide.curNode.path; @@ -278,12 +298,18 @@ var tree = { $fileRMenu.find(".remove").addClass("disabled"); } - if (wide.curNode.path.indexOf("zip", wide.curNode.path.length - "zip".length) === -1) { // !path.endsWith("zip") + if (-1 === wide.curNode.path.indexOf("zip", wide.curNode.path.length - "zip".length)) { // !path.endsWith("zip") $fileRMenu.find(".decompress").hide(); } else { $fileRMenu.find(".decompress").show(); } + if (-1 === wide.curNode.path.indexOf("go", wide.curNode.path.length - "go".length)) { // !path.endsWith("go") + $fileRMenu.find(".linux64").hide(); + } else { + $fileRMenu.find(".linux64").show(); + } + var top = event.clientY - 10; if ($fileRMenu.height() + top > $('.content').height()) { top = top - $fileRMenu.height() - 25; diff --git a/static/js/wide.js b/static/js/wide.js index 1ed6e13..ed1bbd4 100644 --- a/static/js/wide.js +++ b/static/js/wide.js @@ -86,8 +86,8 @@ var wide = { $("#dialogAlert").dialog({ "modal": true, - "height": 36, - "width": 260, + "height": 40, + "width": 350, "title": config.label.tip, "hiddenOk": true, "cancelText": config.label.confirm, @@ -483,6 +483,7 @@ var wide = { break; case 'build': + case 'cross-build': bottomGroup.fillOutput($('.bottom-window-group .output > div').html() + data.output); if (data.lints) { // has build error @@ -506,6 +507,30 @@ var wide = { var editor = editors.getEditorByPath(path); CodeMirror.signal(editor, "change", editor); } + } else { + if ('cross-build' === data.cmd) { + var request = newWideRequest(), + isSucc = false; + request.path = data.executable; + request.name = data.name; + + $.ajax({ + async: false, + type: 'POST', + url: config.context + '/file/zip/new', + data: JSON.stringify(request), + dataType: "json", + success: function (data) { + if (!data.succ) { + $("#dialogAlert").dialog("open", data.msg); + + return false; + } + + window.open(config.context + '/file/zip?path=' + data.path + ".zip"); + } + }); + } } break; diff --git a/util/go.go b/util/go.go index 6225abe..d2845ad 100644 --- a/util/go.go +++ b/util/go.go @@ -33,6 +33,27 @@ type mygo struct{} // Go utilities. var Go = mygo{} +func (*mygo) GetCrossPlatforms() []string { + ret := []string{} + + toolDir := runtime.GOROOT() + "/pkg/tool" + f, _ := os.Open(toolDir) + names, _ := f.Readdirnames(-1) + f.Close() + + for _, name := range names { + subDir, _ := os.Open(toolDir + "/" + name) + tools, _ := subDir.Readdirnames(10) + subDir.Close() + + if len(tools) > 5 { + ret = append(ret, name) + } + } + + return ret +} + // GetAPIPath gets the Go source code path. // // 1. before Go 1.4: $GOROOT/src/pkg diff --git a/views/index.html b/views/index.html index e42ec37..5e44e7a 100644 --- a/views/index.html +++ b/views/index.html @@ -5,7 +5,7 @@ {{.i18n.wide}} - {{.i18n.wide_title}} - + @@ -457,6 +457,12 @@ {{.i18n.rename}}
  • + {{range .crossPlatforms}} +
  • + {{.}} +
  • + {{end}} +
  • {{.i18n.export}}
  • @@ -660,10 +666,10 @@ - + - +