-
Notifications
You must be signed in to change notification settings - Fork 17
/
Copy pathinstaller.go
147 lines (125 loc) · 4.2 KB
/
installer.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
package cpython
import (
"fmt"
"os"
"path/filepath"
"regexp"
"runtime"
"strings"
"github.com/paketo-buildpacks/packit/v2"
"github.com/paketo-buildpacks/packit/v2/pexec"
"github.com/paketo-buildpacks/packit/v2/postal"
"github.com/paketo-buildpacks/packit/v2/scribe"
)
//go:generate faux --interface Executable --output fakes/executable.go
// CPythonInstaller implements the PythonInstaller interface.
type CPythonInstaller struct {
configureProcess Executable
makeProcess Executable
logger scribe.Emitter
}
// NewCPythonInstaller creates an instance of the CPythonInstaller given a scribe.Emitter.
func NewCPythonInstaller(
configureProcess Executable,
makeProcess Executable,
logger scribe.Emitter,
) CPythonInstaller {
return CPythonInstaller{
configureProcess: configureProcess,
makeProcess: makeProcess,
logger: logger,
}
}
// Executable defines the interface for invoking an executable.
type Executable interface {
Execute(pexec.Execution) error
}
// Installs python from source code located in the given sourcePath into the layer path designated by layerPath.
func (i CPythonInstaller) Install(
sourcePath string,
workingDir string,
entry packit.BuildpackPlanEntry,
dependency postal.Dependency,
layerPath string,
) error {
flags, _ := entry.Metadata["configure-flags"].(string)
if flags == "" {
flags = "--with-ensurepip"
i.logger.Debug.Subprocess("Using default configure flags: %v\n", flags)
}
whiteSpace := regexp.MustCompile(`\s+`)
configureFlags := whiteSpace.Split(flags, -1)
configureFlags = append(configureFlags, "--prefix="+layerPath)
if err := os.Chdir(sourcePath); err != nil {
return err
}
i.logger.Debug.Subprocess("Running 'configure %s'", strings.Join(configureFlags, " "))
err := i.configureProcess.Execute(pexec.Execution{
Args: configureFlags,
Env: environWithUpdatedPath(os.Environ(), "PATH", sourcePath),
Stdout: i.logger.Debug.ActionWriter,
Stderr: i.logger.Debug.ActionWriter,
})
if err != nil {
i.logger.Subprocess("configure failed. Run with --env BP_LOG_LEVEL=DEBUG to see more information")
return err
}
makeFlags := []string{"-j", fmt.Sprint(runtime.NumCPU()), `LDFLAGS="-Wl,--strip-all"`}
i.logger.Debug.Subprocess("Running 'make %s'", strings.Join(makeFlags, " "))
err = i.makeProcess.Execute(pexec.Execution{
Args: makeFlags,
Env: environWithUpdatedPath(os.Environ(), "PATH", sourcePath),
Stdout: i.logger.Debug.ActionWriter,
Stderr: i.logger.Debug.ActionWriter,
})
if err != nil {
i.logger.Subprocess("make failed. Run with --env BP_LOG_LEVEL=DEBUG to see more information")
return err
}
makeInstallFlags := []string{"altinstall"}
i.logger.Debug.Subprocess("Running 'make %s'", strings.Join(makeInstallFlags, " "))
err = i.makeProcess.Execute(pexec.Execution{
Args: makeInstallFlags,
Env: environWithUpdatedPath(os.Environ(), "PATH", sourcePath),
Stdout: i.logger.Debug.ActionWriter,
Stderr: i.logger.Debug.ActionWriter,
})
if err != nil {
i.logger.Subprocess("make install failed. Run with --env BP_LOG_LEVEL=DEBUG to see more information")
return err
}
versionList := strings.Split(dependency.Version, ".")
major := versionList[0]
majorMinor := strings.Join(versionList[:len(versionList)-1], ".")
if err = os.Chdir(filepath.Join(layerPath, "bin")); err != nil {
return err
}
for _, name := range []string{"python", "pip"} {
i.logger.Debug.Action("Writing symlink bin/%s", name+major)
if err = os.Symlink(name+majorMinor, name+major); err != nil {
return err
}
}
if err = os.Chdir(workingDir); err != nil {
return err
}
return nil
}
// Returns environment variables with customPath inserted at the beginning of given environment variable
func environWithUpdatedPath(environ []string, variableName, customPath string) []string {
var env []string = nil
varNameWithEqual := variableName + "="
environmentVariableExists := false
for _, v := range environ {
if strings.HasPrefix(v, varNameWithEqual) {
env = append(env, varNameWithEqual+customPath+":"+strings.TrimPrefix(v, varNameWithEqual))
environmentVariableExists = true
} else {
env = append(env, v)
}
}
if !environmentVariableExists {
env = append(env, varNameWithEqual+customPath)
}
return env
}