github.com/joey-fossa/fossa-cli@v0.7.34-0.20190708193710-569f1e8679f0/analyzers/buck/buck.go (about)

     1  // Package buck implements the analyzer for Buck. https://buckbuild.com
     2  //
     3  // A `BuildTarget` in Buck is defined as a Build Target by Buck which is in
     4  // in the format of `//src/build:target`. Buck defines this as a string used to
     5  // identify a Build Rule.
     6  //
     7  // Understanding target patterns is helpful to understand how buck projects are built
     8  // and managed. Documentation can be found on Bazel's website here:
     9  // https://docs.bazel.build/versions/master/guide.html#target-patterns
    10  //
    11  // This package is implemented by externally calling the `buck` build tool.
    12  //
    13  // FAQ
    14  //
    15  // 1. Why is analyzing manifest files not a supported strategy as it is for other tools?
    16  //
    17  // `.buckconfig` can be used to discover cells but the `repository` field which
    18  // defines cells is not required .
    19  // `BUCK` files are written in Skylark (a dialect of Python) and are impossible to statically analyze.
    20  // `buck audit` provides json formatted data for dependency and input information.
    21  package buck
    22  
    23  import (
    24  	"os"
    25  	"path"
    26  	"strings"
    27  
    28  	"github.com/apex/log"
    29  	"github.com/pkg/errors"
    30  	"gopkg.in/go-ini/ini.v1"
    31  
    32  	"github.com/fossas/fossa-cli/buildtools/buck"
    33  	"github.com/fossas/fossa-cli/exec"
    34  	"github.com/fossas/fossa-cli/files"
    35  	"github.com/fossas/fossa-cli/graph"
    36  	"github.com/fossas/fossa-cli/module"
    37  	"github.com/fossas/fossa-cli/pkg"
    38  )
    39  
    40  // Analyzer defines a Buck analyzer.
    41  type Analyzer struct {
    42  	Module module.Module
    43  	Upload bool
    44  	Setup  buck.Buck
    45  }
    46  
    47  // New constructs a new Buck analyzer from a module.
    48  func New(module module.Module) (*Analyzer, error) {
    49  	buckCmd, _, err := exec.Which("--version", os.Getenv("FOSSA_BUCK_CMD"), "./buckw", "buck")
    50  	if err != nil {
    51  		return nil, err
    52  	}
    53  
    54  	analyzer := Analyzer{
    55  		Module: module,
    56  		Upload: true,
    57  		Setup:  buck.New(module.BuildTarget, buckCmd),
    58  	}
    59  	return &analyzer, nil
    60  }
    61  
    62  // Clean is not implemented.
    63  func (a *Analyzer) Clean() error {
    64  	return nil
    65  }
    66  
    67  // Build is not implemented.
    68  func (a *Analyzer) Build() error {
    69  	return nil
    70  }
    71  
    72  // IsBuilt is not implemented.
    73  func (a *Analyzer) IsBuilt() (bool, error) {
    74  	return true, nil
    75  }
    76  
    77  // Analyze analyzes a buck build target and its dependencies.
    78  func (a *Analyzer) Analyze() (graph.Deps, error) {
    79  	return a.Setup.Deps(a.Upload)
    80  }
    81  
    82  // Discover is used to operate Discovery with a custom `buck` command.
    83  func Discover(dir string, opts map[string]interface{}) ([]module.Module, error) {
    84  	buckCmd, _, err := exec.Which("--version", os.Getenv("FOSSA_BUCK_CMD"), "./buckw", "buck")
    85  	if err != nil {
    86  		return nil, err
    87  	}
    88  
    89  	return DiscoverWithCommand(dir, opts, buck.NewCmd(buckCmd))
    90  }
    91  
    92  // DiscoverWithCommand finds a Buck project by first looking for a ".buckconfig" file and then a "BUCK" file.
    93  // 1. ".buckconfig" file is found and we know that that we are at the root of a Buck cell.
    94  // 	a. Attempt to find user defined aliases in .buckconfig.
    95  // 	b. No aliases, run `buck targets //` to find all local targets.
    96  // 2. "BUCK" file is found.
    97  // 	a. Run `buck targets <directory>:` to find all local targets.
    98  func DiscoverWithCommand(dir string, opts map[string]interface{}, buckCommand func(string, ...string) (string, error)) ([]module.Module, error) {
    99  	var moduleList []module.Module
   100  	buckConfig, err := files.Exists(dir, ".buckconfig")
   101  
   102  	if err == nil && buckConfig {
   103  		file, err := ini.Load(path.Join(dir, ".buckconfig"))
   104  		if err != nil {
   105  			return nil, errors.Errorf("Unable to read `.buckconfig`: %s", err)
   106  		}
   107  
   108  		aliases, err := file.GetSection("alias")
   109  		if err == nil && len(aliases.Keys()) > 0 {
   110  			for name, target := range aliases.KeysHash() {
   111  				moduleList = append(moduleList, newModule(name, target, dir))
   112  			}
   113  			return moduleList, nil
   114  		}
   115  
   116  		out, err := buckCommand("targets", "//")
   117  		if err != nil {
   118  			return nil, err
   119  		}
   120  
   121  		buckTargetList := strings.Split(strings.TrimSpace(out), "\n")
   122  		for _, target := range buckTargetList {
   123  			moduleList = append(moduleList, newModule(target, target, dir))
   124  		}
   125  		return moduleList, nil
   126  	}
   127  
   128  	buckFile, err := files.Exists(dir, "BUCK")
   129  	if err == nil && buckFile {
   130  		wd, err := os.Getwd()
   131  		if err != nil {
   132  			return nil, errors.Errorf("Cannot get working directory: %s", err)
   133  		}
   134  
   135  		buckRoot, err := buckCommand("root")
   136  		if err != nil {
   137  			return nil, err
   138  		}
   139  
   140  		// Condition the current directory to the format of "parent/child:" from the root directory.
   141  		buckDirectory := strings.TrimPrefix(wd, strings.TrimSpace(buckRoot)+"/")
   142  		out, err := buckCommand("targets", buckDirectory+":")
   143  		if err != nil {
   144  			return nil, err
   145  		}
   146  
   147  		targets := strings.Split(out, "\n")
   148  		for _, target := range targets {
   149  			if len(target) > 0 {
   150  				moduleList = append(moduleList, newModule(target, target, dir))
   151  			}
   152  		}
   153  	}
   154  	return moduleList, nil
   155  }
   156  
   157  func newModule(name, target, dir string) module.Module {
   158  	log.WithFields(log.Fields{
   159  		"path": dir,
   160  		"name": name,
   161  	}).Debug("constructing Buck module")
   162  
   163  	return module.Module{
   164  		Name:        name,
   165  		Type:        pkg.Buck,
   166  		BuildTarget: target,
   167  		Dir:         dir,
   168  	}
   169  }