blob: d70102a1c800c242cd7d7342f7fb45e0a7ba6c9f [file] [log] [blame]
commit-bot@chromium.org6d036c22014-04-09 18:59:44 +00001package main
2
3import (
4 "bytes"
5 "crypto/md5"
6 "encoding/base64"
7 "encoding/json"
8 "flag"
9 "fmt"
10 "io/ioutil"
11 "log"
12 "net/http"
13 "os"
14 "os/exec"
15 "path/filepath"
16 "strings"
17 "text/template"
18)
19
20const (
21 RESULT_COMPILE = `c++ -DSK_GAMMA_SRGB -DSK_GAMMA_APPLY_TO_A8 -DSK_SCALAR_TO_FLOAT_EXCLUDED -DSK_ALLOW_STATIC_GLOBAL_INITIALIZERS=1 -DSK_SUPPORT_GPU=0 -DSK_SUPPORT_OPENCL=0 -DSK_FORCE_DISTANCEFIELD_FONTS=0 -DSK_SCALAR_IS_FLOAT -DSK_CAN_USE_FLOAT -DSK_SAMPLES_FOR_X -DSK_BUILD_FOR_UNIX -DSK_USE_POSIX_THREADS -DSK_SYSTEM_ZLIB=1 -DSK_DEBUG -DSK_DEVELOPER=1 -I../../src/core -I../../src/images -I../../tools/flags -I../../include/config -I../../include/core -I../../include/pathops -I../../include/pipe -I../../include/effects -I../../include/ports -I../../src/sfnt -I../../include/utils -I../../src/utils -I../../include/images -g -fno-exceptions -fstrict-aliasing -Wall -Wextra -Winit-self -Wpointer-arith -Wno-unused-parameter -Wno-c++11-extensions -Werror -m64 -fno-rtti -Wnon-virtual-dtor -c ../../../cache/%s.cpp -o ../../../cache/%s.o`
22 LINK = `c++ -m64 -lstdc++ -lm -o ../../../inout/%s -Wl,--start-group ../../../cache/%s.o obj/experimental/webtry/webtry.main.o obj/experimental/webtry/webtry.syscall_reporter.o obj/gyp/libflags.a libskia_images.a libskia_core.a libskia_effects.a obj/gyp/libjpeg.a obj/gyp/libwebp_dec.a obj/gyp/libwebp_demux.a obj/gyp/libwebp_dsp.a obj/gyp/libwebp_enc.a obj/gyp/libwebp_utils.a libskia_utils.a libskia_opts.a libskia_opts_ssse3.a libskia_ports.a libskia_sfnt.a -Wl,--end-group -lpng -lz -lgif -lpthread -lfontconfig -ldl -lfreetype`
23)
24
25var (
26 // codeTemplate is the cpp code template the user's code is copied into.
27 codeTemplate *template.Template = nil
28
29 // index is the main index.html page we serve.
30 index []byte
31)
32
33// flags
34var (
35 useChroot = flag.Bool("use_chroot", false, "Run the compiled code in the schroot jail.")
36)
37
38// lineNumbers adds #line numbering to the user's code.
39func LineNumbers(c string) string {
40 lines := strings.Split(c, "\n")
41 ret := []string{}
42 for i, line := range lines {
43 ret = append(ret, fmt.Sprintf("#line %d", i+1))
44 ret = append(ret, line)
45 }
46 return strings.Join(ret, "\n")
47}
48
49func init() {
50 // Change the current working directory to the directory of the executable.
51 var err error
52 cwd, err := filepath.Abs(filepath.Dir(os.Args[0]))
53 if err != nil {
54 log.Fatal(err)
55 }
56 os.Chdir(cwd)
57
58 codeTemplate, err = template.ParseFiles(filepath.Join(cwd, "templates/template.cpp"))
59 if err != nil {
60 panic(err)
61 }
62 index, err = ioutil.ReadFile(filepath.Join(cwd, "templates/index.html"))
63 if err != nil {
64 panic(err)
65 }
66}
67
68// userCode is used in template expansion.
69type userCode struct {
70 UserCode string
71}
72
73// expandToFile expands the template and writes the result to the file.
74func expandToFile(filename string, code string, t *template.Template) error {
75 f, err := os.Create(filename)
76 if err != nil {
77 return err
78 }
79 defer f.Close()
80 return t.Execute(f, struct{ UserCode string }{UserCode: code})
81}
82
83// expandCode expands the template into a file and calculate the MD5 hash.
84func expandCode(code string) (string, error) {
85 h := md5.New()
86 h.Write([]byte(code))
87 hash := fmt.Sprintf("%x", h.Sum(nil))
88 // At this point we are running in skia/experimental/webtry, making cache a
89 // peer directory to skia.
90 // TODO(jcgregorio) Make all relative directories into flags.
91 err := expandToFile(fmt.Sprintf("../../../cache/%s.cpp", hash), code, codeTemplate)
92 return hash, err
93}
94
95// response is serialized to JSON as a response to POSTs.
96type response struct {
97 Message string `json:"message"`
98 Img string `json:"img"`
99}
100
101// doCmd executes the given command line string in either the out/Debug
102// directory or the inout directory. Returns the stdout, and stderr in the case
103// of a non-zero exit code.
104func doCmd(commandLine string, moveToDebug bool) (string, error) {
105 log.Printf("Command: %q\n", commandLine)
106 programAndArgs := strings.SplitN(commandLine, " ", 2)
107 program := programAndArgs[0]
108 args := []string{}
109 if len(programAndArgs) > 1 {
110 args = strings.Split(programAndArgs[1], " ")
111 }
112 cmd := exec.Command(program, args...)
113 abs, err := filepath.Abs("../../out/Debug")
114 if err != nil {
115 return "", fmt.Errorf("Failed to find absolute path to Debug directory.")
116 }
117 if moveToDebug {
118 cmd.Dir = abs
119 } else if !*useChroot { // Don't set cmd.Dir when using chroot.
120 abs, err := filepath.Abs("../../../inout")
121 if err != nil {
122 return "", fmt.Errorf("Failed to find absolute path to inout directory.")
123 }
124 cmd.Dir = abs
125 }
126 log.Printf("Run in directory: %q\n", cmd.Dir)
127 var stdOut bytes.Buffer
128 cmd.Stdout = &stdOut
129 var stdErr bytes.Buffer
130 cmd.Stderr = &stdErr
131 cmd.Start()
132 err = cmd.Wait()
133 message := stdOut.String()
134 log.Printf("StdOut: %s\n", message)
135 if err != nil {
136 log.Printf("Exit status: %s\n", err.Error())
137 log.Printf("StdErr: %s\n", stdErr.String())
138 message += stdErr.String()
139 return message, fmt.Errorf("Failed to run command.")
140 }
141 return message, nil
142}
143
144// reportError formats an HTTP error response and also logs the detailed error message.
145func reportError(w http.ResponseWriter, r *http.Request, err error, message string) {
146 m := response{
147 Message: message,
148 }
149 log.Printf("Error: %s\n%s", message, err.Error())
150 resp, err := json.Marshal(m)
151 if err != nil {
152 http.Error(w, "Failed to serialize a response", 500)
153 return
154 }
155 w.Write(resp)
156}
157
158// mainHandler handles the GET and POST of the main page.
159func mainHandler(w http.ResponseWriter, r *http.Request) {
160 if r.Method == "GET" {
161 w.Write(index)
162 } else if r.Method == "POST" {
163 w.Header().Set("Content-Type", "application/json")
164 b, err := ioutil.ReadAll(r.Body)
165 if err != nil {
166 reportError(w, r, err, "Failed to read a request body.")
167 return
168 }
169 hash, err := expandCode(LineNumbers(string(b)))
170 if err != nil {
171 reportError(w, r, err, "Failed to write the code to compile.")
172 return
173 }
174 message, err := doCmd(fmt.Sprintf(RESULT_COMPILE, hash, hash), true)
175 if err != nil {
176 reportError(w, r, err, "Failed to compile the code:\n"+message)
177 return
178 }
179 linkMessage, err := doCmd(fmt.Sprintf(LINK, hash, hash), true)
180 if err != nil {
181 reportError(w, r, err, "Failed to link the code:\n"+linkMessage)
182 return
183 }
184 message += linkMessage
185 cmd := hash + " --out " + hash + ".png"
186 if *useChroot {
187 cmd = "schroot -c webtry --directory=/inout -- /inout/" + cmd
188 } else {
189 abs, err := filepath.Abs("../../../inout")
190 if err != nil {
191 reportError(w, r, err, "Failed to find executable directory.")
192 return
193 }
194 cmd = abs + "/" + cmd
195 }
196
197 execMessage, err := doCmd(cmd, false)
198 if err != nil {
199 reportError(w, r, err, "Failed to run the code:\n"+execMessage)
200 return
201 }
202 png, err := ioutil.ReadFile("../../../inout/" + hash + ".png")
203 if err != nil {
204 reportError(w, r, err, "Failed to open the generated PNG.")
205 return
206 }
207
208 m := response{
209 Message: message,
210 Img: base64.StdEncoding.EncodeToString([]byte(png)),
211 }
212 resp, err := json.Marshal(m)
213 if err != nil {
214 reportError(w, r, err, "Failed to serialize a response.")
215 return
216 }
217 w.Write(resp)
218 }
219}
220
221func main() {
222 flag.Parse()
223
224 http.HandleFunc("/", mainHandler)
225 log.Fatal(http.ListenAndServe(":8000", nil))
226}