sigs.k8s.io/kubebuilder/v3@v3.14.0/pkg/plugins/golang/repository.go (about)

     1  /*
     2  Copyright 2017 The Kubernetes 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 golang
    18  
    19  import (
    20  	"encoding/json"
    21  	"fmt"
    22  	"os"
    23  	"os/exec"
    24  
    25  	"golang.org/x/tools/go/packages"
    26  )
    27  
    28  // module and goMod arg just enough of the output of `go mod edit -json` for our purposes
    29  type goMod struct {
    30  	Module module
    31  }
    32  type module struct {
    33  	Path string
    34  }
    35  
    36  // findGoModulePath finds the path of the current module, if present.
    37  func findGoModulePath() (string, error) {
    38  	cmd := exec.Command("go", "mod", "edit", "-json")
    39  	cmd.Env = append(cmd.Env, os.Environ()...)
    40  	out, err := cmd.Output()
    41  	if err != nil {
    42  		if exitErr, isExitErr := err.(*exec.ExitError); isExitErr {
    43  			err = fmt.Errorf("%s", string(exitErr.Stderr))
    44  		}
    45  		return "", err
    46  	}
    47  	mod := goMod{}
    48  	if err := json.Unmarshal(out, &mod); err != nil {
    49  		return "", err
    50  	}
    51  	return mod.Module.Path, nil
    52  }
    53  
    54  // FindCurrentRepo attempts to determine the current repository
    55  // though a combination of go/packages and `go mod` commands/tricks.
    56  func FindCurrentRepo() (string, error) {
    57  	// easiest case: existing go module
    58  	path, err := findGoModulePath()
    59  	if err == nil {
    60  		return path, nil
    61  	}
    62  
    63  	// next, check if we've got a package in the current directory
    64  	pkgCfg := &packages.Config{
    65  		Mode: packages.NeedName, // name gives us path as well
    66  	}
    67  	pkgs, err := packages.Load(pkgCfg, ".")
    68  	// NB(directxman12): when go modules are off and we're outside GOPATH and
    69  	// we don't otherwise have a good guess packages.Load will fabricate a path
    70  	// that consists of `_/absolute/path/to/current/directory`.  We shouldn't
    71  	// use that when it happens.
    72  	if err == nil && len(pkgs) > 0 && len(pkgs[0].PkgPath) > 0 && pkgs[0].PkgPath[0] != '_' {
    73  		return pkgs[0].PkgPath, nil
    74  	}
    75  
    76  	// otherwise, try to get `go mod init` to guess for us -- it's pretty good
    77  	cmd := exec.Command("go", "mod", "init")
    78  	cmd.Env = append(cmd.Env, os.Environ()...)
    79  	if _, err := cmd.Output(); err != nil {
    80  		if exitErr, isExitErr := err.(*exec.ExitError); isExitErr {
    81  			err = fmt.Errorf("%s", string(exitErr.Stderr))
    82  		}
    83  		// give up, let the user figure it out
    84  		return "", fmt.Errorf("could not determine repository path from module data, "+
    85  			"package data, or by initializing a module: %v", err)
    86  	}
    87  	//nolint:errcheck
    88  	defer os.Remove("go.mod") // clean up after ourselves
    89  	return findGoModulePath()
    90  }