在 Go os/exec 命令中激活 Python venv

Posted

技术标签:

【中文标题】在 Go os/exec 命令中激活 Python venv【英文标题】:Activating Python venv in Go os/exec Command 【发布时间】:2022-01-16 12:03:47 【问题描述】:

我正在尝试从 Go os/exec CommandRun 方法“激活”(或者更确切地说是伪激活)python 虚拟环境以用于其他命令执行。我知道每个命令执行实际上都是一次隔离运行,因此不会保留环境变量等,因此我一直在尝试手动重新创建激活期间发生的环境更改。

根据docs,这应该是可能的:

您不需要特别激活环境; 激活只是​​将虚拟环境的二进制目录添加到您的路径中,以便“python”调用虚拟环境的 Python 解释器,您可以运行已安装的脚本,而无需使用它们的完整路径。但是,所有安装在虚拟环境应该可以在不激活的情况下运行,并自动使用虚拟环境的 Python 运行。

但是,当我在 Go 中尝试此操作时,我无法让命令在虚拟环境中运行 - 例如,pip install requests 始终安装到全局 pip 缓存。以下是我正在使用的代码:

  func Run(cmd *exec.Cmd) (exitCode int, err error) 
    cmdErr := cmd.Run()
    if cmdErr != nil   
        exitCode, err = getExitCode(cmdErr) 
    
    return exitCode, err
  

  func getExitCode(exitError error) (rc int, err error) 
    if exitErrorOnly, ok := exitError.(*exec.ExitError); ok 
        waitStatus := exitErrorOnly.Sys().(syscall.WaitStatus)
        rc = waitStatus.ExitStatus()
     else 
        err = fmt.Errorf("could not get exit code, using default")
    
    return rc, err
  

  func main() 
    // using pre-existing venv for testing
    const venv = "C:\\Users\\acalder\\Projects\\go\\runinvenv\\venv"

    cmd := exec.Command("pip", "install", "requests")
    cmd.Stdout = os.Stdout
    cmd.Stderr = os.Stderr
    cmd.Env = append(os.Environ(),
        // these were the only ones i could see changing on 'activation'
        "VIRTUAL_ENV=" + venv,
        "PATH=" + venv + "\\Scripts;" + os.Getenv("PATH"),
    )
    exitCode, err := Run(cmd)
    fmt.Println("exitCode:", exitCode)
    fmt.Println("err:", err)
  

正如我在下面的评论中提到的那样;不幸的是,在查找PATH 时,LookPath 似乎总是使用os.Environ() 而不是cmd.Env。因此,如果您想避免将cmd.Path 指定给可执行文件,您将不得不修改操作系统环境本身。使用 maxm 的建议,这是我想出的解决方案 - 它实际上与 venv/Scripts/Activatevenv/Scripts/Deactivate 文件非常相似:

// file: venv_run_windows.go
func activateVenv(old_path, venv_path string) (err error) 
    err = os.Setenv("PATH", filepath.Join(venv_path, "Scripts") + ";" + old_path)
    if err != nil 
        return err
    
    return os.Setenv("VIRTUAL_ENV", venv_path)


func deactivateVenv(old_path string) (err error) 
    err = os.Setenv("PATH", old_path)
    if err != nil 
        return err
    
    return os.Unsetenv("VIRTUAL_ENV")


func VenvRun(cmd *exec.Cmd) (exitCode int, err error)
    old_path := os.Getenv("PATH")
    err = activateVenv(old_path, venv)
    if (err != nil)  
        return exitCode, err
    
    defer deactivateVenv(old_path)
    return Run(cmd)

【问题讨论】:

【参考方案1】:

当你运行时:

cmd := exec.Command("pip", "install", "requests")

Go calls exec.LookPath 查找pip 可执行文件的文件路径。由于您在调用exec.Command 之后将PATH 调整添加到环境变量,因此cmd.Path 将指向您的系统python。您可以在调用exec.Command 后通过打印cmd.Path 来确认这一点。

我建议将“pip”替换为 venv 中“pip”可执行文件的位置。 (抱歉,我不懂windows)类似:

cmd := exec.Command("C:\\Users\\acalder\\Projects\\go\\runinvenv\\venv\\bin\\pip", "install", "requests")

或者:

    cmd := exec.Command("pip", "install", "requests")
    cmd.Path = "C:\\Users\\acalder\\Projects\\go\\runinvenv\\venv\\bin\\pip"

由于 exec.LookPath 依赖于 os.Getenv,或者我认为这也可以:

os.Setenv("PATH",  venv + "\\Scripts;" + os.Getenv("PATH"))
cmd := exec.Command("pip", "install", "requests")

一旦你开始工作并且“pip”指向正确的位置,我猜你仍然需要更新cmd.Env(就像你已经拥有的那样),以便对“pip”或“python”的任何底层调用也在您的 venv 中使用正确的可执行文件。

【讨论】:

啊,有道理,谢谢!遗憾的是 LookPath 使用 os 环境而不是命令环境 - 我可以在命令构造函数中设置 Env 作为一个简单的修复。在标记为已接受之前,我会尝试您的建议。

以上是关于在 Go os/exec 命令中激活 Python venv的主要内容,如果未能解决你的问题,请参考以下文章

[go]os/exec执行shell命令

如何在 Go 中传递多个命令?

go任务调度1(go的操作系统命令调用)

go 多参数命令行

GO语言学习-操作系统命令

go语言中os/exec包的学习与使用