Skip to content

Commit 4c114f0

Browse files
authored
Merge pull request #61 from mxstack/fix/22-relative-constraints-path
Fix constraints path to be relative to requirements file location
2 parents 300c3b0 + ba43752 commit 4c114f0

File tree

3 files changed

+134
-1
lines changed

3 files changed

+134
-1
lines changed

CHANGES.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@
1111
- Fix #46: Git tags in branch option are now correctly detected and handled during updates. Previously, updating from one tag to another failed because tags were incorrectly treated as branches.
1212
[jensens]
1313

14+
- Fix #22 and #25: Constraints file path in requirements-out is now correctly calculated as a relative path from the requirements file's directory. This allows requirements and constraints files to be in different directories. Previously, the path was written from the config file's perspective, causing pip to fail when looking for the constraints file. On Windows, paths are now normalized to use forward slashes for pip compatibility.
15+
[jensens]
16+
1417
- Fix #53: Per-package target setting now correctly overrides default-target when constructing checkout paths.
1518
[jensens]
1619

src/mxdev/processing.py

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from urllib import request
88
from urllib.error import URLError
99

10+
import os
1011
import typing
1112

1213

@@ -271,9 +272,26 @@ def write(state: State) -> None:
271272
logger.info(f"Write [r]: {cfg.out_requirements}")
272273
with open(cfg.out_requirements, "w") as fio:
273274
if constraints or cfg.overrides:
275+
# Calculate relative path from requirements-out directory to constraints-out file
276+
# This ensures pip can find the constraints file regardless of where requirements
277+
# and constraints files are located
278+
req_path = Path(cfg.out_requirements)
279+
const_path = Path(cfg.out_constraints)
280+
281+
# Calculate relative path from requirements directory to constraints file
282+
try:
283+
constraints_ref = os.path.relpath(const_path, req_path.parent)
284+
# Convert backslashes to forward slashes for pip compatibility
285+
# pip expects forward slashes even on Windows
286+
constraints_ref = constraints_ref.replace("\\", "/")
287+
except ValueError:
288+
# On Windows, relpath can fail if paths are on different drives
289+
# In that case, use absolute path with forward slashes
290+
constraints_ref = str(const_path.absolute()).replace("\\", "/")
291+
274292
fio.write("#" * 79 + "\n")
275293
fio.write("# mxdev combined constraints\n")
276-
fio.write(f"-c {cfg.out_constraints}\n\n")
294+
fio.write(f"-c {constraints_ref}\n\n")
277295
write_dev_sources(fio, cfg.packages)
278296
fio.writelines(requirements)
279297
write_main_package(fio, cfg.settings)

tests/test_processing.py

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import os
12
import pathlib
23
import pytest
34
from io import StringIO
@@ -407,3 +408,114 @@ def test_write_no_constraints(tmp_path):
407408
assert not const_file.exists()
408409
finally:
409410
os.chdir(old_cwd)
411+
412+
413+
def test_relative_constraints_path_in_subdirectory(tmp_path):
414+
"""Test that constraints path in requirements-out is relative to requirements file location.
415+
416+
This reproduces issue #22: when requirements-out and constraints-out are in subdirectories,
417+
the constraints reference should be relative to the requirements file's directory.
418+
"""
419+
from mxdev.processing import read, write
420+
from mxdev.state import State
421+
from mxdev.config import Configuration
422+
423+
old_cwd = os.getcwd()
424+
try:
425+
os.chdir(tmp_path)
426+
427+
# Create subdirectory for output files
428+
(tmp_path / "requirements").mkdir()
429+
430+
# Create input constraints file
431+
constraints_in = tmp_path / "constraints.txt"
432+
constraints_in.write_text("requests==2.28.0\nurllib3==1.26.9\n")
433+
434+
# Create input requirements file with a constraint reference
435+
requirements_in = tmp_path / "requirements.txt"
436+
requirements_in.write_text("-c constraints.txt\nrequests\n")
437+
438+
# Create config with both output files in subdirectory
439+
config_file = tmp_path / "mx.ini"
440+
config_file.write_text(
441+
"""[settings]
442+
requirements-in = requirements.txt
443+
requirements-out = requirements/plone.txt
444+
constraints-out = requirements/constraints.txt
445+
"""
446+
)
447+
448+
config = Configuration(str(config_file))
449+
state = State(configuration=config)
450+
451+
# Read and write
452+
read(state)
453+
write(state)
454+
455+
# Check requirements file contains relative path to constraints
456+
req_file = tmp_path / "requirements" / "plone.txt"
457+
assert req_file.exists()
458+
req_content = req_file.read_text()
459+
460+
# Bug: Currently writes "-c requirements/constraints.txt"
461+
# Expected: Should write "-c constraints.txt" (relative to requirements file's directory)
462+
assert "-c constraints.txt\n" in req_content, (
463+
f"Expected '-c constraints.txt' (relative path), "
464+
f"but got:\n{req_content}"
465+
)
466+
467+
# Should NOT contain the full path from config file's perspective
468+
assert "-c requirements/constraints.txt" not in req_content
469+
finally:
470+
os.chdir(old_cwd)
471+
472+
473+
def test_relative_constraints_path_different_directories(tmp_path):
474+
"""Test constraints path when requirements and constraints are in different directories."""
475+
from mxdev.processing import read, write
476+
from mxdev.state import State
477+
from mxdev.config import Configuration
478+
479+
old_cwd = os.getcwd()
480+
try:
481+
os.chdir(tmp_path)
482+
483+
# Create different subdirectories
484+
(tmp_path / "reqs").mkdir()
485+
(tmp_path / "constraints").mkdir()
486+
487+
# Create input constraints file
488+
constraints_in = tmp_path / "constraints.txt"
489+
constraints_in.write_text("requests==2.28.0\nurllib3==1.26.9\n")
490+
491+
# Create input requirements file with a constraint reference
492+
requirements_in = tmp_path / "requirements.txt"
493+
requirements_in.write_text("-c constraints.txt\nrequests\n")
494+
495+
config_file = tmp_path / "mx.ini"
496+
config_file.write_text(
497+
"""[settings]
498+
requirements-in = requirements.txt
499+
requirements-out = reqs/requirements.txt
500+
constraints-out = constraints/constraints.txt
501+
"""
502+
)
503+
504+
config = Configuration(str(config_file))
505+
state = State(configuration=config)
506+
507+
read(state)
508+
write(state)
509+
510+
req_file = tmp_path / "reqs" / "requirements.txt"
511+
assert req_file.exists()
512+
req_content = req_file.read_text()
513+
514+
# Should write path relative to reqs/ directory
515+
# From reqs/ to constraints/constraints.txt = ../constraints/constraints.txt
516+
assert "-c ../constraints/constraints.txt\n" in req_content, (
517+
f"Expected '-c ../constraints/constraints.txt' (relative path), "
518+
f"but got:\n{req_content}"
519+
)
520+
finally:
521+
os.chdir(old_cwd)

0 commit comments

Comments
 (0)