fritzkink
2023-11-30 18c651518f1313d0881e7cdd4e5fdb3283477420
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)"
ba2ab2 155 PYTHON_ENV +=    PKG_CONFIG_PATH="$(PKG_CONFIG_PATH)"
ad2fa3 156
NJ 157 COMPONENT_BUILD_ENV += $(PYTHON_ENV)
158 COMPONENT_INSTALL_ENV += $(PYTHON_ENV)
159 COMPONENT_TEST_ENV += $(PYTHON_ENV)
fa74c0 160
ea207c 161 # Set CARGO_HOME to make sure projects built using rust (for example via
MT 162 # setuptools-rust) do not pollute user's home directory with cargo bits.
163 COMPONENT_BUILD_ENV += CARGO_HOME=$(@D)/.cargo
164
6aefa3 165 # Make sure the default Python version is installed last and so is the
MT 166 # canonical version.  This is needed for components that keep PYTHON_VERSIONS
167 # set to more than single value, but deliver unversioned binaries in usr/bin or
168 # other overlapping files.
169 define python-order-rule
170 $(BUILD_DIR)/%-$(PYTHON_VERSION)/.installed:    $(BUILD_DIR)/%-$(1)/.installed
171 endef
172 $(foreach pyver,$(filter-out $(PYTHON_VERSION),$(PYTHON_VERSIONS)),$(eval $(call python-order-rule,$(pyver))))
173
381aab 174 # We need to copy the source dir to avoid its modification by install target
MT 175 # where egg-info is re-generated
176 CLONEY_ARGS = CLONEY_MODE="copy"
a7216a 177
115d4d 178 COMPONENT_BUILD_CMD = $(PYTHON) setup.py --no-user-cfg build $(COMPONENT_BUILD_SETUP_PY_ARGS)
7589de 179
9c75c0 180 # build the configured source
381aab 181 $(BUILD_DIR)/%/.built:    $(SOURCE_DIR)/.prep
9c75c0 182     $(RM) -r $(@D) ; $(MKDIR) $(@D)
381aab 183     $(ENV) $(CLONEY_ARGS) $(CLONEY) $(SOURCE_DIR) $(@D)
9c75c0 184     $(COMPONENT_PRE_BUILD_ACTION)
b3a428 185     (cd $(@D)$(COMPONENT_SUBDIR:%=/%) ; $(ENV) $(COMPONENT_BUILD_ENV) \
7589de 186         $(COMPONENT_BUILD_CMD) $(COMPONENT_BUILD_ARGS))
9c75c0 187     $(COMPONENT_POST_BUILD_ACTION)
NJ 188     $(TOUCH) $@
189
7589de 190
MT 191 COMPONENT_INSTALL_CMD = $(PYTHON) setup.py --no-user-cfg install
ff17eb 192
e83e52 193 COMPONENT_INSTALL_ARGS +=    --root $(PROTO_DIR) 
NJ 194 COMPONENT_INSTALL_ARGS +=    --install-lib=$(PYTHON_LIB)
fa74c0 195 COMPONENT_INSTALL_ARGS +=    --install-data=$(PYTHON_DATA)
381aab 196 COMPONENT_INSTALL_ARGS +=    --skip-build
6aefa3 197 COMPONENT_INSTALL_ARGS +=    --force
e83e52 198
9c75c0 199 # install the built source into a prototype area
381aab 200 $(BUILD_DIR)/%/.installed:    $(BUILD_DIR)/%/.built
9c75c0 201     $(COMPONENT_PRE_INSTALL_ACTION)
b3a428 202     (cd $(@D)$(COMPONENT_SUBDIR:%=/%) ; $(ENV) $(COMPONENT_INSTALL_ENV) \
7589de 203         $(COMPONENT_INSTALL_CMD) $(COMPONENT_INSTALL_ARGS))
9c75c0 204     $(COMPONENT_POST_INSTALL_ACTION)
NJ 205     $(TOUCH) $@
e83e52 206
e537d3 207 ifeq ($(strip $(SINGLE_PYTHON_VERSION)),no)
053684 208 # Rename binaries in /usr/bin to contain version number
MT 209 COMPONENT_POST_INSTALL_ACTION += \
210     for f in $(PROTOUSRBINDIR)/* ; do \
9b979e 211         [ -f $$f ] || continue ; \
053684 212         for v in $(PYTHON_VERSIONS) ; do \
9b979e 213             [ "$$f" == "$${f%%$$v}" ] || continue 2 ; \
053684 214         done ; \
MT 215         $(MV) $$f $$f-$(PYTHON_VERSION) ; \
216     done ;
e537d3 217 endif
053684 218
dfab6b 219 # Remove any previous dependency files
MT 220 COMPONENT_POST_INSTALL_ACTION +=    $(RM) $(@D)/.depend-runtime $(@D)/.depend-test ;
06c6b6 221
17a961 222 # Define Python version specific filenames for tests.
30eb16 223 ifeq ($(strip $(USE_COMMON_TEST_MASTER)),no)
17a961 224 COMPONENT_TEST_MASTER =    $(COMPONENT_TEST_RESULTS_DIR)/results-$(PYTHON_VERSION).master
30eb16 225 endif
5d7921 226 COMPONENT_TEST_BUILD_DIR = $(BUILD_DIR)/test-$(PYTHON_VERSION)
17a961 227 COMPONENT_TEST_OUTPUT =    $(COMPONENT_TEST_BUILD_DIR)/test-$(PYTHON_VERSION)-results
MT 228 COMPONENT_TEST_DIFFS =    $(COMPONENT_TEST_BUILD_DIR)/test-$(PYTHON_VERSION)-diffs
229 COMPONENT_TEST_SNAPSHOT = $(COMPONENT_TEST_BUILD_DIR)/results-$(PYTHON_VERSION).snapshot
230 COMPONENT_TEST_TRANSFORM_CMD = $(COMPONENT_TEST_BUILD_DIR)/transform-$(PYTHON_VERSION)-results
8d70f8 231
34664f 232 # Generic transforms for Python test results.
MT 233 # See below for test style specific transforms.
234 COMPONENT_TEST_TRANSFORMS += "-e 's|$(PYTHON_DIR)|\$$(PYTHON_DIR)|g'"
848948 235
dcf6a8 236 # Make sure the test environment is prepared before we start tests
06c6b6 237 COMPONENT_TEST_DEP +=    component-test-environment-prep
dcf6a8 238 # Testing depends on install target because we want to test installed modules
MT 239 COMPONENT_TEST_DEP +=    $(BUILD_DIR)/%/.installed
240 # Point Python to the proto area so it is able to find installed modules there
241 COMPONENT_TEST_ENV +=    PYTHONPATH=$(PROTO_DIR)/$(PYTHON_LIB)
9b979e 242 # Make sure testing is able to find own installed executables (if any)
MT 243 COMPONENT_TEST_ENV +=    PATH=$(PROTOUSRBINDIR):$(PATH)
59c102 244
8d70f8 245 # determine the type of tests we want to run.
RB 246 ifeq ($(strip $(wildcard $(COMPONENT_TEST_RESULTS_DIR)/results-*.master)),)
44f1c2 247 TEST_32 = $(PYTHON_32_VERSIONS:%=$(BUILD_DIR)/$(MACH32)-%/.tested)
8d70f8 248 TEST_64 = $(PYTHON_VERSIONS:%=$(BUILD_DIR)/$(MACH64)-%/.tested)
RB 249 TEST_NO_ARCH = $(PYTHON_VERSIONS:%=$(BUILD_DIR)/$(MACH)-%/.tested)
250 else
44f1c2 251 TEST_32 = $(PYTHON_32_VERSIONS:%=$(BUILD_DIR)/$(MACH32)-%/.tested-and-compared)
8d70f8 252 TEST_64 = $(PYTHON_VERSIONS:%=$(BUILD_DIR)/$(MACH64)-%/.tested-and-compared)
RB 253 TEST_NO_ARCH = $(PYTHON_VERSIONS:%=$(BUILD_DIR)/$(MACH)-%/.tested-and-compared)
254 endif
255
381aab 256 #
792915 257 # Testing in the Python world is complex.  Python projects usually do not
MT 258 # support Makefile with common 'check' or 'test' target to get built bits
259 # tested.
381aab 260 #
792915 261 # De facto standard way to test Python projects these days is tox which is
MT 262 # designed and used primarily for release testing; to make sure the released
263 # python project runs on all supported Python versions, platforms, etc.  tox
264 # does so using virtualenv and creates isolated test environments where the
265 # tested package together with all its dependencies is automatically installed
266 # (using pip) and tested.  This is great for Python projects developers but it
267 # is hardly usable for operating system distributions like OpenIndiana.
381aab 268 #
792915 269 # We do not need such release testing.  Instead we need something closer to
MT 270 # integration testing: we need to test the built component in our real
271 # environment without automatic installation of any dependencies using pip.  In
272 # addition, we need to run tests only for Python versions we actually support
273 # and the component is built for.
381aab 274 #
792915 275 # To achieve that we do few things.  First, to avoid isolated environments
MT 276 # (virtualenv) we run tox with the tox-current-env plugin.  Second, to test
277 # only Python versions we are interested in we use -e option for tox to select
278 # single Python version only.  Since we run separate test target per Python
279 # version this will make sure we test all needed Python versions.
280 #
25e842 281 # The tox tool itself uses some other tools under the hood to run tests, for
MT 282 # example pytest.  Some projects could even support pytest testing directly
283 # without support for tox.  For such projects we offer separate "pytest"-style
284 # testing.
285 #
286 # For projects that do not support testing using neither tox nor pytest we
855acc 287 # offer either unittest or (deprecated) "setup.py test" testing too.
25e842 288 #
MT 289 # The TEST_STYLE variable is used to select (or force) particular test style
290 # for Python projects.  Valid values are:
291 #
292 #     tox        - "tox"-style testing
293 #     pytest        - "pytest"-style testing
855acc 294 #     unittest    - "unittest"-style testing
25e842 295 #     setup.py    - "setup.py test"-style testing
7c8106 296 #     none        - no testing is supported (or desired) at all
792915 297 #
MT 298
299 TEST_STYLE ?= tox
300 ifeq ($(strip $(TEST_STYLE)),tox)
9b979e 301 # tox needs PATH environment variable - see https://github.com/tox-dev/tox/issues/2538
MT 302 # We already added it to the test environment - see above
3db057 303 COMPONENT_TEST_ENV +=        PYTEST_ADDOPTS="$(PYTEST_ADDOPTS)"
dd736d 304 COMPONENT_TEST_ENV +=        NOSE_VERBOSE=2
792915 305 COMPONENT_TEST_CMD =        $(TOX)
81789e 306 COMPONENT_TEST_ARGS =        --current-env --no-provision
e7d6fe 307 COMPONENT_TEST_ARGS +=        --recreate
b621ca 308 COMPONENT_TEST_ARGS +=        $(TOX_TESTENV)
MT 309 COMPONENT_TEST_TARGETS =
310
ccec59 311 TOX_TESTENV = -e py$(subst .,,$(PYTHON_VERSION))
792915 312
83badd 313 # Make sure following tools are called indirectly to properly support tox-current-env
573ee5 314 TOX_CALL_INDIRECTLY += py.test
051a62 315 TOX_CALL_INDIRECTLY += pytest
MT 316 TOX_CALL_INDIRECTLY += coverage
1eb2df 317 TOX_CALL_INDIRECTLY += zope-testrunner
MT 318 TOX_CALL_INDIRECTLY.zope-testrunner = zope.testrunner
319 TOX_CALL_INDIRECTLY += sphinx-build
320 TOX_CALL_INDIRECTLY.sphinx-build = sphinx.cmd.build
dd736d 321 TOX_CALL_INDIRECTLY += nosetests
MT 322 TOX_CALL_INDIRECTLY.nosetests = nose
1eb2df 323 $(foreach indirectly, $(TOX_CALL_INDIRECTLY), $(eval TOX_CALL_INDIRECTLY.$(indirectly) ?= $(indirectly)))
MT 324 COMPONENT_PRE_TEST_ACTION += COMPONENT_TEST_DIR=$(COMPONENT_TEST_DIR) ;
950890 325 COMPONENT_PRE_TEST_ACTION += \
1eb2df 326     $(foreach indirectly, $(TOX_CALL_INDIRECTLY), \
MT 327         [ -f $$COMPONENT_TEST_DIR/tox.ini ] && \
a67d39 328             $(GSED) -i -e '/^commands *=/,/^$$/{ \
MT 329                 s/^\(\(commands *=\)\{0,1\}[ \t]*\)'$(indirectly)'\([ \t]\{1,\}.*\)\{0,1\}$$/\1python -m '$(TOX_CALL_INDIRECTLY.$(indirectly))'\3/ \
330             }' $$COMPONENT_TEST_DIR/tox.ini ; \
1eb2df 331     )
MT 332 COMPONENT_PRE_TEST_ACTION += true ;
ae4a72 333
792915 334 # Normalize tox test results.
ccec59 335 COMPONENT_TEST_TRANSFORMS += "-e 's/py$(subst .,,$(PYTHON_VERSION))/py\$$(PYV)/g'"    # normalize PYV
34664f 336 COMPONENT_TEST_TRANSFORMS += "-e '/^py\$$(PYV) installed:/d'"        # depends on set of installed packages
MT 337 COMPONENT_TEST_TRANSFORMS += "-e '/PYTHONHASHSEED/d'"            # this is random
792915 338
a3f365 339 # Normalize zope.testrunner test results
8937be 340 COMPONENT_TEST_TRANSFORMS += \
MT 341     "-e 's/ in \([0-9]\{1,\} minutes \)\{0,1\}[0-9]\{1,\}\.[0-9]\{3\} seconds//'"    # timing
a3f365 342
5cfe42 343 # Remove timing for tox 4 test results
MT 344 COMPONENT_TEST_TRANSFORMS += "-e 's/^\(  py\$$(PYV): OK\) (.* seconds)$$/\1/'"
345 COMPONENT_TEST_TRANSFORMS += "-e 's/^\(  congratulations :)\) (.* seconds)$$/\1/'"
346
22f684 347 # Remove useless lines from the "coverage combine" output
3ef0e5 348 COMPONENT_TEST_TRANSFORMS += "-e '/^Combined data file \.coverage/d'"
MT 349 COMPONENT_TEST_TRANSFORMS += "-e '/^Skipping duplicate data \.coverage/d'"
22f684 350
f86079 351 # sort list of Sphinx doctest results
MT 352 COMPONENT_TEST_TRANSFORMS += \
353     "| ( \
354         $(GSED) -u -e '/^running tests\.\.\.$$/q' ; \
355         $(GSED) -u -e '/^Doctest summary/Q' \
356             | $(NAWK) '/^$$/{\$$0=\"\\\\n\"}1' ORS='|' \
357             | $(GNU_GREP) -v '^|$$' \
a99182 358             | $(SORT) \
f86079 359             | tr -d '\\\\n' | tr '|' '\\\\n' \
MT 360             | $(NAWK) '{print}END{if(NR>0)printf(\"\\\\nDoctest summary\\\\n\")}' ; \
361         $(CAT) \
362     ) | $(COMPONENT_TEST_TRANSFORMER)"
363
792915 364 # tox package together with the tox-current-env plugin is needed
b1d234 365 USERLAND_TEST_REQUIRED_PACKAGES += library/python/tox
MT 366 USERLAND_TEST_REQUIRED_PACKAGES += library/python/tox-current-env
06c6b6 367
MT 368 # Generate raw lists of test dependencies per Python version
1eea40 369 # Please note we set PATH below four times for $(COMPONENT_TEST_CMD) (aka tox)
MT 370 # to workaround https://github.com/tox-dev/tox/issues/2538
06c6b6 371 COMPONENT_POST_INSTALL_ACTION += \
b1d234 372     if [ -x "$(COMPONENT_TEST_CMD)" ] ; then \
b3a428 373         cd $(@D)$(COMPONENT_SUBDIR:%=/%) ; \
b1d234 374         echo "Testing dependencies:" ; \
b621ca 375         PATH=$(PATH) $(COMPONENT_TEST_CMD) -qq --no-provision --print-deps-to=- $(TOX_TESTENV) || exit 1 ; \
b1d234 376         echo "Testing extras:" ; \
b621ca 377         PATH=$(PATH) $(COMPONENT_TEST_CMD) -qq --no-provision --print-extras-to=- $(TOX_TESTENV) || exit 1 ; \
MT 378         ( PATH=$(PATH) $(COMPONENT_TEST_CMD) -qq --no-provision --print-deps-to=- $(TOX_TESTENV) \
b1d234 379             | $(WS_TOOLS)/python-resolve-deps \
MT 380                 PYTHONPATH=$(PROTO_DIR)/$(PYTHON_DIR)/site-packages:$(PROTO_DIR)/$(PYTHON_LIB) \
381                 $(PYTHON) $(WS_TOOLS)/python-requires $(COMPONENT_NAME) \
382             | $(PYTHON) $(WS_TOOLS)/python-requires - ; \
b621ca 383         for e in $$(PATH=$(PATH) $(COMPONENT_TEST_CMD) -qq --no-provision --print-extras-to=- $(TOX_TESTENV)) ; do \
7416d1 384             PYTHONPATH=$(PROTO_DIR)/$(PYTHON_DIR)/site-packages:$(PROTO_DIR)/$(PYTHON_LIB) \
b1d234 385                 $(PYTHON) $(WS_TOOLS)/python-requires $(COMPONENT_NAME) $$e ; \
dfab6b 386         done ) | $(GSED) -e '/^tox\(-current-env\)\?$$/d' >> $(@D)/.depend-test ; \
b1d234 387     fi ;
25e842 388 else ifeq ($(strip $(TEST_STYLE)),pytest)
MT 389 COMPONENT_TEST_CMD =        $(PYTHON) -m pytest
3db057 390 COMPONENT_TEST_ARGS =        $(PYTEST_ADDOPTS)
25e842 391 COMPONENT_TEST_TARGETS =
3db057 392
b1d234 393 USERLAND_TEST_REQUIRED_PACKAGES += library/python/pytest
855acc 394 else ifeq ($(strip $(TEST_STYLE)),unittest)
MT 395 COMPONENT_TEST_CMD =        $(PYTHON) -m unittest
396 COMPONENT_TEST_ARGS =
397 COMPONENT_TEST_ARGS +=        --verbose
398 COMPONENT_TEST_TARGETS =
25e842 399 else ifeq ($(strip $(TEST_STYLE)),setup.py)
MT 400 # Old and deprecated "setup.py test"-style testing
792915 401 COMPONENT_TEST_CMD =        $(PYTHON) setup.py
MT 402 COMPONENT_TEST_ARGS =        --no-user-cfg
403 COMPONENT_TEST_TARGETS =    test
25e842 404 else ifeq ($(strip $(TEST_STYLE)),none)
MT 405 TEST_TARGET = $(NO_TESTS)
792915 406 endif
381aab 407
3db057 408 # Run pytest verbose to get separate line per test in results output
MT 409 PYTEST_ADDOPTS += --verbose
410
808d8a 411 # Force pytest to not use colored output so the results normalization is unaffected
MT 412 PYTEST_ADDOPTS += --color=no
413
ed465b 414 # Avoid loading of unexpected pytest plugins.
40cc3a 415 define disable-pytest-plugin
ed465b 416 PYTEST_ADDOPTS += $$(if $$(filter library/python/$(2)-$$(subst .,,$$(PYTHON_VERSION)), $$(REQUIRED_PACKAGES) $$(TEST_REQUIRED_PACKAGES) $$(COMPONENT_FMRI)-$$(subst .,,$$(PYTHON_VERSION))),,-p 'no:$(1)')
40cc3a 417 endef
ed465b 418 $(eval $(call disable-pytest-plugin,anyio,anyio))
1595bc 419 $(eval $(call disable-pytest-plugin,asyncio,pytest-asyncio))        # adds line to test report header
MT 420 $(eval $(call disable-pytest-plugin,benchmark,pytest-benchmark))    # adds line to test report header; adds benchmark report
0f943a 421 $(eval $(call disable-pytest-plugin,black,pytest-black))        # runs extra test(s)
ed465b 422 $(eval $(call disable-pytest-plugin,check,pytest-check))
0f943a 423 $(eval $(call disable-pytest-plugin,checkdocs,pytest-checkdocs))    # runs extra test(s)
ed465b 424 $(eval $(call disable-pytest-plugin,console-scripts,pytest-console-scripts))
40cc3a 425 $(eval $(call disable-pytest-plugin,cov,pytest-cov))
ed465b 426 $(eval $(call disable-pytest-plugin,custom_exit_code,pytest-custom-exit-code))
MT 427 $(eval $(call disable-pytest-plugin,env,pytest-env))
428 $(eval $(call disable-pytest-plugin,faker,faker))
429 $(eval $(call disable-pytest-plugin,flake8,pytest-flake8))
40cc3a 430 $(eval $(call disable-pytest-plugin,flaky,flaky))
ed465b 431 $(eval $(call disable-pytest-plugin,freezegun,pytest-freezegun))
MT 432 $(eval $(call disable-pytest-plugin,freezer,pytest-freezer))
433 $(eval $(call disable-pytest-plugin,helpers_namespace,pytest-helpers-namespace))
1595bc 434 $(eval $(call disable-pytest-plugin,hypothesispytest,hypothesis))    # adds line to test report header
ed465b 435 $(eval $(call disable-pytest-plugin,jaraco.test.http,jaraco-test))
MT 436 $(eval $(call disable-pytest-plugin,kgb,kgb))
437 $(eval $(call disable-pytest-plugin,lazy-fixture,pytest-lazy-fixture))
140fab 438 $(eval $(call disable-pytest-plugin,metadata,pytest-metadata))        # adds line to test report header
0f943a 439 $(eval $(call disable-pytest-plugin,mypy,pytest-mypy))            # runs extra test(s)
MT 440 $(eval $(call disable-pytest-plugin,perf,pytest-perf))            # https://github.com/jaraco/pytest-perf/issues/9
ed465b 441 $(eval $(call disable-pytest-plugin,plugin-enabled options,pytest-enabler))
MT 442 $(eval $(call disable-pytest-plugin,pytest-datadir,pytest-datadir))
a38a08 443 $(eval $(call disable-pytest-plugin,pytest-mypy-plugins,pytest-mypy-plugins))    # could cause tests to fail
ed465b 444 $(eval $(call disable-pytest-plugin,pytest-teamcity,teamcity-messages))
MT 445 $(eval $(call disable-pytest-plugin,pytest_expect,pytest-expect))
446 $(eval $(call disable-pytest-plugin,pytest_fakefs,pyfakefs))
447 $(eval $(call disable-pytest-plugin,pytest_forked,pytest-forked))
448 $(eval $(call disable-pytest-plugin,pytest_httpserver,pytest-httpserver))
449 $(eval $(call disable-pytest-plugin,pytest_ignore_flaky,pytest-ignore-flaky))
450 $(eval $(call disable-pytest-plugin,pytest_mock,pytest-mock))
0f943a 451 $(eval $(call disable-pytest-plugin,randomly,pytest-randomly))        # reorders tests
ed465b 452 $(eval $(call disable-pytest-plugin,regressions,pytest-regressions))
95dfd1 453 $(eval $(call disable-pytest-plugin,relaxed,pytest-relaxed))        # runs extra test(s); produces different test report
40cc3a 454 $(eval $(call disable-pytest-plugin,reporter,pytest-reporter))        # https://github.com/christiansandberg/pytest-reporter/issues/8
ed465b 455 $(eval $(call disable-pytest-plugin,rerunfailures,pytest-rerunfailures))
af147f 456 $(eval $(call disable-pytest-plugin,salt-factories,pytest-salt-factories))            # requires salt
MT 457 $(eval $(call disable-pytest-plugin,salt-factories-event-listener,pytest-salt-factories))    # requires salt
458 $(eval $(call disable-pytest-plugin,salt-factories-factories,pytest-salt-factories))        # requires salt
459 $(eval $(call disable-pytest-plugin,salt-factories-loader-mock,pytest-salt-factories))        # requires salt
460 $(eval $(call disable-pytest-plugin,salt-factories-log-server,pytest-salt-factories))        # requires salt
461 $(eval $(call disable-pytest-plugin,salt-factories-markers,pytest-salt-factories))        # requires salt
462 $(eval $(call disable-pytest-plugin,salt-factories-sysinfo,pytest-salt-factories))        # requires salt
463 $(eval $(call disable-pytest-plugin,salt-factories-sysstats,pytest-salt-factories))        # requires salt
ed465b 464 $(eval $(call disable-pytest-plugin,shell-utilities,pytest-shell-utilities))
MT 465 $(eval $(call disable-pytest-plugin,skip-markers,pytest-skip-markers))
466 $(eval $(call disable-pytest-plugin,socket,pytest-socket))
467 $(eval $(call disable-pytest-plugin,subprocess,pytest-subprocess))
468 $(eval $(call disable-pytest-plugin,subtests,pytest-subtests))
1a8beb 469 $(eval $(call disable-pytest-plugin,tempdir,pytest-tempdir))        # adds line to test report header
ed465b 470 $(eval $(call disable-pytest-plugin,time_machine,time-machine))
MT 471 $(eval $(call disable-pytest-plugin,timeout,pytest-timeout))
472 $(eval $(call disable-pytest-plugin,travis-fold,pytest-travis-fold))
473 $(eval $(call disable-pytest-plugin,typeguard,typeguard))
474 $(eval $(call disable-pytest-plugin,unittest_mock,backports-unittest-mock))
475 $(eval $(call disable-pytest-plugin,xdist,pytest-xdist))
476 $(eval $(call disable-pytest-plugin,xdist.looponfail,pytest-xdist))
af147f 477 $(eval $(call disable-pytest-plugin,xprocess,pytest-xprocess))        # adds a reminder line to test output
7d6724 478
18da4f 479 # By default we are not interested in full list of test failures so exit on
MT 480 # first failure to save time.  This could be easily overridden from environment
481 # if needed (for example to debug test failures) or in per-component Makefile.
482 PYTEST_FASTFAIL = -x
483 PYTEST_ADDOPTS += $(PYTEST_FASTFAIL)
484
ae8210 485 # Normalize pytest test results.  The pytest framework could be used either
MT 486 # directly or via tox or setup.py so add these transforms for all test styles
487 # unconditionally.
34664f 488 COMPONENT_TEST_TRANSFORMS += \
MT 489     "-e 's/^\(platform sunos5 -- Python \)$(shell echo $(PYTHON_VERSION) | $(GSED) -e 's/\./\\./g')\.[0-9]\{1,\}.*\( -- .*\)/\1\$$(PYTHON_VERSION).X\2/'"
ae8210 490 COMPONENT_TEST_TRANSFORMS += "-e '/^Using --randomly-seed=[0-9]\{1,\}$$/d'"    # this is random
MT 491 COMPONENT_TEST_TRANSFORMS += "-e '/^benchmark: /d'"                # line with version details
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
ce8823 509 # Normalize setup.py test results.  The setup.py testing could be used either
MT 510 # directly or via tox so add these transforms for all test styles
511 # unconditionally.
512 COMPONENT_TEST_TRANSFORMS += "-e '/SetuptoolsDeprecationWarning:/,+1d'"        # depends on Python version and is useless
9f6a07 513 COMPONENT_TEST_TRANSFORMS += "-e 's/^\(Ran [0-9]\{1,\} tests\{0,1\}\) in .*$$/\1/'"    # delete timing from test results
b3a428 514
MT 515 COMPONENT_TEST_DIR = $(@D)$(COMPONENT_SUBDIR:%=/%)
ce8823 516
59c102 517 # test the built source
8d70f8 518 $(BUILD_DIR)/%/.tested-and-compared:    $(COMPONENT_TEST_DEP)
237543 519     $(RM) -rf $(COMPONENT_TEST_BUILD_DIR)
AP 520     $(MKDIR) $(COMPONENT_TEST_BUILD_DIR)
59c102 521     $(COMPONENT_PRE_TEST_ACTION)
8d70f8 522     -(cd $(COMPONENT_TEST_DIR) ; \
381aab 523         $(COMPONENT_TEST_ENV_CMD) $(COMPONENT_TEST_ENV) \
792915 524         $(COMPONENT_TEST_CMD) \
MT 525         $(COMPONENT_TEST_ARGS) $(COMPONENT_TEST_TARGETS)) \
8d70f8 526         &> $(COMPONENT_TEST_OUTPUT)
59c102 527     $(COMPONENT_POST_TEST_ACTION)
8d70f8 528     $(COMPONENT_TEST_CREATE_TRANSFORMS)
RB 529     $(COMPONENT_TEST_PERFORM_TRANSFORM)
530     $(COMPONENT_TEST_COMPARE)
531     $(COMPONENT_TEST_CLEANUP)
532     $(TOUCH) $@
533
89aae0 534 $(BUILD_DIR)/%/.tested:    SHELLOPTS=pipefail
8d70f8 535 $(BUILD_DIR)/%/.tested:    $(COMPONENT_TEST_DEP)
89aae0 536     $(RM) -rf $(COMPONENT_TEST_BUILD_DIR)
MT 537     $(MKDIR) $(COMPONENT_TEST_BUILD_DIR)
8d70f8 538     $(COMPONENT_PRE_TEST_ACTION)
RB 539     (cd $(COMPONENT_TEST_DIR) ; \
381aab 540         $(COMPONENT_TEST_ENV_CMD) $(COMPONENT_TEST_ENV) \
792915 541         $(COMPONENT_TEST_CMD) \
89aae0 542         $(COMPONENT_TEST_ARGS) $(COMPONENT_TEST_TARGETS)) \
MT 543         |& $(TEE) $(COMPONENT_TEST_OUTPUT)
8d70f8 544     $(COMPONENT_POST_TEST_ACTION)
89aae0 545     $(COMPONENT_TEST_CREATE_TRANSFORMS)
MT 546     $(COMPONENT_TEST_PERFORM_TRANSFORM)
8d70f8 547     $(COMPONENT_TEST_CLEANUP)
59c102 548     $(TOUCH) $@
7999b2 549
9b979e 550 ifeq ($(strip $(SINGLE_PYTHON_VERSION)),no)
MT 551 # Temporarily create symlinks for renamed binaries
552 COMPONENT_PRE_TEST_ACTION += \
553     for f in $(PROTOUSRBINDIR)/*-$(PYTHON_VERSION) ; do \
554         [ -f $$f ] || continue ; \
555         [ -e $${f%%-$(PYTHON_VERSION)} ] && continue ; \
556         $(SYMLINK) $$(basename $$f) $${f%%-$(PYTHON_VERSION)} ; \
557     done ;
558
559 # Cleanup of temporary symlinks
560 COMPONENT_POST_TEST_ACTION += \
561     for f in $(PROTOUSRBINDIR)/*-$(PYTHON_VERSION) ; do \
562         [ -f $$f ] || continue ; \
563         [ ! -L $${f%%-$(PYTHON_VERSION)} ] || $(RM) $${f%%-$(PYTHON_VERSION)} ; \
564     done ;
565 endif
566
053684 567
e537d3 568 ifeq ($(strip $(SINGLE_PYTHON_VERSION)),no)
053684 569 # We need to add -$(PYV) to package fmri
MT 570 GENERATE_EXTRA_CMD += | \
e537d3 571     $(GSED) -e 's/^\(set name=pkg.fmri [^@]*\)\(.*\)$$/\1-$$(PYV)\2/'
MT 572 endif
053684 573
095208 574 # Add runtime dependencies from project metadata to generated manifest
MT 575 GENERATE_EXTRA_DEPS += $(BUILD_DIR)/META.depend-runtime.res
576 GENERATE_EXTRA_CMD += | \
577     $(CAT) - <( \
578         echo "" ; \
82fd1e 579         echo "\# python modules are unusable without python runtime binary" ; \
MT 580         echo "depend type=require fmri=__TBD pkg.debug.depend.file=python\$$(PYVER) \\" ; \
581         echo "    pkg.debug.depend.path=usr/bin" ; \
582         echo "" ; \
095208 583         echo "\# Automatically generated dependencies based on distribution metadata" ; \
MT 584         $(CAT) $(BUILD_DIR)/META.depend-runtime.res \
585     )
586
587 # Add runtime dependencies from project metadata to REQUIRED_PACKAGES
588 REQUIRED_PACKAGES_RESOLVED += $(BUILD_DIR)/META.depend-runtime.res
589
590
591 # Generate raw lists of runtime dependencies per Python version
592 COMPONENT_POST_INSTALL_ACTION += \
06c6b6 593     PYTHONPATH=$(PROTO_DIR)/$(PYTHON_DIR)/site-packages:$(PROTO_DIR)/$(PYTHON_LIB) \
dfab6b 594         $(PYTHON) $(WS_TOOLS)/python-requires $(COMPONENT_NAME) >> $(@D)/.depend-runtime ;
095208 595
MT 596 # Convert raw per version lists of runtime dependencies to single resolved
06c6b6 597 # runtime dependency list.  The dependency on META.depend-test.required here is
MT 598 # purely to get the file created as a side effect of this target.
599 $(BUILD_DIR)/META.depend-runtime.res:    $(INSTALL_$(MK_BITS)) $(BUILD_DIR)/META.depend-test.required
a99182 600     $(CAT) $(INSTALL_$(MK_BITS):%.installed=%.depend-runtime) | $(SORT) -u \
095208 601         | $(GSED) -e 's/.*/depend type=require fmri=pkg:\/library\/python\/&-$$(PYV)/' > $@
MT 602
dfab6b 603 # Generate raw lists of test dependencies per Python version
MT 604 COMPONENT_POST_INSTALL_ACTION += \
b3a428 605     cd $(@D)$(COMPONENT_SUBDIR:%=/%) ; \
9d43f1 606     ( for f in $(TEST_REQUIREMENTS) ; do \
ace7a9 607         $(CAT) $$f | $(DOS2UNIX) -ascii ; \
9d43f1 608     done ; \
MT 609     for e in $(TEST_REQUIREMENTS_EXTRAS) ; do \
610         PYTHONPATH=$(PROTO_DIR)/$(PYTHON_DIR)/site-packages:$(PROTO_DIR)/$(PYTHON_LIB) \
611             $(PYTHON) $(WS_TOOLS)/python-requires $(COMPONENT_NAME) $$e ; \
612     done ) | $(WS_TOOLS)/python-resolve-deps \
dfab6b 613         PYTHONPATH=$(PROTO_DIR)/$(PYTHON_DIR)/site-packages:$(PROTO_DIR)/$(PYTHON_LIB) \
MT 614         $(PYTHON) $(WS_TOOLS)/python-requires $(COMPONENT_NAME) \
615     | $(PYTHON) $(WS_TOOLS)/python-requires - >> $(@D)/.depend-test ;
616
06c6b6 617 # Convert raw per version lists of test dependencies to single list of
MT 618 # TEST_REQUIRED_PACKAGES entries
619 $(BUILD_DIR)/META.depend-test.required:    $(INSTALL_$(MK_BITS))
a99182 620     $(CAT) $(INSTALL_$(MK_BITS):%.installed=%.depend-test) | $(SORT) -u \
06c6b6 621         | $(GSED) -e 's/.*/TEST_REQUIRED_PACKAGES.python += library\/python\/&/' > $@
MT 622
623 # Add META.depend-test.required to the generated list of REQUIRED_PACKAGES
624 REQUIRED_PACKAGES_TRANSFORM += -e '$$r $(BUILD_DIR)/META.depend-test.required'
625
c49523 626 # The python-requires script requires packaging to provide useful output but
6b1855 627 # packaging might be unavailable during bootstrap until we reach bootstrap
MT 628 # checkpoint 2.  So require it conditionally.
629 ifeq ($(filter $(strip $(COMPONENT_NAME)),$(PYTHON_BOOTSTRAP_CHECKPOINT_2)),)
eb37ff 630 USERLAND_REQUIRED_PACKAGES.python += library/python/packaging
c49523 631 endif
MT 632
053684 633
35a012 634 clean::
NJ 635     $(RM) -r $(SOURCE_DIR) $(BUILD_DIR)
6005c4 636
c437ed 637 # Make it easy to construct a URL for a pypi source download.
8beffa 638 pypi_url_multi = pypi:///$(COMPONENT_NAME_$(1))==$(COMPONENT_VERSION_$(1))
DD 639 pypi_url_single = pypi:///$(COMPONENT_NAME)==$(COMPONENT_VERSION)
640 pypi_url = $(if $(COMPONENT_NAME_$(1)),$(pypi_url_multi),$(pypi_url_single))