Skip to content

Commit 91d135c

Browse files
committed
Fix #65: Make missing sources fatal error in non-offline mode
In offline mode: - Missing sources log WARNING (expected) - Packages written as comments - mxdev continues In non-offline mode: - Missing sources log ERROR (fatal) - Packages written as comments - mxdev raises RuntimeError and exits This prevents silent failures when sources fail to checkout and makes mxmake two-stage installation more robust. Tests: - Updated test_write_dev_sources_missing_directories to use offline mode - Added test_write_dev_sources_missing_directories_raises_error - All 7 write_dev_sources tests pass
1 parent b554a7f commit 91d135c

File tree

3 files changed

+84
-9
lines changed

3 files changed

+84
-9
lines changed

CHANGES.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
[jensens]
1212
- Feature: #54: Add `fixed` install mode for non-editable installations to support production and Docker deployments. The new `editable` mode replaces `direct` as the default (same behavior, clearer naming). The `direct` mode is now deprecated but still works with a warning. Install modes: `editable` (with `-e`, for development), `fixed` (without `-e`, for production/Docker), `skip` (clone only).
1313
[jensens]
14-
- Fix #65: When generating requirements-mxdev.txt, check if source directories actually exist before writing package references. If a source directory doesn't exist (e.g., when using `mxdev -n` without prior fetch, or in offline mode), write it as a comment with a contextual warning instead of causing pip install to fail later. This fixes the mxmake two-stage installation workflow where `mxdev -n` runs before sources are checked out. Warning messages include context about offline mode when applicable.
14+
- Fix #65: Check source directories exist before writing to requirements-mxdev.txt. In **offline mode**: missing sources log WARNING and are written as comments (expected behavior). In **non-offline mode**: missing sources log ERROR and mxdev exits with RuntimeError (fatal error indicating checkout failure). This fixes mxmake two-stage installation workflow and prevents silent failures when sources fail to check out.
1515
[jensens]
1616
- Fix #35: Add `smart-threading` configuration option to prevent overlapping credential prompts when using HTTPS URLs. When enabled (default), HTTPS packages are processed serially first to ensure clean credential prompts, then other packages are processed in parallel for speed. Can be disabled with `smart-threading = false` if you have credential helpers configured.
1717
[jensens]

src/mxdev/processing.py

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,7 @@ def write_dev_sources(fio, packages: dict[str, dict[str, typing.Any]], state: St
216216
from .config import to_bool
217217

218218
offline_mode = to_bool(state.configuration.settings.get("offline", False))
219+
missing_sources = [] # Track missing sources for error handling
219220

220221
fio.write("#" * 79 + "\n")
221222
fio.write("# mxdev development sources\n")
@@ -235,19 +236,25 @@ def write_dev_sources(fio, packages: dict[str, dict[str, typing.Any]], state: St
235236
install_line = f"""{prefix}./{package['target']}/{name}{subdir}{extras}"""
236237

237238
if not source_path.exists():
238-
# Source not checked out yet - write as comment with warning
239+
# Source not checked out yet - write as comment
240+
missing_sources.append(name)
241+
239242
if offline_mode:
243+
# In offline mode, missing sources are expected - log as WARNING
240244
reason = (
241245
f"Source directory does not exist: {source_path} (package: {name}). "
242246
f"This is expected in offline mode. Run mxdev without -n and --offline flags to fetch sources."
243247
)
248+
logger.warning(reason)
244249
else:
250+
# In non-offline mode, missing sources are a fatal error - log as ERROR
245251
reason = (
246252
f"Source directory does not exist: {source_path} (package: {name}). "
247-
f"This could be because -n (no-fetch) flag was used or sources haven't been checked out yet. "
253+
f"This indicates a failure in the checkout process. "
248254
f"Run mxdev without -n flag to fetch sources."
249255
)
250-
logger.warning(reason)
256+
logger.error(reason)
257+
251258
fio.write(f"# {install_line} # mxdev: source not checked out\n")
252259
else:
253260
# Source exists - write normally
@@ -256,6 +263,14 @@ def write_dev_sources(fio, packages: dict[str, dict[str, typing.Any]], state: St
256263

257264
fio.write("\n\n")
258265

266+
# In non-offline mode, missing sources are a fatal error
267+
if not offline_mode and missing_sources:
268+
raise RuntimeError(
269+
f"Source directories missing for packages: {', '.join(missing_sources)}. "
270+
f"This indicates a failure in the checkout process. "
271+
f"Run mxdev without -n flag to fetch sources."
272+
)
273+
259274

260275
def write_dev_overrides(fio, overrides: dict[str, str], package_keys: list[str]):
261276
"""Create requirements configuration for overridden packages."""

tests/test_processing.py

Lines changed: 65 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -628,20 +628,21 @@ def test_write_relative_constraints_path_different_dirs(tmp_path):
628628

629629

630630
def test_write_dev_sources_missing_directories(tmp_path, caplog):
631-
"""Test write_dev_sources with non-existing source directories.
631+
"""Test write_dev_sources with non-existing source directories in offline mode.
632632
633-
When source directories don't exist (e.g., when using mxdev -n),
633+
When source directories don't exist in offline mode (expected behavior),
634634
packages should be written as comments with warnings.
635635
"""
636636
from mxdev.config import Configuration
637637
from mxdev.processing import write_dev_sources
638638
from mxdev.state import State
639639

640-
# Create config without offline mode
640+
# Create config WITH offline mode
641641
config_file = tmp_path / "mx.ini"
642642
config_file.write_text(
643643
"""[settings]
644644
requirements-in = requirements.txt
645+
offline = true
645646
"""
646647
)
647648
config = Configuration(str(config_file))
@@ -688,11 +689,70 @@ def test_write_dev_sources_missing_directories(tmp_path, caplog):
688689
assert "# -e ./sources/missing.package # mxdev: source not checked out\n" in content
689690
assert "# ./sources/missing.fixed[test] # mxdev: source not checked out\n" in content
690691

691-
# Check warnings were logged
692+
# Check warnings were logged (offline mode specific)
692693
assert "Source directory does not exist" in caplog.text
693694
assert "missing.package" in caplog.text
694695
assert "missing.fixed" in caplog.text
695-
assert "Run mxdev without -n flag" in caplog.text
696+
assert "This is expected in offline mode" in caplog.text
697+
assert "Run mxdev without -n and --offline flags" in caplog.text
698+
699+
700+
def test_write_dev_sources_missing_directories_raises_error(tmp_path, caplog):
701+
"""Test write_dev_sources raises RuntimeError when sources missing in non-offline mode.
702+
703+
When source directories don't exist and we're NOT in offline mode,
704+
this is a fatal error - something went wrong earlier in the workflow.
705+
"""
706+
from mxdev.config import Configuration
707+
from mxdev.processing import write_dev_sources
708+
from mxdev.state import State
709+
710+
import pytest
711+
712+
# Create config WITHOUT offline mode (non-offline mode)
713+
config_file = tmp_path / "mx.ini"
714+
config_file.write_text(
715+
"""[settings]
716+
requirements-in = requirements.txt
717+
"""
718+
)
719+
config = Configuration(str(config_file))
720+
state = State(configuration=config)
721+
722+
# Define packages but DON'T create source directories
723+
packages = {
724+
"missing.package": {
725+
"target": "sources",
726+
"path": str(tmp_path / "sources" / "missing.package"),
727+
"extras": "",
728+
"subdirectory": "",
729+
"install-mode": "editable",
730+
},
731+
"missing.fixed": {
732+
"target": "sources",
733+
"path": str(tmp_path / "sources" / "missing.fixed"),
734+
"extras": "test",
735+
"subdirectory": "",
736+
"install-mode": "fixed",
737+
},
738+
}
739+
740+
outfile = tmp_path / "requirements.txt"
741+
742+
# Should raise RuntimeError for missing sources in non-offline mode
743+
with pytest.raises(RuntimeError) as exc_info:
744+
with open(outfile, "w") as fio:
745+
write_dev_sources(fio, packages, state)
746+
747+
# Error message should contain package names
748+
error_msg = str(exc_info.value)
749+
assert "missing.package" in error_msg
750+
assert "missing.fixed" in error_msg
751+
assert "Source directories missing" in error_msg
752+
753+
# Should log ERROR (not just WARNING)
754+
assert any(record.levelname == "ERROR" for record in caplog.records)
755+
assert "Source directory does not exist" in caplog.text
696756

697757

698758
def test_write_dev_sources_missing_directories_offline_mode(tmp_path, caplog):

0 commit comments

Comments
 (0)