Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 43 additions & 14 deletions plugins/builder/environment.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,15 @@ func newBuildEnvironment(b *Builder) (*buildEnvironment, error) {
return env, nil
}

// ensureModuleRequired adds a replaced module to go.mod as a requirement.
// Replaced modules need an explicit require directive with a placeholder version,
// otherwise Go reports "replaced but not required" errors during compilation.
func (env *buildEnvironment) ensureModuleRequired(ctx context.Context, pkg string) error {
// Strip version suffix if present, use placeholder version for replaced modules.
mod, _, _ := strings.Cut(pkg, "@")
return env.execGoMod(ctx, "edit", "-require", mod+"@v0.0.0")
}

func (env *buildEnvironment) CreateModFile(ctx context.Context, opts *BuildOptions) error {
var err error
// Create go.mod.
Expand All @@ -89,47 +98,67 @@ func (env *buildEnvironment) CreateModFile(ctx context.Context, opts *BuildOptio
return err
}

// Replace requested modules.
// Apply requested module replacements.
for o, n := range opts.ModReplace {
err = env.execGoMod(ctx, "edit", "-replace", o+"="+n)
if err != nil {
return err
}
}

// Download the requested dependencies directly.
// Download dependencies.
if opts.NoCache {
domains := make([]string, len(opts.Plugins))
for i := 0; i < len(domains); i++ {
domains[i] = opts.Plugins[i].Path
// Set GONOSUMDB and GONOPROXY for modules that should not be cached or verified.
domains := make([]string, 0, len(opts.Plugins)+1)
for _, p := range opts.Plugins {
domains = append(domains, p.Path)
}
if opts.CorePkg.Path != "" {
domains = append(domains, opts.CorePkg.Path)
}
noproxy := strings.Join(domains, ",")
env.env = append(env.env, "GONOSUMDB="+noproxy, "GONOPROXY="+noproxy)
}

// Download core.
err = env.execGoGet(ctx, opts.CorePkg.String())
if err != nil {
// Download core package.
// Replaced modules need an explicit require directive first, otherwise Go
// reports "replaced but not required" errors during compilation.
if _, ok := opts.ModReplace[opts.CorePkg.Path]; ok {
if err = env.ensureModuleRequired(ctx, opts.CorePkg.String()); err != nil {
return err
}
}
if err = env.execGoGet(ctx, opts.CorePkg.String()); err != nil {
return err
}

// Download plugins.
nextPlugin:
for _, p := range opts.Plugins {
// Do not get plugins of module subpath.
// Skip plugins that are subpaths of replaced modules.
isSubpath := false
for repl := range opts.ModReplace {
if p.Path != repl && strings.HasPrefix(p.Path, repl) {
continue nextPlugin
isSubpath = true
break
}
}
err = env.execGoGet(ctx, p.String())
if err != nil {
if isSubpath {
continue
}

// Replaced modules need an explicit require directive before go get.
if _, ok := opts.ModReplace[p.Path]; ok {
if err = env.ensureModuleRequired(ctx, p.String()); err != nil {
return err
}
}
if err = env.execGoGet(ctx, p.String()); err != nil {
return err
}
}
// @todo update all but with fixed versions if requested

return err
return nil
}

func (env *buildEnvironment) Filepath(s string) string {
Expand Down
22 changes: 22 additions & 0 deletions test/testdata/build/replace.txtar
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Launchr Replace Module Test
#
# Validates that replaced modules are properly required in go.mod.
# Regression test for: "replaced but not required" error when using --replace flag.
# When a module is replaced, `go mod edit -require` must be used to add it
# to go.mod, otherwise Go compilation fails.

env HOME=$REAL_HOME
env APP_PLUGIN_LOCAL=example.com/genaction@v1.1.1
env APP_PLUGIN_LOCAL_PATH=$REPO_PATH/test/plugins/genaction

# Test 1: Build with only replaced plugin (no external plugins).
# This is the minimal reproduction of the "replaced but not required" bug.
! exists launchr
exec launchr build -r $CORE_PKG=$REPO_PATH -p $APP_PLUGIN_LOCAL -r $APP_PLUGIN_LOCAL=$APP_PLUGIN_LOCAL_PATH
! stderr .
exists launchr

# Test 2: Verify the built binary works.
exec ./launchr genaction:example
stdout 'hello world'
! stderr .