From f24a18f45e1c71108689f94f5784dc3dd7bd06a8 Mon Sep 17 00:00:00 2001 From: rdb Date: Sat, 14 Oct 2023 09:54:42 +0200 Subject: [PATCH 01/10] tests: Add unit test for `Freezer.generateRuntimeFromStub` --- tests/dist/test_FreezeTool.py | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/tests/dist/test_FreezeTool.py b/tests/dist/test_FreezeTool.py index 898c53e7e1..c9cfa364fc 100644 --- a/tests/dist/test_FreezeTool.py +++ b/tests/dist/test_FreezeTool.py @@ -1,4 +1,6 @@ from direct.dist.FreezeTool import Freezer, PandaModuleFinder +import pytest +import os import sys @@ -62,3 +64,35 @@ def test_Freezer_getModulePath_getModuleStar(tmp_path): assert freezer.getModuleStar("module3") == ['test'] finally: sys.path = backup + + +@pytest.mark.parametrize("use_console", (False, True)) +def test_Freezer_generateRuntimeFromStub(tmp_path, use_console): + try: + # If installed as a wheel + import panda3d_tools + bin_dir = os.path.dirname(panda3d_tools.__file__) + except: + import panda3d + bin_dir = os.path.join(os.path.dirname(os.path.dirname(panda3d.__file__)), 'bin') + + if sys.platform == 'win32': + suffix = '.exe' + else: + suffix = '' + + if not use_console: + stub_file = os.path.join(bin_dir, 'deploy-stubw' + suffix) + + if use_console or not os.path.isfile(stub_file): + stub_file = os.path.join(bin_dir, 'deploy-stub' + suffix) + + if not os.path.isfile(stub_file): + pytest.skip("Unable to find deploy-stub executable") + + target = str(tmp_path / 'stubtest') + + freezer = Freezer() + freezer.addModule('__main__', 'main.py', text='print("Hello world")') + freezer.done(addStartupModules=True) + freezer.generateRuntimeFromStub(target, open(stub_file, 'rb'), use_console) From c77697a2c0bed34714511775755e3ce09b554e48 Mon Sep 17 00:00:00 2001 From: rdb Date: Sat, 14 Oct 2023 09:55:51 +0200 Subject: [PATCH 02/10] pytest: Don't skip "dist" directory in test discovery --- setup.cfg | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.cfg b/setup.cfg index c3a79e56c0..76a9a6ef21 100644 --- a/setup.cfg +++ b/setup.cfg @@ -43,3 +43,4 @@ project_urls = [tool:pytest] testpaths = tests +norecursedirs = *.egg .* _darcs build CVS node_modules venv {arch} From c1c035d5c9fd7910b6e9f94d4507eed6bd511fe3 Mon Sep 17 00:00:00 2001 From: rdb Date: Sat, 14 Oct 2023 11:22:09 +0200 Subject: [PATCH 03/10] showbase: Switch Loader entry point detection to importlib.metadata Only in Python 3.8 and up, where this module is available, otherwise it falls back to pkg_resources Add unit test for custom entry point loaders --- direct/src/showbase/Loader.py | 24 +++++--- tests/showbase/test_Loader.py | 105 +++++++++++++++++++++++++++++++++- 2 files changed, 121 insertions(+), 8 deletions(-) diff --git a/direct/src/showbase/Loader.py b/direct/src/showbase/Loader.py index 711a40f30e..0237fbe9b2 100644 --- a/direct/src/showbase/Loader.py +++ b/direct/src/showbase/Loader.py @@ -8,6 +8,7 @@ from panda3d.core import * from panda3d.core import Loader as PandaLoader from direct.directnotify.DirectNotifyGlobal import * from direct.showbase.DirectObject import DirectObject +import sys # You can specify a phaseChecker callback to check # a modelPath to see if it is being loaded in the correct @@ -167,16 +168,25 @@ class Loader(DirectObject): if not ConfigVariableBool('loader-support-entry-points', True): return - import importlib - try: - pkg_resources = importlib.import_module('pkg_resources') - except ImportError: - pkg_resources = None + if sys.version_info >= (3, 8): + from importlib.metadata import entry_points + eps = entry_points() + if isinstance(eps, dict): # Python 3.8 and 3.9 + loaders = eps.get('panda3d.loaders', ()) + else: + loaders = entry_points().select(group='panda3d.loaders') + else: + import importlib + try: + pkg_resources = importlib.import_module('pkg_resources') + loaders = pkg_resources.iter_entry_points('panda3d.loaders') + except ImportError: + loaders = () - if pkg_resources: + if loaders: registry = LoaderFileTypeRegistry.getGlobalPtr() - for entry_point in pkg_resources.iter_entry_points('panda3d.loaders'): + for entry_point in loaders: registry.register_deferred_type(entry_point) cls._loadedPythonFileTypes = True diff --git a/tests/showbase/test_Loader.py b/tests/showbase/test_Loader.py index 0dadecb384..2414e0f385 100644 --- a/tests/showbase/test_Loader.py +++ b/tests/showbase/test_Loader.py @@ -1,6 +1,7 @@ -from panda3d.core import Filename, NodePath +from panda3d.core import Filename, NodePath, LoaderFileTypeRegistry from direct.showbase.Loader import Loader import pytest +import sys @pytest.fixture @@ -68,3 +69,105 @@ def test_load_model_missing(loader): def test_load_model_okmissing(loader): model = loader.load_model('/nonexistent.bam', okMissing=True) assert model is None + + +def test_loader_entry_points(tmp_path): + # A dummy loader for .fnrgl files. + (tmp_path / "fnargle.py").write_text(""" +from panda3d.core import ModelRoot +import sys + +sys._fnargle_loaded = True + +class FnargleLoader: + name = "Fnargle" + extensions = ['fnrgl'] + supports_compressed = False + + @staticmethod + def load_file(path, options, record=None): + return ModelRoot("fnargle") +""") + (tmp_path / "fnargle.dist-info").mkdir() + (tmp_path / "fnargle.dist-info" / "METADATA").write_text(""" +Metadata-Version: 2.0 +Name: fnargle +Version: 1.0.0 +""") + (tmp_path / "fnargle.dist-info" / "entry_points.txt").write_text(""" +[panda3d.loaders] +fnrgl = fnargle:FnargleLoader +""") + + model_path = tmp_path / "test.fnrgl" + model_path.write_text("") + + if sys.version_info >= (3, 11): + import sysconfig + stdlib = sysconfig.get_path("stdlib") + platstdlib = sysconfig.get_path("platstdlib") + else: + from distutils import sysconfig + stdlib = sysconfig.get_python_lib(False, True) + platstdlib = sysconfig.get_python_lib(True, True) + + if sys.version_info < (3, 8): + # Older Python versions don't have importlib.metadata, so we rely on + # pkg_resources - but this caches the results once. Fortunately, it + # provides this function for reinitializing the cached entry points. + # See pypa/setuptools#373 + pkg_resources = pytest.importorskip("pkg_resources") + if not hasattr(pkg_resources, "_initialize_master_working_set"): + pytest.skip("pkg_resources too old") + + registry = LoaderFileTypeRegistry.get_global_ptr() + prev_loaded = Loader._loadedPythonFileTypes + prev_path = sys.path + file_type = None + try: + # We do this so we don't re-register thirdparty loaders + sys.path = [str(tmp_path), platstdlib, stdlib] + if sys.version_info < (3, 8): + pkg_resources._initialize_master_working_set() + + Loader._loadedPythonFileTypes = False + + # base parameter is only used for audio + loader = Loader(None) + assert Loader._loadedPythonFileTypes + + # Should be registered, not yet loaded + file_type = registry.get_type_from_extension('fnrgl') + assert file_type is not None + assert not hasattr(sys, '_fnargle_loaded') + + assert file_type.supports_load() + assert not file_type.supports_save() + assert not file_type.supports_compressed() + assert file_type.get_extension() == 'fnrgl' + + # The above should have caused it to load + assert sys._fnargle_loaded + assert 'fnargle' in sys.modules + + # Now try loading a fnargle file + model = loader.load_model(model_path) + assert model is not None + assert model.name == "fnargle" + + finally: + # Set everything back to what it was + Loader._loadedPythonFileTypes = prev_loaded + sys.path = prev_path + + if hasattr(sys, '_fnargle_loaded'): + del sys._fnargle_loaded + + if 'fnargle' in sys.modules: + del sys.modules['fnargle'] + + if file_type is not None: + registry.unregister_type(file_type) + + if sys.version_info < (3, 8): + pkg_resources._initialize_master_working_set() From 972c0009ac457f95a3cf75650e990d21c928322d Mon Sep 17 00:00:00 2001 From: rdb Date: Sat, 14 Oct 2023 11:28:05 +0200 Subject: [PATCH 04/10] workflow: Add setuptools as requirement when testing Python 3.12 --- .github/workflows/ci.yml | 2 +- tests/requirements.txt | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 64978eaf1c..f5c571310a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -46,7 +46,7 @@ jobs: if: matrix.os != 'windows-2019' shell: bash run: | - python -m pip install pytest + python -m pip install pytest setuptools PYTHONPATH=built LD_LIBRARY_PATH=built/lib DYLD_LIBRARY_PATH=built/lib python -m pytest - name: Set up Python 3.11 diff --git a/tests/requirements.txt b/tests/requirements.txt index 3bc9b3f390..1019c223b6 100644 --- a/tests/requirements.txt +++ b/tests/requirements.txt @@ -1,2 +1,3 @@ pytest==3.2.0 panda3d +setuptools From b2465c3b38441be9079fdf3dff5e9d919ecec664 Mon Sep 17 00:00:00 2001 From: rdb Date: Sat, 14 Oct 2023 17:31:35 +0200 Subject: [PATCH 05/10] tests: Fix running tests on Windows and with Python 2.7 --- direct/src/dist/FreezeTool.py | 4 ++++ tests/dist/test_FreezeTool.py | 19 ++++++++++--------- tests/showbase/test_Loader.py | 12 +++++++----- 3 files changed, 21 insertions(+), 14 deletions(-) diff --git a/direct/src/dist/FreezeTool.py b/direct/src/dist/FreezeTool.py index ce0aa1ad38..5d9d785e4e 100644 --- a/direct/src/dist/FreezeTool.py +++ b/direct/src/dist/FreezeTool.py @@ -1420,6 +1420,10 @@ class Freezer: fp = open(pathname, 'rb') stuff = ("", "rb", _PY_COMPILED) self.mf.load_module(mdef.moduleName, fp, pathname, stuff) + elif isinstance(mdef.text, bytes): + stuff = ("", "rb", _PY_SOURCE) + fp = io.BytesIO(mdef.text) + self.mf.load_module(mdef.moduleName, fp, pathname, stuff) else: stuff = ("", "rb", _PY_SOURCE) if mdef.text: diff --git a/tests/dist/test_FreezeTool.py b/tests/dist/test_FreezeTool.py index c9cfa364fc..31d8e05438 100644 --- a/tests/dist/test_FreezeTool.py +++ b/tests/dist/test_FreezeTool.py @@ -2,6 +2,8 @@ from direct.dist.FreezeTool import Freezer, PandaModuleFinder import pytest import os import sys +import subprocess +import platform def test_Freezer_moduleSuffixes(): @@ -16,24 +18,23 @@ def test_Freezer_getModulePath_getModuleStar(tmp_path): # Package 1 can be imported package1 = tmp_path / "package1" package1.mkdir() - (package1 / "submodule1.py").write_text("") - (package1 / "__init__.py").write_text("") + (package1 / "submodule1.py").write_text(u"") + (package1 / "__init__.py").write_text(u"") # Package 2 can not be imported package2 = tmp_path / "package2" package2.mkdir() - (package2 / "submodule2.py").write_text("") - (package2 / "__init__.py").write_text("raise ImportError\n") + (package2 / "submodule2.py").write_text(u"") + (package2 / "__init__.py").write_text(u"raise ImportError\n") # Module 1 can be imported - (tmp_path / "module1.py").write_text("") + (tmp_path / "module1.py").write_text(u"") # Module 2 can not be imported - (tmp_path / "module2.py").write_text("raise ImportError\n") + (tmp_path / "module2.py").write_text(u"raise ImportError\n") # Module 3 has a custom __path__ and __all__ - (tmp_path / "module3.py").write_text("__path__ = ['foobar']\n" - "__all__ = ['test']\n") + (tmp_path / "module3.py").write_text(u"__path__ = ['foobar']\n__all__ = ['test']\n") backup = sys.path try: @@ -90,7 +91,7 @@ def test_Freezer_generateRuntimeFromStub(tmp_path, use_console): if not os.path.isfile(stub_file): pytest.skip("Unable to find deploy-stub executable") - target = str(tmp_path / 'stubtest') + target = str(tmp_path / ('stubtest' + suffix)) freezer = Freezer() freezer.addModule('__main__', 'main.py', text='print("Hello world")') diff --git a/tests/showbase/test_Loader.py b/tests/showbase/test_Loader.py index 2414e0f385..2073803c65 100644 --- a/tests/showbase/test_Loader.py +++ b/tests/showbase/test_Loader.py @@ -73,7 +73,7 @@ def test_load_model_okmissing(loader): def test_loader_entry_points(tmp_path): # A dummy loader for .fnrgl files. - (tmp_path / "fnargle.py").write_text(""" + (tmp_path / "fnargle.py").write_text(u""" from panda3d.core import ModelRoot import sys @@ -89,18 +89,18 @@ class FnargleLoader: return ModelRoot("fnargle") """) (tmp_path / "fnargle.dist-info").mkdir() - (tmp_path / "fnargle.dist-info" / "METADATA").write_text(""" + (tmp_path / "fnargle.dist-info" / "METADATA").write_text(u""" Metadata-Version: 2.0 Name: fnargle Version: 1.0.0 """) - (tmp_path / "fnargle.dist-info" / "entry_points.txt").write_text(""" + (tmp_path / "fnargle.dist-info" / "entry_points.txt").write_text(u""" [panda3d.loaders] fnrgl = fnargle:FnargleLoader """) model_path = tmp_path / "test.fnrgl" - model_path.write_text("") + model_path.write_text(u"") if sys.version_info >= (3, 11): import sysconfig @@ -151,7 +151,9 @@ fnrgl = fnargle:FnargleLoader assert 'fnargle' in sys.modules # Now try loading a fnargle file - model = loader.load_model(model_path) + model_fn = Filename(model_path) + model_fn.make_true_case() + model = loader.load_model(model_fn) assert model is not None assert model.name == "fnargle" From f9c213fcbb8bd29c0991db02e4fc0f9c2e25ee70 Mon Sep 17 00:00:00 2001 From: rdb Date: Sat, 14 Oct 2023 17:35:16 +0200 Subject: [PATCH 06/10] tests: Run runtime executable generated by FreezeTool test --- tests/dist/test_FreezeTool.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/tests/dist/test_FreezeTool.py b/tests/dist/test_FreezeTool.py index 31d8e05438..17a23dc3ea 100644 --- a/tests/dist/test_FreezeTool.py +++ b/tests/dist/test_FreezeTool.py @@ -94,6 +94,18 @@ def test_Freezer_generateRuntimeFromStub(tmp_path, use_console): target = str(tmp_path / ('stubtest' + suffix)) freezer = Freezer() - freezer.addModule('__main__', 'main.py', text='print("Hello world")') + freezer.addModule('module2', filename='module2.py', text='print("Module imported")') + freezer.addModule('__main__', filename='main.py', text='import module2\nprint("Hello world")') + assert '__main__' in freezer.modules + freezer.done(addStartupModules=True) + assert '__main__' in dict(freezer.getModuleDefs()) + freezer.generateRuntimeFromStub(target, open(stub_file, 'rb'), use_console) + + if sys.platform == 'darwin' and platform.machine().lower() == 'arm64': + # Not supported; see #1348 + return + + output = subprocess.check_output(target) + assert output.replace(b'\r\n', b'\n') == b'Module imported\nHello world\n' From 9dce403bee16cda0545aed8b033242f84c1dc54d Mon Sep 17 00:00:00 2001 From: rdb Date: Sat, 14 Oct 2023 18:05:47 +0200 Subject: [PATCH 07/10] tinydisplay: fix missing include in zmath.h --- panda/src/tinydisplay/zmath.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/panda/src/tinydisplay/zmath.h b/panda/src/tinydisplay/zmath.h index 5296a8ad06..73fae86d6c 100644 --- a/panda/src/tinydisplay/zmath.h +++ b/panda/src/tinydisplay/zmath.h @@ -1,6 +1,8 @@ #ifndef __ZMATH__ #define __ZMATH__ +#include "numeric_types.h" + /* Matrix & Vertex */ typedef struct { From 87e69b71bf5a3335b9dbad9ca189771369925e12 Mon Sep 17 00:00:00 2001 From: rdb Date: Sat, 14 Oct 2023 19:38:22 +0200 Subject: [PATCH 08/10] workflow: Add `$pythonLocation/lib` to LD_LIBRARY_PATH for tests --- .github/workflows/ci.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f5c571310a..58aa81ba72 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -47,7 +47,7 @@ jobs: shell: bash run: | python -m pip install pytest setuptools - PYTHONPATH=built LD_LIBRARY_PATH=built/lib DYLD_LIBRARY_PATH=built/lib python -m pytest + PYTHONPATH=built LD_LIBRARY_PATH=built/lib:$pythonLocation/lib DYLD_LIBRARY_PATH=built/lib python -m pytest - name: Set up Python 3.11 uses: actions/setup-python@v4 @@ -61,7 +61,7 @@ jobs: shell: bash run: | python -m pip install pytest - PYTHONPATH=built LD_LIBRARY_PATH=built/lib DYLD_LIBRARY_PATH=built/lib python -m pytest + PYTHONPATH=built LD_LIBRARY_PATH=built/lib:$pythonLocation/lib DYLD_LIBRARY_PATH=built/lib python -m pytest - name: Set up Python 3.10 uses: actions/setup-python@v4 @@ -75,7 +75,7 @@ jobs: shell: bash run: | python -m pip install pytest - PYTHONPATH=built LD_LIBRARY_PATH=built/lib DYLD_LIBRARY_PATH=built/lib python -m pytest + PYTHONPATH=built LD_LIBRARY_PATH=built/lib:$pythonLocation/lib DYLD_LIBRARY_PATH=built/lib python -m pytest - name: Set up Python 3.9 uses: actions/setup-python@v4 @@ -89,7 +89,7 @@ jobs: shell: bash run: | python -m pip install pytest - PYTHONPATH=built LD_LIBRARY_PATH=built/lib DYLD_LIBRARY_PATH=built/lib python -m pytest + PYTHONPATH=built LD_LIBRARY_PATH=built/lib:$pythonLocation/lib DYLD_LIBRARY_PATH=built/lib python -m pytest - name: Set up Python 3.8 uses: actions/setup-python@v4 @@ -103,7 +103,7 @@ jobs: shell: bash run: | python -m pip install pytest - PYTHONPATH=built LD_LIBRARY_PATH=built/lib DYLD_LIBRARY_PATH=built/lib python -m pytest + PYTHONPATH=built LD_LIBRARY_PATH=built/lib:$pythonLocation/lib DYLD_LIBRARY_PATH=built/lib python -m pytest - name: Make installer run: | From 1db6dbc1668a297eeb5e70ed2decc942a53f0e17 Mon Sep 17 00:00:00 2001 From: rdb Date: Sat, 14 Oct 2023 21:24:22 +0200 Subject: [PATCH 09/10] tests: Don't cache loaded test model --- tests/showbase/test_Loader.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/showbase/test_Loader.py b/tests/showbase/test_Loader.py index 2073803c65..bce23d61f2 100644 --- a/tests/showbase/test_Loader.py +++ b/tests/showbase/test_Loader.py @@ -153,7 +153,7 @@ fnrgl = fnargle:FnargleLoader # Now try loading a fnargle file model_fn = Filename(model_path) model_fn.make_true_case() - model = loader.load_model(model_fn) + model = loader.load_model(model_fn, noCache=True) assert model is not None assert model.name == "fnargle" From 5e4fc7999e9b5362c2d2144704fe727abc7675f3 Mon Sep 17 00:00:00 2001 From: rdb Date: Sat, 14 Oct 2023 21:25:42 +0200 Subject: [PATCH 10/10] Switch to 1.10.14 thirdparties, enable Python 3.12 CI on Windows --- .github/workflows/ci.yml | 15 ++++++--------- README.md | 6 +++--- 2 files changed, 9 insertions(+), 12 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 58aa81ba72..990de25e94 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -20,30 +20,27 @@ jobs: shell: powershell run: | $wc = New-Object System.Net.WebClient - $wc.DownloadFile("https://www.panda3d.org/download/panda3d-1.10.13/panda3d-1.10.13-tools-win64.zip", "thirdparty-tools.zip") + $wc.DownloadFile("https://www.panda3d.org/download/panda3d-1.10.14/panda3d-1.10.14-tools-win64.zip", "thirdparty-tools.zip") Expand-Archive -Path thirdparty-tools.zip - Move-Item -Path thirdparty-tools/panda3d-1.10.13/thirdparty -Destination . + Move-Item -Path thirdparty-tools/panda3d-1.10.14/thirdparty -Destination . - name: Get thirdparty packages (macOS) if: runner.os == 'macOS' run: | - curl -O https://www.panda3d.org/download/panda3d-1.10.13/panda3d-1.10.13-tools-mac.tar.gz - tar -xf panda3d-1.10.13-tools-mac.tar.gz - mv panda3d-1.10.13/thirdparty thirdparty - rmdir panda3d-1.10.13 + curl -O https://www.panda3d.org/download/panda3d-1.10.14/panda3d-1.10.14-tools-mac.tar.gz + tar -xf panda3d-1.10.14-tools-mac.tar.gz + mv panda3d-1.10.14/thirdparty thirdparty + rmdir panda3d-1.10.14 (cd thirdparty/darwin-libs-a && rm -rf rocket) - name: Set up Python 3.12 - if: matrix.os != 'windows-2019' uses: actions/setup-python@v4 with: python-version: '3.12' - name: Build Python 3.12 - if: matrix.os != 'windows-2019' shell: bash run: | python makepanda/makepanda.py --git-commit=${{github.sha}} --outputdir=built --everything --no-eigen --python-incdir="$pythonLocation/include" --python-libdir="$pythonLocation/lib" --verbose --threads=4 --windows-sdk=10 - name: Test Python 3.12 - if: matrix.os != 'windows-2019' shell: bash run: | python -m pip install pytest setuptools diff --git a/README.md b/README.md index c697b273d2..a1983a9314 100644 --- a/README.md +++ b/README.md @@ -64,8 +64,8 @@ depending on whether you are on a 32-bit or 64-bit system, or you can [click here](https://github.com/rdb/panda3d-thirdparty) for instructions on building them from source. -- https://www.panda3d.org/download/panda3d-1.10.13/panda3d-1.10.13-tools-win64.zip -- https://www.panda3d.org/download/panda3d-1.10.13/panda3d-1.10.13-tools-win32.zip +- https://www.panda3d.org/download/panda3d-1.10.14/panda3d-1.10.14-tools-win64.zip +- https://www.panda3d.org/download/panda3d-1.10.14/panda3d-1.10.14-tools-win32.zip After acquiring these dependencies, you can build Panda3D from the command prompt using the following command. Change the `--msvc-version` option based @@ -136,7 +136,7 @@ macOS ----- On macOS, you will need to download a set of precompiled thirdparty packages in order to -compile Panda3D, which can be acquired from [here](https://www.panda3d.org/download/panda3d-1.10.13/panda3d-1.10.13-tools-mac.tar.gz). +compile Panda3D, which can be acquired from [here](https://www.panda3d.org/download/panda3d-1.10.14/panda3d-1.10.14-tools-mac.tar.gz). After placing the thirdparty directory inside the panda3d source directory, you may build Panda3D using a command like the following: