mirror of
https://github.com/caddyserver/xcaddy.git
synced 2024-09-07 21:19:17 +01:00
Support replacements and customizing the core version; update readme
This commit is contained in:
parent
681037f5e1
commit
fd8e902223
5 changed files with 151 additions and 25 deletions
29
README.md
29
README.md
|
@ -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.
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
73
cmd/xcaddy/main_test.go
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue