1#! /usr/bin/env python
2# encoding: utf-8
3# Eclipse CDT 5.0 generator for Waf
4# Richard Quirk 2009-1011 (New BSD License)
5# Thomas Nagy 2011 (ported to Waf 1.6)
6
7"""
8Usage:
9
10def options(opt):
11	opt.load('eclipse')
12
13$ waf configure eclipse
14"""
15
16import sys, os
17from waflib import Utils, Logs, Context, Build, TaskGen, Scripting, Errors, Node
18from xml.dom.minidom import Document
19
20STANDARD_INCLUDES = [ '/usr/local/include', '/usr/include' ]
21
22oe_cdt = 'org.eclipse.cdt'
23cdt_mk = oe_cdt + '.make.core'
24cdt_core = oe_cdt + '.core'
25cdt_bld = oe_cdt + '.build.core'
26extbuilder_dir = '.externalToolBuilders'
27extbuilder_name = 'Waf_Builder.launch'
28
29class eclipse(Build.BuildContext):
30	cmd = 'eclipse'
31	fun = Scripting.default_cmd
32
33	def execute(self):
34		"""
35		Entry point
36		"""
37		self.restore()
38		if not self.all_envs:
39			self.load_envs()
40		self.recurse([self.run_dir])
41
42		appname = getattr(Context.g_module, Context.APPNAME, os.path.basename(self.srcnode.abspath()))
43		self.create_cproject(appname, pythonpath=self.env['ECLIPSE_PYTHON_PATH'])
44
45	# Helper to dump the XML document content to XML with UTF-8 encoding
46	def write_conf_to_xml(self, filename, document):
47		self.srcnode.make_node(filename).write(document.toprettyxml(encoding='UTF-8'), flags='wb')
48
49	def create_cproject(self, appname, workspace_includes=[], pythonpath=[]):
50		"""
51		Create the Eclipse CDT .project and .cproject files
52		@param appname The name that will appear in the Project Explorer
53		@param build The BuildContext object to extract includes from
54		@param workspace_includes Optional project includes to prevent
55			  "Unresolved Inclusion" errors in the Eclipse editor
56		@param pythonpath Optional project specific python paths
57		"""
58		hasc = hasjava = haspython = False
59		source_dirs = []
60		cpppath = self.env['CPPPATH']
61		javasrcpath = []
62		javalibpath = []
63		includes = STANDARD_INCLUDES
64		if sys.platform != 'win32':
65			cc = self.env.CC or self.env.CXX
66			if cc:
67				cmd = cc + ['-xc++', '-E', '-Wp,-v', '-']
68				try:
69					gccout = self.cmd_and_log(cmd, output=Context.STDERR, quiet=Context.BOTH, input='\n'.encode()).splitlines()
70				except Errors.WafError:
71					pass
72				else:
73					includes = []
74					for ipath in gccout:
75						if ipath.startswith(' /'):
76							includes.append(ipath[1:])
77			cpppath += includes
78		Logs.warn('Generating Eclipse CDT project files')
79
80		for g in self.groups:
81			for tg in g:
82				if not isinstance(tg, TaskGen.task_gen):
83					continue
84
85				tg.post()
86
87				# Add local Python modules paths to configuration so object resolving will work in IDE
88				# This may also contain generated files (ie. pyqt5 or protoc) that get picked from build
89				if 'py' in tg.features:
90					pypath = tg.path.relpath()
91					py_installfrom = getattr(tg, 'install_from', None)
92					if isinstance(py_installfrom, Node.Node):
93						pypath = py_installfrom.path_from(self.root.make_node(self.top_dir))
94					if pypath not in pythonpath:
95						pythonpath.append(pypath)
96					haspython = True
97
98				# Add Java source directories so object resolving works in IDE
99				# This may also contain generated files (ie. protoc) that get picked from build
100				if 'javac' in tg.features:
101					java_src = tg.path.relpath()
102					java_srcdir = getattr(tg.javac_task, 'srcdir', None)
103					if java_srcdir:
104						if isinstance(java_srcdir, Node.Node):
105							java_srcdir = [java_srcdir]
106						for x in Utils.to_list(java_srcdir):
107							x = x.path_from(self.root.make_node(self.top_dir))
108							if x not in javasrcpath:
109								javasrcpath.append(x)
110					else:
111						if java_src not in javasrcpath:
112							javasrcpath.append(java_src)
113					hasjava = True
114
115					# Check if there are external dependencies and add them as external jar so they will be resolved by Eclipse
116					usedlibs=getattr(tg, 'use', [])
117					for x in Utils.to_list(usedlibs):
118						for cl in Utils.to_list(tg.env['CLASSPATH_'+x]):
119							if cl not in javalibpath:
120								javalibpath.append(cl)
121
122				if not getattr(tg, 'link_task', None):
123					continue
124
125				features = Utils.to_list(getattr(tg, 'features', ''))
126
127				is_cc = 'c' in features or 'cxx' in features
128
129				incnodes = tg.to_incnodes(tg.to_list(getattr(tg, 'includes', [])) + tg.env['INCLUDES'])
130				for p in incnodes:
131					path = p.path_from(self.srcnode)
132
133					if (path.startswith("/")):
134						cpppath.append(path)
135					else:
136						workspace_includes.append(path)
137
138					if is_cc and path not in source_dirs:
139						source_dirs.append(path)
140
141					hasc = True
142
143		waf_executable = os.path.abspath(sys.argv[0])
144		project = self.impl_create_project(sys.executable, appname, hasc, hasjava, haspython, waf_executable)
145		self.write_conf_to_xml('.project', project)
146
147		if hasc:
148			project = self.impl_create_cproject(sys.executable, waf_executable, appname, workspace_includes, cpppath, source_dirs)
149			self.write_conf_to_xml('.cproject', project)
150
151		if haspython:
152			project = self.impl_create_pydevproject(sys.path, pythonpath)
153			self.write_conf_to_xml('.pydevproject', project)
154
155		if hasjava:
156			project = self.impl_create_javaproject(javasrcpath, javalibpath)
157			self.write_conf_to_xml('.classpath', project)
158
159	def impl_create_project(self, executable, appname, hasc, hasjava, haspython, waf_executable):
160		doc = Document()
161		projectDescription = doc.createElement('projectDescription')
162		self.add(doc, projectDescription, 'name', appname)
163		self.add(doc, projectDescription, 'comment')
164		self.add(doc, projectDescription, 'projects')
165		buildSpec = self.add(doc, projectDescription, 'buildSpec')
166		buildCommand = self.add(doc, buildSpec, 'buildCommand')
167		self.add(doc, buildCommand, 'triggers', 'clean,full,incremental,')
168		arguments = self.add(doc, buildCommand, 'arguments')
169		dictionaries = {}
170
171		# If CDT is present, instruct this one to call waf as it is more flexible (separate build/clean ...)
172		if hasc:
173			self.add(doc, buildCommand, 'name', oe_cdt + '.managedbuilder.core.genmakebuilder')
174			# the default make-style targets are overwritten by the .cproject values
175			dictionaries = {
176					cdt_mk + '.contents': cdt_mk + '.activeConfigSettings',
177					cdt_mk + '.enableAutoBuild': 'false',
178					cdt_mk + '.enableCleanBuild': 'true',
179					cdt_mk + '.enableFullBuild': 'true',
180					}
181		else:
182			# Otherwise for Java/Python an external builder tool is created that will call waf build
183			self.add(doc, buildCommand, 'name', 'org.eclipse.ui.externaltools.ExternalToolBuilder')
184			dictionaries = {
185					'LaunchConfigHandle': '<project>/%s/%s'%(extbuilder_dir, extbuilder_name),
186					}
187			# The definition is in a separate directory XML file
188			try:
189				os.mkdir(extbuilder_dir)
190			except OSError:
191				pass	# Ignore error if already exists
192
193			# Populate here the external builder XML calling waf
194			builder = Document()
195			launchConfiguration = doc.createElement('launchConfiguration')
196			launchConfiguration.setAttribute('type', 'org.eclipse.ui.externaltools.ProgramBuilderLaunchConfigurationType')
197			self.add(doc, launchConfiguration, 'booleanAttribute', {'key': 'org.eclipse.debug.ui.ATTR_LAUNCH_IN_BACKGROUND', 'value': 'false'})
198			self.add(doc, launchConfiguration, 'booleanAttribute', {'key': 'org.eclipse.ui.externaltools.ATTR_TRIGGERS_CONFIGURED', 'value': 'true'})
199			self.add(doc, launchConfiguration, 'stringAttribute', {'key': 'org.eclipse.ui.externaltools.ATTR_LOCATION', 'value': waf_executable})
200			self.add(doc, launchConfiguration, 'stringAttribute', {'key': 'org.eclipse.ui.externaltools.ATTR_RUN_BUILD_KINDS', 'value': 'full,incremental,'})
201			self.add(doc, launchConfiguration, 'stringAttribute', {'key': 'org.eclipse.ui.externaltools.ATTR_TOOL_ARGUMENTS', 'value': 'build'})
202			self.add(doc, launchConfiguration, 'stringAttribute', {'key': 'org.eclipse.ui.externaltools.ATTR_WORKING_DIRECTORY', 'value': '${project_loc}'})
203			builder.appendChild(launchConfiguration)
204			# And write the XML to the file references before
205			self.write_conf_to_xml('%s%s%s'%(extbuilder_dir, os.path.sep, extbuilder_name), builder)
206
207
208		for k, v in dictionaries.items():
209			self.addDictionary(doc, arguments, k, v)
210
211		natures = self.add(doc, projectDescription, 'natures')
212
213		if hasc:
214			nature_list = """
215				core.ccnature
216				managedbuilder.core.ScannerConfigNature
217				managedbuilder.core.managedBuildNature
218				core.cnature
219			""".split()
220			for n in nature_list:
221				self.add(doc, natures, 'nature', oe_cdt + '.' + n)
222
223		if haspython:
224			self.add(doc, natures, 'nature', 'org.python.pydev.pythonNature')
225		if hasjava:
226			self.add(doc, natures, 'nature', 'org.eclipse.jdt.core.javanature')
227
228		doc.appendChild(projectDescription)
229		return doc
230
231	def impl_create_cproject(self, executable, waf_executable, appname, workspace_includes, cpppath, source_dirs=[]):
232		doc = Document()
233		doc.appendChild(doc.createProcessingInstruction('fileVersion', '4.0.0'))
234		cconf_id = cdt_core + '.default.config.1'
235		cproject = doc.createElement('cproject')
236		storageModule = self.add(doc, cproject, 'storageModule',
237				{'moduleId': cdt_core + '.settings'})
238		cconf = self.add(doc, storageModule, 'cconfiguration', {'id':cconf_id})
239
240		storageModule = self.add(doc, cconf, 'storageModule',
241				{'buildSystemId': oe_cdt + '.managedbuilder.core.configurationDataProvider',
242				 'id': cconf_id,
243				 'moduleId': cdt_core + '.settings',
244				 'name': 'Default'})
245
246		self.add(doc, storageModule, 'externalSettings')
247
248		extensions = self.add(doc, storageModule, 'extensions')
249		extension_list = """
250			VCErrorParser
251			MakeErrorParser
252			GCCErrorParser
253			GASErrorParser
254			GLDErrorParser
255		""".split()
256		self.add(doc, extensions, 'extension', {'id': cdt_core + '.ELF', 'point':cdt_core + '.BinaryParser'})
257		for e in extension_list:
258			self.add(doc, extensions, 'extension', {'id': cdt_core + '.' + e, 'point':cdt_core + '.ErrorParser'})
259
260		storageModule = self.add(doc, cconf, 'storageModule',
261				{'moduleId': 'cdtBuildSystem', 'version': '4.0.0'})
262		config = self.add(doc, storageModule, 'configuration',
263					{'artifactName': appname,
264					 'id': cconf_id,
265					 'name': 'Default',
266					 'parent': cdt_bld + '.prefbase.cfg'})
267		folderInfo = self.add(doc, config, 'folderInfo',
268							{'id': cconf_id+'.', 'name': '/', 'resourcePath': ''})
269
270		toolChain = self.add(doc, folderInfo, 'toolChain',
271				{'id': cdt_bld + '.prefbase.toolchain.1',
272				 'name': 'No ToolChain',
273				 'resourceTypeBasedDiscovery': 'false',
274				 'superClass': cdt_bld + '.prefbase.toolchain'})
275
276		self.add(doc, toolChain, 'targetPlatform', {'binaryParser': 'org.eclipse.cdt.core.ELF', 'id': cdt_bld + '.prefbase.toolchain.1', 'name': ''})
277
278		waf_build = '"%s" %s'%(waf_executable, eclipse.fun)
279		waf_clean = '"%s" clean'%(waf_executable)
280		self.add(doc, toolChain, 'builder',
281					{'autoBuildTarget': waf_build,
282					 'command': executable,
283					 'enableAutoBuild': 'false',
284					 'cleanBuildTarget': waf_clean,
285					 'enableIncrementalBuild': 'true',
286					 'id': cdt_bld + '.settings.default.builder.1',
287					 'incrementalBuildTarget': waf_build,
288					 'managedBuildOn': 'false',
289					 'name': 'Gnu Make Builder',
290					 'superClass': cdt_bld + '.settings.default.builder'})
291
292		tool_index = 1;
293		for tool_name in ("Assembly", "GNU C++", "GNU C"):
294			tool = self.add(doc, toolChain, 'tool',
295					{'id': cdt_bld + '.settings.holder.' + str(tool_index),
296					 'name': tool_name,
297					 'superClass': cdt_bld + '.settings.holder'})
298			if cpppath or workspace_includes:
299				incpaths = cdt_bld + '.settings.holder.incpaths'
300				option = self.add(doc, tool, 'option',
301						{'id': incpaths + '.' +  str(tool_index),
302						 'name': 'Include Paths',
303						 'superClass': incpaths,
304						 'valueType': 'includePath'})
305				for i in workspace_includes:
306					self.add(doc, option, 'listOptionValue',
307								{'builtIn': 'false',
308								'value': '"${workspace_loc:/%s/%s}"'%(appname, i)})
309				for i in cpppath:
310					self.add(doc, option, 'listOptionValue',
311								{'builtIn': 'false',
312								'value': '"%s"'%(i)})
313			if tool_name == "GNU C++" or tool_name == "GNU C":
314				self.add(doc,tool,'inputType',{ 'id':'org.eclipse.cdt.build.core.settings.holder.inType.' + str(tool_index), \
315					'languageId':'org.eclipse.cdt.core.gcc' if tool_name == "GNU C" else 'org.eclipse.cdt.core.g++','languageName':tool_name, \
316					'sourceContentType':'org.eclipse.cdt.core.cSource,org.eclipse.cdt.core.cHeader', \
317					'superClass':'org.eclipse.cdt.build.core.settings.holder.inType' })
318			tool_index += 1
319
320		if source_dirs:
321			sourceEntries = self.add(doc, config, 'sourceEntries')
322			for i in source_dirs:
323				 self.add(doc, sourceEntries, 'entry',
324							{'excluding': i,
325							'flags': 'VALUE_WORKSPACE_PATH|RESOLVED',
326							'kind': 'sourcePath',
327							'name': ''})
328				 self.add(doc, sourceEntries, 'entry',
329							{
330							'flags': 'VALUE_WORKSPACE_PATH|RESOLVED',
331							'kind': 'sourcePath',
332							'name': i})
333
334		storageModule = self.add(doc, cconf, 'storageModule',
335							{'moduleId': cdt_mk + '.buildtargets'})
336		buildTargets = self.add(doc, storageModule, 'buildTargets')
337		def addTargetWrap(name, runAll):
338			return self.addTarget(doc, buildTargets, executable, name,
339								'"%s" %s'%(waf_executable, name), runAll)
340		addTargetWrap('configure', True)
341		addTargetWrap('dist', False)
342		addTargetWrap('install', False)
343		addTargetWrap('check', False)
344
345		storageModule = self.add(doc, cproject, 'storageModule',
346							{'moduleId': 'cdtBuildSystem',
347							 'version': '4.0.0'})
348
349		self.add(doc, storageModule, 'project', {'id': '%s.null.1'%appname, 'name': appname})
350
351		doc.appendChild(cproject)
352		return doc
353
354	def impl_create_pydevproject(self, system_path, user_path):
355		# create a pydevproject file
356		doc = Document()
357		doc.appendChild(doc.createProcessingInstruction('eclipse-pydev', 'version="1.0"'))
358		pydevproject = doc.createElement('pydev_project')
359		prop = self.add(doc, pydevproject,
360					   'pydev_property',
361					   'python %d.%d'%(sys.version_info[0], sys.version_info[1]))
362		prop.setAttribute('name', 'org.python.pydev.PYTHON_PROJECT_VERSION')
363		prop = self.add(doc, pydevproject, 'pydev_property', 'Default')
364		prop.setAttribute('name', 'org.python.pydev.PYTHON_PROJECT_INTERPRETER')
365		# add waf's paths
366		wafadmin = [p for p in system_path if p.find('wafadmin') != -1]
367		if wafadmin:
368			prop = self.add(doc, pydevproject, 'pydev_pathproperty',
369					{'name':'org.python.pydev.PROJECT_EXTERNAL_SOURCE_PATH'})
370			for i in wafadmin:
371				self.add(doc, prop, 'path', i)
372		if user_path:
373			prop = self.add(doc, pydevproject, 'pydev_pathproperty',
374					{'name':'org.python.pydev.PROJECT_SOURCE_PATH'})
375			for i in user_path:
376				self.add(doc, prop, 'path', '/${PROJECT_DIR_NAME}/'+i)
377
378		doc.appendChild(pydevproject)
379		return doc
380
381	def impl_create_javaproject(self, javasrcpath, javalibpath):
382		# create a .classpath file for java usage
383		doc = Document()
384		javaproject = doc.createElement('classpath')
385		if javasrcpath:
386			for i in javasrcpath:
387				self.add(doc, javaproject, 'classpathentry',
388					{'kind': 'src', 'path': i})
389
390		if javalibpath:
391			for i in javalibpath:
392				self.add(doc, javaproject, 'classpathentry',
393					{'kind': 'lib', 'path': i})
394
395		self.add(doc, javaproject, 'classpathentry', {'kind': 'con', 'path': 'org.eclipse.jdt.launching.JRE_CONTAINER'})
396		self.add(doc, javaproject, 'classpathentry', {'kind': 'output', 'path': self.bldnode.name })
397		doc.appendChild(javaproject)
398		return doc
399
400	def addDictionary(self, doc, parent, k, v):
401		dictionary = self.add(doc, parent, 'dictionary')
402		self.add(doc, dictionary, 'key', k)
403		self.add(doc, dictionary, 'value', v)
404		return dictionary
405
406	def addTarget(self, doc, buildTargets, executable, name, buildTarget, runAllBuilders=True):
407		target = self.add(doc, buildTargets, 'target',
408						{'name': name,
409						 'path': '',
410						 'targetID': oe_cdt + '.build.MakeTargetBuilder'})
411		self.add(doc, target, 'buildCommand', executable)
412		self.add(doc, target, 'buildArguments', None)
413		self.add(doc, target, 'buildTarget', buildTarget)
414		self.add(doc, target, 'stopOnError', 'true')
415		self.add(doc, target, 'useDefaultCommand', 'false')
416		self.add(doc, target, 'runAllBuilders', str(runAllBuilders).lower())
417
418	def add(self, doc, parent, tag, value = None):
419		el = doc.createElement(tag)
420		if (value):
421			if type(value) == type(str()):
422				el.appendChild(doc.createTextNode(value))
423			elif type(value) == type(dict()):
424				self.setAttributes(el, value)
425		parent.appendChild(el)
426		return el
427
428	def setAttributes(self, node, attrs):
429		for k, v in attrs.items():
430			node.setAttribute(k, v)
431
432