Skip to content

Commit 0767ee8

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 83f862a commit 0767ee8

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
@@ -20,7 +20,7 @@
2020
[jensens]
2121
- 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).
2222
[jensens]
23-
- 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.
23+
- 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.
2424
[jensens]
2525
- 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.
2626
[jensens]

src/mxdev/processing.py

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

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

223224
fio.write("#" * 79 + "\n")
224225
fio.write("# mxdev development sources\n")
@@ -238,19 +239,25 @@ def write_dev_sources(fio, packages: dict[str, dict[str, typing.Any]], state: St
238239
install_line = f"""{prefix}./{package['target']}/{name}{subdir}{extras}"""
239240

240241
if not source_path.exists():
241-
# Source not checked out yet - write as comment with warning
242+
# Source not checked out yet - write as comment
243+
missing_sources.append(name)
244+
242245
if offline_mode:
246+
# In offline mode, missing sources are expected - log as WARNING
243247
reason = (
244248
f"Source directory does not exist: {source_path} (package: {name}). "
245249
f"This is expected in offline mode. Run mxdev without -n and --offline flags to fetch sources."
246250
)
251+
logger.warning(reason)
247252
else:
253+
# In non-offline mode, missing sources are a fatal error - log as ERROR
248254
reason = (
249255
f"Source directory does not exist: {source_path} (package: {name}). "
250-
f"This could be because -n (no-fetch) flag was used or sources haven't been checked out yet. "
256+
f"This indicates a failure in the checkout process. "
251257
f"Run mxdev without -n flag to fetch sources."
252258
)
253-
logger.warning(reason)
259+
logger.error(reason)
260+
254261
fio.write(f"# {install_line} # mxdev: source not checked out\n")
255262
else:
256263
# Source exists - write normally
@@ -259,6 +266,14 @@ def write_dev_sources(fio, packages: dict[str, dict[str, typing.Any]], state: St
259266

260267
fio.write("\n\n")
261268

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

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

tests/test_processing.py

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

674674

675675
def test_write_dev_sources_missing_directories(tmp_path, caplog):
676-
"""Test write_dev_sources with non-existing source directories.
676+
"""Test write_dev_sources with non-existing source directories in offline mode.
677677
678-
When source directories don't exist (e.g., when using mxdev -n),
678+
When source directories don't exist in offline mode (expected behavior),
679679
packages should be written as comments with warnings.
680680
"""
681681
from mxdev.config import Configuration
682682
from mxdev.processing import write_dev_sources
683683
from mxdev.state import State
684684

685-
# Create config without offline mode
685+
# Create config WITH offline mode
686686
config_file = tmp_path / "mx.ini"
687687
config_file.write_text(
688688
"""[settings]
689689
requirements-in = requirements.txt
690+
offline = true
690691
"""
691692
)
692693
config = Configuration(str(config_file))
@@ -733,11 +734,70 @@ def test_write_dev_sources_missing_directories(tmp_path, caplog):
733734
assert "# -e ./sources/missing.package # mxdev: source not checked out\n" in content
734735
assert "# ./sources/missing.fixed[test] # mxdev: source not checked out\n" in content
735736

736-
# Check warnings were logged
737+
# Check warnings were logged (offline mode specific)
737738
assert "Source directory does not exist" in caplog.text
738739
assert "missing.package" in caplog.text
739740
assert "missing.fixed" in caplog.text
740-
assert "Run mxdev without -n flag" in caplog.text
741+
assert "This is expected in offline mode" in caplog.text
742+
assert "Run mxdev without -n and --offline flags" in caplog.text
743+
744+
745+
def test_write_dev_sources_missing_directories_raises_error(tmp_path, caplog):
746+
"""Test write_dev_sources raises RuntimeError when sources missing in non-offline mode.
747+
748+
When source directories don't exist and we're NOT in offline mode,
749+
this is a fatal error - something went wrong earlier in the workflow.
750+
"""
751+
from mxdev.config import Configuration
752+
from mxdev.processing import write_dev_sources
753+
from mxdev.state import State
754+
755+
import pytest
756+
757+
# Create config WITHOUT offline mode (non-offline mode)
758+
config_file = tmp_path / "mx.ini"
759+
config_file.write_text(
760+
"""[settings]
761+
requirements-in = requirements.txt
762+
"""
763+
)
764+
config = Configuration(str(config_file))
765+
state = State(configuration=config)
766+
767+
# Define packages but DON'T create source directories
768+
packages = {
769+
"missing.package": {
770+
"target": "sources",
771+
"path": str(tmp_path / "sources" / "missing.package"),
772+
"extras": "",
773+
"subdirectory": "",
774+
"install-mode": "editable",
775+
},
776+
"missing.fixed": {
777+
"target": "sources",
778+
"path": str(tmp_path / "sources" / "missing.fixed"),
779+
"extras": "test",
780+
"subdirectory": "",
781+
"install-mode": "fixed",
782+
},
783+
}
784+
785+
outfile = tmp_path / "requirements.txt"
786+
787+
# Should raise RuntimeError for missing sources in non-offline mode
788+
with pytest.raises(RuntimeError) as exc_info:
789+
with open(outfile, "w") as fio:
790+
write_dev_sources(fio, packages, state)
791+
792+
# Error message should contain package names
793+
error_msg = str(exc_info.value)
794+
assert "missing.package" in error_msg
795+
assert "missing.fixed" in error_msg
796+
assert "Source directories missing" in error_msg
797+
798+
# Should log ERROR (not just WARNING)
799+
assert any(record.levelname == "ERROR" for record in caplog.records)
800+
assert "Source directory does not exist" in caplog.text
741801

742802

743803
def test_write_dev_sources_missing_directories_offline_mode(tmp_path, caplog):

0 commit comments

Comments
 (0)