Skip to content

Instantly share code, notes, and snippets.

@mrkvn
Last active March 15, 2024 11:36
Show Gist options
  • Save mrkvn/f503ed64b30640835f45f49de7314ce0 to your computer and use it in GitHub Desktop.
Save mrkvn/f503ed64b30640835f45f49de7314ce0 to your computer and use it in GitHub Desktop.
Run Python executable in Go binary
package main
import (
"bytes"
"embed"
"io"
"io/fs"
"log"
"os"
"os/exec"
"path/filepath"
)
//go:embed all:dist/main
var embeddedFiles embed.FS
func main() {
if err := extractAndRunExecutable(); err != nil {
log.Fatalf("Failed to extract and run the executable: %v", err)
}
}
func extractDirectory(dst string, fsys embed.FS, root string) error {
return fs.WalkDir(fsys, root, func(path string, d fs.DirEntry, err error) error {
if err != nil {
return err
}
relPath, err := filepath.Rel(root, path)
if err != nil {
log.Printf("Error walking through extracted directory: %v", err)
return err
}
// Construct the target path using the correctly obtained relPath
targetPath := filepath.Join(dst, relPath)
if d.IsDir() {
// Create directory
return os.MkdirAll(targetPath, 0755)
} else {
// Extract file
inFile, err := fsys.Open(path)
if err != nil {
return err
}
defer inFile.Close()
outFile, err := os.Create(targetPath)
if err != nil {
return err
}
defer outFile.Close()
_, err = io.Copy(outFile, inFile)
return err
}
})
}
func extractAndRunExecutable() error {
// Create a temporary directory
tmpDir, err := os.MkdirTemp("", "tempdir")
if err != nil {
return err
}
defer os.RemoveAll(tmpDir) // Clean up after execution
// Extract the embedded directory structure
if err := extractDirectory(tmpDir, embeddedFiles, "dist/main"); err != nil {
return err
}
// Make the main executable file executable
binaryPath := filepath.Join(tmpDir, "main")
if err := os.Chmod(binaryPath, 0755); err != nil {
return err
}
// Execute the binary with its dependencies available
cmd := exec.Command(binaryPath)
cmd.Dir = tmpDir // Set the working directory to the temp directory
// Capture both stdout and stderr
var stdout, stderr bytes.Buffer
cmd.Stdout = &stdout
cmd.Stderr = &stderr
// Run the command
err = cmd.Run()
if err != nil {
log.Printf("Failed to execute the command: %v", err)
log.Printf("stdout: %s", stdout.String())
log.Printf("stderr: %s", stderr.String())
return err
}
log.Printf("Command stdout: %s", stdout.String())
// return cmd.Run()
return nil
}
@mrkvn
Copy link
Author

mrkvn commented Mar 15, 2024

Tested in MacOS M2 Pro

  • Create main.py
print('from python')
  • Create executable
pyinstaller -w main.py
  • Replace the Python alias file in the dist/main/_internal/ with the actual file.
  • Copy the dist/main/ and paste it besides main.go.

This main.go file, effectively embed the dist/main/ in the Go binary and to run the Python executable, it copies those directories/files/dependencies in a temp directory, then execute the Python executable afterwards.

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