Commit a4303921 authored by Xavier Thompson's avatar Xavier Thompson

[wkrd] Use pip install --editable --user

Prior to pip 21.1, pip install --editable --target fails because it
results in wrong parameters being passed to setup.py develop by pip.

Prior to setuptools 45.2.0, both pip install --editable --target and
pip install --editable --prefix fail because the temporary install
directory used internally by pip is not added to PYTHONPATH prior
to pip calling setup.py develop. In later version setuptools emits a
warning instead of an error.

Temporarily override PYTHONUSERBASE to point to the target directory,
so as to emulate --prefix=<dir> with PYTHONUSERBASE=<dir> and --user.

This is needed for Python2 because pip 21.1 and setuptools 45.2.0 are
both Python3 only.
parent c1b47cc5
...@@ -115,6 +115,10 @@ pip_path = setuptools_path = [ ...@@ -115,6 +115,10 @@ pip_path = setuptools_path = [
] ]
pip_pythonpath = setuptools_pythonpath = os.pathsep.join(pip_path) pip_pythonpath = setuptools_pythonpath = os.pathsep.join(pip_path)
pip_version = pkg_resources.working_set.find(
pkg_resources.Requirement.parse('pip')
).parsed_version
python_lib = distutils.sysconfig.get_python_lib() python_lib = distutils.sysconfig.get_python_lib()
FILE_SCHEME = re.compile('file://', re.I).match FILE_SCHEME = re.compile('file://', re.I).match
...@@ -1117,7 +1121,7 @@ def _rm(*paths): ...@@ -1117,7 +1121,7 @@ def _rm(*paths):
_develop_distutils_scripts = {} _develop_distutils_scripts = {}
def _detect_distutils_scripts(directory): def _detect_distutils_scripts(egg_name, directory):
"""Record detected distutils scripts from develop eggs """Record detected distutils scripts from develop eggs
``setup.py develop`` doesn't generate metadata on distutils scripts, in ``setup.py develop`` doesn't generate metadata on distutils scripts, in
...@@ -1125,15 +1129,11 @@ def _detect_distutils_scripts(directory): ...@@ -1125,15 +1129,11 @@ def _detect_distutils_scripts(directory):
later. later.
""" """
dir_contents = os.listdir(directory) if not os.path.isdir(directory):
egginfo_filenames = [filename for filename in dir_contents
if filename.endswith('.egg-link')]
if not egginfo_filenames:
return return
egg_name = egginfo_filenames[0].replace('.egg-link', '')
marker = 'EASY-INSTALL-DEV-SCRIPT' marker = 'EASY-INSTALL-DEV-SCRIPT'
scripts_found = [] scripts_found = []
for filename in dir_contents: for filename in os.listdir(directory):
if filename.endswith('.exe'): if filename.endswith('.exe'):
continue continue
filepath = os.path.join(directory, filename) filepath = os.path.join(directory, filename)
...@@ -1192,12 +1192,11 @@ def develop(setup, dest, build_ext=None, executable=None, verbosity=-10): ...@@ -1192,12 +1192,11 @@ def develop(setup, dest, build_ext=None, executable=None, verbosity=-10):
class options: pass class options: pass
options._allow_picked_versions = allow_picked_versions() options._allow_picked_versions = allow_picked_versions()
call_pip_editable(directory, tmp3, options, verbosity=verbosity) lib_dir, scripts_dir = call_pip_editable(
directory, tmp3, options, verbosity=verbosity)
_detect_distutils_scripts(tmp3)
egg_link, dist_info = [], [] egg_link, dist_info = [], []
for entry in os.listdir(tmp3): for entry in os.listdir(lib_dir):
if entry.endswith('.egg-link'): if entry.endswith('.egg-link'):
egg_link.append(entry) egg_link.append(entry)
if entry.endswith('.dist-info') or entry.endswith('.egg-info'): if entry.endswith('.dist-info') or entry.endswith('.egg-info'):
...@@ -1206,11 +1205,14 @@ def develop(setup, dest, build_ext=None, executable=None, verbosity=-10): ...@@ -1206,11 +1205,14 @@ def develop(setup, dest, build_ext=None, executable=None, verbosity=-10):
assert len(egg_link) + len(dist_info) == 1, str(egg_link + dist_info) assert len(egg_link) + len(dist_info) == 1, str(egg_link + dist_info)
entry, = (egg_link or dist_info) entry, = (egg_link or dist_info)
entry_path = os.path.join(tmp3, entry) entry_path = os.path.join(lib_dir, entry)
entry_filename, ext = os.path.splitext(entry) entry_filename, ext = os.path.splitext(entry)
project_name = entry_filename.split('-', 1)[0] project_name = entry_filename.split('-', 1)[0]
_detect_distutils_scripts(
pkg_resources.safe_name(project_name), scripts_dir)
def move(src, dst): def move(src, dst):
_rm(dst) _rm(dst)
os.rename(src, dst) os.rename(src, dst)
...@@ -1239,7 +1241,7 @@ def develop(setup, dest, build_ext=None, executable=None, verbosity=-10): ...@@ -1239,7 +1241,7 @@ def develop(setup, dest, build_ext=None, executable=None, verbosity=-10):
"paths = {p: list(s.submodule_search_locations)" "paths = {p: list(s.submodule_search_locations)"
" for p, s in zip(packages, specs)}; " " for p, s in zip(packages, specs)}; "
"print(json.dumps(paths))" % { "print(json.dumps(paths))" % {
'site': tmp3, 'site': lib_dir,
'packages': tuple(p for p in top_level if p), 'packages': tuple(p for p in top_level if p),
} }
], ],
...@@ -1941,10 +1943,45 @@ def call_pip_editable(path, dest, options, verbosity=-10): ...@@ -1941,10 +1943,45 @@ def call_pip_editable(path, dest, options, verbosity=-10):
""" """
Call `pip install --editable` from a subprocess to install a Call `pip install --editable` from a subprocess to install a
the project in `path` into `dest` in editable mode. the project in `path` into `dest` in editable mode.
Returns the actual installation and script directories.
""" """
call_pip_command( if (pip_version >= pkg_resources.parse_version('21.1') and
['install', '-t', dest], ['--editable', path], setuptools_version >= pkg_resources.parse_version('45.2.0')):
options, verbosity) call_pip_command(
['install', '-t', dest], ['--editable', path],
options, verbosity)
return dest, dest
else:
# BBB pip < 21.1, setuptools < 45.2.0, Python2
# Combining pip --editable <src> --target <dest> fails in pip<21.1.
# See https://github.com/pypa/pip/issues/562
# See https://github.com/pypa/pip/pull/9636
# Also --editable with --target/--prefix fails with setuptools<45.2.0.
# See https://github.com/pypa/pip/issues/7627
# See https://github.com/pypa/setuptools/pull/1941
# Workaround: PYTHONUSERBASE=<dest> pip --editable <src> --user
# The install directory will be an OS-dependent subpath of <dest>.
if os.name == 'posix':
python = 'python%d.%d' % sys.version_info[0:2]
lib_dir = os.path.join(dest, 'lib', python, 'site-packages')
script_dir = os.path.join(dest, 'bin')
else:
lib_dir = os.join(dest, 'Lib', 'site-packages')
script_dir = os.path.join(dest, 'Scripts')
for install_path in (lib_dir, script_dir):
os.makedirs(install_path)
old = {k: os.environ.get(k)
for k in ('PYTHONUSERBASE', 'PYTHONNOUSERSITE')}
try:
os.environ['PYTHONUSERBASE'] = dest
os.environ.pop('PYTHONNOUSERSITE', None)
call_pip_command(
['install', '--user'], ['--editable', path],
options, verbosity)
finally:
for k, v in old.items():
(os.environ.pop if v is None else os.environ.__setitem__)(k, v)
return lib_dir, script_dir
def call_pip_wheel(spec, dest, options): def call_pip_wheel(spec, dest, options):
......
...@@ -216,7 +216,7 @@ We should be able to deal with setup scripts that aren't setuptools based. ...@@ -216,7 +216,7 @@ We should be able to deal with setup scripts that aren't setuptools based.
Installing... Installing...
Develop: '/sample-buildout/foo' Develop: '/sample-buildout/foo'
Running pip install --editable /sample-buildout/foo Running pip install --editable /sample-buildout/foo
... -m pip install -t ... --editable /sample-buildout/foo ... -m pip install ... --editable /sample-buildout/foo
... ...
""" """
......
  • I don't know if this is related but python2 tests seem to fail in loop https://erp5.nexedi.net/test_result_module/20240908-1310003AB

    2024-09-09 01:54:19,002 INFO     $ PATH=/srv/slapgrid/slappart6/srv/slapos/soft/53fcf681ef67cd4481fe1120e21c79d7/bin:/opt/slapos/parts/bison/bin:/opt/slapos/parts/bzip2/bin:/opt/slapos/parts/gettext/bin:/opt/slapos/parts/glib/bin:/opt/slapos/parts/libxml2/bin:/opt/slapos/parts/libxslt/bin:/opt/slapos/parts/m4/bin:/opt/slapos/parts/ncurses/bin:/opt/slapos/parts/openssl/bin:/opt/slapos/parts/pkgconfig/bin:/opt/slapos/parts/python3/bin:/opt/slapos/parts/readline/bin:/opt/slapos/parts/sqlite3/bin:/opt/slapos/parts/swig/bin:/opt/slapos/bin:/opt/slapos/parts/patch/bin:/opt/slapos/parts/socat/bin:/usr/bin:/usr/sbin:/sbin:/bin SLAPOS_TEST_LOG_DIRECTORY=/srv/slapgrid/slappart6/var/log/testnode/ddv-gcczHmoEQs SLAPOS_TEST_SHARED_PART_LIST=:/srv/slapgrid/slappart6/srv/shared:/srv/slapgrid/slappart6/t/ddv/shared /srv/slapgrid/slappart6/t/ddv/soft/fff494c099a0d2609cd92b10b9b49fdf/bin/runTestSuite_py /srv/slapgrid/slappart6/t/ddv/i/0/bin/runTestSuite --master_url $DISTRIBUTOR_URL --revision slapos=17024-9085318c2a9f896d28d1c2fab1fe68e630ccd6cf,slapos.buildout=1818-fb45b3e5e43a8ab7246d05e5f8ad7dce94c8e03d --test_node_title rapidspace-testnode-009-3Nodes-ERP5PROJECT1 --test_suite SLAPOS --test_suite_title SlapOS.Buildout.UnitTest-Master.Python2 --project_title 'Rapid.Space Project'
    2024-09-09 01:54:19,812 INFO     runTestSuite: Creating directory '/srv/slapgrid/slappart6/t/ddv/test_suite/eggs'.
    2024-09-09 01:54:19,813 INFO     runTestSuite: Creating directory '/srv/slapgrid/slappart6/t/ddv/test_suite/bin'.
    2024-09-09 01:54:19,813 INFO     runTestSuite: Creating directory '/srv/slapgrid/slappart6/t/ddv/test_suite/parts'.
    2024-09-09 01:54:19,813 INFO     runTestSuite: Creating directory '/srv/slapgrid/slappart6/t/ddv/test_suite/develop-eggs'.
    2024-09-09 01:54:19,813 INFO     runTestSuite: Develop: '/srv/slapgrid/slappart6/t/ddv/i/0/parts/slapos.buildout-repository'
    2024-09-09 01:54:22,429 INFO     runTestSuite:     ERROR: Command errored out with exit status 1:
    2024-09-09 01:54:22,430 INFO     runTestSuite:      command: /srv/slapgrid/slappart6/t/ddv/shared/python2.7/0caabaf5db1645243f5d32834d6c3d6e/bin/python2.7 -c 'import sys, setuptools, tokenize; sys.argv[0] = '"'"'/srv/slapgrid/slappart6/t/ddv/i/0/parts/slapos.buildout-repository/setup.py'"'"'; __file__='"'"'/srv/slapgrid/slappart6/t/ddv/i/0/parts/slapos.buildout-repository/setup.py'"'"';f=getattr(tokenize, '"'"'open'"'"', open)(__file__);code=f.read().replace('"'"'\r\n'"'"', '"'"'\n'"'"');f.close();exec(compile(code, __file__, '"'"'exec'"'"'))' develop --no-deps --user --prefix=
    2024-09-09 01:54:22,430 INFO     runTestSuite:          cwd: /srv/slapgrid/slappart6/t/ddv/i/0/parts/slapos.buildout-repository/
    2024-09-09 01:54:22,430 INFO     runTestSuite:     Complete output (6 lines):
    2024-09-09 01:54:22,430 INFO     runTestSuite:     usage: setup.py [global_opts] cmd1 [cmd1_opts] [cmd2 [cmd2_opts] ...]
    2024-09-09 01:54:22,430 INFO     runTestSuite:        or: setup.py --help [cmd1 cmd2 ...]
    2024-09-09 01:54:22,430 INFO     runTestSuite:        or: setup.py --help-commands
    2024-09-09 01:54:22,430 INFO     runTestSuite:        or: setup.py cmd --help
    2024-09-09 01:54:22,430 INFO     runTestSuite:     
    2024-09-09 01:54:22,430 INFO     runTestSuite:     error: option --user not recognized
    2024-09-09 01:54:22,430 INFO     runTestSuite:     ----------------------------------------
    2024-09-09 01:54:22,430 INFO     runTestSuite: ERROR: Command errored out with exit status 1: /srv/slapgrid/slappart6/t/ddv/shared/python2.7/0caabaf5db1645243f5d32834d6c3d6e/bin/python2.7 -c 'import sys, setuptools, tokenize; sys.argv[0] = '"'"'/srv/slapgrid/slappart6/t/ddv/i/0/parts/slapos.buildout-repository/setup.py'"'"'; __file__='"'"'/srv/slapgrid/slappart6/t/ddv/i/0/parts/slapos.buildout-repository/setup.py'"'"';f=getattr(tokenize, '"'"'open'"'"', open)(__file__);code=f.read().replace('"'"'\r\n'"'"', '"'"'\n'"'"');f.close();exec(compile(code, __file__, '"'"'exec'"'"'))' develop --no-deps --user --prefix= Check the logs for full command output.
    2024-09-09 01:54:22,763 INFO     runTestSuite: Traceback (most recent call last):
    2024-09-09 01:54:22,763 INFO     runTestSuite:   File "/srv/slapgrid/slappart6/t/ddv/soft/fff494c099a0d2609cd92b10b9b49fdf/bin/runTestSuite_py", line 37, in <module>
    2024-09-09 01:54:22,763 INFO     runTestSuite:     exec(compile(__file__f.read(), __file__, "exec"))
    2024-09-09 01:54:22,763 INFO     runTestSuite:   File "/srv/slapgrid/slappart6/t/ddv/i/0/bin/runTestSuite", line 139, in <module>
    2024-09-09 01:54:22,763 INFO     runTestSuite:     main()
    2024-09-09 01:54:22,763 INFO     runTestSuite:   File "/srv/slapgrid/slappart6/t/ddv/i/0/bin/runTestSuite", line 129, in main
    2024-09-09 01:54:22,763 INFO     runTestSuite:     Buildout('buildout.cfg', {}).install(None)
    2024-09-09 01:54:22,763 INFO     runTestSuite:   File "/srv/slapgrid/slappart6/t/ddv/soft/fff494c099a0d2609cd92b10b9b49fdf/eggs/zc.buildout-3.0.1+slapos004-py2.7.egg/zc/buildout/buildout.py", line 855, in install
    2024-09-09 01:54:22,763 INFO     runTestSuite:     self._install_parts(install_args)
    2024-09-09 01:54:22,763 INFO     runTestSuite:   File "/srv/slapgrid/slappart6/t/ddv/soft/fff494c099a0d2609cd92b10b9b49fdf/eggs/zc.buildout-3.0.1+slapos004-py2.7.egg/zc/buildout/buildout.py", line 890, in _install_parts
    2024-09-09 01:54:22,763 INFO     runTestSuite:     installed_develop_eggs = self._develop()
    2024-09-09 01:54:22,764 INFO     runTestSuite:   File "/srv/slapgrid/slappart6/t/ddv/soft/fff494c099a0d2609cd92b10b9b49fdf/eggs/zc.buildout-3.0.1+slapos004-py2.7.egg/zc/buildout/buildout.py", line 1139, in _develop
    2024-09-09 01:54:22,764 INFO     runTestSuite:     verbosity=-20)
    2024-09-09 01:54:22,764 INFO     runTestSuite:   File "/srv/slapgrid/slappart6/t/ddv/soft/fff494c099a0d2609cd92b10b9b49fdf/eggs/zc.buildout-3.0.1+slapos004-py2.7.egg/zc/buildout/easy_install.py", line 1253, in develop
    2024-09-09 01:54:22,764 INFO     runTestSuite:     directory, tmp3, options, verbosity=verbosity)
    2024-09-09 01:54:22,764 INFO     runTestSuite:   File "/srv/slapgrid/slappart6/t/ddv/soft/fff494c099a0d2609cd92b10b9b49fdf/eggs/zc.buildout-3.0.1+slapos004-py2.7.egg/zc/buildout/easy_install.py", line 2051, in call_pip_editable
    2024-09-09 01:54:22,764 INFO     runTestSuite:     options, verbosity)
    2024-09-09 01:54:22,764 INFO     runTestSuite:   File "/srv/slapgrid/slappart6/t/ddv/soft/fff494c099a0d2609cd92b10b9b49fdf/eggs/zc.buildout-3.0.1+slapos004-py2.7.egg/zc/buildout/easy_install.py", line 2007, in call_pip_command
    2024-09-09 01:54:22,764 INFO     runTestSuite:     subprocess.check_call(args, env=env)
    2024-09-09 01:54:22,764 INFO     runTestSuite:   File "/srv/slapgrid/slappart6/t/ddv/shared/python2.7/0caabaf5db1645243f5d32834d6c3d6e/lib/python2.7/subprocess.py", line 190, in check_call
    2024-09-09 01:54:22,764 INFO     runTestSuite:     raise CalledProcessError(retcode, cmd)
    2024-09-09 01:54:22,764 INFO     runTestSuite: subprocess.CalledProcessError: Command '['/srv/slapgrid/slappart6/t/ddv/shared/python2.7/0caabaf5db1645243f5d32834d6c3d6e/bin/python2.7', '-m', 'pip', 'install', '--user', '--no-deps', '-qq', '--no-cache-dir', '--editable', '/srv/slapgrid/slappart6/t/ddv/i/0/parts/slapos.buildout-repository']' returned non-zero exit status 1
  • Thanks @jerome for pointing this out. This is indeed related.

    After investigating for a while, I found what was going on: the buildout.cfg of buildout-testing's runTestSuite (used for setting up the tests) does not set allow-picked-versions=false, thus pip uses build isolation (the default for pip) when installing running pip install --editable <buildout-repository> to install buildout in develop mode from sources (for the tests), which a) reinstalls setuptools and b) breaks --user option.

    So the fix I have in mind will be to force --no-build-isolation when we're in this # BBB pip < 21.1, setuptools < 45.2.0, Python2 case.

  • I think this is fixed in !35 (merged) @xavier_thompson ?

    If yes, could you mark this thread as resolved ?

  • this is a comment on a commit so we can't mark it as "Resolved". But it is "resolved". I don't know why this comment is displayed on !34 (merged)

Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment