diff --git a/cmd/bashbrew/cmd-build.go b/cmd/bashbrew/cmd-build.go index 517839a5..43a8542e 100644 --- a/cmd/bashbrew/cmd-build.go +++ b/cmd/bashbrew/cmd-build.go @@ -95,16 +95,27 @@ func cmdBuild(c *cli.Context) error { // 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) + switch builder := entry.ArchBuilder(arch); builder { + case "classic", "": + var platform string + if fromScratch { + platform = ociArch.String() + } + err = dockerBuild(cacheTag, entry.ArchFile(arch), archive, platform) + if err != nil { + return cli.NewMultiError(fmt.Errorf(`failed building %q (tags %q)`, r.RepoName, entry.TagsString()), err) + } + case "buildkit": + var platform string + if fromScratch { + platform = ociArch.String() + } + err = dockerBuildxBuild(cacheTag, entry.ArchFile(arch), archive, platform) + if err != nil { + return cli.NewMultiError(fmt.Errorf(`failed building %q (tags %q)`, r.RepoName, entry.TagsString()), err) + } + default: + return cli.NewMultiError(fmt.Errorf(`unknown builder %q`, builder)) } archive.Close() // be sure this happens sooner rather than later (defer might take a while, and we want to reap zombies more aggressively) } diff --git a/cmd/bashbrew/docker.go b/cmd/bashbrew/docker.go index 3024d299..17f45db6 100644 --- a/cmd/bashbrew/docker.go +++ b/cmd/bashbrew/docker.go @@ -13,8 +13,8 @@ import ( "strconv" "strings" - "github.com/urfave/cli" "github.com/docker-library/bashbrew/manifest" + "github.com/urfave/cli" ) type dockerfileMetadata struct { @@ -218,6 +218,11 @@ func (r Repo) dockerBuildUniqueBits(entry *manifest.Manifest2822Entry) ([]string entry.ArchDirectory(arch), entry.ArchFile(arch), } + if builder := entry.ArchBuilder(arch); builder != "" { + // NOTE: preserve long-term unique id by only attaching builder if + // explicitly specified + uniqueBits = append(uniqueBits, entry.ArchBuilder(arch)) + } meta, err := r.dockerfileMetadata(entry) if err != nil { return nil, err @@ -237,14 +242,20 @@ func (r Repo) dockerBuildUniqueBits(entry *manifest.Manifest2822Entry) ([]string return uniqueBits, nil } -func dockerBuild(tag string, file string, context io.Reader, extraEnv []string) error { +func dockerBuild(tag string, file string, context io.Reader, platform string) error { args := []string{"build", "--tag", tag, "--file", file, "--rm", "--force-rm"} args = append(args, "-") cmd := exec.Command("docker", args...) - if extraEnv != nil { - cmd.Env = append(os.Environ(), extraEnv...) + cmd.Env = append(os.Environ(), "DOCKER_BUILDKIT=0") + if debugFlag { + fmt.Println("$ export DOCKER_BUILDKIT=0") + } + if platform != "" { + // 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+ + // https://github.com/docker/cli/blob/v20.10.7/cli/command/image/build.go#L163 + cmd.Env = append(cmd.Env, "DOCKER_DEFAULT_PLATFORM="+platform) if debugFlag { - fmt.Printf("$ export %q\n", extraEnv) + fmt.Printf("$ export DOCKER_DEFAULT_PLATFORM=%q\n", platform) } } cmd.Stdin = context @@ -265,6 +276,46 @@ func dockerBuild(tag string, file string, context io.Reader, extraEnv []string) } } +const dockerfileSyntaxEnv = "BASHBREW_BUILDKIT_SYNTAX" + +func dockerBuildxBuild(tag string, file string, context io.Reader, platform string) error { + dockerfileSyntax, ok := os.LookupEnv(dockerfileSyntaxEnv) + if !ok { + return fmt.Errorf("missing %q", dockerfileSyntaxEnv) + } + + args := []string{ + "buildx", + "build", + "--progress", "plain", + "--build-arg", "BUILDKIT_SYNTAX=" + dockerfileSyntax, + "--tag", tag, + "--file", file, + } + if platform != "" { + args = append(args, "--platform", platform) + } + args = append(args, "-") + + cmd := exec.Command("docker", args...) + cmd.Stdin = context + if debugFlag { + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + fmt.Printf("$ docker %q\n", args) + return cmd.Run() + } else { + buf := &bytes.Buffer{} + cmd.Stdout = buf + cmd.Stderr = buf + err := cmd.Run() + if err != nil { + err = cli.NewMultiError(err, fmt.Errorf(`docker %q output:%s`, args, "\n"+buf.String())) + } + return err + } +} + func dockerTag(tag1 string, tag2 string) error { if debugFlag { fmt.Printf("$ docker tag %q %q\n", tag1, tag2) diff --git a/manifest/rfc2822.go b/manifest/rfc2822.go index 2e962b6e..c94cb992 100644 --- a/manifest/rfc2822.go +++ b/manifest/rfc2822.go @@ -43,6 +43,7 @@ type Manifest2822Entry struct { GitCommit string Directory string File string + Builder string // architecture-specific versions of the above fields ArchValues map[string]string @@ -93,7 +94,7 @@ func (entry Manifest2822Entry) Clone() Manifest2822Entry { func (entry *Manifest2822Entry) SeedArchValues() { for field, val := range entry.Paragraph.Values { - if strings.HasSuffix(field, "-GitRepo") || strings.HasSuffix(field, "-GitFetch") || strings.HasSuffix(field, "-GitCommit") || strings.HasSuffix(field, "-Directory") || strings.HasSuffix(field, "-File") { + if strings.HasSuffix(field, "-GitRepo") || strings.HasSuffix(field, "-GitFetch") || strings.HasSuffix(field, "-GitCommit") || strings.HasSuffix(field, "-Directory") || strings.HasSuffix(field, "-File") || strings.HasSuffix(field, "-Builder") { entry.ArchValues[field] = val } } @@ -142,7 +143,7 @@ func (a Manifest2822Entry) SameBuildArtifacts(b Manifest2822Entry) bool { } } - return a.ArchitecturesString() == b.ArchitecturesString() && a.GitRepo == b.GitRepo && a.GitFetch == b.GitFetch && a.GitCommit == b.GitCommit && a.Directory == b.Directory && a.File == b.File && a.ConstraintsString() == b.ConstraintsString() + return a.ArchitecturesString() == b.ArchitecturesString() && a.GitRepo == b.GitRepo && a.GitFetch == b.GitFetch && a.GitCommit == b.GitCommit && a.Directory == b.Directory && a.File == b.File && a.Builder == b.Builder && a.ConstraintsString() == b.ConstraintsString() } // returns a list of architecture-specific fields in an Entry @@ -187,6 +188,9 @@ func (entry Manifest2822Entry) ClearDefaults(defaults Manifest2822Entry) Manifes if entry.File == defaults.File { entry.File = "" } + if entry.Builder == defaults.Builder { + entry.Builder = "" + } for _, key := range defaults.archFields() { if defaults.ArchValues[key] == entry.ArchValues[key] { delete(entry.ArchValues, key) @@ -227,6 +231,9 @@ func (entry Manifest2822Entry) String() string { if str := entry.File; str != "" { ret = append(ret, "File: "+str) } + if str := entry.Builder; str != "" { + ret = append(ret, "Builder: "+str) + } for _, key := range entry.archFields() { ret = append(ret, key+": "+entry.ArchValues[key]) } @@ -300,6 +307,13 @@ func (entry Manifest2822Entry) ArchFile(arch string) string { return entry.File } +func (entry Manifest2822Entry) ArchBuilder(arch string) string { + if val, ok := entry.ArchValues[arch+"-Builder"]; ok && val != "" { + return val + } + return entry.Builder +} + func (entry Manifest2822Entry) HasTag(tag string) bool { for _, existingTag := range entry.Tags { if tag == existingTag {