diff --git a/.github/workflows/pylint.yml b/.github/workflows/pylint.yml new file mode 100644 index 0000000..383e65c --- /dev/null +++ b/.github/workflows/pylint.yml @@ -0,0 +1,23 @@ +name: Pylint + +on: [push] + +jobs: + build: + runs-on: ubuntu-latest + strategy: + matrix: + python-version: ["3.8", "3.9", "3.10"] + steps: + - uses: actions/checkout@v3 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v3 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install pylint + - name: Analysing the code with pylint + run: | + pylint $(git ls-files '*.py') diff --git a/.github/workflows/python-package-mac.yml b/.github/workflows/python-package-mac.yml index 1755148..5387086 100644 --- a/.github/workflows/python-package-mac.yml +++ b/.github/workflows/python-package-mac.yml @@ -12,8 +12,8 @@ jobs: runs-on: macos-latest strategy: matrix: - python-version: ["3.6", "3.7", "3.8", "3.9", "3.10"] - xcode-version: [latest-stable, 11] + python-version: ["3.7", "3.8", "3.9", "3.10", "3.11"] + xcode-version: [latest-stable, 14] steps: - uses: actions/checkout@v2 @@ -30,6 +30,7 @@ jobs: python -m pip install --upgrade pip pip install flake8 if [ -f requirements.txt ]; then pip install -r requirements.txt; fi + if [ -f requirements-dev.txt ]; then pip install -r requirements-dev.txt; fi - name: Lint with flake8 run: | # stop the build if there are Python syntax errors or undefined names @@ -41,7 +42,7 @@ jobs: pip install . - name: Test of run_tests.py run: | - python setup.py build test + python run_tests.py - name: Run end to end tests with e2etest.py run: | mkdir empty diff --git a/.github/workflows/python-package-ubuntu.yml b/.github/workflows/python-package-ubuntu.yml index 5fcdd10..c39db49 100644 --- a/.github/workflows/python-package-ubuntu.yml +++ b/.github/workflows/python-package-ubuntu.yml @@ -16,7 +16,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: ["3.6", "3.7", "3.8", "3.9", "3.10"] + python-version: ["3.7", "3.8", "3.9", "3.10", "3.11"] steps: - uses: actions/checkout@v2 @@ -45,7 +45,7 @@ jobs: pip install . - name: Test of run_tests.py run: | - python setup.py build test + python run_tests.py - name: Run end to end tests with e2etest.py run: | mkdir empty diff --git a/bootstrap.py b/bootstrap.py index f73ff1a..c147415 100755 --- a/bootstrap.py +++ b/bootstrap.py @@ -10,73 +10,94 @@ __authors__ = ["Frédéric-Emmanuel Picca", "Jérôme Kieffer"] __contact__ = "jerome.kieffer@esrf.eu" __license__ = "MIT" -__date__ = "09/07/2020" +__date__ = "03/03/2023" import sys import os -import distutils.util import subprocess import logging -import collections -from argparse import ArgumentParser - +if sys.version_info[:2] < (3, 11): + import tomli +else: + import tomllib as tomli logging.basicConfig() logger = logging.getLogger("bootstrap") -def is_debug_python(): - """Returns true if the Python interpreter is in debug mode.""" - try: - import sysconfig - except ImportError: # pragma nocover - # Python < 2.7 - import distutils.sysconfig as sysconfig +def get_project_name(root_dir): + """Retrieve project name by running python setup.py --name in root_dir. - if sysconfig.get_config_var("Py_DEBUG"): - return True + :param str root_dir: Directory where to run the command. + :return: The name of the project stored in root_dir + """ + logger.debug("Getting project name in %s", root_dir) + with open("pyproject.toml") as f: + pyproject = tomli.loads(f.read()) + return pyproject.get("project", {}).get("name") - return hasattr(sys, "gettotalrefcount") +def build_project(name, root_dir): + """Build locally the project using meson -def _distutils_dir_name(dname="lib"): - """ - Returns the name of a distutils build directory + :param str name: Name of the project. + :param str root_dir: Root directory of the project + :return: The path to the directory were build was performed """ - platform = distutils.util.get_platform() - architecture = "%s.%s-%i.%i" % (dname, platform, - sys.version_info[0], sys.version_info[1]) - if is_debug_python(): - architecture += "-pydebug" - return architecture - - -def _distutils_scripts_name(): - """Return the name of the distrutils scripts sirectory""" - f = "scripts-{version[0]}.{version[1]}" - return f.format(version=sys.version_info) + extra = [] + libdir = "lib" + if sys.platform == "win32": + libdir = "Lib" + # extra = ["--buildtype", "plain"] + + build = os.path.join(root_dir, "build") + if not(os.path.isdir(build) and os.path.isdir(os.path.join(build, name))): + p = subprocess.Popen(["meson", "setup", "build"], + shell=False, cwd=root_dir, env=os.environ) + p.wait() + p = subprocess.Popen(["meson", "configure", "--prefix", "/"] + extra, + shell=False, cwd=build, env=os.environ) + p.wait() + p = subprocess.Popen(["meson", "install", "--destdir", "."], + shell=False, cwd=build, env=os.environ) + logger.debug("meson install ended with rc= %s", p.wait()) + + home = None + if os.environ.get("PYBUILD_NAME") == name: + # we are in the debian packaging way + home = os.environ.get("PYTHONPATH", "").split(os.pathsep)[-1] + if not home: + if os.environ.get("BUILDPYTHONPATH"): + home = os.path.abspath(os.environ.get("BUILDPYTHONPATH", "")) + else: + if sys.platform == "win32": + home = os.path.join(build, libdir, "site-packages") + else: + python_version = f"python{sys.version_info.major}.{sys.version_info.minor}" + home = os.path.join(build, libdir, python_version, "site-packages") + home = os.path.abspath(home) + cnt = 0 + while not os.path.isdir(home): + cnt += 1 + home = os.path.split(home)[0] + for _ in range(cnt): + n = os.listdir(home)[0] + home = os.path.join(home, n) -def _get_available_scripts(path): - res = [] - try: - res = " ".join([s.rstrip('.py') for s in os.listdir(path)]) - except OSError: - res = ["no script available, did you ran " - "'python setup.py build' before bootstrapping ?"] - return res + logger.warning("Building %s to %s", name, home) + return home -if sys.version_info[0] >= 3: # Python3 - def execfile(fullpath, globals=None, locals=None): - "Python3 implementation for execfile" - with open(fullpath) as f: - try: - data = f.read() - except UnicodeDecodeError: - raise SyntaxError("Not a Python script") - code = compile(data, fullpath, 'exec') - exec(code, globals, locals) +def execfile(fullpath, globals=None, locals=None): + "Python3 implementation for execfile" + with open(fullpath) as f: + try: + data = f.read() + except UnicodeDecodeError: + raise SyntaxError("Not a Python script") + code = compile(data, fullpath, 'exec') + exec(code, globals, locals) def run_file(filename, argv): @@ -117,21 +138,18 @@ def run_file(filename, argv): run.wait() -def run_entry_point(entry_point, argv): +def run_entry_point(target_name, entry_point, argv): """ Execute an entry_point using the current python context - (http://setuptools.readthedocs.io/en/latest/setuptools.html#automatic-script-creation) :param str entry_point: A string identifying a function from a module - (NAME = PACKAGE.MODULE:FUNCTION [EXTRA]) + (NAME = PACKAGE.MODULE:FUNCTION) + :param argv: list of arguments """ import importlib - elements = entry_point.split("=") - target_name = elements[0].strip() - elements = elements[1].split(":") + elements = entry_point.split(":") module_name = elements[0].strip() - # Take care of entry_point optional "extra" requirements declaration - function_name = elements[1].split()[0].strip() + function_name = elements[1].strip() logger.info("Execute target %s (function %s from module %s) using importlib", target_name, function_name, module_name) full_args = [target_name] @@ -163,74 +181,49 @@ def find_executable(target): if os.path.isfile(target): return ("path", os.path.abspath(target)) - # search the file from setup.py - import setup - config = setup.get_project_configuration(dry_run=True) - # scripts from project configuration - if "scripts" in config: - for script_name in config["scripts"]: - if os.path.basename(script_name) == target: - return ("path", os.path.abspath(script_name)) - # entry-points from project configuration - if "entry_points" in config: - for kind in config["entry_points"]: - for entry_point in config["entry_points"][kind]: - elements = entry_point.split("=") - name = elements[0].strip() - if name == target: - return ("entry_point", entry_point) - - # search the file from env PATH - for dirname in os.environ.get("PATH", "").split(os.pathsep): - path = os.path.join(dirname, target) - if os.path.isfile(path): - return ("path", path) + # search the executable in pyproject.toml + with open(os.path.join(PROJECT_DIR, "pyproject.toml")) as f: + pyproject = tomli.loads(f.read()) + scripts = {} + scripts.update(pyproject.get("project", {}).get("scripts", {})) + scripts.update(pyproject.get("project", {}).get("gui-scripts", {})) + + for script, entry_point in scripts.items(): + if script == target: + print(script, entry_point) + return ("entry_point", target, entry_point) return None, None -def main(argv): - parser = ArgumentParser(prog="bootstrap", usage="./bootstrap.py