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

Activating Python venv in Go os/exec Command

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

根据docs,这应该是possible:

You don’t specifically need to activate an environment; activation just prepends the virtual environment’s binary directory to your path, so that “python” invokes the virtual environment’s Python interpreter and you can run installed scripts without having to use their full path. However, all scripts installed in a virtual environment should be runnable without activating it, and run with the virtual environment’s Python automatically.

但是,当我在 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,您将不得不修改 os 环境本身。使用 maxm 的建议,这是我想出的解决方案 - 它实际上与 venv/Scripts/Activate & venv/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)
}

当你 运行:

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 中使用正确的可执行文件。