Skip to content

Commit

Permalink
Support go.mod replace version pinning (#27)
Browse files Browse the repository at this point in the history
* support go.mod replace version pinning

* use string replace instead of regex

* Minor cleanups

Co-authored-by: Matthew Holt <[email protected]>
  • Loading branch information
jpughcs and mholt authored Aug 10, 2020
1 parent 1fec981 commit b7fd102
Show file tree
Hide file tree
Showing 4 changed files with 120 additions and 17 deletions.
25 changes: 23 additions & 2 deletions builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -140,13 +140,34 @@ type Dependency struct {
Version string `json:"version,omitempty"`
}

// ReplacementPath represents an old or new path component
// within a Go module replacement directive.
type ReplacementPath string

// Param reformats a go.mod replace directive to be
// compatible with the `go mod edit` command.
func (r ReplacementPath) Param() string {
return strings.Replace(string(r), " ", "@", 1)
}

func (r ReplacementPath) String() string { return string(r) }

// Replace represents a Go module replacement.
type Replace struct {
// The import path of the module being replaced.
Old string `json:"old,omitempty"`
Old ReplacementPath `json:"old,omitempty"`

// The path to the replacement module.
New string `json:"new,omitempty"`
New ReplacementPath `json:"new,omitempty"`
}

// NewReplace creates a new instance of Replace provided old and
// new Go module paths
func NewReplace(old, new string) Replace {
return Replace{
Old: ReplacementPath(old),
New: ReplacementPath(new),
}
}

// newTempFolder creates a new folder in a temporary location.
Expand Down
88 changes: 88 additions & 0 deletions builder_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
// Copyright 2020 Matthew Holt
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package xcaddy

import (
"fmt"
"reflect"
"testing"
)

func TestReplacementPath_Param(t *testing.T) {
tests := []struct {
name string
r ReplacementPath
want string
}{
{
"Empty",
ReplacementPath(""),
"",
},
{
"ModulePath",
ReplacementPath("github.com/x/y"),
"github.com/x/y",
},
{
"ModulePath Version Pinned",
ReplacementPath("github.com/x/y v0.0.0-20200101000000-xxxxxxxxxxxx"),
"github.com/x/[email protected]",
},
{
"FilePath",
ReplacementPath("/x/y/z"),
"/x/y/z",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
fmt.Println(tt.r.Param())
if got := tt.r.Param(); got != tt.want {
t.Errorf("ReplacementPath.Param() = %v, want %v", got, tt.want)
}
})
}
}

func TestNewReplace(t *testing.T) {
type args struct {
old string
new string
}
tests := []struct {
name string
args args
want Replace
}{
{
"Empty",
args{"", ""},
Replace{"", ""},
},
{
"Constructor",
args{"a", "b"},
Replace{"a", "b"},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := NewReplace(tt.args.old, tt.args.new); !reflect.DeepEqual(got, tt.want) {
t.Errorf("NewReplace() = %v, want %v", got, tt.want)
}
})
}
}
18 changes: 6 additions & 12 deletions cmd/xcaddy/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,10 +74,7 @@ func runBuild(ctx context.Context, args []string) error {
Version: ver,
})
if repl != "" {
replacements = append(replacements, xcaddy.Replace{
Old: mod,
New: repl,
})
replacements = append(replacements, xcaddy.NewReplace(mod, repl))
}

case "--output":
Expand Down Expand Up @@ -168,10 +165,7 @@ func runDev(ctx context.Context, args []string) error {
// make sure the module being developed is replaced
// so that the local copy is used
replacements := []xcaddy.Replace{
{
Old: currentModule,
New: moduleDir,
},
xcaddy.NewReplace(currentModule, moduleDir),
}

// replace directives only apply to the top-level/main go.mod,
Expand All @@ -189,10 +183,10 @@ func runDev(ctx context.Context, args []string) error {
if len(parts) != 2 || parts[0] == "" || parts[1] == "" {
continue
}
replacements = append(replacements, xcaddy.Replace{
Old: strings.TrimSpace(parts[0]),
New: strings.TrimSpace(parts[1]),
})
replacements = append(replacements, xcaddy.NewReplace(
strings.TrimSpace(parts[0]),
strings.TrimSpace(parts[1]),
))
}

// reconcile remaining path segments; for example if a module foo/a
Expand Down
6 changes: 3 additions & 3 deletions environment.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,14 +109,14 @@ func (b Builder) newEnvironment(ctx context.Context) (*environment, error) {
// specify module replacements before pinning versions
replaced := make(map[string]string)
for _, r := range b.Replacements {
log.Printf("[INFO] Replace %s => %s", r.Old, r.New)
log.Printf("[INFO] Replace %s => %s", r.Old.String(), r.New.String())
cmd := env.newCommand("go", "mod", "edit",
"-replace", fmt.Sprintf("%s=%s", r.Old, r.New))
"-replace", fmt.Sprintf("%s=%s", r.Old.Param(), r.New.Param()))
err := env.runCommand(ctx, cmd, 10*time.Second)
if err != nil {
return nil, err
}
replaced[r.Old] = r.New
replaced[r.Old.String()] = r.New.String()
}

// check for early abort
Expand Down

0 comments on commit b7fd102

Please sign in to comment.