1# Copyright 2014-2017 Insight Software Consortium. 2# Copyright 2004-2009 Roman Yakovenko. 3# Distributed under the Boost Software License, Version 1.0. 4# See http://www.boost.org/LICENSE_1_0.txt 5 6""" 7Defines C++ parser configuration classes. 8 9""" 10 11import os 12import copy 13import platform 14import subprocess 15# In py3, ConfigParser was renamed to the more-standard configparser. 16# But there's a py3 backport that installs "configparser" in py2, and I don't 17# want it because it has annoying deprecation warnings. So try the real py2 18# import first 19# Inspired by https://bitbucket.org/ned/coveragepy/commits/f8e9d62f1412 20try: 21 from ConfigParser import SafeConfigParser as ConfigParser 22except ImportError: 23 from configparser import ConfigParser 24from .. import utils 25 26 27class parser_configuration_t(object): 28 29 """ 30 C++ parser configuration holder 31 32 This class serves as a base class for the parameters that can be used 33 to customize the call to a C++ parser. 34 35 This class also allows users to work with relative files paths. In this 36 case files are searched in the following order: 37 38 1. current directory 39 2. working directory 40 3. additional include paths specified by the user 41 42 """ 43 44 def __init__( 45 self, 46 working_directory='.', 47 include_paths=None, 48 define_symbols=None, 49 undefine_symbols=None, 50 cflags="", 51 compiler=None, 52 xml_generator=None, 53 keep_xml=False, 54 compiler_path=None, 55 flags=None, 56 castxml_epic_version=None): 57 58 object.__init__(self) 59 self.__working_directory = working_directory 60 61 if not include_paths: 62 include_paths = [] 63 self.__include_paths = include_paths 64 65 if not define_symbols: 66 define_symbols = [] 67 self.__define_symbols = define_symbols 68 69 if not undefine_symbols: 70 undefine_symbols = [] 71 self.__undefine_symbols = undefine_symbols 72 73 self.__cflags = cflags 74 75 self.__compiler = compiler 76 77 self.__xml_generator = xml_generator 78 79 self.__castxml_epic_version = castxml_epic_version 80 81 self.__keep_xml = keep_xml 82 83 if flags is None: 84 flags = [] 85 self.__flags = flags 86 87 # If no compiler path was set and we are using castxml, set the path 88 self.__compiler_path = create_compiler_path( 89 xml_generator, compiler_path) 90 91 def clone(self): 92 raise NotImplementedError(self.__class__.__name__) 93 94 @property 95 def working_directory(self): 96 return self.__working_directory 97 98 @working_directory.setter 99 def working_directory(self, working_dir): 100 self.__working_directory = working_dir 101 102 @property 103 def include_paths(self): 104 """list of include paths to look for header files""" 105 return self.__include_paths 106 107 @property 108 def define_symbols(self): 109 """list of "define" directives """ 110 return self.__define_symbols 111 112 @property 113 def undefine_symbols(self): 114 """list of "undefine" directives """ 115 return self.__undefine_symbols 116 117 @property 118 def compiler(self): 119 """get compiler name to simulate""" 120 return self.__compiler 121 122 @compiler.setter 123 def compiler(self, compiler): 124 """set compiler name to simulate""" 125 self.__compiler = compiler 126 127 @property 128 def xml_generator(self): 129 """get xml_generator (gccxml or castxml)""" 130 return self.__xml_generator 131 132 @xml_generator.setter 133 def xml_generator(self, xml_generator): 134 """set xml_generator (gccxml or castxml)""" 135 if "real" in xml_generator: 136 # Support for gccxml.real from newer gccxml package 137 # Can be removed once gccxml support is dropped. 138 xml_generator = "gccxml" 139 self.__xml_generator = xml_generator 140 141 @property 142 def castxml_epic_version(self): 143 """ 144 File format version used by castxml. 145 """ 146 return self.__castxml_epic_version 147 148 @castxml_epic_version.setter 149 def castxml_epic_version(self, castxml_epic_version): 150 """ 151 File format version used by castxml. 152 """ 153 self.__castxml_epic_version = castxml_epic_version 154 155 @property 156 def keep_xml(self): 157 """Are xml files kept after errors.""" 158 return self.__keep_xml 159 160 @keep_xml.setter 161 def keep_xml(self, keep_xml): 162 """Set if xml files kept after errors.""" 163 self.__keep_xml = keep_xml 164 165 @property 166 def flags(self): 167 """Optional flags for pygccxml.""" 168 return self.__flags 169 170 @flags.setter 171 def flags(self, flags): 172 """Optional flags for pygccxml.""" 173 if flags is None: 174 flags = [] 175 self.__flags = flags 176 177 @property 178 def compiler_path(self): 179 """Get the path for the compiler.""" 180 return self.__compiler_path 181 182 @compiler_path.setter 183 def compiler_path(self, compiler_path): 184 """Set the path for the compiler.""" 185 self.__compiler_path = compiler_path 186 187 @property 188 def cflags(self): 189 """additional flags to pass to compiler""" 190 return self.__cflags 191 192 @cflags.setter 193 def cflags(self, val): 194 self.__cflags = val 195 196 def append_cflags(self, val): 197 self.__cflags = self.__cflags + ' ' + val 198 199 def __ensure_dir_exists(self, dir_path, meaning): 200 if os.path.isdir(dir_path): 201 return 202 if os.path.exists(self.working_directory): 203 raise RuntimeError( 204 '%s("%s") does not exist!' % (meaning, dir_path)) 205 else: 206 raise RuntimeError( 207 '%s("%s") should be "directory", not a file.' % 208 (meaning, dir_path)) 209 210 def raise_on_wrong_settings(self): 211 """ 212 Validates the configuration settings and raises RuntimeError on error 213 """ 214 self.__ensure_dir_exists(self.working_directory, 'working directory') 215 for idir in self.include_paths: 216 self.__ensure_dir_exists(idir, 'include directory') 217 if self.__xml_generator not in ["castxml", "gccxml"]: 218 msg = ('xml_generator("%s") should either be ' + 219 '"castxml" or "gccxml".') % self.xml_generator 220 raise RuntimeError(msg) 221 222 223class xml_generator_configuration_t(parser_configuration_t): 224 """ 225 Configuration object to collect parameters for invoking gccxml or castxml. 226 227 This class serves as a container for the parameters that can be used 228 to customize the call to gccxml or castxml. 229 230 """ 231 232 def __init__( 233 self, 234 gccxml_path='', 235 xml_generator_path='', 236 working_directory='.', 237 include_paths=None, 238 define_symbols=None, 239 undefine_symbols=None, 240 start_with_declarations=None, 241 ignore_gccxml_output=False, 242 cflags="", 243 compiler=None, 244 xml_generator=None, 245 keep_xml=False, 246 compiler_path=None, 247 flags=None, 248 castxml_epic_version=None): 249 250 parser_configuration_t.__init__( 251 self, 252 working_directory=working_directory, 253 include_paths=include_paths, 254 define_symbols=define_symbols, 255 undefine_symbols=undefine_symbols, 256 cflags=cflags, 257 compiler=compiler, 258 xml_generator=xml_generator, 259 keep_xml=keep_xml, 260 compiler_path=compiler_path, 261 flags=flags, 262 castxml_epic_version=castxml_epic_version) 263 264 if gccxml_path != '': 265 self.__gccxml_path = gccxml_path 266 self.__xml_generator_path = xml_generator_path 267 268 if not start_with_declarations: 269 start_with_declarations = [] 270 self.__start_with_declarations = start_with_declarations 271 272 self.__ignore_gccxml_output = ignore_gccxml_output 273 274 self.__xml_generator_from_xml_file = None 275 276 def clone(self): 277 return copy.deepcopy(self) 278 279 @property 280 def xml_generator_path(self): 281 """ 282 XML generator binary location 283 284 """ 285 286 return self.__xml_generator_path 287 288 @xml_generator_path.setter 289 def xml_generator_path(self, new_path): 290 self.__xml_generator_path = new_path 291 292 @property 293 def xml_generator_from_xml_file(self): 294 """ 295 Configuration object containing information about the xml generator 296 read from the xml file. 297 298 Returns: 299 utils.xml_generators: configuration object 300 """ 301 return self.__xml_generator_from_xml_file 302 303 @xml_generator_from_xml_file.setter 304 def xml_generator_from_xml_file(self, xml_generator_from_xml_file): 305 self.__xml_generator_from_xml_file = xml_generator_from_xml_file 306 307 @property 308 def start_with_declarations(self): 309 """list of declarations gccxml should start with, when it dumps 310 declaration tree""" 311 return self.__start_with_declarations 312 313 @property 314 def ignore_gccxml_output(self): 315 """set this property to True, if you want pygccxml to ignore any 316 error warning that comes from gccxml""" 317 return self.__ignore_gccxml_output 318 319 @ignore_gccxml_output.setter 320 def ignore_gccxml_output(self, val=True): 321 self.__ignore_gccxml_output = val 322 323 def raise_on_wrong_settings(self): 324 super(xml_generator_configuration_t, self).raise_on_wrong_settings() 325 if self.xml_generator_path is None or \ 326 not os.path.isfile(self.xml_generator_path): 327 msg = ( 328 'xml_generator_path("%s") should be set and exist.') \ 329 % self.xml_generator_path 330 raise RuntimeError(msg) 331 332 333def load_xml_generator_configuration(configuration, **defaults): 334 """ 335 Loads CastXML or GCC-XML configuration. 336 337 Args: 338 configuration (string|configparser.ConfigParser): can be 339 a string (file path to a configuration file) or 340 instance of :class:`configparser.ConfigParser`. 341 defaults: can be used to override single configuration values. 342 343 Returns: 344 :class:`.xml_generator_configuration_t`: a configuration object 345 346 347 The file passed needs to be in a format that can be parsed by 348 :class:`configparser.ConfigParser`. 349 350 An example configuration file skeleton can be found 351 `here <https://github.com/gccxml/pygccxml/blob/develop/ 352 unittests/xml_generator.cfg>`_. 353 354 """ 355 parser = configuration 356 if utils.is_str(configuration): 357 parser = ConfigParser() 358 parser.read(configuration) 359 360 # Create a new empty configuration 361 cfg = xml_generator_configuration_t() 362 363 values = defaults 364 if not values: 365 values = {} 366 367 if parser.has_section('xml_generator'): 368 for name, value in parser.items('xml_generator'): 369 if value.strip(): 370 values[name] = value 371 372 for name, value in values.items(): 373 if isinstance(value, str): 374 value = value.strip() 375 if name == 'gccxml_path': 376 cfg.gccxml_path = value 377 if name == 'xml_generator_path': 378 cfg.xml_generator_path = value 379 elif name == 'working_directory': 380 cfg.working_directory = value 381 elif name == 'include_paths': 382 for p in value.split(';'): 383 p = p.strip() 384 if p: 385 cfg.include_paths.append(os.path.normpath(p)) 386 elif name == 'compiler': 387 cfg.compiler = value 388 elif name == 'xml_generator': 389 cfg.xml_generator = value 390 elif name == 'castxml_epic_version': 391 cfg.castxml_epic_version = int(value) 392 elif name == 'keep_xml': 393 cfg.keep_xml = value 394 elif name == 'cflags': 395 cfg.cflags = value 396 elif name == 'flags': 397 cfg.flags = value 398 elif name == 'compiler_path': 399 cfg.compiler_path = value 400 else: 401 print('\n%s entry was ignored' % name) 402 403 # If no compiler path was set and we are using castxml, set the path 404 # Here we overwrite the default configuration done in the cfg because 405 # the xml_generator was set through the setter after the creation of a new 406 # emppty configuration object. 407 cfg.compiler_path = create_compiler_path( 408 cfg.xml_generator, cfg.compiler_path) 409 410 return cfg 411 412 413def create_compiler_path(xml_generator, compiler_path): 414 """ 415 Try to guess a path for the compiler. 416 417 If you want ot use a specific compiler, please provide the compiler 418 path manually, as the guess may not be what you are expecting. 419 Providing the path can be done by passing it as an argument (compiler_path) 420 to the xml_generator_configuration_t() or by defining it in your pygccxml 421 configuration file. 422 423 """ 424 425 if xml_generator == 'castxml' and compiler_path is None: 426 if platform.system() == 'Windows': 427 # Look for msvc 428 p = subprocess.Popen( 429 ['where', 'cl'], 430 stdout=subprocess.PIPE, 431 stderr=subprocess.PIPE) 432 compiler_path = p.stdout.read().decode("utf-8").rstrip() 433 p.wait() 434 p.stdout.close() 435 p.stderr.close() 436 # No msvc found; look for mingw 437 if compiler_path == '': 438 p = subprocess.Popen( 439 ['where', 'mingw'], 440 stdout=subprocess.PIPE, 441 stderr=subprocess.PIPE) 442 compiler_path = p.stdout.read().decode("utf-8").rstrip() 443 p.wait() 444 p.stdout.close() 445 p.stderr.close() 446 else: 447 # OS X or Linux 448 # Look for clang first, then gcc 449 p = subprocess.Popen( 450 ['which', 'clang++'], 451 stdout=subprocess.PIPE, 452 stderr=subprocess.PIPE) 453 compiler_path = p.stdout.read().decode("utf-8").rstrip() 454 p.wait() 455 p.stdout.close() 456 p.stderr.close() 457 # No clang found; use gcc 458 if compiler_path == '': 459 p = subprocess.Popen( 460 ['which', 'c++'], 461 stdout=subprocess.PIPE, 462 stderr=subprocess.PIPE) 463 compiler_path = p.stdout.read().decode("utf-8").rstrip() 464 p.wait() 465 p.stdout.close() 466 p.stderr.close() 467 468 if compiler_path == "": 469 compiler_path = None 470 471 return compiler_path 472 473 474if __name__ == '__main__': 475 print(load_xml_generator_configuration('xml_generator.cfg').__dict__) 476