#! /usr/bin/env python # encoding: utf-8 # Eclipse CDT 5.0 generator for Waf # Richard Quirk 2009-1011 (New BSD License) # Thomas Nagy 2011 (ported to Waf 1.6) """ Usage: def options(opt): opt.load('eclipse') To add additional targets beside standard ones (configure, dist, install, check) the environment ECLIPSE_EXTRA_TARGETS can be set (ie. to ['test', 'lint', 'docs']) $ waf configure eclipse """ import sys, os from waflib import Utils, Logs, Context, Build, TaskGen, Scripting, Errors, Node from xml.dom.minidom import Document STANDARD_INCLUDES = [ '/usr/local/include', '/usr/include' ] oe_cdt = 'org.eclipse.cdt' cdt_mk = oe_cdt + '.make.core' cdt_core = oe_cdt + '.core' cdt_bld = oe_cdt + '.build.core' extbuilder_dir = '.externalToolBuilders' extbuilder_name = 'Waf_Builder.launch' settings_dir = '.settings' settings_name = 'language.settings.xml' class eclipse(Build.BuildContext): cmd = 'eclipse' fun = Scripting.default_cmd def execute(self): """ Entry point """ self.restore() if not self.all_envs: self.load_envs() self.recurse([self.run_dir]) appname = getattr(Context.g_module, Context.APPNAME, os.path.basename(self.srcnode.abspath())) self.create_cproject(appname, pythonpath=self.env['ECLIPSE_PYTHON_PATH']) # Helper to dump the XML document content to XML with UTF-8 encoding def write_conf_to_xml(self, filename, document): self.srcnode.make_node(filename).write(document.toprettyxml(encoding='UTF-8'), flags='wb') def create_cproject(self, appname, workspace_includes=[], pythonpath=[]): """ Create the Eclipse CDT .project and .cproject files @param appname The name that will appear in the Project Explorer @param build The BuildContext object to extract includes from @param workspace_includes Optional project includes to prevent "Unresolved Inclusion" errors in the Eclipse editor @param pythonpath Optional project specific python paths """ hasc = hasjava = haspython = False source_dirs = [] cpppath = self.env['CPPPATH'] javasrcpath = [] javalibpath = [] includes = STANDARD_INCLUDES if sys.platform != 'win32': cc = self.env.CC or self.env.CXX if cc: cmd = cc + ['-xc++', '-E', '-Wp,-v', '-'] try: gccout = self.cmd_and_log(cmd, output=Context.STDERR, quiet=Context.BOTH, input='\n'.encode()).splitlines() except Errors.WafError: pass else: includes = [] for ipath in gccout: if ipath.startswith(' /'): includes.append(ipath[1:]) cpppath += includes Logs.warn('Generating Eclipse CDT project files') for g in self.groups: for tg in g: if not isinstance(tg, TaskGen.task_gen): continue tg.post() # Add local Python modules paths to configuration so object resolving will work in IDE # This may also contain generated files (ie. pyqt5 or protoc) that get picked from build if 'py' in tg.features: pypath = tg.path.relpath() py_installfrom = getattr(tg, 'install_from', None) if isinstance(py_installfrom, Node.Node): pypath = py_installfrom.path_from(self.root.make_node(self.top_dir)) if pypath not in pythonpath: pythonpath.append(pypath) haspython = True # Add Java source directories so object resolving works in IDE # This may also contain generated files (ie. protoc) that get picked from build if 'javac' in tg.features: java_src = tg.path.relpath() java_srcdir = getattr(tg.javac_task, 'srcdir', None) if java_srcdir: if isinstance(java_srcdir, Node.Node): java_srcdir = [java_srcdir] for x in Utils.to_list(java_srcdir): x = x.path_from(self.root.make_node(self.top_dir)) if x not in javasrcpath: javasrcpath.append(x) else: if java_src not in javasrcpath: javasrcpath.append(java_src) hasjava = True # Check if there are external dependencies and add them as external jar so they will be resolved by Eclipse usedlibs=getattr(tg, 'use', []) for x in Utils.to_list(usedlibs): for cl in Utils.to_list(tg.env['CLASSPATH_'+x]): if cl not in javalibpath: javalibpath.append(cl) if not getattr(tg, 'link_task', None): continue features = Utils.to_list(getattr(tg, 'features', '')) is_cc = 'c' in features or 'cxx' in features incnodes = tg.to_incnodes(tg.to_list(getattr(tg, 'includes', [])) + tg.env['INCLUDES']) for p in incnodes: path = p.path_from(self.srcnode) if (path.startswith("/")): if path not in cpppath: cpppath.append(path) else: if path not in workspace_includes: workspace_includes.append(path) if is_cc and path not in source_dirs: source_dirs.append(path) hasc = True waf_executable = os.path.abspath(sys.argv[0]) project = self.impl_create_project(sys.executable, appname, hasc, hasjava, haspython, waf_executable) self.write_conf_to_xml('.project', project) if hasc: project = self.impl_create_cproject(sys.executable, waf_executable, appname, workspace_includes, cpppath, source_dirs) self.write_conf_to_xml('.cproject', project) if haspython: project = self.impl_create_pydevproject(sys.path, pythonpath) self.write_conf_to_xml('.pydevproject', project) if hasjava: project = self.impl_create_javaproject(javasrcpath, javalibpath) self.write_conf_to_xml('.classpath', project) # Create editor language settings to have correct standards applied in IDE, as per project configuration try: os.mkdir(settings_dir) except OSError: pass # Ignore if dir already exists lang_settings = Document() project = lang_settings.createElement('project') # Language configurations for C and C++ via cdt if hasc: configuration = self.add(lang_settings, project, 'configuration', {'id' : 'org.eclipse.cdt.core.default.config.1', 'name': 'Default'}) extension = self.add(lang_settings, configuration, 'extension', {'point': 'org.eclipse.cdt.core.LanguageSettingsProvider'}) provider = self.add(lang_settings, extension, 'provider', { 'copy-of': 'extension', 'id': 'org.eclipse.cdt.ui.UserLanguageSettingsProvider'}) provider = self.add(lang_settings, extension, 'provider-reference', { 'id': 'org.eclipse.cdt.core.ReferencedProjectsLanguageSettingsProvider', 'ref': 'shared-provider'}) provider = self.add(lang_settings, extension, 'provider-reference', { 'id': 'org.eclipse.cdt.managedbuilder.core.MBSLanguageSettingsProvider', 'ref': 'shared-provider'}) # C and C++ are kept as separated providers so appropriate flags are used also in mixed projects if self.env.CC: provider = self.add(lang_settings, extension, 'provider', { 'class': 'org.eclipse.cdt.managedbuilder.language.settings.providers.GCCBuiltinSpecsDetector', 'console': 'false', 'id': 'org.eclipse.cdt.managedbuilder.language.settings.providers.GCCBuiltinSpecsDetector.1', 'keep-relative-paths' : 'false', 'name': 'CDT GCC Built-in Compiler Settings', 'parameter': '%s %s ${FLAGS} -E -P -v -dD "${INPUTS}"'%(self.env.CC[0],' '.join(self.env['CFLAGS'])), 'prefer-non-shared': 'true' }) self.add(lang_settings, provider, 'language-scope', { 'id': 'org.eclipse.cdt.core.gcc'}) if self.env.CXX: provider = self.add(lang_settings, extension, 'provider', { 'class': 'org.eclipse.cdt.managedbuilder.language.settings.providers.GCCBuiltinSpecsDetector', 'console': 'false', 'id': 'org.eclipse.cdt.managedbuilder.language.settings.providers.GCCBuiltinSpecsDetector.2', 'keep-relative-paths' : 'false', 'name': 'CDT GCC Built-in Compiler Settings', 'parameter': '%s %s ${FLAGS} -E -P -v -dD "${INPUTS}"'%(self.env.CXX[0],' '.join(self.env['CXXFLAGS'])), 'prefer-non-shared': 'true' }) self.add(lang_settings, provider, 'language-scope', { 'id': 'org.eclipse.cdt.core.g++'}) lang_settings.appendChild(project) self.write_conf_to_xml('%s%s%s'%(settings_dir, os.path.sep, settings_name), lang_settings) def impl_create_project(self, executable, appname, hasc, hasjava, haspython, waf_executable): doc = Document() projectDescription = doc.createElement('projectDescription') self.add(doc, projectDescription, 'name', appname) self.add(doc, projectDescription, 'comment') self.add(doc, projectDescription, 'projects') buildSpec = self.add(doc, projectDescription, 'buildSpec') buildCommand = self.add(doc, buildSpec, 'buildCommand') self.add(doc, buildCommand, 'triggers', 'clean,full,incremental,') arguments = self.add(doc, buildCommand, 'arguments') dictionaries = {} # If CDT is present, instruct this one to call waf as it is more flexible (separate build/clean ...) if hasc: self.add(doc, buildCommand, 'name', oe_cdt + '.managedbuilder.core.genmakebuilder') # the default make-style targets are overwritten by the .cproject values dictionaries = { cdt_mk + '.contents': cdt_mk + '.activeConfigSettings', cdt_mk + '.enableAutoBuild': 'false', cdt_mk + '.enableCleanBuild': 'true', cdt_mk + '.enableFullBuild': 'true', } else: # Otherwise for Java/Python an external builder tool is created that will call waf build self.add(doc, buildCommand, 'name', 'org.eclipse.ui.externaltools.ExternalToolBuilder') dictionaries = { 'LaunchConfigHandle': '/%s/%s'%(extbuilder_dir, extbuilder_name), } # The definition is in a separate directory XML file try: os.mkdir(extbuilder_dir) except OSError: pass # Ignore error if already exists # Populate here the external builder XML calling waf builder = Document() launchConfiguration = doc.createElement('launchConfiguration') launchConfiguration.setAttribute('type', 'org.eclipse.ui.externaltools.ProgramBuilderLaunchConfigurationType') self.add(doc, launchConfiguration, 'booleanAttribute', {'key': 'org.eclipse.debug.ui.ATTR_LAUNCH_IN_BACKGROUND', 'value': 'false'}) self.add(doc, launchConfiguration, 'booleanAttribute', {'key': 'org.eclipse.ui.externaltools.ATTR_TRIGGERS_CONFIGURED', 'value': 'true'}) self.add(doc, launchConfiguration, 'stringAttribute', {'key': 'org.eclipse.ui.externaltools.ATTR_LOCATION', 'value': waf_executable}) self.add(doc, launchConfiguration, 'stringAttribute', {'key': 'org.eclipse.ui.externaltools.ATTR_RUN_BUILD_KINDS', 'value': 'full,incremental,'}) self.add(doc, launchConfiguration, 'stringAttribute', {'key': 'org.eclipse.ui.externaltools.ATTR_TOOL_ARGUMENTS', 'value': 'build'}) self.add(doc, launchConfiguration, 'stringAttribute', {'key': 'org.eclipse.ui.externaltools.ATTR_WORKING_DIRECTORY', 'value': '${project_loc}'}) builder.appendChild(launchConfiguration) # And write the XML to the file references before self.write_conf_to_xml('%s%s%s'%(extbuilder_dir, os.path.sep, extbuilder_name), builder) for k, v in dictionaries.items(): self.addDictionary(doc, arguments, k, v) natures = self.add(doc, projectDescription, 'natures') if hasc: nature_list = """ core.ccnature managedbuilder.core.ScannerConfigNature managedbuilder.core.managedBuildNature core.cnature """.split() for n in nature_list: self.add(doc, natures, 'nature', oe_cdt + '.' + n) if haspython: self.add(doc, natures, 'nature', 'org.python.pydev.pythonNature') if hasjava: self.add(doc, natures, 'nature', 'org.eclipse.jdt.core.javanature') doc.appendChild(projectDescription) return doc def impl_create_cproject(self, executable, waf_executable, appname, workspace_includes, cpppath, source_dirs=[]): doc = Document() doc.appendChild(doc.createProcessingInstruction('fileVersion', '4.0.0')) cconf_id = cdt_core + '.default.config.1' cproject = doc.createElement('cproject') storageModule = self.add(doc, cproject, 'storageModule', {'moduleId': cdt_core + '.settings'}) cconf = self.add(doc, storageModule, 'cconfiguration', {'id':cconf_id}) storageModule = self.add(doc, cconf, 'storageModule', {'buildSystemId': oe_cdt + '.managedbuilder.core.configurationDataProvider', 'id': cconf_id, 'moduleId': cdt_core + '.settings', 'name': 'Default'}) self.add(doc, storageModule, 'externalSettings') extensions = self.add(doc, storageModule, 'extensions') extension_list = """ VCErrorParser MakeErrorParser GCCErrorParser GASErrorParser GLDErrorParser """.split() self.add(doc, extensions, 'extension', {'id': cdt_core + '.ELF', 'point':cdt_core + '.BinaryParser'}) for e in extension_list: self.add(doc, extensions, 'extension', {'id': cdt_core + '.' + e, 'point':cdt_core + '.ErrorParser'}) storageModule = self.add(doc, cconf, 'storageModule', {'moduleId': 'cdtBuildSystem', 'version': '4.0.0'}) config = self.add(doc, storageModule, 'configuration', {'artifactName': appname, 'id': cconf_id, 'name': 'Default', 'parent': cdt_bld + '.prefbase.cfg'}) folderInfo = self.add(doc, config, 'folderInfo', {'id': cconf_id+'.', 'name': '/', 'resourcePath': ''}) toolChain = self.add(doc, folderInfo, 'toolChain', {'id': cdt_bld + '.prefbase.toolchain.1', 'name': 'No ToolChain', 'resourceTypeBasedDiscovery': 'false', 'superClass': cdt_bld + '.prefbase.toolchain'}) self.add(doc, toolChain, 'targetPlatform', {'binaryParser': 'org.eclipse.cdt.core.ELF', 'id': cdt_bld + '.prefbase.toolchain.1', 'name': ''}) waf_build = '"%s" %s'%(waf_executable, eclipse.fun) waf_clean = '"%s" clean'%(waf_executable) self.add(doc, toolChain, 'builder', {'autoBuildTarget': waf_build, 'command': executable, 'enableAutoBuild': 'false', 'cleanBuildTarget': waf_clean, 'enableIncrementalBuild': 'true', 'id': cdt_bld + '.settings.default.builder.1', 'incrementalBuildTarget': waf_build, 'managedBuildOn': 'false', 'name': 'Gnu Make Builder', 'superClass': cdt_bld + '.settings.default.builder'}) tool_index = 1; for tool_name in ("Assembly", "GNU C++", "GNU C"): tool = self.add(doc, toolChain, 'tool', {'id': cdt_bld + '.settings.holder.' + str(tool_index), 'name': tool_name, 'superClass': cdt_bld + '.settings.holder'}) if cpppath or workspace_includes: incpaths = cdt_bld + '.settings.holder.incpaths' option = self.add(doc, tool, 'option', {'id': incpaths + '.' + str(tool_index), 'name': 'Include Paths', 'superClass': incpaths, 'valueType': 'includePath'}) for i in workspace_includes: self.add(doc, option, 'listOptionValue', {'builtIn': 'false', 'value': '"${workspace_loc:/%s/%s}"'%(appname, i)}) for i in cpppath: self.add(doc, option, 'listOptionValue', {'builtIn': 'false', 'value': '"%s"'%(i)}) if tool_name == "GNU C++" or tool_name == "GNU C": self.add(doc,tool,'inputType',{ 'id':'org.eclipse.cdt.build.core.settings.holder.inType.' + str(tool_index), \ 'languageId':'org.eclipse.cdt.core.gcc' if tool_name == "GNU C" else 'org.eclipse.cdt.core.g++','languageName':tool_name, \ 'sourceContentType':'org.eclipse.cdt.core.cSource,org.eclipse.cdt.core.cHeader', \ 'superClass':'org.eclipse.cdt.build.core.settings.holder.inType' }) tool_index += 1 if source_dirs: sourceEntries = self.add(doc, config, 'sourceEntries') for i in source_dirs: self.add(doc, sourceEntries, 'entry', {'excluding': i, 'flags': 'VALUE_WORKSPACE_PATH|RESOLVED', 'kind': 'sourcePath', 'name': ''}) self.add(doc, sourceEntries, 'entry', { 'flags': 'VALUE_WORKSPACE_PATH|RESOLVED', 'kind': 'sourcePath', 'name': i}) storageModule = self.add(doc, cconf, 'storageModule', {'moduleId': cdt_mk + '.buildtargets'}) buildTargets = self.add(doc, storageModule, 'buildTargets') def addTargetWrap(name, runAll): return self.addTarget(doc, buildTargets, executable, name, '"%s" %s'%(waf_executable, name), runAll) addTargetWrap('configure', True) addTargetWrap('dist', False) addTargetWrap('install', False) for addTgt in self.env.ECLIPSE_EXTRA_TARGETS or []: addTargetWrap(addTgt, False) storageModule = self.add(doc, cproject, 'storageModule', {'moduleId': 'cdtBuildSystem', 'version': '4.0.0'}) self.add(doc, storageModule, 'project', {'id': '%s.null.1'%appname, 'name': appname}) storageModule = self.add(doc, cproject, 'storageModule', {'moduleId': 'org.eclipse.cdt.core.LanguageSettingsProviders'}) storageModule = self.add(doc, cproject, 'storageModule', {'moduleId': 'scannerConfiguration'}) doc.appendChild(cproject) return doc def impl_create_pydevproject(self, system_path, user_path): # create a pydevproject file doc = Document() doc.appendChild(doc.createProcessingInstruction('eclipse-pydev', 'version="1.0"')) pydevproject = doc.createElement('pydev_project') prop = self.add(doc, pydevproject, 'pydev_property', 'python %d.%d'%(sys.version_info[0], sys.version_info[1])) prop.setAttribute('name', 'org.python.pydev.PYTHON_PROJECT_VERSION') prop = self.add(doc, pydevproject, 'pydev_property', 'Default') prop.setAttribute('name', 'org.python.pydev.PYTHON_PROJECT_INTERPRETER') # add waf's paths wafadmin = [p for p in system_path if p.find('wafadmin') != -1] if wafadmin: prop = self.add(doc, pydevproject, 'pydev_pathproperty', {'name':'org.python.pydev.PROJECT_EXTERNAL_SOURCE_PATH'}) for i in wafadmin: self.add(doc, prop, 'path', i) if user_path: prop = self.add(doc, pydevproject, 'pydev_pathproperty', {'name':'org.python.pydev.PROJECT_SOURCE_PATH'}) for i in user_path: self.add(doc, prop, 'path', '/${PROJECT_DIR_NAME}/'+i) doc.appendChild(pydevproject) return doc def impl_create_javaproject(self, javasrcpath, javalibpath): # create a .classpath file for java usage doc = Document() javaproject = doc.createElement('classpath') if javasrcpath: for i in javasrcpath: self.add(doc, javaproject, 'classpathentry', {'kind': 'src', 'path': i}) if javalibpath: for i in javalibpath: self.add(doc, javaproject, 'classpathentry', {'kind': 'lib', 'path': i}) self.add(doc, javaproject, 'classpathentry', {'kind': 'con', 'path': 'org.eclipse.jdt.launching.JRE_CONTAINER'}) self.add(doc, javaproject, 'classpathentry', {'kind': 'output', 'path': self.bldnode.name }) doc.appendChild(javaproject) return doc def addDictionary(self, doc, parent, k, v): dictionary = self.add(doc, parent, 'dictionary') self.add(doc, dictionary, 'key', k) self.add(doc, dictionary, 'value', v) return dictionary def addTarget(self, doc, buildTargets, executable, name, buildTarget, runAllBuilders=True): target = self.add(doc, buildTargets, 'target', {'name': name, 'path': '', 'targetID': oe_cdt + '.build.MakeTargetBuilder'}) self.add(doc, target, 'buildCommand', executable) self.add(doc, target, 'buildArguments', None) self.add(doc, target, 'buildTarget', buildTarget) self.add(doc, target, 'stopOnError', 'true') self.add(doc, target, 'useDefaultCommand', 'false') self.add(doc, target, 'runAllBuilders', str(runAllBuilders).lower()) def add(self, doc, parent, tag, value = None): el = doc.createElement(tag) if (value): if type(value) == type(str()): el.appendChild(doc.createTextNode(value)) elif type(value) == type(dict()): self.setAttributes(el, value) parent.appendChild(el) return el def setAttributes(self, node, attrs): for k, v in attrs.items(): node.setAttribute(k, v)