bump product version to 5.0.4.1
[LibreOffice.git] / bin / gbuild-to-ide
blob0ec56d120cd8c68e91737dbf32b69b90dbe0642b
1 #! /usr/bin/env python3
2 # -*- Mode: python; tab-width: 4; indent-tabs-mode: t -*-
4 # This file is part of the LibreOffice project.
6 # This Source Code Form is subject to the terms of the Mozilla Public
7 # License, v. 2.0. If a copy of the MPL was not distributed with this
8 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
11 import argparse
12 import os
13 import os.path
14 import shutil
15 import re
16 import sys
17 import uuid
18 import json
19 import xml.etree.ElementTree as ET
20 import xml.dom.minidom as minidom
23 class GbuildParserState:
25     def __init__(self):
26         self.target = None
27         self.ilib = None
28         self.include = []
29         self.defs = {}
30         self.cxxobjects = []
31         self.cxxflags = []
32         self.linked_libs = []
33         self.include_sys = []
36 class GbuildLinkTarget:
38     def __init__(self, name, location, include, include_sys, defs, cxxobjects, cxxflags, linked_libs):
39         (self.name, self.location, self.include, self.include_sys, self.defs, self.cxxobjects, self.cxxflags, self.linked_libs) = (
40             name, location, include, include_sys, defs, cxxobjects, cxxflags, linked_libs)
42     def short_name(self):
43         return self.name
45     def is_empty(self):
46         return not self.include and not self.defs and not self.cxxobjects and not self.linked_libs
48     def __str__(self):
49         return '%s at %s with include path: %s, isystem includes: %s, defines: %s, objects: %s, cxxflags: %s and linked libs: %s' % (
50             self.short_name(), self.location, self.include, self.include_sys, self.defs, self.cxxobjects,
51             self.cxxflags, self.linked_libs)
54 class GbuildLib(GbuildLinkTarget):
56     def __init__(self, name, library, location, include, include_sys, defs, cxxobjects, cxxflags, linked_libs):
57         GbuildLinkTarget.__init__(self, name, location, include, include_sys, defs, cxxobjects, cxxflags, linked_libs)
58         self.library = library
60     def short_name(self):
61         """Return the short name of target based on the Library_* makefile name"""
62         return 'Library %s' % self.name
64     def target_name(self):
65         return 'Library_%s' % self.library
67     def library_name(self):
68         return self.library
71 class GbuildExe(GbuildLinkTarget):
73     def __init__(self, name, executable, location, include, include_sys, defs, cxxobjects, cxxflags, linked_libs):
74         GbuildLinkTarget.__init__(self, name, location, include, include_sys, defs, cxxobjects, cxxflags, linked_libs)
75         self.executable = executable
77     def short_name(self):
78         """Return the short name of target based on the Executable_* makefile name"""
79         return 'Executable %s' % self.name
81     def target_name(self):
82         return 'Executable_%s' % self.executable
85 class GbuildParser:
86     makecmdpattern = re.compile('^MAKE_COMMAND := (.*)')
87     srcdirpattern = re.compile('^SRCDIR = (.*)')
88     builddirpattern = re.compile('^BUILDDIR = (.*)')
89     instdirpattern = re.compile('^INSTDIR = (.*)')
90     binpathpattern = re.compile('^GPERF = (.*)gperf(.exe)?')
91     libnamespattern = re.compile('^gb_Library_ILIBFILENAMES := (.*)')
92     exenamepattern = re.compile('^gb_Executable_FILENAMES_FOR_BUILD := (.*)')
93     rulepattern = re.compile('^(.+?):( .*)?$')
94     libpattern = re.compile('#  [a-z]+ to execute \(from [\'`](.*)/Library_(.*)\.mk\', line [0-9]*\):')
95     exepattern = re.compile('#  [a-z]+ to execute \(from [\'`](.*)/Executable_(.*)\.mk\', line [0-9]*\):')
96     includepattern = re.compile('-I(\S+)')
97     isystempattern = re.compile('-isystem\s*(\S+)')
98     defspattern = re.compile('# DEFS := (.*)')
99     cxxpattern = re.compile('# CXXOBJECTS := (.*)')
100     linkedlibspattern = re.compile('# LINKED_LIBS := (.*)')
101     ilibpattern = re.compile('# ILIBTARGET := (.*)')
102     warningpattern = re.compile('-W\S+')
104     def __init__(self):
105         (self.makecmd, self.srcdir, self.builddir, self.instdir, self.libs,
106          self.exes, self.libnames, self.exenames, self.target_by_path, self.target_by_location) = ('', '', '', '', [], [], {}, {}, {}, {})
108     def __mapping_to_dict(self, mapping):
109         mapping_dict = {}
110         for item in mapping.split(' '):
111             library, target = item.split(':')
112             mapping_dict[target] = library
113         return mapping_dict
115     def _parse_hash(self, line, state):
116         libmatch = GbuildParser.libpattern.match(line)
117         if libmatch:
118             libname = self.libnames.get(state.ilib, None)
119             self.libs.append(
120                 GbuildLib(libmatch.group(2), libname, libmatch.group(1),
121                           state.include, state.include_sys, state.defs, state.cxxobjects,
122                           state.cxxflags, state.linked_libs))
123             state = GbuildParserState()
124             return state
125         exematch = GbuildParser.exepattern.match(line)
126         if exematch:
127             exename = self.exenames.get(state.target, None)
128             self.exes.append(
129                 GbuildExe(exematch.group(2), exename, exematch.group(1),
130                           state.include, state.include_sys, state.defs, state.cxxobjects,
131                           state.cxxflags, state.linked_libs))
132             state = GbuildParserState()
133             return state
134         if line.find('# INCLUDE :=') == 0:
135             isystemmatch = GbuildParser.isystempattern.findall(line)
136             if isystemmatch:
137                 state.include_sys = isystemmatch
138             state.include = [includeswitch.strip() for includeswitch in GbuildParser.includepattern.findall(line) if
139                              len(includeswitch) > 2]
140             return state
141         defsmatch = GbuildParser.defspattern.match(line)
142         if defsmatch:
143             alldefs = [defswitch.strip()[2:] for defswitch in defsmatch.group(1).split(' ') if len(defswitch) > 2]
144             for d in alldefs:
145                 defparts = d.split('=')
146                 if len(defparts) == 1:
147                     defparts.append(None)
148                 state.defs[defparts[0]] = defparts[1]
149             state.defs["LIBO_INTERNAL_ONLY"] = None
150             return state
151         cxxmatch = GbuildParser.cxxpattern.match(line)
152         if cxxmatch:
153             state.cxxobjects = [obj for obj in cxxmatch.group(1).split(' ') if len(obj) > 0]
154             return state
155         linkedlibsmatch = GbuildParser.linkedlibspattern.match(line)
156         if linkedlibsmatch:
157             state.linked_libs = linkedlibsmatch.group(1).strip().split(' ')
158             return state
159         ilibmatch = GbuildParser.ilibpattern.match(line)
160         if ilibmatch:
161             state.ilib = os.path.basename(ilibmatch.group(1))
162             return state
163         if line.find('# T_CXXFLAGS :=') == 0:
164             state.cxxflags = [cxxflag.strip() for cxxflag in GbuildParser.warningpattern.sub('', line.replace('# T_CXXFLAGS :=', '')).split(' ') if len(cxxflag) > 1]
165             return state
166         # we could match a lot of other stuff here if needed for integration rpaths etc.
167         return state
169     def parse(self, gbuildstate):
170         state = GbuildParserState()
171         for line in gbuildstate:
172             line = line.rstrip('\r\n')
173             if line.startswith('#'):
174                 state = self._parse_hash(line, state)
175             else:
176                 makecmdmatch = GbuildParser.makecmdpattern.match(line)
177                 if makecmdmatch:
178                     self.makecmd = makecmdmatch.group(1)
179                     # FIXME: Hack
180                     if self.makecmd == 'make':
181                         self.makecmd = '/usr/bin/make'
182                     continue
183                 srcdirmatch = GbuildParser.srcdirpattern.match(line)
184                 if srcdirmatch:
185                     self.srcdir = srcdirmatch.group(1)
186                     continue
187                 builddirmatch = GbuildParser.builddirpattern.match(line)
188                 if builddirmatch:
189                     self.builddir = builddirmatch.group(1)
190                     continue
191                 instdirmatch = GbuildParser.instdirpattern.match(line)
192                 if instdirmatch:
193                     self.instdir = instdirmatch.group(1)
194                     continue
195                 binpathmatch = GbuildParser.binpathpattern.match(line)
196                 if binpathmatch:
197                     self.binpath = binpathmatch.group(1)
198                     continue
199                 rulematch = self.rulepattern.match(line)
200                 if rulematch:
201                     if len(rulematch.groups()) == 2 \
202                        and rulematch.group(2) is not None \
203                        and ':=' in rulematch.group(2):
204                         # Hack to make GNU make >= 4.x happy
205                         state = self._parse_hash('#' + rulematch.group(2), state)
206                     else:
207                         state.target = os.path.basename(rulematch.group(1))
208                     continue
209                 libnamesmatch = GbuildParser.libnamespattern.match(line)
210                 if libnamesmatch:
211                     self.libnames = self.__mapping_to_dict(libnamesmatch.group(1))
212                     continue
213                 exenamesmatch = GbuildParser.exenamepattern.match(line)
214                 if exenamesmatch:
215                     self.exenames = self.__mapping_to_dict(exenamesmatch.group(1))
216                     continue
217                 state = GbuildParserState()
219         for target in set(self.libs) | set(self.exes):
220             if target.location not in self.target_by_location:
221                 self.target_by_location[target.location] = set()
222             self.target_by_location[target.location] |= set([target])
223             for cxx in target.cxxobjects:
224                 path = '/'.join(cxx.split('/')[:-1])
225                 if path not in self.target_by_path:
226                     self.target_by_path[path] = set()
227                 self.target_by_path[path] |= set([target])
228         for path in self.target_by_path:
229             if len(self.target_by_path[path]) > 1:
230                 print('fdo#70422: multiple target use dir %s: %s' % (
231                     path, ', '.join([target.short_name() for target in self.target_by_path[path]])))
233         return self
236 class IdeIntegrationGenerator:
238     def __init__(self, gbuildparser, ide):
239         self.gbuildparser = gbuildparser
240         self.ide = ide
242     def emit(self):
243         pass
245 class EclipseCDTIntegrationGenerator(IdeIntegrationGenerator):
246     def __init__(self, gbuildparser, ide):
247         IdeIntegrationGenerator.__init__(self, gbuildparser, ide)
248         self.oe_cdt = 'org.eclipse.cdt'
249         self.cdt_mb = self.oe_cdt + '.managebuilder.core'
250         self.cdt_core = self.oe_cdt + '.core'
252     def generate_project_file(self, name, comment, xmlversion, encoding):
254         projectfiletemplate = """
255 <?xml version="%(xmlversion)s" encoding="%(encoding)s"?>
256 <projectDescription>
257         <name>%(name)s</name>
258         <comment>%(comment)s</comment>
259         <projects>
260         </projects>
261         <buildSpec>
262                 <buildCommand>
263                         <name>"""+ self.cdt_mb +""".genmakebuilder</name>
264                         <triggers>clean,full,incremental,</triggers>
265                         <arguments>
266                         </arguments>
267                 </buildCommand>
268                 <buildCommand>
269                         <name>"""+ self.cdt_mb +""".ScannerConfigBuilder</name>
270                         <triggers>full,incremental,</triggers>
271                         <arguments>
272                         </arguments>
273                 </buildCommand>
274         </buildSpec>
275         <natures>
276                 <nature>""" + self.cdt_core + """.cnature</nature>
277                 <nature>""" + self.cdt_core + """.ccnature</nature>
278                 <nature>""" + self.cdt_mb + """.managedBuildNature</nature>
279                 <nature>""" + self.cdt_mb + """.ScannerConfigNature</nature>
280         </natures>
281 </projectDescription>
284         return projectfiletemplate % {'name': name, 'comment': comment, 'xmlversion': xmlversion, 'encoding':encoding}
286 class DebugIntegrationGenerator(IdeIntegrationGenerator):
288     def __init__(self, gbuildparser, ide):
289         IdeIntegrationGenerator.__init__(self, gbuildparser, ide)
291     def emit(self):
292         print(self.gbuildparser.srcdir)
293         print(self.gbuildparser.builddir)
294         for lib in self.gbuildparser.libs:
295             print(lib)
296         for exe in self.gbuildparser.exes:
297             print(exe)
300 class VimIntegrationGenerator(IdeIntegrationGenerator):
302     def __init__(self, gbuildparser, ide):
303         IdeIntegrationGenerator.__init__(self, gbuildparser, ide)
305     def emit(self):
306         global_list = []
307         for lib in self.gbuildparser.libs:
308             entries = []
309             for file in lib.cxxobjects:
310                 filePath = os.path.join(self.gbuildparser.srcdir, file) + ".cxx"
311                 entry = {'directory': lib.location, 'file': filePath, 'command': self.generateCommand(lib, filePath)}
312                 entries.append(entry)
313             global_list.extend(entries)
314         export_file = open('compile_commands.json', 'w')
315         json.dump(global_list, export_file)
317     def generateCommand(self, lib, file):
318         command = 'clang++ '
319         for key, value in lib.defs.items():
320             command += ' -D'
321             command += key
322             if value is not None:
323                 command += '='
324                 command += value
325         for include in lib.include:
326             command += ' -I'
327             command += include
328         for isystem in lib.include_sys:
329             command += ' -isystem '
330             command += isystem
331         for cxxflag in lib.cxxflags:
332             command += ' '
333             command += cxxflag
334         command += ' -c '
335         command += file
336         return command.replace('-std=gnu++11', '-std=c++11')
339 class KdevelopIntegrationGenerator(IdeIntegrationGenerator):
341     def encode_int(self, i):
342         temp = '%08x' % i
343         return '\\x%s\\x%s\\x%s\\x%s' % (temp[0:2], temp[2:4], temp[4:6], temp[6:8])
345     def encode_string(self, string):
346         result = self.encode_int(len(string) * 2)
347         for c in string.encode('utf-16-be'):
348             if c in range(32, 126):
349                 result += chr(c)
350             else:
351                 result += '\\x%02x' % c
352         return result
354     def generate_buildsystemconfigtool(self, configid, tool, args, exe, typenr):
355         return KdevelopIntegrationGenerator.buildsystemconfigtooltemplate % {'configid': configid, 'tool': tool,
356                                                                              'args': args, 'exe': exe, 'typenr': typenr}
358     buildsystemconfigtooltemplate = """
359 [CustomBuildSystem][BuildConfig%(configid)d][Tool%(tool)s]
360 Arguments=%(args)s
361 Enabled=true
362 Environment=
363 Executable=%(exe)s
364 Type=%(typenr)d
368     def generate_buildsystemconfig(self, configid, moduledir, builddir, title, buildparms=''):
369         result = KdevelopIntegrationGenerator.buildsystemconfigtemplate % {'configid': configid, 'builddir': builddir,
370                                                                            'title': title}
371         result += self.generate_buildsystemconfigtool(configid, 'Clean', 'clean %s' % buildparms,
372                                                       self.gbuildparser.makecmd, 3)
373         result += self.generate_buildsystemconfigtool(configid, 'Build', 'all %s' % buildparms,
374                                                       self.gbuildparser.makecmd, 0)
375         return result
377     buildsystemconfigtemplate = """
378 [CustomBuildSystem][BuildConfig%(configid)d]
379 BuildDir=file://%(builddir)s
380 Title=%(title)s
384     def generate_buildsystem(self, moduledir):
385         result = KdevelopIntegrationGenerator.buildsystemtemplate % {'defaultconfigid': 0}
386         result += self.generate_buildsystemconfig(0, moduledir, moduledir, 'Module Build -- Release')
387         result += self.generate_buildsystemconfig(1, moduledir, self.gbuildparser.builddir, 'Full Build -- Release')
388         result += self.generate_buildsystemconfig(2, moduledir, moduledir, 'Module Build -- Debug', 'debug=T')
389         result += self.generate_buildsystemconfig(3, moduledir, self.gbuildparser.builddir, 'Full Build -- Debug',
390                                                   'debug=T')
391         return result
393     buildsystemtemplate = """
394 [CustomBuildSystem]
395 CurrentConfiguration=BuildConfig%(defaultconfigid)d
399     def generate_launch(self, launchid, launchname, executablepath, args, workdir):
400         return KdevelopIntegrationGenerator.launchtemplate % {'launchid': launchid, 'launchname': launchname,
401                                                               'executablepath': executablepath, 'args': args,
402                                                               'workdir': workdir}
404     launchtemplate = """
405 [Launch][Launch Configuration %(launchid)d]
406 Configured Launch Modes=execute
407 Configured Launchers=nativeAppLauncher
408 Name=%(launchname)s
409 Type=Native Application
411 [Launch][Launch Configuration %(launchid)d][Data]
412 Arguments=%(args)s
413 Dependencies=@Variant(\\x00\\x00\\x00\\t\\x00\\x00\\x00\\x00\\x00)
414 Dependency Action=Nothing
415 EnvironmentGroup=default
416 Executable=file://%(executablepath)s
417 External Terminal=konsole --noclose --workdir %%workdir -e %%exe
418 Project Target=
419 Use External Terminal=false
420 Working Directory=file://%(workdir)s
421 isExecutable=true
425     def generate_launches(self, moduledir):
426         launches = ','.join(['Launch Configuration %d' % i for i in range(7)])
427         result = KdevelopIntegrationGenerator.launchestemplate % {'launches': launches}
428         result += self.generate_launch(0, 'Local tests -- quick tests (unitcheck)', self.gbuildparser.makecmd,
429                                        'unitcheck', moduledir)
430         result += self.generate_launch(1, 'Local tests -- slow tests (unitcheck, slowcheck)', self.gbuildparser.makecmd,
431                                        'unitcheck slowcheck', moduledir)
432         result += self.generate_launch(2, 'Local tests -- integration tests (unitcheck, slowcheck, subsequentcheck)',
433                                        self.gbuildparser.makecmd, 'unitcheck slowcheck subsequentcheck', moduledir)
434         result += self.generate_launch(3, 'Global tests -- quick tests (unitcheck)', self.gbuildparser.makecmd,
435                                        'unitcheck', self.gbuildparser.builddir)
436         result += self.generate_launch(4, 'Global tests -- slow tests (unitcheck, slowcheck)',
437                                        self.gbuildparser.makecmd, 'unitcheck slowcheck', self.gbuildparser.builddir)
438         result += self.generate_launch(5, 'Global tests -- integration tests (unitcheck, slowcheck, subsequentcheck)',
439                                        self.gbuildparser.makecmd, 'unitcheck slowcheck subsequentcheck',
440                                        self.gbuildparser.builddir)
441         result += self.generate_launch(6, 'Run LibreOffice',
442                                        os.path.join(self.gbuildparser.instdir, 'program/soffice.bin'), '',
443                                        self.gbuildparser.instdir)
444         return result
446     launchestemplate = """
447 [Launch]
448 Launch Configurations=%(launches)s
452     def write_modulebeef(self, moduledir, modulename):
453         beefdir = os.path.join(moduledir, '.kdev4')
454         os.mkdir(beefdir)
455         beeffile = open(os.path.join(beefdir, 'Module_%s.kdev4' % modulename), 'w')
456         beeffile.write(self.generate_buildsystem(moduledir))
457         beeffile.write(self.generate_launches(moduledir))
458         beeffile.close()
460     def write_modulestub(self, moduledir, modulename):
461         stubfile = open(os.path.join(moduledir, 'Module_%s.kdev4' % modulename), 'w')
462         stubfile.write(KdevelopIntegrationGenerator.modulestubtemplate % {'modulename': modulename,
463                                                                           'builditem': self.encode_string(
464                                                                               'Module_%s' % modulename)})
465         stubfile.close()
467     modulestubtemplate = """
468 [Buildset]
469 BuildItems=@Variant(\\x00\\x00\\x00\\t\\x00\\x00\\x00\\x00\\x01\\x00\\x00\\x00\\x0b\\x00\\x00\\x00\\x00\\x01%(builditem)s)
471 [Project]
472 Name=Module_%(modulename)s
473 Manager=KDevCustomBuildSystem
474 VersionControl=kdevgit
477     def write_includepaths(self, path):
478         includedirfile = open(os.path.join(path, '.kdev_include_paths'), 'w')
479         include = set()
480         for target in self.gbuildparser.target_by_path[path]:
481             include |= set(target.include)
482         includedirfile.write('\n'.join(include))
483         includedirfile.close()
485     def __init__(self, gbuildparser, ide):
486         IdeIntegrationGenerator.__init__(self, gbuildparser, ide)
489     def emit(self):
490         for path in self.gbuildparser.target_by_path:
491             self.write_includepaths(path)
492         for location in self.gbuildparser.target_by_location:
493             for f in os.listdir(location):
494                 if f.endswith('.kdev4'):
495                     try:
496                         os.remove(os.path.join(location, f))
497                     except OSError:
498                         shutil.rmtree(os.path.join(location, f))
499         for location in self.gbuildparser.target_by_location:
500             modulename = os.path.split(location)[1]
501             self.write_modulestub(location, modulename)
502             self.write_modulebeef(location, modulename)
505 class XcodeIntegrationGenerator(IdeIntegrationGenerator):
507     def indent(self, file, level):
508         if level == 0:
509             return
510         for i in range(0, level):
511             file.write(' ')
513     def write_object(self, object, file, indent):
514         if isinstance(object, int):
515             file.write('%d' % object)
516         elif isinstance(object, str) and not re.search('[^A-Za-z0-9_]', object):
517             file.write('%s' % object)
518         elif isinstance(object, str):
519             file.write('"%s"' % object)
520         elif isinstance(object, dict):
521             self.write_dict(object, file, indent)
523     # Write a dictionary out as an "old-style (NeXT) ASCII plist"
524     def write_dict(self, dict, file, indent):
525         file.write('{')
526         file.write('\n')
527         for key in sorted(dict.keys()):
528             self.indent(file, indent + 1)
529             file.write('%s = ' % key)
530             self.write_object(dict[key], file, indent + 1)
531             file.write(';\n')
532         self.indent(file, indent)
533         file.write('}')
535     def write_dict_to_plist(self, dict, file):
536         file.write('// !$*UTF8*$!\n')
537         self.write_dict(dict, file, 0)
539     def get_product_type(self, modulename):
540         if modulename in self.gbuildparser.libs:
541             return 'com.apple.product-type.library.dynamic'
542         elif modulename in self.gbuildparser.exes:
543             return 'com.apple.product-type.something'
545     counter = 0
547     def generate_id(self):
548         XcodeIntegrationGenerator.counter = XcodeIntegrationGenerator.counter + 1
549         return str('X%07x' % XcodeIntegrationGenerator.counter)
551     def generate_build_phases(self, modulename):
552         result = [self.sourcesBuildPhaseId]
553         return result
555     def generate_root_object(self, modulename):
556         result = {'isa': 'PBXProject',
557                   'attributes': {'LastUpgradeCheck': '0500',
558                                  'ORGANIZATIONNAME': 'LibreOffice'},
559                   'buildConfigurationList': self.generate_id(),
560                   'compatibilityVersion': 'Xcode 3.2',
561                   'hasScannedForEncodings': 0,
562                   'knownRegions': ['en'],
563                   'mainGroup': self.mainGroupId,
564                   'productRefGroup': self.productRefGroupId,
565                   'projectDirPath': '',
566                   'projectRoot': '',
567                   'targets': self.targetId}
568         return result
570     def generate_target(self, modulename):
571         result = {'isa': 'PBXNativeTarget',
572                   'buildConfigurationList': self.generate_id(),
573                   'buildPhases': self.generate_build_phases(modulename),
574                   'buildRules': [],
575                   'dependencies': [],
576                   'name': modulename,
577                   'productName': modulename,
578                   'productReference': self.productReferenceId,
579                   'productType': self.get_product_type(modulename)}
580         return result
582     def generate_main_group(self, modulename):
583         result = {'isa': 'PBXGroup',
584                   'children': [self.subMainGroupId, self.productGroupId],
585                   'sourceTree': '<group>'}
586         return result
588     def generate_sub_main_children(self, modulename):
589         return {}
591     def generate_sub_main_group(self, modulename):
592         result = {'isa': 'PBXGroup',
593                   'children': self.generate_sub_main_children(modulename),
594                   'path': modulename,
595                   'sourceTree': '<group>'}
596         return result
598     def generate_product_group(self, modulename):
599         result = {'isa': 'PBXGroup',
600                   'children': [self.productReferenceId],
601                   'name': 'Products',
602                   'sourceTree': '<group>'}
603         return result
605     def build_source_list(self, module):
606         self.sourceRefList = {}
607         self.sourceList = {}
609         for i in module.cxxobjects:
610             ref = self.generate_id()
611             self.sourceList[self.generate_id()] = ref
612             self.sourceRefList[ref] = {'lastKnownFileType': 'sourcecode.cpp.cpp',
613                                        'path': i + '.cxx',
614                                        'sourceTree': '<group>'}
616     def generate_sources_build_phase(self, modulename):
617         result = {'isa': 'PBXSourcesBuildPhase',
618                   'buildActionMask': 2147483647,
619                   'files': self.sourceList.keys(),
620                   'runOnlyForDeploymentPostprocessing': 0}
621         return result
623     def generate_project(self, target):
624         self.rootObjectId = self.generate_id()
625         self.mainGroupId = self.generate_id()
626         self.subMainGroupId = self.generate_id()
627         self.productReferenceId = self.generate_id()
628         self.productRefGroupId = self.generate_id()
629         self.productGroupId = self.generate_id()
630         self.targetId = self.generate_id()
631         self.build_source_list(target)
632         self.sourcesBuildPhaseId = self.generate_id()
633         objects = {self.rootObjectId: self.generate_root_object(target),
634                    self.targetId: self.generate_target(target),
635                    self.mainGroupId: self.generate_main_group(target),
636                    self.subMainGroupId: self.generate_sub_main_group(target),
637                    self.productGroupId: self.generate_product_group(target),
638                    self.sourcesBuildPhaseId: self.generate_sources_build_phase(target)
639                    }
640         for i in self.sourceList.keys():
641             ref = self.sourceList[i]
642             objects[i] = {'isa': 'PBXBuildFile',
643                           'fileRef': ref}
644             objects[ref] = {'isa': 'PBXFileReference',
645                             'lastKnownFileType': self.sourceRefList[ref]['lastKnownFileType'],
646                             'path': self.sourceRefList[ref]['path']}
647         project = {'archiveVersion': 1,
648                    'classes': {},
649                    'objectVersion': 46,
650                    'objects': objects,
651                    'rootObject': self.rootObjectId}
652         return project
654     # For some reverse-engineered documentation on the project.pbxproj format,
655     # see http://www.monobjc.net/xcode-project-file-format.html .
656     def write_xcodeproj(self, moduledir, target):
657         xcodeprojdir = os.path.join(moduledir, '%s.xcodeproj' % target.name)
658         try:
659             os.mkdir(xcodeprojdir)
660         except:
661             pass
662         self.write_dict_to_plist(self.generate_project(target),
663                                  open(os.path.join(xcodeprojdir, 'project.pbxproj'), 'w'))
665     def __init__(self, gbuildparser, ide):
666         IdeIntegrationGenerator.__init__(self, gbuildparser, ide)
668     def emit(self):
669         self.rootlocation = './'
670         for location in self.gbuildparser.target_by_location:
671             module = location.split('/')[-1]
672             module_directory = os.path.join(self.rootlocation, module)
673             for target in self.gbuildparser.target_by_location[location]:
674                 # project_path = os.path.join(module_directory, '%s.pbxroj' % target.name)
675                 self.write_xcodeproj(location, target)
677 class VisualStudioIntegrationGenerator(IdeIntegrationGenerator):
679     def __init__(self, gbuildparser, ide):
680         IdeIntegrationGenerator.__init__(self, gbuildparser, ide)
681         self.toolset = self.retrieve_toolset(ide)
682         self.solution_directory = './'
683         self.configurations = {
684             'Build': {
685                 'build': self.module_make_command('%(target)s'),
686                 'clean': self.module_make_command('%(target)s.clean'),
687                 'rebuild': self.module_make_command('%(target)s.clean %(target)s')
688             },
689             'Unit Tests': {
690                 'build': self.module_make_command('unitcheck'),
691                 'clean': self.module_make_command('clean'),
692                 'rebuild': self.module_make_command('clean unitcheck'),
693             },
694             'Integration tests': {
695                 'build': self.module_make_command('unitcheck slowcheck subsequentcheck'),
696                 'clean': self.module_make_command('clean'),
697                 'rebuild': self.module_make_command('clean unitcheck slowcheck subsequentcheck')
698             }
699         }
701     def retrieve_toolset(self, ide):
702         ide_toolset_map = {'vs2012': 'v110', 'vs2013': 'v120'}
703         return ide_toolset_map[ide]
705     def module_make_command(self, targets):
706         return '%(sh)s -c "PATH=\\"/bin:$PATH\\";BUILDDIR=\\"%(builddir)s\\" %(makecmd)s -rsC %(location)s ' + targets + '"'
708     class Project:
710         def __init__(self, guid, target, project_path):
711             self.guid = guid
712             self.target = target
713             self.path = project_path
715     def emit(self):
716         all_projects = []
717         for location in self.gbuildparser.target_by_location:
718             projects = []
719             module = location.split('/')[-1]
720             module_directory = os.path.join(self.solution_directory, module)
721             for target in self.gbuildparser.target_by_location[location]:
722                 project_path = os.path.join(module_directory, '%s.vcxproj' % target.name)
723                 project_guid = self.write_project(project_path, target)
724                 p = VisualStudioIntegrationGenerator.Project(project_guid, target, project_path)
725                 projects.append(p)
726             self.write_solution(os.path.join(module_directory, '%s.sln' % module), projects)
727             all_projects += projects
729         self.write_solution(os.path.join(self.solution_directory, 'LibreOffice.sln'), all_projects)
731     nmake_project_guid = '8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942'
733     def get_dependency_libs(self, linked_libs, library_projects):
734         dependency_libs = {}
735         for linked_lib in linked_libs:
736             for library_project in library_projects:
737                 if library_project.target.library_name() == linked_lib:
738                     dependency_libs[library_project.guid] = library_project
739         return dependency_libs
741     def write_solution(self, solution_path, projects):
742         print('Solution %s:' % os.path.splitext(os.path.basename(solution_path))[0], end='')
743         library_projects = [project for project in projects if project.target in self.gbuildparser.libs]
744         with open(solution_path, 'w') as f:
745             f.write('Microsoft Visual Studio Solution File, Format Version 12.00\n')
746             for project in projects:
747                 target = project.target
748                 print(' %s' % target.name, end='')
749                 proj_path = os.path.relpath(project.path, os.path.abspath(os.path.dirname(solution_path)))
750                 f.write('Project("{%s}") = "%s", "%s", "{%s}"\n' %
751                         (VisualStudioIntegrationGenerator.nmake_project_guid,
752                          target.short_name(), proj_path, project.guid))
753                 libs_in_solution = self.get_dependency_libs(target.linked_libs,
754                                                             library_projects)
755                 if libs_in_solution:
756                     f.write('\tProjectSection(ProjectDependencies) = postProject\n')
757                     for lib_guid in libs_in_solution.keys():
758                         f.write('\t\t{%(guid)s} = {%(guid)s}\n' % {'guid': lib_guid})
759                     f.write('\tEndProjectSection\n')
760                 f.write('EndProject\n')
761             f.write('Global\n')
762             platform = 'Win32'
763             f.write('\tGlobalSection(SolutionConfigurationPlatforms) = preSolution\n')
764             for cfg in self.configurations:
765                 f.write('\t\t%(cfg)s|%(platform)s = %(cfg)s|%(platform)s\n' % {'cfg': cfg, 'platform': platform})
766             f.write('\tEndGlobalSection\n')
767             f.write('\tGlobalSection(ProjectConfigurationPlatforms) = postSolution\n')
768             # Specifies project configurations for solution configuration
769             for project in projects:
770                 for cfg in self.configurations:
771                     params = {'guid': project.guid, 'sol_cfg': cfg, 'proj_cfg': cfg, 'platform': platform}
772                     f.write('\t\t{%(guid)s}.%(sol_cfg)s|%(platform)s.ActiveCfg = %(proj_cfg)s|%(platform)s\n' % params)
773                     # Build.0 is basically 'Build checkbox' in configuration manager
774                     f.write('\t\t{%(guid)s}.%(sol_cfg)s|%(platform)s.Build.0 = %(proj_cfg)s|%(platform)s\n' % params)
775             f.write('\tEndGlobalSection\n')
776             f.write('EndGlobal\n')
777         print('')
779     def write_project(self, project_path, target):
780         # See info at http://blogs.msdn.com/b/visualstudio/archive/2010/05/14/a-guide-to-vcxproj-and-props-file-structure.aspx
781         folder = os.path.dirname(project_path)
782         if not os.path.exists(folder):
783             os.makedirs(folder)
784         project_guid = str(uuid.uuid4()).upper()
785         ns = 'http://schemas.microsoft.com/developer/msbuild/2003'
786         ET.register_namespace('', ns)
787         proj_node = ET.Element('{%s}Project' % ns, DefaultTargets='Build', ToolsVersion='4.0')
788         proj_confs_node = ET.SubElement(proj_node, '{%s}ItemGroup' % ns, Label='ProjectConfigurations')
789         platform = 'Win32'
790         for configuration in self.configurations:
791             proj_conf_node = ET.SubElement(proj_confs_node,
792                                            '{%s}ProjectConfiguration' % ns,
793                                            Include='%s|%s' % (configuration, platform))
794             conf_node = ET.SubElement(proj_conf_node, '{%s}Configuration' % ns)
795             conf_node.text = configuration
796             platform_node = ET.SubElement(proj_conf_node, '{%s}Platform' % ns)
797             platform_node.text = platform
799         globals_node = ET.SubElement(proj_node, '{%s}PropertyGroup' % ns, Label='Globals')
800         proj_guid_node = ET.SubElement(globals_node, '{%s}ProjectGuid' % ns)
801         proj_guid_node.text = '{%s}' % project_guid
802         proj_keyword_node = ET.SubElement(globals_node, '{%s}Keyword' % ns)
803         proj_keyword_node.text = 'MakeFileProj'
804         proj_name_node = ET.SubElement(globals_node, '{%s}ProjectName' % ns)
805         proj_name_node.text = target.short_name()
807         ET.SubElement(proj_node, '{%s}Import' % ns, Project='$(VCTargetsPath)\Microsoft.Cpp.Default.props')
808         for configuration in self.configurations:
809             conf_node = ET.SubElement(proj_node, '{%s}PropertyGroup' % ns, Label="Configuration",
810                                       Condition="'$(Configuration)|$(Platform)'=='%s|%s'" % (configuration, platform))
811             # Type of project used by the MSBuild to determine build process, see Microsoft.Makefile.targets
812             conf_type_node = ET.SubElement(conf_node, '{%s}ConfigurationType' % ns)
813             conf_type_node.text = 'Makefile'
814             # VS2012: I need to have this otherwise the names of projects will contain text Visual Studio 2010 in the Solution Explorer
815             platform_toolset_node = ET.SubElement(conf_node, '{%s}PlatformToolset' % ns)
816             platform_toolset_node.text = self.toolset
818         ET.SubElement(proj_node, '{%s}Import' % ns, Project='$(VCTargetsPath)\Microsoft.Cpp.props')
819         ET.SubElement(proj_node, '{%s}ImportGroup' % ns, Label='ExtensionSettings')
820         for configuration in self.configurations:
821             prop_sheets_node = ET.SubElement(proj_node, '{%s}ImportGroup' % ns, Label='Configuration',
822                                              Condition="'$(Configuration)|$(Platform)'=='%s|%s'" % (configuration, platform))
823             ET.SubElement(prop_sheets_node, '{%s}Import' % ns,
824                           Project='$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props',
825                           Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')",
826                           Label='LocalAppDataPlatform')
828         ET.SubElement(proj_node, '{%s}PropertyGroup' % ns, Label='UserMacros')
829         for cfg_name, cfg_targets in self.configurations.items():
830             conf_node = ET.SubElement(proj_node, '{%s}PropertyGroup' % ns,
831                                       Condition="'$(Configuration)|$(Platform)'=='%s|%s'" % (cfg_name, platform))
832             nmake_params = {
833                 'sh': os.path.join(self.gbuildparser.binpath, 'dash.exe'),
834                 'builddir': self.gbuildparser.builddir,
835                 'location': target.location,
836                 'makecmd': self.gbuildparser.makecmd,
837                 'target': target.target_name()}
838             nmake_build_node = ET.SubElement(conf_node, '{%s}NMakeBuildCommandLine' % ns)
839             nmake_build_node.text = cfg_targets['build'] % nmake_params
840             nmake_clean_node = ET.SubElement(conf_node, '{%s}NMakeCleanCommandLine' % ns)
841             nmake_clean_node.text = cfg_targets['clean'] % nmake_params
842             nmake_rebuild_node = ET.SubElement(conf_node, '{%s}NMakeReBuildCommandLine' % ns)
843             nmake_rebuild_node.text = cfg_targets['rebuild'] % nmake_params
844             nmake_output_node = ET.SubElement(conf_node, '{%s}NMakeOutput' % ns)
845             nmake_output_node.text = os.path.join(self.gbuildparser.instdir, 'program', 'soffice.exe')
846             nmake_defs_node = ET.SubElement(conf_node, '{%s}NMakePreprocessorDefinitions' % ns)
847             nmake_defs_node.text = ';'.join(list(target.defs) + ['$(NMakePreprocessorDefinitions)'])
848             include_path_node = ET.SubElement(conf_node, '{%s}IncludePath' % ns)
849             include_path_node.text = ';'.join(target.include + ['$(IncludePath)'])
851         ET.SubElement(proj_node, '{%s}ItemDefinitionGroup' % ns)
853         cxxobjects_node = ET.SubElement(proj_node, '{%s}ItemGroup' % ns)
854         for cxxobject in target.cxxobjects:
855             cxxabspath = os.path.join(self.gbuildparser.srcdir, cxxobject)
856             cxxfile = cxxabspath + '.cxx'
857             if os.path.isfile(cxxfile):
858                 ET.SubElement(cxxobjects_node, '{%s}ClCompile' % ns, Include=cxxfile)
859             else:
860                 print('Source %s in project %s does not exist' % (cxxfile, target.name))
862         includes_node = ET.SubElement(proj_node, '{%s}ItemGroup' % ns)
863         for cxxobject in target.cxxobjects:
864             include_abs_path = os.path.join(self.gbuildparser.srcdir, cxxobject)
865             hxxfile = include_abs_path + '.hxx'
866             if os.path.isfile(hxxfile):
867                 ET.SubElement(includes_node, '{%s}ClInclude' % ns, Include=hxxfile)
868             # Few files have corresponding .h files
869             hfile = include_abs_path + '.h'
870             if os.path.isfile(hfile):
871                 ET.SubElement(includes_node, '{%s}ClInclude' % ns, Include=hfile)
872         ET.SubElement(proj_node, '{%s}Import' % ns, Project='$(VCTargetsPath)\Microsoft.Cpp.targets')
873         ET.SubElement(proj_node, '{%s}ImportGroup' % ns, Label='ExtensionTargets')
874         self.write_pretty_xml(proj_node, project_path)
875         self.write_filters(project_path + '.filters',
876                            os.path.join(self.gbuildparser.srcdir, os.path.basename(target.location)),
877                            [cxx_node.get('Include') for cxx_node in cxxobjects_node.findall('{%s}ClCompile' % ns)],
878                            [include_node.get('Include') for include_node in includes_node.findall('{%s}ClInclude' % ns)])
879         return project_guid
881     def get_filter(self, module_dir, proj_file):
882         return '\\'.join(os.path.relpath(proj_file, module_dir).split('/')[:-1])
884     def get_subfilters(self, proj_filter):
885         parts = proj_filter.split('\\')
886         subfilters = set([proj_filter])
887         for i in range(1, len(parts)):
888             subfilters.add('\\'.join(parts[:i]))
889         return subfilters
891     def write_pretty_xml(self, node, file_path):
892         xml_str = ET.tostring(node, encoding='unicode')
893         pretty_str = minidom.parseString(xml_str).toprettyxml(encoding='utf-8')
894         with open(file_path, 'w') as f:
895             f.write(pretty_str.decode())
897     def add_nodes(self, files_node, module_dir, tag, project_files):
898         ns = 'http://schemas.microsoft.com/developer/msbuild/2003'
899         filters = set()
900         for project_file in project_files:
901             file_node = ET.SubElement(files_node, tag, Include=project_file)
902             if os.path.commonprefix([module_dir, project_file]) == module_dir:
903                 project_filter = self.get_filter(module_dir, project_file)
904                 filter_node = ET.SubElement(file_node, '{%s}Filter' % ns)
905                 filter_node.text = project_filter
906                 filters |= self.get_subfilters(project_filter)
907         return filters
909     def write_filters(self, filters_path, module_dir, compile_files, include_files):
910         ns = 'http://schemas.microsoft.com/developer/msbuild/2003'
911         ET.register_namespace('', ns)
912         proj_node = ET.Element('{%s}Project' % ns, ToolsVersion='4.0')
913         filters = set()
914         compiles_node = ET.SubElement(proj_node, '{%s}ItemGroup' % ns)
915         filters |= self.add_nodes(compiles_node, module_dir, '{%s}ClCompile' % ns, compile_files)
916         include_node = ET.SubElement(proj_node, '{%s}ItemGroup' % ns)
917         filters |= self.add_nodes(include_node, module_dir, '{%s}ClInclude' % ns, include_files)
919         filters_node = ET.SubElement(proj_node, '{%s}ItemGroup' % ns)
920         for proj_filter in filters:
921             filter_node = ET.SubElement(filters_node, '{%s}Filter' % ns, Include=proj_filter)
922             filter_id_node = ET.SubElement(filter_node, '{%s}UniqueIdentifier' % ns)
923             filter_id_node.text = '{%s}' % str(uuid.uuid4())
924         self.write_pretty_xml(proj_node, filters_path)
927 if __name__ == '__main__':
928     parser = argparse.ArgumentParser(
929         description='LibreOffice gbuild IDE project generator')
930     parser.add_argument('--ide', dest='ide', required=True,
931                         help='the IDE to generate project files for')
932     parser.add_argument('--input', dest='input', required=False,
933                         help='the input file, not normally used, for debugging this script')
934     args = parser.parse_args()
935     paths = {}
936     generators = {
937         'eclipsecdt': EclipseCDTIntegrationGenerator,
938         'kdevelop': KdevelopIntegrationGenerator,
939         'xcode': XcodeIntegrationGenerator,
940         'vs2012': VisualStudioIntegrationGenerator,
941         'vs2013': VisualStudioIntegrationGenerator,
942         'vim': VimIntegrationGenerator,
943         'debug': DebugIntegrationGenerator}
945     if args.ide not in generators.keys():
946         print("Invalid ide. valid values are %s" % ','.join(generators.keys()))
947         sys.exit(1)
949     if args.input:
950         gbuildparser = GbuildParser().parse(open(args.input, 'r'))
951     else:
952         gbuildparser = GbuildParser().parse(sys.stdin)
954     generators[args.ide](gbuildparser, args.ide).emit()
956 # Local Variables:
957 # indent-tabs-mode: nil
958 # End:
960 # vim: set et sw=4 ts=4: