bashbrew/cmd/bashbrew/cmd-build.go

129 lines
4.1 KiB
Go

package main
import (
"fmt"
"github.com/urfave/cli"
)
func cmdBuild(c *cli.Context) error {
repos, err := repos(c.Bool("all"), c.Args()...)
if err != nil {
return cli.NewMultiError(fmt.Errorf(`failed gathering repo list`), err)
}
repos, err = sortRepos(repos, true)
if err != nil {
return cli.NewMultiError(fmt.Errorf(`failed sorting repo list`), err)
}
uniq := c.Bool("uniq")
pull := c.String("pull")
switch pull {
case "always", "missing", "never":
// legit
default:
return fmt.Errorf(`invalid value for --pull: %q`, pull)
}
dryRun := c.Bool("dry-run")
for _, repo := range repos {
r, err := fetch(repo)
if err != nil {
return cli.NewMultiError(fmt.Errorf(`failed fetching repo %q`, repo), err)
}
entries, err := r.SortedEntries(true)
if err != nil {
return cli.NewMultiError(fmt.Errorf(`failed sorting entries list for %q`, repo), err)
}
for _, entry := range entries {
if r.SkipConstraints(entry) {
continue
}
froms, err := r.DockerFroms(entry)
if err != nil {
return cli.NewMultiError(fmt.Errorf(`failed fetching/scraping FROM for %q (tags %q)`, r.RepoName, entry.TagsString()), err)
}
fromScratch := false
for _, from := range froms {
fromScratch = fromScratch || from == "scratch"
if from != "scratch" && pull != "never" {
doPull := false
switch pull {
case "always":
doPull = true
case "missing":
_, err := dockerInspect("{{.Id}}", from)
doPull = (err != nil)
default:
return fmt.Errorf(`unexpected value for --pull: %s`, pull)
}
if doPull {
// TODO detect if "from" is something we've built (ie, "python:3-onbuild" is "FROM python:3" but we don't want to pull "python:3" if we "bashbrew build python")
fmt.Printf("Pulling %s (%s)\n", from, r.EntryIdentifier(entry))
if !dryRun {
dockerPull(from)
}
}
}
}
cacheTag, err := r.DockerCacheName(entry)
if err != nil {
return cli.NewMultiError(fmt.Errorf(`failed calculating "cache hash" for %q (tags %q)`, r.RepoName, entry.TagsString()), err)
}
// check whether we've already built this artifact
_, err = dockerInspect("{{.Id}}", cacheTag)
if err != nil {
fmt.Printf("Building %s (%s)\n", cacheTag, r.EntryIdentifier(entry))
if !dryRun {
commit, err := r.fetchGitRepo(arch, entry)
if err != nil {
return cli.NewMultiError(fmt.Errorf(`failed fetching git repo for %q (tags %q)`, r.RepoName, entry.TagsString()), err)
}
archive, err := gitArchive(commit, entry.ArchDirectory(arch))
if err != nil {
return cli.NewMultiError(fmt.Errorf(`failed generating git archive for %q (tags %q)`, r.RepoName, entry.TagsString()), err)
}
defer archive.Close()
// TODO use "meta.StageNames" to do "docker build --target" so we can tag intermediate stages too for cache (streaming "git archive" directly to "docker build" makes that a little hard to accomplish without re-streaming)
var extraEnv []string = nil
if fromScratch {
// https://github.com/docker/cli/blob/v20.10.7/cli/command/image/build.go#L163
extraEnv = []string{"DOCKER_DEFAULT_PLATFORM=" + ociArch.String()}
// ideally, we would set this via an explicit "--platform" flag on "docker build", but it's not supported without buildkit until 20.10+ and this is a trivial way to get Docker to do the right thing in both cases without explicitly trying to detect whether we're on 20.10+
}
err = dockerBuild(cacheTag, entry.ArchFile(arch), archive, extraEnv)
if err != nil {
return cli.NewMultiError(fmt.Errorf(`failed building %q (tags %q)`, r.RepoName, entry.TagsString()), err)
}
archive.Close() // be sure this happens sooner rather than later (defer might take a while, and we want to reap zombies more aggressively)
}
} else {
fmt.Printf("Using %s (%s)\n", cacheTag, r.EntryIdentifier(entry))
}
for _, tag := range r.Tags(namespace, uniq, entry) {
fmt.Printf("Tagging %s\n", tag)
if !dryRun {
err := dockerTag(cacheTag, tag)
if err != nil {
return cli.NewMultiError(fmt.Errorf(`failed tagging %q as %q`, cacheTag, tag), err)
}
}
}
}
}
return nil
}