diff --git a/plugins/builder/environment.go b/plugins/builder/environment.go index 61d5412..1def723 100644 --- a/plugins/builder/environment.go +++ b/plugins/builder/environment.go @@ -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. @@ -89,7 +98,7 @@ 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 { @@ -97,39 +106,59 @@ func (env *buildEnvironment) CreateModFile(ctx context.Context, opts *BuildOptio } } - // 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 { diff --git a/test/testdata/build/replace.txtar b/test/testdata/build/replace.txtar new file mode 100644 index 0000000..887cd44 --- /dev/null +++ b/test/testdata/build/replace.txtar @@ -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 .