-
-
Notifications
You must be signed in to change notification settings - Fork 105
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Migrate xcaddy to Cobra #174
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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) | ||
} |
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\".") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Similar to |
||
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.") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Use |
||
} | ||
|
||
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 | ||
}, | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The
--with
flag can be specified multiple times, so this should be a string array