Skip to content

Comments

feat(run): add support for custom runner defined in gox.mod#2517

Open
joeykchen wants to merge 2 commits intogoplus:mainfrom
joeykchen:feature/add-run-directive
Open

feat(run): add support for custom runner defined in gox.mod#2517
joeykchen wants to merge 2 commits intogoplus:mainfrom
joeykchen:feature/add-run-directive

Conversation

@joeykchen
Copy link

Add support for 'run' directive in gop.mod files that allows projects to specify a custom runner command. When 'xgo run' is executed and the project has a 'run' directive, it will invoke the specified runner instead of the standard Go run.

Example gop.mod:
run github.com/goplus/spx/v2/cmd/spxrun

Changes:

  • Add getCustomRunner() function that loads the target project's gop.mod using xgomod.Load() to check for run directive (minimal impact on existing xgo logic)
  • Add runCustomRunner() function that:
    • Extracts command name from package path
    • Looks up the command in PATH and GOPATH/bin
    • Auto-installs the command if not found
    • Passes project directory and arguments to the runner

This enables SPX and other classfile projects to define their own specialized run workflows.

@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello @joeykchen, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request introduces a flexible mechanism for projects to define and execute custom runtime environments. By allowing a 'run' directive in 'gop.mod', 'xgo run' can now delegate execution to a specified external command, enabling specialized workflows like those required by SPX and other classfile projects. This enhances the extensibility of the 'xgo' tool without altering its core 'run' logic for projects without a custom runner.

Highlights

  • Custom Runner Support: Adds support for a 'run' directive in 'gop.mod' files, allowing projects to specify a custom runner command that 'xgo run' will invoke instead of the standard Go run.
  • getCustomRunner Function: Introduces 'getCustomRunner()' to load 'gop.mod' and check for the 'run' directive, minimizing impact on existing 'xgo' logic.
  • runCustomRunner Function: Implements 'runCustomRunner()' which extracts the command name, looks it up in PATH and GOPATH/bin, auto-installs it if not found, and passes project directory and arguments to the runner.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a feature to support custom runners defined in gop.mod. While the implementation is a good start for local projects, I've identified several issues that need addressing. My review highlights a critical flaw in handling remote packages (PkgPathProj) which renders the feature incomplete for that use case. Additionally, there's a bug related to loading configuration from the wrong directory, fragile logic for installing and locating the custom runner binary, and a concern about build reproducibility due to fetching the @latest version of the runner. I've provided specific comments and code suggestions to resolve these issues.

Comment on lines 104 to 106
if absDir, err := filepath.Abs(projDir); err == nil {
projDir = absDir
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The error from filepath.Abs is silently ignored. If filepath.Abs fails (for example, if os.Getwd() fails), projDir will remain a relative path. This could lead to unexpected behavior later, as subsequent logic might rely on an absolute path. It's safer to handle this potential error, at least by logging it.

if absDir, err := filepath.Abs(projDir); err == nil {
			projDir = absDir
		} else {
			log.Warnln("failed to get absolute path for", projDir, ":", err)
		}

if _, err := os.Stat(cmdPath); os.IsNotExist(err) {
// Try to install the command
fmt.Printf("Installing custom runner: %s\n", cmdPkgPath)
installCmd := exec.Command("go", "install", cmdPkgPath+"@latest")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The custom runner is installed using go install <pkg>@latest. This can lead to non-reproducible builds, as running the same command at different times might fetch different versions of the runner, potentially with breaking changes. This also introduces a potential security risk if a new malicious version is published. It is recommended to remove @latest and let go install resolve the version based on the project's dependencies, which provides more stable and secure behavior.

installCmd := exec.Command("go", "install", cmdPkgPath)

func runCustomRunner(cmdPkgPath, projDir string, args []string) error {
// Extract the command name from package path
// e.g., "github.com/goplus/spx/v2/cmd/spxrun" -> "spxrun"
cmdName := filepath.Base(cmdPkgPath)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Security: Command injection vulnerability

cmdPkgPath comes from user-controlled gop.mod file without validation. Malicious values like "../../../evil/cmd" or "cmd;malicious" could lead to executing unintended binaries.

Recommendation: Validate package path format:

func validatePackagePath(pkgPath string) error {
    // Valid Go package path pattern
    validPattern := `^[a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?(\.[a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?)*(/[a-zA-Z0-9._-]+)+$`
    matched, _ := regexp.MatchString(validPattern, pkgPath)
    if !matched {
        return fmt.Errorf("invalid package path format: %s", pkgPath)
    }
    // Reject dangerous characters
    if strings.ContainsAny(pkgPath, ";|&`$\n\r") {
        return fmt.Errorf("package path contains forbidden characters")
    }
    return nil
}

Call before using cmdPkgPath.

// Try GOPATH/bin
gopath := os.Getenv("GOPATH")
if gopath == "" {
gopath = filepath.Join(os.Getenv("HOME"), "go")
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cross-platform issue: Windows compatibility

Using $HOME environment variable doesn't work on Windows. Use os.UserHomeDir() instead:

userHome, err := os.UserHomeDir()
if err != nil {
    return fmt.Errorf("failed to determine user home directory: %w", err)
}
gopath = filepath.Join(userHome, "go")

Also consider that Windows binaries have .exe extension.

Comment on lines 139 to 141
mod, err := xgomod.Load(projDir)
if err != nil {
return ""
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Error handling: Silent failure obscures issues

Returning empty string on any error makes debugging difficult. Users won't know if their gop.mod is malformed vs. simply missing.

Suggestion: Log errors in debug mode:

mod, err := xgomod.Load(projDir)
if err != nil {
    if *flagDebug {
        log.Printf("Failed to load gop.mod from %s: %v", projDir, err)
    }
    return ""
}

Comment on lines 177 to 179
// Build the command arguments
cmdArgs := []string{"--path", projDir}
cmdArgs = append(cmdArgs, args...)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Documentation: Undocumented CLI contract

The --path flag convention is hardcoded but not documented. Custom runners MUST implement this interface, which should be clearly specified.

Suggestion: Update the runCustomRunner function documentation to specify:

  • Custom runners must accept --path <dir> flag
  • Additional args are passed through
  • Working directory is set to projDir
  • stdin/stdout/stderr are inherited

Comment on lines 135 to 136
// getCustomRunner checks if the target project directory has a custom runner
// defined in its gop.mod file. Returns the runner command package path or empty string.
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Documentation: Missing gop.mod format specification

The documentation doesn't explain the expected gop.mod structure or provide an example.

Suggestion: Enhance documentation:

// getCustomRunner checks if the target project directory has a custom runner
// defined in its gop.mod file. It looks for the opt.run.cmd field containing
// a Go package import path.
//
// Example gop.mod:
//   [opt.run]
//   cmd = "github.com/goplus/spx/v2/cmd/spxrun"
//
// Returns the runner command package path (Go import path) or empty string if
// no custom runner is configured or if the gop.mod file cannot be loaded.

@xgopilot
Copy link

xgopilot bot commented Dec 18, 2025

Code Review Summary

This PR adds a useful feature for custom runners, but has critical issues that must be fixed before merging:

Critical Issues (Must Fix)

  1. Bug: Command path not updated after installation - will fail for newly installed runners
  2. Security: Command injection vulnerability via unvalidated package paths
  3. Security: Automatic code execution without user consent creates supply chain attack vector

Important Issues (Should Fix)

  1. Cross-platform: Windows compatibility (HOME env var, .exe extension)
  2. Error handling: Silent failures make debugging difficult
  3. Documentation: Missing CLI contract and gop.mod format specification

The implementation shows good code structure, but the security concerns and critical bug need immediate attention. Please address the inline comments before merging.

@joeykchen joeykchen mentioned this pull request Dec 22, 2025
@joeykchen joeykchen force-pushed the feature/add-run-directive branch 3 times, most recently from 5753165 to d9b7c24 Compare December 24, 2025 04:30
Fix:builds a Go command package  if not found
feat(run): add support for custom runner defined in gop.mod

Add support for 'run' directive in gop.mod files that allows projects
to specify a custom runner command. When 'xgo run' is executed and the
project has a 'run' directive, it will invoke the specified runner
instead of the standard Go run.

Example gop.mod:
  run github.com/goplus/spx/v2/cmd/spxrun

Changes:
- Add getCustomRunner() function that loads the target project's gop.mod
  using xgomod.Load() to check for run directive (minimal impact on
  existing xgo logic)
- Add runCustomRunner() function that:
  - Extracts command name from package path
  - Looks up the command in PATH and GOPATH/bin
  - Auto-installs the command if not found
  - Passes project directory and arguments to the runner

This enables SPX and other classfile projects to define their own
specialized run workflows.
@joeykchen joeykchen force-pushed the feature/add-run-directive branch from d9b7c24 to 32e31db Compare December 26, 2025 12:08
@xushiwei xushiwei changed the title feat(run): add support for custom runner defined in gop.mod feat(run): add support for custom runner defined in gox.mod Dec 28, 2025
@codecov
Copy link

codecov bot commented Dec 28, 2025

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 93.98%. Comparing base (18c1cfd) to head (0e22e2c).
⚠️ Report is 5 commits behind head on main.

Additional details and impacted files
@@           Coverage Diff           @@
##             main    #2517   +/-   ##
=======================================
  Coverage   93.98%   93.98%           
=======================================
  Files          34       34           
  Lines        9613     9613           
=======================================
  Hits         9035     9035           
  Misses        413      413           
  Partials      165      165           

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant