Support replacements and customizing the core version; update readme

This commit is contained in:
Matthew Holt 2020-04-22 16:29:57 -06:00
parent 681037f5e1
commit fd8e902223
No known key found for this signature in database
GPG key ID: 2A349DD577D586A5
5 changed files with 151 additions and 25 deletions

View file

@ -13,8 +13,8 @@ Stay updated, be aware of changes, and please submit feedback! Thanks!
## Requirements ## Requirements
- Go installed - [Go installed](https://golang.org/doc/install)
- Go modules enabled - [Go modules](https://github.com/golang/go/wiki/Modules) enabled
## Command usage ## Command usage
@ -30,31 +30,44 @@ Install the `xcaddy` command with:
$ go get -u github.com/caddyserver/xcaddy/cmd/xcaddy $ 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 ### Custom builds
Syntax: Syntax:
``` ```
$ xcaddy build <caddy_version> $ xcaddy build [<caddy_version>]
[--output <file>] [--output <file>]
[--with <module[@version]>...] [--with <module[@version][=replacement]>...]
``` ```
- `<caddy_version>` is the core Caddy version to build (required, for now). - `<caddy_version>` is the core Caddy version to build; defaults to `CADDY_VERSION` env variable or latest.
- `--output` changes the output file. - `--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`. - `--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 ```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 --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 ### 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. The binary will be built and run from the current directory, then cleaned up.

View file

@ -42,9 +42,6 @@ type Builder struct {
// Build builds Caddy at the configured version with the // Build builds Caddy at the configured version with the
// configured plugins and plops down a binary at outputFile. // configured plugins and plops down a binary at outputFile.
func (b Builder) Build(ctx context.Context, outputFile string) error { func (b Builder) Build(ctx context.Context, outputFile string) error {
if b.CaddyVersion == "" {
return fmt.Errorf("CaddyVersion must be set")
}
if outputFile == "" { if outputFile == "" {
return fmt.Errorf("output file path is required") 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. // 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 // 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. // 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) { func versionedModulePath(modulePath, moduleVersion string) (string, error) {
if moduleVersion == "" {
return modulePath, nil
}
ver, err := semver.StrictNewVersion(strings.TrimPrefix(moduleVersion, "v")) ver, err := semver.StrictNewVersion(strings.TrimPrefix(moduleVersion, "v"))
if err != nil { if err != nil {
// only return the error if we know they were trying to use a semantic version // only return the error if we know they were trying to use a semantic version

View file

@ -29,6 +29,8 @@ import (
"github.com/caddyserver/xcaddy" "github.com/caddyserver/xcaddy"
) )
var caddyVersion = os.Getenv("CADDY_VERSION")
func main() { func main() {
ctx, cancel := context.WithCancel(context.Background()) ctx, cancel := context.WithCancel(context.Background())
defer cancel() defer cancel()
@ -41,16 +43,16 @@ func main() {
return return
} }
// TODO: the caddy version needs to be settable by the user... maybe an env var? if err := runDev(ctx, os.Args[1:]); err != nil {
if err := runDev(ctx, "v2.0.0-rc.3", os.Args[1:]); err != nil {
log.Fatalf("[ERROR] %v", err) log.Fatalf("[ERROR] %v", err)
} }
} }
func runBuild(ctx context.Context, args []string) error { func runBuild(ctx context.Context, args []string) error {
// parse the command line args... rather primitively // parse the command line args... rather primitively
var caddyVersion, output string var argCaddyVersion, output string
var plugins []xcaddy.Dependency var plugins []xcaddy.Dependency
var replacements []xcaddy.Replace
for i := 0; i < len(args); i++ { for i := 0; i < len(args); i++ {
switch args[i] { switch args[i] {
case "--with": case "--with":
@ -58,17 +60,20 @@ func runBuild(ctx context.Context, args []string) error {
return fmt.Errorf("expected value after --with flag") return fmt.Errorf("expected value after --with flag")
} }
i++ i++
var mod, ver string mod, ver, repl, err := splitWith(args[i])
arg := args[i] if err != nil {
parts := strings.SplitN(arg, "@", 2) return err
mod = parts[0]
if len(parts) == 2 {
ver = parts[1]
} }
plugins = append(plugins, xcaddy.Dependency{ plugins = append(plugins, xcaddy.Dependency{
ModulePath: mod, ModulePath: mod,
Version: ver, Version: ver,
}) })
if repl != "" {
replacements = append(replacements, xcaddy.Replace{
Old: mod,
New: repl,
})
}
case "--output": case "--output":
if i == len(args)-1 { if i == len(args)-1 {
@ -78,13 +83,18 @@ func runBuild(ctx context.Context, args []string) error {
output = args[i] output = args[i]
default: default:
if caddyVersion != "" { if argCaddyVersion != "" {
return fmt.Errorf("missing flag; caddy version already set at %s", caddyVersion) 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 // ensure an output file is always specified
if output == "" { if output == "" {
if runtime.GOOS == "windows" { if runtime.GOOS == "windows" {
@ -98,6 +108,7 @@ func runBuild(ctx context.Context, args []string) error {
builder := xcaddy.Builder{ builder := xcaddy.Builder{
CaddyVersion: caddyVersion, CaddyVersion: caddyVersion,
Plugins: plugins, Plugins: plugins,
Replacements: replacements,
} }
err := builder.Build(ctx, output) err := builder.Build(ctx, output)
if err != nil { if err != nil {
@ -121,7 +132,7 @@ func runBuild(ctx context.Context, args []string) error {
return nil return nil
} }
func runDev(ctx context.Context, caddyVersion string, args []string) error { func runDev(ctx context.Context, args []string) error {
const binOutput = "./caddy" const binOutput = "./caddy"
// get current/main module name // get current/main module name
@ -220,3 +231,31 @@ func trapSignals(ctx context.Context, cancel context.CancelFunc) {
return 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
}

73
cmd/xcaddy/main_test.go Normal file
View file

@ -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)
}
}
}

View file

@ -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 // pin versions by populating go.mod, first for Caddy itself and then plugins
log.Println("[INFO] Pinning versions") log.Println("[INFO] Pinning versions")
err = env.execGoGet(ctx, caddyModulePath, b.CaddyVersion) err = env.execGoGet(ctx, caddyModulePath, env.caddyVersion)
if err != nil { if err != nil {
return nil, err return nil, err
} }