| // Copyright 2022 The LUCI Authors. | |
| // | |
| // 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 includes generating python venv and parsing python command | |
| // line arguments. | |
| package python | |
| import ( | |
| "embed" | |
| "fmt" | |
| "io" | |
| "io/fs" | |
| "os" | |
| "path/filepath" | |
| "runtime" | |
| "go.chromium.org/luci/cipd/client/cipd/ensure" | |
| "go.chromium.org/luci/cipkg/base/generators" | |
| "go.chromium.org/luci/cipkg/base/workflow" | |
| "go.chromium.org/luci/common/errors" | |
| "go.chromium.org/luci/vpython/common" | |
| ) | |
| type Environment struct { | |
| Executable string | |
| CPython generators.Generator | |
| Virtualenv generators.Generator | |
| } | |
| func CPythonFromCIPD(version string) generators.Generator { | |
| return &generators.CIPDExport{ | |
| Name: "cpython", | |
| Ensure: ensure.File{ | |
| PackagesBySubdir: map[string]ensure.PackageSlice{ | |
| "": { | |
| {PackageTemplate: "infra/3pp/tools/cpython/${platform}", UnresolvedVersion: version}, | |
| }, | |
| }, | |
| }, | |
| } | |
| } | |
| func CPython3FromCIPD(version string) generators.Generator { | |
| return &generators.CIPDExport{ | |
| Name: "cpython", | |
| Ensure: ensure.File{ | |
| PackagesBySubdir: map[string]ensure.PackageSlice{ | |
| "": { | |
| {PackageTemplate: "infra/3pp/tools/cpython3/${platform}", UnresolvedVersion: version}, | |
| }, | |
| }, | |
| }, | |
| } | |
| } | |
| func VirtualenvFromCIPD(version string) generators.Generator { | |
| return &generators.CIPDExport{ | |
| Name: "virtualenv", | |
| Ensure: ensure.File{ | |
| PackagesBySubdir: map[string]ensure.PackageSlice{ | |
| "": { | |
| {PackageTemplate: "infra/3pp/tools/virtualenv", UnresolvedVersion: version}, | |
| }, | |
| }, | |
| }, | |
| } | |
| } | |
| //go:embed bootstrap.py pep425tags.py | |
| var bootstrapEmbed embed.FS | |
| var bootstrapGen = generators.InitEmbeddedFS("bootstrap", bootstrapEmbed) | |
| func (e *Environment) Pep425Tags() generators.Generator { | |
| // Generate an empty virtual environment to probe the pep425tags | |
| empty := &workflow.Generator{ | |
| Name: "python_venv", | |
| Args: []string{ | |
| common.Python("{{.cpython}}", e.Executable), | |
| filepath.Join("{{.bootstrap}}", "bootstrap.py"), | |
| }, | |
| Dependencies: []generators.Dependency{ | |
| {Type: generators.DepsHostTarget, Generator: e.CPython, Runtime: true}, | |
| {Type: generators.DepsHostTarget, Generator: e.Virtualenv}, | |
| {Type: generators.DepsHostTarget, Generator: bootstrapGen}, | |
| }, | |
| } | |
| return &workflow.Generator{ | |
| Name: "python_pep425tags", | |
| Args: []string{ | |
| common.PythonVENV("{{.python_venv}}", e.Executable), | |
| filepath.Join("{{.bootstrap}}", "pep425tags.py"), | |
| }, | |
| Dependencies: []generators.Dependency{ | |
| {Type: generators.DepsHostTarget, Generator: empty}, | |
| {Type: generators.DepsHostTarget, Generator: bootstrapGen}, | |
| }, | |
| } | |
| } | |
| func (e *Environment) WithWheels(wheels generators.Generator) generators.Generator { | |
| return &workflow.Generator{ | |
| Name: "python_venv", | |
| Args: []string{ | |
| common.Python("{{.cpython}}", e.Executable), | |
| "-BssE", | |
| filepath.Join("{{.bootstrap}}", "bootstrap.py"), | |
| }, | |
| Dependencies: []generators.Dependency{ | |
| {Type: generators.DepsHostTarget, Generator: e.CPython, Runtime: true}, | |
| {Type: generators.DepsHostTarget, Generator: e.Virtualenv}, | |
| {Type: generators.DepsHostTarget, Generator: wheels}, | |
| {Type: generators.DepsHostTarget, Generator: bootstrapGen}, | |
| }, | |
| } | |
| } | |
| func CPythonFromPath(dir, cipdName string) (generators.Generator, error) { | |
| cpythonDir := dir | |
| if !filepath.IsAbs(dir) { | |
| path, err := os.Executable() | |
| if err != nil { | |
| return nil, errors.Annotate(err, "failed to get executable").Err() | |
| } | |
| if runtime.GOOS != "windows" { | |
| if path, err = filepath.EvalSymlinks(path); err != nil { | |
| return nil, errors.Annotate(err, "failed to eval symlink to executable").Err() | |
| } | |
| } | |
| cpythonDir = filepath.Join(filepath.Dir(path), dir) | |
| } | |
| v, err := os.Open(filepath.Join(cpythonDir, ".versions", fmt.Sprintf("%s.cipd_version", cipdName))) | |
| if err != nil { | |
| return nil, errors.Annotate(err, "Bundled Python %s not found. Use VPYTHON_BYPASS if prebuilt cpython not available on this platform", dir).Err() | |
| } | |
| defer v.Close() | |
| version, err := io.ReadAll(v) | |
| if err != nil { | |
| return nil, errors.Annotate(err, "failed to read version file").Err() | |
| } | |
| return &generators.ImportTargets{ | |
| Name: "cpython", | |
| Targets: map[string]generators.ImportTarget{ | |
| ".": {Source: cpythonDir, Version: string(version), Mode: fs.ModeDir, FollowSymlinks: true}, | |
| }, | |
| }, nil | |
| } |