aboutsummaryrefslogtreecommitdiffhomepage
path: root/experimental/fiddle/fiddler.go
blob: d3056c95d3f0cecbbcff9a1aa41a75972a1346be (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
/*
 * Copyright 2015 Google Inc.
 *
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */
package main

// Example use:
//   git clone https://skia.googlesource.com/skia.git
//   cd skia
//   SKIA=$PWD
//   cd experimental/fiddle
//   go get github.com/skia-dev/glog
//   go get go.skia.org/infra/go/util
//   go build fiddler.go
//   # compile prerequisites
//   ./fiddler "$SKIA"
//   # compile and run a fiddle
//   ./fiddler "$SKIA" draw.cpp | ./parse-fiddle-output
//   # compile and run a different fiddle
//   ./fiddler "$SKIA" ANOTHER_FIDDLE.cpp | ./parse-fiddle-output

import (
	"bytes"
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"os/exec"
	"path"
	"syscall"

	"github.com/skia-dev/glog"
	"go.skia.org/infra/go/util"
)

func setResourceLimits() error {
	const maximumTimeInSeconds = 5
	limit := syscall.Rlimit{maximumTimeInSeconds, maximumTimeInSeconds}
	if err := syscall.Setrlimit(syscall.RLIMIT_CPU, &limit); err != nil {
		return err
	}
	const maximumMemoryInBytes = 1 << 28
	limit = syscall.Rlimit{maximumMemoryInBytes, maximumMemoryInBytes}
	return syscall.Setrlimit(syscall.RLIMIT_AS, &limit)
}

// execCommand runs command and returns an error if it fails.  If there is no
// error, all output is discarded.
func execCommand(input io.Reader, dir string, name string, arg ...string) error {
	var buffer bytes.Buffer
	cmd := exec.Command(name, arg...)
	cmd.Dir = dir
	cmd.Stdout = &buffer
	cmd.Stderr = &buffer
	cmd.Stdin = input
	if err := cmd.Run(); err != nil {
		return fmt.Errorf("execution failed:\n\n%s\n[%v]", buffer.String(), err)
	}
	return nil
}

func compileArgs(skiaSrc string) string {
	return "@" + path.Join(skiaSrc, "cmake", "skia_compile_arguments.txt")
}

func linkArgs(skiaSrc string) string {
	return "@" + path.Join(skiaSrc, "cmake", "skia_link_arguments.txt")
}

// fiddler compiles the input, links against skia, and runs the executable.
// @param skiaSrc: the base directory of the Skia repository
// @param inputReader: C++ fiddle source
// @param output: stdout of executable sent here
// @param tempDir: where to place the compiled executable
func fiddler(skiaSrc string, inputReader io.Reader, output io.Writer, tempDir string) error {
	binarypath := path.Join(tempDir, "fiddle")
	fiddle_dir := path.Join(skiaSrc, "experimental", "fiddle")
	if err := execCommand(inputReader, fiddle_dir,
		"c++",
		compileArgs(skiaSrc),
		"-I", fiddle_dir,
		"-o", binarypath,
		"-x", "c++", "-", "-x", "none",
		"fiddle_main.o",
		"-lOSMesa",
		linkArgs(skiaSrc),
	); err != nil {
		return err
	}
	var buffer bytes.Buffer
	runCmd := exec.Cmd{Path: binarypath, Stdout: output, Stderr: &buffer}
	if err := runCmd.Run(); err != nil {
		return fmt.Errorf("execution failed:\n\n%s\n[%v]", buffer.String(), err)
	}
	return nil
}

// Compile Skia library and fiddle_main.cpp
// @param skiaSrc: the base directory of the Skia repository.
func fiddlerPrerequisites(skiaSrc string) error {
	cmakeDir := path.Join(skiaSrc, "cmake")
	if err := execCommand(nil, cmakeDir, "cmake", "-G", "Ninja", "."); err != nil {
		return err
	}
	if err := execCommand(nil, cmakeDir, "ninja", "skia"); err != nil {
		return err
	}
	fiddle_dir := path.Join(skiaSrc, "experimental", "fiddle")
	if err := execCommand(nil, fiddle_dir, "c++", compileArgs(skiaSrc),
		"fiddle_main.h"); err != nil {
		return err
	}
	return execCommand(nil, fiddle_dir, "c++", compileArgs(skiaSrc),
		"-c", "-o", "fiddle_main.o", "fiddle_main.cpp")
}

func main() {
	if len(os.Args) < 2 {
		glog.Fatalf("usage: %s SKIA_SRC_PATH [PATH_TO_DRAW.CPP]", os.Args[0])
	}
	skiaSrc := os.Args[1]
	if len(os.Args) < 3 {
		// execCommand(nil, skiaSrc, "git", "fetch")
		// execCommand(nil, skiaSrc, "git", "checkout", "origin/master")
		if err := fiddlerPrerequisites(skiaSrc); err != nil {
			glog.Fatal(err)
		}
	} else {
		if err := setResourceLimits(); err != nil {
			glog.Fatal(err)
		}
		tempDir, err := ioutil.TempDir("", "fiddle_")
		if err != nil {
			glog.Fatal(err)
		}
		defer func() {
			err = os.RemoveAll(tempDir)
			if err != nil {
				glog.Fatalf("os.RemoveAll(tempDir) failed: %v", err)
			}
		}()
		if os.Args[2] == "-" {
			if err := fiddler(skiaSrc, os.Stdin, os.Stdout, tempDir); err != nil {
				glog.Fatal(err)
			}
		} else {
			inputFile, err := os.Open(os.Args[2])
			if err != nil {
				glog.Fatalf("unable to open \"%s\": %v", os.Args[2], err)
			}
			util.Close(inputFile)
			if err = fiddler(skiaSrc, inputFile, os.Stdout, tempDir); err != nil {
				glog.Fatal(err)
			}
		}
	}
}