github.com/GoogleContainerTools/skaffold@v1.39.18/pkg/skaffold/build/bazel/dependencies.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  	"os"
    24  	"os/exec"
    25  	"path/filepath"
    26  	"strings"
    27  	"sync"
    28  	"time"
    29  
    30  	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/output/log"
    31  	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest"
    32  	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/util"
    33  )
    34  
    35  const sourceQuery = "kind('source file', deps('%[1]s')) union buildfiles(deps('%[1]s'))"
    36  
    37  func query(target string) string {
    38  	return fmt.Sprintf(sourceQuery, target)
    39  }
    40  
    41  var once sync.Once
    42  
    43  // GetDependencies finds the sources dependencies for the given bazel artifact.
    44  // All paths are relative to the workspace.
    45  func GetDependencies(ctx context.Context, dir string, a *latest.BazelArtifact) ([]string, error) {
    46  	timer := time.NewTimer(1 * time.Second)
    47  	defer timer.Stop()
    48  
    49  	go func() {
    50  		<-timer.C
    51  		once.Do(func() { log.Entry(ctx).Warn("Retrieving Bazel dependencies can take a long time the first time") })
    52  	}()
    53  
    54  	topLevelFolder, err := findWorkspace(dir)
    55  	if err != nil {
    56  		return nil, fmt.Errorf("unable to find the WORKSPACE file: %w", err)
    57  	}
    58  
    59  	absDir, err := filepath.Abs(dir)
    60  	if err != nil {
    61  		return nil, fmt.Errorf("unable to find absolute path for %q: %w", dir, err)
    62  	}
    63  
    64  	cmd := exec.CommandContext(ctx, "bazel", "query", query(a.BuildTarget), "--noimplicit_deps", "--order_output=no", "--output=label")
    65  	cmd.Dir = dir
    66  	stdout, err := util.RunCmdOut(ctx, cmd)
    67  	if err != nil {
    68  		return nil, fmt.Errorf("getting bazel dependencies: %w", err)
    69  	}
    70  
    71  	labels := strings.Split(string(stdout), "\n")
    72  	var deps []string
    73  	for _, l := range labels {
    74  		if strings.HasPrefix(l, "@") {
    75  			continue
    76  		}
    77  		if strings.HasPrefix(l, "//external") {
    78  			continue
    79  		}
    80  		if l == "" {
    81  			continue
    82  		}
    83  
    84  		rel, err := filepath.Rel(absDir, filepath.Join(topLevelFolder, depToPath(l)))
    85  		if err != nil {
    86  			return nil, fmt.Errorf("unable to find absolute path: %w", err)
    87  		}
    88  		deps = append(deps, rel)
    89  	}
    90  
    91  	rel, err := filepath.Rel(absDir, filepath.Join(topLevelFolder, "WORKSPACE"))
    92  	if err != nil {
    93  		return nil, fmt.Errorf("unable to find absolute path: %w", err)
    94  	}
    95  	deps = append(deps, rel)
    96  
    97  	log.Entry(ctx).Debugf("Found dependencies for bazel artifact: %v", deps)
    98  
    99  	return deps, nil
   100  }
   101  
   102  func depToPath(dep string) string {
   103  	return strings.TrimPrefix(strings.Replace(strings.TrimPrefix(dep, "//"), ":", "/", 1), "/")
   104  }
   105  
   106  func findWorkspace(workingDir string) (string, error) {
   107  	dir, err := filepath.Abs(workingDir)
   108  	if err != nil {
   109  		return "", fmt.Errorf("invalid working dir: %w", err)
   110  	}
   111  
   112  	for {
   113  		if _, err := os.Stat(filepath.Join(dir, "WORKSPACE")); err == nil {
   114  			return dir, nil
   115  		}
   116  
   117  		parent := filepath.Dir(dir)
   118  		if parent == dir {
   119  			return "", errors.New("no WORKSPACE file found")
   120  		}
   121  		dir = parent
   122  	}
   123  }