fritzkink
2023-12-27 706019354bab81fc3f01995caf1ae1a2dfa346cf
commit | author | age
9c75c0 1 #
NJ 2 # CDDL HEADER START
3 #
4 # The contents of this file are subject to the terms of the
5 # Common Development and Distribution License (the "License").
6 # You may not use this file except in compliance with the License.
7 #
8 # You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9 # or http://www.opensolaris.org/os/licensing.
10 # See the License for the specific language governing permissions
11 # and limitations under the License.
12 #
13 # When distributing Covered Code, include this CDDL HEADER in each
14 # file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15 # If applicable, add the following below this CDDL HEADER, with the
16 # fields enclosed by brackets "[]" replaced with your own identifying
17 # information: Portions Copyright [yyyy] [name of copyright owner]
18 #
19 # CDDL HEADER END
20 #
8beffa 21 # Copyright (c) 2010, 2016, Oracle and/or its affiliates. All rights reserved.
9c75c0 22 #
NJ 23
7c8106 24 #
MT 25 # The Python build infrastructure in setup.py.mk and pyproject.mk files uses
26 # several Python projects to work properly.  Since we cannot use these projects
27 # until they are actually built and installed we need to bootstrap.
28 #
29 # We do have several sequential bootstrap checkpoints during the process:
30 #
31 # (0)    Nothing works yet.
32 #
33 #     Just core Python runtime is available (with no additional projects).
34 #     While here almost nothing works.  We cannot do following tasks with
35 #     regular Python projects:
36 #         - detect their requirements,
37 #         - build and publish them,
38 #         - test them.
39 #
40 # (1)     The bootstrapper is ready.
41 #
42 #     The bootstrapper is special tool that requires just core Python with no
43 #     dependency on other Python projects and it is able to build and publish
44 #     itself and other Python projects.
45 #
46 #     For projects using the 'setup.py' build style we do not need any
47 #     special bootstrapper because such projects are built using their own
48 #     'setup.py' script.  The only issue with the 'setup.py' build style
49 #     projects is that their 'setup.py' script usually depends on some other
50 #     projects (typically setuptools) to get successfully built.
51 #
52 #     For 'pyproject'-style projects we use pyproject_installer as the
53 #     bootstrapper.
54 #
55 #     To achieve this checkpoint we just need to build pyproject_installer
56 #     using pyproject_installer without detecting its requirements (they are
6b1855 57 #     none anyway) and without testing it (since no testing infrastructure is
7c8106 58 #     ready yet).
MT 59 #
60 # (2)    The python-requires script works.
61 #
62 #     Once the python-requires script works we can start to detect runtime
63 #     dependencie of other Python projects automatically.
64 #
65 #     To achieve this checkpoint we need to build the packaging project
66 #     (directly needed by the python-requires script) and all projects
67 #     required by packaging.  During this all projects' dependencies needs to
68 #     be manually evaluated to make sure they are correct.
69 #
70 # (3)    The build infrastructure is fully working.
71 #
72 #    Once we are here we can build any Python project, but we cannot test it
73 #    yet.
74 #
75 #     For projects using the 'setup.py' build style we do not need any
76 #     special build infrastructure.  See checkpoint (1) above for detialed
77 #     discussion about 'setup.py' build style projects.
78 #
79 #     For 'pyproject'-style projects we need to build both 'build' and
80 #     'installer' projects and all projects they depends on.
81 #
82 # (4)    The testing infrastructure is fully working.
83 #
84 #     Once we are here we can finally use all features of the Python build
85 #     framework.  Including testing.
86 #
87 #     To achieve this we need to build tox, tox-current-env, and pytest
88 #     projects together with their dependencies.
89 #
90 # All projects needed to achieve checkpoints (1), (2), and (3) should set
91 # PYTHON_BOOTSTRAP to 'yes' in their Makefile to make sure the regular build
92 # infrastructure is not used for them and special set of build rules is applied
93 # instead.
94 #
95 # All projects needed to go from checkpoint (3) to checkpoint (4) should set
96 # PYTHON_TEST_BOOTSTRAP to 'yes' in their Makefile to let the build
97 # infrastructure know that testing for such projects might not work properly.
98 #
99 # The PYTHON_BOOTSTRAP set to 'yes' implies PYTHON_TEST_BOOTSTRAP set to 'yes'
100 # too.
101 #
102 ifeq ($(strip $(PYTHON_BOOTSTRAP)),yes)
103 PYTHON_TEST_BOOTSTRAP = yes
104 endif
6b1855 105
MT 106 #
107 # Lists of Python projects needed to achieve particular bootstrap checkpoint.
2172bd 108 # Indentation shows project dependencies (e.g. packaging requires flit_core).
6b1855 109 #
MT 110 PYTHON_BOOTSTRAP_CHECKPOINT_1 +=    pyproject_installer
111 #
112 PYTHON_BOOTSTRAP_CHECKPOINT_2 +=    $(PYTHON_BOOTSTRAP_CHECKPOINT_1)
113 PYTHON_BOOTSTRAP_CHECKPOINT_2 +=    packaging
2172bd 114 PYTHON_BOOTSTRAP_CHECKPOINT_2 +=        flit_core
7c8106 115
792915 116 # Particular python runtime is always required (at least to run setup.py)
MT 117 PYTHON_REQUIRED_PACKAGES += runtime/python
118
668bcb 119 define python-rule
AL 120 $(BUILD_DIR)/%-$(1)/.built:        PYTHON_VERSION=$(1)
121 $(BUILD_DIR)/%-$(1)/.installed:        PYTHON_VERSION=$(1)
122 $(BUILD_DIR)/%-$(1)/.tested:        PYTHON_VERSION=$(1)
123 $(BUILD_DIR)/%-$(1)/.tested-and-compared:    PYTHON_VERSION=$(1)
124 endef
125
126 $(foreach pyver, $(PYTHON_VERSIONS), $(eval $(call python-rule,$(pyver))))
127
74300c 128 $(BUILD_DIR)/$(MACH32)-%/.built:    BITS=32
NJ 129 $(BUILD_DIR)/$(MACH64)-%/.built:    BITS=64
130 $(BUILD_DIR)/$(MACH32)-%/.installed:    BITS=32
131 $(BUILD_DIR)/$(MACH64)-%/.installed:    BITS=64
8d70f8 132 $(BUILD_DIR)/$(MACH32)-%/.tested:    BITS=32
RB 133 $(BUILD_DIR)/$(MACH64)-%/.tested:    BITS=64
134 $(BUILD_DIR)/$(MACH32)-%/.tested-and-compared:    BITS=32
135 $(BUILD_DIR)/$(MACH64)-%/.tested-and-compared:    BITS=64
224ba0 136
44f1c2 137 PYTHON_32_VERSIONS = $(filter-out $(PYTHON_64_ONLY_VERSIONS), $(PYTHON_VERSIONS))
AP 138
139 BUILD_32 = $(PYTHON_32_VERSIONS:%=$(BUILD_DIR)/$(MACH32)-%/.built)
74300c 140 BUILD_64 = $(PYTHON_VERSIONS:%=$(BUILD_DIR)/$(MACH64)-%/.built)
ad2fa3 141 BUILD_NO_ARCH = $(PYTHON_VERSIONS:%=$(BUILD_DIR)/$(MACH)-%/.built)
224ba0 142
668bcb 143 ifeq ($(filter-out $(PYTHON_64_ONLY_VERSIONS), $(PYTHON_VERSION)),)
44f1c2 144 BUILD_32_and_64 = $(BUILD_64)
AP 145 endif
146
147 INSTALL_32 = $(PYTHON_32_VERSIONS:%=$(BUILD_DIR)/$(MACH32)-%/.installed)
74300c 148 INSTALL_64 = $(PYTHON_VERSIONS:%=$(BUILD_DIR)/$(MACH64)-%/.installed)
ad2fa3 149 INSTALL_NO_ARCH = $(PYTHON_VERSIONS:%=$(BUILD_DIR)/$(MACH)-%/.installed)
9c75c0 150
35a012 151 PYTHON_ENV =    CC="$(CC)"
NJ 152 PYTHON_ENV +=    CFLAGS="$(CFLAGS)"
0d76b8 153 PYTHON_ENV +=    CXX="$(CXX)"
MT 154 PYTHON_ENV +=    CXXFLAGS="$(CXXFLAGS)"
fd5873 155 PYTHON_ENV +=    LDFLAGS="$(LDFLAGS)"
ba2ab2 156 PYTHON_ENV +=    PKG_CONFIG_PATH="$(PKG_CONFIG_PATH)"
ad2fa3 157
NJ 158 COMPONENT_BUILD_ENV += $(PYTHON_ENV)
159 COMPONENT_INSTALL_ENV += $(PYTHON_ENV)
160 COMPONENT_TEST_ENV += $(PYTHON_ENV)
fa74c0 161
ea207c 162 # Set CARGO_HOME to make sure projects built using rust (for example via
MT 163 # setuptools-rust) do not pollute user's home directory with cargo bits.
164 COMPONENT_BUILD_ENV += CARGO_HOME=$(@D)/.cargo
165
6aefa3 166 # Make sure the default Python version is installed last and so is the
MT 167 # canonical version.  This is needed for components that keep PYTHON_VERSIONS
168 # set to more than single value, but deliver unversioned binaries in usr/bin or
169 # other overlapping files.
170 define python-order-rule
171 $(BUILD_DIR)/%-$(PYTHON_VERSION)/.installed:    $(BUILD_DIR)/%-$(1)/.installed
172 endef
173 $(foreach pyver,$(filter-out $(PYTHON_VERSION),$(PYTHON_VERSIONS)),$(eval $(call python-order-rule,$(pyver))))
174
381aab 175 # We need to copy the source dir to avoid its modification by install target
MT 176 # where egg-info is re-generated
177 CLONEY_ARGS = CLONEY_MODE="copy"
a7216a 178
115d4d 179 COMPONENT_BUILD_CMD = $(PYTHON) setup.py --no-user-cfg build $(COMPONENT_BUILD_SETUP_PY_ARGS)
7589de 180
9c75c0 181 # build the configured source
381aab 182 $(BUILD_DIR)/%/.built:    $(SOURCE_DIR)/.prep
9c75c0 183     $(RM) -r $(@D) ; $(MKDIR) $(@D)
381aab 184     $(ENV) $(CLONEY_ARGS) $(CLONEY) $(SOURCE_DIR) $(@D)
9c75c0 185     $(COMPONENT_PRE_BUILD_ACTION)
b3a428 186     (cd $(@D)$(COMPONENT_SUBDIR:%=/%) ; $(ENV) $(COMPONENT_BUILD_ENV) \
7589de 187         $(COMPONENT_BUILD_CMD) $(COMPONENT_BUILD_ARGS))
9c75c0 188     $(COMPONENT_POST_BUILD_ACTION)
NJ 189     $(TOUCH) $@
190
7589de 191
MT 192 COMPONENT_INSTALL_CMD = $(PYTHON) setup.py --no-user-cfg install
ff17eb 193
e83e52 194 COMPONENT_INSTALL_ARGS +=    --root $(PROTO_DIR) 
NJ 195 COMPONENT_INSTALL_ARGS +=    --install-lib=$(PYTHON_LIB)
fa74c0 196 COMPONENT_INSTALL_ARGS +=    --install-data=$(PYTHON_DATA)
381aab 197 COMPONENT_INSTALL_ARGS +=    --skip-build
6aefa3 198 COMPONENT_INSTALL_ARGS +=    --force
e83e52 199
9c75c0 200 # install the built source into a prototype area
381aab 201 $(BUILD_DIR)/%/.installed:    $(BUILD_DIR)/%/.built
9c75c0 202     $(COMPONENT_PRE_INSTALL_ACTION)
b3a428 203     (cd $(@D)$(COMPONENT_SUBDIR:%=/%) ; $(ENV) $(COMPONENT_INSTALL_ENV) \
7589de 204         $(COMPONENT_INSTALL_CMD) $(COMPONENT_INSTALL_ARGS))
9c75c0 205     $(COMPONENT_POST_INSTALL_ACTION)
NJ 206     $(TOUCH) $@
e83e52 207
e537d3 208 ifeq ($(strip $(SINGLE_PYTHON_VERSION)),no)
053684 209 # Rename binaries in /usr/bin to contain version number
MT 210 COMPONENT_POST_INSTALL_ACTION += \
211     for f in $(PROTOUSRBINDIR)/* ; do \
9b979e 212         [ -f $$f ] || continue ; \
053684 213         for v in $(PYTHON_VERSIONS) ; do \
9b979e 214             [ "$$f" == "$${f%%$$v}" ] || continue 2 ; \
053684 215         done ; \
MT 216         $(MV) $$f $$f-$(PYTHON_VERSION) ; \
217     done ;
e537d3 218 endif
053684 219
dfab6b 220 # Remove any previous dependency files
MT 221 COMPONENT_POST_INSTALL_ACTION +=    $(RM) $(@D)/.depend-runtime $(@D)/.depend-test ;
06c6b6 222
17a961 223 # Define Python version specific filenames for tests.
30eb16 224 ifeq ($(strip $(USE_COMMON_TEST_MASTER)),no)
17a961 225 COMPONENT_TEST_MASTER =    $(COMPONENT_TEST_RESULTS_DIR)/results-$(PYTHON_VERSION).master
30eb16 226 endif
5d7921 227 COMPONENT_TEST_BUILD_DIR = $(BUILD_DIR)/test-$(PYTHON_VERSION)
17a961 228 COMPONENT_TEST_OUTPUT =    $(COMPONENT_TEST_BUILD_DIR)/test-$(PYTHON_VERSION)-results
MT 229 COMPONENT_TEST_DIFFS =    $(COMPONENT_TEST_BUILD_DIR)/test-$(PYTHON_VERSION)-diffs
230 COMPONENT_TEST_SNAPSHOT = $(COMPONENT_TEST_BUILD_DIR)/results-$(PYTHON_VERSION).snapshot
231 COMPONENT_TEST_TRANSFORM_CMD = $(COMPONENT_TEST_BUILD_DIR)/transform-$(PYTHON_VERSION)-results
8d70f8 232
34664f 233 # Generic transforms for Python test results.
MT 234 # See below for test style specific transforms.
235 COMPONENT_TEST_TRANSFORMS += "-e 's|$(PYTHON_DIR)|\$$(PYTHON_DIR)|g'"
848948 236
dcf6a8 237 # Make sure the test environment is prepared before we start tests
06c6b6 238 COMPONENT_TEST_DEP +=    component-test-environment-prep
dcf6a8 239 # Testing depends on install target because we want to test installed modules
MT 240 COMPONENT_TEST_DEP +=    $(BUILD_DIR)/%/.installed
241 # Point Python to the proto area so it is able to find installed modules there
242 COMPONENT_TEST_ENV +=    PYTHONPATH=$(PROTO_DIR)/$(PYTHON_LIB)
9b979e 243 # Make sure testing is able to find own installed executables (if any)
MT 244 COMPONENT_TEST_ENV +=    PATH=$(PROTOUSRBINDIR):$(PATH)
59c102 245
8d70f8 246 # determine the type of tests we want to run.
RB 247 ifeq ($(strip $(wildcard $(COMPONENT_TEST_RESULTS_DIR)/results-*.master)),)
44f1c2 248 TEST_32 = $(PYTHON_32_VERSIONS:%=$(BUILD_DIR)/$(MACH32)-%/.tested)
8d70f8 249 TEST_64 = $(PYTHON_VERSIONS:%=$(BUILD_DIR)/$(MACH64)-%/.tested)
RB 250 TEST_NO_ARCH = $(PYTHON_VERSIONS:%=$(BUILD_DIR)/$(MACH)-%/.tested)
251 else
44f1c2 252 TEST_32 = $(PYTHON_32_VERSIONS:%=$(BUILD_DIR)/$(MACH32)-%/.tested-and-compared)
8d70f8 253 TEST_64 = $(PYTHON_VERSIONS:%=$(BUILD_DIR)/$(MACH64)-%/.tested-and-compared)
RB 254 TEST_NO_ARCH = $(PYTHON_VERSIONS:%=$(BUILD_DIR)/$(MACH)-%/.tested-and-compared)
255 endif
256
381aab 257 #
792915 258 # Testing in the Python world is complex.  Python projects usually do not
MT 259 # support Makefile with common 'check' or 'test' target to get built bits
260 # tested.
381aab 261 #
792915 262 # De facto standard way to test Python projects these days is tox which is
MT 263 # designed and used primarily for release testing; to make sure the released
264 # python project runs on all supported Python versions, platforms, etc.  tox
265 # does so using virtualenv and creates isolated test environments where the
266 # tested package together with all its dependencies is automatically installed
267 # (using pip) and tested.  This is great for Python projects developers but it
268 # is hardly usable for operating system distributions like OpenIndiana.
381aab 269 #
792915 270 # We do not need such release testing.  Instead we need something closer to
MT 271 # integration testing: we need to test the built component in our real
272 # environment without automatic installation of any dependencies using pip.  In
273 # addition, we need to run tests only for Python versions we actually support
274 # and the component is built for.
381aab 275 #
792915 276 # To achieve that we do few things.  First, to avoid isolated environments
MT 277 # (virtualenv) we run tox with the tox-current-env plugin.  Second, to test
278 # only Python versions we are interested in we use -e option for tox to select
279 # single Python version only.  Since we run separate test target per Python
280 # version this will make sure we test all needed Python versions.
281 #
25e842 282 # The tox tool itself uses some other tools under the hood to run tests, for
MT 283 # example pytest.  Some projects could even support pytest testing directly
284 # without support for tox.  For such projects we offer separate "pytest"-style
285 # testing.
286 #
287 # For projects that do not support testing using neither tox nor pytest we
855acc 288 # offer either unittest or (deprecated) "setup.py test" testing too.
25e842 289 #
MT 290 # The TEST_STYLE variable is used to select (or force) particular test style
291 # for Python projects.  Valid values are:
292 #
293 #     tox        - "tox"-style testing
294 #     pytest        - "pytest"-style testing
855acc 295 #     unittest    - "unittest"-style testing
25e842 296 #     setup.py    - "setup.py test"-style testing
7c8106 297 #     none        - no testing is supported (or desired) at all
792915 298 #
MT 299
300 TEST_STYLE ?= tox
301 ifeq ($(strip $(TEST_STYLE)),tox)
9b979e 302 # tox needs PATH environment variable - see https://github.com/tox-dev/tox/issues/2538
MT 303 # We already added it to the test environment - see above
3db057 304 COMPONENT_TEST_ENV +=        PYTEST_ADDOPTS="$(PYTEST_ADDOPTS)"
dd736d 305 COMPONENT_TEST_ENV +=        NOSE_VERBOSE=2
792915 306 COMPONENT_TEST_CMD =        $(TOX)
81789e 307 COMPONENT_TEST_ARGS =        --current-env --no-provision
e7d6fe 308 COMPONENT_TEST_ARGS +=        --recreate
b621ca 309 COMPONENT_TEST_ARGS +=        $(TOX_TESTENV)
MT 310 COMPONENT_TEST_TARGETS =
311
ccec59 312 TOX_TESTENV = -e py$(subst .,,$(PYTHON_VERSION))
792915 313
83badd 314 # Make sure following tools are called indirectly to properly support tox-current-env
573ee5 315 TOX_CALL_INDIRECTLY += py.test
051a62 316 TOX_CALL_INDIRECTLY += pytest
MT 317 TOX_CALL_INDIRECTLY += coverage
1eb2df 318 TOX_CALL_INDIRECTLY += zope-testrunner
MT 319 TOX_CALL_INDIRECTLY.zope-testrunner = zope.testrunner
320 TOX_CALL_INDIRECTLY += sphinx-build
321 TOX_CALL_INDIRECTLY.sphinx-build = sphinx.cmd.build
dd736d 322 TOX_CALL_INDIRECTLY += nosetests
MT 323 TOX_CALL_INDIRECTLY.nosetests = nose
1eb2df 324 $(foreach indirectly, $(TOX_CALL_INDIRECTLY), $(eval TOX_CALL_INDIRECTLY.$(indirectly) ?= $(indirectly)))
MT 325 COMPONENT_PRE_TEST_ACTION += COMPONENT_TEST_DIR=$(COMPONENT_TEST_DIR) ;
950890 326 COMPONENT_PRE_TEST_ACTION += \
1eb2df 327     $(foreach indirectly, $(TOX_CALL_INDIRECTLY), \
MT 328         [ -f $$COMPONENT_TEST_DIR/tox.ini ] && \
a67d39 329             $(GSED) -i -e '/^commands *=/,/^$$/{ \
MT 330                 s/^\(\(commands *=\)\{0,1\}[ \t]*\)'$(indirectly)'\([ \t]\{1,\}.*\)\{0,1\}$$/\1python -m '$(TOX_CALL_INDIRECTLY.$(indirectly))'\3/ \
331             }' $$COMPONENT_TEST_DIR/tox.ini ; \
1eb2df 332     )
MT 333 COMPONENT_PRE_TEST_ACTION += true ;
ae4a72 334
792915 335 # Normalize tox test results.
ccec59 336 COMPONENT_TEST_TRANSFORMS += "-e 's/py$(subst .,,$(PYTHON_VERSION))/py\$$(PYV)/g'"    # normalize PYV
34664f 337 COMPONENT_TEST_TRANSFORMS += "-e '/^py\$$(PYV) installed:/d'"        # depends on set of installed packages
MT 338 COMPONENT_TEST_TRANSFORMS += "-e '/PYTHONHASHSEED/d'"            # this is random
792915 339
a3f365 340 # Normalize zope.testrunner test results
8937be 341 COMPONENT_TEST_TRANSFORMS += \
MT 342     "-e 's/ in \([0-9]\{1,\} minutes \)\{0,1\}[0-9]\{1,\}\.[0-9]\{3\} seconds//'"    # timing
a3f365 343
5cfe42 344 # Remove timing for tox 4 test results
MT 345 COMPONENT_TEST_TRANSFORMS += "-e 's/^\(  py\$$(PYV): OK\) (.* seconds)$$/\1/'"
346 COMPONENT_TEST_TRANSFORMS += "-e 's/^\(  congratulations :)\) (.* seconds)$$/\1/'"
347
22f684 348 # Remove useless lines from the "coverage combine" output
e91f55 349 COMPONENT_TEST_TRANSFORMS += "-e '/^Combined data file .*\.coverage/d'"
MT 350 COMPONENT_TEST_TRANSFORMS += "-e '/^Skipping duplicate data .*\.coverage/d'"
22f684 351
f86079 352 # sort list of Sphinx doctest results
MT 353 COMPONENT_TEST_TRANSFORMS += \
354     "| ( \
355         $(GSED) -u -e '/^running tests\.\.\.$$/q' ; \
356         $(GSED) -u -e '/^Doctest summary/Q' \
357             | $(NAWK) '/^$$/{\$$0=\"\\\\n\"}1' ORS='|' \
358             | $(GNU_GREP) -v '^|$$' \
a99182 359             | $(SORT) \
f86079 360             | tr -d '\\\\n' | tr '|' '\\\\n' \
MT 361             | $(NAWK) '{print}END{if(NR>0)printf(\"\\\\nDoctest summary\\\\n\")}' ; \
362         $(CAT) \
363     ) | $(COMPONENT_TEST_TRANSFORMER)"
364
792915 365 # tox package together with the tox-current-env plugin is needed
b1d234 366 USERLAND_TEST_REQUIRED_PACKAGES += library/python/tox
MT 367 USERLAND_TEST_REQUIRED_PACKAGES += library/python/tox-current-env
06c6b6 368
MT 369 # Generate raw lists of test dependencies per Python version
1eea40 370 # Please note we set PATH below four times for $(COMPONENT_TEST_CMD) (aka tox)
MT 371 # to workaround https://github.com/tox-dev/tox/issues/2538
06c6b6 372 COMPONENT_POST_INSTALL_ACTION += \
b1d234 373     if [ -x "$(COMPONENT_TEST_CMD)" ] ; then \
b3a428 374         cd $(@D)$(COMPONENT_SUBDIR:%=/%) ; \
b1d234 375         echo "Testing dependencies:" ; \
b621ca 376         PATH=$(PATH) $(COMPONENT_TEST_CMD) -qq --no-provision --print-deps-to=- $(TOX_TESTENV) || exit 1 ; \
b1d234 377         echo "Testing extras:" ; \
b621ca 378         PATH=$(PATH) $(COMPONENT_TEST_CMD) -qq --no-provision --print-extras-to=- $(TOX_TESTENV) || exit 1 ; \
MT 379         ( PATH=$(PATH) $(COMPONENT_TEST_CMD) -qq --no-provision --print-deps-to=- $(TOX_TESTENV) \
b1d234 380             | $(WS_TOOLS)/python-resolve-deps \
MT 381                 PYTHONPATH=$(PROTO_DIR)/$(PYTHON_DIR)/site-packages:$(PROTO_DIR)/$(PYTHON_LIB) \
382                 $(PYTHON) $(WS_TOOLS)/python-requires $(COMPONENT_NAME) \
383             | $(PYTHON) $(WS_TOOLS)/python-requires - ; \
b621ca 384         for e in $$(PATH=$(PATH) $(COMPONENT_TEST_CMD) -qq --no-provision --print-extras-to=- $(TOX_TESTENV)) ; do \
7416d1 385             PYTHONPATH=$(PROTO_DIR)/$(PYTHON_DIR)/site-packages:$(PROTO_DIR)/$(PYTHON_LIB) \
b1d234 386                 $(PYTHON) $(WS_TOOLS)/python-requires $(COMPONENT_NAME) $$e ; \
dfab6b 387         done ) | $(GSED) -e '/^tox\(-current-env\)\?$$/d' >> $(@D)/.depend-test ; \
b1d234 388     fi ;
25e842 389 else ifeq ($(strip $(TEST_STYLE)),pytest)
MT 390 COMPONENT_TEST_CMD =        $(PYTHON) -m pytest
3db057 391 COMPONENT_TEST_ARGS =        $(PYTEST_ADDOPTS)
25e842 392 COMPONENT_TEST_TARGETS =
3db057 393
b1d234 394 USERLAND_TEST_REQUIRED_PACKAGES += library/python/pytest
855acc 395 else ifeq ($(strip $(TEST_STYLE)),unittest)
MT 396 COMPONENT_TEST_CMD =        $(PYTHON) -m unittest
397 COMPONENT_TEST_ARGS =
398 COMPONENT_TEST_ARGS +=        --verbose
399 COMPONENT_TEST_TARGETS =
25e842 400 else ifeq ($(strip $(TEST_STYLE)),setup.py)
MT 401 # Old and deprecated "setup.py test"-style testing
792915 402 COMPONENT_TEST_CMD =        $(PYTHON) setup.py
MT 403 COMPONENT_TEST_ARGS =        --no-user-cfg
404 COMPONENT_TEST_TARGETS =    test
25e842 405 else ifeq ($(strip $(TEST_STYLE)),none)
MT 406 TEST_TARGET = $(NO_TESTS)
792915 407 endif
381aab 408
3db057 409 # Run pytest verbose to get separate line per test in results output
MT 410 PYTEST_ADDOPTS += --verbose
411
808d8a 412 # Force pytest to not use colored output so the results normalization is unaffected
MT 413 PYTEST_ADDOPTS += --color=no
414
ed465b 415 # Avoid loading of unexpected pytest plugins.
40cc3a 416 define disable-pytest-plugin
ed465b 417 PYTEST_ADDOPTS += $$(if $$(filter library/python/$(2)-$$(subst .,,$$(PYTHON_VERSION)), $$(REQUIRED_PACKAGES) $$(TEST_REQUIRED_PACKAGES) $$(COMPONENT_FMRI)-$$(subst .,,$$(PYTHON_VERSION))),,-p 'no:$(1)')
40cc3a 418 endef
ed465b 419 $(eval $(call disable-pytest-plugin,anyio,anyio))
1595bc 420 $(eval $(call disable-pytest-plugin,asyncio,pytest-asyncio))        # adds line to test report header
MT 421 $(eval $(call disable-pytest-plugin,benchmark,pytest-benchmark))    # adds line to test report header; adds benchmark report
0f943a 422 $(eval $(call disable-pytest-plugin,black,pytest-black))        # runs extra test(s)
ed465b 423 $(eval $(call disable-pytest-plugin,check,pytest-check))
0f943a 424 $(eval $(call disable-pytest-plugin,checkdocs,pytest-checkdocs))    # runs extra test(s)
ed465b 425 $(eval $(call disable-pytest-plugin,console-scripts,pytest-console-scripts))
40cc3a 426 $(eval $(call disable-pytest-plugin,cov,pytest-cov))
ed465b 427 $(eval $(call disable-pytest-plugin,custom_exit_code,pytest-custom-exit-code))
3380cb 428 $(eval $(call disable-pytest-plugin,enabler,pytest-enabler))
ed465b 429 $(eval $(call disable-pytest-plugin,env,pytest-env))
MT 430 $(eval $(call disable-pytest-plugin,faker,faker))
431 $(eval $(call disable-pytest-plugin,flake8,pytest-flake8))
40cc3a 432 $(eval $(call disable-pytest-plugin,flaky,flaky))
ed465b 433 $(eval $(call disable-pytest-plugin,freezegun,pytest-freezegun))
MT 434 $(eval $(call disable-pytest-plugin,freezer,pytest-freezer))
435 $(eval $(call disable-pytest-plugin,helpers_namespace,pytest-helpers-namespace))
1595bc 436 $(eval $(call disable-pytest-plugin,hypothesispytest,hypothesis))    # adds line to test report header
ed465b 437 $(eval $(call disable-pytest-plugin,jaraco.test.http,jaraco-test))
MT 438 $(eval $(call disable-pytest-plugin,kgb,kgb))
439 $(eval $(call disable-pytest-plugin,lazy-fixture,pytest-lazy-fixture))
140fab 440 $(eval $(call disable-pytest-plugin,metadata,pytest-metadata))        # adds line to test report header
0f943a 441 $(eval $(call disable-pytest-plugin,mypy,pytest-mypy))            # runs extra test(s)
MT 442 $(eval $(call disable-pytest-plugin,perf,pytest-perf))            # https://github.com/jaraco/pytest-perf/issues/9
ed465b 443 $(eval $(call disable-pytest-plugin,pytest-datadir,pytest-datadir))
a38a08 444 $(eval $(call disable-pytest-plugin,pytest-mypy-plugins,pytest-mypy-plugins))    # could cause tests to fail
ed465b 445 $(eval $(call disable-pytest-plugin,pytest-teamcity,teamcity-messages))
MT 446 $(eval $(call disable-pytest-plugin,pytest_expect,pytest-expect))
447 $(eval $(call disable-pytest-plugin,pytest_fakefs,pyfakefs))
448 $(eval $(call disable-pytest-plugin,pytest_forked,pytest-forked))
449 $(eval $(call disable-pytest-plugin,pytest_httpserver,pytest-httpserver))
450 $(eval $(call disable-pytest-plugin,pytest_ignore_flaky,pytest-ignore-flaky))
451 $(eval $(call disable-pytest-plugin,pytest_mock,pytest-mock))
0f943a 452 $(eval $(call disable-pytest-plugin,randomly,pytest-randomly))        # reorders tests
ed465b 453 $(eval $(call disable-pytest-plugin,regressions,pytest-regressions))
95dfd1 454 $(eval $(call disable-pytest-plugin,relaxed,pytest-relaxed))        # runs extra test(s); produces different test report
40cc3a 455 $(eval $(call disable-pytest-plugin,reporter,pytest-reporter))        # https://github.com/christiansandberg/pytest-reporter/issues/8
ed465b 456 $(eval $(call disable-pytest-plugin,rerunfailures,pytest-rerunfailures))
af147f 457 $(eval $(call disable-pytest-plugin,salt-factories,pytest-salt-factories))            # requires salt
MT 458 $(eval $(call disable-pytest-plugin,salt-factories-event-listener,pytest-salt-factories))    # requires salt
459 $(eval $(call disable-pytest-plugin,salt-factories-factories,pytest-salt-factories))        # requires salt
460 $(eval $(call disable-pytest-plugin,salt-factories-loader-mock,pytest-salt-factories))        # requires salt
461 $(eval $(call disable-pytest-plugin,salt-factories-log-server,pytest-salt-factories))        # requires salt
462 $(eval $(call disable-pytest-plugin,salt-factories-markers,pytest-salt-factories))        # requires salt
463 $(eval $(call disable-pytest-plugin,salt-factories-sysinfo,pytest-salt-factories))        # requires salt
464 $(eval $(call disable-pytest-plugin,salt-factories-sysstats,pytest-salt-factories))        # requires salt
ed465b 465 $(eval $(call disable-pytest-plugin,shell-utilities,pytest-shell-utilities))
MT 466 $(eval $(call disable-pytest-plugin,skip-markers,pytest-skip-markers))
467 $(eval $(call disable-pytest-plugin,socket,pytest-socket))
468 $(eval $(call disable-pytest-plugin,subprocess,pytest-subprocess))
469 $(eval $(call disable-pytest-plugin,subtests,pytest-subtests))
1a8beb 470 $(eval $(call disable-pytest-plugin,tempdir,pytest-tempdir))        # adds line to test report header
ed465b 471 $(eval $(call disable-pytest-plugin,time_machine,time-machine))
MT 472 $(eval $(call disable-pytest-plugin,timeout,pytest-timeout))
473 $(eval $(call disable-pytest-plugin,travis-fold,pytest-travis-fold))
474 $(eval $(call disable-pytest-plugin,typeguard,typeguard))
475 $(eval $(call disable-pytest-plugin,unittest_mock,backports-unittest-mock))
476 $(eval $(call disable-pytest-plugin,xdist,pytest-xdist))
477 $(eval $(call disable-pytest-plugin,xdist.looponfail,pytest-xdist))
af147f 478 $(eval $(call disable-pytest-plugin,xprocess,pytest-xprocess))        # adds a reminder line to test output
7d6724 479
18da4f 480 # By default we are not interested in full list of test failures so exit on
MT 481 # first failure to save time.  This could be easily overridden from environment
482 # if needed (for example to debug test failures) or in per-component Makefile.
483 PYTEST_FASTFAIL = -x
484 PYTEST_ADDOPTS += $(PYTEST_FASTFAIL)
485
ae8210 486 # Normalize pytest test results.  The pytest framework could be used either
MT 487 # directly or via tox or setup.py so add these transforms for all test styles
488 # unconditionally.
34664f 489 COMPONENT_TEST_TRANSFORMS += \
MT 490     "-e 's/^\(platform sunos5 -- Python \)$(shell echo $(PYTHON_VERSION) | $(GSED) -e 's/\./\\./g')\.[0-9]\{1,\}.*\( -- .*\)/\1\$$(PYTHON_VERSION).X\2/'"
ae8210 491 COMPONENT_TEST_TRANSFORMS += "-e '/^Using --randomly-seed=[0-9]\{1,\}$$/d'"    # this is random
MT 492 COMPONENT_TEST_TRANSFORMS += "-e '/^plugins: /d'"                # order of listed plugins could vary
c44dc0 493 COMPONENT_TEST_TRANSFORMS += "-e '/^-\{1,\} coverage: /,/^$$/d'"        # remove coverage report
27b18a 494 # sort list of pytest unit tests and drop percentage
MT 495 COMPONENT_TEST_TRANSFORMS += \
08f372 496     "| ( \
MT 497         $(GSED) -u -e '/^=\{1,\} test session starts /q' ; \
498         $(GSED) -u -e '/^$$/q' ; \
a99182 499         $(GSED) -u -e 's/ *\[...%\]$$//' -e '/^$$/Q' | $(SORT) | $(NAWK) '{print}END{if(NR>0)printf(\"\\\\n\")}' ; \
08f372 500         $(CAT) \
MT 501     ) | $(COMPONENT_TEST_TRANSFORMER)"
6dfcfa 502 COMPONENT_TEST_TRANSFORMS += \
b4e51a 503     "-e 's/^=\{1,\} \(.*\) in [0-9]\{1,\}\.[0-9]\{1,\}s \(([^)]*) \)\?=\{1,\}$$/======== \1 ========/'"    # remove timing
a033e0 504 # Remove slowest durations report for projects that run pytest with --durations option
0abb3a 505 COMPONENT_TEST_TRANSFORMS += "-e '/^=\{1,\} slowest [0-9]\{1,\} durations =\{1,\}$$/,/^=/{/^=/!d}'"
MT 506 # Remove short test summary info for projects that run pytest with -r option
507 COMPONENT_TEST_TRANSFORMS += "-e '/^=\{1,\} short test summary info =\{1,\}$$/,/^=/{/^=/!d}'"
ae8210 508
3d923b 509 # Normalize test results produced by pytest-benchmark
MT 510 COMPONENT_TEST_TRANSFORMS += \
511     $(if $(filter library/python/pytest-benchmark-$(subst .,,$(PYTHON_VERSION)), $(REQUIRED_PACKAGES) $(TEST_REQUIRED_PACKAGES)),"| ( \
512         $(GSED) -e '/^-\{1,\} benchmark/,/^=/{/^=/!d}' \
513     ) | $(COMPONENT_TEST_TRANSFORMER) -e ''")
514
d381c2 515 # Normalize test results produced by pytest-xdist
MT 516 COMPONENT_TEST_TRANSFORMS += \
517     $(if $(filter library/python/pytest-xdist-$(subst .,,$(PYTHON_VERSION)), $(REQUIRED_PACKAGES) $(TEST_REQUIRED_PACKAGES)),"| ( \
518         $(GSED) -u \
519             -e '/^created: .* workers$$/d' \
520             -e 's/^[0-9]\{1,\}\( workers \[[0-9]\{1,\} items\]\)$$/X\1/' \
521             -e '/^scheduling tests via /q' ; \
522         $(GSED) -u -e '/^$$/q' ; \
523         $(GSED) -u -n -e '/^\[gw/p' -e '/^$$/Q' | ( $(GSED) \
524             -e 's/^\[gw[0-9]\{1,\}\] \[...%\] //' \
525             -e 's/ *$$//' \
526             -e 's/\([^ ]\{1,\}\) \(.*\)$$/\2 \1/' \
527             | $(SORT) | $(NAWK) '{print}END{if(NR>0)printf(\"\\\\n\")}' ; \
528         ) ; \
529         $(CAT) \
530     ) | $(COMPONENT_TEST_TRANSFORMER) -e ''")
531
ce8823 532 # Normalize setup.py test results.  The setup.py testing could be used either
MT 533 # directly or via tox so add these transforms for all test styles
534 # unconditionally.
535 COMPONENT_TEST_TRANSFORMS += "-e '/SetuptoolsDeprecationWarning:/,+1d'"        # depends on Python version and is useless
9f6a07 536 COMPONENT_TEST_TRANSFORMS += "-e 's/^\(Ran [0-9]\{1,\} tests\{0,1\}\) in .*$$/\1/'"    # delete timing from test results
b3a428 537
MT 538 COMPONENT_TEST_DIR = $(@D)$(COMPONENT_SUBDIR:%=/%)
ce8823 539
59c102 540 # test the built source
8d70f8 541 $(BUILD_DIR)/%/.tested-and-compared:    $(COMPONENT_TEST_DEP)
237543 542     $(RM) -rf $(COMPONENT_TEST_BUILD_DIR)
AP 543     $(MKDIR) $(COMPONENT_TEST_BUILD_DIR)
59c102 544     $(COMPONENT_PRE_TEST_ACTION)
8d70f8 545     -(cd $(COMPONENT_TEST_DIR) ; \
381aab 546         $(COMPONENT_TEST_ENV_CMD) $(COMPONENT_TEST_ENV) \
792915 547         $(COMPONENT_TEST_CMD) \
MT 548         $(COMPONENT_TEST_ARGS) $(COMPONENT_TEST_TARGETS)) \
8d70f8 549         &> $(COMPONENT_TEST_OUTPUT)
59c102 550     $(COMPONENT_POST_TEST_ACTION)
8d70f8 551     $(COMPONENT_TEST_CREATE_TRANSFORMS)
RB 552     $(COMPONENT_TEST_PERFORM_TRANSFORM)
553     $(COMPONENT_TEST_COMPARE)
554     $(COMPONENT_TEST_CLEANUP)
555     $(TOUCH) $@
556
89aae0 557 $(BUILD_DIR)/%/.tested:    SHELLOPTS=pipefail
8d70f8 558 $(BUILD_DIR)/%/.tested:    $(COMPONENT_TEST_DEP)
89aae0 559     $(RM) -rf $(COMPONENT_TEST_BUILD_DIR)
MT 560     $(MKDIR) $(COMPONENT_TEST_BUILD_DIR)
8d70f8 561     $(COMPONENT_PRE_TEST_ACTION)
RB 562     (cd $(COMPONENT_TEST_DIR) ; \
381aab 563         $(COMPONENT_TEST_ENV_CMD) $(COMPONENT_TEST_ENV) \
792915 564         $(COMPONENT_TEST_CMD) \
89aae0 565         $(COMPONENT_TEST_ARGS) $(COMPONENT_TEST_TARGETS)) \
MT 566         |& $(TEE) $(COMPONENT_TEST_OUTPUT)
8d70f8 567     $(COMPONENT_POST_TEST_ACTION)
89aae0 568     $(COMPONENT_TEST_CREATE_TRANSFORMS)
MT 569     $(COMPONENT_TEST_PERFORM_TRANSFORM)
8d70f8 570     $(COMPONENT_TEST_CLEANUP)
59c102 571     $(TOUCH) $@
7999b2 572
9b979e 573 ifeq ($(strip $(SINGLE_PYTHON_VERSION)),no)
MT 574 # Temporarily create symlinks for renamed binaries
575 COMPONENT_PRE_TEST_ACTION += \
576     for f in $(PROTOUSRBINDIR)/*-$(PYTHON_VERSION) ; do \
577         [ -f $$f ] || continue ; \
578         [ -e $${f%%-$(PYTHON_VERSION)} ] && continue ; \
579         $(SYMLINK) $$(basename $$f) $${f%%-$(PYTHON_VERSION)} ; \
580     done ;
581
582 # Cleanup of temporary symlinks
583 COMPONENT_POST_TEST_ACTION += \
584     for f in $(PROTOUSRBINDIR)/*-$(PYTHON_VERSION) ; do \
585         [ -f $$f ] || continue ; \
586         [ ! -L $${f%%-$(PYTHON_VERSION)} ] || $(RM) $${f%%-$(PYTHON_VERSION)} ; \
587     done ;
588 endif
589
053684 590
e537d3 591 ifeq ($(strip $(SINGLE_PYTHON_VERSION)),no)
053684 592 # We need to add -$(PYV) to package fmri
MT 593 GENERATE_EXTRA_CMD += | \
e537d3 594     $(GSED) -e 's/^\(set name=pkg.fmri [^@]*\)\(.*\)$$/\1-$$(PYV)\2/'
MT 595 endif
053684 596
095208 597 # Add runtime dependencies from project metadata to generated manifest
MT 598 GENERATE_EXTRA_DEPS += $(BUILD_DIR)/META.depend-runtime.res
599 GENERATE_EXTRA_CMD += | \
600     $(CAT) - <( \
601         echo "" ; \
82fd1e 602         echo "\# python modules are unusable without python runtime binary" ; \
MT 603         echo "depend type=require fmri=__TBD pkg.debug.depend.file=python\$$(PYVER) \\" ; \
604         echo "    pkg.debug.depend.path=usr/bin" ; \
605         echo "" ; \
095208 606         echo "\# Automatically generated dependencies based on distribution metadata" ; \
MT 607         $(CAT) $(BUILD_DIR)/META.depend-runtime.res \
608     )
609
610 # Add runtime dependencies from project metadata to REQUIRED_PACKAGES
611 REQUIRED_PACKAGES_RESOLVED += $(BUILD_DIR)/META.depend-runtime.res
612
613
614 # Generate raw lists of runtime dependencies per Python version
615 COMPONENT_POST_INSTALL_ACTION += \
06c6b6 616     PYTHONPATH=$(PROTO_DIR)/$(PYTHON_DIR)/site-packages:$(PROTO_DIR)/$(PYTHON_LIB) \
dfab6b 617         $(PYTHON) $(WS_TOOLS)/python-requires $(COMPONENT_NAME) >> $(@D)/.depend-runtime ;
095208 618
MT 619 # Convert raw per version lists of runtime dependencies to single resolved
06c6b6 620 # runtime dependency list.  The dependency on META.depend-test.required here is
MT 621 # purely to get the file created as a side effect of this target.
622 $(BUILD_DIR)/META.depend-runtime.res:    $(INSTALL_$(MK_BITS)) $(BUILD_DIR)/META.depend-test.required
a99182 623     $(CAT) $(INSTALL_$(MK_BITS):%.installed=%.depend-runtime) | $(SORT) -u \
095208 624         | $(GSED) -e 's/.*/depend type=require fmri=pkg:\/library\/python\/&-$$(PYV)/' > $@
MT 625
dfab6b 626 # Generate raw lists of test dependencies per Python version
MT 627 COMPONENT_POST_INSTALL_ACTION += \
b3a428 628     cd $(@D)$(COMPONENT_SUBDIR:%=/%) ; \
9d43f1 629     ( for f in $(TEST_REQUIREMENTS) ; do \
ace7a9 630         $(CAT) $$f | $(DOS2UNIX) -ascii ; \
9d43f1 631     done ; \
MT 632     for e in $(TEST_REQUIREMENTS_EXTRAS) ; do \
633         PYTHONPATH=$(PROTO_DIR)/$(PYTHON_DIR)/site-packages:$(PROTO_DIR)/$(PYTHON_LIB) \
634             $(PYTHON) $(WS_TOOLS)/python-requires $(COMPONENT_NAME) $$e ; \
635     done ) | $(WS_TOOLS)/python-resolve-deps \
dfab6b 636         PYTHONPATH=$(PROTO_DIR)/$(PYTHON_DIR)/site-packages:$(PROTO_DIR)/$(PYTHON_LIB) \
MT 637         $(PYTHON) $(WS_TOOLS)/python-requires $(COMPONENT_NAME) \
638     | $(PYTHON) $(WS_TOOLS)/python-requires - >> $(@D)/.depend-test ;
639
06c6b6 640 # Convert raw per version lists of test dependencies to single list of
MT 641 # TEST_REQUIRED_PACKAGES entries
642 $(BUILD_DIR)/META.depend-test.required:    $(INSTALL_$(MK_BITS))
a99182 643     $(CAT) $(INSTALL_$(MK_BITS):%.installed=%.depend-test) | $(SORT) -u \
06c6b6 644         | $(GSED) -e 's/.*/TEST_REQUIRED_PACKAGES.python += library\/python\/&/' > $@
MT 645
646 # Add META.depend-test.required to the generated list of REQUIRED_PACKAGES
647 REQUIRED_PACKAGES_TRANSFORM += -e '$$r $(BUILD_DIR)/META.depend-test.required'
648
c49523 649 # The python-requires script requires packaging to provide useful output but
6b1855 650 # packaging might be unavailable during bootstrap until we reach bootstrap
MT 651 # checkpoint 2.  So require it conditionally.
652 ifeq ($(filter $(strip $(COMPONENT_NAME)),$(PYTHON_BOOTSTRAP_CHECKPOINT_2)),)
eb37ff 653 USERLAND_REQUIRED_PACKAGES.python += library/python/packaging
c49523 654 endif
MT 655
053684 656
35a012 657 clean::
NJ 658     $(RM) -r $(SOURCE_DIR) $(BUILD_DIR)
6005c4 659
c437ed 660 # Make it easy to construct a URL for a pypi source download.
8beffa 661 pypi_url_multi = pypi:///$(COMPONENT_NAME_$(1))==$(COMPONENT_VERSION_$(1))
DD 662 pypi_url_single = pypi:///$(COMPONENT_NAME)==$(COMPONENT_VERSION)
663 pypi_url = $(if $(COMPONENT_NAME_$(1)),$(pypi_url_multi),$(pypi_url_single))