summaryrefslogtreecommitdiff
path: root/build
diff options
context:
space:
mode:
authorDan Duvall <dduvall@wikimedia.org>2017-08-07 17:12:58 -0700
committerDan Duvall <dduvall@wikimedia.org>2017-08-30 09:49:07 -0700
commita0ece14e6e34d7c64f95585859690e1bd1e1a32f (patch)
tree854972376cc181a9c2749153d4ce781a2b3e94cb /build
parent3071d9290e5f91c11e4682cebde558346c4a0908 (diff)
downloadblubber-a0ece14e6e34d7c64f95585859690e1bd1e1a32f.tar.gz
Use real types for build instructions
Summary: Refactored build instructions to use concrete types and `build.Instruction` as an interface instead of relying on a simple enum and arbitrary string arguments. The formal types result in: 1. Clearer internal data structures 2. Partial compilation and proper argument quoting for all instructions moved into the common `build` package 3. Higher order instructions like `build.RunAll` that easily reduce to compiler specific output Test Plan: Run `arc unit` or `go test ./...` Reviewers: thcipriani, mmodell, #release-engineering-team Reviewed By: thcipriani, #release-engineering-team Tags: #release-engineering-team Differential Revision: https://phabricator.wikimedia.org/D741
Diffstat (limited to 'build')
-rw-r--r--build/instructions.go98
-rw-r--r--build/instructions_test.go51
2 files changed, 141 insertions, 8 deletions
diff --git a/build/instructions.go b/build/instructions.go
index e807b5b..5c12796 100644
--- a/build/instructions.go
+++ b/build/instructions.go
@@ -1,14 +1,96 @@
package build
-type InstructionType int
-
-const (
- Run InstructionType = iota
- Copy
- Env
+import (
+ "fmt"
+ "sort"
+ "strconv"
+ "strings"
)
-type Instruction struct {
- Type InstructionType
+type Instruction interface {
+ Compile() []string
+}
+
+type Run struct {
+ Command string
Arguments []string
}
+
+func (run Run) Compile() []string {
+ numInnerArgs := strings.Count(run.Command, `%`) - strings.Count(run.Command, `%%`)
+ command := sprintf(run.Command, run.Arguments[0:numInnerArgs])
+
+ if len(run.Arguments) > numInnerArgs {
+ command += " " + strings.Join(quoteAll(run.Arguments[numInnerArgs:]), " ")
+ }
+
+ return []string{command}
+}
+
+type RunAll struct {
+ Runs []Run
+}
+
+func (runAll RunAll) Compile() []string {
+ commands := make([]string, len(runAll.Runs))
+
+ for i, run := range runAll.Runs {
+ commands[i] = run.Compile()[0]
+ }
+
+ return []string{strings.Join(commands, " && ")}
+}
+
+type Copy struct {
+ Sources []string
+ Destination string
+}
+
+func (copy Copy) Compile() []string {
+ return append(quoteAll(copy.Sources), quote(copy.Destination))
+}
+
+type Env struct {
+ Definitions map[string]string
+}
+
+func (env Env) Compile() []string {
+ defs := make([]string, 0, len(env.Definitions))
+ names := make([]string, 0, len(env.Definitions))
+
+ for name := range env.Definitions {
+ names = append(names, name)
+ }
+
+ sort.Strings(names)
+
+ for _, name := range names {
+ defs = append(defs, name+"="+quote(env.Definitions[name]))
+ }
+
+ return defs
+}
+
+func quote(arg string) string {
+ return strconv.Quote(arg)
+}
+
+func quoteAll(arguments []string) []string {
+ quoted := make([]string, len(arguments))
+
+ for i, arg := range arguments {
+ quoted[i] = quote(arg)
+ }
+
+ return quoted
+}
+
+func sprintf(format string, arguments []string) string {
+ args := make([]interface{}, len(arguments))
+
+ for i, v := range arguments {
+ args[i] = quote(v)
+ }
+
+ return fmt.Sprintf(format, args...)
+}
diff --git a/build/instructions_test.go b/build/instructions_test.go
new file mode 100644
index 0000000..dbab4b5
--- /dev/null
+++ b/build/instructions_test.go
@@ -0,0 +1,51 @@
+package build_test
+
+import (
+ "testing"
+
+ "gopkg.in/stretchr/testify.v1/assert"
+
+ "phabricator.wikimedia.org/source/blubber.git/build"
+)
+
+func TestRun(t *testing.T) {
+ i := build.Run{"echo %s %s", []string{"foo bar", "baz"}}
+
+ assert.Equal(t, []string{`echo "foo bar" "baz"`}, i.Compile())
+}
+
+func TestRunWithInnerAndOuterArguments(t *testing.T) {
+ i := build.Run{"useradd -d %s -u %s", []string{"/foo", "666", "bar"}}
+
+ assert.Equal(t, []string{`useradd -d "/foo" -u "666" "bar"`}, i.Compile())
+}
+
+func TestRunAll(t *testing.T) {
+ i := build.RunAll{[]build.Run{
+ {"echo %s", []string{"foo"}},
+ {"cat %s", []string{"/bar"}},
+ {"baz", []string{}},
+ }}
+
+ assert.Equal(t, []string{`echo "foo" && cat "/bar" && baz`}, i.Compile())
+}
+
+func TestCopy(t *testing.T) {
+ i := build.Copy{[]string{"source1", "source2"}, "dest"}
+
+ assert.Equal(t, []string{`"source1"`, `"source2"`, `"dest"`}, i.Compile())
+}
+
+func TestEnv(t *testing.T) {
+ i := build.Env{map[string]string{
+ "fooname": "foovalue",
+ "barname": "barvalue",
+ "quxname": "quxvalue",
+ }}
+
+ assert.Equal(t, []string{
+ `barname="barvalue"`,
+ `fooname="foovalue"`,
+ `quxname="quxvalue"`,
+ }, i.Compile())
+}