...

Source file src/github.com/bazelbuild/buildtools/wspace/workspace.go

Documentation: github.com/bazelbuild/buildtools/wspace

     1  /*
     2  Copyright 2016 Google LLC
     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      https://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 wspace provides a method to find the root of the bazel tree.
    18  package wspace
    19  
    20  import (
    21  	"io/ioutil"
    22  	"os"
    23  	"path/filepath"
    24  	"sort"
    25  	"strings"
    26  
    27  	"github.com/bazelbuild/buildtools/build"
    28  )
    29  
    30  const workspaceFile = "WORKSPACE"
    31  const buildFile = "BUILD"
    32  
    33  // IsRegularFile returns true if the path refers to a regular file after
    34  // following symlinks.
    35  func IsRegularFile(path string) bool {
    36  	path, err := filepath.EvalSymlinks(path)
    37  	if err != nil {
    38  		return false
    39  	}
    40  	info, err := os.Stat(path)
    41  	if err != nil {
    42  		return false
    43  	}
    44  	return info.Mode().IsRegular()
    45  }
    46  
    47  func isFile(fi os.FileInfo) bool {
    48  	return fi.Mode()&os.ModeType == 0
    49  }
    50  
    51  func isExecutable(fi os.FileInfo) bool {
    52  	return isFile(fi) && fi.Mode()&0100 == 0100
    53  }
    54  
    55  var repoRootFiles = map[string]func(os.FileInfo) bool{
    56  	workspaceFile:            isFile,
    57  	workspaceFile + ".bazel": isFile,
    58  	".buckconfig":            isFile,
    59  	"pants":                  isExecutable,
    60  }
    61  
    62  var packageRootFiles = map[string]func(os.FileInfo) bool{
    63  	buildFile:            isFile,
    64  	buildFile + ".bazel": isFile,
    65  }
    66  
    67  // findContextPath finds the context path inside of a WORKSPACE-rooted source tree.
    68  func findContextPath(rootDir string) (string, error) {
    69  	if rootDir == "" {
    70  		return os.Getwd()
    71  	}
    72  	return rootDir, nil
    73  }
    74  
    75  // FindWorkspaceRoot splits the current code context (the rootDir if present,
    76  // the working directory if not.) It returns the path of the directory
    77  // containing the WORKSPACE file, and the rest.
    78  func FindWorkspaceRoot(rootDir string) (root string, rest string) {
    79  	wd, err := findContextPath(rootDir)
    80  	if err != nil {
    81  		return "", ""
    82  	}
    83  	if root, err = Find(wd, repoRootFiles); err != nil {
    84  		return "", ""
    85  	}
    86  	if len(wd) == len(root) {
    87  		return root, ""
    88  	}
    89  	return root, wd[len(root)+1:]
    90  }
    91  
    92  // Find searches from the given dir and up for the file that satisfies a condition of `rootFiles`
    93  // returning the directory containing it, or an error if none found in the tree.
    94  func Find(dir string, rootFiles map[string]func(os.FileInfo) bool) (string, error) {
    95  	if dir == "" || dir == "/" || dir == "." || (len(dir) == 3 && strings.HasSuffix(dir, ":\\")) {
    96  		return "", os.ErrNotExist
    97  	}
    98  
    99  	// Sort the files to make the function deterministic
   100  	rootFilesSorted := make([]string, 0, len(rootFiles))
   101  	for file := range rootFiles {
   102  		rootFilesSorted = append(rootFilesSorted, file)
   103  	}
   104  	sort.Strings(rootFilesSorted)
   105  
   106  	for _, repoRootFile := range rootFilesSorted {
   107  		fiFunc := rootFiles[repoRootFile]
   108  		if fi, err := os.Stat(filepath.Join(dir, repoRootFile)); err == nil && fiFunc(fi) {
   109  			return dir, nil
   110  		} else if err != nil && !os.IsNotExist(err) {
   111  			return "", err
   112  		}
   113  	}
   114  	return Find(filepath.Dir(dir), rootFiles)
   115  }
   116  
   117  // FindRepoBuildFiles parses the WORKSPACE to find BUILD files for non-Bazel
   118  // external repositories, specifically those defined by one of these rules:
   119  //
   120  //	git_repository(), new_local_repository(), new_http_archive()
   121  func FindRepoBuildFiles(root string) (map[string]string, error) {
   122  	ws := filepath.Join(root, workspaceFile)
   123  	kinds := []string{
   124  		"git_repository",
   125  		"new_local_repository",
   126  		"new_git_repository",
   127  		"new_http_archive",
   128  	}
   129  	data, err := ioutil.ReadFile(ws)
   130  	if err != nil {
   131  		return nil, err
   132  	}
   133  	ast, err := build.Parse(ws, data)
   134  	if err != nil {
   135  		return nil, err
   136  	}
   137  	files := make(map[string]string)
   138  	for _, kind := range kinds {
   139  		for _, r := range ast.Rules(kind) {
   140  			buildFile := r.AttrString("build_file")
   141  			if buildFile == "" {
   142  				continue
   143  			}
   144  			buildFile = strings.Replace(buildFile, ":", "/", -1)
   145  			files[r.Name()] = filepath.Join(root, buildFile)
   146  		}
   147  	}
   148  	return files, nil
   149  }
   150  
   151  // relPath returns a path for `target` relative to `base`, but an empty string
   152  // instead of "." if the directories are equivalent, and with forward slashes.
   153  func relPath(base, target string) (string, error) {
   154  	rel, err := filepath.Rel(base, target)
   155  	if err != nil {
   156  		return "", err
   157  	}
   158  	if rel == "." {
   159  		return "", nil
   160  	}
   161  	return strings.ReplaceAll(rel, string(os.PathSeparator), "/"), nil
   162  }
   163  
   164  // SplitFilePath splits a file path into the workspace root, package name and label.
   165  // Workspace root is determined as the last directory in the file path that
   166  // contains a WORKSPACE (or WORKSPACE.bazel) file.
   167  // Package and label are always separated with forward slashes.
   168  // Returns empty strings if no WORKSPACE file is found.
   169  func SplitFilePath(filename string) (workspaceRoot, pkg, label string) {
   170  	dir := filepath.Dir(filename)
   171  	workspaceRoot, err := Find(dir, repoRootFiles)
   172  	if err != nil {
   173  		return "", "", ""
   174  	}
   175  	packageRoot, err := Find(dir, packageRootFiles)
   176  	if err != nil || !strings.HasPrefix(packageRoot, workspaceRoot) {
   177  		// No BUILD file or it's outside of the workspace. Shouldn't happen,
   178  		// but assume it's in the workspace root.
   179  		packageRoot = workspaceRoot
   180  	}
   181  	pkg, err = relPath(workspaceRoot, packageRoot)
   182  	if err != nil {
   183  		return "", "", ""
   184  	}
   185  	label, err = relPath(packageRoot, filename)
   186  	if err != nil {
   187  		return "", "", ""
   188  	}
   189  	return workspaceRoot, pkg, label
   190  }
   191  

View as plain text