|
32 | 32 | @author: Jens Timmerman (Ghent University) |
33 | 33 | @author: Alexander Grund (TU Dresden) |
34 | 34 | """ |
35 | | -import json |
36 | 35 | import os |
37 | 36 | import re |
38 | 37 | import sys |
|
43 | 42 | import easybuild.tools.environment as env |
44 | 43 | from easybuild.base import fancylogger |
45 | 44 | from easybuild.easyblocks.python import EXTS_FILTER_PYTHON_PACKAGES |
| 45 | +from easybuild.easyblocks.python import det_installed_python_packages, det_pip_version, run_pip_check |
46 | 46 | from easybuild.framework.easyconfig import CUSTOM |
47 | 47 | from easybuild.framework.easyconfig.default import DEFAULT_CONFIG |
48 | 48 | from easybuild.framework.easyconfig.templates import PYPI_SOURCE |
@@ -295,27 +295,6 @@ def get_pylibdirs(python_cmd): |
295 | 295 | return all_pylibdirs |
296 | 296 |
|
297 | 297 |
|
298 | | -def det_pip_version(python_cmd='python'): |
299 | | - """Determine version of currently active 'pip' module.""" |
300 | | - |
301 | | - pip_version = None |
302 | | - log = fancylogger.getLogger('det_pip_version', fname=False) |
303 | | - log.info("Determining pip version...") |
304 | | - |
305 | | - res = run_shell_cmd("%s -m pip --version" % python_cmd, hidden=True) |
306 | | - out = res.output |
307 | | - |
308 | | - pip_version_regex = re.compile('^pip ([0-9.]+)') |
309 | | - res = pip_version_regex.search(out) |
310 | | - if res: |
311 | | - pip_version = res.group(1) |
312 | | - log.info("Found pip version: %s", pip_version) |
313 | | - else: |
314 | | - log.warning("Failed to determine pip version from '%s' using pattern '%s'", out, pip_version_regex.pattern) |
315 | | - |
316 | | - return pip_version |
317 | | - |
318 | | - |
319 | 298 | def det_py_install_scheme(python_cmd='python'): |
320 | 299 | """ |
321 | 300 | Try to determine active installation scheme used by Python. |
@@ -398,108 +377,6 @@ def symlink_dist_site_packages(install_dir, pylibdirs): |
398 | 377 | symlink(dist_pkgs, site_pkgs_path, use_abspath_source=False) |
399 | 378 |
|
400 | 379 |
|
401 | | -def det_installed_python_packages(names_only=True, python_cmd=None): |
402 | | - """ |
403 | | - Return list of Python packages that are installed |
404 | | -
|
405 | | - Note that the names are reported by pip and might be different to the name that need to be used to import it. |
406 | | -
|
407 | | - :param names_only: boolean indicating whether only names or full info from `pip list` should be returned |
408 | | - :param python_cmd: Python command to use (if None, 'python' is used) |
409 | | - """ |
410 | | - log = fancylogger.getLogger('det_installed_python_packages', fname=False) |
411 | | - |
412 | | - if python_cmd is None: |
413 | | - python_cmd = 'python' |
414 | | - |
415 | | - # Check installed Python packages |
416 | | - cmd = ' '.join([ |
417 | | - python_cmd, '-m', 'pip', |
418 | | - 'list', |
419 | | - '--isolated', |
420 | | - '--disable-pip-version-check', |
421 | | - '--format', 'json', |
422 | | - ]) |
423 | | - res = run_shell_cmd(cmd, fail_on_error=False, hidden=True) |
424 | | - if res.exit_code: |
425 | | - raise EasyBuildError(f'Failed to determine installed python packages: {res.output}') |
426 | | - |
427 | | - # only check stdout, not stderr which might contain user facing warnings |
428 | | - log.info(f'Got list of installed Python packages: {res.output}') |
429 | | - pkgs = json.loads(res.output.strip()) |
430 | | - return [pkg['name'] for pkg in pkgs] if names_only else pkgs |
431 | | - |
432 | | - |
433 | | -def run_pip_check(python_cmd=None, unversioned_packages=None): |
434 | | - """ |
435 | | - Check installed Python packages using 'pip check' |
436 | | -
|
437 | | - :param unversioned_packages: list of Python packages to exclude in the version existence check |
438 | | - :param python_cmd: Python command to use (if None, 'python' is used) |
439 | | - """ |
440 | | - log = fancylogger.getLogger('det_installed_python_packages', fname=False) |
441 | | - |
442 | | - if python_cmd is None: |
443 | | - python_cmd = 'python' |
444 | | - if unversioned_packages is None: |
445 | | - unversioned_packages = [] |
446 | | - |
447 | | - pip_check_cmd = f"{python_cmd} -m pip check" |
448 | | - |
449 | | - pip_version = det_pip_version(python_cmd=python_cmd) |
450 | | - if not pip_version: |
451 | | - raise EasyBuildError("Failed to determine pip version!") |
452 | | - min_pip_version = LooseVersion('9.0.0') |
453 | | - if LooseVersion(pip_version) < min_pip_version: |
454 | | - raise EasyBuildError(f"pip >= {min_pip_version} is required for '{pip_check_cmd}', found {pip_version}") |
455 | | - |
456 | | - pip_check_errors = [] |
457 | | - |
458 | | - res = run_shell_cmd(pip_check_cmd, fail_on_error=False) |
459 | | - if res.exit_code: |
460 | | - pip_check_errors.append(f"`{pip_check_cmd}` failed:\n{res.output}") |
461 | | - else: |
462 | | - log.info(f"`{pip_check_cmd}` passed successfully") |
463 | | - |
464 | | - # Also check for a common issue where the package version shows up as 0.0.0 often caused |
465 | | - # by using setup.py as the installation method for a package which is released as a generic wheel |
466 | | - # named name-version-py2.py3-none-any.whl. `tox` creates those from version controlled source code |
467 | | - # so it will contain a version, but the raw tar.gz does not. |
468 | | - pkgs = det_installed_python_packages(names_only=False, python_cmd=python_cmd) |
469 | | - faulty_version = '0.0.0' |
470 | | - faulty_pkg_names = [pkg['name'] for pkg in pkgs if pkg['version'] == faulty_version] |
471 | | - |
472 | | - for unversioned_package in unversioned_packages: |
473 | | - try: |
474 | | - faulty_pkg_names.remove(unversioned_package) |
475 | | - log.debug(f"Excluding unversioned package '{unversioned_package}' from check") |
476 | | - except ValueError: |
477 | | - try: |
478 | | - version = next(pkg['version'] for pkg in pkgs if pkg['name'] == unversioned_package) |
479 | | - except StopIteration: |
480 | | - msg = f"Package '{unversioned_package}' in unversioned_packages was not found in " |
481 | | - msg += "the installed packages. Check that the name from `python -m pip list` is used " |
482 | | - msg += "which may be different than the module name." |
483 | | - else: |
484 | | - msg = f"Package '{unversioned_package}' in unversioned_packages has a version of {version} " |
485 | | - msg += "which is valid. Please remove it from unversioned_packages." |
486 | | - pip_check_errors.append(msg) |
487 | | - |
488 | | - log.info("Found %s invalid packages out of %s packages", len(faulty_pkg_names), len(pkgs)) |
489 | | - if faulty_pkg_names: |
490 | | - faulty_pkg_names_str = '\n'.join(faulty_pkg_names) |
491 | | - msg = "The following Python packages were likely not installed correctly because they show a " |
492 | | - msg += f"version of '{faulty_version}':\n{faulty_pkg_names_str}\n" |
493 | | - msg += "This may be solved by using a *-none-any.whl file as the source instead. " |
494 | | - msg += "See e.g. the SOURCE*_WHL templates.\n" |
495 | | - msg += "Otherwise you could check if the package provides a version at all or if e.g. poetry is " |
496 | | - msg += "required (check the source for a pyproject.toml and see PEP517 for details on that)." |
497 | | - pip_check_errors.append(msg) |
498 | | - |
499 | | - if pip_check_errors: |
500 | | - raise EasyBuildError('\n'.join(pip_check_errors)) |
501 | | - |
502 | | - |
503 | 380 | class PythonPackage(ExtensionEasyBlock): |
504 | 381 | """Builds and installs a Python package, and provides a dedicated module file.""" |
505 | 382 |
|
|
0 commit comments