1/*
2 * Copyright 2015 Google Inc.
3 *
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
6 */
7package main
8
9// Example use:
10//   git clone https://skia.googlesource.com/skia.git
11//   cd skia
12//   SKIA=$PWD
13//   cd experimental/fiddle
14//   go get github.com/skia-dev/glog
15//   go get go.skia.org/infra/go/util
16//   go build fiddler.go
17//   # compile prerequisites
18//   ./fiddler "$SKIA"
19//   # compile and run a fiddle
20//   ./fiddler "$SKIA" draw.cpp | ./parse-fiddle-output
21//   # compile and run a different fiddle
22//   ./fiddler "$SKIA" ANOTHER_FIDDLE.cpp | ./parse-fiddle-output
23
24import (
25	"bytes"
26	"fmt"
27	"io"
28	"io/ioutil"
29	"os"
30	"os/exec"
31	"path"
32	"syscall"
33
34	"github.com/skia-dev/glog"
35	"go.skia.org/infra/go/util"
36)
37
38func setResourceLimits() error {
39	const maximumTimeInSeconds = 5
40	limit := syscall.Rlimit{maximumTimeInSeconds, maximumTimeInSeconds}
41	if err := syscall.Setrlimit(syscall.RLIMIT_CPU, &limit); err != nil {
42		return err
43	}
44	const maximumMemoryInBytes = 1 << 28
45	limit = syscall.Rlimit{maximumMemoryInBytes, maximumMemoryInBytes}
46	return syscall.Setrlimit(syscall.RLIMIT_AS, &limit)
47}
48
49// execCommand runs command and returns an error if it fails.  If there is no
50// error, all output is discarded.
51func execCommand(input io.Reader, dir string, name string, arg ...string) error {
52	var buffer bytes.Buffer
53	cmd := exec.Command(name, arg...)
54	cmd.Dir = dir
55	cmd.Stdout = &buffer
56	cmd.Stderr = &buffer
57	cmd.Stdin = input
58	if err := cmd.Run(); err != nil {
59		return fmt.Errorf("execution failed:\n\n%s\n[%v]", buffer.String(), err)
60	}
61	return nil
62}
63
64func compileArgs(skiaSrc string) string {
65	return "@" + path.Join(skiaSrc, "cmake", "skia_compile_arguments.txt")
66}
67
68func linkArgs(skiaSrc string) string {
69	return "@" + path.Join(skiaSrc, "cmake", "skia_link_arguments.txt")
70}
71
72// fiddler compiles the input, links against skia, and runs the executable.
73// @param skiaSrc: the base directory of the Skia repository
74// @param inputReader: C++ fiddle source
75// @param output: stdout of executable sent here
76// @param tempDir: where to place the compiled executable
77func fiddler(skiaSrc string, inputReader io.Reader, output io.Writer, tempDir string) error {
78	binarypath := path.Join(tempDir, "fiddle")
79	fiddle_dir := path.Join(skiaSrc, "experimental", "fiddle")
80	if err := execCommand(inputReader, fiddle_dir,
81		"c++",
82		compileArgs(skiaSrc),
83		"-I", fiddle_dir,
84		"-o", binarypath,
85		"-x", "c++", "-", "-x", "none",
86		"fiddle_main.o",
87		"-lOSMesa",
88		linkArgs(skiaSrc),
89	); err != nil {
90		return err
91	}
92	var buffer bytes.Buffer
93	runCmd := exec.Cmd{Path: binarypath, Stdout: output, Stderr: &buffer}
94	if err := runCmd.Run(); err != nil {
95		return fmt.Errorf("execution failed:\n\n%s\n[%v]", buffer.String(), err)
96	}
97	return nil
98}
99
100// Compile Skia library and fiddle_main.cpp
101// @param skiaSrc: the base directory of the Skia repository.
102func fiddlerPrerequisites(skiaSrc string) error {
103	cmakeDir := path.Join(skiaSrc, "cmake")
104	if err := execCommand(nil, cmakeDir, "cmake", "-G", "Ninja", "."); err != nil {
105		return err
106	}
107	if err := execCommand(nil, cmakeDir, "ninja", "skia"); err != nil {
108		return err
109	}
110	fiddle_dir := path.Join(skiaSrc, "experimental", "fiddle")
111	if err := execCommand(nil, fiddle_dir, "c++", compileArgs(skiaSrc),
112		"fiddle_main.h"); err != nil {
113		return err
114	}
115	return execCommand(nil, fiddle_dir, "c++", compileArgs(skiaSrc),
116		"-c", "-o", "fiddle_main.o", "fiddle_main.cpp")
117}
118
119func main() {
120	if len(os.Args) < 2 {
121		glog.Fatalf("usage: %s SKIA_SRC_PATH [PATH_TO_DRAW.CPP]", os.Args[0])
122	}
123	skiaSrc := os.Args[1]
124	if len(os.Args) < 3 {
125		// execCommand(nil, skiaSrc, "git", "fetch")
126		// execCommand(nil, skiaSrc, "git", "checkout", "origin/master")
127		if err := fiddlerPrerequisites(skiaSrc); err != nil {
128			glog.Fatal(err)
129		}
130	} else {
131		if err := setResourceLimits(); err != nil {
132			glog.Fatal(err)
133		}
134		tempDir, err := ioutil.TempDir("", "fiddle_")
135		if err != nil {
136			glog.Fatal(err)
137		}
138		defer func() {
139			err = os.RemoveAll(tempDir)
140			if err != nil {
141				glog.Fatalf("os.RemoveAll(tempDir) failed: %v", err)
142			}
143		}()
144		if os.Args[2] == "-" {
145			if err := fiddler(skiaSrc, os.Stdin, os.Stdout, tempDir); err != nil {
146				glog.Fatal(err)
147			}
148		} else {
149			inputFile, err := os.Open(os.Args[2])
150			if err != nil {
151				glog.Fatalf("unable to open \"%s\": %v", os.Args[2], err)
152			}
153			util.Close(inputFile)
154			if err = fiddler(skiaSrc, inputFile, os.Stdout, tempDir); err != nil {
155				glog.Fatal(err)
156			}
157		}
158	}
159}
160