// PythonVenv is the path of the uv environment that will be created if use-virtualenv is true. const PythnonUvVenvs = LocalLibPrefix + "/uv" type PythonConfig struct { // Python binary to use when installing dependencies Version string `json:"version"` // Install requirements from given files Requirements RequirementsConfig `json:"requirements" validate:"omitempty,unique,dive"` UseSystemSitePackages Flag `json:"use-system-site-packages"` UseNoDepsFlag Flag `json:"no-deps"` // Use Poetry for package management Poetry PoetryConfig `json:"poetry"` //Use UV for package management Uv UvConfig `json:"uv"` // Specify a specific version of tox to install (T346226) ToxVersion string `json:"tox-version"` } // UvConfig holds configuration fields related to installation of project // dependencies via UV package manager type UvConfig struct { Version string `json:"version" validate:"omitempty,pypkgver"` Devel Flag `json:"devel"` } func (pc PythonConfig) InstructionsForPhase(phase build.Phase) []build.Instruction { ins := []build.Instruction{} if !pc.isEnabled() { return ins } // This only does something for build.PhasePreInstall ins = append(ins, pc.Requirements.InstructionsForPhase(phase)...) python := pc.version() switch phase { case build.PhasePreInstall: venvSetupCmd := []string{"-m", "venv", PythonVenv} if pc.UseSystemSitePackages.True { venvSetupCmd = append(venvSetupCmd, "--system-site-packages") } ins = append(ins, build.Run{python, venvSetupCmd}) // "Activate" the virtualenv ins = append(ins, build.Env{map[string]string{ "VIRTUAL_ENV": PythonVenv, "PATH": PythonVenv + "/bin:$PATH", }}) ins = append(ins, pc.setupPipAndPoetryAndUv()...) if pc.usePoetry() { cmd := []string{"install", "--no-root"} if !pc.Poetry.Devel.True { cmd = append(cmd, "--no-dev") } ins = append(ins, build.CreateDirectory(PythonPoetryVenvs)) ins = append(ins, build.Run{"poetry", cmd}) } else if pc.useUv() { cmd := []string{"sync"} if !pc.Uv.Devel.True { cmd = append(cmd, "--no-group", "dev") } ins = append(ins, build.CreateDirectory(PythnonUvVenvs)) ins = append(ins, build.Run{"uv", cmd}) } else { args := pc.RequirementsArgs() if args != nil { installCmd := []string{"-m", "pip", "install"} if pc.UseNoDepsFlag.True { installCmd = append(installCmd, "--no-deps") } ins = append(ins, build.Run{python, append(installCmd, args...)}) } } case build.PhasePostInstall: if !pc.usePoetry() { if pc.UseNoDepsFlag.True { // Ensure requirements has all transitive dependencies ins = append(ins, build.Run{ python, []string{ "-m", "pip", "check", }, }) } } } return ins } func (pc PythonConfig) setupPipAndPoetryAndUv() []build.Instruction { ins := []build.Instruction{} ins = append(ins, build.RunAll{[]build.Run{ {pc.version(), []string{"-m", "pip", "install", "-U", "setuptools!=60.9.0"}}, {pc.version(), []string{"-m", "pip", "install", "-U", "wheel", pc.toxPackage(), pc.pipPackage()}}, }}) if pc.usePoetry() { ins = append(ins, build.Env{map[string]string{ "POETRY_VIRTUALENVS_PATH": PythonPoetryVenvs, }}) ins = append(ins, build.Run{ pc.version(), []string{ "-m", "pip", "install", "-U", "poetry" + pc.Poetry.Version, }, }) } else { ins = append(ins, build.Env{map[string]string{ "UV_VIRTUALENVS_PATH": PythnonUvVenvs, }}) ins = append(ins, build.Run{ pc.version(), []string{ "-m", "pip", "install", "-U", "uv" + pc.Uv.Version, }, }) } return ins } func (pc PythonConfig) useUv() bool { return pc.Uv.Version != "" }