Skip to content

Commit

Permalink
Migrated xcaddy to cobra
Browse files Browse the repository at this point in the history
  • Loading branch information
armadi1809 committed Jun 25, 2024
1 parent ff8268a commit 383f894
Show file tree
Hide file tree
Showing 5 changed files with 295 additions and 236 deletions.
110 changes: 110 additions & 0 deletions cmd/cobra.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
package xcaddycmd

import (
"fmt"
"log"
"os"
"os/exec"

"github.com/caddyserver/xcaddy"
"github.com/caddyserver/xcaddy/internal/utils"
"github.com/spf13/cobra"
)

var rootCmd = &cobra.Command{
Use: "xcaddy",
Long: "",
SilenceUsage: true,
Version: xcaddyVersion(),
RunE: func(cmd *cobra.Command, args []string) error {
binOutput := getCaddyOutputFile()

// get current/main module name and the root directory of the main module
//
// make sure the module being developed is replaced
// so that the local copy is used
//
// replace directives only apply to the top-level/main go.mod,
// and since this tool is a carry-through for the user's actual
// go.mod, we need to transfer their replace directives through
// to the one we're making
execCmd := exec.Command(utils.GetGo(), "list", "-mod=readonly", "-m", "-json", "all")
execCmd.Stderr = os.Stderr
out, err := execCmd.Output()
if err != nil {
return fmt.Errorf("exec %v: %v: %s", cmd.Args, err, string(out))
}
currentModule, moduleDir, replacements, err := parseGoListJson(out)
if err != nil {
return fmt.Errorf("json parse error: %v", err)
}

// reconcile remaining path segments; for example if a module foo/a
// is rooted at directory path /home/foo/a, but the current directory
// is /home/foo/a/b, then the package to import should be foo/a/b
cwd, err := os.Getwd()
if err != nil {
return fmt.Errorf("unable to determine current directory: %v", err)
}
importPath := normalizeImportPath(currentModule, cwd, moduleDir)

// build caddy with this module plugged in
builder := xcaddy.Builder{
Compile: xcaddy.Compile{
Cgo: os.Getenv("CGO_ENABLED") == "1",
},
CaddyVersion: caddyVersion,
Plugins: []xcaddy.Dependency{
{PackagePath: importPath},
},
Replacements: replacements,
RaceDetector: raceDetector,
SkipBuild: skipBuild,
SkipCleanup: skipCleanup,
Debug: buildDebugOutput,
}
err = builder.Build(cmd.Context(), binOutput)
if err != nil {
return err
}

// if requested, run setcap to allow binding to low ports
err = setcapIfRequested(binOutput)
if err != nil {
return err
}

log.Printf("[INFO] Running %v\n\n", append([]string{binOutput}, args...))

execCmd = exec.Command(binOutput, args...)
execCmd.Stdin = os.Stdin
execCmd.Stdout = os.Stdout
execCmd.Stderr = os.Stderr
err = execCmd.Start()
if err != nil {
return err
}
defer func() {
if skipCleanup {
log.Printf("[INFO] Skipping cleanup as requested; leaving artifact: %s", binOutput)
return
}
err = os.Remove(binOutput)
if err != nil && !os.IsNotExist(err) {
log.Printf("[ERROR] Deleting temporary binary %s: %v", binOutput, err)
}
}()

return execCmd.Wait()
},
}

const fullDocsFooter = `Full documentation is available at:
https://github.com/caddyserver/xcaddy`

func init() {
rootCmd.SetVersionTemplate("{{.Version}}\n")
rootCmd.SetHelpTemplate(rootCmd.HelpTemplate() + "\n" + fullDocsFooter + "\n")
rootCmd.AddCommand(buildCommand)
rootCmd.AddCommand(versionCommand)
}
171 changes: 171 additions & 0 deletions cmd/commands.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
package xcaddycmd

import (
"fmt"
"log"
"os"
"os/exec"
"path/filepath"
"runtime"
"strings"

"github.com/caddyserver/xcaddy"
"github.com/caddyserver/xcaddy/internal/utils"
"github.com/spf13/cobra"
)

func init() {
buildCommand.Flags().String("with", "", "can be used multiple times to add plugins by specifying the Go module name and optionally its version, similar to go get. Module name is required, but specific version and/or local replacement are optional.")
buildCommand.Flags().String("output", "", "changes the output file.")
buildCommand.Flags().String("replace", "", "is like --with, but does not add a blank import to the code; it only writes a replace directive to go.mod, which is useful when developing on Caddy's dependencies (ones that are not Caddy modules). Try this if you got an error when using --with, like \"cannot find module providing package\".")
buildCommand.Flags().StringSlice("embed", []string{}, "can be used multiple times to embed directories into the built Caddy executable. The directory can be prefixed with a custom alias and a colon : to use it with the root directive and sub-directive.")
}

var versionCommand = &cobra.Command{
Use: "version",
Long: "Getting xcaddy version",
RunE: func(cm *cobra.Command, args []string) error {
fmt.Println(xcaddyVersion())
return nil
},
}

var buildCommand = &cobra.Command{
Use: `build [<caddy_version>]
[--output <file>]
[--with <module[@version][=replacement]>...]
[--replace <module[@version]=replacement>...]
[--embed <[alias]:path/to/dir>...]`,
Long: "",
Args: cobra.MaximumNArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
var output string
var plugins []xcaddy.Dependency
var replacements []xcaddy.Replace
var embedDir []string
var argCaddyVersion string
if len(args) > 0 {
argCaddyVersion = args[0]
}
withArg, err := cmd.Flags().GetString("with")
if err != nil {
return fmt.Errorf("unable to parse --with arguments: %s", err.Error())
}

replaceArg, err := cmd.Flags().GetString("replace")
if err != nil {
return fmt.Errorf("unable to parse --replace arguments: %s", err.Error())
}

if withArg != "" || replaceArg != "" {
mod, ver, repl, err := splitWith(withArg)
if err != nil {
return err
}
mod = strings.TrimSuffix(mod, "/") // easy to accidentally leave a trailing slash if pasting from a URL, but is invalid for Go modules
if withArg != "" {
plugins = append(plugins, xcaddy.Dependency{
PackagePath: mod,
Version: ver,
})
}

if repl != "" {
// adjust relative replacements in current working directory since our temporary module is in a different directory
if strings.HasPrefix(repl, ".") {
repl, err = filepath.Abs(repl)
if err != nil {
log.Fatalf("[FATAL] %v", err)
}
log.Printf("[INFO] Resolved relative replacement %s to %s", withArg, repl)
}
replacements = append(replacements, xcaddy.NewReplace(xcaddy.Dependency{PackagePath: mod, Version: ver}.String(), repl))
}
}

output, err = cmd.Flags().GetString("output")
if err != nil {
return fmt.Errorf("unable to parse --output arguments: %s", err.Error())
}

embedDir, err = cmd.Flags().GetStringSlice("embed")
if err != nil {
return fmt.Errorf("unable to parse --embed arguments: %s", err.Error())
}
// prefer caddy version from command line argument over env var
if argCaddyVersion != "" {
caddyVersion = argCaddyVersion
}

// ensure an output file is always specified
if output == "" {
output = getCaddyOutputFile()
}

// perform the build
builder := xcaddy.Builder{
Compile: xcaddy.Compile{
Cgo: os.Getenv("CGO_ENABLED") == "1",
},
CaddyVersion: caddyVersion,
Plugins: plugins,
Replacements: replacements,
RaceDetector: raceDetector,
SkipBuild: skipBuild,
SkipCleanup: skipCleanup,
Debug: buildDebugOutput,
BuildFlags: buildFlags,
ModFlags: modFlags,
}
for _, md := range embedDir {
if before, after, found := strings.Cut(md, ":"); found {
builder.EmbedDirs = append(builder.EmbedDirs, struct {
Dir string `json:"dir,omitempty"`
Name string `json:"name,omitempty"`
}{
after, before,
})
} else {
builder.EmbedDirs = append(builder.EmbedDirs, struct {
Dir string `json:"dir,omitempty"`
Name string `json:"name,omitempty"`
}{
before, "",
})
}
}
err = builder.Build(cmd.Root().Context(), output)
if err != nil {
log.Fatalf("[FATAL] %v", err)
}

// done if we're skipping the build
if builder.SkipBuild {
return nil
}

// if requested, run setcap to allow binding to low ports
err = setcapIfRequested(output)
if err != nil {
return err
}

// prove the build is working by printing the version
if runtime.GOOS == utils.GetGOOS() && runtime.GOARCH == utils.GetGOARCH() {
if !filepath.IsAbs(output) {
output = "." + string(filepath.Separator) + output
}
fmt.Println()
fmt.Printf("%s version\n", output)
cmd := exec.Command(output, "version")
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
err = cmd.Run()
if err != nil {
log.Fatalf("[FATAL] %v", err)
}
}

return nil
},
}
Loading

0 comments on commit 383f894

Please sign in to comment.