1try: 2 # python3 vs. python2 3 from configparser import ConfigParser 4except ImportError: 5 from ConfigParser import SafeConfigParser as ConfigParser 6import io 7import logging 8import numpy.distutils.system_info as numpy_sys 9import numpy 10import os 11from shlex import split as shsplit 12import sys 13 14logger = logging.getLogger('pythran') 15 16 17def get_include(): 18 # using / as separator as advised in the distutils doc 19 return (os.path.dirname(os.path.dirname(__file__)) or '.') + '/pythran' 20 21 22class silent(object): 23 ''' 24 Silent sys.stderr at the system level 25 ''' 26 27 def __enter__(self): 28 try: 29 self.prevfd = os.dup(sys.stderr.fileno()) 30 os.close(sys.stderr.fileno()) 31 except io.UnsupportedOperation: 32 self.prevfd = None 33 34 self.prevstream = sys.stderr 35 sys.stderr = open(os.devnull, 'r') 36 37 def __exit__(self, exc_type, exc_value, traceback): 38 sys.stderr.close() 39 sys.stderr = self.prevstream 40 if self.prevfd: 41 os.dup2(self.prevfd, sys.stderr.fileno()) 42 os.close(self.prevfd) 43 44 45def get_paths_cfg( 46 sys_file='pythran.cfg', 47 platform_file='pythran-{}.cfg'.format(sys.platform), 48 user_file='.pythranrc' 49): 50 sys_config_dir = os.path.dirname(__file__) 51 sys_config_path = os.path.join(sys_config_dir, sys_file) 52 53 platform_config_path = os.path.join(sys_config_dir, platform_file) 54 if not os.path.exists(platform_config_path): 55 platform_config_path = os.path.join(sys_config_dir, 56 "pythran-default.cfg") 57 58 user_config_path = os.environ.get('PYTHRANRC', None) 59 if not user_config_path: 60 user_config_dir = os.environ.get('XDG_CONFIG_HOME', None) 61 if not user_config_dir: 62 user_config_dir = os.environ.get('HOME', None) 63 if not user_config_dir: 64 user_config_dir = '~' 65 user_config_path = os.path.expanduser( 66 os.path.join(user_config_dir, user_file)) 67 return {"sys": sys_config_path, 68 "platform": platform_config_path, 69 "user": user_config_path} 70 71 72def init_cfg(sys_file, platform_file, user_file, config_args=None): 73 paths = get_paths_cfg(sys_file, platform_file, user_file) 74 75 sys_config_path = paths["sys"] 76 platform_config_path = paths["platform"] 77 user_config_path = paths["user"] 78 79 cfgp = ConfigParser() 80 for required in (sys_config_path, platform_config_path): 81 cfgp.read([required]) 82 cfgp.read([user_config_path]) 83 84 if config_args is not None: 85 update_cfg(cfgp, config_args) 86 87 return cfgp 88 89 90def update_cfg(cfgp, config_args): 91 # Override the config options with those provided on the command line 92 # e.g. compiler.blas=pythran-openblas. 93 for arg in config_args: 94 try: 95 lhs, rhs = arg.split('=', maxsplit=1) 96 section, item = lhs.split('.') 97 if not cfgp.has_section(section): 98 cfgp.add_section(section) 99 cfgp.set(section, item, rhs) 100 except Exception: 101 pass 102 103 104def lint_cfg(cfgp, **paths): 105 if not paths: 106 paths = get_paths_cfg() 107 108 # Use configuration from sys and platform as "reference" 109 cfgp_ref = ConfigParser() 110 cfgp_ref.read([paths["sys"], paths["platform"]]) 111 112 # Check if pythran configuration files exists 113 for loc, path in paths.items(): 114 exists = os.path.exists(path) 115 116 msg = " ".join([ 117 "{} file".format(loc).rjust(13), 118 "exists:" if exists else "does not exist:", 119 path 120 ]) 121 logger.info(msg) if exists else logger.warning(msg) 122 123 for section in cfgp.sections(): 124 # Check if section in the current configuration exists in the 125 # reference configuration 126 if cfgp_ref.has_section(section): 127 options = set(cfgp.options(section)) 128 options_ref = set(cfgp_ref.options(section)) 129 130 # Check if the options in the section are supported by the 131 # reference configuration 132 if options.issubset(options_ref): 133 logger.info( 134 ( 135 "pythranrc section [{}] is valid and options are " 136 "correct" 137 ).format(section) 138 ) 139 else: 140 logger.warning( 141 ( 142 "pythranrc section [{}] is valid but options {} " 143 "are incorrect!" 144 ).format(section, options.difference(options_ref)) 145 ) 146 else: 147 logger.warning("pythranrc section [{}] is invalid!" 148 .format(section)) 149 150 151def make_extension(python, **extra): 152 # load platform specific configuration then user configuration 153 cfg = init_cfg('pythran.cfg', 154 'pythran-{}.cfg'.format(sys.platform), 155 '.pythranrc', 156 extra.get('config', None)) 157 158 if 'config' in extra: 159 extra.pop('config') 160 161 def parse_define(define): 162 index = define.find('=') 163 if index < 0: 164 return (define, None) 165 else: 166 return define[:index], define[index + 1:] 167 168 extension = { 169 "language": "c++", 170 # forcing str conversion to handle Unicode case (the default on MS) 171 "define_macros": [str(x) for x in 172 shsplit(cfg.get('compiler', 'defines'))], 173 "undef_macros": [str(x) for x in 174 shsplit(cfg.get('compiler', 'undefs'))], 175 "include_dirs": [str(x) for x in 176 shsplit(cfg.get('compiler', 'include_dirs'))], 177 "library_dirs": [str(x) for x in 178 shsplit(cfg.get('compiler', 'library_dirs'))], 179 "libraries": [str(x) for x in 180 shsplit(cfg.get('compiler', 'libs'))], 181 "extra_compile_args": [str(x) for x in 182 shsplit(cfg.get('compiler', 'cflags'))], 183 "extra_link_args": [str(x) for x in 184 shsplit(cfg.get('compiler', 'ldflags'))], 185 "extra_objects": [] 186 } 187 188 if python: 189 extension['define_macros'].append('ENABLE_PYTHON_MODULE') 190 extension['define_macros'].append( 191 '__PYTHRAN__={}'.format(sys.version_info.major)) 192 193 pythonic_dir = get_include() 194 195 extension["include_dirs"].append(pythonic_dir) 196 197 extra.pop('language', None) # forced to c++ anyway 198 cxx = extra.pop('cxx', None) 199 cc = extra.pop('cc', None) 200 201 if cxx is None: 202 cxx = compiler() 203 if cxx is not None: 204 extension['cxx'] = cxx 205 extension['cc'] = cc or cxx 206 207 # Honor CXXFLAGS (note: Pythran calls this `cflags` everywhere, however the 208 # standard environment variable is `CXXFLAGS` not `CFLAGS`). 209 cflags = os.environ.get('CXXFLAGS', None) 210 if cflags is not None: 211 extension['extra_compile_args'].extend(shsplit(cflags)) 212 213 # Honor LDFLAGS 214 ldflags = os.environ.get('LDFLAGS', None) 215 if ldflags is not None: 216 extension['extra_link_args'].extend(shsplit(ldflags)) 217 218 for k, w in extra.items(): 219 extension[k].extend(w) 220 if cfg.getboolean('pythran', 'complex_hook'): 221 # the patch is *not* portable 222 extension["include_dirs"].append(pythonic_dir + '/pythonic/patch') 223 224 # numpy specific 225 if python: 226 extension['include_dirs'].append(numpy.get_include()) 227 228 # blas dependency 229 reserved_blas_entries = 'pythran-openblas', 'none' 230 user_blas = cfg.get('compiler', 'blas') 231 if user_blas == 'pythran-openblas': 232 try: 233 import pythran_openblas as openblas 234 # required to cope with atlas missing extern "C" 235 extension['define_macros'].append('PYTHRAN_BLAS_OPENBLAS') 236 extension['include_dirs'].extend(openblas.include_dirs) 237 extension['extra_objects'].append( 238 os.path.join(openblas.library_dir, openblas.static_library) 239 ) 240 except ImportError: 241 logger.warning("Failed to find 'pythran-openblas' package. " 242 "Please install it or change the compiler.blas " 243 "setting. Defaulting to 'blas'") 244 user_blas = 'blas' 245 elif user_blas == 'none': 246 extension['define_macros'].append('PYTHRAN_BLAS_NONE') 247 248 if user_blas not in reserved_blas_entries: 249 # Numpy can pollute stdout with checks 250 with silent(): 251 numpy_blas = numpy_sys.get_info(user_blas) 252 # required to cope with atlas missing extern "C" 253 extension['define_macros'].append('PYTHRAN_BLAS_{}' 254 .format(user_blas.upper())) 255 extension['libraries'].extend(numpy_blas.get('libraries', [])) 256 extension['library_dirs'].extend( 257 numpy_blas.get('library_dirs', [])) 258 extension['include_dirs'].extend( 259 numpy_blas.get('include_dirs', [])) 260 261 262 # final macro normalization 263 extension["define_macros"] = [ 264 dm if isinstance(dm, tuple) else parse_define(dm) 265 for dm in extension["define_macros"]] 266 return extension 267 268 269def compiler(): 270 """Get compiler to use for C++ to binary process. The precedence for 271 choosing the compiler is as follows:: 272 273 1. `CXX` environment variable 274 2. User configuration (~/.pythranrc) 275 276 Returns None if none is set or if it's set to the empty string 277 278 """ 279 cfg_cxx = str(cfg.get('compiler', 'CXX')) 280 if not cfg_cxx: 281 cfg_cxx = None 282 return os.environ.get('CXX', cfg_cxx) or None 283 284 285# load platform specific configuration then user configuration 286cfg = init_cfg('pythran.cfg', 287 'pythran-{}.cfg'.format(sys.platform), 288 '.pythranrc') 289 290 291def run(): 292 ''' 293 Dump on stdout the config flags required to compile pythran-generated code. 294 ''' 295 import argparse 296 import distutils.ccompiler 297 import distutils.sysconfig 298 import pythran 299 import numpy 300 301 parser = argparse.ArgumentParser( 302 prog='pythran-config', 303 description='output build options for pythran-generated code', 304 epilog="It's a megablast!" 305 ) 306 307 parser.add_argument('--compiler', action='store_true', 308 help='print default compiler') 309 310 parser.add_argument('--cflags', action='store_true', 311 help='print compilation flags') 312 313 parser.add_argument('--libs', action='store_true', 314 help='print linker flags') 315 316 parser.add_argument('--no-python', action='store_true', 317 help='do not include Python-related flags') 318 319 parser.add_argument('--verbose', '-v', action='count', default=0, 320 help=( 321 'verbose mode: [-v] prints warnings if pythranrc ' 322 'has an invalid configuration; use ' 323 '[-vv] for more information') 324 ) 325 326 args = parser.parse_args(sys.argv[1:]) 327 328 args.python = not args.no_python 329 330 output = [] 331 332 extension = pythran.config.make_extension(python=args.python) 333 334 if args.verbose >= 1: 335 if args.verbose == 1: 336 logger.setLevel(logging.WARNING) 337 else: 338 logger.setLevel(logging.INFO) 339 340 lint_cfg(cfg) 341 342 if args.compiler or args.verbose >= 2: 343 cxx = compiler() or 'c++' 344 logger.info('CXX = '.rjust(10) + cxx) 345 if args.compiler: 346 output.append(cxx) 347 348 compiler_obj = distutils.ccompiler.new_compiler() 349 distutils.sysconfig.customize_compiler(compiler_obj) 350 351 if args.cflags or args.verbose >= 2: 352 def fmt_define(define): 353 name, value = define 354 if value is None: 355 return '-D' + name 356 else: 357 return '-D' + name + '=' + value 358 359 cflags = [] 360 cflags.extend(fmt_define(define) 361 for define in extension['define_macros']) 362 cflags.extend(('-I' + include) 363 for include in extension['include_dirs']) 364 if args.python: 365 cflags.append('-I' + numpy.get_include()) 366 cflags.append('-I' + distutils.sysconfig.get_python_inc()) 367 368 logger.info('CXXFLAGS = '.rjust(10) + ' '.join(cflags)) 369 if args.cflags: 370 output.extend(cflags) 371 372 if args.libs or args.verbose >= 2: 373 ldflags = [] 374 ldflags.extend((compiler_obj.library_dir_option(include)) 375 for include in extension['library_dirs']) 376 ldflags.extend((compiler_obj.library_option(include)) 377 for include in extension['libraries']) 378 379 if args.python: 380 libpl = distutils.sysconfig.get_config_var('LIBPL') 381 if libpl: 382 ldflags.append(libpl) 383 libs = distutils.sysconfig.get_config_var('LIBS') 384 if libs: 385 ldflags.extend(shsplit(libs)) 386 ldflags.append(compiler_obj.library_option('python') 387 + distutils.sysconfig.get_config_var('VERSION')) 388 389 logger.info('LDFLAGS = '.rjust(10) + ' '.join(ldflags)) 390 if args.libs: 391 output.extend(ldflags) 392 393 if output: 394 print(' '.join(output)) 395 396 397if __name__ == '__main__': 398 run() 399