@@ -52,6 +52,12 @@ EXTRA_PATH?=
5252# are `npm` and `pnpm`
5353# Default: npm
5454NODEJS_PACKAGE_MANAGER? =pnpm
55+ # Path to Python project relative to Makefile (repository root).
56+ # Leave empty if Python project is in the same directory as Makefile.
57+ # For monorepo setups, set to subdirectory name (e.g., `backend`).
58+ # Future-proofed for multi-language monorepos (e.g., PROJECT_PATH_NODEJS).
59+ # No default value.
60+ PROJECT_PATH_PYTHON? =
5561
5662# Value for `--prefix` option when installing packages.
5763# Default: .
@@ -116,26 +122,30 @@ ROLLUP_CONFIG?=js/rollup.conf.js
116122
117123# Primary Python interpreter to use. It is used to create the
118124# virtual environment if `VENV_ENABLED` and `VENV_CREATE` are set to `true`.
125+ # If global `uv` is used, this value is passed as `--python VALUE` to the venv creation.
126+ # uv then downloads the Python interpreter if it is not available.
127+ # for more on this feature read the [uv python documentation](https://docs.astral.sh/uv/concepts/python-versions/)
119128# Default: python3
120129PRIMARY_PYTHON? =python3
121130
122131# Minimum required Python version.
123- # Default: 3.9
124- PYTHON_MIN_VERSION? =3.9
132+ # Default: 3.10
133+ PYTHON_MIN_VERSION? =3.10
125134
126135# Install packages using the given package installer method.
127- # Supported are `pip` and `uv`. If uv is used, its global availability is
128- # checked. Otherwise, it is installed, either in the virtual environment or
129- # using the `PRIMARY_PYTHON`, dependent on the `VENV_ENABLED` setting. If
130- # `VENV_ENABLED` and uv is selected, uv is used to create the virtual
131- # environment.
136+ # Supported are `pip` and `uv`. When `uv` is selected, a global installation
137+ # is auto-detected and used if available. Otherwise, uv is installed in the
138+ # virtual environment or using `PRIMARY_PYTHON`, depending on the
139+ # `VENV_ENABLED` setting.
132140# Default: pip
133141PYTHON_PACKAGE_INSTALLER? =uv
134142
135- # Flag whether to use a global installed 'uv' or install
136- # it in the virtual environment.
137- # Default: false
138- MXENV_UV_GLOBAL? =false
143+ # Python version for UV to install/use when creating virtual
144+ # environments with global UV. Passed to `uv venv -p VALUE`. Supports version
145+ # specs like `3.11`, `3.14`, `[email protected] `. Defaults to PRIMARY_PYTHON value146+ # for backward compatibility.
147+ # Default: $(PRIMARY_PYTHON)
148+ UV_PYTHON? =$(PRIMARY_PYTHON )
139149
140150# Flag whether to use virtual environment. If `false`, the
141151# interpreter according to `PRIMARY_PYTHON` found in `PATH` is used.
@@ -180,6 +190,10 @@ DOCS_SOURCE_FOLDER?=docs/source
180190# Default: docs/html
181191DOCS_TARGET_FOLDER? =docs/html
182192
193+ # Documentation linkcheck output folder.
194+ # Default: docs/linkcheck
195+ DOCS_LINKCHECK_FOLDER? =docs/linkcheck
196+
183197# Documentation Python requirements to be installed (via pip).
184198# No default value.
185199DOCS_REQUIREMENTS? =
@@ -207,7 +221,7 @@ TEST_COMMAND?=.mxmake/files/run-tests.sh
207221# Additional Python requirements for running tests to be
208222# installed (via pip).
209223# Default: pytest
210- TEST_REQUIREMENTS? =zope.testrunner
224+ TEST_REQUIREMENTS? =pytest
211225
212226# Additional make targets the test target depends on.
213227# No default value.
@@ -262,6 +276,9 @@ FORMAT_TARGETS?=
262276
263277export PATH: =$(if $(EXTRA_PATH ) ,$(EXTRA_PATH ) :,)$(PATH )
264278
279+ # Helper variable: adds trailing slash to PROJECT_PATH_PYTHON only if non-empty
280+ PYTHON_PROJECT_PREFIX =$(if $(PROJECT_PATH_PYTHON ) ,$(PROJECT_PATH_PYTHON ) /,)
281+
265282# Defensive settings for make: https://tech.davis-hansson.com/p/make/
266283SHELL: =bash
267284.ONESHELL :
@@ -388,26 +405,61 @@ else
388405MXENV_PYTHON =$(PRIMARY_PYTHON )
389406endif
390407
391- # Determine the package installer
408+ # Determine the package installer with non-interactive flags
392409ifeq ("$(PYTHON_PACKAGE_INSTALLER ) ","uv")
393- PYTHON_PACKAGE_COMMAND =uv pip
410+ PYTHON_PACKAGE_COMMAND =uv pip --no-progress
394411else
395412PYTHON_PACKAGE_COMMAND =$(MXENV_PYTHON ) -m pip
396413endif
397414
415+ # Auto-detect global uv availability (simple existence check)
416+ ifeq ("$(PYTHON_PACKAGE_INSTALLER ) ","uv")
417+ UV_AVAILABLE: =$(shell command -v uv >/dev/null 2>&1 && echo "true" || echo "false")
418+ else
419+ UV_AVAILABLE: =false
420+ endif
421+
422+ # Determine installation strategy
423+ # depending on the PYTHON_PACKAGE_INSTALLER and UV_AVAILABLE
424+ # - both vars can be false or
425+ # - one of them can be true,
426+ # - but never boths.
427+ USE_GLOBAL_UV: =$(shell [[ "$(PYTHON_PACKAGE_INSTALLER ) " == "uv" && "$(UV_AVAILABLE ) " == "true" ]] && echo "true" || echo "false")
428+ USE_LOCAL_UV: =$(shell [[ "$(PYTHON_PACKAGE_INSTALLER ) " == "uv" && "$(UV_AVAILABLE ) " == "false" ]] && echo "true" || echo "false")
429+
430+ # Check if global UV is outdated (non-blocking warning)
431+ ifeq ("$(USE_GLOBAL_UV ) ","true")
432+ UV_OUTDATED: =$(shell uv self update --dry-run 2>&1 | grep -q "Would update" && echo "true" || echo "false")
433+ else
434+ UV_OUTDATED: =false
435+ endif
436+
398437MXENV_TARGET: =$(SENTINEL_FOLDER ) /mxenv.sentinel
399438$(MXENV_TARGET ) : $(SENTINEL )
439+ # Validation: Check Python version if not using global uv
440+ ifneq ("$(USE_GLOBAL_UV ) ","true")
400441 @$(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)" \
401442 && echo "Need Python >= $(PYTHON_MIN_VERSION)" && exit 1 || :
443+ else
444+ @echo "Using global uv for Python $(UV_PYTHON)"
445+ endif
446+ # Validation: Check VENV_FOLDER is set if venv enabled
402447 @[[ "$(VENV_ENABLED)" == "true" && "$(VENV_FOLDER)" == "" ]] \
403448 && echo "VENV_FOLDER must be configured if VENV_ENABLED is true" && exit 1 || :
404- @[[ " $( VENV_ENABLED) $( PYTHON_PACKAGE_INSTALLER) " == " falseuv" ]] \
449+ # Validation: Check uv not used with system Python
450+ @[[ "$(VENV_ENABLED)" == "false" && "$(PYTHON_PACKAGE_INSTALLER)" == "uv" ]] \
405451 && echo "Package installer uv does not work with a global Python interpreter." && exit 1 || :
452+ # Warning: Notify if global UV is outdated
453+ ifeq ("$(UV_OUTDATED ) ","true")
454+ @echo "WARNING: A newer version of uv is available. Run 'uv self update' to upgrade."
455+ endif
456+
457+ # Create virtual environment
406458ifeq ("$(VENV_ENABLED ) ", "true")
407459ifeq ("$(VENV_CREATE ) ", "true")
408- ifeq ("$(PYTHON_PACKAGE_INSTALLER )$( MXENV_UV_GLOBAL ) ","uvtrue ")
409- @echo "Setup Python Virtual Environment using package 'uv' at '$(VENV_FOLDER)'"
410- @uv venv -p $(PRIMARY_PYTHON ) --seed $(VENV_FOLDER)
460+ ifeq ("$(USE_GLOBAL_UV ) ","true ")
461+ @echo "Setup Python Virtual Environment using global uv at '$(VENV_FOLDER)'"
462+ @uv venv --allow-existing --no-progress - p $(UV_PYTHON ) --seed $(VENV_FOLDER)
411463else
412464 @echo "Setup Python Virtual Environment using module 'venv' at '$(VENV_FOLDER)'"
413465 @$(PRIMARY_PYTHON) -m venv $(VENV_FOLDER)
@@ -417,10 +469,14 @@ endif
417469else
418470 @echo "Using system Python interpreter"
419471endif
420- ifeq ("$(PYTHON_PACKAGE_INSTALLER )$(MXENV_UV_GLOBAL ) ","uvfalse")
421- @echo "Install uv"
472+
473+ # Install uv locally if needed
474+ ifeq ("$(USE_LOCAL_UV ) ","true")
475+ @echo "Install uv in virtual environment"
422476 @$(MXENV_PYTHON) -m pip install uv
423477endif
478+
479+ # Install/upgrade core packages
424480 @$(PYTHON_PACKAGE_COMMAND) install -U pip setuptools wheel
425481 @echo "Install/Update MXStack Python packages"
426482 @$(PYTHON_PACKAGE_COMMAND) install -U $(MXDEV) $(MXMAKE)
@@ -509,6 +565,11 @@ docs-live: $(DOCS_TARGET) $(DOCS_TARGETS)
509565 @echo " Rebuild Sphinx documentation on changes, with live-reload in the browser"
510566 @$(SPHINX_AUTOBUILD_BIN ) $(DOCS_SOURCE_FOLDER ) $(DOCS_TARGET_FOLDER )
511567
568+ .PHONY : docs-linkcheck
569+ docs-linkcheck : $(DOCS_TARGET ) $(DOCS_TARGETS )
570+ @echo " Run Sphinx linkcheck"
571+ @$(SPHINX_BIN ) -b linkcheck $(DOCS_SOURCE_FOLDER ) $(DOCS_LINKCHECK_FOLDER )
572+
512573.PHONY : docs-dirty
513574docs-dirty :
514575 @rm -f $(DOCS_TARGET )
@@ -530,7 +591,7 @@ CLEAN_TARGETS+=docs-clean
530591SOURCES_TARGET: =$(SENTINEL_FOLDER ) /sources.sentinel
531592$(SOURCES_TARGET ) : $(PROJECT_CONFIG ) $(MXENV_TARGET )
532593 @echo " Checkout project sources"
533- @mxdev -o -c $(PROJECT_CONFIG )
594+ @mxdev -f -c $(PROJECT_CONFIG )
534595 @touch $(SOURCES_TARGET )
535596
536597.PHONY : sources
575636 @echo "[settings]" > $(PROJECT_CONFIG)
576637endif
577638
578- LOCAL_PACKAGE_FILES: =$(wildcard pyproject.toml setup.cfg setup.py requirements.txt constraints.txt)
639+ 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)
579640
580641FILES_TARGET: =requirements-mxdev.txt
581642$(FILES_TARGET ) : $(PROJECT_CONFIG ) $(MXENV_TARGET ) $(SOURCES_TARGET ) $(LOCAL_PACKAGE_FILES )
0 commit comments