Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@
- 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.
[jensens]

- 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.
[jensens]

- Fix #53: Per-package target setting now correctly overrides default-target when constructing checkout paths.
[jensens]

Expand Down
20 changes: 19 additions & 1 deletion src/mxdev/processing.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from urllib import request
from urllib.error import URLError

import os
import typing


Expand Down Expand Up @@ -271,9 +272,26 @@ def write(state: State) -> None:
logger.info(f"Write [r]: {cfg.out_requirements}")
with open(cfg.out_requirements, "w") as fio:
if constraints or cfg.overrides:
# Calculate relative path from requirements-out directory to constraints-out file
# This ensures pip can find the constraints file regardless of where requirements
# and constraints files are located
req_path = Path(cfg.out_requirements)
const_path = Path(cfg.out_constraints)

# Calculate relative path from requirements directory to constraints file
try:
constraints_ref = os.path.relpath(const_path, req_path.parent)
# Convert backslashes to forward slashes for pip compatibility
# pip expects forward slashes even on Windows
constraints_ref = constraints_ref.replace("\\", "/")
except ValueError:
# On Windows, relpath can fail if paths are on different drives
# In that case, use absolute path with forward slashes
constraints_ref = str(const_path.absolute()).replace("\\", "/")

fio.write("#" * 79 + "\n")
fio.write("# mxdev combined constraints\n")
fio.write(f"-c {cfg.out_constraints}\n\n")
fio.write(f"-c {constraints_ref}\n\n")
write_dev_sources(fio, cfg.packages)
fio.writelines(requirements)
write_main_package(fio, cfg.settings)
112 changes: 112 additions & 0 deletions tests/test_processing.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import os
import pathlib
import pytest
from io import StringIO
Expand Down Expand Up @@ -407,3 +408,114 @@ def test_write_no_constraints(tmp_path):
assert not const_file.exists()
finally:
os.chdir(old_cwd)


def test_relative_constraints_path_in_subdirectory(tmp_path):
"""Test that constraints path in requirements-out is relative to requirements file location.

This reproduces issue #22: when requirements-out and constraints-out are in subdirectories,
the constraints reference should be relative to the requirements file's directory.
"""
from mxdev.processing import read, write
from mxdev.state import State
from mxdev.config import Configuration

old_cwd = os.getcwd()
try:
os.chdir(tmp_path)

# Create subdirectory for output files
(tmp_path / "requirements").mkdir()

# Create input constraints file
constraints_in = tmp_path / "constraints.txt"
constraints_in.write_text("requests==2.28.0\nurllib3==1.26.9\n")

# Create input requirements file with a constraint reference
requirements_in = tmp_path / "requirements.txt"
requirements_in.write_text("-c constraints.txt\nrequests\n")

# Create config with both output files in subdirectory
config_file = tmp_path / "mx.ini"
config_file.write_text(
"""[settings]
requirements-in = requirements.txt
requirements-out = requirements/plone.txt
constraints-out = requirements/constraints.txt
"""
)

config = Configuration(str(config_file))
state = State(configuration=config)

# Read and write
read(state)
write(state)

# Check requirements file contains relative path to constraints
req_file = tmp_path / "requirements" / "plone.txt"
assert req_file.exists()
req_content = req_file.read_text()

# Bug: Currently writes "-c requirements/constraints.txt"
# Expected: Should write "-c constraints.txt" (relative to requirements file's directory)
assert "-c constraints.txt\n" in req_content, (
f"Expected '-c constraints.txt' (relative path), "
f"but got:\n{req_content}"
)

# Should NOT contain the full path from config file's perspective
assert "-c requirements/constraints.txt" not in req_content
finally:
os.chdir(old_cwd)


def test_relative_constraints_path_different_directories(tmp_path):
"""Test constraints path when requirements and constraints are in different directories."""
from mxdev.processing import read, write
from mxdev.state import State
from mxdev.config import Configuration

old_cwd = os.getcwd()
try:
os.chdir(tmp_path)

# Create different subdirectories
(tmp_path / "reqs").mkdir()
(tmp_path / "constraints").mkdir()

# Create input constraints file
constraints_in = tmp_path / "constraints.txt"
constraints_in.write_text("requests==2.28.0\nurllib3==1.26.9\n")

# Create input requirements file with a constraint reference
requirements_in = tmp_path / "requirements.txt"
requirements_in.write_text("-c constraints.txt\nrequests\n")

config_file = tmp_path / "mx.ini"
config_file.write_text(
"""[settings]
requirements-in = requirements.txt
requirements-out = reqs/requirements.txt
constraints-out = constraints/constraints.txt
"""
)

config = Configuration(str(config_file))
state = State(configuration=config)

read(state)
write(state)

req_file = tmp_path / "reqs" / "requirements.txt"
assert req_file.exists()
req_content = req_file.read_text()

# Should write path relative to reqs/ directory
# From reqs/ to constraints/constraints.txt = ../constraints/constraints.txt
assert "-c ../constraints/constraints.txt\n" in req_content, (
f"Expected '-c ../constraints/constraints.txt' (relative path), "
f"but got:\n{req_content}"
)
finally:
os.chdir(old_cwd)