aboutsummaryrefslogtreecommitdiffstats
path: root/packaging-tools/python_env.py
blob: 5219d78463082b4bc8a1cacd1e557055f769462d (plain)
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
148
149
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

#############################################################################
#
# Copyright (C) 2023 The Qt Company Ltd.
# Contact: https://www.qt.io/licensing/
#
# This file is part of the release tools of the Qt Toolkit.
#
# $QT_BEGIN_LICENSE:GPL-EXCEPT$
# Commercial License Usage
# Licensees holding valid commercial Qt licenses may use this file in
# accordance with the commercial license agreement provided with the
# Software or, alternatively, in accordance with the terms contained in
# a written agreement between you and The Qt Company. For licensing terms
# and conditions see https://www.qt.io/terms-conditions. For further
# information use the contact form at https://www.qt.io/contact-us.
#
# GNU General Public License Usage
# Alternatively, this file may be used under the terms of the GNU
# General Public License version 3 as published by the Free Software
# Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
# included in the packaging of this file. Please review the following
# information to ensure the GNU General Public License requirements will
# be met: https://www.gnu.org/licenses/gpl-3.0.html.
#
# $QT_END_LICENSE$
#
#############################################################################

import argparse
import os
import platform
import sys
from pathlib import Path
from shutil import rmtree
from typing import Dict, Tuple

from bld_python import build_python
from installer_utils import download_archive, is_valid_url_path
from logging_util import init_logger
from runner import run_cmd, run_cmd_async

if sys.version_info < (3, 7):
    import asyncio_backport as asyncio
else:
    import asyncio

log = init_logger(__name__, debug_mode=False)


class PythonEnvError(Exception):
    pass


def get_env(python_installation: str) -> Dict[str, str]:
    env: Dict[str, str] = {}
    system = platform.system().lower()
    lib_dir = os.path.join(python_installation, "lib")
    bin_dir = os.path.join(python_installation, "bin")
    if "windows" in system:
        bin_dir = os.path.join(python_installation, "PCbuild", "amd64")
        assert os.path.isdir(bin_dir), f"The python binary directory did not exist: {bin_dir}"
        env["LIB_PATH"] = bin_dir
        env["PATH"] = bin_dir + ";" + os.environ.get("PATH", "")
        env["SYSTEMROOT"] = os.environ["SYSTEMROOT"]
        env["HOMEPATH"] = os.environ["HOMEPATH"]
    elif "darwin" in system:
        env["DYLD_LIBRARY_PATH"] = lib_dir
        env["PATH"] = bin_dir + ":" + os.environ.get("PATH", "")
    else:
        env["LD_LIBRARY_PATH"] = lib_dir
        env["PATH"] = bin_dir + ":" + os.environ.get("PATH", "")
    return env


def locate_venv(pipenv: str, env: Dict[str, str]) -> str:
    output = run_cmd(cmd=[pipenv, "--venv"], env=env, timeout=60)
    return output.splitlines()[0].strip()


async def install_pip(get_pip_file: str, python_installation: str) -> str:
    log.info("Installing pip...")
    if is_valid_url_path(get_pip_file):
        pip_tmp_dir = Path.cwd() / "pip_install_tmp"
        rmtree(pip_tmp_dir, ignore_errors=True)
        pip_tmp_dir.mkdir(parents=True)
        get_pip_file = download_archive(get_pip_file, str(pip_tmp_dir))
    elif not (get_pip_file and os.path.isfile(get_pip_file)):
        raise PythonEnvError(f"Could not install pip from: {get_pip_file}")

    python_exe = os.path.join(python_installation, "PCBuild", "amd64", "python.exe")
    assert os.path.isfile(python_exe), f"The 'python' executable did not exist: {python_exe}"
    install_pip_cmd = [python_exe, get_pip_file]
    await run_cmd_async(cmd=install_pip_cmd)
    return os.path.join(python_installation, "Scripts", "pip3.exe")


async def create_venv(python_src: str, get_pip_file: str) -> Tuple[str, str, Dict[str, str]]:
    log.info("Creating Python virtual env..")
    system = platform.system().lower()
    install_path = Path.home() / "_python_bld"
    await build_python(python_src, str(install_path))
    env = get_env(str(install_path))
    if "windows" in system:
        pip3 = await install_pip(get_pip_file, str(install_path))
    else:
        pip3 = os.path.join(install_path, "bin", "pip3")
    assert os.path.isfile(pip3), f"The 'pip3' executable did not exist: {pip3}"
    log.info("Installing pipenv using: %s", pip3)
    cmd = [pip3, "install", "pipenv"]
    await run_cmd_async(cmd=cmd, env=env, timeout=60 * 15)  # give it 15 mins
    if "windows" in system:
        pipenv = os.path.join(install_path, "Scripts", "pipenv.exe")
    else:
        pipenv = os.path.join(install_path, "bin", "pipenv")
    assert os.path.isfile(pipenv), f"The 'pipenv' executable did not exist: {pipenv}"
    cmd = [pipenv, "install"]
    log.info("Installing pipenv requirements into: %s", install_path)
    await run_cmd_async(cmd=cmd, env=env, timeout=60 * 30)  # give it 30 mins
    venv_folder = locate_venv(pipenv, env)
    log.info("The pipenv virtualenv is created located in: %s", venv_folder)
    return (venv_folder, pipenv, env)


def main() -> None:
    """Main"""
    parser = argparse.ArgumentParser(prog="Create Python virtual env")
    parser.add_argument(
        "--python-src",
        dest="python_src",
        type=str,
        default=os.getenv("PYTHON_SRC"),
        help="Path to local checkout or .zip/.7z/.tar.gz",
    )
    parser.add_argument(
        "--get-pip-file",
        dest="get_pip_file",
        type=str,
        default=os.getenv("GET_PIP_FILE"),
        help="Path to get-pip.py needed for installing pip on Windows",
    )
    args = parser.parse_args(sys.argv[1:])
    asyncio.run(create_venv(args.python_src, args.get_pip_file))


if __name__ == "__main__":
    main()