func (stage *buildStage) build(cacheMgr cache.Manager, lastStage, copiedFrom bool) error {
	var err error
	diffIDs := make([]image.Digest, 0)
	histories := make([]image.History, 0)
	for i, node := range stage.nodes {
		modifyFS := stage.opts.requireOnDisk || copiedFrom
		if modifyFS && !stage.opts.allowModifyFS {
			return fmt.Errorf("fs not allowed to be modified")
		}
		skipBuild := i < stage.latestFetched() && i > 0
		lastStep := i == len(stage.nodes)-1
		forceCommit := i == 0 || (lastStage && lastStep) || stage.opts.forceCommit
		nodeOpts := &buildNodeOptions{
			skipBuild:   skipBuild,
			forceCommit: forceCommit,
			modifyFS:    modifyFS,
		}
		log.Infof("* Step %d/%d (%s) : %s", i+1, len(stage.nodes), nodeOpts.String(), node.String())
		stage.lastImageConfig, err = node.Build(cacheMgr, stage.lastImageConfig, nodeOpts)
		if err != nil {
			return fmt.Errorf("build node: %s", err)
		}
		for _, digestPair := range node.digestPairs {
			diffIDs = append(diffIDs, digestPair.TarDigest)
			histories = append(histories, image.History{
				Created:   time.Now(),
				CreatedBy: fmt.Sprintf("makisu: %s", node.String()),
				Author:    "makisu",
			})
		}
	}
	stage.lastImageConfig.Created = time.Now()
	stage.lastImageConfig.History = histories
	stage.lastImageConfig.RootFS.DiffIDs = diffIDs
	stage.lastImageConfig.ContainerConfiguration = nil
	return nil
}
build performs the build for that stage. There are side effects that should
be expected on each node within the stage.
package builder
import (
	"crypto/sha256"
	"encoding/hex"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"os"
	"path"
	"time"
	"github.com/uber/makisu/lib/builder/step"
	"github.com/uber/makisu/lib/cache"
	"github.com/uber/makisu/lib/context"
	"github.com/uber/makisu/lib/docker/image"
	"github.com/uber/makisu/lib/log"
	"github.com/uber/makisu/lib/parser/dockerfile"
	"github.com/uber/makisu/lib/storage"
)
type buildStageOptions struct {
	forceCommit   bool
	allowModifyFS bool
	requireOnDisk bool
}
type buildStage struct {
	ctx             *context.BuildContext
	alias           string
	copyFromDirs    map[string][]string
	nodes           []*buildNode
	lastImageConfig *image.Config
	opts *buildStageOptions
}
func newBuildStage(
	baseCtx *context.BuildContext, alias, seed string, parsedStage *dockerfile.Stage,
	planOpts *buildPlanOptions) (*buildStage, error) {
	ctx, err := context.NewBuildContext(
		baseCtx.RootDir, baseCtx.ContextDir, baseCtx.ImageStore)
	if err != nil {
		return nil, fmt.Errorf("create stage build context: %s", err)
	}
	steps, err := createDockerfileSteps(ctx, seed, parsedStage, planOpts)
	if err != nil {
		return nil, fmt.Errorf("new dockerfile steps: %s", err)
	}
	return newBuildStageHelper(ctx, alias, steps, planOpts)
}
func newRemoteImageStage(
	baseCtx *context.BuildContext, alias, seed string,
	planOpts *buildPlanOptions) (*buildStage, error) {
	ctx, err := context.NewBuildContext(
		baseCtx.RootDir, baseCtx.ContextDir, baseCtx.ImageStore)
	if err != nil {
		return nil, fmt.Errorf("create stage build context: %s", err)
	}
	from, err := step.NewFromStep(alias, alias, alias)
	if err != nil {
		return nil, fmt.Errorf("new from step: %s", err)
	}
	if err := from.SetCacheID(ctx, seed); err != nil {
		return nil, fmt.Errorf("set cache id: %s", err)
	}
	steps := []step.BuildStep{from}
	opts := &buildPlanOptions{
		forceCommit:   false,
		allowModifyFS: planOpts.allowModifyFS,
	}
	return newBuildStageHelper(ctx, alias, steps, opts)
}
func newBuildStageHelper(
	ctx *context.BuildContext, alias string, steps []step.BuildStep,
	planOpts *buildPlanOptions) (*buildStage, error) {
	var requireOnDisk bool
	nodes := make([]*buildNode, 0)
	copyFromDirs := make(map[string][]string)
	for _, step := range steps {
		newNode := newBuildNode(ctx, step)
		nodes = append(nodes, newNode)
		alias, dirs := step.ContextDirs()
		if len(dirs) > 0 {
			if _, ok := copyFromDirs[alias]; !ok {
				copyFromDirs[alias] = make([]string, 0)
			}
			copyFromDirs[alias] = append(copyFromDirs[alias], dirs...)
		}
		if step.RequireOnDisk() {
			requireOnDisk = true
		}
	}
	stage := &buildStage{
		ctx:          ctx,
		copyFromDirs: copyFromDirs,
		alias:        alias,
		nodes:        nodes,
		opts: &buildStageOptions{
			allowModifyFS: planOpts.allowModifyFS,
			forceCommit:   planOpts.forceCommit,
			requireOnDisk: requireOnDisk,
		},
	}
	return stage, nil
}
func createDockerfileSteps(
	ctx *context.BuildContext, seed string, stage *dockerfile.Stage,
	planOpts *buildPlanOptions) ([]step.BuildStep, error) {
	directives := append([]dockerfile.Directive{stage.From}, stage.Directives...)
	var steps []step.BuildStep
	for _, directive := range directives {
		step, err := step.NewDockerfileStep(ctx, directive, seed)
		if err != nil {
			return nil, fmt.Errorf("directive to build step: %s", err)
		}
		steps = append(steps, step)
		seed = step.CacheID()
	}
	return steps, nil
}