2020-05-23 00:01:15 +02:00
|
|
|
// Copyright 2016-2020, Pulumi Corporation.
|
|
|
|
//
|
|
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
// you may not use this file except in compliance with the License.
|
|
|
|
// You may obtain a copy of the License at
|
|
|
|
//
|
|
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
//
|
|
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
// See the License for the specific language governing permissions and
|
|
|
|
// limitations under the License.
|
|
|
|
|
|
|
|
package python
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"os"
|
|
|
|
"os/exec"
|
|
|
|
"path/filepath"
|
|
|
|
"runtime"
|
|
|
|
"strings"
|
2020-06-10 01:42:53 +02:00
|
|
|
|
|
|
|
"github.com/pkg/errors"
|
2020-05-23 00:01:15 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
const windows = "windows"
|
|
|
|
|
|
|
|
// Command returns an *exec.Cmd for running `python`. If the `PULUMI_PYTHON_CMD` variable is set
|
|
|
|
// it will be looked for on `PATH`, otherwise, `python3` and `python` will be looked for.
|
|
|
|
func Command(arg ...string) (*exec.Cmd, error) {
|
|
|
|
var err error
|
|
|
|
var pythonCmds []string
|
|
|
|
var pythonPath string
|
|
|
|
|
|
|
|
if pythonCmd := os.Getenv("PULUMI_PYTHON_CMD"); pythonCmd != "" {
|
|
|
|
pythonCmds = []string{pythonCmd}
|
|
|
|
} else {
|
|
|
|
// Look for "python3" by default, but fallback to `python` if not found as some Python 3
|
|
|
|
// distributions (in particular the default python.org Windows installation) do not include
|
|
|
|
// a `python3` binary.
|
|
|
|
pythonCmds = []string{"python3", "python"}
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, pythonCmd := range pythonCmds {
|
|
|
|
pythonPath, err = exec.LookPath(pythonCmd)
|
|
|
|
// Break on the first cmd we find on the path (if any)
|
|
|
|
if err == nil {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf(
|
|
|
|
"Failed to locate any of %q on your PATH. Have you installed Python 3.6 or greater?",
|
|
|
|
pythonCmds)
|
|
|
|
}
|
|
|
|
|
|
|
|
return exec.Command(pythonPath, arg...), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// VirtualEnvCommand returns an *exec.Cmd for running a command from the specified virtual environment
|
|
|
|
// directory.
|
2020-06-10 01:42:53 +02:00
|
|
|
func VirtualEnvCommand(virtualEnvDir, name string, arg ...string) *exec.Cmd {
|
2020-05-23 00:01:15 +02:00
|
|
|
if runtime.GOOS == windows {
|
|
|
|
name = fmt.Sprintf("%s.exe", name)
|
|
|
|
}
|
|
|
|
cmdPath := filepath.Join(virtualEnvDir, virtualEnvBinDirName(), name)
|
|
|
|
return exec.Command(cmdPath, arg...)
|
|
|
|
}
|
|
|
|
|
2020-06-10 01:42:53 +02:00
|
|
|
// IsVirtualEnv returns true if the specified directory contains a python binary.
|
|
|
|
func IsVirtualEnv(dir string) bool {
|
|
|
|
pyBin := filepath.Join(dir, virtualEnvBinDirName(), "python")
|
|
|
|
if runtime.GOOS == windows {
|
|
|
|
pyBin = fmt.Sprintf("%s.exe", pyBin)
|
|
|
|
}
|
|
|
|
if info, err := os.Stat(pyBin); err == nil && !info.IsDir() {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2020-05-23 00:01:15 +02:00
|
|
|
// ActivateVirtualEnv takes an array of environment variables (same format as os.Environ()) and path to
|
|
|
|
// a virtual environment directory, and returns a new "activated" array with the virtual environment's
|
|
|
|
// "bin" dir ("Scripts" on Windows) prepended to the `PATH` environment variable and `PYTHONHOME` variable
|
|
|
|
// removed.
|
|
|
|
func ActivateVirtualEnv(environ []string, virtualEnvDir string) []string {
|
|
|
|
virtualEnvBin := filepath.Join(virtualEnvDir, virtualEnvBinDirName())
|
|
|
|
var hasPath bool
|
|
|
|
var result []string
|
|
|
|
for _, env := range environ {
|
|
|
|
if strings.HasPrefix(env, "PATH=") {
|
|
|
|
hasPath = true
|
|
|
|
// Prepend the virtual environment bin directory to PATH so any calls to run
|
|
|
|
// python or pip will use the binaries in the virtual environment.
|
|
|
|
originalValue := env[len("PATH="):]
|
|
|
|
path := fmt.Sprintf("PATH=%s%s%s", virtualEnvBin, string(os.PathListSeparator), originalValue)
|
|
|
|
result = append(result, path)
|
|
|
|
} else if strings.HasPrefix(env, "PYTHONHOME=") {
|
|
|
|
// Skip PYTHONHOME to "unset" this value.
|
|
|
|
} else {
|
|
|
|
result = append(result, env)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if !hasPath {
|
|
|
|
path := fmt.Sprintf("PATH=%s", virtualEnvBin)
|
|
|
|
result = append(result, path)
|
|
|
|
}
|
|
|
|
return result
|
|
|
|
}
|
|
|
|
|
2020-06-10 01:42:53 +02:00
|
|
|
// InstallDependencies will create a new virtual environment and install dependencies in the root directory.
|
|
|
|
func InstallDependencies(root string, showOutput bool, saveProj func(virtualenv string) error) error {
|
|
|
|
if showOutput {
|
|
|
|
fmt.Println("Creating virtual environment...")
|
|
|
|
fmt.Println()
|
|
|
|
}
|
|
|
|
|
|
|
|
// Create the virtual environment by running `python -m venv venv`.
|
|
|
|
venvDir := filepath.Join(root, "venv")
|
|
|
|
cmd, err := Command("-m", "venv", venvDir)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if output, err := cmd.CombinedOutput(); err != nil {
|
|
|
|
if len(output) > 0 {
|
|
|
|
os.Stdout.Write(output)
|
|
|
|
fmt.Println()
|
|
|
|
}
|
|
|
|
return errors.Wrapf(err, "creating virtual environment at %s", venvDir)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Save project with venv info.
|
|
|
|
if err := saveProj("venv"); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
if showOutput {
|
|
|
|
fmt.Println("Finished creating virtual environment")
|
|
|
|
fmt.Println()
|
|
|
|
}
|
|
|
|
|
|
|
|
// If `requirements.txt` doesn't exist, just exit early.
|
|
|
|
requirementsPath := filepath.Join(root, "requirements.txt")
|
|
|
|
if _, err := os.Stat(requirementsPath); os.IsNotExist(err) {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
if showOutput {
|
|
|
|
fmt.Println("Installing dependencies...")
|
|
|
|
fmt.Println()
|
|
|
|
}
|
|
|
|
|
|
|
|
// Install dependencies by running `pip install -r requirements.txt` using the `pip`
|
|
|
|
// in the virtual environment.
|
|
|
|
pipCmd := VirtualEnvCommand(venvDir, "pip", "install", "-r", "requirements.txt")
|
|
|
|
pipCmd.Dir = root
|
|
|
|
pipCmd.Env = ActivateVirtualEnv(os.Environ(), venvDir)
|
|
|
|
if showOutput {
|
|
|
|
// Show stdout/stderr output.
|
|
|
|
pipCmd.Stdout = os.Stdout
|
|
|
|
pipCmd.Stderr = os.Stderr
|
|
|
|
if err := pipCmd.Run(); err != nil {
|
|
|
|
return errors.Wrap(err, "installing dependencies via `pip install -r requirements.txt`")
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// Otherwise, only show output if there is an error.
|
|
|
|
if output, err := pipCmd.CombinedOutput(); err != nil {
|
|
|
|
if len(output) > 0 {
|
|
|
|
os.Stdout.Write(output)
|
|
|
|
fmt.Println()
|
|
|
|
}
|
|
|
|
return errors.Wrap(err, "installing dependencies via `pip install -r requirements.txt`")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if showOutput {
|
|
|
|
fmt.Println("Finished installing dependencies")
|
|
|
|
fmt.Println()
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2020-05-23 00:01:15 +02:00
|
|
|
func virtualEnvBinDirName() string {
|
|
|
|
if runtime.GOOS == windows {
|
|
|
|
return "Scripts"
|
|
|
|
}
|
|
|
|
return "bin"
|
|
|
|
}
|