def test_snap_to_minimum(constraints, expected) -> None: universe = ["2.7", "3.5", "3.6", "3.7", "3.8", "3.9", "3.10"] ics = InterpreterConstraints(constraints) snapped = ics.snap_to_minimum(universe) if expected is None: assert snapped is None else: assert snapped == InterpreterConstraints([expected])
async def export_venv(request: ExportedVenvRequest, python_setup: PythonSetup, pex_env: PexEnvironment) -> ExportableData: # Pick a single interpreter for the venv. interpreter_constraints = InterpreterConstraints.create_from_targets( request.targets, python_setup) if not interpreter_constraints: # If there were no targets that defined any constraints, fall back to the global ones. interpreter_constraints = InterpreterConstraints( python_setup.interpreter_constraints) min_interpreter = interpreter_constraints.snap_to_minimum( python_setup.interpreter_universe) if not min_interpreter: raise ExportError( "The following interpreter constraints were computed for all the targets for which " f"export was requested: {interpreter_constraints}. There is no python interpreter " "compatible with these constraints. Please restrict the target set to one that shares " "a compatible interpreter.") venv_pex = await Get( VenvPex, PexFromTargetsRequest, PexFromTargetsRequest.for_requirements( (tgt.address for tgt in request.targets), internal_only=True, hardcoded_interpreter_constraints=min_interpreter, ), ) complete_pex_env = pex_env.in_workspace() venv_abspath = os.path.join(complete_pex_env.pex_root, venv_pex.venv_rel_dir) # Run the venv_pex to get the full python version (including patch #), so we # can use it in the symlink name. res = await Get( ProcessResult, VenvPexProcess( venv_pex=venv_pex, description="Create virtualenv", argv=[ "-c", "import sys; print('.'.join(str(x) for x in sys.version_info[0:3]))" ], input_digest=venv_pex.digest, ), ) py_version = res.stdout.strip().decode() return ExportableData( f"virtualenv for {min_interpreter}", os.path.join("python", "virtualenv"), symlinks=[Symlink(venv_abspath, py_version)], )
async def export_virtualenv(request: _ExportVenvRequest, python_setup: PythonSetup, pex_pex: PexPEX) -> ExportResult: if request.resolve: interpreter_constraints = InterpreterConstraints( python_setup.resolves_to_interpreter_constraints.get( request.resolve, python_setup.interpreter_constraints)) else: interpreter_constraints = InterpreterConstraints.create_from_targets( request.root_python_targets, python_setup) or InterpreterConstraints( python_setup.interpreter_constraints) min_interpreter = interpreter_constraints.snap_to_minimum( python_setup.interpreter_universe) if not min_interpreter: err_msg = (( f"The resolve '{request.resolve}' (from `[python].resolves`) has invalid interpreter " f"constraints, which are set via `[python].resolves_to_interpreter_constraints`: " f"{interpreter_constraints}. Could not determine the minimum compatible interpreter." ) if request.resolve else ( "The following interpreter constraints were computed for all the targets for which " f"export was requested: {interpreter_constraints}. There is no python interpreter " "compatible with these constraints. Please restrict the target set to one that shares " "a compatible interpreter.")) raise ExportError(err_msg) requirements_pex = await Get( Pex, RequirementsPexRequest( (tgt.address for tgt in request.root_python_targets), internal_only=True, hardcoded_interpreter_constraints=min_interpreter, ), ) # Get the full python version (including patch #), so we can use it as the venv name. res = await Get( ProcessResult, PexProcess( pex=requirements_pex, description="Get interpreter version", argv=[ "-c", "import sys; print('.'.join(str(x) for x in sys.version_info[0:3]))" ], ), ) py_version = res.stdout.strip().decode() dest = (os.path.join("python", "virtualenvs", path_safe(request.resolve)) if request.resolve else os.path.join("python", "virtualenv")) merged_digest = await Get( Digest, MergeDigests([pex_pex.digest, requirements_pex.digest])) pex_pex_path = os.path.join("{digest_root}", pex_pex.exe) return ExportResult( f"virtualenv for the resolve '{request.resolve}' (using {min_interpreter})", dest, digest=merged_digest, post_processing_cmds=[ PostProcessingCommand( [ pex_pex_path, os.path.join("{digest_root}", requirements_pex.name), "venv", "--pip", "--collisions-ok", "--remove=all", f"{{digest_root}}/{py_version}", ], {"PEX_MODULE": "pex.tools"}, ), PostProcessingCommand(["rm", "-f", pex_pex_path]), ], )