Skip to content

Commit 80e3a4d

Browse files
authored
Merge pull request #248 from roots/remove-virtualenv-install
Remove virtualenv install fallback
2 parents ae9e519 + 878ebf9 commit 80e3a4d

File tree

3 files changed

+94
-110
lines changed

3 files changed

+94
-110
lines changed

cmd/init.go

Lines changed: 17 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -56,31 +56,15 @@ func (c *InitCommand) Run(args []string) int {
5656
return 1
5757
}
5858

59-
c.UI.Info("Initializing project\n")
59+
c.UI.Info("Initializing project...\n")
6060
ok, virtualenvCmd := c.Trellis.Virtualenv.Installed()
6161

6262
if !ok {
63-
c.UI.Info("virtualenv not found")
64-
spinner := NewSpinner(
65-
SpinnerCfg{
66-
Message: "Installing virtualenv",
67-
FailMessage: "Error installing virtualenv",
68-
},
69-
)
70-
spinner.Start()
71-
_, err := c.Trellis.Virtualenv.Install()
72-
spinner.Stop()
73-
74-
c.UI.Error(err.Error())
75-
c.UI.Error("")
76-
c.UI.Error("Project initialization failed due to the error above.")
63+
c.UI.Error("virtualenv not found. trellis-cli looked for two options:")
64+
c.UI.Error(" 1. python3's built-in `venv` module")
65+
c.UI.Error(" 2. standalone `virtualenv` command")
7766
c.UI.Error("")
78-
c.UI.Error("trellis-cli attempted to install virtualenv as a fallback but failed.")
79-
c.UI.Error("Without virtualenv, a Python virtual environment cannot be created and the required dependencies (eg: Ansible) can't be installed either.")
80-
c.UI.Error("")
81-
c.UI.Error("There are two options:")
82-
c.UI.Error(" 1. Ensure Python 3 is installed and the `python3` command works. trellis-cli will use python3's built-in venv feature.")
83-
c.UI.Error(fmt.Sprintf(" 2. Disable trellis-cli's virtual env feature by setting this env variable: export %s=false", trellis.TrellisVenvEnvName))
67+
virtualenvError(c.UI)
8468
return 1
8569
}
8670

@@ -123,13 +107,7 @@ func (c *InitCommand) Run(args []string) int {
123107
c.UI.Error("trellis-cli tried to create a virtual environment but failed.")
124108
c.UI.Error(fmt.Sprintf(" => %s", virtualenvCmd.String()))
125109
c.UI.Error("")
126-
c.UI.Error("Without a Python virtual environment the required dependencies (eg: Ansible) can't be installed.")
127-
c.UI.Error("")
128-
c.UI.Error("There are two options:")
129-
c.UI.Error(" 1. Ensure Python 3 is installed and the `python3` command works. trellis-cli will use python3's built-in venv feature.")
130-
c.UI.Error(fmt.Sprintf(" 2. Disable trellis-cli's virtual env feature by setting this env variable: export %s=false", trellis.TrellisVenvEnvName))
131-
c.UI.Error("")
132-
c.UI.Error("This could also be a bug in trellis-cli. Please create an issue at https://github.com/roots/trellis-cli and include the output above.")
110+
virtualenvError(c.UI)
133111
return 1
134112
}
135113

@@ -199,3 +177,14 @@ Options:
199177

200178
return strings.TrimSpace(helpText)
201179
}
180+
181+
func virtualenvError(ui cli.Ui) {
182+
ui.Error("Without virtualenv, a Python virtual environment cannot be created and the required dependencies (eg: Ansible) can't be installed either.")
183+
ui.Error("")
184+
ui.Error("There are two options:")
185+
ui.Error(" 1. Ensure Python 3 is installed and the `python3` command works. trellis-cli will use python3's built-in venv feature.")
186+
ui.Error(" Ubuntu/Debian users (including Windows WSL): venv is not built-in, to install it run `sudo apt-get install python3-pip python3-venv`")
187+
ui.Error("")
188+
ui.Error(" 2. Disable trellis-cli's virtual env feature, and manage dependencies manually, by setting this env variable:")
189+
ui.Error(fmt.Sprintf(" export %s=false", trellis.TrellisVenvEnvName))
190+
}

trellis/virtualenv.go

Lines changed: 7 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,11 @@ package trellis
22

33
import (
44
"fmt"
5-
"log"
65
"os"
76
"os/exec"
87
"path/filepath"
98

10-
"github.com/roots/trellis-cli/github"
9+
"github.com/roots/trellis-cli/command"
1110
)
1211

1312
const TrellisVenvEnvName string = "TRELLIS_VENV"
@@ -73,21 +72,6 @@ func (v *Virtualenv) Delete() error {
7372
return os.RemoveAll(v.Path)
7473
}
7574

76-
func (v *Virtualenv) LocalPath() string {
77-
configHome := os.Getenv("XDG_CONFIG_HOME")
78-
79-
if configHome == "" {
80-
homeDir, err := os.UserHomeDir()
81-
if err != nil {
82-
log.Fatal(err)
83-
}
84-
85-
configHome = filepath.Join(homeDir, ".local", "share")
86-
}
87-
88-
return filepath.Join(configHome, "trellis", "virtualenv")
89-
}
90-
9175
func (v *Virtualenv) Initialized() bool {
9276
if _, err := os.Stat(filepath.Join(v.BinPath, "python")); os.IsNotExist(err) {
9377
return false
@@ -100,34 +84,19 @@ func (v *Virtualenv) Initialized() bool {
10084
return true
10185
}
10286

103-
func (v *Virtualenv) Install() (installPath string, err error) {
104-
localPath := v.LocalPath()
105-
configDir := filepath.Dir(localPath)
106-
107-
if _, err = os.Stat(configDir); os.IsNotExist(err) {
108-
if err = os.MkdirAll(configDir, 0755); err != nil {
109-
return "", err
110-
}
111-
}
112-
113-
return github.DownloadRelease("pypa/virtualenv", "latest", os.TempDir(), localPath), nil
114-
}
115-
11687
func (v *Virtualenv) Installed() (ok bool, cmd *exec.Cmd) {
11788
path, err := exec.LookPath("python3")
11889
if err == nil {
119-
return true, exec.Command(path, "-m", "venv")
90+
err = command.Cmd(path, []string{"-m", "ensurepip", "--version"}).Run()
91+
92+
if err == nil {
93+
return true, command.Cmd(path, []string{"-m", "venv"})
94+
}
12095
}
12196

12297
path, err = exec.LookPath("virtualenv")
12398
if err == nil {
124-
return true, exec.Command(path)
125-
}
126-
127-
localVenvPath := filepath.Join(v.LocalPath(), "virtualenv.py")
128-
129-
if _, err = os.Stat(localVenvPath); !os.IsNotExist(err) {
130-
return true, exec.Command("python", localVenvPath)
99+
return true, command.Cmd(path, []string{})
131100
}
132101

133102
return false, nil

trellis/virtualenv_test.go

Lines changed: 70 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,16 @@
11
package trellis
22

33
import (
4+
"bytes"
45
"fmt"
56
"io/ioutil"
67
"os"
8+
"os/exec"
79
"path/filepath"
810
"strings"
911
"testing"
12+
13+
"github.com/roots/trellis-cli/command"
1014
)
1115

1216
func TestNewVirtualenv(t *testing.T) {
@@ -103,30 +107,6 @@ func TestDeactive(t *testing.T) {
103107
}
104108
}
105109

106-
func TestLocalPath(t *testing.T) {
107-
venv := NewVirtualenv("trellis")
108-
originalConfigHome := os.Getenv("XDG_CONFIG_HOME")
109-
os.Unsetenv("XDG_CONFIG_HOME")
110-
defer os.Setenv("XDG_CONFIG_HOME", originalConfigHome)
111-
112-
homeDir, _ := os.UserHomeDir()
113-
114-
localPath := venv.LocalPath()
115-
116-
if localPath != filepath.Join(homeDir, ".local/share/trellis/virtualenv") {
117-
t.Error("Expected LocalPath to default to $USER/.local/share")
118-
}
119-
120-
os.Setenv("XDG_CONFIG_HOME", "mydir")
121-
defer os.Setenv("XDG_CONFIG_HOME", originalConfigHome)
122-
123-
localPath = venv.LocalPath()
124-
125-
if localPath != filepath.Join("mydir", "trellis/virtualenv") {
126-
t.Error("Expected LocalPath to use XDG_CONFIG_HOME when set")
127-
}
128-
}
129-
130110
func TestInitialized(t *testing.T) {
131111
tempDir, err := ioutil.TempDir("", "trellis")
132112
defer os.RemoveAll(tempDir)
@@ -163,7 +143,7 @@ func TestInstalled(t *testing.T) {
163143
}
164144
}
165145

166-
func TestInstalledPython3(t *testing.T) {
146+
func TestInstalledPython3WithEnsurepip(t *testing.T) {
167147
tempDir, err := ioutil.TempDir("", "trellis")
168148
defer os.RemoveAll(tempDir)
169149

@@ -178,18 +158,36 @@ func TestInstalledPython3(t *testing.T) {
178158

179159
venv := NewVirtualenv(tempDir)
180160

161+
var output bytes.Buffer
162+
163+
mockExecCommand := func(command string, args []string) *exec.Cmd {
164+
cs := []string{"-test.run=TestEnsurePipSuccessHelperProcess", "--", command}
165+
cs = append(cs, args...)
166+
cmd := exec.Command(os.Args[0], cs...)
167+
cmd.Stderr = &output
168+
cmd.Stdout = &output
169+
cmd.Env = []string{"GO_WANT_HELPER_PROCESS=1"}
170+
return cmd
171+
}
172+
173+
command.Mock(mockExecCommand)
174+
defer command.Restore()
175+
181176
ok, cmd := venv.Installed()
182177

183178
if !ok {
184179
t.Error("Expected to be installed")
185180
}
186181

187-
if strings.Join(cmd.Args, " ") != fmt.Sprintf("%s -m venv", pythonPath) {
188-
t.Error("Expected args incorrect")
182+
expected := "python3 -m venv"
183+
actual := cmd.String()
184+
185+
if !strings.Contains(actual, expected) {
186+
t.Errorf("Expected command incorrect.\nexpected: %s\ngot: %s", expected, actual)
189187
}
190188
}
191189

192-
func TestInstalledVirtualenv(t *testing.T) {
190+
func TestInstalledPython3WithoutEnsurepip(t *testing.T) {
193191
tempDir, err := ioutil.TempDir("", "trellis")
194192
defer os.RemoveAll(tempDir)
195193

@@ -199,45 +197,55 @@ func TestInstalledVirtualenv(t *testing.T) {
199197

200198
defer testSetEnv("PATH", tempDir)()
201199

202-
venvPath := filepath.Join(tempDir, "virtualenv")
203-
os.OpenFile(venvPath, os.O_CREATE, 0555)
204-
205-
venv := NewVirtualenv(tempDir)
200+
pythonPath := filepath.Join(tempDir, "python3")
201+
os.OpenFile(pythonPath, os.O_CREATE, 0555)
206202

207-
ok, cmd := venv.Installed()
203+
var output bytes.Buffer
208204

209-
if !ok {
210-
t.Error("Expected to be installed")
205+
mockExecCommand := func(command string, args []string) *exec.Cmd {
206+
cs := []string{"-test.run=TestEnsurePipFailureHelperProcess", "--", command}
207+
cs = append(cs, args...)
208+
cmd := exec.Command(os.Args[0], cs...)
209+
cmd.Stderr = &output
210+
cmd.Stdout = &output
211+
cmd.Env = []string{"GO_WANT_HELPER_PROCESS=1"}
212+
return cmd
211213
}
212214

213-
if strings.Join(cmd.Args, " ") != venvPath {
214-
t.Error("Expected args incorrect")
215+
command.Mock(mockExecCommand)
216+
defer command.Restore()
217+
218+
venv := NewVirtualenv(tempDir)
219+
220+
ok, _ := venv.Installed()
221+
222+
if ok {
223+
t.Error("Expected not to be installed")
215224
}
216225
}
217226

218-
func TestInstalledLocalVirtualenv(t *testing.T) {
227+
func TestInstalledVirtualenv(t *testing.T) {
219228
tempDir, err := ioutil.TempDir("", "trellis")
220229
defer os.RemoveAll(tempDir)
221230

222231
if err != nil {
223232
t.Fatalf("err: %s", err)
224233
}
225234

226-
defer testSetEnv("PATH", "")()
227-
defer testSetEnv("XDG_CONFIG_HOME", tempDir)()
235+
defer testSetEnv("PATH", tempDir)()
236+
237+
venvPath := filepath.Join(tempDir, "virtualenv")
238+
os.OpenFile(venvPath, os.O_CREATE, 0555)
228239

229240
venv := NewVirtualenv(tempDir)
230-
localVenvPath := filepath.Join(venv.LocalPath(), "virtualenv.py")
231-
os.MkdirAll(venv.LocalPath(), os.ModePerm)
232-
testCreateFile(t, localVenvPath)()
233241

234242
ok, cmd := venv.Installed()
235243

236244
if !ok {
237245
t.Error("Expected to be installed")
238246
}
239247

240-
if strings.Join(cmd.Args, " ") != fmt.Sprintf("python %s", localVenvPath) {
248+
if strings.Join(cmd.Args, " ") != venvPath {
241249
t.Error("Expected args incorrect")
242250
}
243251
}
@@ -257,3 +265,21 @@ func testSetEnv(env string, value string) func() {
257265
os.Setenv(env, value)
258266
return func() { os.Setenv(env, old) }
259267
}
268+
269+
func TestEnsurePipSuccessHelperProcess(t *testing.T) {
270+
if os.Getenv("GO_WANT_HELPER_PROCESS") != "1" {
271+
return
272+
}
273+
274+
fmt.Fprintf(os.Stdout, strings.Join(os.Args[3:], " "))
275+
os.Exit(0)
276+
}
277+
278+
func TestEnsurePipFailureHelperProcess(t *testing.T) {
279+
if os.Getenv("GO_WANT_HELPER_PROCESS") != "1" {
280+
return
281+
}
282+
283+
fmt.Fprintf(os.Stderr, strings.Join(os.Args[3:], " "))
284+
os.Exit(1)
285+
}

0 commit comments

Comments
 (0)