github.com/please-build/go-rules/tools/please_go@v0.0.0-20240319165128-ea27d6f5caba/please_go.go (about)

     1  package main
     2  
     3  import (
     4  	"log"
     5  	"os"
     6  	"path/filepath"
     7  	"runtime/debug"
     8  	"strings"
     9  
    10  	"github.com/peterebden/go-cli-init/v5/flags"
    11  
    12  	"github.com/please-build/go-rules/tools/please_go/cover"
    13  	"github.com/please-build/go-rules/tools/please_go/covervars"
    14  	"github.com/please-build/go-rules/tools/please_go/embed"
    15  	"github.com/please-build/go-rules/tools/please_go/filter"
    16  	"github.com/please-build/go-rules/tools/please_go/generate"
    17  	"github.com/please-build/go-rules/tools/please_go/goget"
    18  	"github.com/please-build/go-rules/tools/please_go/install"
    19  	"github.com/please-build/go-rules/tools/please_go/modinfo"
    20  	"github.com/please-build/go-rules/tools/please_go/packageinfo"
    21  	"github.com/please-build/go-rules/tools/please_go/test"
    22  )
    23  
    24  var opts = struct {
    25  	Usage string
    26  
    27  	Install struct {
    28  		BuildTags         []string `long:"build_tag" description:"Any build tags to apply to the build"`
    29  		SrcRoot           string   `short:"r" long:"src_root" description:"The src root of the module to inspect" default:"."`
    30  		ModuleName        string   `short:"n" long:"module_name" description:"The name of the module" required:"true"`
    31  		ImportConfig      string   `short:"i" long:"importcfg" description:"The import config for the modules dependencies" required:"true"`
    32  		LDFlags           string   `long:"ld_flags" description:"Any additional flags to apply to the C linker" env:"LDFLAGS"`
    33  		CFlags            string   `long:"c_flags" description:"Any additional flags to apply when compiling C" env:"CFLAGS"`
    34  		GoTool            string   `short:"g" long:"go_tool" description:"The location of the go binary" default:"go"`
    35  		CCTool            string   `short:"c" long:"cc_tool" description:"The c compiler to use"`
    36  		Out               string   `short:"o" long:"out" description:"The output directory to put compiled artifacts in" required:"true"`
    37  		TrimPath          string   `short:"t" long:"trim_path" description:"Removes prefix from recorded source file paths."`
    38  		PackageConfigTool string   `short:"p" long:"pkg_config_tool" env:"PKG_CONFIG_TOOL" description:"The path to the pkg config" default:"pkg-config"`
    39  		Args              struct {
    40  			Packages []string `positional-arg-name:"packages" description:"The packages to compile"`
    41  		} `positional-args:"true" required:"true"`
    42  	} `command:"install" alias:"i" description:"Compile a go module similarly to 'go install'"`
    43  	Test struct {
    44  		GoTool      string   `short:"g" long:"go_tool" description:"The location of the go binary" env:"TOOLS_GO" default:"go"`
    45  		Dir         string   `short:"d" long:"dir" description:"Directory to search for Go package files for coverage"`
    46  		Exclude     []string `short:"x" long:"exclude" default:"third_party/go" description:"Directories to exclude from search"`
    47  		Output      string   `short:"o" long:"output" description:"Output filename" required:"true"`
    48  		TestPackage string   `short:"t" long:"test_package" description:"The import path of the test package"`
    49  		Benchmark   bool     `short:"b" long:"benchmark" description:"Whether to run benchmarks instead of tests"`
    50  		External    bool     `long:"external" description:"Whether the test is external or not"`
    51  		Args        struct {
    52  			Sources []string `positional-arg-name:"sources" description:"Test source files" required:"true"`
    53  		} `positional-args:"true" required:"true"`
    54  	} `command:"testmain" alias:"t" description:"Generates a go main package to run the tests in a package."`
    55  	CoverVars struct {
    56  		ImportPath string `short:"i" long:"import_path" description:"The import path for the source files"`
    57  		Args       struct {
    58  			Sources []string `positional-arg-name:"sources" description:"Source files to generate embed config for"`
    59  		} `positional-args:"true"`
    60  	} `command:"covervars" description:"Generates coverage variable config for a set of go src files"`
    61  	Cover struct {
    62  		GoTool      string `short:"g" long:"go" default:"go" env:"TOOLS_GO" description:"Go binary to run"`
    63  		CoverTool   string `short:"t" long:"go_cover" env:"TOOLS_COVER" description:"Go coverage tool to run"`
    64  		CoverageCfg string `short:"c" long:"covcfg" required:"true" description:"Output coveragecfg file to feed into go tool compile"`
    65  		Output      string `short:"o" long:"output" required:"true" description:"File that will contain output names of modified files"`
    66  		Pkg         string `long:"pkg" env:"PKG_DIR" description:"Package that we're in within the repo"`
    67  		PkgName     string `short:"p" long:"pkg_name" hidden:"true" description:"Deprecated, has no effect"`
    68  		Args        struct {
    69  			Sources []string `positional-arg-name:"sources" required:"true" description:"Source files to generate embed config for"`
    70  		} `positional-args:"true"`
    71  	} `command:"cover" description:"Generates coverage information for a package."`
    72  	Filter struct {
    73  		Tags []string `short:"t" long:"tags" description:"Additional build tags to apply"`
    74  		Args struct {
    75  			Sources []string `positional-arg-name:"sources" description:"Source files to filter"`
    76  		} `positional-args:"true"`
    77  	} `command:"filter" alias:"f" description:"Filter go sources based on the go build tag rules."`
    78  	Embed struct {
    79  		Args struct {
    80  			Sources []string `positional-arg-name:"sources" description:"Source files to generate embed config for"`
    81  		} `positional-args:"true"`
    82  	} `command:"embed" alias:"e" description:"Generate embed config for a set of Go source files"`
    83  	PackageInfo struct {
    84  		ImportPath string            `short:"i" long:"import_path" description:"Go import path (e.g. github.com/please-build/go-rules)"`
    85  		Pkg        string            `long:"pkg" env:"PKG_DIR" description:"Package that we're in within the repo"`
    86  		ImportMap  map[string]string `short:"m" long:"import_map" description:"Existing map of imports"`
    87  		Subrepo    string            `short:"s" long:"subrepo" description:"Subrepo root that this package is within"`
    88  		Module     string            `long:"mod" description:"The module this is within, if present"`
    89  	} `command:"package_info" alias:"p" description:"Creates an info file about a Go package"`
    90  	ModuleInfo struct {
    91  		ModulePath   string   `short:"m" long:"module_path" required:"true" description:"Import path of the module in question"`
    92  		Srcs         string   `long:"srcs" env:"SRCS_SRCS" required:"true" description:"Source files of the module"`
    93  		ImportConfig string   `long:"importconfig" env:"SRCS_IC" description:"Importconfig file for locating gc export data"`
    94  		Packages     []string `short:"p" long:"packages" description:"Packages to include in the module"`
    95  	} `command:"module_info" alias:"m" description:"Creates an info file about a series of packages in a go_module"`
    96  	Generate struct {
    97  		SrcRoot          string   `short:"r" long:"src_root" description:"The src root of the module to inspect"`
    98  		ImportPath       string   `long:"import_path" description:"overrides the module's import path. If not set, the import path from the go.mod will be used.'"`
    99  		ThirdPartyFolder string   `short:"t" long:"third_part_folder" description:"The folder containing the third party subrepos" default:"third_party/go"`
   100  		ModFile          string   `long:"mod_file" description:"Path to the host repo mod file to use to resolve dependencies against (dependencies will be resolved against the module as well if it exists)"`
   101  		Module           string   `long:"module" description:"The name of the current module"`
   102  		Version          string   `long:"version" description:"The version of the current module"`
   103  		Install          []string `long:"install" description:"The packages to add to the :install alias"`
   104  		BuildTags        []string `long:"build_tag" description:"Any build tags to apply to the build"`
   105  		Subrepo          string   `long:"subrepo" description:"The subrepo root to output into"`
   106  		Args             struct {
   107  			Requirements []string `positional-arg-name:"requirements" description:"Any module requirements not included in the go.mod"`
   108  		} `positional-args:"true"`
   109  	} `command:"generate" alias:"g" description:"Generate build targets for a Go module"`
   110  	GoGet struct {
   111  		ModFile string `short:"m" long:"mod_file" description:"A go.mod file to use as a set of reuirementzs"`
   112  		Args    struct {
   113  			Requirements []string `positional-arg-name:"requirements" description:"a set of module@version pairs"`
   114  		} `positional-args:"true"`
   115  	} `command:"get" description:"Generate go_get rules"`
   116  	ModInfo struct {
   117  		GoTool     string `short:"g" long:"go" env:"TOOLS_GO" required:"true" description:"The Go tool we'll use"`
   118  		ModulePath string `short:"m" long:"module_path" description:"The path for the module being built"`
   119  		Pkg        string `short:"p" long:"package" env:"PKG_DIR" description:"The package directory within the repo"`
   120  		BuildMode  string `short:"b" long:"build_mode" default:"exe" description:"The Go build mode being used"`
   121  		Out        string `short:"o" long:"out" env:"OUT" required:"true" description:"File to write the output to"`
   122  		Write      bool   `short:"w" long:"write" hidden:"true" description:"Print this binary's own modinfo"`
   123  		CgoEnabled string `short:"c" long:"cgo_enabled" env:"CGO_ENABLED" description:"Whether cgo is enabled or not"`
   124  		GoOS       string `long:"goos" env:"OS" description:"OS we're compiling for"`
   125  		GoArch     string `long:"goarch" env:"ARCH" description:"Architecture we're compiling for"`
   126  	} `command:"modinfo" description:"Generates Go modinfo for the linter"`
   127  	GenerateModuleVersion struct {
   128  		ModulePath string `short:"m" long:"module_path" required:"true" description:"The module's path"`
   129  		Version    string `long:"version" required:"true" description:"The module's (semantic) version number"`
   130  		Validate   bool   `long:"validate" description:"Check validity of the given module import path and version number"`
   131  		Out        string `short:"o" long:"out" env:"OUT" required:"true" description:"File to write the output to"`
   132  	} `command:"generate_module_version" description:"Generates a module version file for a third-party Go module"`
   133  }{
   134  	Usage: `
   135  please-go is used by the go build rules to compile and test go modules and packages.
   136  
   137  Unlike 'go build', this tool doesn't rely on the go path or modules to find packages. Instead it takes in
   138  a go import config just like 'go tool compile/link -importcfg'.
   139  `,
   140  }
   141  
   142  var subCommands = map[string]func() int{
   143  	"install": func() int {
   144  		pleaseGoInstall := install.New(
   145  			opts.Install.BuildTags,
   146  			opts.Install.SrcRoot,
   147  			opts.Install.ModuleName,
   148  			opts.Install.ImportConfig,
   149  			opts.Install.LDFlags,
   150  			opts.Install.CFlags,
   151  			mustResolvePath(opts.Install.GoTool),
   152  			mustResolvePath(opts.Install.CCTool),
   153  			opts.Install.PackageConfigTool,
   154  			opts.Install.Out,
   155  			opts.Install.TrimPath,
   156  		)
   157  		if err := pleaseGoInstall.Install(opts.Install.Args.Packages); err != nil {
   158  			log.Fatal(err)
   159  		}
   160  		return 0
   161  	},
   162  	"testmain": func() int {
   163  		test.PleaseGoTest(opts.Test.GoTool, opts.Test.Dir, opts.Test.TestPackage, opts.Test.Output, opts.Test.Args.Sources, opts.Test.Exclude, opts.Test.Benchmark, opts.Test.External)
   164  		return 0
   165  	},
   166  	"cover": func() int {
   167  		if err := cover.WriteCoverage(opts.Cover.GoTool, opts.Cover.CoverTool, opts.Cover.CoverageCfg, opts.Cover.Output, opts.Cover.Pkg, opts.Cover.Args.Sources); err != nil {
   168  			log.Fatalf("failed to write coverage: %s", err)
   169  		}
   170  		return 0
   171  	},
   172  	"covervars": func() int {
   173  		covervars.GenCoverVars(os.Stdout, opts.CoverVars.ImportPath, opts.CoverVars.Args.Sources)
   174  		return 0
   175  	},
   176  	"filter": func() int {
   177  		filter.Filter(opts.Filter.Tags, opts.Filter.Args.Sources)
   178  		return 0
   179  	},
   180  	"embed": func() int {
   181  		if err := embed.WriteEmbedConfig(opts.Embed.Args.Sources, os.Stdout); err != nil {
   182  			log.Fatalf("failed to generate embed config: %v", err)
   183  		}
   184  		return 0
   185  	},
   186  	"generate": func() int {
   187  		gen := opts.Generate
   188  		g := generate.New(gen.SrcRoot, gen.ThirdPartyFolder, gen.ModFile, gen.Module, gen.Version, gen.Subrepo, []string{"BUILD", "BUILD.plz"}, gen.Args.Requirements, gen.Install, gen.BuildTags)
   189  		if err := g.Generate(); err != nil {
   190  			log.Fatalf("failed to generate go rules: %v", err)
   191  		}
   192  		return 0
   193  	},
   194  	"get": func() int {
   195  		if opts.GoGet.ModFile != "" {
   196  			if err := goget.GetMod(opts.GoGet.ModFile); err != nil {
   197  				log.Fatalf("failed to generate go rules: %v", err)
   198  			}
   199  			return 0
   200  		}
   201  		if err := goget.GoGet(opts.GoGet.Args.Requirements); err != nil {
   202  			log.Fatalf("failed to generate go rules: %v", err)
   203  		}
   204  		return 0
   205  	},
   206  	"package_info": func() int {
   207  		pi := opts.PackageInfo
   208  		if err := packageinfo.WritePackageInfo(pi.ImportPath, pi.Pkg, "", pi.ImportMap, nil, pi.Subrepo, pi.Module, os.Stdout); err != nil {
   209  			log.Fatalf("failed to write package info: %s", err)
   210  		}
   211  		return 0
   212  	},
   213  	"module_info": func() int {
   214  		mi := opts.ModuleInfo
   215  		if err := packageinfo.WritePackageInfo(mi.ModulePath, mi.Srcs, mi.ImportConfig, nil, mi.Packages, "", "", os.Stdout); err != nil {
   216  			log.Fatalf("failed to write module info: %s", err)
   217  		}
   218  		return 0
   219  	},
   220  	"modinfo": func() int {
   221  		mi := opts.ModInfo
   222  		if mi.Write {
   223  			info, ok := debug.ReadBuildInfo()
   224  			if !ok {
   225  				log.Fatalf("build info not available")
   226  			}
   227  			os.Stderr.Write([]byte(info.String() + "\n"))
   228  		}
   229  		if err := modinfo.WriteModInfo(mi.GoTool, mi.ModulePath, filepath.Join(mi.ModulePath, mi.Pkg), mi.BuildMode, mi.CgoEnabled, mi.GoOS, mi.GoArch, mi.Out); err != nil {
   230  			log.Fatalf("failed to write modinfo: %s", err)
   231  		}
   232  		return 0
   233  	},
   234  	"generate_module_version": func() int {
   235  		mv := opts.GenerateModuleVersion
   236  		if err := modinfo.WriteModuleVersion(mv.ModulePath, mv.Version, mv.Validate, mv.Out); err != nil {
   237  			log.Fatalf("failed to generate module version file: %v", err)
   238  		}
   239  		return 0
   240  	},
   241  }
   242  
   243  func main() {
   244  	command := flags.ParseFlagsOrDie("please-go", &opts, nil)
   245  	os.Exit(subCommands[command]())
   246  }
   247  
   248  // mustResolvePath converts a relative path to absolute if it has any separators in it.
   249  func mustResolvePath(in string) string {
   250  	if in == "" {
   251  		return in
   252  	}
   253  	if !filepath.IsAbs(in) && strings.ContainsRune(in, filepath.Separator) {
   254  		abs, err := filepath.Abs(in)
   255  		if err != nil {
   256  			log.Fatalf("Failed to make %s absolute: %s", in, err)
   257  		}
   258  		return abs
   259  	}
   260  	return in
   261  }