pyFAI/bootstrap.py

240 lines
7.8 KiB
Python
Raw Permalink Normal View History

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
2017-05-18 17:42:15 +08:00
Bootstrap helps you to test scripts without installing them
2014-11-28 01:42:28 +08:00
by patching your PYTHONPATH on the fly
2017-05-18 17:42:15 +08:00
example: ./bootstrap.py ipython
"""
__authors__ = ["Frédéric-Emmanuel Picca", "Jérôme Kieffer"]
__contact__ = "jerome.kieffer@esrf.eu"
2017-05-15 22:05:10 +08:00
__license__ = "MIT"
2023-03-03 22:00:36 +08:00
__date__ = "03/03/2023"
import sys
import os
import subprocess
import logging
2023-03-03 22:00:36 +08:00
if sys.version_info[:2] < (3, 11):
import tomli
else:
import tomllib as tomli
2022-12-26 23:34:16 +08:00
logging.basicConfig()
2017-05-18 17:42:15 +08:00
logger = logging.getLogger("bootstrap")
2015-11-21 18:24:00 +08:00
2022-12-26 23:34:16 +08:00
def get_project_name(root_dir):
"""Retrieve project name by running python setup.py --name in root_dir.
2022-12-26 23:34:16 +08:00
: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")
2014-11-27 20:56:37 +08:00
2022-12-26 23:34:16 +08:00
def build_project(name, root_dir):
"""Build locally the project using meson
2020-12-04 16:24:45 +08:00
2022-12-26 23:34:16 +08:00
: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
"""
extra = []
libdir = "lib"
if sys.platform == "win32":
libdir = "Lib"
2023-01-06 01:07:20 +08:00
# extra = ["--buildtype", "plain"]
2022-12-26 23:34:16 +08:00
build = os.path.join(root_dir, "build")
if not(os.path.isdir(build) and os.path.isdir(os.path.join(build, name))):
2023-01-25 00:10:01 +08:00
p = subprocess.Popen(["meson", "setup", "build"],
2022-12-26 23:34:16 +08:00
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
2022-12-26 23:34:16 +08:00
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", ""))
2022-12-26 23:34:16 +08:00
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)
2022-12-26 23:34:16 +08:00
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)
logger.warning("Building %s to %s", name, home)
2022-12-26 23:34:16 +08:00
return home
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)
2016-03-22 16:41:41 +08:00
2017-05-18 17:42:15 +08:00
def run_file(filename, argv):
"""
Execute a script trying first to use execfile, then a subprocess
:param str filename: Script to execute
:param list[str] argv: Arguments passed to the filename
"""
full_args = [filename]
full_args.extend(argv)
try:
2017-05-18 17:42:15 +08:00
logger.info("Execute target using exec")
# execfile is considered as a local call.
# Providing globals() as locals will force to feed the file into
# globals() (for examples imports).
# Without this any function call from the executed file loses imports
try:
old_argv = sys.argv
sys.argv = full_args
logger.info("Patch the sys.argv: %s", sys.argv)
logger.info("Executing %s.main()", filename)
print("########### EXECFILE ###########")
2022-12-26 23:34:16 +08:00
module_globals = globals().copy()
module_globals['__file__'] = filename
execfile(filename, module_globals, module_globals)
2017-05-18 17:42:15 +08:00
finally:
sys.argv = old_argv
except SyntaxError as error:
logger.error(error)
logger.info("Execute target using subprocess")
env = os.environ.copy()
env.update({"PYTHONPATH": LIBPATH + os.pathsep + os.environ.get("PYTHONPATH", ""),
2017-05-18 17:42:15 +08:00
"PATH": os.environ.get("PATH", "")})
print("########### SUBPROCESS ###########")
run = subprocess.Popen(full_args, shell=False, env=env)
run.wait()
2022-12-26 23:34:16 +08:00
def run_entry_point(target_name, entry_point, argv):
"""
2017-05-18 17:42:15 +08:00
Execute an entry_point using the current python context
:param str entry_point: A string identifying a function from a module
(NAME = PACKAGE.MODULE:FUNCTION)
2022-12-26 23:34:16 +08:00
:param argv: list of arguments
2017-05-18 17:42:15 +08:00
"""
import importlib
2022-12-26 23:34:16 +08:00
elements = entry_point.split(":")
2017-05-18 17:42:15 +08:00
module_name = elements[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]
full_args.extend(argv)
try:
old_argv = sys.argv
sys.argv = full_args
print("########### IMPORTLIB ###########")
module = importlib.import_module(module_name)
if hasattr(module, function_name):
func = getattr(module, function_name)
func()
else:
logger.info("Function %s not found", function_name)
finally:
sys.argv = old_argv
def find_executable(target):
"""Find a filename from a script name.
- Check the script name as file path,
- Then checks if the name is a target of the setup.py
- Then search the script from the PATH environment variable.
:param str target: Name of the script
:returns: Returns a tuple: kind, name.
"""
if os.path.isfile(target):
return ("path", os.path.abspath(target))
2022-12-26 23:34:16 +08:00
# search the executable in pyproject.toml
with open(os.path.join(PROJECT_DIR, "pyproject.toml")) as f:
pyproject = tomli.loads(f.read())
2023-09-06 16:47:09 +08:00
scripts = {}
scripts.update(pyproject.get("project", {}).get("scripts", {}))
scripts.update(pyproject.get("project", {}).get("gui-scripts", {}))
for script, entry_point in scripts.items():
2022-12-26 23:34:16 +08:00
if script == target:
print(script, entry_point)
return ("entry_point", target, entry_point)
2017-05-18 17:42:15 +08:00
return None, None
2016-06-15 22:08:41 +08:00
2022-12-26 23:34:16 +08:00
PROJECT_DIR = os.path.dirname(os.path.abspath(__file__))
PROJECT_NAME = get_project_name(PROJECT_DIR)
logger.info("Project name: %s", PROJECT_NAME)
if __name__ == "__main__":
2022-12-26 23:34:16 +08:00
LIBPATH = build_project(PROJECT_NAME, PROJECT_DIR)
if len(sys.argv) < 2:
2017-05-18 17:42:15 +08:00
logger.warning("usage: ./bootstrap.py <script>\n")
script = None
else:
script = sys.argv[1]
if script:
logger.info("Executing %s from source checkout", script)
else:
logging.info("Running iPython by default")
sys.path.insert(0, LIBPATH)
2017-05-18 17:42:15 +08:00
logger.info("Patched sys.path with %s", LIBPATH)
if script:
2017-05-18 17:42:15 +08:00
argv = sys.argv[2:]
2022-12-26 23:34:16 +08:00
res = find_executable(script)
if res[0] == "path":
2022-12-26 23:34:16 +08:00
run_file(res[1], argv)
elif res[0] == "entry_point":
2022-12-26 23:34:16 +08:00
run_entry_point(res[1], res[2], argv)
2016-06-15 21:59:47 +08:00
else:
2017-05-18 17:42:15 +08:00
logger.error("Script %s not found", script)
else:
2023-12-01 20:35:11 +08:00
logging.info("Running IPython by default")
2017-05-18 17:42:15 +08:00
logger.info("Patch the sys.argv: %s", sys.argv)
sys.path.insert(2, "")
try:
from IPython import embed
except Exception as err:
logger.error("Unable to execute iPython, using normal Python")
logger.error(err)
import code
code.interact()
else:
embed()