diff --git a/.github/workflows/automatic_versioning.yml b/.github/workflows/automatic_versioning.yml new file mode 100644 index 00000000..e9daaa45 --- /dev/null +++ b/.github/workflows/automatic_versioning.yml @@ -0,0 +1,126 @@ +# documentation: https://help.github.com/en/articles/workflow-syntax-for-github-actions +name: Test version for tarball without git metadata +on: [push, pull_request, workflow_dispatch] +permissions: read-all +jobs: + test_versioning_from_tarball: + # ubuntu <= 20.04 is required for python 3.6 + runs-on: ubuntu-20.04 + strategy: + fail-fast: false + matrix: + python-version: ['3.6', '3.7', '3.8', '3.9', '3.10', '3.11'] + steps: +# - name: Check out repository +# uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 +# with: +# persist-credentials: false +# fetch-depth: 0 + + - name: Set up Python + uses: actions/setup-python@61a6322f88396a6271a6ee3565807d608ecaddd1 # v4.7.0 + with: + python-version: ${{ matrix.python-version }} + + - name: Install setuptools + run: | + if [[ "${{ matrix.python-version }}" == "3.6" ]]; then + # system installed setuptools version in RHEL8 and CO7 + python -m pip install --user setuptools==39.2.0 + fi + + - name: Install setuptools_scm + run: | + if [[ "${{ matrix.python-version }}" == "3.6" ]]; then + python -m pip install --user 'setuptools_scm>=4.0.0,<=4.1.2' + else + python -m pip install --user setuptools_scm + fi + + - name: Check python and setuptools versions + run: | + python --version + python -m pip --version + python -c 'import setuptools; print("setuptools", setuptools.__version__)' + python -m pip show setuptools_scm | grep Version + + - name: Download and extract tarball for current commit + run: | + wget "$GITHUB_SERVER_URL/$GITHUB_REPOSITORY/archive/$GITHUB_SHA.tar.gz" + tar -xzf "$GITHUB_SHA.tar.gz" + # Check current directory contents + find . + + - name: Check version when running against uninstalled git clone + run: | + echo "importing eessi.testsuite from:" + original_pythonpath=$PYTHONPATH + export PYTHONPATH="$PWD/test-suite-$GITHUB_SHA:$PYTHONPATH" + echo "PYTHONPATH: $PYTHONPATH" + python3 -c "import eessi.testsuite; print(eessi.testsuite.__file__)" + + uninstalled_version=$(python3 -c "import eessi.testsuite; print(eessi.testsuite.__version__)") + echo "Version from uninstalled git clone: $uninstalled_version" + fallback_version=$(grep -oP 'fallback_version\s*=\s*"\K[^"]+' "test-suite-$GITHUB_SHA/pyproject.toml") + + echo "Testing if this version is the fallback version from pyproject.toml ..." + if [[ "$uninstalled_version" != "$fallback_version" ]]; then + echo "Version $uninstalled_version not equal to $fallback_version" + exit 1 + else + echo "... yes!" + fi + + export PYTHONPATH="$original_pythonpath" + + - name: Install from extracted tarball + run: | + # Make sure we get the fallback version from the pyprject.toml before changing workdir + fallback_version=$(grep -oP 'fallback_version\s*=\s*"\K[^"]+' "test-suite-$GITHUB_SHA/pyproject.toml") + + # Make it easier to figure out CI issues in case of CI failures related to SCM versioning + export SETUPTOOLS_SCM_DEBUG=1 + + # Change dir to the extracted tarball + cd "test-suite-$GITHUB_SHA" + + python -m pip install . --user + + echo "Checking contents of .local" + find $HOME/.local + + # make sure we are not in the source directory + cd $HOME + + echo "Checking if file 'eessi/testsuite/_version.py' was generated by setuptools_scm": + cat $HOME/.local/lib/python${{ matrix.python-version}}/site-packages/eessi/testsuite/_version.py + + echo "Checking if version can be imported directly from the version file" + if [[ "${{ matrix.python-version }}" == "3.6" ]]; then + versionfile_version=$(python -c 'from eessi.testsuite._version import version; print(version)') + else + versionfile_version=$(python -c 'from eessi.testsuite._version import __version__; print(__version__)') + fi + echo "Version from version file: $versionfile_version" + + echo "Checking if we can import the __version__ from eessi.testsuite" + installed_version=$(python -c 'import eessi.testsuite; print(eessi.testsuite.__version__)') + echo "Version from installed testsuite: $installed_version" + + # Read the fallback version from the pyproject.toml + echo "Testing if this is the fallback version from pyproject.toml ..." + if [[ "$installed_version" != "$fallback_version" ]]; then + echo "Version $installed_version not equal to $fallback_version" + exit 1 + else + echo "... yes!" + fi + + echo "Checking if the version imported from eessi.testsuite matches that from the version file ..." + if [[ "$versionfile_version" != "$installed_version" ]]; then + echo "Version $versionfile_version not equal to $installed_version" + exit 1 + else + echo "... yes!" + fi + diff --git a/.github/workflows/check_versions.yml b/.github/workflows/check_versions.yml new file mode 100644 index 00000000..c09dd07b --- /dev/null +++ b/.github/workflows/check_versions.yml @@ -0,0 +1,47 @@ +# documentation: https://help.github.com/en/articles/workflow-syntax-for-github-actions +name: Test fallback_version and version in run_reframe.sh against tags +on: [push, pull_request, workflow_dispatch] +permissions: read-all +jobs: + test_fallback_version_against_tags: + # ubuntu <= 20.04 is required for python 3.6 + runs-on: ubuntu-20.04 + strategy: + fail-fast: false + steps: + - name: Check out repository + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + with: + persist-credentials: false + fetch-depth: 0 + + - name: Check fallback version and version used in run_reframe.sh + run: | + # Get fallback version + fallback_version=$(grep -oP 'fallback_version\s*=\s*"\K[^"]+' "pyproject.toml") + # Prepend fallback version with 'v', as that is also the case for the other two version strings + fallback_version="v$fallback_version" + + # Get version from run_reframe.sh + run_reframe_testsuite_version=$(grep -oP 'EESSI_TESTSUITE_BRANCH\s*=\s*[^v]*\K[^"\x27]*' "CI/run_reframe.sh") + + # Grab the tag for the highest version, by sorting by (semantic) version, and then filtering on patterns + # that match a pattern like v0.1.2. Finally, we grab the last to get the highest version + most_recent_version=$(git tag --sort=version:refname | grep -P "v[0-9]+\.[0-9]+\.[0-9]+" | tail -n 1) + + echo "Testing if fallback version and EESSI_TESTSUITE_BRANCH version in CI/run_reframe.sh are the same" + if [[ "$fallback_version" != "$run_reframe_testsuite_version" ]]; then + echo "Version $fallback_version not equal to $run_reframe_testsuite_version" + exit 1 + else + echo "... yes!" + fi + + echo "Testing if fallback version and most recent version tag are the same" + if [[ "$fallback_version" != "$most_recent_version" ]]; then + echo "Version $fallback_version not equal to $most_recent_version" + exit 1 + else + echo "... yes!" + fi + diff --git a/.github/workflows/pip_install.yml b/.github/workflows/pip_install.yml index 5e8f2d9d..9a24dfe6 100644 --- a/.github/workflows/pip_install.yml +++ b/.github/workflows/pip_install.yml @@ -15,6 +15,7 @@ jobs: uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 with: persist-credentials: false + fetch-depth: 0 - name: Set up Python uses: actions/setup-python@61a6322f88396a6271a6ee3565807d608ecaddd1 # v4.7.0 @@ -34,12 +35,18 @@ jobs: - name: Install EESSI test suite with 'pip install' run: | + # Make it easier to figure out CI issues in case of CI failures related to SCM versioning + export SETUPTOOLS_SCM_DEBUG=1 # install from source distribution tarball, to test release as published on PyPI rm -rf dist + echo "Running python setup.py sdist" python setup.py sdist ls dist + echo "Running python -m pip install --user dist/eessi*.tar.gz" python -m pip install --user dist/eessi*.tar.gz + + echo "Checking contents of .local" find $HOME/.local # make sure we are not in the source directory @@ -49,5 +56,84 @@ jobs: python -m pip --version python -c 'import setuptools; print("setuptools", setuptools.__version__)' + echo "Checking if file 'eessi/testsuite/_version.py' was generated by setuptools_scm": + cat $HOME/.local/lib/python${{ matrix.python-version}}/site-packages/eessi/testsuite/_version.py + + echo "Checking if version can be imported directly from the version file" + if [[ "${{ matrix.python-version }}" == "3.6" ]]; then + versionfile_version=$(python -c 'from eessi.testsuite._version import version; print(version)') + else + versionfile_version=$(python -c 'from eessi.testsuite._version import __version__; print(__version__)') + fi + echo "Version from version file: $versionfile_version" + + echo "Checking if we can import the __version__ from eessi.testsuite" + testsuite_version=$(python -c 'import eessi.testsuite; print(eessi.testsuite.__version__)') + echo "Version imported from eessi.testsuite: $testsuite_version" + + echo "Checking if the version imported from eessi.testsuite matches that from the version file ..." + if [[ "$versionfile_version" != "$testsuite_version" ]]; then + echo "Version $versionfile_version not equal to $testsuite_version" + exit 1 + else + echo "... yes!" + fi + + echo "Checking if we can import eessi.testsuite.utils" + python -c 'import eessi.testsuite.utils' + + echo "Checking if we can import eessi.testsuite.tests.apps" + python -c 'import eessi.testsuite.tests.apps' + + + - name: Install EESSI test suite with 'pip install git+https' + run: | + # Get version from the installation in the previous step + testsuite_version=$(python -c 'import eessi.testsuite; print(eessi.testsuite.__version__)') + + # Cleanup installation from previous step + echo "Uninstalling testsuite for next step" + python -m pip uninstall -y eessi-testsuite + + pip install --user "git+$GITHUB_SERVER_URL/$GITHUB_REPOSITORY.git@$GITHUB_SHA" + + echo "Checking contents of .local" + find $HOME/.local + + echo "Checking if file 'eessi/testsuite/_version.py' was generated by setuptools_scm": + cat $HOME/.local/lib/python${{ matrix.python-version}}/site-packages/eessi/testsuite/_version.py + + echo "Checking if version can be imported directly from the version file" + if [[ "${{ matrix.python-version }}" == "3.6" ]]; then + githttps_versionfile_version=$(python -c 'from eessi.testsuite._version import version; print(version)') + else + githttps_versionfile_version=$(python -c 'from eessi.testsuite._version import __version__; print(__version__)') + fi + echo "Version from version file: $githttps_versionfile_version" + + echo "Checking if we can import the __version__ from eessi.testsuite" + githttps_testsuite_version=$(python -c 'import eessi.testsuite; print(eessi.testsuite.__version__)') + echo "Version imported from eessi.testsuite: $githttps_testsuite_version" + + echo "Checking if the version imported from eessi.testsuite matches that from the version file ..." + if [[ "$githttps_versionfile_version" != "$githttps_testsuite_version" ]]; then + echo "Version $githttps_versionfile_version not equal to $githttps_testsuite_version" + exit 1 + else + echo "... yes!" + fi + + echo "Checking if the version import from a regular pip install and the git+https based install are the same ..." + if [[ "$githttps_testsuite_version" != "$testsuite_version" ]]; then + echo "Version $githttps_testsuite_version not equal to $testsuite_version" + exit 1 + else + echo "... yes!" + fi + + echo "Checking if we can import eessi.testsuite.utils" python -c 'import eessi.testsuite.utils' + + echo "Checking if we can import eessi.testsuite.tests.apps" python -c 'import eessi.testsuite.tests.apps' + diff --git a/eessi/testsuite/__init__.py b/eessi/testsuite/__init__.py index e69de29b..c40994e4 100644 --- a/eessi/testsuite/__init__.py +++ b/eessi/testsuite/__init__.py @@ -0,0 +1,83 @@ +# WARNING: this file is imported in setup.py +# To make sure this works, we should avoid using imports other than from the Python standard library + +try: + # If this is an installed package, setuptools_scm will have written the _version.py file in the current directory + from ._version import __version__ +except ImportError: + try: + # Setuptools_scm 4.1.2 (compatible with setuptools 39.2.0) write version instead of __version__ + # This can be removed once we no longer care about python 3.6 with setuptools 39.2.0 + from ._version import version + __version__ = version + except ImportError: + # Fallback for when the package is not installed, but git cloned. Note that this requires setuptools_scm to be + # available as a runtime dependency + # The advantage here is that it will generate development versions if not on a tagged release version + try: + from setuptools_scm import get_version + # Using a relative path for relative_to doesn't work, because it will be relative to the current working + # directory (which could be anywhere) + # __file__ is the location of this init file (a full path), and this gives us a predictable path to the root + # (namely: two levels up). Note that if we ever move this __init__ file relative to the root of the git + # tree, we'll need to adjust this + __version__ = get_version(root='../..', relative_to=__file__) + except (ImportError, LookupError): + # If running from a tarball (e.g. release tarball) downloaded from github, we will not have the .git + # folder available. Thus, setuptools_scm cannot determine the version in any way. Thus, use the + # fallback_version from the pyproject.toml file (which doesn't exist when this is installed as a package, + # but SHOULD exist when this is run from a downloaded tarball from git) + + # Pyproject.toml should be two levels up from this file + import os.path + pyproject_toml = "%s/../../pyproject.toml" % os.path.dirname(__file__) + + # Variables to track if we're in the right section and to store the fallback_version + in_setuptools_scm_section = False + fallback_version = None + + file = None + try: + file = open(pyproject_toml, 'r') + # Open the file and parse it manually + fallback_version = None + with file: + for line in file: + stripped_line = line.strip() + + # Check if we're entering the [tool.setuptools_scm] section + if stripped_line == "[tool.setuptools_scm]": + in_setuptools_scm_section = True + elif stripped_line.startswith("[") and in_setuptools_scm_section: + # We've reached a new section, so stop searching + break + + # If we're in the right section, look for the fallback_version key + if in_setuptools_scm_section and stripped_line.startswith("fallback_version"): + # Extract the value after the '=' sign and strip any surrounding quotes or whitespace + fallback_version = stripped_line.split('=', 1)[1].strip().strip('"').strip("'") + break + # Account for the possibility that we failed to extract the fallback_version field from pyproject.toml + if fallback_version: + __version__ = fallback_version + else: + msg = "fallback_version not found in file %s" % pyproject_toml + msg += " when trying the get the EESSI test suite version. This should never happen." + msg += " Please report an issue on Github, including information on how you installed" + msg += " the EESSI test suite." + print(msg) + except FileNotFoundError: + msg = "File %s not found when trying to extract the EESSI test suite version from" % pyproject_toml + msg += " pyproject.toml. This should never happen. Please report an issue on GitHub," + msg += " including information on how you installed the EESSI test suite." + print(msg) + except Exception as e: + print("When trying to open file %s, an exception was raised: %s." % (pyproject_toml, e)) + +# One of the above three methods to get __version__ defined SHOULD work in any situation. +# It's considered a bug you reach this point without having a __version__ set +if not __version__: + msg = "__version__ should have been defined by now, but it is not." + msg += " This is considered a bug, please report it in an issue on Github for the" + msg += " EESSI test suite." + raise ValueError(msg) diff --git a/pyproject.toml b/pyproject.toml index bcc958c6..c8af85a5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,10 +1,9 @@ [build-system] -requires = ["setuptools"] +requires = ["setuptools>=42", "wheel"] build-backend = "setuptools.build_meta" [project] name = "eessi-testsuite" -version = "0.4.0" description = "Test suite for the EESSI software stack" readme = "README.md" license = {file = "LICENSE"} @@ -12,6 +11,7 @@ classifiers = [ "Programming Language :: Python" ] requires-python = ">=3.6" +dynamic = ["version"] [project.urls] "Homepage" = "https://eessi.io/docs/test-suite" @@ -19,3 +19,9 @@ requires-python = ">=3.6" [tool.setuptools.packages.find] include = ["eessi*"] + +[tool.setuptools_scm] +version_scheme = "guess-next-dev" +local_scheme = "node-and-date" +write_to = "eessi/testsuite/_version.py" +fallback_version = "0.4.0" diff --git a/setup.cfg b/setup.cfg index 6c08d50c..3ef11c6e 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,5 @@ [metadata] name = eessi-testsuite -version = 0.4.0 description = Test suite for the EESSI software stack long_description = file: README.md long_description_content_type = text/markdown @@ -21,6 +20,10 @@ namespace_packages = eessi [options.packages.find] include = eessi* +[setuptools_scm] +version_scheme = guess-next-dev +local_scheme = node-and-date + [flake8] max-line-length = 120 # ignore star imports (F403, F405) diff --git a/setup.py b/setup.py index a4f49f92..aff5ab6e 100644 --- a/setup.py +++ b/setup.py @@ -1,2 +1,42 @@ import setuptools -setuptools.setup() +import sys +import pkg_resources + + +def get_version_by_import(): + # Add the fallback version to whatever was set for scm_dict + sys.path.append('.') + from eessi.testsuite import __version__ + return __version__ + + +# Get python version +python_version = sys.version_info + +# Get setuptools version +# We control it when installing from pyproject.toml, but not when installing from setup.py / setup.cfg +# Check setuptools version +current_setuptools_version = pkg_resources.parse_version(pkg_resources.get_distribution("setuptools").version) + +# Set the version requirement for setuptools_scm, depending on the combination of Python and setuptools version +version_file_path = 'eessi/testsuite/_version.py' +scm_dict = {'write_to': version_file_path} +if python_version >= (3, 8) and current_setuptools_version >= pkg_resources.parse_version("61.0.0"): + setuptools_scm_requirement = 'setuptools_scm>8.0.0,<=8.1.0' + scm_dict = {'version_file': version_file_path} +elif python_version >= (3, 7) and current_setuptools_version >= pkg_resources.parse_version("45.0.0"): + setuptools_scm_requirement = 'setuptools_scm>7.0.0,<=7.1.0' +elif python_version >= (3, 6) and current_setuptools_version >= pkg_resources.parse_version("45.0.0"): + setuptools_scm_requirement = 'setuptools_scm>=6.0.0,<=6.4.2' +elif python_version >= (3, 6) and current_setuptools_version >= pkg_resources.parse_version("42.0.0"): + setuptools_scm_requirement = 'setuptools_scm>=5.0.0,<=5.0.2' +elif python_version >= (3, 6) and current_setuptools_version >= pkg_resources.parse_version("34.4.0"): + setuptools_scm_requirement = 'setuptools_scm>=4.0.0,<=4.1.2' + +# Set the fallback_version for scm based on what eessi.testsuite.__version__ returns +scm_dict['fallback_version'] = get_version_by_import() + +setuptools.setup( + use_scm_version=scm_dict, + setup_requires=[setuptools_scm_requirement], +)