From fd8e9022234c1e9a1037f78df3003c1b2502c343 Mon Sep 17 00:00:00 2001 From: Matthew Holt Date: Wed, 22 Apr 2020 16:29:57 -0600 Subject: [PATCH] Support replacements and customizing the core version; update readme --- README.md | 29 +++++++++++----- builder.go | 7 ++-- cmd/xcaddy/main.go | 65 ++++++++++++++++++++++++++++-------- cmd/xcaddy/main_test.go | 73 +++++++++++++++++++++++++++++++++++++++++ environment.go | 2 +- 5 files changed, 151 insertions(+), 25 deletions(-) create mode 100644 cmd/xcaddy/main_test.go diff --git a/README.md b/README.md index 40115de..e716868 100644 --- a/README.md +++ b/README.md @@ -13,8 +13,8 @@ Stay updated, be aware of changes, and please submit feedback! Thanks! ## Requirements -- Go installed -- Go modules enabled +- [Go installed](https://golang.org/doc/install) +- [Go modules](https://github.com/golang/go/wiki/Modules) enabled ## Command usage @@ -30,31 +30,44 @@ Install the `xcaddy` command with: $ go get -u github.com/caddyserver/xcaddy/cmd/xcaddy ``` +The `xcaddy` command will use the latest version of Caddy by default. You can customize this for all invocations by setting the `CADDY_VERSION` environment variable. + +As usual with `go` command, the `xcaddy` command will pass through the `GOOS`, `GOARCH`, and `GOARM` environment variables for cross-compilation. + ### Custom builds Syntax: ``` -$ xcaddy build +$ xcaddy build [] [--output ] - [--with ...] + [--with ...] ``` -- `` is the core Caddy version to build (required, for now). +- `` is the core Caddy version to build; defaults to `CADDY_VERSION` env variable or latest. - `--output` changes the output file. - `--with` can be used multiple times to add plugins by specifying the Go module name and optionally its version, similar to `go get`. -For example: +Examples: ```bash -$ xcaddy build v2.0.0-rc.1 \ +$ xcaddy build \ + --with github.com/caddyserver/ntlm-transport + +$ xcaddy build v2.0.1 \ --with github.com/caddyserver/ntlm-transport@v0.1.0 + +$ xcaddy build \ + --with github.com/caddyserver/ntlm-transport=../../my-fork + +$ xcaddy build \ + --with github.com/caddyserver/ntlm-transport=@v0.1.0=../../my-fork ``` ### For plugin development -If you run `xcaddy` from within the folder of the Caddy plugin you're working on without the `build` subcommand described above, it will build Caddy with your current module and run it, as if you manually plugged it in and ran `go run`. +If you run `xcaddy` from within the folder of the Caddy plugin you're working on _without the `build` subcommand_, it will build Caddy with your current module and run it, as if you manually plugged it in and invoked `go run`. The binary will be built and run from the current directory, then cleaned up. diff --git a/builder.go b/builder.go index a0e8291..a6c6304 100644 --- a/builder.go +++ b/builder.go @@ -42,9 +42,6 @@ type Builder struct { // Build builds Caddy at the configured version with the // configured plugins and plops down a binary at outputFile. func (b Builder) Build(ctx context.Context, outputFile string) error { - if b.CaddyVersion == "" { - return fmt.Errorf("CaddyVersion must be set") - } if outputFile == "" { return fmt.Errorf("output file path is required") } @@ -141,7 +138,11 @@ func newTempFolder() (string, error) { // of "foo" and "v2.0.0" will return "foo/v2", for use in Go imports and go commands. // Inputs that conflict, like "foo/v2" and "v3.1.0" are an error. This function // returns the input if the moduleVersion is not a valid semantic version string. +// If moduleVersion is empty string, the input modulePath is returned without error. func versionedModulePath(modulePath, moduleVersion string) (string, error) { + if moduleVersion == "" { + return modulePath, nil + } ver, err := semver.StrictNewVersion(strings.TrimPrefix(moduleVersion, "v")) if err != nil { // only return the error if we know they were trying to use a semantic version diff --git a/cmd/xcaddy/main.go b/cmd/xcaddy/main.go index 6ffed07..71fbb46 100644 --- a/cmd/xcaddy/main.go +++ b/cmd/xcaddy/main.go @@ -29,6 +29,8 @@ import ( "github.com/caddyserver/xcaddy" ) +var caddyVersion = os.Getenv("CADDY_VERSION") + func main() { ctx, cancel := context.WithCancel(context.Background()) defer cancel() @@ -41,16 +43,16 @@ func main() { return } - // TODO: the caddy version needs to be settable by the user... maybe an env var? - if err := runDev(ctx, "v2.0.0-rc.3", os.Args[1:]); err != nil { + if err := runDev(ctx, os.Args[1:]); err != nil { log.Fatalf("[ERROR] %v", err) } } func runBuild(ctx context.Context, args []string) error { // parse the command line args... rather primitively - var caddyVersion, output string + var argCaddyVersion, output string var plugins []xcaddy.Dependency + var replacements []xcaddy.Replace for i := 0; i < len(args); i++ { switch args[i] { case "--with": @@ -58,17 +60,20 @@ func runBuild(ctx context.Context, args []string) error { return fmt.Errorf("expected value after --with flag") } i++ - var mod, ver string - arg := args[i] - parts := strings.SplitN(arg, "@", 2) - mod = parts[0] - if len(parts) == 2 { - ver = parts[1] + mod, ver, repl, err := splitWith(args[i]) + if err != nil { + return err } plugins = append(plugins, xcaddy.Dependency{ ModulePath: mod, Version: ver, }) + if repl != "" { + replacements = append(replacements, xcaddy.Replace{ + Old: mod, + New: repl, + }) + } case "--output": if i == len(args)-1 { @@ -78,13 +83,18 @@ func runBuild(ctx context.Context, args []string) error { output = args[i] default: - if caddyVersion != "" { - return fmt.Errorf("missing flag; caddy version already set at %s", caddyVersion) + if argCaddyVersion != "" { + return fmt.Errorf("missing flag; caddy version already set at %s", argCaddyVersion) } - caddyVersion = args[i] + argCaddyVersion = args[i] } } + // prefer caddy version from command line argument over env var + if argCaddyVersion != "" { + caddyVersion = argCaddyVersion + } + // ensure an output file is always specified if output == "" { if runtime.GOOS == "windows" { @@ -98,6 +108,7 @@ func runBuild(ctx context.Context, args []string) error { builder := xcaddy.Builder{ CaddyVersion: caddyVersion, Plugins: plugins, + Replacements: replacements, } err := builder.Build(ctx, output) if err != nil { @@ -121,7 +132,7 @@ func runBuild(ctx context.Context, args []string) error { return nil } -func runDev(ctx context.Context, caddyVersion string, args []string) error { +func runDev(ctx context.Context, args []string) error { const binOutput = "./caddy" // get current/main module name @@ -220,3 +231,31 @@ func trapSignals(ctx context.Context, cancel context.CancelFunc) { return } } + +func splitWith(arg string) (module, version, replace string, err error) { + const versionSplit, replaceSplit = "@", "=" + + parts := strings.SplitN(arg, versionSplit, 2) + module = parts[0] + + if len(parts) == 1 { + parts := strings.SplitN(module, replaceSplit, 2) + if len(parts) > 1 { + module = parts[0] + replace = parts[1] + } + } else { + version = parts[1] + parts := strings.SplitN(version, replaceSplit, 2) + if len(parts) > 1 { + version = parts[0] + replace = parts[1] + } + } + + if module == "" { + err = fmt.Errorf("module name is required") + } + + return +} diff --git a/cmd/xcaddy/main_test.go b/cmd/xcaddy/main_test.go new file mode 100644 index 0000000..0339fd4 --- /dev/null +++ b/cmd/xcaddy/main_test.go @@ -0,0 +1,73 @@ +package main + +import "testing" + +func TestSplitWith(t *testing.T) { + for i, tc := range []struct { + input string + expectModule string + expectVersion string + expectReplace string + expectErr bool + }{ + { + input: "module", + expectModule: "module", + }, + { + input: "module@version", + expectModule: "module", + expectVersion: "version", + }, + { + input: "module@version=replace", + expectModule: "module", + expectVersion: "version", + expectReplace: "replace", + }, + { + input: "module=replace", + expectModule: "module", + expectReplace: "replace", + }, + { + input: "=replace", + expectErr: true, + }, + { + input: "@version", + expectErr: true, + }, + { + input: "@version=replace", + expectErr: true, + }, + { + input: "", + expectErr: true, + }, + } { + actualModule, actualVersion, actualReplace, actualErr := splitWith(tc.input) + if actualModule != tc.expectModule { + t.Errorf("Test %d: Expected module '%s' but got '%s' (input=%s)", + i, tc.expectModule, actualModule, tc.input) + } + if tc.expectErr { + if actualErr == nil { + t.Errorf("Test %d: Expected error but did not get one (input='%s')", i, tc.input) + } + continue + } + if !tc.expectErr && actualErr != nil { + t.Errorf("Test %d: Expected no error but got: %s (input='%s')", i, actualErr, tc.input) + } + if actualVersion != tc.expectVersion { + t.Errorf("Test %d: Expected version '%s' but got '%s' (input='%s')", + i, tc.expectVersion, actualVersion, tc.input) + } + if actualReplace != tc.expectReplace { + t.Errorf("Test %d: Expected module '%s' but got '%s' (input='%s')", + i, tc.expectReplace, actualReplace, tc.input) + } + } +} diff --git a/environment.go b/environment.go index f751ddb..4c34700 100644 --- a/environment.go +++ b/environment.go @@ -126,7 +126,7 @@ func (b Builder) newEnvironment(ctx context.Context) (*environment, error) { // pin versions by populating go.mod, first for Caddy itself and then plugins log.Println("[INFO] Pinning versions") - err = env.execGoGet(ctx, caddyModulePath, b.CaddyVersion) + err = env.execGoGet(ctx, caddyModulePath, env.caddyVersion) if err != nil { return nil, err }