From fa867e525b769f59bfcb9e0056840cd925050c00 Mon Sep 17 00:00:00 2001 From: "Jens W. Klein" Date: Wed, 22 Oct 2025 08:55:19 +0200 Subject: [PATCH 01/14] Add refactoring plan for mxenv.mk auto-detection Addresses #43: autodetect global uv Key goals: - Auto-detect global UV instead of manual MXENV_UV_GLOBAL setting - Simplify complex nested conditionals - Add UV_PYTHON setting for semantic clarity - Make UV commands non-interactive for CI/CD See REFACTOR_MXENV_PLAN.md for detailed implementation plan. --- REFACTOR_MXENV_PLAN.md | 379 ++++++++++++++++++++++++++++++++++++++ REFACTOR_MXENV_SUMMARY.md | 72 ++++++++ 2 files changed, 451 insertions(+) create mode 100644 REFACTOR_MXENV_PLAN.md create mode 100644 REFACTOR_MXENV_SUMMARY.md diff --git a/REFACTOR_MXENV_PLAN.md b/REFACTOR_MXENV_PLAN.md new file mode 100644 index 0000000..7740e52 --- /dev/null +++ b/REFACTOR_MXENV_PLAN.md @@ -0,0 +1,379 @@ +# Refactor mxenv.mk: Auto-Detect UV and Simplify Logic + +**Issue**: https://github.com/mxstack/mxmake/issues/43 +**Current File**: [src/mxmake/topics/core/mxenv.mk](src/mxmake/topics/core/mxenv.mk) + +## Problem Statement + +The current `mxenv.mk` implementation has become overly complex: + +1. **Deeply nested conditionals** (3+ levels in places) +2. **Hard-to-read string concatenation checks** like `"$(PYTHON_PACKAGE_INSTALLER)$(MXENV_UV_GLOBAL)","uvtrue"` +3. **Scattered logic** mixing validation, venv creation, and package installation +4. **Manual `MXENV_UV_GLOBAL` setting** instead of auto-detection + +This makes it difficult to understand, maintain, and extend with new features like UV auto-detection. + +## Goals + +1. **Auto-detect** global `uv` availability instead of requiring manual `MXENV_UV_GLOBAL` setting +2. **Reduce complexity** and nesting of conditionals +3. **Maintain all existing functionality** without breaking changes to behavior +4. **Make future changes easier** by establishing clearer patterns + +## Proposed Changes + +### 1. Replace `MXENV_UV_GLOBAL` Setting with Auto-Detection + +**Location**: Lines 45-48 (domain metadata) + +**Remove**: +```makefile +#:[setting.MXENV_UV_GLOBAL] +#:description = Flag whether to use a global installed 'uv' or install +#: it in the virtual environment. +#:default = false +``` + +**Add**: +```makefile +#:[setting.UV_PYTHON] +#:description = Python version for UV to install/use when creating virtual +#: environments with global UV. Passed to `uv venv -p VALUE`. Supports version +#: specs like `3.11`, `3.14`, `cpython@3.14`. Defaults to PRIMARY_PYTHON value +#: for backward compatibility. +#:default = +``` + +**Rationale for UV_PYTHON**: +- Provides semantic clarity: `PRIMARY_PYTHON` = system interpreter path, `UV_PYTHON` = UV version spec +- Backward compatible: defaults to `PRIMARY_PYTHON` if not set +- Supports UV's Python management features properly + +**Update** `PYTHON_PACKAGE_INSTALLER` description to mention auto-detection: +```makefile +#:[setting.PYTHON_PACKAGE_INSTALLER] +#:description = Install packages using the given package installer method. +#: Supported are `pip` and `uv`. When `uv` is selected, a global installation +#: is auto-detected and used if available and meets the minimum version +#: requirement. Otherwise, uv is installed in the virtual environment or +#: using `PRIMARY_PYTHON`, depending on the `VENV_ENABLED` setting. +#:default = pip +``` + +### 2. Add UV Detection and Configuration Logic + +**Location**: After line 102 (after PYTHON_PACKAGE_COMMAND determination) + +**Add new section**: +```makefile +# Determine the package installer with non-interactive flags +ifeq ("$(PYTHON_PACKAGE_INSTALLER)","uv") +PYTHON_PACKAGE_COMMAND=uv pip --quiet --no-progress +else +PYTHON_PACKAGE_COMMAND=$(MXENV_PYTHON) -m pip +endif + +# Auto-detect global uv availability (simple existence check) +ifeq ("$(PYTHON_PACKAGE_INSTALLER)","uv") +UV_AVAILABLE:=$(shell command -v uv >/dev/null 2>&1 && echo "true" || echo "false") +else +UV_AVAILABLE:=false +endif + +# Determine installation strategy +USE_GLOBAL_UV:=$(shell [[ "$(PYTHON_PACKAGE_INSTALLER)" == "uv" && "$(UV_AVAILABLE)" == "true" ]] && echo "true" || echo "false") +USE_LOCAL_UV:=$(shell [[ "$(PYTHON_PACKAGE_INSTALLER)" == "uv" && "$(UV_AVAILABLE)" == "false" ]] && echo "true" || echo "false") + +# UV Python version (defaults to PRIMARY_PYTHON for backward compatibility) +UV_PYTHON?=$(PRIMARY_PYTHON) + +# Check if global UV is outdated (non-blocking warning) +ifeq ("$(USE_GLOBAL_UV)","true") +UV_OUTDATED:=$(shell uv self update --dry-run 2>&1 | grep -q "Would update" && echo "true" || echo "false") +else +UV_OUTDATED:=false +endif +``` + +**Rationale**: +- **Pure shell detection**: No Python dependency during Python setup +- **Non-interactive UV**: `--quiet --no-progress` flags ensure CI/CD compatibility +- **Semantic clarity**: `UV_PYTHON` separates system Python path from UV version spec +- **Simple and reliable**: Just checks if `uv` command exists in PATH +- **Backward compatible**: `UV_PYTHON` defaults to `PRIMARY_PYTHON` +- **Update awareness**: Warns if global UV is outdated using `uv self update --dry-run` + +**Note on UV updates**: Instead of checking minimum versions, we use `uv self update --dry-run` +to detect if updates are available. This leverages UV's built-in update mechanism and provides +helpful warnings without blocking execution. + +### 3. Simplify Main Target Logic + +**Location**: Lines 104-137 ($(MXENV_TARGET) recipe) + +**Current structure** (complex): +- Multiple nested `ifeq` statements +- Validation mixed with execution +- Hard to follow control flow + +**New structure** (simplified): + +```makefile +MXENV_TARGET:=$(SENTINEL_FOLDER)/mxenv.sentinel +$(MXENV_TARGET): $(SENTINEL) + # Validation: Check Python version if not using global uv +ifneq ("$(USE_GLOBAL_UV)","true") + @$(PRIMARY_PYTHON) -c "import sys; vi = sys.version_info; sys.exit(1 if (int(vi[0]), int(vi[1])) >= tuple(map(int, '$(PYTHON_MIN_VERSION)'.split('.'))) else 0)" \ + && echo "Need Python >= $(PYTHON_MIN_VERSION)" && exit 1 || : +else + @echo "Using global uv for Python $(UV_PYTHON)" +endif + # Validation: Check VENV_FOLDER is set if venv enabled + @[[ "$(VENV_ENABLED)" == "true" && "$(VENV_FOLDER)" == "" ]] \ + && echo "VENV_FOLDER must be configured if VENV_ENABLED is true" && exit 1 || : + # Validation: Check uv not used with system Python + @[[ "$(VENV_ENABLED)" == "false" && "$(PYTHON_PACKAGE_INSTALLER)" == "uv" ]] \ + && echo "Package installer uv does not work with a global Python interpreter." && exit 1 || : + # Warning: Notify if global UV is outdated +ifeq ("$(UV_OUTDATED)","true") + @echo "WARNING: A newer version of uv is available. Run 'uv self update' to upgrade." +endif + + # Create virtual environment +ifeq ("$(VENV_ENABLED)", "true") +ifeq ("$(VENV_CREATE)", "true") +ifeq ("$(USE_GLOBAL_UV)","true") + @echo "Setup Python Virtual Environment using global uv at '$(VENV_FOLDER)'" + @uv venv --quiet --no-progress -p $(UV_PYTHON) --seed $(VENV_FOLDER) +else + @echo "Setup Python Virtual Environment using module 'venv' at '$(VENV_FOLDER)'" + @$(PRIMARY_PYTHON) -m venv $(VENV_FOLDER) + @$(MXENV_PYTHON) -m ensurepip -U +endif +endif +else + @echo "Using system Python interpreter" +endif + + # Install uv locally if needed +ifeq ("$(USE_LOCAL_UV)","true") + @echo "Install uv in virtual environment" + @$(MXENV_PYTHON) -m pip install uv +endif + + # Install/upgrade core packages + @$(PYTHON_PACKAGE_COMMAND) install -U pip setuptools wheel + @echo "Install/Update MXStack Python packages" + @$(PYTHON_PACKAGE_COMMAND) install -U $(MXDEV) $(MXMAKE) + @touch $(MXENV_TARGET) +``` + +**Key improvements**: +- **All validation at the top** (easier to see preconditions) +- **Single-level conditionals** using computed variables (`USE_GLOBAL_UV`, `USE_LOCAL_UV`) +- **Clear sections**: validation → warnings → venv creation → uv installation → package installation +- **Semantic clarity**: `PRIMARY_PYTHON` (system) vs `UV_PYTHON` (UV version spec) +- **Non-interactive UV**: All UV commands use `--quiet --no-progress` for CI/CD compatibility +- **Update awareness**: Warns users if global UV is outdated (using `uv self update --dry-run`) +- **Removed complex string concatenation checks** like `"$(PYTHON_PACKAGE_INSTALLER)$(MXENV_UV_GLOBAL)","uvtrue"` + +### 4. Update Tests and Documentation + +**Files to update**: + +1. **[src/mxmake/tests/expected/Makefile](src/mxmake/tests/expected/Makefile)**: + - Remove `MXENV_UV_GLOBAL` setting (lines 58-61) + - Add `UV_PYTHON` setting if tests need to verify it + +2. **[src/mxmake/tests/test_parser.py](src/mxmake/tests/test_parser.py)**: + - Check if any tests reference `MXENV_UV_GLOBAL` and update them + - Add tests for `UV_PYTHON` if needed + +3. **[src/mxmake/tests/test_templates.py](src/mxmake/tests/test_templates.py)**: + - Check if any tests set `MXENV_UV_GLOBAL` and update them + - Add tests for non-interactive UV flags if needed + +4. **[CLAUDE.md](CLAUDE.md)**: + - Update if it mentions `MXENV_UV_GLOBAL` + +### 5. Handle Backwards Compatibility + +**Parser changes** ([src/mxmake/parser.py](src/mxmake/parser.py)): +- When parsing old Makefiles with `MXENV_UV_GLOBAL`, silently ignore it +- Don't include it in the settings to preserve + +**Migration note in CHANGES.md**: +```markdown +### Breaking Changes + +- **mxenv domain**: Removed `MXENV_UV_GLOBAL` setting in favor of automatic + UV detection. When `PYTHON_PACKAGE_INSTALLER=uv`, mxmake now automatically + detects and uses a globally installed `uv` if available. To force local + installation of uv, simply don't install it globally or remove it from PATH. + +### New Features + +- **mxenv domain**: Added `UV_PYTHON` setting to specify Python version for + UV-managed virtual environments. Defaults to `PRIMARY_PYTHON` for backward + compatibility. This provides semantic clarity: `PRIMARY_PYTHON` is the system + interpreter path (e.g., `python3.11`), while `UV_PYTHON` is the version spec + for UV (e.g., `3.14`, `cpython@3.14`). + +- **mxenv domain**: All UV commands now run with `--quiet --no-progress` flags + for better CI/CD compatibility and cleaner log output. + +- **mxenv domain**: When using global UV, mxmake now checks if updates are + available using `uv self update --dry-run` and displays a helpful warning + if a newer version is available. This is non-blocking and helps keep UV current. +``` + +## Implementation Order + +1. **Update mxenv.mk** with new logic +2. **Update test fixtures** (expected/Makefile) +3. **Run tests** to verify no breakage +4. **Update documentation** (CHANGES.md, CLAUDE.md if needed) +5. **Test manually** with both global and local uv scenarios +6. **Create PR** with clear migration notes + +## Testing Strategy + +Test the following scenarios: + +1. **Global uv available** (`PYTHON_PACKAGE_INSTALLER=uv`) + - Should use global uv for venv creation + - Should not install uv locally + +2. **No global uv** (`PYTHON_PACKAGE_INSTALLER=uv`) + - Should create venv with Python's venv module + - Should install uv in the virtual environment + +3. **Using pip** (`PYTHON_PACKAGE_INSTALLER=pip`) + - Should work exactly as before + - Should not check for uv + +4. **Force local uv** (`PYTHON_PACKAGE_INSTALLER=uv`) + - Remove global `uv` from PATH temporarily + - Should install uv locally in virtual environment + +5. **Old Makefile migration** + - Makefile with `MXENV_UV_GLOBAL=true` should work after `mxmake update` + - Setting should be removed from updated Makefile + +6. **UV_PYTHON with different value than PRIMARY_PYTHON** + - Set `PRIMARY_PYTHON=python3.11` and `UV_PYTHON=3.14` + - Should use system Python 3.11 for non-UV venv creation + - Should use UV to install Python 3.14 when global UV available + +7. **CI/Non-TTY environment** + - Run in non-interactive shell or CI environment + - UV commands should not output progress bars + - Logs should be clean without ANSI escape codes + +8. **UV update check** + - With outdated global UV: Should display warning message + - With current global UV: Should not display warning + - With local UV: Should not check for updates + +## Benefits + +### Simplification +- **Reduces nesting**: From 3+ levels to 1-2 levels maximum +- **Clearer flow**: Validation → Creation → Installation is explicit +- **Self-documenting**: Variables like `USE_GLOBAL_UV` explain intent + +### Auto-detection +- **No manual configuration**: Users don't need to know about `MXENV_UV_GLOBAL` +- **Smart defaults**: Uses global uv when available +- **Override capability**: Can force local uv by not installing globally +- **Update awareness**: Helpful warnings when global UV is outdated (non-blocking) + +### Semantic Clarity +- **UV_PYTHON setting**: Separates system Python path from UV version spec +- **Backward compatible**: Defaults to `PRIMARY_PYTHON` if not set +- **Explicit intent**: Makes it clear when using UV's Python management vs system Python + +### CI/CD Friendly +- **Non-interactive**: All UV commands use `--quiet --no-progress` +- **Clean logs**: No progress bars or unnecessary output +- **Reliable**: Works in any environment (TTY or non-TTY) + +### Maintainability +- **Easy to extend**: Adding new installers is straightforward +- **Easier debugging**: Clear sections make issues easier to locate +- **Better testing**: Computed variables are easier to test + +### Future-proof +- **Plugin architecture ready**: Clear pattern for adding installers +- **Version checking ready**: Framework exists for version requirements +- **Migration path**: Pattern for removing deprecated settings + +## Risks and Mitigations + +### Risk: Breaking existing workflows +**Mitigation**: +- Thorough testing with various configurations +- Clear migration documentation +- Parser silently handles old settings + +### Risk: UV detection false positives/negatives +**Mitigation**: +- Simple, reliable detection (just check if `uv` command exists) +- Override by controlling UV availability in PATH +- Clear error messages when UV is required but unavailable + +### Risk: Performance impact of shell commands and network checks +**Mitigation**: +- Detection runs once per make invocation +- Uses efficient shell builtins (`command -v`) +- Cached in Make variables +- UV update check is fast (just metadata check, no download) +- Acceptable trade-off: ~100-200ms for helpful update notifications + +**Note**: The `uv self update --dry-run` check makes a network request. In restricted +network environments or offline scenarios, this may add slight delay or fail silently. +This is acceptable as it's a non-blocking warning only. + +**Future enhancement**: Cache update check results: +```makefile +# Only check for updates once per day +UV_UPDATE_CHECK_FILE:=$(MXMAKE_FOLDER)/.uv-update-check +UV_OUTDATED:=$(shell \ + if [[ ! -f "$(UV_UPDATE_CHECK_FILE)" ]] || \ + [[ $$(find "$(UV_UPDATE_CHECK_FILE)" -mtime +1 2>/dev/null) ]]; then \ + uv self update --dry-run 2>&1 | grep -q "Would update" && echo "true" || echo "false"; \ + touch "$(UV_UPDATE_CHECK_FILE)"; \ + else \ + echo "false"; \ + fi) +``` + +## Decisions Made + +1. **Version checking**: ✅ Just check availability, not version + - Simple and reliable + - UV is stable enough that version checking is not critical + - Can be added later if needed + +2. **Non-interactive flags**: ✅ Always apply `--quiet --no-progress` + - Ensures CI/CD compatibility + - Clean log output + - No configuration needed + +3. **UV_PYTHON setting**: ✅ Add new setting with default to PRIMARY_PYTHON + - Provides semantic clarity + - Backward compatible + - Supports UV's Python management features + +4. **Deprecation period**: ✅ Silent removal of `MXENV_UV_GLOBAL` + - Parser ignores it if found + - Clear migration notes in CHANGES.md + - No warning needed + +5. **UV update check**: ✅ Warn if global UV is outdated + - Use `uv self update --dry-run` to detect available updates + - Non-blocking warning message + - Only checks when using global UV + - Helps users keep UV current without enforcing it + - Future: Can add caching to reduce network calls diff --git a/REFACTOR_MXENV_SUMMARY.md b/REFACTOR_MXENV_SUMMARY.md new file mode 100644 index 0000000..5978247 --- /dev/null +++ b/REFACTOR_MXENV_SUMMARY.md @@ -0,0 +1,72 @@ +# mxenv.mk Refactoring Summary + +## Overview + +Refactor `mxenv.mk` to auto-detect global `uv` and simplify the overly complex conditional logic that has accumulated over time. + +## Problem + +The current implementation has become difficult to maintain: +- Deeply nested conditionals (3+ levels) +- Complex string concatenation checks like `"$(PYTHON_PACKAGE_INSTALLER)$(MXENV_UV_GLOBAL)","uvtrue"` +- Manual `MXENV_UV_GLOBAL` setting requiring user configuration +- Mixed concerns (validation, creation, installation) + +## Solution + +**Auto-detect UV availability** instead of requiring manual configuration: +- Simple shell check: `command -v uv` +- No Python dependency during Python setup +- Only applies when `PYTHON_PACKAGE_INSTALLER=uv` + +**Simplify logic** using computed intermediate variables: +- `USE_GLOBAL_UV` - Use detected global UV +- `USE_LOCAL_UV` - Install UV locally +- Clear sections: validation → warnings → venv → installation + +**Improve semantics** with new `UV_PYTHON` setting: +- `PRIMARY_PYTHON` = system interpreter path (e.g., `python3.11`) +- `UV_PYTHON` = UV version spec (e.g., `3.14`, `cpython@3.14`) +- Backward compatible: `UV_PYTHON` defaults to `PRIMARY_PYTHON` + +**Additional improvements**: +- All UV commands use `--quiet --no-progress` for CI/CD compatibility +- Optional warning when global UV is outdated (via `uv self update --dry-run`) +- Non-blocking, helpful notifications + +## Breaking Changes + +- **Removed**: `MXENV_UV_GLOBAL` setting + - Replaced with automatic detection + - Parser will silently ignore if found in old Makefiles + +## New Features + +- **Added**: `UV_PYTHON` setting for UV version specification +- **Added**: Auto-detection of global UV +- **Added**: Non-interactive flags for all UV commands +- **Added**: Optional update check for global UV + +## Benefits + +1. **Simpler**: Reduces complexity from 3+ nesting levels to 1-2 +2. **Automatic**: No manual `MXENV_UV_GLOBAL` configuration needed +3. **Clearer**: Self-documenting variables and clear control flow +4. **Maintainable**: Easy to extend and debug +5. **CI-friendly**: Non-interactive by default +6. **User-helpful**: Warns about outdated UV installations + +## Migration + +Existing Makefiles continue to work: +- `MXENV_UV_GLOBAL` is silently ignored if present +- `UV_PYTHON` defaults to `PRIMARY_PYTHON` value +- Run `mxmake update` to regenerate with new settings + +## Implementation Status + +- [x] Refactoring plan completed: `REFACTOR_MXENV_PLAN.md` +- [ ] Update `mxenv.mk` implementation +- [ ] Update tests and fixtures +- [ ] Update documentation +- [ ] Manual testing with various scenarios From 94d735990c97296838db165659592d84e3d042ab Mon Sep 17 00:00:00 2001 From: "Jens W. Klein" Date: Wed, 22 Oct 2025 08:56:46 +0200 Subject: [PATCH 02/14] Refactor mxenv.mk: auto-detect UV and simplify logic MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Major changes: - Remove MXENV_UV_GLOBAL setting, replaced with auto-detection - Add UV_PYTHON setting for semantic clarity (defaults to PRIMARY_PYTHON) - Add non-interactive flags (--quiet --no-progress) to all UV commands - Simplify main target with computed variables (USE_GLOBAL_UV, USE_LOCAL_UV) - Add UV update check with non-blocking warning - Reduce conditional nesting from 3+ levels to 1-2 levels Benefits: - Pure shell detection (no Python during Python setup) - Clear sections: validation → warnings → venv → installation - Self-documenting with intermediate variables - CI/CD friendly with non-interactive flags --- src/mxmake/topics/core/mxenv.mk | 73 ++++++++++++++++++++++++--------- 1 file changed, 54 insertions(+), 19 deletions(-) diff --git a/src/mxmake/topics/core/mxenv.mk b/src/mxmake/topics/core/mxenv.mk index 3277a1a..b944800 100644 --- a/src/mxmake/topics/core/mxenv.mk +++ b/src/mxmake/topics/core/mxenv.mk @@ -35,17 +35,18 @@ #: #:[setting.PYTHON_PACKAGE_INSTALLER] #:description = Install packages using the given package installer method. -#: Supported are `pip` and `uv`. If uv is used, its global availability is -#: checked. Otherwise, it is installed, either in the virtual environment or -#: using the `PRIMARY_PYTHON`, dependent on the `VENV_ENABLED` setting. If -#: `VENV_ENABLED` and uv is selected, uv is used to create the virtual -#: environment. +#: Supported are `pip` and `uv`. When `uv` is selected, a global installation +#: is auto-detected and used if available. Otherwise, uv is installed in the +#: virtual environment or using `PRIMARY_PYTHON`, depending on the +#: `VENV_ENABLED` setting. #:default = pip #: -#:[setting.MXENV_UV_GLOBAL] -#:description = Flag whether to use a global installed 'uv' or install -#: it in the virtual environment. -#:default = false +#:[setting.UV_PYTHON] +#:description = Python version for UV to install/use when creating virtual +#: environments with global UV. Passed to `uv venv -p VALUE`. Supports version +#: specs like `3.11`, `3.14`, `cpython@3.14`. Defaults to PRIMARY_PYTHON value +#: for backward compatibility. +#:default = #: #:[setting.VENV_ENABLED] #:description = Flag whether to use virtual environment. If `false`, the @@ -94,30 +95,60 @@ else MXENV_PYTHON=$(PRIMARY_PYTHON) endif -# Determine the package installer +# Determine the package installer with non-interactive flags ifeq ("$(PYTHON_PACKAGE_INSTALLER)","uv") -PYTHON_PACKAGE_COMMAND=uv pip +PYTHON_PACKAGE_COMMAND=uv pip --quiet --no-progress else PYTHON_PACKAGE_COMMAND=$(MXENV_PYTHON) -m pip endif +# Auto-detect global uv availability (simple existence check) +ifeq ("$(PYTHON_PACKAGE_INSTALLER)","uv") +UV_AVAILABLE:=$(shell command -v uv >/dev/null 2>&1 && echo "true" || echo "false") +else +UV_AVAILABLE:=false +endif + +# Determine installation strategy +USE_GLOBAL_UV:=$(shell [[ "$(PYTHON_PACKAGE_INSTALLER)" == "uv" && "$(UV_AVAILABLE)" == "true" ]] && echo "true" || echo "false") +USE_LOCAL_UV:=$(shell [[ "$(PYTHON_PACKAGE_INSTALLER)" == "uv" && "$(UV_AVAILABLE)" == "false" ]] && echo "true" || echo "false") + +# UV Python version (defaults to PRIMARY_PYTHON for backward compatibility) +UV_PYTHON?=$(PRIMARY_PYTHON) + +# Check if global UV is outdated (non-blocking warning) +ifeq ("$(USE_GLOBAL_UV)","true") +UV_OUTDATED:=$(shell uv self update --dry-run 2>&1 | grep -q "Would update" && echo "true" || echo "false") +else +UV_OUTDATED:=false +endif + MXENV_TARGET:=$(SENTINEL_FOLDER)/mxenv.sentinel $(MXENV_TARGET): $(SENTINEL) -ifneq ("$(PYTHON_PACKAGE_INSTALLER)$(MXENV_UV_GLOBAL)","uvfalse") + # Validation: Check Python version if not using global uv +ifneq ("$(USE_GLOBAL_UV)","true") @$(PRIMARY_PYTHON) -c "import sys; vi = sys.version_info; sys.exit(1 if (int(vi[0]), int(vi[1])) >= tuple(map(int, '$(PYTHON_MIN_VERSION)'.split('.'))) else 0)" \ && echo "Need Python >= $(PYTHON_MIN_VERSION)" && exit 1 || : else - @echo "Use Python $(PYTHON_MIN_VERSION) over uv" + @echo "Using global uv for Python $(UV_PYTHON)" endif + # Validation: Check VENV_FOLDER is set if venv enabled @[[ "$(VENV_ENABLED)" == "true" && "$(VENV_FOLDER)" == "" ]] \ && echo "VENV_FOLDER must be configured if VENV_ENABLED is true" && exit 1 || : - @[[ "$(VENV_ENABLED)$(PYTHON_PACKAGE_INSTALLER)" == "falseuv" ]] \ + # Validation: Check uv not used with system Python + @[[ "$(VENV_ENABLED)" == "false" && "$(PYTHON_PACKAGE_INSTALLER)" == "uv" ]] \ && echo "Package installer uv does not work with a global Python interpreter." && exit 1 || : + # Warning: Notify if global UV is outdated +ifeq ("$(UV_OUTDATED)","true") + @echo "WARNING: A newer version of uv is available. Run 'uv self update' to upgrade." +endif + + # Create virtual environment ifeq ("$(VENV_ENABLED)", "true") ifeq ("$(VENV_CREATE)", "true") -ifeq ("$(PYTHON_PACKAGE_INSTALLER)$(MXENV_UV_GLOBAL)","uvtrue") - @echo "Setup Python Virtual Environment using package 'uv' at '$(VENV_FOLDER)'" - @uv venv -p $(PRIMARY_PYTHON) --seed $(VENV_FOLDER) +ifeq ("$(USE_GLOBAL_UV)","true") + @echo "Setup Python Virtual Environment using global uv at '$(VENV_FOLDER)'" + @uv venv --quiet --no-progress -p $(UV_PYTHON) --seed $(VENV_FOLDER) else @echo "Setup Python Virtual Environment using module 'venv' at '$(VENV_FOLDER)'" @$(PRIMARY_PYTHON) -m venv $(VENV_FOLDER) @@ -127,10 +158,14 @@ endif else @echo "Using system Python interpreter" endif -ifeq ("$(PYTHON_PACKAGE_INSTALLER)$(MXENV_UV_GLOBAL)","uvfalse") - @echo "Install uv" + + # Install uv locally if needed +ifeq ("$(USE_LOCAL_UV)","true") + @echo "Install uv in virtual environment" @$(MXENV_PYTHON) -m pip install uv endif + + # Install/upgrade core packages @$(PYTHON_PACKAGE_COMMAND) install -U pip setuptools wheel @echo "Install/Update MXStack Python packages" @$(PYTHON_PACKAGE_COMMAND) install -U $(MXDEV) $(MXMAKE) From eeeceb2a883f0dc0ea7b1a91385df5d6ec056c43 Mon Sep 17 00:00:00 2001 From: "Jens W. Klein" Date: Wed, 22 Oct 2025 08:58:18 +0200 Subject: [PATCH 03/14] Update test fixtures for mxenv refactoring - Remove MXENV_UV_GLOBAL from expected Makefile - Add UV detection logic and computed variables - Update main target with simplified structure - Add UV_PYTHON support and non-interactive flags --- src/mxmake/tests/expected/Makefile | 68 +++++++++++++++++++++--------- 1 file changed, 48 insertions(+), 20 deletions(-) diff --git a/src/mxmake/tests/expected/Makefile b/src/mxmake/tests/expected/Makefile index 97f2a10..97b7c1d 100644 --- a/src/mxmake/tests/expected/Makefile +++ b/src/mxmake/tests/expected/Makefile @@ -48,19 +48,13 @@ PRIMARY_PYTHON?=python3 PYTHON_MIN_VERSION?=3.9 # Install packages using the given package installer method. -# Supported are `pip` and `uv`. If uv is used, its global availability is -# checked. Otherwise, it is installed, either in the virtual environment or -# using the `PRIMARY_PYTHON`, dependent on the `VENV_ENABLED` setting. If -# `VENV_ENABLED` and uv is selected, uv is used to create the virtual -# environment. +# Supported are `pip` and `uv`. When `uv` is selected, a global installation +# is auto-detected and used if available. Otherwise, uv is installed in the +# virtual environment or using `PRIMARY_PYTHON`, depending on the +# `VENV_ENABLED` setting. # Default: pip PYTHON_PACKAGE_INSTALLER?=pip -# Flag whether to use a global installed 'uv' or install -# it in the virtual environment. -# Default: false -MXENV_UV_GLOBAL?=false - # Flag whether to use virtual environment. If `false`, the # interpreter according to `PRIMARY_PYTHON` found in `PATH` is used. # Default: true @@ -139,30 +133,60 @@ else MXENV_PYTHON=$(PRIMARY_PYTHON) endif -# Determine the package installer +# Determine the package installer with non-interactive flags ifeq ("$(PYTHON_PACKAGE_INSTALLER)","uv") -PYTHON_PACKAGE_COMMAND=uv pip +PYTHON_PACKAGE_COMMAND=uv pip --quiet --no-progress else PYTHON_PACKAGE_COMMAND=$(MXENV_PYTHON) -m pip endif +# Auto-detect global uv availability (simple existence check) +ifeq ("$(PYTHON_PACKAGE_INSTALLER)","uv") +UV_AVAILABLE:=$(shell command -v uv >/dev/null 2>&1 && echo "true" || echo "false") +else +UV_AVAILABLE:=false +endif + +# Determine installation strategy +USE_GLOBAL_UV:=$(shell [[ "$(PYTHON_PACKAGE_INSTALLER)" == "uv" && "$(UV_AVAILABLE)" == "true" ]] && echo "true" || echo "false") +USE_LOCAL_UV:=$(shell [[ "$(PYTHON_PACKAGE_INSTALLER)" == "uv" && "$(UV_AVAILABLE)" == "false" ]] && echo "true" || echo "false") + +# UV Python version (defaults to PRIMARY_PYTHON for backward compatibility) +UV_PYTHON?=$(PRIMARY_PYTHON) + +# Check if global UV is outdated (non-blocking warning) +ifeq ("$(USE_GLOBAL_UV)","true") +UV_OUTDATED:=$(shell uv self update --dry-run 2>&1 | grep -q "Would update" && echo "true" || echo "false") +else +UV_OUTDATED:=false +endif + MXENV_TARGET:=$(SENTINEL_FOLDER)/mxenv.sentinel $(MXENV_TARGET): $(SENTINEL) -ifneq ("$(PYTHON_PACKAGE_INSTALLER)$(MXENV_UV_GLOBAL)","uvfalse") + # Validation: Check Python version if not using global uv +ifneq ("$(USE_GLOBAL_UV)","true") @$(PRIMARY_PYTHON) -c "import sys; vi = sys.version_info; sys.exit(1 if (int(vi[0]), int(vi[1])) >= tuple(map(int, '$(PYTHON_MIN_VERSION)'.split('.'))) else 0)" \ && echo "Need Python >= $(PYTHON_MIN_VERSION)" && exit 1 || : else - @echo "Use Python $(PYTHON_MIN_VERSION) over uv" + @echo "Using global uv for Python $(UV_PYTHON)" endif + # Validation: Check VENV_FOLDER is set if venv enabled @[[ "$(VENV_ENABLED)" == "true" && "$(VENV_FOLDER)" == "" ]] \ && echo "VENV_FOLDER must be configured if VENV_ENABLED is true" && exit 1 || : - @[[ "$(VENV_ENABLED)$(PYTHON_PACKAGE_INSTALLER)" == "falseuv" ]] \ + # Validation: Check uv not used with system Python + @[[ "$(VENV_ENABLED)" == "false" && "$(PYTHON_PACKAGE_INSTALLER)" == "uv" ]] \ && echo "Package installer uv does not work with a global Python interpreter." && exit 1 || : + # Warning: Notify if global UV is outdated +ifeq ("$(UV_OUTDATED)","true") + @echo "WARNING: A newer version of uv is available. Run 'uv self update' to upgrade." +endif + + # Create virtual environment ifeq ("$(VENV_ENABLED)", "true") ifeq ("$(VENV_CREATE)", "true") -ifeq ("$(PYTHON_PACKAGE_INSTALLER)$(MXENV_UV_GLOBAL)","uvtrue") - @echo "Setup Python Virtual Environment using package 'uv' at '$(VENV_FOLDER)'" - @uv venv -p $(PRIMARY_PYTHON) --seed $(VENV_FOLDER) +ifeq ("$(USE_GLOBAL_UV)","true") + @echo "Setup Python Virtual Environment using global uv at '$(VENV_FOLDER)'" + @uv venv --quiet --no-progress -p $(UV_PYTHON) --seed $(VENV_FOLDER) else @echo "Setup Python Virtual Environment using module 'venv' at '$(VENV_FOLDER)'" @$(PRIMARY_PYTHON) -m venv $(VENV_FOLDER) @@ -172,10 +196,14 @@ endif else @echo "Using system Python interpreter" endif -ifeq ("$(PYTHON_PACKAGE_INSTALLER)$(MXENV_UV_GLOBAL)","uvfalse") - @echo "Install uv" + + # Install uv locally if needed +ifeq ("$(USE_LOCAL_UV)","true") + @echo "Install uv in virtual environment" @$(MXENV_PYTHON) -m pip install uv endif + + # Install/upgrade core packages @$(PYTHON_PACKAGE_COMMAND) install -U pip setuptools wheel @echo "Install/Update MXStack Python packages" @$(PYTHON_PACKAGE_COMMAND) install -U $(MXDEV) $(MXMAKE) From 2915c8695f97e2c391317bf3fe2d5428ade98511 Mon Sep 17 00:00:00 2001 From: "Jens W. Klein" Date: Wed, 22 Oct 2025 08:59:59 +0200 Subject: [PATCH 04/14] Fix tests: replace MXENV_UV_GLOBAL with UV_PYTHON - Update test_parser.py to use UV_PYTHON instead of MXENV_UV_GLOBAL - Update test_templates.py to use UV_PYTHON instead of MXENV_UV_GLOBAL - Both tests now pass with new mxenv.mk implementation --- src/mxmake/tests/test_parser.py | 4 ++-- src/mxmake/tests/test_templates.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/mxmake/tests/test_parser.py b/src/mxmake/tests/test_parser.py index 50b7d6d..67ef3fc 100644 --- a/src/mxmake/tests/test_parser.py +++ b/src/mxmake/tests/test_parser.py @@ -21,7 +21,7 @@ def test_MakefileParser(self, tempdir): "core.mxenv.PRIMARY_PYTHON": "python3", "core.mxenv.PYTHON_MIN_VERSION": "3.7", "core.mxenv.PYTHON_PACKAGE_INSTALLER": "pip", - "core.mxenv.MXENV_UV_GLOBAL": "false", + "core.mxenv.UV_PYTHON": "", "core.mxenv.VENV_ENABLED": "true", "core.mxenv.VENV_CREATE": "true", "core.mxenv.VENV_FOLDER": "venv", @@ -67,7 +67,7 @@ def test_MakefileParser(self, tempdir): "core.mxenv.PRIMARY_PYTHON": "python3", "core.mxenv.PYTHON_MIN_VERSION": "3.7", "core.mxenv.PYTHON_PACKAGE_INSTALLER": "pip", - "core.mxenv.MXENV_UV_GLOBAL": "false", + "core.mxenv.UV_PYTHON": "", "core.mxenv.VENV_ENABLED": "true", "core.mxenv.VENV_CREATE": "true", "core.mxenv.VENV_FOLDER": "venv", diff --git a/src/mxmake/tests/test_templates.py b/src/mxmake/tests/test_templates.py index f3f2238..c637021 100644 --- a/src/mxmake/tests/test_templates.py +++ b/src/mxmake/tests/test_templates.py @@ -564,7 +564,7 @@ def test_Makefile(self, tempdir): "core.mxenv.PRIMARY_PYTHON": "python3", "core.mxenv.PYTHON_MIN_VERSION": "3.9", "core.mxenv.PYTHON_PACKAGE_INSTALLER": "pip", - "core.mxenv.MXENV_UV_GLOBAL": "false", + "core.mxenv.UV_PYTHON": "", "core.mxenv.VENV_ENABLED": "true", "core.mxenv.VENV_CREATE": "true", "core.mxenv.VENV_FOLDER": ".venv", From bf8f82187d1bc201daa72d7bc4a309a799228008 Mon Sep 17 00:00:00 2001 From: "Jens W. Klein" Date: Wed, 22 Oct 2025 09:02:49 +0200 Subject: [PATCH 05/14] Regenerate expected Makefile test fixture - Generate clean expected/Makefile using templates to ensure proper formatting - All tests now pass (31 passed) --- src/mxmake/tests/expected/Makefile | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/mxmake/tests/expected/Makefile b/src/mxmake/tests/expected/Makefile index 97b7c1d..b8b187b 100644 --- a/src/mxmake/tests/expected/Makefile +++ b/src/mxmake/tests/expected/Makefile @@ -55,6 +55,13 @@ PYTHON_MIN_VERSION?=3.9 # Default: pip PYTHON_PACKAGE_INSTALLER?=pip +# Python version for UV to install/use when creating virtual +# environments with global UV. Passed to `uv venv -p VALUE`. Supports version +# specs like `3.11`, `3.14`, `cpython@3.14`. Defaults to PRIMARY_PYTHON value +# for backward compatibility. +# No default value. +UV_PYTHON?= + # Flag whether to use virtual environment. If `false`, the # interpreter according to `PRIMARY_PYTHON` found in `PATH` is used. # Default: true From f89d29203be7d883de60f7d14b57a7a88b501838 Mon Sep 17 00:00:00 2001 From: "Jens W. Klein" Date: Wed, 22 Oct 2025 09:03:40 +0200 Subject: [PATCH 06/14] Document mxenv refactoring in CHANGES.md Add comprehensive changelog entry for mxenv domain refactoring: - Breaking changes: MXENV_UV_GLOBAL removal - New features: UV_PYTHON, auto-detection, non-interactive flags, update checks - Code improvements: simplified logic and better maintainability Addresses #43 --- CHANGES.md | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index 38ffd6c..8ad89c3 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -7,6 +7,35 @@ - Feature: Add support for Python 3.14. - Feature: Add `docs-linkcheck` to check for broken links in the sphinx documentation. +### Breaking Changes - mxenv domain refactoring + +- **Removed**: `MXENV_UV_GLOBAL` setting in favor of automatic UV detection. + When `PYTHON_PACKAGE_INSTALLER=uv`, mxmake now automatically detects and uses + a globally installed `uv` if available. To force local installation of uv, + simply don't install it globally or remove it from PATH. + +### New Features - mxenv domain + +- **Added**: `UV_PYTHON` setting to specify Python version for UV-managed virtual + environments. Defaults to `PRIMARY_PYTHON` for backward compatibility. This + provides semantic clarity: `PRIMARY_PYTHON` is the system interpreter path + (e.g., `python3.11`), while `UV_PYTHON` is the version spec for UV + (e.g., `3.14`, `cpython@3.14`). + +- **Added**: Automatic detection of global UV installation using simple shell check. + No manual configuration required. + +- **Added**: All UV commands now run with `--quiet --no-progress` flags for better + CI/CD compatibility and cleaner log output. + +- **Added**: When using global UV, mxmake checks if updates are available using + `uv self update --dry-run` and displays a helpful non-blocking warning if a + newer version is available. + +- **Improved**: Simplified mxenv.mk logic from 3+ nesting levels to 1-2 levels + using computed intermediate variables (`USE_GLOBAL_UV`, `USE_LOCAL_UV`). + Code is now more maintainable and easier to extend. + ## 1.3.0 (2025-09-03) - Introduce testargs for pytest to have more control over the test and pass it args. From a5a5e4a4a55d21852808dc01973c21d4380dc16b Mon Sep 17 00:00:00 2001 From: "Jens W. Klein" Date: Wed, 22 Oct 2025 09:06:16 +0200 Subject: [PATCH 07/14] Unify CHANGES.md style for 1.3.1 section Convert mxenv refactoring entries to match existing changelog style: - Use simple bullets with prefixes (Fix, Feature, Breaking, Improvement) - Remove subsections for consistent formatting --- CHANGES.md | 22 ++++++---------------- 1 file changed, 6 insertions(+), 16 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 8ad89c3..f7398ad 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -6,33 +6,23 @@ - Fix: interactive uv venv, use --allow-existing instead. - Feature: Add support for Python 3.14. - Feature: Add `docs-linkcheck` to check for broken links in the sphinx documentation. - -### Breaking Changes - mxenv domain refactoring - -- **Removed**: `MXENV_UV_GLOBAL` setting in favor of automatic UV detection. +- Breaking: Removed `MXENV_UV_GLOBAL` setting in favor of automatic UV detection. When `PYTHON_PACKAGE_INSTALLER=uv`, mxmake now automatically detects and uses a globally installed `uv` if available. To force local installation of uv, simply don't install it globally or remove it from PATH. - -### New Features - mxenv domain - -- **Added**: `UV_PYTHON` setting to specify Python version for UV-managed virtual +- Feature: Add `UV_PYTHON` setting to specify Python version for UV-managed virtual environments. Defaults to `PRIMARY_PYTHON` for backward compatibility. This provides semantic clarity: `PRIMARY_PYTHON` is the system interpreter path (e.g., `python3.11`), while `UV_PYTHON` is the version spec for UV (e.g., `3.14`, `cpython@3.14`). - -- **Added**: Automatic detection of global UV installation using simple shell check. +- Feature: Automatic detection of global UV installation using simple shell check. No manual configuration required. - -- **Added**: All UV commands now run with `--quiet --no-progress` flags for better +- Feature: All UV commands now run with `--quiet --no-progress` flags for better CI/CD compatibility and cleaner log output. - -- **Added**: When using global UV, mxmake checks if updates are available using +- Feature: When using global UV, mxmake checks if updates are available using `uv self update --dry-run` and displays a helpful non-blocking warning if a newer version is available. - -- **Improved**: Simplified mxenv.mk logic from 3+ nesting levels to 1-2 levels +- Improvement: Simplified mxenv.mk logic from 3+ nesting levels to 1-2 levels using computed intermediate variables (`USE_GLOBAL_UV`, `USE_LOCAL_UV`). Code is now more maintainable and easier to extend. From dc35287a06d0e5750740242a2123aac48753bc55 Mon Sep 17 00:00:00 2001 From: "Jens W. Klein" Date: Wed, 22 Oct 2025 09:15:43 +0200 Subject: [PATCH 08/14] Update variants workflow for auto-detection - Remove MXENV_UV_GLOBAL references (now auto-detected) - Update test names to reflect auto-detection behavior - Add new test for UV_PYTHON different from PRIMARY_PYTHON - All 8 scenarios now test auto-detection properly --- .github/workflows/variants.yml | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/.github/workflows/variants.yml b/.github/workflows/variants.yml index 47b10db..c19c653 100644 --- a/.github/workflows/variants.yml +++ b/.github/workflows/variants.yml @@ -30,14 +30,14 @@ jobs: run: | make VENV_ENABLED=true VENV_CREATE=true PRIMARY_PYTHON=python3 PYTHON_PACKAGE_INSTALLER=pip test make clean - - name: VENV to be created with uv to be installed + - name: VENV to be created with uv (local install, no global UV) run: | - make VENV_ENABLED=true VENV_CREATE=true PYTHON_PACKAGE_INSTALLER=uv PRIMARY_PYTHON=python3 MXENV_UV_GLOBAL=false test + make VENV_ENABLED=true VENV_CREATE=true PYTHON_PACKAGE_INSTALLER=uv PRIMARY_PYTHON=python3 test make clean - - name: VENV to be created with uv globally pre-installed + - name: VENV to be created with uv (global UV auto-detected) run: | pip install uv - make VENV_ENABLED=true VENV_CREATE=true PYTHON_PACKAGE_INSTALLER=uv PRIMARY_PYTHON=${{ matrix.python-version }} MXENV_UV_GLOBAL=true test + make VENV_ENABLED=true VENV_CREATE=true PYTHON_PACKAGE_INSTALLER=uv PRIMARY_PYTHON=${{ matrix.python-version }} test make clean pip uninstall -y uv - name: VENV pre-installed with pip @@ -46,20 +46,26 @@ jobs: make VENV_ENABLED=true VENV_CREATE=false VENV_FOLDER=existingvenv PRIMARY_PYTHON=python3 PYTHON_PACKAGE_INSTALLER=pip test make clean rm -r existingvenv - - name: VENV pre-installed with uv to be installed + - name: VENV pre-installed with uv (local install, no global UV) run: | python -m venv existingvenv - make VENV_ENABLED=true VENV_CREATE=false VENV_FOLDER=existingvenv PRIMARY_PYTHON=python3 PYTHON_PACKAGE_INSTALLER=uv MXENV_UV_GLOBAL=false test + make VENV_ENABLED=true VENV_CREATE=false VENV_FOLDER=existingvenv PRIMARY_PYTHON=python3 PYTHON_PACKAGE_INSTALLER=uv test make clean rm -r existingvenv - - name: VENV pre-installed with uv globally pre-installed + - name: VENV pre-installed with uv (global UV auto-detected) run: | python -m venv existingvenv pip install uv - make VENV_ENABLED=true VENV_CREATE=false VENV_FOLDER=existingvenv PYTHON_PACKAGE_INSTALLER=uv PRIMARY_PYTHON=python3 MXENV_UV_GLOBAL=true test + make VENV_ENABLED=true VENV_CREATE=false VENV_FOLDER=existingvenv PYTHON_PACKAGE_INSTALLER=uv PRIMARY_PYTHON=python3 test make clean pip uninstall -y uv rm -r existingvenv + - name: VENV with global UV using different UV_PYTHON + run: | + pip install uv + make VENV_ENABLED=true VENV_CREATE=true PYTHON_PACKAGE_INSTALLER=uv PRIMARY_PYTHON=python3 UV_PYTHON=${{ matrix.python-version }} test + make clean + pip uninstall -y uv - name: Global Python with pip run: | make VENV_ENABLED=false VENV_CREATE=false PRIMARY_PYTHON=python3 PYTHON_PACKAGE_INSTALLER=pip test From 7e12bd865bd3261d55b21da89c29fabf97096354 Mon Sep 17 00:00:00 2001 From: "Jens W. Klein" Date: Wed, 22 Oct 2025 09:22:23 +0200 Subject: [PATCH 09/14] Regenerate project Makefile with refactored mxenv.mk The project's own Makefile needs to use the new mxenv.mk logic for CI tests to pass. Regenerated using 'mxmake update'. --- Makefile | 73 +++++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 54 insertions(+), 19 deletions(-) diff --git a/Makefile b/Makefile index 3c6c9f9..81c449f 100644 --- a/Makefile +++ b/Makefile @@ -57,18 +57,19 @@ PRIMARY_PYTHON?=3.14 PYTHON_MIN_VERSION?=3.9 # Install packages using the given package installer method. -# Supported are `pip` and `uv`. If uv is used, its global availability is -# checked. Otherwise, it is installed, either in the virtual environment or -# using the `PRIMARY_PYTHON`, dependent on the `VENV_ENABLED` setting. If -# `VENV_ENABLED` and uv is selected, uv is used to create the virtual -# environment. +# Supported are `pip` and `uv`. When `uv` is selected, a global installation +# is auto-detected and used if available. Otherwise, uv is installed in the +# virtual environment or using `PRIMARY_PYTHON`, depending on the +# `VENV_ENABLED` setting. # Default: pip PYTHON_PACKAGE_INSTALLER?=uv -# Flag whether to use a global installed 'uv' or install -# it in the virtual environment. -# Default: false -MXENV_UV_GLOBAL?=true +# Python version for UV to install/use when creating virtual +# environments with global UV. Passed to `uv venv -p VALUE`. Supports version +# specs like `3.11`, `3.14`, `cpython@3.14`. Defaults to PRIMARY_PYTHON value +# for backward compatibility. +# No default value. +UV_PYTHON?= # Flag whether to use virtual environment. If `false`, the # interpreter according to `PRIMARY_PYTHON` found in `PATH` is used. @@ -233,30 +234,60 @@ else MXENV_PYTHON=$(PRIMARY_PYTHON) endif -# Determine the package installer +# Determine the package installer with non-interactive flags ifeq ("$(PYTHON_PACKAGE_INSTALLER)","uv") -PYTHON_PACKAGE_COMMAND=uv pip +PYTHON_PACKAGE_COMMAND=uv pip --quiet --no-progress else PYTHON_PACKAGE_COMMAND=$(MXENV_PYTHON) -m pip endif +# Auto-detect global uv availability (simple existence check) +ifeq ("$(PYTHON_PACKAGE_INSTALLER)","uv") +UV_AVAILABLE:=$(shell command -v uv >/dev/null 2>&1 && echo "true" || echo "false") +else +UV_AVAILABLE:=false +endif + +# Determine installation strategy +USE_GLOBAL_UV:=$(shell [[ "$(PYTHON_PACKAGE_INSTALLER)" == "uv" && "$(UV_AVAILABLE)" == "true" ]] && echo "true" || echo "false") +USE_LOCAL_UV:=$(shell [[ "$(PYTHON_PACKAGE_INSTALLER)" == "uv" && "$(UV_AVAILABLE)" == "false" ]] && echo "true" || echo "false") + +# UV Python version (defaults to PRIMARY_PYTHON for backward compatibility) +UV_PYTHON?=$(PRIMARY_PYTHON) + +# Check if global UV is outdated (non-blocking warning) +ifeq ("$(USE_GLOBAL_UV)","true") +UV_OUTDATED:=$(shell uv self update --dry-run 2>&1 | grep -q "Would update" && echo "true" || echo "false") +else +UV_OUTDATED:=false +endif + MXENV_TARGET:=$(SENTINEL_FOLDER)/mxenv.sentinel $(MXENV_TARGET): $(SENTINEL) -ifneq ("$(PYTHON_PACKAGE_INSTALLER)$(MXENV_UV_GLOBAL)","uvfalse") + # Validation: Check Python version if not using global uv +ifneq ("$(USE_GLOBAL_UV)","true") @$(PRIMARY_PYTHON) -c "import sys; vi = sys.version_info; sys.exit(1 if (int(vi[0]), int(vi[1])) >= tuple(map(int, '$(PYTHON_MIN_VERSION)'.split('.'))) else 0)" \ && echo "Need Python >= $(PYTHON_MIN_VERSION)" && exit 1 || : else - @echo "Use Python $(PYTHON_MIN_VERSION) over uv" + @echo "Using global uv for Python $(UV_PYTHON)" endif + # Validation: Check VENV_FOLDER is set if venv enabled @[[ "$(VENV_ENABLED)" == "true" && "$(VENV_FOLDER)" == "" ]] \ && echo "VENV_FOLDER must be configured if VENV_ENABLED is true" && exit 1 || : - @[[ "$(VENV_ENABLED)$(PYTHON_PACKAGE_INSTALLER)" == "falseuv" ]] \ + # Validation: Check uv not used with system Python + @[[ "$(VENV_ENABLED)" == "false" && "$(PYTHON_PACKAGE_INSTALLER)" == "uv" ]] \ && echo "Package installer uv does not work with a global Python interpreter." && exit 1 || : + # Warning: Notify if global UV is outdated +ifeq ("$(UV_OUTDATED)","true") + @echo "WARNING: A newer version of uv is available. Run 'uv self update' to upgrade." +endif + + # Create virtual environment ifeq ("$(VENV_ENABLED)", "true") ifeq ("$(VENV_CREATE)", "true") -ifeq ("$(PYTHON_PACKAGE_INSTALLER)$(MXENV_UV_GLOBAL)","uvtrue") - @echo "Setup Python Virtual Environment using package 'uv' at '$(VENV_FOLDER)'" - @uv venv -p $(PRIMARY_PYTHON) --seed $(VENV_FOLDER) +ifeq ("$(USE_GLOBAL_UV)","true") + @echo "Setup Python Virtual Environment using global uv at '$(VENV_FOLDER)'" + @uv venv --quiet --no-progress -p $(UV_PYTHON) --seed $(VENV_FOLDER) else @echo "Setup Python Virtual Environment using module 'venv' at '$(VENV_FOLDER)'" @$(PRIMARY_PYTHON) -m venv $(VENV_FOLDER) @@ -266,10 +297,14 @@ endif else @echo "Using system Python interpreter" endif -ifeq ("$(PYTHON_PACKAGE_INSTALLER)$(MXENV_UV_GLOBAL)","uvfalse") - @echo "Install uv" + + # Install uv locally if needed +ifeq ("$(USE_LOCAL_UV)","true") + @echo "Install uv in virtual environment" @$(MXENV_PYTHON) -m pip install uv endif + + # Install/upgrade core packages @$(PYTHON_PACKAGE_COMMAND) install -U pip setuptools wheel @echo "Install/Update MXStack Python packages" @$(PYTHON_PACKAGE_COMMAND) install -U $(MXDEV) $(MXMAKE) From a746817a16438514c4d105221afe637201d0e790 Mon Sep 17 00:00:00 2001 From: "Jens W. Klein" Date: Wed, 22 Oct 2025 09:32:32 +0200 Subject: [PATCH 10/14] Fix UV_PYTHON default value to prevent empty parameter error The UV_PYTHON setting had an empty default in metadata, causing uv venv to fail with "a value is required for '--python '". Fixed by setting UV_PYTHON default to $(PRIMARY_PYTHON) in mxenv.mk metadata and updating test expectations. --- Makefile | 7 ++----- src/mxmake/tests/expected/Makefile | 7 ++----- src/mxmake/tests/test_parser.py | 4 ++-- src/mxmake/tests/test_templates.py | 2 +- src/mxmake/topics/core/mxenv.mk | 5 +---- 5 files changed, 8 insertions(+), 17 deletions(-) diff --git a/Makefile b/Makefile index 81c449f..38cb2cc 100644 --- a/Makefile +++ b/Makefile @@ -68,8 +68,8 @@ PYTHON_PACKAGE_INSTALLER?=uv # environments with global UV. Passed to `uv venv -p VALUE`. Supports version # specs like `3.11`, `3.14`, `cpython@3.14`. Defaults to PRIMARY_PYTHON value # for backward compatibility. -# No default value. -UV_PYTHON?= +# Default: $(PRIMARY_PYTHON) +UV_PYTHON?=$(PRIMARY_PYTHON) # Flag whether to use virtual environment. If `false`, the # interpreter according to `PRIMARY_PYTHON` found in `PATH` is used. @@ -252,9 +252,6 @@ endif USE_GLOBAL_UV:=$(shell [[ "$(PYTHON_PACKAGE_INSTALLER)" == "uv" && "$(UV_AVAILABLE)" == "true" ]] && echo "true" || echo "false") USE_LOCAL_UV:=$(shell [[ "$(PYTHON_PACKAGE_INSTALLER)" == "uv" && "$(UV_AVAILABLE)" == "false" ]] && echo "true" || echo "false") -# UV Python version (defaults to PRIMARY_PYTHON for backward compatibility) -UV_PYTHON?=$(PRIMARY_PYTHON) - # Check if global UV is outdated (non-blocking warning) ifeq ("$(USE_GLOBAL_UV)","true") UV_OUTDATED:=$(shell uv self update --dry-run 2>&1 | grep -q "Would update" && echo "true" || echo "false") diff --git a/src/mxmake/tests/expected/Makefile b/src/mxmake/tests/expected/Makefile index b8b187b..a31f1da 100644 --- a/src/mxmake/tests/expected/Makefile +++ b/src/mxmake/tests/expected/Makefile @@ -59,8 +59,8 @@ PYTHON_PACKAGE_INSTALLER?=pip # environments with global UV. Passed to `uv venv -p VALUE`. Supports version # specs like `3.11`, `3.14`, `cpython@3.14`. Defaults to PRIMARY_PYTHON value # for backward compatibility. -# No default value. -UV_PYTHON?= +# Default: $(PRIMARY_PYTHON) +UV_PYTHON?=$(PRIMARY_PYTHON) # Flag whether to use virtual environment. If `false`, the # interpreter according to `PRIMARY_PYTHON` found in `PATH` is used. @@ -158,9 +158,6 @@ endif USE_GLOBAL_UV:=$(shell [[ "$(PYTHON_PACKAGE_INSTALLER)" == "uv" && "$(UV_AVAILABLE)" == "true" ]] && echo "true" || echo "false") USE_LOCAL_UV:=$(shell [[ "$(PYTHON_PACKAGE_INSTALLER)" == "uv" && "$(UV_AVAILABLE)" == "false" ]] && echo "true" || echo "false") -# UV Python version (defaults to PRIMARY_PYTHON for backward compatibility) -UV_PYTHON?=$(PRIMARY_PYTHON) - # Check if global UV is outdated (non-blocking warning) ifeq ("$(USE_GLOBAL_UV)","true") UV_OUTDATED:=$(shell uv self update --dry-run 2>&1 | grep -q "Would update" && echo "true" || echo "false") diff --git a/src/mxmake/tests/test_parser.py b/src/mxmake/tests/test_parser.py index 67ef3fc..98aa721 100644 --- a/src/mxmake/tests/test_parser.py +++ b/src/mxmake/tests/test_parser.py @@ -21,7 +21,7 @@ def test_MakefileParser(self, tempdir): "core.mxenv.PRIMARY_PYTHON": "python3", "core.mxenv.PYTHON_MIN_VERSION": "3.7", "core.mxenv.PYTHON_PACKAGE_INSTALLER": "pip", - "core.mxenv.UV_PYTHON": "", + "core.mxenv.UV_PYTHON": "$(PRIMARY_PYTHON)", "core.mxenv.VENV_ENABLED": "true", "core.mxenv.VENV_CREATE": "true", "core.mxenv.VENV_FOLDER": "venv", @@ -67,7 +67,7 @@ def test_MakefileParser(self, tempdir): "core.mxenv.PRIMARY_PYTHON": "python3", "core.mxenv.PYTHON_MIN_VERSION": "3.7", "core.mxenv.PYTHON_PACKAGE_INSTALLER": "pip", - "core.mxenv.UV_PYTHON": "", + "core.mxenv.UV_PYTHON": "$(PRIMARY_PYTHON)", "core.mxenv.VENV_ENABLED": "true", "core.mxenv.VENV_CREATE": "true", "core.mxenv.VENV_FOLDER": "venv", diff --git a/src/mxmake/tests/test_templates.py b/src/mxmake/tests/test_templates.py index c637021..1cfa9af 100644 --- a/src/mxmake/tests/test_templates.py +++ b/src/mxmake/tests/test_templates.py @@ -564,7 +564,7 @@ def test_Makefile(self, tempdir): "core.mxenv.PRIMARY_PYTHON": "python3", "core.mxenv.PYTHON_MIN_VERSION": "3.9", "core.mxenv.PYTHON_PACKAGE_INSTALLER": "pip", - "core.mxenv.UV_PYTHON": "", + "core.mxenv.UV_PYTHON": "$(PRIMARY_PYTHON)", "core.mxenv.VENV_ENABLED": "true", "core.mxenv.VENV_CREATE": "true", "core.mxenv.VENV_FOLDER": ".venv", diff --git a/src/mxmake/topics/core/mxenv.mk b/src/mxmake/topics/core/mxenv.mk index b944800..cb84cbd 100644 --- a/src/mxmake/topics/core/mxenv.mk +++ b/src/mxmake/topics/core/mxenv.mk @@ -46,7 +46,7 @@ #: environments with global UV. Passed to `uv venv -p VALUE`. Supports version #: specs like `3.11`, `3.14`, `cpython@3.14`. Defaults to PRIMARY_PYTHON value #: for backward compatibility. -#:default = +#:default = $(PRIMARY_PYTHON) #: #:[setting.VENV_ENABLED] #:description = Flag whether to use virtual environment. If `false`, the @@ -113,9 +113,6 @@ endif USE_GLOBAL_UV:=$(shell [[ "$(PYTHON_PACKAGE_INSTALLER)" == "uv" && "$(UV_AVAILABLE)" == "true" ]] && echo "true" || echo "false") USE_LOCAL_UV:=$(shell [[ "$(PYTHON_PACKAGE_INSTALLER)" == "uv" && "$(UV_AVAILABLE)" == "false" ]] && echo "true" || echo "false") -# UV Python version (defaults to PRIMARY_PYTHON for backward compatibility) -UV_PYTHON?=$(PRIMARY_PYTHON) - # Check if global UV is outdated (non-blocking warning) ifeq ("$(USE_GLOBAL_UV)","true") UV_OUTDATED:=$(shell uv self update --dry-run 2>&1 | grep -q "Would update" && echo "true" || echo "false") From 245972b2becd0560103ec45e1b68d298d3587466 Mon Sep 17 00:00:00 2001 From: "Jens W. Klein" Date: Wed, 22 Oct 2025 09:41:03 +0200 Subject: [PATCH 11/14] Remove refactoring plan document The plan document was useful during development but is not needed in the final PR. The implementation details are documented in the changelog and commit history. --- REFACTOR_MXENV_PLAN.md | 379 -------------------------------------- REFACTOR_MXENV_SUMMARY.md | 72 -------- 2 files changed, 451 deletions(-) delete mode 100644 REFACTOR_MXENV_PLAN.md delete mode 100644 REFACTOR_MXENV_SUMMARY.md diff --git a/REFACTOR_MXENV_PLAN.md b/REFACTOR_MXENV_PLAN.md deleted file mode 100644 index 7740e52..0000000 --- a/REFACTOR_MXENV_PLAN.md +++ /dev/null @@ -1,379 +0,0 @@ -# Refactor mxenv.mk: Auto-Detect UV and Simplify Logic - -**Issue**: https://github.com/mxstack/mxmake/issues/43 -**Current File**: [src/mxmake/topics/core/mxenv.mk](src/mxmake/topics/core/mxenv.mk) - -## Problem Statement - -The current `mxenv.mk` implementation has become overly complex: - -1. **Deeply nested conditionals** (3+ levels in places) -2. **Hard-to-read string concatenation checks** like `"$(PYTHON_PACKAGE_INSTALLER)$(MXENV_UV_GLOBAL)","uvtrue"` -3. **Scattered logic** mixing validation, venv creation, and package installation -4. **Manual `MXENV_UV_GLOBAL` setting** instead of auto-detection - -This makes it difficult to understand, maintain, and extend with new features like UV auto-detection. - -## Goals - -1. **Auto-detect** global `uv` availability instead of requiring manual `MXENV_UV_GLOBAL` setting -2. **Reduce complexity** and nesting of conditionals -3. **Maintain all existing functionality** without breaking changes to behavior -4. **Make future changes easier** by establishing clearer patterns - -## Proposed Changes - -### 1. Replace `MXENV_UV_GLOBAL` Setting with Auto-Detection - -**Location**: Lines 45-48 (domain metadata) - -**Remove**: -```makefile -#:[setting.MXENV_UV_GLOBAL] -#:description = Flag whether to use a global installed 'uv' or install -#: it in the virtual environment. -#:default = false -``` - -**Add**: -```makefile -#:[setting.UV_PYTHON] -#:description = Python version for UV to install/use when creating virtual -#: environments with global UV. Passed to `uv venv -p VALUE`. Supports version -#: specs like `3.11`, `3.14`, `cpython@3.14`. Defaults to PRIMARY_PYTHON value -#: for backward compatibility. -#:default = -``` - -**Rationale for UV_PYTHON**: -- Provides semantic clarity: `PRIMARY_PYTHON` = system interpreter path, `UV_PYTHON` = UV version spec -- Backward compatible: defaults to `PRIMARY_PYTHON` if not set -- Supports UV's Python management features properly - -**Update** `PYTHON_PACKAGE_INSTALLER` description to mention auto-detection: -```makefile -#:[setting.PYTHON_PACKAGE_INSTALLER] -#:description = Install packages using the given package installer method. -#: Supported are `pip` and `uv`. When `uv` is selected, a global installation -#: is auto-detected and used if available and meets the minimum version -#: requirement. Otherwise, uv is installed in the virtual environment or -#: using `PRIMARY_PYTHON`, depending on the `VENV_ENABLED` setting. -#:default = pip -``` - -### 2. Add UV Detection and Configuration Logic - -**Location**: After line 102 (after PYTHON_PACKAGE_COMMAND determination) - -**Add new section**: -```makefile -# Determine the package installer with non-interactive flags -ifeq ("$(PYTHON_PACKAGE_INSTALLER)","uv") -PYTHON_PACKAGE_COMMAND=uv pip --quiet --no-progress -else -PYTHON_PACKAGE_COMMAND=$(MXENV_PYTHON) -m pip -endif - -# Auto-detect global uv availability (simple existence check) -ifeq ("$(PYTHON_PACKAGE_INSTALLER)","uv") -UV_AVAILABLE:=$(shell command -v uv >/dev/null 2>&1 && echo "true" || echo "false") -else -UV_AVAILABLE:=false -endif - -# Determine installation strategy -USE_GLOBAL_UV:=$(shell [[ "$(PYTHON_PACKAGE_INSTALLER)" == "uv" && "$(UV_AVAILABLE)" == "true" ]] && echo "true" || echo "false") -USE_LOCAL_UV:=$(shell [[ "$(PYTHON_PACKAGE_INSTALLER)" == "uv" && "$(UV_AVAILABLE)" == "false" ]] && echo "true" || echo "false") - -# UV Python version (defaults to PRIMARY_PYTHON for backward compatibility) -UV_PYTHON?=$(PRIMARY_PYTHON) - -# Check if global UV is outdated (non-blocking warning) -ifeq ("$(USE_GLOBAL_UV)","true") -UV_OUTDATED:=$(shell uv self update --dry-run 2>&1 | grep -q "Would update" && echo "true" || echo "false") -else -UV_OUTDATED:=false -endif -``` - -**Rationale**: -- **Pure shell detection**: No Python dependency during Python setup -- **Non-interactive UV**: `--quiet --no-progress` flags ensure CI/CD compatibility -- **Semantic clarity**: `UV_PYTHON` separates system Python path from UV version spec -- **Simple and reliable**: Just checks if `uv` command exists in PATH -- **Backward compatible**: `UV_PYTHON` defaults to `PRIMARY_PYTHON` -- **Update awareness**: Warns if global UV is outdated using `uv self update --dry-run` - -**Note on UV updates**: Instead of checking minimum versions, we use `uv self update --dry-run` -to detect if updates are available. This leverages UV's built-in update mechanism and provides -helpful warnings without blocking execution. - -### 3. Simplify Main Target Logic - -**Location**: Lines 104-137 ($(MXENV_TARGET) recipe) - -**Current structure** (complex): -- Multiple nested `ifeq` statements -- Validation mixed with execution -- Hard to follow control flow - -**New structure** (simplified): - -```makefile -MXENV_TARGET:=$(SENTINEL_FOLDER)/mxenv.sentinel -$(MXENV_TARGET): $(SENTINEL) - # Validation: Check Python version if not using global uv -ifneq ("$(USE_GLOBAL_UV)","true") - @$(PRIMARY_PYTHON) -c "import sys; vi = sys.version_info; sys.exit(1 if (int(vi[0]), int(vi[1])) >= tuple(map(int, '$(PYTHON_MIN_VERSION)'.split('.'))) else 0)" \ - && echo "Need Python >= $(PYTHON_MIN_VERSION)" && exit 1 || : -else - @echo "Using global uv for Python $(UV_PYTHON)" -endif - # Validation: Check VENV_FOLDER is set if venv enabled - @[[ "$(VENV_ENABLED)" == "true" && "$(VENV_FOLDER)" == "" ]] \ - && echo "VENV_FOLDER must be configured if VENV_ENABLED is true" && exit 1 || : - # Validation: Check uv not used with system Python - @[[ "$(VENV_ENABLED)" == "false" && "$(PYTHON_PACKAGE_INSTALLER)" == "uv" ]] \ - && echo "Package installer uv does not work with a global Python interpreter." && exit 1 || : - # Warning: Notify if global UV is outdated -ifeq ("$(UV_OUTDATED)","true") - @echo "WARNING: A newer version of uv is available. Run 'uv self update' to upgrade." -endif - - # Create virtual environment -ifeq ("$(VENV_ENABLED)", "true") -ifeq ("$(VENV_CREATE)", "true") -ifeq ("$(USE_GLOBAL_UV)","true") - @echo "Setup Python Virtual Environment using global uv at '$(VENV_FOLDER)'" - @uv venv --quiet --no-progress -p $(UV_PYTHON) --seed $(VENV_FOLDER) -else - @echo "Setup Python Virtual Environment using module 'venv' at '$(VENV_FOLDER)'" - @$(PRIMARY_PYTHON) -m venv $(VENV_FOLDER) - @$(MXENV_PYTHON) -m ensurepip -U -endif -endif -else - @echo "Using system Python interpreter" -endif - - # Install uv locally if needed -ifeq ("$(USE_LOCAL_UV)","true") - @echo "Install uv in virtual environment" - @$(MXENV_PYTHON) -m pip install uv -endif - - # Install/upgrade core packages - @$(PYTHON_PACKAGE_COMMAND) install -U pip setuptools wheel - @echo "Install/Update MXStack Python packages" - @$(PYTHON_PACKAGE_COMMAND) install -U $(MXDEV) $(MXMAKE) - @touch $(MXENV_TARGET) -``` - -**Key improvements**: -- **All validation at the top** (easier to see preconditions) -- **Single-level conditionals** using computed variables (`USE_GLOBAL_UV`, `USE_LOCAL_UV`) -- **Clear sections**: validation → warnings → venv creation → uv installation → package installation -- **Semantic clarity**: `PRIMARY_PYTHON` (system) vs `UV_PYTHON` (UV version spec) -- **Non-interactive UV**: All UV commands use `--quiet --no-progress` for CI/CD compatibility -- **Update awareness**: Warns users if global UV is outdated (using `uv self update --dry-run`) -- **Removed complex string concatenation checks** like `"$(PYTHON_PACKAGE_INSTALLER)$(MXENV_UV_GLOBAL)","uvtrue"` - -### 4. Update Tests and Documentation - -**Files to update**: - -1. **[src/mxmake/tests/expected/Makefile](src/mxmake/tests/expected/Makefile)**: - - Remove `MXENV_UV_GLOBAL` setting (lines 58-61) - - Add `UV_PYTHON` setting if tests need to verify it - -2. **[src/mxmake/tests/test_parser.py](src/mxmake/tests/test_parser.py)**: - - Check if any tests reference `MXENV_UV_GLOBAL` and update them - - Add tests for `UV_PYTHON` if needed - -3. **[src/mxmake/tests/test_templates.py](src/mxmake/tests/test_templates.py)**: - - Check if any tests set `MXENV_UV_GLOBAL` and update them - - Add tests for non-interactive UV flags if needed - -4. **[CLAUDE.md](CLAUDE.md)**: - - Update if it mentions `MXENV_UV_GLOBAL` - -### 5. Handle Backwards Compatibility - -**Parser changes** ([src/mxmake/parser.py](src/mxmake/parser.py)): -- When parsing old Makefiles with `MXENV_UV_GLOBAL`, silently ignore it -- Don't include it in the settings to preserve - -**Migration note in CHANGES.md**: -```markdown -### Breaking Changes - -- **mxenv domain**: Removed `MXENV_UV_GLOBAL` setting in favor of automatic - UV detection. When `PYTHON_PACKAGE_INSTALLER=uv`, mxmake now automatically - detects and uses a globally installed `uv` if available. To force local - installation of uv, simply don't install it globally or remove it from PATH. - -### New Features - -- **mxenv domain**: Added `UV_PYTHON` setting to specify Python version for - UV-managed virtual environments. Defaults to `PRIMARY_PYTHON` for backward - compatibility. This provides semantic clarity: `PRIMARY_PYTHON` is the system - interpreter path (e.g., `python3.11`), while `UV_PYTHON` is the version spec - for UV (e.g., `3.14`, `cpython@3.14`). - -- **mxenv domain**: All UV commands now run with `--quiet --no-progress` flags - for better CI/CD compatibility and cleaner log output. - -- **mxenv domain**: When using global UV, mxmake now checks if updates are - available using `uv self update --dry-run` and displays a helpful warning - if a newer version is available. This is non-blocking and helps keep UV current. -``` - -## Implementation Order - -1. **Update mxenv.mk** with new logic -2. **Update test fixtures** (expected/Makefile) -3. **Run tests** to verify no breakage -4. **Update documentation** (CHANGES.md, CLAUDE.md if needed) -5. **Test manually** with both global and local uv scenarios -6. **Create PR** with clear migration notes - -## Testing Strategy - -Test the following scenarios: - -1. **Global uv available** (`PYTHON_PACKAGE_INSTALLER=uv`) - - Should use global uv for venv creation - - Should not install uv locally - -2. **No global uv** (`PYTHON_PACKAGE_INSTALLER=uv`) - - Should create venv with Python's venv module - - Should install uv in the virtual environment - -3. **Using pip** (`PYTHON_PACKAGE_INSTALLER=pip`) - - Should work exactly as before - - Should not check for uv - -4. **Force local uv** (`PYTHON_PACKAGE_INSTALLER=uv`) - - Remove global `uv` from PATH temporarily - - Should install uv locally in virtual environment - -5. **Old Makefile migration** - - Makefile with `MXENV_UV_GLOBAL=true` should work after `mxmake update` - - Setting should be removed from updated Makefile - -6. **UV_PYTHON with different value than PRIMARY_PYTHON** - - Set `PRIMARY_PYTHON=python3.11` and `UV_PYTHON=3.14` - - Should use system Python 3.11 for non-UV venv creation - - Should use UV to install Python 3.14 when global UV available - -7. **CI/Non-TTY environment** - - Run in non-interactive shell or CI environment - - UV commands should not output progress bars - - Logs should be clean without ANSI escape codes - -8. **UV update check** - - With outdated global UV: Should display warning message - - With current global UV: Should not display warning - - With local UV: Should not check for updates - -## Benefits - -### Simplification -- **Reduces nesting**: From 3+ levels to 1-2 levels maximum -- **Clearer flow**: Validation → Creation → Installation is explicit -- **Self-documenting**: Variables like `USE_GLOBAL_UV` explain intent - -### Auto-detection -- **No manual configuration**: Users don't need to know about `MXENV_UV_GLOBAL` -- **Smart defaults**: Uses global uv when available -- **Override capability**: Can force local uv by not installing globally -- **Update awareness**: Helpful warnings when global UV is outdated (non-blocking) - -### Semantic Clarity -- **UV_PYTHON setting**: Separates system Python path from UV version spec -- **Backward compatible**: Defaults to `PRIMARY_PYTHON` if not set -- **Explicit intent**: Makes it clear when using UV's Python management vs system Python - -### CI/CD Friendly -- **Non-interactive**: All UV commands use `--quiet --no-progress` -- **Clean logs**: No progress bars or unnecessary output -- **Reliable**: Works in any environment (TTY or non-TTY) - -### Maintainability -- **Easy to extend**: Adding new installers is straightforward -- **Easier debugging**: Clear sections make issues easier to locate -- **Better testing**: Computed variables are easier to test - -### Future-proof -- **Plugin architecture ready**: Clear pattern for adding installers -- **Version checking ready**: Framework exists for version requirements -- **Migration path**: Pattern for removing deprecated settings - -## Risks and Mitigations - -### Risk: Breaking existing workflows -**Mitigation**: -- Thorough testing with various configurations -- Clear migration documentation -- Parser silently handles old settings - -### Risk: UV detection false positives/negatives -**Mitigation**: -- Simple, reliable detection (just check if `uv` command exists) -- Override by controlling UV availability in PATH -- Clear error messages when UV is required but unavailable - -### Risk: Performance impact of shell commands and network checks -**Mitigation**: -- Detection runs once per make invocation -- Uses efficient shell builtins (`command -v`) -- Cached in Make variables -- UV update check is fast (just metadata check, no download) -- Acceptable trade-off: ~100-200ms for helpful update notifications - -**Note**: The `uv self update --dry-run` check makes a network request. In restricted -network environments or offline scenarios, this may add slight delay or fail silently. -This is acceptable as it's a non-blocking warning only. - -**Future enhancement**: Cache update check results: -```makefile -# Only check for updates once per day -UV_UPDATE_CHECK_FILE:=$(MXMAKE_FOLDER)/.uv-update-check -UV_OUTDATED:=$(shell \ - if [[ ! -f "$(UV_UPDATE_CHECK_FILE)" ]] || \ - [[ $$(find "$(UV_UPDATE_CHECK_FILE)" -mtime +1 2>/dev/null) ]]; then \ - uv self update --dry-run 2>&1 | grep -q "Would update" && echo "true" || echo "false"; \ - touch "$(UV_UPDATE_CHECK_FILE)"; \ - else \ - echo "false"; \ - fi) -``` - -## Decisions Made - -1. **Version checking**: ✅ Just check availability, not version - - Simple and reliable - - UV is stable enough that version checking is not critical - - Can be added later if needed - -2. **Non-interactive flags**: ✅ Always apply `--quiet --no-progress` - - Ensures CI/CD compatibility - - Clean log output - - No configuration needed - -3. **UV_PYTHON setting**: ✅ Add new setting with default to PRIMARY_PYTHON - - Provides semantic clarity - - Backward compatible - - Supports UV's Python management features - -4. **Deprecation period**: ✅ Silent removal of `MXENV_UV_GLOBAL` - - Parser ignores it if found - - Clear migration notes in CHANGES.md - - No warning needed - -5. **UV update check**: ✅ Warn if global UV is outdated - - Use `uv self update --dry-run` to detect available updates - - Non-blocking warning message - - Only checks when using global UV - - Helps users keep UV current without enforcing it - - Future: Can add caching to reduce network calls diff --git a/REFACTOR_MXENV_SUMMARY.md b/REFACTOR_MXENV_SUMMARY.md deleted file mode 100644 index 5978247..0000000 --- a/REFACTOR_MXENV_SUMMARY.md +++ /dev/null @@ -1,72 +0,0 @@ -# mxenv.mk Refactoring Summary - -## Overview - -Refactor `mxenv.mk` to auto-detect global `uv` and simplify the overly complex conditional logic that has accumulated over time. - -## Problem - -The current implementation has become difficult to maintain: -- Deeply nested conditionals (3+ levels) -- Complex string concatenation checks like `"$(PYTHON_PACKAGE_INSTALLER)$(MXENV_UV_GLOBAL)","uvtrue"` -- Manual `MXENV_UV_GLOBAL` setting requiring user configuration -- Mixed concerns (validation, creation, installation) - -## Solution - -**Auto-detect UV availability** instead of requiring manual configuration: -- Simple shell check: `command -v uv` -- No Python dependency during Python setup -- Only applies when `PYTHON_PACKAGE_INSTALLER=uv` - -**Simplify logic** using computed intermediate variables: -- `USE_GLOBAL_UV` - Use detected global UV -- `USE_LOCAL_UV` - Install UV locally -- Clear sections: validation → warnings → venv → installation - -**Improve semantics** with new `UV_PYTHON` setting: -- `PRIMARY_PYTHON` = system interpreter path (e.g., `python3.11`) -- `UV_PYTHON` = UV version spec (e.g., `3.14`, `cpython@3.14`) -- Backward compatible: `UV_PYTHON` defaults to `PRIMARY_PYTHON` - -**Additional improvements**: -- All UV commands use `--quiet --no-progress` for CI/CD compatibility -- Optional warning when global UV is outdated (via `uv self update --dry-run`) -- Non-blocking, helpful notifications - -## Breaking Changes - -- **Removed**: `MXENV_UV_GLOBAL` setting - - Replaced with automatic detection - - Parser will silently ignore if found in old Makefiles - -## New Features - -- **Added**: `UV_PYTHON` setting for UV version specification -- **Added**: Auto-detection of global UV -- **Added**: Non-interactive flags for all UV commands -- **Added**: Optional update check for global UV - -## Benefits - -1. **Simpler**: Reduces complexity from 3+ nesting levels to 1-2 -2. **Automatic**: No manual `MXENV_UV_GLOBAL` configuration needed -3. **Clearer**: Self-documenting variables and clear control flow -4. **Maintainable**: Easy to extend and debug -5. **CI-friendly**: Non-interactive by default -6. **User-helpful**: Warns about outdated UV installations - -## Migration - -Existing Makefiles continue to work: -- `MXENV_UV_GLOBAL` is silently ignored if present -- `UV_PYTHON` defaults to `PRIMARY_PYTHON` value -- Run `mxmake update` to regenerate with new settings - -## Implementation Status - -- [x] Refactoring plan completed: `REFACTOR_MXENV_PLAN.md` -- [ ] Update `mxenv.mk` implementation -- [ ] Update tests and fixtures -- [ ] Update documentation -- [ ] Manual testing with various scenarios From c101a29680430079fca95ad87a8871a6b595b68f Mon Sep 17 00:00:00 2001 From: "Jens W. Klein" Date: Wed, 22 Oct 2025 10:06:00 +0200 Subject: [PATCH 12/14] Update documentation to remove obsolete MXENV_UV_GLOBAL setting UV is now auto-detected, MXENV_UV_GLOBAL setting was removed. Updated preseed examples to reflect the new behavior. --- docs/source/preseeds.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/docs/source/preseeds.md b/docs/source/preseeds.md index 4236335..89765e5 100644 --- a/docs/source/preseeds.md +++ b/docs/source/preseeds.md @@ -58,7 +58,6 @@ topics: mxenv: PYTHON_MIN_VERSION: "3.10" PYTHON_PACKAGE_INSTALLER: uv - MXENV_UV_GLOBAL: true sources: qa: ruff @@ -105,7 +104,6 @@ topics: mxenv: PYTHON_MIN_VERSION: "3.10" PYTHON_PACKAGE_INSTALLER: uv - MXENV_UV_GLOBAL: true applications: zope: plone: From 7080f7821feb2f3b70d64f72d940dcf1bad3b9b1 Mon Sep 17 00:00:00 2001 From: "Jens W. Klein" Date: Wed, 22 Oct 2025 10:13:33 +0200 Subject: [PATCH 13/14] Document UV auto-detection and update to Python 3.14 - Removed obsolete MXENV_UV_GLOBAL from all preseed examples - Added UV Package Installer sections explaining auto-detection - Documented new UV_PYTHON setting with version spec examples - Added warnings that UV_PYTHON defaulting to PRIMARY_PYTHON is backward compatibility only and should be explicitly set - Updated all examples to use Python 3.14 (3.13 for Plone) - All preseed examples now explicitly set UV_PYTHON --- docs/source/getting-started.md | 17 +++++++++++++++++ docs/source/preseeds.md | 31 +++++++++++++++++++++++++++---- 2 files changed, 44 insertions(+), 4 deletions(-) diff --git a/docs/source/getting-started.md b/docs/source/getting-started.md index 3653204..af7cdf8 100644 --- a/docs/source/getting-started.md +++ b/docs/source/getting-started.md @@ -63,6 +63,23 @@ For details read the chapter [on topics and it's domains](topics-and-domains). Do not add custom settings to settings section. They will be lost on next `mxmake init` respective `mxmake update` run. +### Python Package Installer + +By default, mxmake uses `pip` as the package installer. You can switch to [UV](https://docs.astral.sh/uv/) by setting `PYTHON_PACKAGE_INSTALLER=uv` in the settings section. + +When using UV, mxmake automatically detects if UV is installed globally or installs it locally in the virtual environment. + +```{note} +When using UV, you should explicitly set `UV_PYTHON` to specify which Python version UV should use. While `UV_PYTHON` currently defaults to `PRIMARY_PYTHON` for backward compatibility, this default may change in future versions. Set `UV_PYTHON` explicitly to avoid surprises. +``` + +Example: +```makefile +PRIMARY_PYTHON?=python3 +PYTHON_PACKAGE_INSTALLER?=uv +UV_PYTHON?=3.14 +``` + ## How to use on the Windows operating system mxmake works excellent on Windows! diff --git a/docs/source/preseeds.md b/docs/source/preseeds.md index 89765e5..87c84d1 100644 --- a/docs/source/preseeds.md +++ b/docs/source/preseeds.md @@ -15,9 +15,10 @@ topics: core: # include domain mxenv mxenv: - # set PYTHON_MIN_VERSION and PYTHON_PACKAGE_INSTALLER - PYTHON_MIN_VERSION: 3.10 + # set PYTHON_MIN_VERSION, PYTHON_PACKAGE_INSTALLER and UV_PYTHON + PYTHON_MIN_VERSION: 3.14 PYTHON_PACKAGE_INSTALLER: uv + UV_PYTHON: "3.14" qa: # include domains from qa topic but do not override default settings ruff: @@ -36,6 +37,26 @@ Now initialize the project with the preseeds: $ mxmake init -p preseeds.yaml ``` +## UV Package Installer + +When `PYTHON_PACKAGE_INSTALLER` is set to `uv`, mxmake automatically detects whether UV is installed globally on your system. + +```{important} +When using UV, you should explicitly set `UV_PYTHON` to specify which Python version UV should use. While `UV_PYTHON` currently defaults to `PRIMARY_PYTHON` for backward compatibility, relying on this default is not recommended and may change in future versions. +``` + +The `UV_PYTHON` setting accepts version specs like `3.13`, `3.14`, or `cpython@3.14`: + +```yaml +topics: + core: + mxenv: + PYTHON_MIN_VERSION: "3.14" + PRIMARY_PYTHON: python3 + PYTHON_PACKAGE_INSTALLER: uv + UV_PYTHON: "3.14" # Explicitly specify Python version for UV +``` + ## Examples ### Create a simple Python project @@ -56,8 +77,9 @@ Enter the `hello-world-` directory and create a file `preseed.yaml`: topics: core: mxenv: - PYTHON_MIN_VERSION: "3.10" + PYTHON_MIN_VERSION: "3.14" PYTHON_PACKAGE_INSTALLER: uv + UV_PYTHON: "3.14" sources: qa: ruff @@ -102,8 +124,9 @@ topics: base: RUN_TARGET: zope-start mxenv: - PYTHON_MIN_VERSION: "3.10" + PYTHON_MIN_VERSION: "3.13" PYTHON_PACKAGE_INSTALLER: uv + UV_PYTHON: "3.13" applications: zope: plone: From 8d46889cfd711af0d793b5bedc890735c4eb550c Mon Sep 17 00:00:00 2001 From: "Jens W. Klein" Date: Thu, 23 Oct 2025 22:05:24 +0200 Subject: [PATCH 14/14] Comment USE_UV_* use. Be a bit more verbose on output. --- src/mxmake/tests/expected/Makefile | 9 +++++++-- src/mxmake/topics/core/mxenv.mk | 8 ++++++-- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/src/mxmake/tests/expected/Makefile b/src/mxmake/tests/expected/Makefile index 2ee03af..795c63b 100644 --- a/src/mxmake/tests/expected/Makefile +++ b/src/mxmake/tests/expected/Makefile @@ -142,7 +142,7 @@ endif # Determine the package installer with non-interactive flags ifeq ("$(PYTHON_PACKAGE_INSTALLER)","uv") -PYTHON_PACKAGE_COMMAND=uv pip --quiet --no-progress +PYTHON_PACKAGE_COMMAND=uv pip --no-progress else PYTHON_PACKAGE_COMMAND=$(MXENV_PYTHON) -m pip endif @@ -155,6 +155,10 @@ UV_AVAILABLE:=false endif # Determine installation strategy +# depending on the PYTHON_PACKAGE_INSTALLER and UV_AVAILABLE +# - both vars can be false or +# - one of them can be true, +# - but never boths. USE_GLOBAL_UV:=$(shell [[ "$(PYTHON_PACKAGE_INSTALLER)" == "uv" && "$(UV_AVAILABLE)" == "true" ]] && echo "true" || echo "false") USE_LOCAL_UV:=$(shell [[ "$(PYTHON_PACKAGE_INSTALLER)" == "uv" && "$(UV_AVAILABLE)" == "false" ]] && echo "true" || echo "false") @@ -188,8 +192,9 @@ endif # Create virtual environment ifeq ("$(VENV_ENABLED)", "true") ifeq ("$(VENV_CREATE)", "true") +ifeq ("$(USE_GLOBAL_UV)","true") @echo "Setup Python Virtual Environment using global uv at '$(VENV_FOLDER)'" - @uv venv --allow-existing --no-progress --python $(UV_PYTHON) --seed $(VENV_FOLDER) + @uv venv --allow-existing --no-progress -p $(UV_PYTHON) --seed $(VENV_FOLDER) else @echo "Setup Python Virtual Environment using module 'venv' at '$(VENV_FOLDER)'" @$(PRIMARY_PYTHON) -m venv $(VENV_FOLDER) diff --git a/src/mxmake/topics/core/mxenv.mk b/src/mxmake/topics/core/mxenv.mk index ff133cf..d479ce4 100644 --- a/src/mxmake/topics/core/mxenv.mk +++ b/src/mxmake/topics/core/mxenv.mk @@ -97,7 +97,7 @@ endif # Determine the package installer with non-interactive flags ifeq ("$(PYTHON_PACKAGE_INSTALLER)","uv") -PYTHON_PACKAGE_COMMAND=uv pip --quiet --no-progress +PYTHON_PACKAGE_COMMAND=uv pip --no-progress else PYTHON_PACKAGE_COMMAND=$(MXENV_PYTHON) -m pip endif @@ -110,6 +110,10 @@ UV_AVAILABLE:=false endif # Determine installation strategy +# depending on the PYTHON_PACKAGE_INSTALLER and UV_AVAILABLE +# - both vars can be false or +# - one of them can be true, +# - but never boths. USE_GLOBAL_UV:=$(shell [[ "$(PYTHON_PACKAGE_INSTALLER)" == "uv" && "$(UV_AVAILABLE)" == "true" ]] && echo "true" || echo "false") USE_LOCAL_UV:=$(shell [[ "$(PYTHON_PACKAGE_INSTALLER)" == "uv" && "$(UV_AVAILABLE)" == "false" ]] && echo "true" || echo "false") @@ -145,7 +149,7 @@ ifeq ("$(VENV_ENABLED)", "true") ifeq ("$(VENV_CREATE)", "true") ifeq ("$(USE_GLOBAL_UV)","true") @echo "Setup Python Virtual Environment using global uv at '$(VENV_FOLDER)'" - @uv venv --allow-existing --quiet --no-progress -p $(UV_PYTHON) --seed $(VENV_FOLDER) + @uv venv --allow-existing --no-progress -p $(UV_PYTHON) --seed $(VENV_FOLDER) else @echo "Setup Python Virtual Environment using module 'venv' at '$(VENV_FOLDER)'" @$(PRIMARY_PYTHON) -m venv $(VENV_FOLDER)