blob: 09878de78de220eea526bbdb5ccd27db09c32949 [file] [log] [blame]
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package main
import (
"bytes"
"exec"
"flag"
"fmt"
"go/build"
"http"
"io/ioutil"
"log"
"os"
"path/filepath"
"runtime"
"strconv"
"strings"
"sync"
// Imports so that goinstall automatically installs them.
_ "go-tour.googlecode.com/hg/pic"
_ "go-tour.googlecode.com/hg/tree"
_ "go-tour.googlecode.com/hg/wc"
)
const basePkg = "go-tour.googlecode.com/hg"
var (
httpListen = flag.String("http", "127.0.0.1:3999", "host:port to listen on")
htmlOutput = flag.Bool("html", false, "render program output as HTML")
)
var (
// a source of numbers, for naming temporary files
uniq = make(chan int)
// the architecture-identifying character of the tool chain, 5, 6, or 8
archChar string
// where gc and ld should find the go-tour packages
pkgDir string
)
func main() {
flag.Parse()
// source of unique numbers
go func() {
for i := 0; ; i++ {
uniq <- i
}
}()
// set archChar
var err os.Error
archChar, err = build.ArchChar(runtime.GOARCH)
if err != nil {
log.Fatal(err)
}
// find and serve the go tour files
t, _, err := build.FindTree(basePkg)
if err != nil {
log.Fatalf("Couldn't find tour files: %v", err)
}
root := filepath.Join(t.SrcDir(), basePkg)
log.Println("Serving content from", root)
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path == "/favicon.ico" || r.URL.Path == "/" {
fn := filepath.Join(root, "static", r.URL.Path[1:])
http.ServeFile(w, r, fn)
return
}
http.Error(w, "not found", 404)
})
http.Handle("/static/", http.FileServer(http.Dir(root)))
http.HandleFunc("/kill", kill)
// set include path for ld and gc
pkgDir = t.PkgDir()
if !strings.HasPrefix(*httpListen, "127.0.0.1") &&
!strings.HasPrefix(*httpListen, "localhost") {
log.Print(localhostWarning)
}
log.Printf("Serving at http://%s/", *httpListen)
log.Fatal(http.ListenAndServe(*httpListen, nil))
}
const localhostWarning = `
WARNING! WARNING! WARNING!
I appear to be listening on an address that is not localhost.
Anyone with access to this address and port will have access
to this machine as the user running gotour.
If you don't understand this message, hit Control-C to terminate this process.
WARNING! WARNING! WARNING!
`
var running struct {
sync.Mutex
cmd *exec.Cmd
}
func stopRun() {
running.Lock()
if running.cmd != nil {
running.cmd.Process.Kill()
running.cmd = nil
}
running.Unlock()
}
func kill(w http.ResponseWriter, r *http.Request) {
stopRun()
}
func compile(req *http.Request) (out []byte, err os.Error) {
stopRun()
// x is the base name for .go, .6, executable files
x := os.TempDir() + "/compile" + strconv.Itoa(<-uniq)
src := x + ".go"
obj := x + "." + archChar
bin := x
if runtime.GOOS == "windows" {
bin += ".exe"
}
// rewrite filename in error output
defer func() {
out = bytes.Replace(out, []byte(src+":"), []byte("main.go:"), -1)
}()
// write body to x.go
body := new(bytes.Buffer)
if _, err = body.ReadFrom(req.Body); err != nil {
return
}
if err = ioutil.WriteFile(src, body.Bytes(), 0666); err != nil {
return
}
// build x.go, creating x.6
out, err = run(archChar+"g", "-I", pkgDir, "-o", obj, src)
defer os.Remove(obj)
if err != nil {
return
}
// link x.6, creating x (the program binary)
out, err = run(archChar+"l", "-L", pkgDir, "-o", bin, obj)
defer os.Remove(bin)
if err != nil {
return
}
// run x
return run(bin)
}
// run executes the specified command and returns its output and an error.
func run(args ...string) ([]byte, os.Error) {
var buf bytes.Buffer
cmd := exec.Command(args[0], args[1:]...)
cmd.Stdout = &buf
cmd.Stderr = cmd.Stdout
// Start command and leave in 'running'.
running.Lock()
if running.cmd != nil {
defer running.Unlock()
return nil, fmt.Errorf("already running %s", running.cmd.Path)
}
if err := cmd.Start(); err != nil {
running.Unlock()
return nil, err
}
running.cmd = cmd
running.Unlock()
// Wait for the command. Clean up,
err := cmd.Wait()
running.Lock()
if running.cmd == cmd {
running.cmd = nil
}
running.Unlock()
return buf.Bytes(), err
}