You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@answer.apache.org by li...@apache.org on 2026/04/15 08:15:50 UTC

(answer) 02/02: fix(build): enhance module replacement handling for v2+ versions and implement local cloning

This is an automated email from the ASF dual-hosted git repository.

linkinstar pushed a commit to branch fix/2.0.1/chat
in repository https://gitbox.apache.org/repos/asf/answer.git

commit 9da00a807db70ebd9033dc8298707d0a1e68ce52
Author: LinkinStars <li...@foxmail.com>
AuthorDate: Wed Apr 15 16:15:13 2026 +0800

    fix(build): enhance module replacement handling for v2+ versions and implement local cloning
---
 internal/cli/build.go | 74 ++++++++++++++++++++++++++++++++++++++++++++++++---
 1 file changed, 71 insertions(+), 3 deletions(-)

diff --git a/internal/cli/build.go b/internal/cli/build.go
index efc876e3..483219d7 100644
--- a/internal/cli/build.go
+++ b/internal/cli/build.go
@@ -200,9 +200,27 @@ func createMainGoFile(b *buildingMaterial) (err error) {
 
 // downloadGoModFile run go mod commands to download dependencies
 func downloadGoModFile(b *buildingMaterial) (err error) {
-	// If user specify a module replacement, use it. Otherwise, use the latest version.
-	if len(b.answerModuleReplacement) > 0 {
-		replacement := fmt.Sprintf("%s=%s", "github.com/apache/answer", b.answerModuleReplacement)
+	answerReplacement := b.answerModuleReplacement
+
+	// If no replacement specified and current binary is v2+, auto-determine replacement.
+	// This is needed because go mod tidy would otherwise resolve github.com/apache/answer
+	// to the latest v1.x version, causing v2+ features (e.g. AI/MCP) to disappear.
+	if len(answerReplacement) == 0 && b.originalAnswerInfo.Version != "" {
+		ver, verErr := semver.NewVersion(strings.TrimPrefix(b.originalAnswerInfo.Version, "v"))
+		if verErr == nil && ver.Major() >= 2 {
+			answerReplacement = fmt.Sprintf("github.com/apache/answer@%s", b.originalAnswerInfo.Version)
+		}
+	}
+
+	if len(answerReplacement) > 0 {
+		// For v2+ versioned module paths (e.g. github.com/apache/answer@v2.0.0),
+		// go mod tidy rejects the version because the module path lacks a /v2 suffix.
+		// Work around this by cloning the repo locally and using a local path replacement.
+		localPath, resolveErr := resolveAnswerModuleReplacement(answerReplacement, b.tmpDir)
+		if resolveErr != nil {
+			return resolveErr
+		}
+		replacement := fmt.Sprintf("%s=%s", "github.com/apache/answer", localPath)
 		err = b.newExecCmd("go", "mod", "edit", "-replace", replacement).Run()
 		if err != nil {
 			return err
@@ -221,6 +239,56 @@ func downloadGoModFile(b *buildingMaterial) (err error) {
 	return
 }
 
+// resolveAnswerModuleReplacement resolves the ANSWER_MODULE value to a usable local path or
+// remote replacement string. For v2+ versioned module paths (e.g. github.com/apache/answer@v2.0.0),
+// Go module system rejects the version because the module path has no /v2 suffix. In that case
+// the repository is cloned locally and the local path is returned instead.
+func resolveAnswerModuleReplacement(replacement, tmpDir string) (string, error) {
+	// Local paths can be used as-is.
+	if strings.HasPrefix(replacement, "/") || strings.HasPrefix(replacement, "./") || strings.HasPrefix(replacement, "../") {
+		return replacement, nil
+	}
+
+	// Parse module@version format.
+	moduleName, version, hasVersion := strings.Cut(replacement, "@")
+	if !hasVersion {
+		return replacement, nil
+	}
+
+	// Only handle v2+ versions on module paths without the /vN suffix.
+	ver, err := semver.StrictNewVersion(strings.TrimPrefix(version, "v"))
+	if err != nil || ver.Major() < 2 {
+		return replacement, nil
+	}
+	if strings.HasSuffix(moduleName, fmt.Sprintf("/v%d", ver.Major())) {
+		return replacement, nil
+	}
+
+	// Clone the repo to a local directory and return its path.
+	gitURL := "https://" + moduleName
+	tag := "v" + strings.TrimPrefix(version, "v")
+	localPath := filepath.Join(filepath.Dir(tmpDir), fmt.Sprintf("answer_src_%s", strings.ReplaceAll(version, ".", "_")))
+
+	if _, statErr := os.Stat(localPath); statErr == nil {
+		fmt.Printf("[build] using cached local clone at %s\n", localPath)
+		return localPath, nil
+	}
+
+	fmt.Printf("[build] v2+ module detected, cloning %s@%s to local path %s...\n", moduleName, version, localPath)
+	cloneCmd := exec.Command("git", "clone", "--depth=1", "--branch="+tag, gitURL, localPath)
+	cloneCmd.Stdout = os.Stdout
+	cloneCmd.Stderr = os.Stderr
+	if err = cloneCmd.Run(); err != nil {
+		return "", fmt.Errorf(
+			"failed to clone %s@%s: %w\nTip: set ANSWER_MODULE to a local checkout path instead, e.g. ANSWER_MODULE=/path/to/answer",
+			moduleName, version, err,
+		)
+	}
+
+	fmt.Printf("[build] successfully cloned to %s\n", localPath)
+	return localPath, nil
+}
+
 // movePluginToVendor move plugin to vendor dir
 // 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.
 func movePluginToVendor(b *buildingMaterial) (err error) {