Skip to content

Comments

For Xgo Run#1082

Merged
joeykchen merged 1 commit intogoplus:devfrom
joeykchen:pr_xgo_run
Dec 22, 2025
Merged

For Xgo Run#1082
joeykchen merged 1 commit intogoplus:devfrom
joeykchen:pr_xgo_run

Conversation

@joeykchen
Copy link
Contributor

No description provided.

@gemini-code-assist
Copy link

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 new spxrun command designed to streamline the execution of SPX 2.0 projects. This command automates the entire project setup and run cycle, from ensuring the correct Godot runtime environment is in place to compiling project-specific Go code into dynamic libraries and launching the project within the Godot engine. This significantly simplifies the developer experience by abstracting away the complexities of runtime dependency management and build configurations.

Highlights

  • New spxrun Command: A new executable spxrun is introduced, designed to simplify the process of running SPX 2.0 projects by automating the entire setup and execution workflow.
  • Automated Runtime Management: The spxrun command automatically checks for, downloads, and sets up the required Godot runtime executable and its .pck file from GitHub releases, ensuring the correct environment is always available.
  • Integrated Build Process: It orchestrates the generation of Go code from SPX files using xgo, builds a C-shared Go dynamic library, and prepares the necessary Godot extension files, handling platform-specific requirements like building for multiple architectures on macOS.
  • gop.mod Integration: The gop.mod file is updated to include a run directive, explicitly registering spxrun as the command to execute for projects that specify a 'run' action.

🧠 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

@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 the spxrun command, a runner for SPX 2.0 projects. The implementation is well-structured, handling runtime downloads, project building, and execution. My review focuses on improving portability and adhering to security best practices. I've suggested replacing an external command dependency with a pure Go solution and adjusting file permissions to be more secure. Overall, these are solid additions to the project.

Comment on lines +531 to +537
func copyFile(src, dst string) error {
input, err := os.ReadFile(src)
if err != nil {
return err
}
return os.WriteFile(dst, input, 0755)
}

Choose a reason for hiding this comment

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

medium

The copyFile function currently sets file permissions to 0755, which makes the destination file executable by everyone. This is not always necessary and violates the principle of least privilege. For example, Go source files or configuration files copied with this function do not need to be executable.

It's better to use more restrictive permissions like 0644 for regular files. Any file that needs to be executable can have its permissions changed explicitly using os.Chmod, as is already being done for the runtime executable in ensureRuntime.

Suggested change
func copyFile(src, dst string) error {
input, err := os.ReadFile(src)
if err != nil {
return err
}
return os.WriteFile(dst, input, 0755)
}
func copyFile(src, dst string) error {
input, err := os.ReadFile(src)
if err != nil {
return err
}
return os.WriteFile(dst, input, 0644)
}

if err := downloadFile(url, tmpZip); err != nil {
return fmt.Errorf("failed to download runtime: %w", err)
}
defer os.Remove(tmpZip)
Copy link
Contributor

Choose a reason for hiding this comment

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

🔴 Critical: Resource leak from misplaced defer

The defer os.Remove(tmpZip) is placed AFTER the error check, meaning if downloadFile fails, the temporary file won't be cleaned up.

Impact: Repeated failures will accumulate temporary files in $GOPATH/bin.

Fix: Move defer immediately after successful download, or clean up explicitly on error:

if err := downloadFile(url, tmpZip); err != nil {
    os.Remove(tmpZip) // Clean up on error
    return fmt.Errorf("failed to download runtime: %w", err)
}
defer os.Remove(tmpZip)

Same issue at line 261 in downloadRuntimePck().

tidyCmd.Dir = r.GoDir
tidyCmd.Stdout = os.Stdout
tidyCmd.Stderr = os.Stderr
tidyCmd.Run() // Ignore error, may not have go.mod
Copy link
Contributor

Choose a reason for hiding this comment

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

🟠 Missing error handling

Silently ignoring ALL errors from go mod tidy makes debugging difficult. Users won't know if there's a legitimate issue vs. missing go.mod.

Recommendation: Log the error with context:

if err := tidyCmd.Run(); err != nil {
    fmt.Printf("Warning: go mod tidy failed (this may be normal if go.mod doesn't exist): %v\n", err)
}

if err != nil {
return err
}
return os.WriteFile(dst, input, 0755)
Copy link
Contributor

Choose a reason for hiding this comment

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

🟠 Security: Incorrect file permissions

The generic copyFile() makes ALL copied files executable (0755), including text configs and .pck files. Only binaries and libraries should be executable.

Fix: Preserve source file permissions:

func copyFile(src, dst string) error {
    input, err := os.ReadFile(src)
    if err != nil {
        return err
    }
    srcInfo, err := os.Stat(src)
    if err != nil {
        return err
    }
    return os.WriteFile(dst, input, srcInfo.Mode())
}

Additionally, this loads entire files into memory. For large runtime files (50-100MB), use streaming copy with io.Copy instead.

defer os.RemoveAll(tmpDir)

// Run unzip command
cmd := exec.Command("unzip", "-o", zipPath, "-d", tmpDir)
Copy link
Contributor

Choose a reason for hiding this comment

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

🔴 Security: Command injection risk + portability issue

  1. Portability: Windows users without Git Bash won't have unzip, causing cryptic failures
  2. Security: Passing file paths to shell commands without validation could enable command injection if input sources change

Recommendation: Use Go's built-in archive/zip package:

func extractFileFromZip(zipPath, fileName, destPath string) error {
    r, err := zip.OpenReader(zipPath)
    if err != nil {
        return fmt.Errorf("failed to open zip: %w", err)
    }
    defer r.Close()
    
    for _, f := range r.File {
        if f.Name == fileName || strings.HasSuffix(f.Name, fileName) {
            // Validate path to prevent traversal
            if strings.Contains(f.Name, "..") {
                return fmt.Errorf("invalid file path in zip")
            }
            
            rc, err := f.Open()
            if err != nil {
                return err
            }
            defer rc.Close()
            
            out, err := os.Create(destPath)
            if err != nil {
                return err
            }
            defer out.Close()
            
            _, err = io.Copy(out, rc)
            return err
        }
    }
    return fmt.Errorf("file %s not found in zip", fileName)
}

Comment on lines +328 to +361
// Build for each architecture
for _, arch := range archs {
libPath := r.getLibPathForArch(arch)
fmt.Printf("Building for %s/%s -> %s\n", r.GOOS, arch, libPath)

// Set environment variables
env := append(os.Environ(),
"CGO_ENABLED=1",
"GOARCH="+arch,
)

// Build command
args := []string{
"build",
"-buildmode=c-shared",
"-o", libPath,
}

cmd := exec.Command("go", args...)
cmd.Env = env
cmd.Dir = r.GoDir
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr

fmt.Printf("Running: CGO_ENABLED=1 GOARCH=%s go %s\n", arch, strings.Join(args, " "))
if err := cmd.Run(); err != nil {
return fmt.Errorf("build failed for %s: %w", arch, err)
}

// Update LibPath if this is the current architecture
if arch == r.GOARCH {
r.LibPath = libPath
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚡ Performance: Serial architecture builds

On macOS, building for arm64 and amd64 sequentially doubles build time unnecessarily. Modern Macs can handle parallel CGO builds.

Estimated improvement: 40-50% faster builds on macOS (30s instead of 60s for typical projects).

Recommendation: Use goroutines with sync.WaitGroup for parallel builds:

var wg sync.WaitGroup
errChan := make(chan error, len(archs))

for _, arch := range archs {
    wg.Add(1)
    go func(arch string) {
        defer wg.Done()
        // Build logic here
    }(arch)
}

wg.Wait()
close(errChan)

for err := range errChan {
    if err != nil {
        return err
    }
}

func downloadFile(url, dest string) error {
fmt.Printf("Downloading: %s\n", url)

resp, err := http.Get(url)
Copy link
Contributor

Choose a reason for hiding this comment

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

🔴 Security + Performance: Missing timeout and integrity verification

Critical security issue: Downloaded executables have no checksum verification. An attacker via MITM or compromised GitHub could serve malicious code.

Performance issue: No timeout means downloads can hang indefinitely.

Recommendations:

  1. Add timeout with context:
client := &http.Client{Timeout: 5 * time.Minute}
resp, err := client.Get(url)
  1. Add checksum verification (critical for security):
const RuntimeChecksums = map[string]string{
    "macos-x86_64.zip": "abc123...",
    "linux-x86_64.zip": "def456...",
}

func verifyChecksum(filepath, expectedHash string) error {
    f, err := os.Open(filepath)
    if err != nil {
        return err
    }
    defer f.Close()
    
    h := sha256.New()
    if _, err := io.Copy(h, f); err != nil {
        return err
    }
    
    actualHash := hex.EncodeToString(h.Sum(nil))
    if actualHash != expectedHash {
        return fmt.Errorf("checksum mismatch: expected %s, got %s", expectedHash, actualHash)
    }
    return nil
}

)

const (
// Version is the SPX runtime version
Copy link
Contributor

Choose a reason for hiding this comment

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

📝 Documentation: Misleading comment

This is the Godot runtime version tag, not the SPX runtime version. The implementation shows it's used in RuntimeURLBase + "spx" + Version.

Suggested fix:

// Version is the Godot runtime version tag for downloading releases
// Used in URL: https://github.com/goplus/godot/releases/download/spx{VERSION}/...
Version = "2.1.36"

Comment on lines +273 to +290
func (r *Runner) buildLibrary() error {
fmt.Println("Building dynamic library...")

// Ensure lib directory exists
if err := os.MkdirAll(r.LibDir, 0755); err != nil {
return fmt.Errorf("failed to create lib directory: %w", err)
}

// Ensure go directory exists
if err := os.MkdirAll(r.GoDir, 0755); err != nil {
return fmt.Errorf("failed to create go directory: %w", err)
}

// Always regenerate Go code from .spx files (project may have changed)
fmt.Println("Generating Go code with xgo...")
if err := r.generateGoCode(); err != nil {
return fmt.Errorf("failed to generate Go code: %w", err)
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🔧 Code Quality: Function complexity

The buildLibrary() function handles too many responsibilities (91 lines):

  1. Directory creation
  2. Go code generation
  3. File validation/copying
  4. Working directory management
  5. go mod tidy
  6. Multi-architecture compilation
  7. Library path tracking

Impact: Hard to test, debug, and maintain.

Recommendation: Break into focused functions:

func (r *Runner) buildLibrary() error {
    if err := r.prepareDirectories(); err != nil {
        return err
    }
    if err := r.generateAndCopyGoCode(); err != nil {
        return err
    }
    return r.compileLibraries()
}

@xgopilot
Copy link
Contributor

xgopilot bot commented Dec 18, 2025

Code Review Summary

Comprehensive review completed using specialized agents. The PR adds a well-structured spxrun command, but several critical issues need attention before merge.

🔴 Critical Issues (Must Fix)

  1. Resource leaks from misplaced defer statements (lines 236, 261)
  2. Security: No integrity verification for downloaded executables
  3. Security: Command injection risk via unvalidated paths in shell commands (line 550)
  4. Incorrect file permissions making non-executable files executable (line 536)

🟠 High Priority

  1. Missing HTTP timeouts (hangs on slow connections)
  2. Memory inefficiency: loading 50-100MB files entirely into RAM
  3. Missing error logging for go mod tidy

⚡ Performance Opportunities

  • Serial builds on macOS (could be 40-50% faster with parallelization)
  • Unnecessary library copies adding 100-500ms per run

✅ Positive Aspects

Well-organized code structure, good error wrapping, progress tracking for downloads, and comprehensive platform support.

See inline comments for details and suggested fixes.

@JiepengTan
Copy link
Contributor

JiepengTan commented Dec 19, 2025

@joeykchen Better to integrate it into CI (add a test in .github/actions/project-test/action.yml, use xgo run in dir test\CI directly before installing spx cmd tool) to facilitate validation of this solution during each PR. If CI passes, it's acceptable; if not, it's unacceptable.

@joeykchen
Copy link
Contributor Author

@joeykchen Better to integrate it into CI (add a test in .github/actions/project-test/action.yml, use xgo run in dir test\CI directly before installing spx cmd tool) to facilitate validation of this solution during each PR. If CI passes, it's acceptable; if not, it's unacceptable.

ok, after:
goplus/mod#125
goplus/xgo#2517

Refactor : Do Not Create A Temporary Directory And Walking It
Rename JSFuncOfWithError
Add SPX Runtime Version
Makefile Support Genarate Template
Remove xgo_autogen.go From Project Directory To Keep It Clean
Refactor Ensure GopMod
For Xgo Run
@joeykchen joeykchen merged commit 1767258 into goplus:dev Dec 22, 2025
7 checks passed
@@ -0,0 +1,7 @@
gop 1.3
Copy link
Member

Choose a reason for hiding this comment

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

rename gop 1.3 => xgo 1.3
rename gop.mod => gox.mod


"github.com/goplus/spx/v2/cmd/spxrun/runner"
)

Copy link
Member

Choose a reason for hiding this comment

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

是否还要考虑命令行参数支持,比如 -f 表示 full screen 等。

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.

3 participants