- package config
-
- import (
- "gerrit.wikimedia.org/r/blubber/build"
- )
-
- // PythonLibPrefix is the path to installed dependency wheels.
- //
- const PythonLibPrefix = LocalLibPrefix + "/python"
-
- // PythonSitePackages is the path to installed Python packages.
- //
- const PythonSitePackages = PythonLibPrefix + "/site-packages"
-
- // PythonSiteBin is the path to installed Python packages bin files.
- //
- const PythonSiteBin = PythonSitePackages + "/bin"
-
- // PythonConfig holds configuration fields related to pre-installation of project
- // dependencies via PIP.
- //
- type PythonConfig struct {
- Version string `json:"version"` // Python binary to use when installing dependencies
- Requirements []string `json:"requirements"` // install requirements from given files
- UseSystemFlag bool `json:"use-system-flag"` // Inject the --system flag into the install command (T227919)
- }
-
- // Merge takes another PythonConfig and merges its fields into this one's,
- // overwriting both the dependencies flag and requirements.
- //
- func (pc *PythonConfig) Merge(pc2 PythonConfig) {
- if pc2.Version != "" {
- pc.Version = pc2.Version
- }
-
- if pc2.Requirements != nil {
- pc.Requirements = pc2.Requirements
- }
-
- if pc2.UseSystemFlag {
- pc.UseSystemFlag = true
- }
- }
-
- // InstructionsForPhase injects instructions into the build related to Python
- // dependency installation.
- //
- // PhasePrivileged
- //
- // Ensures that the newest versions of setuptools, wheel, tox, and pip are
- // installed.
- //
- // PhasePreInstall
- //
- // Sets up Python wheels under the shared library directory (/opt/lib/python)
- // for dependencies found in the declared requirements files. Installing
- // dependencies during the build.PhasePreInstall phase allows a compiler
- // implementation (e.g. Docker) to produce cache-efficient output so only
- // changes to the given requirements files will invalidate these steps of the
- // image build.
- //
- // Injects build.Env instructions for PIP_WHEEL_DIR and PIP_FIND_LINKS that
- // will cause future executions of `pip install` (and by extension, `tox`) to
- // consider packages from the shared library directory first.
- //
- // PhasePostInstall
- //
- // Injects a build.Env instruction for PIP_NO_INDEX that will cause future
- // executions of `pip install` and `tox` to consider _only_ packages from the
- // shared library directory, helping to speed up image builds by reducing
- // network requests from said commands.
- //
- func (pc PythonConfig) InstructionsForPhase(phase build.Phase) []build.Instruction {
- if pc.Version != "" {
- switch phase {
- case build.PhasePrivileged:
- if pc.Requirements != nil {
- return []build.Instruction{build.RunAll{[]build.Run{
- {pc.version(), []string{"-m", "easy_install", "pip"}},
- {pc.version(), []string{"-m", "pip", "install", "-U", "setuptools", "wheel", "tox"}},
- }}}
- }
-
- case build.PhasePreInstall:
- if pc.Requirements != nil {
- ins := []build.Instruction{
- build.Env{map[string]string{
- "PIP_WHEEL_DIR": PythonLibPrefix,
- "PIP_FIND_LINKS": "file://" + PythonLibPrefix,
- }},
- build.CreateDirectory(PythonLibPrefix),
- }
-
- ins = append(ins, build.SyncFiles(pc.Requirements, ".")...)
-
- if args := pc.RequirementsArgs(); len(args) > 0 {
- installCmd := append([]string{"-m", "pip", "install", "--target"}, PythonSitePackages)
- if pc.UseSystemFlag {
- installCmd = InsertElement(installCmd, "--system", PosOf(installCmd, "install") + 1)
- }
- ins = append(ins, build.RunAll{[]build.Run{
- {pc.version(), append([]string{"-m", "pip", "wheel"}, args...)},
- {pc.version(), append(installCmd, args...)},
- }})
- }
-
- return ins
- }
-
- case build.PhasePostInstall:
- env := build.Env{map[string]string{
- "PYTHONPATH": PythonSitePackages,
- "PATH": PythonSiteBin + ":${PATH}",
- }}
-
- if pc.Requirements != nil {
- env.Definitions["PIP_NO_INDEX"] = "1"
- }
-
- return []build.Instruction{env}
- }
- }
-
- return []build.Instruction{}
- }
-
- // RequirementsArgs returns the configured requirements as pip `-r` arguments.
- //
- func (pc PythonConfig) RequirementsArgs() []string {
- args := make([]string, len(pc.Requirements)*2)
-
- for i, req := range pc.Requirements {
- args[i*2] = "-r"
- args[(i*2)+1] = req
- }
-
- return args
- }
-
- func (pc PythonConfig) version() string {
- if pc.Version == "" {
- return "python"
- }
-
- return pc.Version
- }
-
- // InsertElement - insert el into slice at pos
- func InsertElement(slice []string, el string, pos int) []string {
- slice = append(slice, "")
- copy(slice[pos+1:], slice[pos:])
- slice[pos] = el
- return slice
- }
-
- // PosOf - find position of an element in a slice
- func PosOf(slice []string, el string) int {
- for p, v := range slice {
- if v == el {
- return p
- }
- }
- return -1
- }
|