import os import sys import logging def load_setup_py_data(setup_file, from_recipe_dir=False, recipe_dir=None, work_dir=None, permit_undefined_jinja=True): _setuptools_data = {} log = logging.getLogger(__name__) import setuptools import distutils.core cd_to_work = False path_backup = sys.path def _change_cwd(target_dir): cd_to_work = True try: cwd = os.getcwd() except OSError: cwd = recipe_dir or work_dir os.chdir(target_dir) # this is very important - or else if versioneer or otherwise is in the start folder, # things will pick up the wrong versioneer/whatever! sys.path.insert(0, target_dir) return cd_to_work, cwd if from_recipe_dir and recipe_dir: setup_file = os.path.abspath(os.path.join(recipe_dir, setup_file)) # if the setup_file is not in the recipe dir itself, but is say specified as '../setup.py' setup_root, _ = os.path.split(setup_file) if os.path.abspath(setup_root) != os.path.abspath(recipe_dir): cd_to_work, cwd = _change_cwd(setup_root) elif os.path.exists(work_dir): cd_to_work, cwd = _change_cwd(work_dir) if not os.path.isabs(setup_file): setup_file = os.path.join(work_dir, setup_file) else: message = ("Did not find setup.py file in manually specified location, and source " "not downloaded yet.") if permit_undefined_jinja: log.debug(message) return {} else: raise RuntimeError(message) setup_cfg_data = {} try: from setuptools.config import read_configuration except ImportError: pass # setuptools <30.3.0 cannot read metadata / options from 'setup.cfg' else: setup_cfg = os.path.join(os.path.dirname(setup_file), 'setup.cfg') if os.path.isfile(setup_cfg): # read_configuration returns a dict of dicts. Each dict (keys: 'metadata', # 'options'), if present, provides keyword arguments for the setup function. for kwargs in read_configuration(setup_cfg).values(): # explicit arguments to setup.cfg take priority over values in setup.py setup_cfg_data.update(kwargs) def setup(**kw): _setuptools_data.update(kw) # values in setup.cfg take priority over explicit arguments to setup.py _setuptools_data.update(setup_cfg_data) # Patch setuptools, distutils setuptools_setup = setuptools.setup distutils_setup = distutils.core.setup numpy_setup = None versioneer = None if 'versioneer' in sys.modules: versioneer = sys.modules['versioneer'] del sys.modules['versioneer'] try: import numpy.distutils.core numpy_setup = numpy.distutils.core.setup numpy.distutils.core.setup = setup except ImportError: log.debug("Failed to import numpy for setup patch. Is numpy installed?") setuptools.setup = distutils.core.setup = setup ns = { '__name__': '__main__', '__doc__': None, '__file__': setup_file, } if os.path.isfile(setup_file): with open(setup_file) as f: code = compile(f.read(), setup_file, 'exec', dont_inherit=1) exec(code, ns, ns) else: if not permit_undefined_jinja: raise TypeError(f'{setup_file} is not a file that can be read') sys.modules['versioneer'] = versioneer distutils.core.setup = distutils_setup setuptools.setup = setuptools_setup if numpy_setup: numpy.distutils.core.setup = numpy_setup if cd_to_work: os.chdir(cwd) # remove our workdir from sys.path sys.path = path_backup return _setuptools_data if __name__ == '__main__': import json import argparse parser = argparse.ArgumentParser(description='run setup.py file to obtain metadata') parser.add_argument('work_dir', help=('path to work dir, where we\'ll write the output data ' 'json, and potentially also where setup.py should be found')) parser.add_argument('setup_file', help='path or filename of setup.py file') parser.add_argument('--from-recipe-dir', help=('look for setup.py file in recipe ' 'dir (as opposed to work dir)'), default=False, action="store_true") parser.add_argument('--recipe-dir', help=('(optional) path to recipe dir, where ' 'setup.py should be found')) parser.add_argument('--permit-undefined-jinja', help=('look for setup.py file in recipe ' 'dir (as opposed to work dir)'), default=False, action="store_true") args = parser.parse_args() # we get back a dict of the setup data data = load_setup_py_data(**args.__dict__) with open(os.path.join(args.work_dir, 'conda_build_loaded_setup_py.json'), 'w') as f: # this is lossy. Anything that can't be serialized is either forced to None or # removed completely. json.dump(data, f, skipkeys=True, default=lambda x: None)