Skip to content

Commit 9da00a8

Browse files
committed
fix(build): enhance module replacement handling for v2+ versions and implement local cloning
1 parent cfc3e54 commit 9da00a8

File tree

1 file changed

+71
-3
lines changed

1 file changed

+71
-3
lines changed

internal/cli/build.go

Lines changed: 71 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -200,9 +200,27 @@ func createMainGoFile(b *buildingMaterial) (err error) {
200200

201201
// downloadGoModFile run go mod commands to download dependencies
202202
func downloadGoModFile(b *buildingMaterial) (err error) {
203-
// If user specify a module replacement, use it. Otherwise, use the latest version.
204-
if len(b.answerModuleReplacement) > 0 {
205-
replacement := fmt.Sprintf("%s=%s", "github.com/apache/answer", b.answerModuleReplacement)
203+
answerReplacement := b.answerModuleReplacement
204+
205+
// If no replacement specified and current binary is v2+, auto-determine replacement.
206+
// This is needed because go mod tidy would otherwise resolve github.com/apache/answer
207+
// to the latest v1.x version, causing v2+ features (e.g. AI/MCP) to disappear.
208+
if len(answerReplacement) == 0 && b.originalAnswerInfo.Version != "" {
209+
ver, verErr := semver.NewVersion(strings.TrimPrefix(b.originalAnswerInfo.Version, "v"))
210+
if verErr == nil && ver.Major() >= 2 {
211+
answerReplacement = fmt.Sprintf("github.com/apache/answer@%s", b.originalAnswerInfo.Version)
212+
}
213+
}
214+
215+
if len(answerReplacement) > 0 {
216+
// For v2+ versioned module paths (e.g. github.com/apache/answer@v2.0.0),
217+
// go mod tidy rejects the version because the module path lacks a /v2 suffix.
218+
// Work around this by cloning the repo locally and using a local path replacement.
219+
localPath, resolveErr := resolveAnswerModuleReplacement(answerReplacement, b.tmpDir)
220+
if resolveErr != nil {
221+
return resolveErr
222+
}
223+
replacement := fmt.Sprintf("%s=%s", "github.com/apache/answer", localPath)
206224
err = b.newExecCmd("go", "mod", "edit", "-replace", replacement).Run()
207225
if err != nil {
208226
return err
@@ -221,6 +239,56 @@ func downloadGoModFile(b *buildingMaterial) (err error) {
221239
return
222240
}
223241

242+
// resolveAnswerModuleReplacement resolves the ANSWER_MODULE value to a usable local path or
243+
// remote replacement string. For v2+ versioned module paths (e.g. github.com/apache/answer@v2.0.0),
244+
// Go module system rejects the version because the module path has no /v2 suffix. In that case
245+
// the repository is cloned locally and the local path is returned instead.
246+
func resolveAnswerModuleReplacement(replacement, tmpDir string) (string, error) {
247+
// Local paths can be used as-is.
248+
if strings.HasPrefix(replacement, "/") || strings.HasPrefix(replacement, "./") || strings.HasPrefix(replacement, "../") {
249+
return replacement, nil
250+
}
251+
252+
// Parse module@version format.
253+
moduleName, version, hasVersion := strings.Cut(replacement, "@")
254+
if !hasVersion {
255+
return replacement, nil
256+
}
257+
258+
// Only handle v2+ versions on module paths without the /vN suffix.
259+
ver, err := semver.StrictNewVersion(strings.TrimPrefix(version, "v"))
260+
if err != nil || ver.Major() < 2 {
261+
return replacement, nil
262+
}
263+
if strings.HasSuffix(moduleName, fmt.Sprintf("/v%d", ver.Major())) {
264+
return replacement, nil
265+
}
266+
267+
// Clone the repo to a local directory and return its path.
268+
gitURL := "https://" + moduleName
269+
tag := "v" + strings.TrimPrefix(version, "v")
270+
localPath := filepath.Join(filepath.Dir(tmpDir), fmt.Sprintf("answer_src_%s", strings.ReplaceAll(version, ".", "_")))
271+
272+
if _, statErr := os.Stat(localPath); statErr == nil {
273+
fmt.Printf("[build] using cached local clone at %s\n", localPath)
274+
return localPath, nil
275+
}
276+
277+
fmt.Printf("[build] v2+ module detected, cloning %s@%s to local path %s...\n", moduleName, version, localPath)
278+
cloneCmd := exec.Command("git", "clone", "--depth=1", "--branch="+tag, gitURL, localPath)
279+
cloneCmd.Stdout = os.Stdout
280+
cloneCmd.Stderr = os.Stderr
281+
if err = cloneCmd.Run(); err != nil {
282+
return "", fmt.Errorf(
283+
"failed to clone %s@%s: %w\nTip: set ANSWER_MODULE to a local checkout path instead, e.g. ANSWER_MODULE=/path/to/answer",
284+
moduleName, version, err,
285+
)
286+
}
287+
288+
fmt.Printf("[build] successfully cloned to %s\n", localPath)
289+
return localPath, nil
290+
}
291+
224292
// movePluginToVendor move plugin to vendor dir
225293
// Traverse the plugins, and if the plugin path is not github.com/apache/answer-plugins, move the contents of the current plugin to the vendor/github.com/apache/answer-plugins/ directory.
226294
func movePluginToVendor(b *buildingMaterial) (err error) {

0 commit comments

Comments
 (0)