github.com/GoogleContainerTools/skaffold@v1.39.18/pkg/skaffold/build/bazel/build.go (about)

     1  /*
     2  Copyright 2019 The Skaffold Authors
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package bazel
    18  
    19  import (
    20  	"context"
    21  	"errors"
    22  	"fmt"
    23  	"io"
    24  	"os"
    25  	"os/exec"
    26  	"path/filepath"
    27  	"strings"
    28  
    29  	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/docker"
    30  	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/output"
    31  	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/output/log"
    32  	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/platform"
    33  	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest"
    34  	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/util"
    35  )
    36  
    37  // Build builds an artifact with Bazel.
    38  func (b *Builder) Build(ctx context.Context, out io.Writer, artifact *latest.Artifact, tag string, matcher platform.Matcher) (string, error) {
    39  	// TODO: Implement building multi-platform images
    40  	if matcher.IsMultiPlatform() {
    41  		log.Entry(ctx).Warnf("multiple target platforms %q found for artifact %q. Skaffold doesn't yet support multi-platform builds for the bazel builder. Consider specifying a single target platform explicitly. See https://skaffold.dev/docs/pipeline-stages/builders/#cross-platform-build-support", matcher.String(), artifact.ImageName)
    42  	}
    43  
    44  	a := artifact.ArtifactType.BazelArtifact
    45  
    46  	tarPath, err := b.buildTar(ctx, out, artifact.Workspace, a)
    47  	if err != nil {
    48  		return "", err
    49  	}
    50  
    51  	if b.pushImages {
    52  		return docker.Push(tarPath, tag, b.cfg, nil)
    53  	}
    54  	return b.loadImage(ctx, out, tarPath, a, tag)
    55  }
    56  
    57  func (b *Builder) SupportedPlatforms() platform.Matcher { return platform.All }
    58  
    59  func (b *Builder) buildTar(ctx context.Context, out io.Writer, workspace string, a *latest.BazelArtifact) (string, error) {
    60  	if !strings.HasSuffix(a.BuildTarget, ".tar") {
    61  		return "", errors.New("the bazel build target should end with .tar, see https://github.com/bazelbuild/rules_docker#using-with-docker-locally")
    62  	}
    63  
    64  	args := []string{"build"}
    65  	args = append(args, a.BuildArgs...)
    66  	args = append(args, a.BuildTarget)
    67  
    68  	if output.IsColorable(out) {
    69  		args = append(args, "--color=yes")
    70  	} else {
    71  		args = append(args, "--color=no")
    72  	}
    73  
    74  	// FIXME: is it possible to apply b.skipTests?
    75  	cmd := exec.CommandContext(ctx, "bazel", args...)
    76  	cmd.Dir = workspace
    77  	cmd.Stdout = out
    78  	cmd.Stderr = out
    79  	if err := util.RunCmd(ctx, cmd); err != nil {
    80  		return "", fmt.Errorf("running command: %w", err)
    81  	}
    82  
    83  	tarPath, err := bazelTarPath(ctx, workspace, a)
    84  	if err != nil {
    85  		return "", fmt.Errorf("getting bazel tar path: %w", err)
    86  	}
    87  
    88  	return tarPath, nil
    89  }
    90  
    91  func (b *Builder) loadImage(ctx context.Context, out io.Writer, tarPath string, a *latest.BazelArtifact, tag string) (string, error) {
    92  	imageTar, err := os.Open(tarPath)
    93  	if err != nil {
    94  		return "", fmt.Errorf("opening image tarball: %w", err)
    95  	}
    96  	defer imageTar.Close()
    97  
    98  	bazelTag := buildImageTag(a.BuildTarget)
    99  	imageID, err := b.localDocker.Load(ctx, out, imageTar, bazelTag)
   100  	if err != nil {
   101  		return "", fmt.Errorf("loading image into docker daemon: %w", err)
   102  	}
   103  
   104  	if err := b.localDocker.Tag(ctx, imageID, tag); err != nil {
   105  		return "", fmt.Errorf("tagging the image: %w", err)
   106  	}
   107  
   108  	return imageID, nil
   109  }
   110  
   111  func bazelTarPath(ctx context.Context, workspace string, a *latest.BazelArtifact) (string, error) {
   112  	args := []string{
   113  		"cquery",
   114  		a.BuildTarget,
   115  		"--output",
   116  		"starlark",
   117  		// Bazel docker .tar output targets have a single output file, which is
   118  		// the path to the image tar.
   119  		"--starlark:expr",
   120  		"target.files.to_list()[0].path",
   121  	}
   122  	args = append(args, a.BuildArgs...)
   123  
   124  	cmd := exec.CommandContext(ctx, "bazel", args...)
   125  	cmd.Dir = workspace
   126  
   127  	buf, err := util.RunCmdOut(ctx, cmd)
   128  	if err != nil {
   129  		return "", err
   130  	}
   131  
   132  	targetPath := strings.TrimSpace(string(buf))
   133  
   134  	cmd = exec.CommandContext(ctx, "bazel", "info", "execution_root")
   135  	cmd.Dir = workspace
   136  
   137  	buf, err = util.RunCmdOut(ctx, cmd)
   138  	if err != nil {
   139  		return "", err
   140  	}
   141  
   142  	execRoot := strings.TrimSpace(string(buf))
   143  
   144  	return filepath.Join(execRoot, targetPath), nil
   145  }
   146  
   147  func trimTarget(buildTarget string) string {
   148  	// TODO(r2d4): strip off leading //:, bad
   149  	trimmedTarget := strings.TrimPrefix(buildTarget, "//")
   150  	// Useful if root target "//:target"
   151  	trimmedTarget = strings.TrimPrefix(trimmedTarget, ":")
   152  
   153  	return trimmedTarget
   154  }
   155  
   156  func buildImageTag(buildTarget string) string {
   157  	imageTag := trimTarget(buildTarget)
   158  	imageTag = strings.TrimPrefix(imageTag, ":")
   159  
   160  	// TODO(r2d4): strip off trailing .tar, even worse
   161  	imageTag = strings.TrimSuffix(imageTag, ".tar")
   162  
   163  	if strings.Contains(imageTag, ":") {
   164  		return fmt.Sprintf("bazel/%s", imageTag)
   165  	}
   166  
   167  	return fmt.Sprintf("bazel:%s", imageTag)
   168  }