Skip to content

Commit

Permalink
Support replacements and customizing the core version; update readme
Browse files Browse the repository at this point in the history
  • Loading branch information
mholt committed Apr 22, 2020
1 parent 681037f commit fd8e902
Show file tree
Hide file tree
Showing 5 changed files with 151 additions and 25 deletions.
29 changes: 21 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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 <caddy_version>
$ xcaddy build [<caddy_version>]
[--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.
- `--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/[email protected]

$ xcaddy build \
--with github.com/caddyserver/ntlm-transport=../../my-fork

$ xcaddy build \
--with github.com/caddyserver/[email protected]=../../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.

Expand Down
7 changes: 4 additions & 3 deletions builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
}
Expand Down Expand Up @@ -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
Expand Down
65 changes: 52 additions & 13 deletions cmd/xcaddy/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand All @@ -41,34 +43,37 @@ 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":
if i == len(args)-1 {
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 {
Expand All @@ -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" {
Expand All @@ -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 {
Expand All @@ -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
Expand Down Expand Up @@ -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
}
73 changes: 73 additions & 0 deletions cmd/xcaddy/main_test.go
Original file line number Diff line number Diff line change
@@ -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)
}
}
}
2 changes: 1 addition & 1 deletion environment.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down

0 comments on commit fd8e902

Please sign in to comment.