@@ -38,30 +38,41 @@ INCLUDE_MAKEFILE?=include.mk
3838# No default value.
3939EXTRA_PATH? =
4040
41+ # Path to Python project relative to Makefile (repository root).
42+ # Leave empty if Python project is in the same directory as Makefile.
43+ # For monorepo setups, set to subdirectory name (e.g., `backend`).
44+ # Future-proofed for multi-language monorepos (e.g., PROJECT_PATH_NODEJS).
45+ # No default value.
46+ PROJECT_PATH_PYTHON? =
47+
4148# # core.mxenv
4249
4350# Primary Python interpreter to use. It is used to create the
4451# virtual environment if `VENV_ENABLED` and `VENV_CREATE` are set to `true`.
52+ # If global `uv` is used, this value is passed as `--python VALUE` to the venv creation.
53+ # uv then downloads the Python interpreter if it is not available.
54+ # for more on this feature read the [uv python documentation](https://docs.astral.sh/uv/concepts/python-versions/)
4555# Default: python3
4656PRIMARY_PYTHON? =python3
4757
4858# Minimum required Python version.
49- # Default: 3.9
50- PYTHON_MIN_VERSION? =3.9
59+ # Default: 3.10
60+ PYTHON_MIN_VERSION? =3.10
5161
5262# Install packages using the given package installer method.
53- # Supported are `pip` and `uv`. If uv is used, its global availability is
54- # checked. Otherwise, it is installed, either in the virtual environment or
55- # using the `PRIMARY_PYTHON`, dependent on the `VENV_ENABLED` setting. If
56- # `VENV_ENABLED` and uv is selected, uv is used to create the virtual
57- # environment.
63+ # Supported are `pip` and `uv`. When `uv` is selected, a global installation
64+ # is auto-detected and used if available. Otherwise, uv is installed in the
65+ # virtual environment or using `PRIMARY_PYTHON`, depending on the
66+ # `VENV_ENABLED` setting.
5867# Default: pip
5968PYTHON_PACKAGE_INSTALLER? =uv
6069
61- # Flag whether to use a global installed 'uv' or install
62- # it in the virtual environment.
63- # Default: false
64- MXENV_UV_GLOBAL? =false
70+ # Python version for UV to install/use when creating virtual
71+ # environments with global UV. Passed to `uv venv -p VALUE`. Supports version
72+ # specs like `3.11`, `3.14`, `cpython@3.14`. Defaults to PRIMARY_PYTHON value
73+ # for backward compatibility.
74+ # Default: $(PRIMARY_PYTHON)
75+ UV_PYTHON? =$(PRIMARY_PYTHON )
6576
6677# Flag whether to use virtual environment. If `false`, the
6778# interpreter according to `PRIMARY_PYTHON` found in `PATH` is used.
@@ -158,6 +169,9 @@ FORMAT_TARGETS?=
158169
159170export PATH: =$(if $(EXTRA_PATH ) ,$(EXTRA_PATH ) :,)$(PATH )
160171
172+ # Helper variable: adds trailing slash to PROJECT_PATH_PYTHON only if non-empty
173+ PYTHON_PROJECT_PREFIX =$(if $(PROJECT_PATH_PYTHON ) ,$(PROJECT_PATH_PYTHON ) /,)
174+
161175# Defensive settings for make: https://tech.davis-hansson.com/p/make/
162176SHELL: =bash
163177.ONESHELL :
@@ -182,7 +196,7 @@ $(SENTINEL): $(firstword $(MAKEFILE_LIST))
182196# mxenv
183197# #############################################################################
184198
185- export OS: = $( OS )
199+ OS? =
186200
187201# Determine the executable path
188202ifeq ("$(VENV_ENABLED ) ", "true")
@@ -198,26 +212,61 @@ else
198212MXENV_PYTHON =$(PRIMARY_PYTHON )
199213endif
200214
201- # Determine the package installer
215+ # Determine the package installer with non-interactive flags
202216ifeq ("$(PYTHON_PACKAGE_INSTALLER ) ","uv")
203- PYTHON_PACKAGE_COMMAND =uv pip
217+ PYTHON_PACKAGE_COMMAND =uv pip --no-progress
204218else
205219PYTHON_PACKAGE_COMMAND =$(MXENV_PYTHON ) -m pip
206220endif
207221
222+ # Auto-detect global uv availability (simple existence check)
223+ ifeq ("$(PYTHON_PACKAGE_INSTALLER ) ","uv")
224+ UV_AVAILABLE: =$(shell command -v uv >/dev/null 2>&1 && echo "true" || echo "false")
225+ else
226+ UV_AVAILABLE: =false
227+ endif
228+
229+ # Determine installation strategy
230+ # depending on the PYTHON_PACKAGE_INSTALLER and UV_AVAILABLE
231+ # - both vars can be false or
232+ # - one of them can be true,
233+ # - but never boths.
234+ USE_GLOBAL_UV: =$(shell [[ "$(PYTHON_PACKAGE_INSTALLER ) " == "uv" && "$(UV_AVAILABLE ) " == "true" ]] && echo "true" || echo "false")
235+ USE_LOCAL_UV: =$(shell [[ "$(PYTHON_PACKAGE_INSTALLER ) " == "uv" && "$(UV_AVAILABLE ) " == "false" ]] && echo "true" || echo "false")
236+
237+ # Check if global UV is outdated (non-blocking warning)
238+ ifeq ("$(USE_GLOBAL_UV ) ","true")
239+ UV_OUTDATED: =$(shell uv self update --dry-run 2>&1 | grep -q "Would update" && echo "true" || echo "false")
240+ else
241+ UV_OUTDATED: =false
242+ endif
243+
208244MXENV_TARGET: =$(SENTINEL_FOLDER ) /mxenv.sentinel
209245$(MXENV_TARGET ) : $(SENTINEL )
246+ # Validation: Check Python version if not using global uv
247+ ifneq ("$(USE_GLOBAL_UV ) ","true")
210248 @$(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)" \
211249 && echo "Need Python >= $(PYTHON_MIN_VERSION)" && exit 1 || :
250+ else
251+ @echo "Using global uv for Python $(UV_PYTHON)"
252+ endif
253+ # Validation: Check VENV_FOLDER is set if venv enabled
212254 @[[ "$(VENV_ENABLED)" == "true" && "$(VENV_FOLDER)" == "" ]] \
213255 && echo "VENV_FOLDER must be configured if VENV_ENABLED is true" && exit 1 || :
214- @[[ " $( VENV_ENABLED) $( PYTHON_PACKAGE_INSTALLER) " == " falseuv" ]] \
256+ # Validation: Check uv not used with system Python
257+ @[[ "$(VENV_ENABLED)" == "false" && "$(PYTHON_PACKAGE_INSTALLER)" == "uv" ]] \
215258 && echo "Package installer uv does not work with a global Python interpreter." && exit 1 || :
259+ # Warning: Notify if global UV is outdated
260+ ifeq ("$(UV_OUTDATED ) ","true")
261+ @echo "WARNING: A newer version of uv is available. Run 'uv self update' to upgrade."
262+ endif
263+
264+ # Create virtual environment
216265ifeq ("$(VENV_ENABLED ) ", "true")
217266ifeq ("$(VENV_CREATE ) ", "true")
218- ifeq ("$(PYTHON_PACKAGE_INSTALLER )$( MXENV_UV_GLOBAL ) ","uvtrue ")
219- @echo "Setup Python Virtual Environment using package 'uv' at '$(VENV_FOLDER)'"
220- @uv venv -p $(PRIMARY_PYTHON ) --seed $(VENV_FOLDER)
267+ ifeq ("$(USE_GLOBAL_UV ) ","true ")
268+ @echo "Setup Python Virtual Environment using global uv at '$(VENV_FOLDER)'"
269+ @uv venv --allow-existing --no-progress - p $(UV_PYTHON ) --seed $(VENV_FOLDER)
221270else
222271 @echo "Setup Python Virtual Environment using module 'venv' at '$(VENV_FOLDER)'"
223272 @$(PRIMARY_PYTHON) -m venv $(VENV_FOLDER)
@@ -227,10 +276,14 @@ endif
227276else
228277 @echo "Using system Python interpreter"
229278endif
230- ifeq ("$(PYTHON_PACKAGE_INSTALLER )$(MXENV_UV_GLOBAL ) ","uvfalse")
231- @echo "Install uv"
279+
280+ # Install uv locally if needed
281+ ifeq ("$(USE_LOCAL_UV ) ","true")
282+ @echo "Install uv in virtual environment"
232283 @$(MXENV_PYTHON) -m pip install uv
233284endif
285+
286+ # Install/upgrade core packages
234287 @$(PYTHON_PACKAGE_COMMAND) install -U pip setuptools wheel
235288 @echo "Install/Update MXStack Python packages"
236289 @$(PYTHON_PACKAGE_COMMAND) install -U $(MXDEV) $(MXMAKE)
285338 @echo "[settings]" > $(PROJECT_CONFIG)
286339endif
287340
288- LOCAL_PACKAGE_FILES: =$(wildcard pyproject.toml setup.cfg setup.py requirements.txt constraints.txt)
341+ LOCAL_PACKAGE_FILES: =$(wildcard $( PYTHON_PROJECT_PREFIX ) pyproject.toml $( PYTHON_PROJECT_PREFIX ) setup.cfg $( PYTHON_PROJECT_PREFIX ) setup.py $( PYTHON_PROJECT_PREFIX ) requirements.txt $( PYTHON_PROJECT_PREFIX ) constraints.txt)
289342
290343FILES_TARGET: =requirements-mxdev.txt
291344$(FILES_TARGET ) : $(PROJECT_CONFIG ) $(MXENV_TARGET ) $(SOURCES_TARGET ) $(LOCAL_PACKAGE_FILES )
0 commit comments