Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Is hatchling really needed? #120

Closed
frenzymadness opened this issue Jan 31, 2022 · 58 comments
Closed

Is hatchling really needed? #120

frenzymadness opened this issue Jan 31, 2022 · 58 comments

Comments

@frenzymadness
Copy link
Contributor

Hello.

I wanted to update hatch and userpath in Fedora rawhide but I see that they use a new build backend hatchling. What is the motivation for using your own build backend? It's making my situation much harder because if I want to update the mentioned package, I need to create a new one. And the new one seems to use itself for the build. Also, where are the tests for hatchling? Is there any plan to make it a separate project?

@ofek
Copy link
Sponsor Collaborator

ofek commented Jan 31, 2022

Hello again!

What is the motivation for using your own build backend?

Hatch is now more like Poetry & Flit which have their own build backends, poetry-core and flit-core, respectively. The most unique thing about Hatchling is its plugin system.

situation much harder [...] seems to use itself for the build

Yeah based on https://www.python.org/dev/peps/pep-0517/#in-tree-build-backends they all do that:

Maybe this helps? https://src.fedoraproject.org/rpms/python-poetry-core/blob/rawhide/f/python-poetry-core.spec

where are the tests for hatchling?

They are mixed in with Hatch https://github.com/ofek/hatch/tree/master/tests/backend. However, https://github.com/ofek/hatch/tree/master/tests/backend/downstream is standalone and I can move it tonight!

Is there any plan to make it a separate project?

No, one repo is easier to develop on.

@stefanor
Copy link

stefanor commented Feb 2, 2022

Hi from Debian, I'm here for the same reason (building userpath).

Currently hatchling's version seems unrelated to hatch, is it expected to stay that way? Normally we'd build both of these modules from the same source tree (they share docs, tests, etc.), but that gets messy if they have separate versions.

@ofek
Copy link
Sponsor Collaborator

ofek commented Feb 2, 2022

Hey! Yes they are unrelated, and I'd be reluctant to change that. There is precedence:

Poetry:

PDM:

It's best to conceptualize them as separate projects.

Are tests & docs required when packaging for distros like Debian & Fedora?

@frenzymadness
Copy link
Contributor Author

Are tests & docs required when packaging for distros like Debian & Fedora?

Not strictly required in Fedora but it's very good to have them. Especially when we package a new Python version sooner than others. Also, tests can uncover incompatibilities in dependencies faster than dependant packages.

Let me mention a few points, why the current situation is not ideal from our point of view:

  • we cannot produce two RPM packages from the single source tarball because they don't share the same version
  • a source RPM package contains sources and a specfile (recipe saying how to build the package) and it'd be bigger than necessary because hatch would contain also hatchling's sources and vice versa - that's for the case if we create two separated packages from the single repo
  • comparing tags are harder - when I do an update in Fedora, I usually compare tags upstream to see what's new between two versions. This is harder because the diff is mixed for hatch and hatchling together.

Poetry and poetry-core are similar but because they are separated packages, we can package them as they are and treat them individually.

@stefanor
Copy link

stefanor commented Feb 2, 2022

I do like the idea of having the pep517 backend being a minimal library, that doesn't bring in all the baggage that the main CLI does, 👍 for following that trend.

In poetry-core and pdm-pep517's cases, they are a separate github repo for the pep517 plugin, together with their own tests.

The difficulty we have here is that if we like to be able to run the upstream test suites, when possible:

  1. At package build time
  2. When dependencies change, to catch breakage as soon as possible

The tests aren't in the source tarball on PyPI, only in the github repo. The github repo contains both hatch and hatchling, as well as their tests. So either we do:

  1. One source package (from the github repo) builds both hatch and hatchling (but then we preferably need shared versions)
  2. Two source packages, both from the github repo, both containing the source for both libraries, but each building a different library as the binary.
  3. Two source packages, from PyPI sources, without tests, each building their own library.

2 seems like the best option, but it involves annoying duplication.

@frenzymadness
Copy link
Contributor Author

2. Two source packages, both from the github repo, both containing the source for both libraries, but each building a different library as the binary.

The problem with this option also is that we'd like to run tests of hatch with hatchling from the RPM package and vice versa so we can check that the hatchling is correctly packaged during the build of hatch. That would be harder when both projects share the same repo because they see each other and probably ignore system site-packages.

@stefanor
Copy link

stefanor commented Feb 2, 2022

Good point. That can be worked around by deleting one of the libraries, of course. But if they're intertwined, that gets messy.

@ofek
Copy link
Sponsor Collaborator

ofek commented Feb 5, 2022

harder when both projects share the same repo because they see each other and probably ignore system site-packages

No path manipulation is done, but to make it extra explicit I switched each to a src-layout structure so Python adding the current directory to sys.path also does not matter.

compare tags upstream to see what's new between two versions. This is harder because the diff is mixed for hatch and hatchling together.

I think you can just do:

git diff hatchling-vX.Y.Z..hatchling-vX.Y.Z -- backend
git diff hatch-vX.Y.Z..hatch-vX.Y.Z -- . :^backend

The tests aren't in the source tarball on PyPI

Hatchling now ships the downstream tests https://pypi.org/project/hatchling/0.11.0/

@musicinmybrain
Copy link
Contributor

I’m currently investigating packaging hatchling and using it for hatch and userpath in Fedora.

It’s too early to tell if I will encounter any serious issues, but I very much appreciate the switch to src layout and the addition of tests to the hatchling sdist.

@ofek
Copy link
Sponsor Collaborator

ofek commented Feb 5, 2022

Please let me know if I can be of further assistance to all of you. I greatly appreciate your efforts on this 🙏

@musicinmybrain
Copy link
Contributor

It looks like the tests for hatchling start by building a copy of hatchling itself with a bogus incremented version:

https://github.com/ofek/hatch/blob/5df8cc50675d07a5dfc3064473e913ae01b01fb3/backend/tests/downstream/integrate.py#L89

…and this assumes the original hatch git repository structure with a backend directory. I can work around that with cp -rp . ../backend.

Then, they download (via git) a couple of external packages and build them. It looks like I could possibly do this part in an offline build environment by patching each data.json to contain an archive_url using URL scheme file:// instead of a repo_url and bundling the appropriate zip files as test data, although I haven’t gotten far enough along to try this.


What I’m seeing in the test that builds a copy of hatchling is that it wants to install fresh copies of hatchling’s dependencies in a virtualenv, presumably by downloading them from PyPI:

* Installing packages in isolated environment... (editables~=0.2; python_version > "3", importlib-metadata; python_version < "3.8", packaging~=20.9; python_version < "3", packaging~=21.3; python_version > "3", pathspec~=0.9, pluggy~=0.13; python_version < "3", pluggy~=1.0.0; python_version > "3", tomli>=1.2.2; python_version > "3", toml~=0.10.2; python_version < "3")

Traceback (most recent call last):
  File "/usr/lib/python3.10/site-packages/build/__main__.py", line 372, in main
    built = build_call(
  File "/usr/lib/python3.10/site-packages/build/__main__.py", line 202, in build_package
    out = _build(isolation, builder, outdir, distribution, config_settings, skip_dependency_check)
  File "/usr/lib/python3.10/site-packages/build/__main__.py", line 140, in _build
    return _build_in_isolated_env(builder, outdir, distribution, config_settings)
  File "/usr/lib/python3.10/site-packages/build/__main__.py", line 110, in _build_in_isolated_env
    env.install(builder.get_requires_for_build(distribution))
  File "/usr/lib/python3.10/site-packages/build/env.py", line 211, in install
    _subprocess(cmd)
  File "/usr/lib/python3.10/site-packages/build/env.py", line 81, in _subprocess
    raise e
  File "/usr/lib/python3.10/site-packages/build/env.py", line 78, in _subprocess
    subprocess.check_output(cmd, stderr=subprocess.STDOUT)
  File "/usr/lib64/python3.10/subprocess.py", line 420, in check_output
    return run(*popenargs, stdout=PIPE, timeout=timeout, check=True,
  File "/usr/lib64/python3.10/subprocess.py", line 524, in run
    raise CalledProcessError(retcode, process.args,
subprocess.CalledProcessError: Command '['/tmp/build-env-_cxjoa9d/bin/python', '-Im', 'pip', 'install', '--use-pep517', '--no-warn-script-location', '-r', '/tmp/build-reqs-c0n9rqjc.txt']' returned non-zero exit status 1.

Is it true that projects using hatchling must always download their dependencies from PyPI, and cannot use system copies of the dependencies? I haven’t read enough source code to be sure.

If dependencies must always be downloaded, it probably won’t be possible to package hatch 1.0, userpath (a dependency for pipx), or any other projects that use hatchling in major Linux distributions, since distribution packages are generally built in offline environments using system copies of their dependencies.

@ofek
Copy link
Sponsor Collaborator

ofek commented Feb 6, 2022

Is it true that projects using hatchling must always download their dependencies from PyPI, and cannot use system copies of the dependencies? I haven’t read enough source code to be sure.

No.

All that hacky test logic is to force pip to use the local version of hatchling rather than pull from PyPI/cache for each build. Here's an example from Discord of someone who recently adopted a similar approach https://github.com/scikit-build/scikit-build/blob/0b55cf13d5fd80e149cab456ef61240b7ee0d739/tests/conftest.py#L14-L29

How would you recommend I change the script to meet your needs?

@musicinmybrain
Copy link
Contributor

That makes sense. It occurred to me after I commented that hatchling was building itself successfully, so this was probably specific to the tests.

I’ll have to look more closely at what the script is doing. I see two obvious ways it could go:

  1. It’s straightforward enough to find a simple modification to the test script that would allow me to tell them where to find an existing copy of hatchling to test—something like PYTHONPATH—and bypass most of the hackery; and this turns out to be sufficient to run the tests offline without a lot of additonal hacking and patching.
  2. There is too much patching required for it to be worth getting these sample builds working offline, and I just proceed with a “smoke test” that verifies all the modules in hatchling at least import successfully. This is not too worrisome to me, because if there is a more subtle problem with the way I am building hatchling it will surely break the packages that build with it, and be found one way or another.

@musicinmybrain
Copy link
Contributor

I’ve decided to do without hatchling’s integration tests for now, and just do the import “smoke test”.

I’ve been trying to make a working package for hatch 1.0.0rc12, and I’m very close to having something I’m happy with. I’ve encountered the following:

  • It would be helpful if hatchling implemented the prepare_metadata_for_build_wheel hook, although I can reasonably work around this.
  • On a 64-bit platform, hatch is installed into lib64/python3.10/site-packages instead of lib/python3.10/site-packages, as if it were a binary/architecture-dependent package. I can work around this, too, but it seems like it’s probably not intentional.
  • If I run the tests (ignoring those in tests/backend since they belong to hatchling) most of the tests pass but quite a few fail with AttributeError: 'NoneType' object has no attribute 'version_tuple'. It doesn’t matter whether I invoke the tests with pytest --ignore='tests/backend' or rm -rf tests/backend; hatch run full. It seems like there’s something I’m not providing that would allow project versions to be set for these tests, but I’m having a hard time figuring out what it is.
[…]
_______________________________ test_python_data _______________________________

temp_dir = Path('/tmp/tmpuubm53ul')
platform = <hatch.utils.platform.Platform object at 0x7fa0d4bafa30>

    def test_python_data(temp_dir, platform):
        venv_dir = temp_dir / 'venv'
        venv = VirtualEnv(venv_dir, platform)
>       venv.create(sys.executable)

/builddir/build/BUILD/hatch-hatch-v1.0.0rc12/tests/venv/test_core.py:142: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
/builddir/build/BUILDROOT/hatch-1.0.0~rc12-1.fc36.x86_64/usr/lib/python3.10/site-packages/hatch/venv/core.py:57: in create
    cli_run(command)
/usr/lib/python3.10/site-packages/virtualenv/run/__init__.py:32: in cli_run
    of_session.run()
/usr/lib/python3.10/site-packages/virtualenv/run/session.py:47: in run
    self._seed()
/usr/lib/python3.10/site-packages/virtualenv/run/session.py:60: in _seed
    self.seeder.run(self.creator)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = FromAppData(extra_search_dir=/usr/share/python-wheels,download=False, pip=embed, setuptools=bundle, wheel=bundle, via=copy, app_data_dir=/builddir/.local/share/virtualenv)
creator = CPython3Posix(dest=/tmp/tmpuubm53ul/venv, clear=False, no_vcs_ignore=False, global=False)

    def run(self, creator):
        if not self.enabled:
            return
        self.insert_system_wheels_paths(creator)
        with self._get_seed_wheels(creator) as name_to_whl:
>           pip_version = name_to_whl["pip"].version_tuple if "pip" in name_to_whl else None
E           AttributeError: 'NoneType' object has no attribute 'version_tuple'

/usr/lib/python3.10/site-packages/virtualenv/seed/embed/via_app_data/via_app_data.py:44: AttributeError
=========================== short test summary info ============================
FAILED tests/cli/build/test_build.py::test_shipped - AttributeError: 'NoneTyp...
FAILED tests/cli/build/test_build.py::test_build_dependencies - AttributeErro...
FAILED tests/cli/env/test_create.py::test_new - AttributeError: 'NoneType' ob...
FAILED tests/cli/env/test_create.py::test_selected_absolute_directory - Attri...
FAILED tests/cli/env/test_create.py::test_selected_local_directory - Attribut...
FAILED tests/cli/env/test_create.py::test_enter_project_directory - Attribute...
FAILED tests/cli/env/test_create.py::test_already_created - AttributeError: '...
FAILED tests/cli/env/test_create.py::test_default - AttributeError: 'NoneType...
FAILED tests/cli/env/test_create.py::test_matrix - AttributeError: 'NoneType'...
FAILED tests/cli/env/test_create.py::test_incompatible_matrix_partial - Attri...
FAILED tests/cli/env/test_create.py::test_install_project_default_dev_mode - ...
FAILED tests/cli/env/test_create.py::test_install_project_no_dev_mode - Attri...
FAILED tests/cli/env/test_create.py::test_pre_install_commands - AttributeErr...
FAILED tests/cli/env/test_create.py::test_pre_install_commands_error - Attrib...
FAILED tests/cli/env/test_create.py::test_post_install_commands - AttributeEr...
FAILED tests/cli/env/test_create.py::test_post_install_commands_error - Attri...
FAILED tests/cli/env/test_create.py::test_sync_dependencies - AttributeError:...
FAILED tests/cli/env/test_create.py::test_features - AttributeError: 'NoneTyp...
FAILED tests/cli/env/test_find.py::test_single - AttributeError: 'NoneType' o...
FAILED tests/cli/env/test_find.py::test_matrix - AttributeError: 'NoneType' o...
FAILED tests/cli/env/test_prune.py::test_all - AttributeError: 'NoneType' obj...
FAILED tests/cli/env/test_remove.py::test_single - AttributeError: 'NoneType'...
FAILED tests/cli/env/test_remove.py::test_all - AttributeError: 'NoneType' ob...
FAILED tests/cli/env/test_remove.py::test_matrix_all - AttributeError: 'NoneT...
FAILED tests/cli/env/test_remove.py::test_active_override - AttributeError: '...
FAILED tests/cli/run/test_run.py::test_automatic_creation - AttributeError: '...
FAILED tests/cli/run/test_run.py::test_enter_project_directory - AttributeErr...
FAILED tests/cli/run/test_run.py::test_sync_dependencies - AttributeError: 'N...
FAILED tests/cli/run/test_run.py::test_scripts - AttributeError: 'NoneType' o...
FAILED tests/cli/run/test_run.py::test_scripts_specific_environment - Attribu...
FAILED tests/cli/run/test_run.py::test_scripts_no_environment - AssertionError: 
FAILED tests/cli/run/test_run.py::test_error - AttributeError: 'NoneType' obj...
FAILED tests/cli/run/test_run.py::test_matrix - AttributeError: 'NoneType' ob...
FAILED tests/cli/run/test_run.py::test_incompatible_matrix_partial - Attribut...
FAILED tests/cli/run/test_run.py::test_incompatible_missing_python - Attribut...
FAILED tests/cli/run/test_run.py::test_env_detection - AttributeError: 'NoneT...
FAILED tests/cli/run/test_run.py::test_env_detection_override - AttributeErro...
FAILED tests/venv/test_core.py::test_creation - AttributeError: 'NoneType' ob...
FAILED tests/venv/test_core.py::test_executables_directory - AttributeError: ...
FAILED tests/venv/test_core.py::test_activation - AttributeError: 'NoneType' ...
FAILED tests/venv/test_core.py::test_activation_path_env_var_missing - Attrib...
FAILED tests/venv/test_core.py::test_context_manager - AttributeError: 'NoneT...
FAILED tests/venv/test_core.py::test_creation_allow_system_packages - Attribu...
FAILED tests/venv/test_core.py::test_python_data - AttributeError: 'NoneType'...
================= 44 failed, 657 passed, 28 skipped in 27.12s ==================

@ofek
Copy link
Sponsor Collaborator

ofek commented Feb 13, 2022

On a 64-bit platform, hatch is installed into lib64/python3.10/site-packages instead of lib/python3.10/site-packages, as if it were a binary/architecture-dependent package.

Hmm, what determines that?

AttributeError: 'NoneType' object has no attribute 'version_tuple'

That's a virtualenv error about the embedded pip. @gaborbernat Do you have any ideas?

@frenzymadness
Copy link
Contributor Author

AttributeError: 'NoneType' object has no attribute 'version_tuple'

That's a virtualenv error about the embedded pip. @gaborbernat Do you have any ideas?

Virtualenv in Fedora does not use the embedded wheels, we have our own provided by RPM package python-pip-wheel so the pip in virtualenv is the same version as the system pip provided by python3-pip. But this is just a note. I believe that this should make no difference for hatch.

@gaborbernat
Copy link

The error basically shows that virtualenv fails to load the pip wheel to install it. Can someone create a Docker image repro of the problem for deep dive into the issue?

@musicinmybrain
Copy link
Contributor

I’ve determined that this AttributeError happens on any invocation of

virtualenv --pip embed

in Fedora, and that

virtualenv --pip bundle

works fine. So it’s easy to reproduce and not at all specific to hatch.

Unfortunately, that means the hatch functionality being tested really does fail at runtime in my experimental RPM package, not just in the RPM build environment.

I found a couple of issues filed upstream on virtualenv by Fedora packagers with similar issues (pypa/virtualenv#2283 pypa/virtualenv#2247). In both cases, upstream’s response was more or less “this is due to a Fedora patch, so you’re on your own.”

I haven’t been able to find any documentation about what the difference between embed and bundle is supposed to be. I assume there’s some good reason hatch chooses to use embed.

I’ve opened a Fedora issue to solicit discussion on whether this is expected behavior, and what, if anything, can be done about it.

@gaborbernat
Copy link

@musicinmybrain
Copy link
Contributor

You might want to read https://virtualenv.pypa.io/en/latest/user_guide.html#wheels

Interesting. Thanks for the pointer. It seems like Fedora’s python-virtualenv package tries to do the right thing as recommended in the ”Embed wheels for distributions” section, but something’s obviously not adding up for this use case. I think this part of the patch is probably where the None is coming from:

diff --git a/src/virtualenv/seed/wheels/embed/__init__.py b/src/virtualenv/seed/wheels/embed/__init__.py
index d38ccf8..4b80d98 100644
--- a/src/virtualenv/seed/wheels/embed/__init__.py
+++ b/src/virtualenv/seed/wheels/embed/__init__.py
@@ -48,8 +48,11 @@ BUNDLE_SUPPORT = {
 }
 MAX = "3.11"

+# Redefined here because bundled wheels are removed in RPM build
+BUNDLE_SUPPORT = None

 def get_embed_wheel(distribution, for_py_version):
+    return None  # BUNDLE_SUPPORT == None anyway
     path = BUNDLE_FOLDER / (BUNDLE_SUPPORT.get(for_py_version, {}) or BUNDLE_SUPPORT[MAX]).get(distribution)
     return Wheel.from_path(path)

Anyway, I’m curious what the core maintainers of the Python stack in Fedora have to say about the matter. I haven’t spent much time in the internals of these tools.

@stefanor
Copy link

I’ve decided to do without hatchling’s integration tests for now, and just do the import “smoke test”.

I came to the same decision, those tests all depend on pulling code from the Internet and executing it, which isn't acceptable in our build and test infrastructure.

If there were unit tests, or a fully off-line test suite, those would be better suited to our use-case.


RE Fedora's BUNDLE_SUPPORT patch:

FWIW, Debian's virtualenv patch-set basically just removes the bundled wheels: https://salsa.debian.org/python-team/packages/python-virtualenv/-/tree/master/debian/patches

The BUNDLE_SUPPORT logic is more complicated than you'd expect, to allow old python 2.7 compatible wheels to be present in the same directory, without being selected for Python 3.x

@ofek
Copy link
Sponsor Collaborator

ofek commented Feb 13, 2022

I haven’t been able to find any documentation about what the difference between embed and bundle is supposed to be. I assume there’s some good reason hatch chooses to use embed.

I mentioned this in the virtualenv channel of the PyPA's Discord some time ago. Essentially, Hatch needs virtual environments to have pip 21.3 for PEP 660 support which requires virtualenv 20.9.0. When I locally upgraded to that version, virtual environments weren't being populated with that new version of pip until using --pip embed once. The response was that must be some bug in our wheel cache logic.

I use Windows, so maybe it's just a Windows-only bug. I'm fine with removing that flag to help here, though can Debian/Fedora guarantee virtual environments are populated with at least version 21.3 of pip?

@musicinmybrain
Copy link
Contributor

musicinmybrain commented Feb 13, 2022

On a 64-bit platform, hatch is installed into lib64/python3.10/site-packages instead of lib/python3.10/site-packages, as if it were a binary/architecture-dependent package.

Hmm, what determines that?

I studied this a little, and my understanding is as follows:

It’s controlled by Root-Is-Purelib in the metadata. It looks like hatchling sets this based on zip_safe. While packages with compiled extensions are not zip-safe, packages can certainly be “purelib” without being zip-safe; for example, packages that construct paths to package resources via __file__ rather than via something like pkg_resources are not zip-safe.

@gaborbernat
Copy link

When I locally upgraded to that version, virtual environments weren't being populated with that new version of pip until using --pip embed once. The response was that must be some bug in our wheel cache logic.

There's been many fixes to that part in the last two months, so that might be now fixed 👌

@musicinmybrain
Copy link
Contributor

I use Windows, so maybe it's just a Windows-only bug. I'm fine with removing that flag to help here, though can Debian/Fedora guarantee virtual environments are populated with at least version 21.3 of pip?

In Fedora, the answer is basically “yes”:

  • The next release of Fedora (36), in mid-2022, will have pip 21.3 or later.
  • There isn’t a need to package hatch 1.0 in current Fedora releases (F34/F35). They have hatch 0.23.0, which works fine in this respect, and a major version bump wouldn’t be appropriate in a stable release anyway.
  • If there were a direct, versioned dependency on pip, e.g. pip>=21.3.0, the minimum version could be enforced at build and install time, which would prevent accidentally backporting to a release with too old a pip (EPEL9?). Or, I could add the versioned requirement to the spec file downstream based on this conversation, which would accomplish the same thing.

Alternatively, if the --pip embed is still needed in general, but the reasons don’t apply to Fedora, it’s easy enough to patch it out downstream.

I’d be happy to patch out --pip embed and check if the original problem occurs, if you can share steps to reproduce it.

@ofek
Copy link
Sponsor Collaborator

ofek commented Feb 13, 2022

packages can certainly be “purelib” without being zip-safe

Wow. python/mypy#8802

I had no idea that was just a legacy setuptools thing 🤦‍♂️ I'm sorry, I'll fix that now.

There's been many fixes to that part in the last two months, so that might be now fixed

I’d be happy to patch out --pip embed

I'll remove that flag now as well.

@musicinmybrain
Copy link
Contributor

musicinmybrain commented Feb 13, 2022

As a test, I applied

diff -Naur hatch-hatch-v1.0.0rc12-original/src/hatch/venv/core.py hatch-hatch-v1.0.0rc12/src/hatch/venv/core.py
--- hatch-hatch-v1.0.0rc12-original/src/hatch/venv/core.py	2022-02-05 00:58:01.000000000 -0500
+++ hatch-hatch-v1.0.0rc12/src/hatch/venv/core.py	2022-02-13 11:41:04.927660495 -0500
@@ -47,7 +47,7 @@
 
         self.directory.ensure_parent_dir_exists()
 
-        command = [str(self.directory), '--no-download', '--no-periodic-update', '--pip', 'embed', '--python', python]
+        command = [str(self.directory), '--no-download', '--no-periodic-update', '--python', python]
 
         if allow_system_packages:
             command.append('--system-site-packages')

The --pip embed option is also used in backend/tests/downstream/integrate.py, but I’m not running the hatchling integration tests, here or in the package for hatchling, so I left that alone.

I can confirm that this change fixes the AttributeError failures.

Some of those tests now produce an AssertionError. For example, some need to be skipped because they try to download something from the Internet, and some are trying to use an unversioned python command (vs. `python3), which isn’t present in the Fedora buildroot by default. I’ll need to look at these on a case-by-case basis. Most or all of them probably will not require any upstream changes.

@frenzymadness
Copy link
Contributor Author

I don't have time to dive into it today but I promise to take a look tomorrow. I'm the author of the Fedora virtualenv patch so if we find a better way to handle our own wheels, I should be able to implement it.

@musicinmybrain
Copy link
Contributor

I don't have time to dive into it today but I promise to take a look tomorrow. I'm the author of the Fedora virtualenv patch so if we find a better way to handle our own wheels, I should be able to implement it.

Thanks! I really appreciate it. (It doesn’t have to be tomorrow.)

@ofek
Copy link
Sponsor Collaborator

ofek commented Feb 13, 2022

@frenzymadness
Copy link
Contributor Author

bundle means using the latest pip version available locally (from a merge of the following paths: embedded, custom search paths set via CLI or conf and local cache - e.g. previously downloaded into the app data cache). embed is the pip version virtualenv was shipped with.

Thanks a lot for the explanation. I think something like this might be useful to have in the documentation.

RE Fedora's BUNDLE_SUPPORT patch:
FWIW, Debian's virtualenv patch-set basically just removes the bundled wheels: https://salsa.debian.org/python-team/packages/python-virtualenv/-/tree/master/debian/patches
The BUNDLE_SUPPORT logic is more complicated than you'd expect, to allow old python 2.7 compatible wheels to be present in the same directory, without being selected for Python 3.x

—or how its approach compares to Fedora’s, or whether its choices are appropriate for Fedora. However, I can confirm that both --pip bundle and --pip embed seem to work on a fresh Debian Bullseye container.

Let's discuss this in the bugzilla https://bugzilla.redhat.com/show_bug.cgi?id=2053948 . This discussion is important for us but not that much for upstream projects.

@ofek
Copy link
Sponsor Collaborator

ofek commented Feb 17, 2022

@musicinmybrain Were you able to successfully add Hatchling to Fedora? I couldn't find a link.

@musicinmybrain
Copy link
Contributor

It is in the package review process. Once that is done, it will be here. @frenzymadness is handing over primary maintenance of the hatch package to me and has agreed to review the new dependencies (editables and hatchling) within the next week or so—editables is already done and ready to import to the distribution.

I have been using COPR to test these packages, and have successfully built userpath 1.8.0 with hatchling.

As I noted, it will take some time to work through the remaining failing tests in hatch. That package won’t be updated in Fedora until 1.0 final is released anyway, but I would like to be prepared when that happens.

@ofek
Copy link
Sponsor Collaborator

ofek commented Feb 17, 2022

Great news, thank you all very much! I'll keep this open until both hatch (v1 final) and hatchling are available in Debian and Fedora.

until 1.0 final is released

I'm planning to release and announce the non-RC v1.0.0 of the CLI the week of PyCon which begins on Wednesday, April 27. If you'd prefer, I can do the release earlier so that the system packages are already available by then.

@musicinmybrain
Copy link
Contributor

I'm planning to release and announce the non-RC v1.0.0 of the CLI the week of PyCon which begins on Wednesday, April 27. If you'd prefer, I can do the release earlier so that the system packages are already available by then.

Version 1.0 of hatch won’t be delivered as an update to the current Fedora release (34 and 35), since stable releases don’t get new major versions in general. Plus, they don’t have pip>=21.3.0; this also blocks inclusion in EPEL9 for now (pip is 21.2.3), although I wouldn’t be surprised to see a pip update appear in a future RHEL9 minor release.

There is plenty of time to get version 1.0 of hatch into Fedora 37 (late 2022 release).

Fedora 36 is currently targeted for 2022-04-26. Getting hatch 1.0 into that is technically possible, but the timeline is very tight. Doing it “right” would look like:

  • Before the beta freeze on 2022-02-22 (in five days!):
    • Resolve (fix or explain and skip) all remaining test failures.
    • Petition FESCo for approval to package a pre-release (rc), explaining how the upstream release schedule nearly aligns with Fedora 36 and the “leaf package” nature of hatch mitigates the scope of any risk from packaging a pre-release.
    • If FESCo agrees with the plan, build the latest rc of hatch in time for it to be included in the beta compose.
  • Before the final freeze, scheduled 2022-04-05
    • Upstream public release of 1.0 final
    • Handle any changes or new issues downstream
    • Build the final package in time for it to be included in the final compose.

Personally, I think that’s all pushing it—more risk than necessary, and more intensive effort in the short term than I want to sign up for—and it’s better to keep hatch 0.23.1 in Fedora 36. I still intend to package hatchling and update userpath for F36.

@musicinmybrain
Copy link
Contributor

[…] Essentially, Hatch needs virtual environments to have pip 21.3 for PEP 660 support which requires virtualenv 20.9.0. […]

Do either or both of these minimum versions apply only to hatch, or also to hatchling?

Currently, looking at the sources in this repository:

  • hatch explicitly requires "virtualenv>=20.13.1" and doesn’t depend directly on pip
  • hatchling doesn’t depend directly on pip or virtualenv

It seems like any minimum versions should be explicitly encoded in the wheel metadata; or, failing that, if I understand exactly what they are, I can at least add them at the RPM level to remind me not to do any unwise backports.

The python-hatchling package is now in the development version of Fedora (Rawhide) as well as in the upcoming Fedora 36 release, by the way.

@ofek
Copy link
Sponsor Collaborator

ofek commented Feb 22, 2022

[…] Essentially, Hatch needs virtual environments to have pip 21.3 for PEP 660 support which requires virtualenv 20.9.0. […]

Do either or both of these minimum versions apply only to hatch, or also to hatchling?

Only to hatch, which only interacts with pip inside envs created by virtualenv

The python-hatchling package is now in the development version of Fedora (Rawhide) as well as in the upcoming Fedora 36 release, by the way.

Great news, thanks!!!

@ofek
Copy link
Sponsor Collaborator

ofek commented Mar 7, 2022

assumes the original hatch git repository structure with a backend directory. I can work around that with cp -rp . ../backend

Fixed:

@musicinmybrain
Copy link
Contributor

I just started looking at preparing a hatch 1.0 package for Fedora again, using 1.0.0rc16. I see that there is a clever pytest mark called requires_internet, which is used only for tests/cli/new/test_new.py::test_default_no_license_cache. However, in an offline build environment, all of the following tests fail with something like:

E       AssertionError: Creating environment: foo.9000
E         Syncing dependencies
E         
E       assert 1 == 0
E        +  where 1 = <Result SystemExit(1)>.exit_code

/builddir/build/BUILD/hatch-hatch-v1.0.0rc16/tests/cli/env/test_remove.py:181: AssertionError
----------------------------- Captured stderr call -----------------------------
WARNING: Retrying (Retry(total=4, connect=None, read=None, redirect=None, status=None)) after connection broken by 'NewConnectionError('<pip._vendor.urllib3.connection.HTTPSConnection object at 0x7f629447d990>: Failed to establish a new connection: [Errno -3] Temporary failure in name resolution')': /simple/pytest/
WARNING: Retrying (Retry(total=3, connect=None, read=None, redirect=None, status=None)) after connection broken by 'NewConnectionError('<pip._vendor.urllib3.connection.HTTPSConnection object at 0x7f629447c640>: Failed to establish a new connection: [Errno -3] Temporary failure in name resolution')': /simple/pytest/
WARNING: Retrying (Retry(total=2, connect=None, read=None, redirect=None, status=None)) after connection broken by 'NewConnectionError('<pip._vendor.urllib3.connection.HTTPSConnection object at 0x7f629447c850>: Failed to establish a new connection: [Errno -3] Temporary failure in name resolution')': /simple/pytest/
WARNING: Retrying (Retry(total=1, connect=None, read=None, redirect=None, status=None)) after connection broken by 'NewConnectionError('<pip._vendor.urllib3.connection.HTTPSConnection object at 0x7f629447ffd0>: Failed to establish a new connection: [Errno -3] Temporary failure in name resolution')': /simple/pytest/
WARNING: Retrying (Retry(total=0, connect=None, read=None, redirect=None, status=None)) after connection broken by 'NewConnectionError('<pip._vendor.urllib3.connection.HTTPSConnection object at 0x7f629447ded0>: Failed to establish a new connection: [Errno -3] Temporary failure in name resolution')': /simple/pytest/
ERROR: Could not find a version that satisfies the requirement pytest (from versions: none)
ERROR: No matching distribution found for pytest

which, to me, seems to imply that these tests need to install wheels from PyPI.

FAILED tests/cli/env/test_create.py::test_post_install_commands - AssertionEr...
FAILED tests/cli/env/test_create.py::test_features - AssertionError: Creating...
FAILED tests/cli/env/test_create.py::test_post_install_commands_error - asser...
FAILED tests/cli/env/test_create.py::test_sync_dependencies - AssertionError:...
FAILED tests/cli/env/test_create.py::test_install_project_no_dev_mode - Asser...
FAILED tests/cli/env/test_create.py::test_install_project_default_dev_mode - ...
FAILED tests/cli/env/test_create.py::test_pre_install_commands - AssertionErr...
FAILED tests/cli/run/test_run.py::test_sync_dependencies - AssertionError: Sy...
FAILED tests/cli/run/test_run.py::test_scripts_no_environment - AssertionErro...
FAILED tests/cli/env/test_prune.py::test_all - AssertionError: Creating envir...
FAILED tests/cli/build/test_build.py::test_shipped - AssertionError: Setting ...
FAILED tests/cli/build/test_build.py::test_build_dependencies - AssertionErro...
FAILED tests/cli/env/test_remove.py::test_active_override - AssertionError: C...
FAILED tests/cli/env/test_remove.py::test_all - AssertionError: Creating envi...
FAILED tests/cli/env/test_remove.py::test_single - AssertionError: Creating e...
FAILED tests/cli/env/test_remove.py::test_matrix_all - AssertionError: Creati...

Should the @pytest.mark.requires_internet decorator be added to these tests, or am I missing something?

(Note that, for now, Fedora Rawhide has keyring 21.8.0, platformdirs 2.3.0, pyperclip 1.8.0, and tomlkit 0.7.2, all older than specified in pyproject.toml. I don’t think that is making a difference in these particular tests. I’m planning to send PRs to help the Fedora package maintainers for those lagging dependencies update to the latest versions.)

@ofek
Copy link
Sponsor Collaborator

ofek commented Mar 22, 2022

Yeah I forgot after a while, please make a PR 🙂 Any ideas on how to emulate no internet in CI?

@musicinmybrain
Copy link
Contributor

Yeah I forgot after a while, please make a PR slightly_smiling_face

Will do. Thanks.

Any ideas on how to emulate no internet in CI?

If all you need to do is break pip, setting something like

export PIP_PROXY='https://localhost:1'

should do the trick.

@musicinmybrain
Copy link
Contributor

To what extent should hatch rely on the presence of an interpreter called python? It seems like there is a mix of hard-coded python interpreters and sys.executable, both in the tests and in the library, and I haven’t figured out what the underlying philosophy is.

Generally, in Fedora Linux, only /usr/bin/python3 can be relied on to be present. (When /usr/bin/python is present, it is the same as /usr/bin/python3.)

@ofek
Copy link
Sponsor Collaborator

ofek commented Mar 22, 2022

Never as of latest RC, default should now be sys.executable.

@musicinmybrain
Copy link
Contributor

Never as of latest RC, default should now be sys.executable.

Okay, I like that answer. I’ll start a PR for any exceptions that I find.

@musicinmybrain
Copy link
Contributor

Never as of latest RC, default should now be sys.executable.

Okay, I like that answer. I’ll start a PR for any exceptions that I find.

Okay, it turns out that this seems to be fine now. Awesome!

I’ve helped get keyring and tomlkit up to date in Fedora, so now only platformdirs and pyperclip are older than hatch wants in pyproject.toml (platformdirs 2.3.0, pyperclip 1.8.0). I have PR’s pending for them, too.

With that work done, I’m down to a single failing test for 1.0.0rc16! 🎉

=================================== FAILURES ===================================
_______________ test_project_location_complex_set_first_project ________________

hatch = <tests.conftest.CliRunner object at 0x7f96ed7ef7c0>
config_file = <hatch.config.user.ConfigFile object at 0x7f96edd54ee0>
helpers = <module 'tests.helpers.helpers' from '/builddir/build/BUILD/hatch-hatch-v1.0.0rc16/tests/helpers/helpers.py'>
temp_dir = Path('/tmp/tmp4k3zh6p2')

    def test_project_location_complex_set_first_project(hatch, config_file, helpers, temp_dir):
        with temp_dir.as_cwd():
            result = hatch('config', 'set', 'projects.foo.location', '.')
    
        path = str(temp_dir).replace('\\', '\\\\')
    
        assert result.exit_code == 0, result.output
>       assert result.output == helpers.dedent(
            f"""
            New setting:
            project = "foo"
            [projects.foo]
            location = "{path}"
            """
        )
E       assert ('New setting:\n'\n 'project = "foo"\n'\n '\n'\n '[projects.foo]\n'\n 'location = "/tmp/tmp4k3zh6p2"\n') == 'New setting:\nproject = "foo"\n[projects.foo]\nlocation = "/tmp/tmp4k3zh6p2"\n'
E           New setting:
E           project = "foo"
E         + 
E           [projects.foo]
E           location = "/tmp/tmp4k3zh6p2"

/builddir/build/BUILD/hatch-hatch-v1.0.0rc16/tests/cli/config/test_set.py:205: AssertionError
=========================== short test summary info ============================
FAILED tests/cli/config/test_set.py::test_project_location_complex_set_first_project
================== 1 failed, 706 passed, 44 skipped in 27.77s ==================

Should TOML output tests be made insensitive to blank lines?

@ofek
Copy link
Sponsor Collaborator

ofek commented Mar 28, 2022

Nice!!! Hmm, looks like new tomlkit. Should I bump minimum + fix test, then rc17?

@ofek
Copy link
Sponsor Collaborator

ofek commented Mar 29, 2022

Fixed ^, + 3 other tests that started failing b/c of some different output from the new version of Hatchling

@musicinmybrain
Copy link
Contributor

Okay, with #171 and #174 all the tests pass, except that I forgot I still have

BuildRequires:  python-unversioned-command

in my spec file as a workaround. When I remove that, such that the build environment has only /usr/bin/python3 and not /usr/bin/python, test_new_selected_python fails:

=================================== FAILURES ===================================
___________________________ test_new_selected_python ___________________________

hatch = <tests.conftest.CliRunner object at 0x7f3047aa2da0>
helpers = <module 'tests.helpers.helpers' from '/builddir/build/BUILD/hatch-hatch-v1.0.0rc16/tests/helpers/helpers.py'>
temp_dir = Path('/tmp/tmpxvilqluq')
config_file = <hatch.config.user.ConfigFile object at 0x7f3047af4730>
mocker = <pytest_mock.plugin.MockerFixture object at 0x7f3047af74c0>

    def test_new_selected_python(hatch, helpers, temp_dir, config_file, mocker):
        mocker.patch('sys.executable')
    
        config_file.model.template.plugins['default']['tests'] = False
        config_file.save()
    
        project_name = 'My App'
    
        with temp_dir.as_cwd():
            result = hatch('new', project_name)
    
        assert result.exit_code == 0, result.output
    
        project_path = temp_dir / 'my-app'
        data_path = temp_dir / 'data'
        data_path.mkdir()
    
        project = Project(project_path)
        helpers.update_project_environment(project, 'default', {'skip-install': True, **project.config.envs['default']})
        helpers.update_project_environment(project, 'test', {})
    
        with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path), AppEnvVars.PYTHON: 'python'}):
>           result = hatch('env', 'create', 'test')

/builddir/build/BUILD/hatch-hatch-v1.0.0rc16/tests/cli/env/test_create.py:150: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
/builddir/build/BUILD/hatch-hatch-v1.0.0rc16/tests/conftest.py:35: in __call__
    return self.invoke(self._command, args, **kwargs)
/usr/lib/python3.10/site-packages/click/testing.py:408: in invoke
    return_value = cli.main(args=args or (), prog_name=prog_name, **extra)
/usr/lib/python3.10/site-packages/click/core.py:1053: in main
    rv = self.invoke(ctx)
/usr/lib/python3.10/site-packages/click/core.py:1659: in invoke
    return _process_result(sub_ctx.command.invoke(sub_ctx))
/usr/lib/python3.10/site-packages/click/core.py:1659: in invoke
    return _process_result(sub_ctx.command.invoke(sub_ctx))
/usr/lib/python3.10/site-packages/click/core.py:1395: in invoke
    return ctx.invoke(self.callback, **ctx.params)
/usr/lib/python3.10/site-packages/click/core.py:754: in invoke
    return __callback(*args, **kwargs)
/usr/lib/python3.10/site-packages/click/decorators.py:38: in new_func
    return f(get_current_context().obj, *args, **kwargs)
/builddir/build/BUILDROOT/hatch-1.0.0~rc16-2.fc37.x86_64/usr/lib/python3.10/site-packages/hatch/cli/env/__init__.py:129: in create
    app.prepare_environment(environment)
/builddir/build/BUILDROOT/hatch-1.0.0~rc16-2.fc37.x86_64/usr/lib/python3.10/site-packages/hatch/cli/application.py:71: in prepare_environment
    environment.create()
/builddir/build/BUILDROOT/hatch-1.0.0~rc16-2.fc37.x86_64/usr/lib/python3.10/site-packages/hatch/env/virtual.py:44: in create
    self.virtual_env.create(self.parent_python, allow_system_packages=self.config.get('system-packages', False))
/builddir/build/BUILDROOT/hatch-1.0.0~rc16-2.fc37.x86_64/usr/lib/python3.10/site-packages/hatch/venv/core.py:57: in create
    cli_run(command)
/usr/lib/python3.10/site-packages/virtualenv/run/__init__.py:30: in cli_run
    of_session = session_via_cli(args, options, setup_logging, env)
/usr/lib/python3.10/site-packages/virtualenv/run/__init__.py:48: in session_via_cli
    parser, elements = build_parser(args, options, setup_logging, env)
/usr/lib/python3.10/site-packages/virtualenv/run/__init__.py:70: in build_parser
    parser._interpreter = interpreter = discover.interpreter
/usr/lib/python3.10/site-packages/virtualenv/discovery/discover.py:45: in interpreter
    self._interpreter = self.run()
/usr/lib/python3.10/site-packages/virtualenv/discovery/builtin.py:47: in run
    result = get_interpreter(python_spec, self.try_first_with, self.app_data, self._env)
/usr/lib/python3.10/site-packages/virtualenv/discovery/builtin.py:65: in get_interpreter
    for interpreter, impl_must_match in propose_interpreters(spec, try_first_with, app_data, env):
/usr/lib/python3.10/site-packages/virtualenv/discovery/builtin.py:101: in propose_interpreters
    yield PythonInfo.current_system(app_data), True
/usr/lib/python3.10/site-packages/virtualenv/discovery/py_info.py:328: in current_system
    cls._current_system = cls.from_exe(sys.executable, app_data, raise_on_error=True, resolve_to_host=True)
/usr/lib/python3.10/site-packages/virtualenv/discovery/py_info.py:355: in from_exe
    raise exception
/usr/lib/python3.10/site-packages/virtualenv/discovery/py_info.py:352: in from_exe
    proposed = proposed._resolve_to_system(app_data, proposed)
/usr/lib/python3.10/site-packages/virtualenv/discovery/py_info.py:392: in _resolve_to_system
    target = cls.from_exe(target.system_executable, app_data)
/usr/lib/python3.10/site-packages/virtualenv/discovery/py_info.py:348: in from_exe
    proposed = from_exe(cls, app_data, exe, env=env, raise_on_error=raise_on_error, ignore_cache=ignore_cache)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

cls = <class 'virtualenv.discovery.py_info.PythonInfo'>
app_data = AppDataDiskFolder(/builddir/.local/share/virtualenv)
exe = '/tmp/tmpxvilqluq/my-app/MagicMock/executable/139845411653808'
env = environ({'PATH': '/builddir/build/BUILDROOT/hatch-1.0.0~rc16-2.fc37.x86_64/usr/bin:/builddir/.local/bin:/builddir/bin:...selected_python0/config.toml', 'PYTEST_CURRENT_TEST': 'tests/cli/env/test_create.py::test_new_selected_python (call)'})
raise_on_error = True, ignore_cache = False

    def from_exe(cls, app_data, exe, env=None, raise_on_error=True, ignore_cache=False):
        env = os.environ if env is None else env
        result = _get_from_cache(cls, app_data, exe, env, ignore_cache=ignore_cache)
        if isinstance(result, Exception):
            if raise_on_error:
>               raise result
E               RuntimeError: failed to query /tmp/tmpxvilqluq/my-app/MagicMock/executable/139845411653808 with code 2 err: 'No such file or directory'

/usr/lib/python3.10/site-packages/virtualenv/discovery/cached_py_info.py:31: RuntimeError
=========================== short test summary info ============================
FAILED tests/cli/env/test_create.py::test_new_selected_python - RuntimeError:...
================== 1 failed, 706 passed, 44 skipped in 19.73s ==================

What do you think is the correct fix for this?

@ofek
Copy link
Sponsor Collaborator

ofek commented Mar 29, 2022

#176 ?

@musicinmybrain
Copy link
Contributor

#176 ?

Yes, that works for me—all tests pass for rc16 with #171, #174, and #176 applied, so it looks like rc17 will build cleanly without patches. Thanks!

@ofek
Copy link
Sponsor Collaborator

ofek commented Apr 28, 2022

FYI I'm about to release Hatchling 0.23.0 then finally Hatch 1.0.0, just in time for PyCon 😄

I'll close this issue after that

@ofek
Copy link
Sponsor Collaborator

ofek commented Apr 28, 2022

https://github.com/ofek/hatch/releases/tag/hatch-v1.0.0

@ofek ofek closed this as completed Apr 28, 2022
@musicinmybrain
Copy link
Contributor

Thanks for helping me understand Hatch and Hatchling and working with me and others to get them properly packaged in Fedora Linux. The number of packages built with Hatchling is already increasing rapidly.

Meanwhile, I announced Hatch 1.0.0 on the devel mailing list for Fedora. I will build it for Rawhide (the development version of Fedora Linux) in one week, and it will be included in Fedora Linux 37, scheduled for release in late October.

@ofek
Copy link
Sponsor Collaborator

ofek commented May 1, 2022

Thank you all very much!

Note that Hatch will likely be adopted by PyPA in the next week so the repo location will change:

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants