forked from mirror/conan-config
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
172 lines
8.4 KiB
172 lines
8.4 KiB
import sys |
|
|
|
import os |
|
from io import StringIO |
|
|
|
from pathlib import Path |
|
|
|
from jinja2 import Template |
|
|
|
from conan import ConanFile |
|
from conan.tools.env import VirtualRunEnv |
|
from conans.model import Generator |
|
from conans.errors import ConanException |
|
|
|
|
|
class VirtualPythonEnv(Generator): |
|
|
|
@property |
|
def _script_ext(self): |
|
if self.conanfile.settings.get_safe("os") == "Windows": |
|
if self.conanfile.conf.get("tools.env.virtualenv:powershell", check_type = bool): |
|
return ".ps1" |
|
else: |
|
return ".bat" |
|
return ".sh" |
|
|
|
@property |
|
def _venv_path(self): |
|
if self.settings.os == "Windows": |
|
return "Scripts" |
|
return "bin" |
|
|
|
@property |
|
def filename(self): |
|
pass |
|
|
|
@property |
|
def content(self): |
|
conanfile: ConanFile = self.conanfile |
|
python_interpreter = Path(self.conanfile.deps_user_info["cpython"].python) |
|
|
|
# When on Windows execute as Windows Path |
|
if conanfile.settings.os == "Windows": |
|
python_interpreter = Path(*[f'"{p}"' if " " in p else p for p in python_interpreter.parts]) |
|
|
|
# Create the virtual environment |
|
print(f"Creating virtual environment in {conanfile.install_folder}") |
|
print(f"Creating virtual environment in {conanfile.build_folder}") |
|
if conanfile.in_local_cache: |
|
venv_folder = conanfile.install_folder |
|
else: |
|
venv_folder = conanfile.build_folder if conanfile.build_folder else Path(os.getcwd(), "venv") |
|
|
|
conanfile.output.info(f"Creating virtual environment in {venv_folder}") |
|
conanfile.run(f"""{python_interpreter} -m venv {venv_folder}""", run_environment = True, env = "conanrun") |
|
|
|
# Make sure there executable is named the same on all three OSes this allows it to be called with `python` |
|
# simplifying GH Actions steps |
|
if conanfile.settings.os != "Windows": |
|
python_venv_interpreter = Path(venv_folder, self._venv_path, "python") |
|
if not python_venv_interpreter.exists(): |
|
python_venv_interpreter.hardlink_to( |
|
Path(venv_folder, self._venv_path, Path(sys.executable).stem + Path(sys.executable).suffix)) |
|
else: |
|
python_venv_interpreter = Path(venv_folder, self._venv_path, Path(sys.executable).stem + Path(sys.executable).suffix) |
|
|
|
if not python_venv_interpreter.exists(): |
|
raise ConanException(f"Virtual environment Python interpreter not found at: {python_venv_interpreter}") |
|
if conanfile.settings.os == "Windows": |
|
python_venv_interpreter = Path(*[f'"{p}"' if " " in p else p for p in python_venv_interpreter.parts]) |
|
|
|
buffer = StringIO() |
|
outer = '"' if conanfile.settings.os == "Windows" else "'" |
|
inner = "'" if conanfile.settings.os == "Windows" else '"' |
|
conanfile.run(f"{python_venv_interpreter} -c {outer}import sysconfig; print(sysconfig.get_path({inner}purelib{inner})){outer}", |
|
env = "conanrun", |
|
output = buffer) |
|
pythonpath = buffer.getvalue().splitlines()[-1] |
|
|
|
run_env = VirtualRunEnv(conanfile) |
|
env = run_env.environment() |
|
|
|
env.define_path("VIRTUAL_ENV", venv_folder) |
|
env.prepend_path("PATH", os.path.join(venv_folder, self._venv_path)) |
|
env.prepend_path("PYTHONPATH", pythonpath) |
|
env.unset("PYTHONHOME") |
|
|
|
envvars = env.vars(conanfile, scope = "run") |
|
|
|
# Install some base_packages |
|
conanfile.run(f"""{python_venv_interpreter} -m pip install wheel setuptools""", run_environment = True, env = "conanrun") |
|
|
|
# Install pip_requirements from dependencies |
|
for dep_name in reversed(conanfile.deps_user_info): |
|
dep_user_info = conanfile.deps_user_info[dep_name] |
|
if len(dep_user_info.vars) == 0: |
|
continue |
|
pip_req_paths = [conanfile.deps_cpp_info[dep_name].res_paths[i] for i, req_path in |
|
enumerate(conanfile.deps_cpp_info[dep_name].resdirs) if req_path.endswith("pip_requirements")] |
|
if len(pip_req_paths) != 1: |
|
continue |
|
pip_req_base_path = Path(pip_req_paths[0]) |
|
if hasattr(dep_user_info, "pip_requirements"): |
|
req_txt = pip_req_base_path.joinpath(dep_user_info.pip_requirements) |
|
if req_txt.exists(): |
|
conanfile.run(f"{python_venv_interpreter} -m pip install -r {req_txt} --force-reinstall", run_environment = True, |
|
env = "conanrun") |
|
conanfile.output.success(f"Dependency {dep_name} specifies pip_requirements in user_info installed!") |
|
else: |
|
conanfile.output.warn(f"Dependency {dep_name} specifies pip_requirements in user_info but {req_txt} can't be found!") |
|
|
|
if hasattr(dep_user_info, "pip_requirements_git"): |
|
req_txt = pip_req_base_path.joinpath(dep_user_info.pip_requirements_git) |
|
if req_txt.exists(): |
|
conanfile.run(f"{python_venv_interpreter} -m pip install -r {req_txt} --force-reinstall", run_environment = True, |
|
env = "conanrun") |
|
conanfile.output.success(f"Dependency {dep_name} specifies pip_requirements_git in user_info installed!") |
|
else: |
|
conanfile.output.warn( |
|
f"Dependency {dep_name} specifies pip_requirements_git in user_info but {req_txt} can't be found!") |
|
|
|
if hasattr(dep_user_info, "pip_requirements_build"): |
|
req_txt = pip_req_base_path.joinpath(dep_user_info.pip_requirements_build) |
|
if req_txt.exists(): |
|
conanfile.run(f"{python_venv_interpreter} -m pip install -r {req_txt} --force-reinstall", run_environment = True, |
|
env = "conanrun") |
|
conanfile.output.success(f"Dependency {dep_name} specifies pip_requirements_build in user_info installed!") |
|
else: |
|
conanfile.output.warn( |
|
f"Dependency {dep_name} specifies pip_requirements_build in user_info but {req_txt} can't be found!") |
|
|
|
if not conanfile.in_local_cache and hasattr(conanfile, "requirements_txts"): |
|
# Install the Python requirements of the current conanfile requirements*.txt |
|
pip_req_base_path = Path(conanfile.source_folder) |
|
|
|
for req_path in sorted(conanfile.requirements_txts, reverse = True): |
|
req_txt = pip_req_base_path.joinpath(req_path) |
|
if req_txt.exists(): |
|
conanfile.run(f"{python_venv_interpreter} -m pip install -r {req_txt} --force-reinstall", run_environment = True, |
|
env = "conanrun") |
|
conanfile.output.success(f"Requirements file {req_txt} installed!") |
|
else: |
|
conanfile.output.warn(f"Requirements file {req_txt} can't be found!") |
|
|
|
# Add all dlls/dylibs/so found in site-packages to the PATH, DYLD_LIBRARY_PATH and LD_LIBRARY_PATH |
|
dll_paths = list({ dll.parent for dll in Path(pythonpath).glob("**/*.dll") }) |
|
for dll_path in dll_paths: |
|
env.append_path("PATH", str(dll_path)) |
|
|
|
dylib_paths = list({ dylib.parent for dylib in Path(pythonpath).glob("**/*.dylib") }) |
|
for dylib_path in dylib_paths: |
|
env.append_path("DYLD_LIBRARY_PATH", str(dylib_path)) |
|
|
|
so_paths = list({ so.parent for so in Path(pythonpath).glob("**/*.dylib") }) |
|
for so_path in so_paths: |
|
env.append_path("LD_LIBRARY_PATH", str(so_path)) |
|
|
|
full_envvars = env.vars(conanfile, scope = "run") |
|
|
|
# Generate the Python Virtual Environment Script |
|
full_envvars.save_sh(Path(venv_folder, self._venv_path, "activate")) |
|
full_envvars.save_bat(Path(venv_folder, self._venv_path, "activate.bat")) |
|
full_envvars.save_ps1(Path(venv_folder, self._venv_path, "Activate.ps1")) |
|
|
|
# Generate the GitHub Action activation script |
|
env_prefix = "Env:" if conanfile.settings.os == "Windows" else "" |
|
activate_github_actions_buildenv = Template(r"""{% for var, value in envvars.items() %}echo "{{ var }}={{ value }}" >> ${{ env_prefix }}GITHUB_ENV |
|
{% endfor %}""").render(envvars = full_envvars, env_prefix = env_prefix) |
|
|
|
return { |
|
str(Path(venv_folder, self._venv_path, f"activate_github_actions_env{self._script_ext}")): activate_github_actions_buildenv |
|
}
|
|
|