Skip to content

Commit 909368a

Browse files
Backend: add support for path rewrite (#1956)
1 parent 4776f11 commit 909368a

File tree

7 files changed

+113
-26
lines changed

7 files changed

+113
-26
lines changed

backend/src/hatchling/builders/constants.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,4 +40,4 @@ class BuildEnvVars:
4040
CLEAN_HOOKS_AFTER = "HATCH_BUILD_CLEAN_HOOKS_AFTER"
4141

4242

43-
EDITABLES_REQUIREMENT = "editables~=0.3"
43+
EDITABLES_REQUIREMENT = "editables~=0.4"

backend/src/hatchling/builders/wheel.py

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -531,6 +531,7 @@ def build_editable_detection(self, directory: str, **build_data: Any) -> str:
531531
RecordFile() as records,
532532
):
533533
exposed_packages = {}
534+
exposed_subpackages = {}
534535
for included_file in self.recurse_selected_project_files():
535536
if not included_file.path.endswith(".py"):
536537
continue
@@ -541,7 +542,10 @@ def build_editable_detection(self, directory: str, **build_data: Any) -> str:
541542

542543
# Root file
543544
if len(path_parts) == 1: # no cov
544-
exposed_packages[os.path.splitext(relative_path)[0]] = os.path.join(self.root, relative_path)
545+
if distribution_path in relative_path:
546+
exposed_packages[os.path.splitext(relative_path)[0]] = os.path.join(self.root, relative_path)
547+
else:
548+
exposed_subpackages[distribution_path] = os.path.join(self.root, relative_path)
545549
continue
546550

547551
# Root package
@@ -550,27 +554,33 @@ def build_editable_detection(self, directory: str, **build_data: Any) -> str:
550554
exposed_packages[root_module] = os.path.join(self.root, root_module)
551555
else:
552556
distribution_module = distribution_path.split(os.sep)[0]
553-
try:
557+
if distribution_path in relative_path:
554558
exposed_packages[distribution_module] = os.path.join(
555559
self.root,
556560
f"{relative_path[: relative_path.index(distribution_path)]}{distribution_module}",
557561
)
558-
except ValueError:
559-
message = (
560-
"Dev mode installations are unsupported when any path rewrite in the `sources` option "
561-
"changes a prefix rather than removes it, see: "
562-
"https://github.com/pfmoore/editables/issues/20"
563-
)
564-
raise ValueError(message) from None
562+
else:
563+
exposed_subpackages[distribution_path] = os.path.join(self.root, relative_path)
565564

566565
editable_project = EditableProject(self.metadata.core.name, self.root)
567566

568567
if self.config.dev_mode_exact:
569568
for module, relative_path in exposed_packages.items():
570569
editable_project.map(module, relative_path)
570+
for module, relative_path in exposed_subpackages.items():
571+
editable_project.map(module, relative_path)
571572
else:
572573
for relative_path in exposed_packages.values():
573574
editable_project.add_to_path(os.path.dirname(relative_path))
575+
exposed_subpackages_dirnames = {}
576+
for module, relative_path in exposed_subpackages.items():
577+
exposed_subpackages_dirnames[os.path.dirname(module)] = os.path.dirname(relative_path)
578+
for module, relative_path in exposed_subpackages_dirnames.items():
579+
# Do not add packages if parent already is included
580+
if os.path.dirname(module) not in exposed_subpackages_dirnames or exposed_subpackages_dirnames[
581+
os.path.dirname(module)
582+
] != os.path.dirname(relative_path):
583+
editable_project.add_to_subpackage(module, relative_path)
574584

575585
for raw_filename, content in sorted(editable_project.files()):
576586
filename = raw_filename

tests/backend/builders/test_wheel.py

Lines changed: 34 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3484,7 +3484,8 @@ def test_no_strict_naming(self, hatch, helpers, temp_dir, config_file):
34843484
)
34853485
helpers.assert_files(extraction_directory, expected_files)
34863486

3487-
def test_editable_sources_rewrite_error(self, hatch, temp_dir):
3487+
@fixed_pathlib_resolution
3488+
def test_editable_sources_rewrite(self, hatch, helpers, temp_dir):
34883489
project_name = "My.App"
34893490

34903491
with temp_dir.as_cwd():
@@ -3516,18 +3517,38 @@ def test_editable_sources_rewrite_error(self, hatch, temp_dir):
35163517
build_path = project_path / "dist"
35173518
build_path.mkdir()
35183519

3519-
with (
3520-
project_path.as_cwd(),
3521-
pytest.raises(
3522-
ValueError,
3523-
match=(
3524-
"Dev mode installations are unsupported when any path rewrite in the `sources` option "
3525-
"changes a prefix rather than removes it, see: "
3526-
"https://github.com/pfmoore/editables/issues/20"
3527-
),
3528-
),
3529-
):
3530-
list(builder.build(directory=str(build_path)))
3520+
with project_path.as_cwd():
3521+
artifacts = list(builder.build(directory=str(build_path)))
3522+
3523+
assert len(artifacts) == 1
3524+
expected_artifact = artifacts[0]
3525+
3526+
build_artifacts = list(build_path.iterdir())
3527+
assert len(build_artifacts) == 1
3528+
assert expected_artifact == str(build_artifacts[0])
3529+
assert expected_artifact == str(build_path / f"{builder.project_id}-{get_python_versions_tag()}-none-any.whl")
3530+
3531+
extraction_directory = temp_dir / "_archive"
3532+
extraction_directory.mkdir()
3533+
3534+
with zipfile.ZipFile(str(expected_artifact), "r") as zip_archive:
3535+
zip_archive.extractall(str(extraction_directory))
3536+
3537+
metadata_directory = f"{builder.project_id}.dist-info"
3538+
expected_files = helpers.get_template_files(
3539+
"wheel.standard_editable_sources_rewrite",
3540+
project_name,
3541+
metadata_directory=metadata_directory,
3542+
namespace=["namespace", "plugins"],
3543+
source_path=project_path / "src" / "my_app",
3544+
)
3545+
helpers.assert_files(extraction_directory, expected_files)
3546+
3547+
# Inspect the archive rather than the extracted files because on Windows they lose their metadata
3548+
# https://stackoverflow.com/q/9813243
3549+
with zipfile.ZipFile(str(expected_artifact), "r") as zip_archive:
3550+
zip_info = zip_archive.getinfo(f"{metadata_directory}/WHEEL")
3551+
assert zip_info.date_time == (2020, 2, 2, 0, 0, 0)
35313552

35323553
@pytest.mark.skipif(
35333554
sys.platform != "darwin" or sys.version_info < (3, 8),

tests/helpers/templates/wheel/standard_editable_exact.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ def get_files(**kwargs):
4545
Name: {kwargs["project_name"]}
4646
Version: 0.0.1
4747
License-File: LICENSE.txt
48-
Requires-Dist: editables~=0.3
48+
Requires-Dist: editables~=0.4
4949
""",
5050
),
5151
))

tests/helpers/templates/wheel/standard_editable_exact_extra_dependencies.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ def get_files(**kwargs):
4646
Version: 0.0.1
4747
License-File: LICENSE.txt
4848
Requires-Dist: binary
49-
Requires-Dist: editables~=0.3
49+
Requires-Dist: editables~=0.4
5050
""",
5151
),
5252
))

tests/helpers/templates/wheel/standard_editable_exact_force_include.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ def get_files(**kwargs):
4646
Name: {kwargs["project_name"]}
4747
Version: 0.0.1
4848
License-File: LICENSE.txt
49-
Requires-Dist: editables~=0.3
49+
Requires-Dist: editables~=0.4
5050
""",
5151
),
5252
))
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
from hatch.template import File
2+
from hatch.utils.fs import Path
3+
from hatchling.__about__ import __version__
4+
from hatchling.metadata.spec import DEFAULT_METADATA_VERSION
5+
6+
from ..new.default import get_files as get_template_files
7+
from .utils import update_record_file_contents
8+
9+
10+
def get_files(**kwargs):
11+
metadata_directory = kwargs.get("metadata_directory", "")
12+
namespace_package = kwargs["namespace"]
13+
14+
files = []
15+
for f in get_template_files(**kwargs):
16+
if str(f.path) == "LICENSE.txt":
17+
files.append(File(Path(metadata_directory, "licenses", f.path), f.contents))
18+
19+
if f.path.parts[0] != kwargs["package_name"]:
20+
continue
21+
22+
f.path = Path(namespace_package, f.path)
23+
files.append(f)
24+
25+
pth_file_name = f"_{kwargs['package_name']}.pth"
26+
files.extend((
27+
File(Path(pth_file_name), ""),
28+
File(
29+
Path(*namespace_package, kwargs["package_name"], "__init__.py"), f"__path__ = ['{kwargs['source_path']}']"
30+
),
31+
File(
32+
Path(metadata_directory, "WHEEL"),
33+
f"""\
34+
Wheel-Version: 1.0
35+
Generator: hatchling {__version__}
36+
Root-Is-Purelib: true
37+
Tag: py2-none-any
38+
Tag: py3-none-any
39+
""",
40+
),
41+
File(
42+
Path(metadata_directory, "METADATA"),
43+
f"""\
44+
Metadata-Version: {DEFAULT_METADATA_VERSION}
45+
Name: {kwargs["project_name"]}
46+
Version: 0.0.1
47+
License-File: LICENSE.txt
48+
""",
49+
),
50+
))
51+
52+
record_file = File(Path(metadata_directory, "RECORD"), "")
53+
update_record_file_contents(record_file, files, generated_files={pth_file_name})
54+
files.append(record_file)
55+
56+
return files

0 commit comments

Comments
 (0)