#! /usr/bin/env python3
# -*- Mode: python; tab-width: 4; indent-tabs-mode: t -*-
#
# This file is part of the LibreOffice project.
#
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
#
import argparse
import ntpath
import os
import os.path
import shutil
import re
import sys
import uuid
import json
import xml.etree.ElementTree as ET
import xml.dom.minidom as minidom
import traceback
import subprocess
from sys import platform
import collections
import urllib.parse
class GbuildLinkTarget:
includepattern = re.compile(r'-I(\S+)')
isystempattern = re.compile(r'-isystem\s*(\S+)')
warningpattern = re.compile(r'-W\S+')
@staticmethod
def __split_includes(includes):
foundisystem = GbuildLinkTarget.isystempattern.findall(includes)
foundincludes = [includeswitch.strip() for includeswitch in GbuildLinkTarget.includepattern.findall(includes) if
len(includeswitch) > 2]
return (foundincludes, foundisystem)
@staticmethod
def __split_objs(objsline):
return [obj for obj in objsline.strip().split(' ') if obj not in { '', 'CXXOBJECTS', 'COBJECTS', 'OBJCXXOBJECTS', 'CXXCLROBJECTS', '+=' }]
@staticmethod
def __split_defs(defsline):
defs = {}
alldefs = [defswitch.strip() for defswitch in defsline.strip().lstrip('-D').split(' -D') if len(defswitch) > 2]
for d in alldefs:
dparts = d.split(' -U')
"""after dparts.pop(0), dparts will contain only undefs"""
defparts = dparts.pop(0).strip().split('=')
if len(defparts) == 1:
defparts.append(None)
defs[defparts[0]] = defparts[1]
"""Drop undefed items (if any) from previous defs"""
for u in dparts:
defs.pop(u.strip(), '')
defs["LIBO_INTERNAL_ONLY"] = None
return defs
@staticmethod
def __split_flags(flagsline, flagslineappend):
return [cxxflag.strip() for cxxflag in GbuildLinkTarget.warningpattern.sub('', '%s %s' % (flagsline, flagslineappend)).split(' ') if len(cxxflag) > 1]
def __init__(self, json):
(foundincludes, foundisystem) = GbuildLinkTarget.__split_includes(json['INCLUDE'])
(self.name, self.location, self.include, self.include_sys, self.defs, self.cxxobjects, self.objcxxobjects, self.cxxflags, self.cobjects, self.cflags, self.cxxclrobjects, self.cxxclrflags, self.linked_libs, self.linked_static_libs, self.link_target) = (
type(self).targetpattern.match(os.path.basename(json['MAKEFILE'])).group(1),
os.path.dirname(json['MAKEFILE']),
foundincludes,
foundisystem,
GbuildLinkTarget.__split_defs(json['DEFS']),
GbuildLinkTarget.__split_objs(json['CXXOBJECTS']),
GbuildLinkTarget.__split_objs(json['OBJCXXOBJECTS']),
GbuildLinkTarget.__split_flags(json['CXXFLAGS'], json['CXXFLAGSAPPEND']),
GbuildLinkTarget.__split_objs(json['COBJECTS']),
GbuildLinkTarget.__split_flags(json['CFLAGS'], json['CFLAGSAPPEND']),
GbuildLinkTarget.__split_objs(json['CXXCLROBJECTS']),
GbuildLinkTarget.__split_flags(json['CXXCLRFLAGS'], json['CXXCLRFLAGSAPPEND']),
json['LINKED_LIBS'].strip().split(' '),
json['LINKED_STATIC_LIBS'].strip().split(' '),
json['LINKTARGET'].strip())
def short_name(self):
"""Return the short name of target based on the Foo_* makefile name"""
return '%s %s' % (type(self).targetprefix, self.name)
def target_name(self):
return '%s_%s' % (type(self).targetprefix, self.name)
def __str__(self):
return '%s at %s with include path: %s, isystem includes: %s, defines: %s, objects: %s, cxxflags: %s, cobjects: %s, cflags: %s, linked libs: %s and linked static libs: %s' % (
self.short_name(), self.location, self.include, self.include_sys, self.defs, self.cxxobjects,
self.cxxflags, self.cobjects, self.cflags, self.linked_libs, self.linked_static_libs)
class GbuildLib(GbuildLinkTarget):
targetpattern = re.compile(r'Library_(.*)\.mk')
targetprefix = "Library"
class GbuildStaticLib(GbuildLinkTarget):
targetpattern = re.compile(r'StaticLibrary_(.*)\.mk')
targetprefix = "StaticLibrary"
class GbuildTest(GbuildLinkTarget):
targetpattern = re.compile(r'CppunitTest_(.*)\.mk')
targetprefix = "CppunitTest"
class GbuildExe(GbuildLinkTarget):
targetpattern = re.compile(r'Executable_(.*)\.mk')
targetprefix = "Executable"
class GbuildParser:
"""Main data model object.
Attributes:
target_by_path : dict[path:string, set(target)]
where target is one of the GbuildLinkTarget subclasses
target_by_location : dict[path:string, set(target)]
where target is one of the GbuildLinkTarget subclasses
"""
def __init__(self, makecmd):
self.makecmd = makecmd
self.binpath = os.path.dirname(os.environ['GPERF']) # woha, this is quite a hack
(self.srcdir, self.builddir, self.instdir, self.workdir) = (os.environ['SRCDIR'], os.environ['BUILDDIR'], os.environ['INSTDIR'], os.environ['WORKDIR'])
(self.libs, self.static_libs, self.exes, self.tests, self.modulenamelist) = (set(), set(), set(), set(), [])
(self.target_by_path, self.target_by_location) = ({}, {})
def parse(self):
for jsonfilename in os.listdir(os.path.join(self.workdir, 'GbuildToJson', 'Library')):
with open(os.path.join(self.workdir, 'GbuildToJson', 'Library', jsonfilename), 'r') as f:
lib = GbuildLib(json.load(f))
self.libs.add(lib)
for jsonfilename in os.listdir(os.path.join(self.workdir, 'GbuildToJson', 'StaticLibrary')):
with open(os.path.join(self.workdir, 'GbuildToJson', 'StaticLibrary', jsonfilename), 'r') as f:
static_lib = GbuildStaticLib(json.load(f))
self.static_libs.add(static_lib)
for jsonfilename in os.listdir(os.path.join(self.workdir, 'GbuildToJson', 'Executable')):
with open(os.path.join(self.workdir, 'GbuildToJson', 'Executable', jsonfilename), 'r') as f:
exe = GbuildExe(json.load(f))
self.exes.add(exe)
for jsonfilename in os.listdir(os.path.join(self.workdir, 'GbuildToJson', 'CppunitTest')):
with open(os.path.join(self.workdir, 'GbuildToJson', 'CppunitTest', jsonfilename), 'r') as f:
test = GbuildTest(json.load(f))
self.tests.add(test)
for target in self.libs | self.static_libs | self.exes | self.tests:
if target.location not in self.target_by_location:
self.target_by_location[target.location] = set()
self.target_by_location[target.location] |= set([target])
for cxx in target.cxxobjects:
path = '/'.join(cxx.split('/')[:-1])
if path not in self.target_by_path:
self.target_by_path[path] = set()
self.target_by_path[path] |= set([target])
for c in target.cobjects:
path = '/'.join(c.split('/')[:-1])
if path not in self.target_by_path:
self.target_by_path[path] = set()
self.target_by_path[path] |= set([target])
for location in self.target_by_location:
self.modulenamelist.append(os.path.split(location)[1])
return self
class IdeIntegrationGenerator:
def __init__(self, gbuildparser, ide):
self.gbuildparser = gbuildparser
self.ide = ide
def emit(self):
pass
class EclipseCDTIntegrationGenerator(IdeIntegrationGenerator):
def __init__(self, gbuildparser, ide):
IdeIntegrationGenerator.__init__(self, gbuildparser, ide)
def create_include_paths(self):
for module in self.gbuildparser.modulenamelist:
modulepath = os.path.join(self.gbuildparser.builddir, module)
includedirfile = open(os.path.join(modulepath, '.eclipsesettingfile'), 'w')
modulelibs = []
for lib in self.gbuildparser.target_by_path.keys():
if lib.startswith(module+'/'):
modulelibs.append(lib)
include = set()
for lib in modulelibs:
for target in self.gbuildparser.target_by_path[lib]:
include |= set(target.include)
includedirfile.write('\n'.join(include))
includedirfile.close()
def create_macros(self):
for module in self.gbuildparser.modulenamelist:
modulepath = os.path.join(self.gbuildparser.builddir, module)
macrofile = open(os.path.join(modulepath, '.macros'), 'w')
modulelibs = []
for lib in self.gbuildparser.target_by_path.keys():
if lib.startswith(module+'/'):
modulelibs.append(lib)
define = []
defineset = set()
for lib in modulelibs:
for target in self.gbuildparser.target_by_path[lib]:
for i in target.defs.keys():
tmp = str(i) +','+str(target.defs[i])
if tmp not in defineset:
defineset.add(tmp)
macrofile.write('\n'.join(defineset))
macrofile.close()
def create_settings_file(self):
settingsfiletemplate = """\
"""
for module in self.gbuildparser.modulenamelist:
tempxml = []
modulepath = os.path.join(self.gbuildparser.builddir, module)
settingsfile = open(os.path.join(modulepath, 'eclipsesettingfile.xml'), 'w')
settingsfile.write(settingsfiletemplate)
settingsfile.close()
settingsfile = open(os.path.join(modulepath, 'eclipsesettingfile.xml'), 'r')
tempxml = settingsfile.readlines()
tempinclude = open(os.path.join(modulepath, '.eclipsesettingfile'), 'r')
tempmacro = open(os.path.join(modulepath, '.macros'), 'r')
for includepath in tempinclude:
if includepath[-1:] == "\n":
includepath = includepath[:-1]
templine = "%s\n" % includepath
tempxml.insert(5, templine)
for line in tempmacro:
macroskeyvalue = line.split(',')
macrokey = macroskeyvalue[0]
macrovalue = macroskeyvalue[1]
if macrovalue[-1:] == "\n":
macrovalue = macrovalue[:-1]
templine = "%s%s\n" %(macrokey, macrovalue)
tempxml.insert(-13, templine)
tempxml="".join(tempxml)
settingsfile.close
settingsfile = open(os.path.join(modulepath, 'eclipsesettingfile.xml'), 'w')
settingsfile.write(tempxml)
settingsfile.close()
os.remove(os.path.join(modulepath, '.eclipsesettingfile'))
os.remove(os.path.join(modulepath, '.macros'))
def emit(self):
self.create_include_paths()
self.create_macros()
self.create_settings_file()
class CodeliteIntegrationGenerator(IdeIntegrationGenerator):
def __init__(self, gbuildparser, ide):
IdeIntegrationGenerator.__init__(self, gbuildparser, ide)
def emit(self):
self.create_workspace_file()
for module in self.gbuildparser.modulenamelist:
if os.path.exists(module): # ignore external modules
self.create_project_file(module)
#self.create_project_file('vcl')
def create_workspace_file(self):
root_node = ET.Element('CodeLite_Workspace', Name='libo2', Database='./libo2.tags', Version='10.0.0')
for module in self.gbuildparser.modulenamelist:
if os.path.exists(module): # ignore external modules
ET.SubElement(root_node, 'Project', Name=module, Path='%s/%s.project' % (module, module), Active='No')
build_matrix_node = ET.SubElement(root_node, 'BuildMatrix')
workspace_config_node = ET.SubElement(build_matrix_node, 'WorkspaceConfiguration', Name='Debug', Selected='yes')
ET.SubElement(workspace_config_node, 'Environment')
for module in self.gbuildparser.modulenamelist:
if os.path.exists(module): # ignore external modules
ET.SubElement(workspace_config_node, 'Project', Name=module, ConfigName='Debug')
workspace_config_node = ET.SubElement(build_matrix_node, 'WorkspaceConfiguration', Name='Release', Selected='yes')
ET.SubElement(workspace_config_node, 'Environment')
for module in self.gbuildparser.modulenamelist:
if os.path.exists(module): # ignore external modules
ET.SubElement(workspace_config_node, 'Project', Name=module, ConfigName='Release')
self.write_pretty_xml(root_node, os.path.join(self.gbuildparser.builddir, 'libo2.workspace'))
def create_project_file(self, module_name):
root_node = ET.Element('CodeLite_Project', Name=module_name, InternalType='')
ET.SubElement(root_node, 'Plugins')
# add CXX files
virtual_dirs = collections.defaultdict(set)
for target_path in self.gbuildparser.target_by_path.keys():
if target_path.startswith(module_name+'/'):
for target in self.gbuildparser.target_by_path[target_path]:
for file in target.cxxobjects:
relative_file = '/'.join(file.split('/')[1:])
path = '/'.join(file.split('/')[1:-1])
virtual_dirs[path].add(relative_file + '.cxx')
# add HXX files
all_libs = self.gbuildparser.libs | self.gbuildparser.exes
for lib in all_libs:
if lib.name == module_name:
for hdir in lib.include:
# only want the module-internal ones
if hdir.startswith(module_name+'/'):
for hf in os.listdir(hdir):
if hf.endswith(('.h', '.hxx', '.hpp', '.hrc')):
path = '/'.join(hf.split('/')[1:-1])
virtual_dirs[path].add(hf)
# add HXX files from the root/include/** folders
module_include = os.path.join(self.gbuildparser.builddir, 'include', module_name)
if os.path.exists(module_include):
for hf in os.listdir(module_include):
if hf.endswith(('.h', '.hxx', '.hpp', '.hrc')):
path = '../include/' + ('/'.join(hf.split('/')[1:-1]))
virtual_dirs['include/' + module_name].add('../include/' + module_name + '/' + hf)
for vd_name in sorted(virtual_dirs.keys()):
vd_files = sorted(virtual_dirs[vd_name])
parent_node = root_node
for subname in vd_name.split('/'):
parent_node = ET.SubElement(parent_node, 'VirtualDirectory', Name=subname)
for file in vd_files:
ET.SubElement(parent_node, 'File', Name=file)
ET.SubElement(root_node, 'Description')
ET.SubElement(root_node, 'Dependencies')
ET.SubElement(root_node, 'Dependencies', Name='Debug')
ET.SubElement(root_node, 'Dependencies', Name='Release')
settingstemplate = """\
make %s.clean
make %s.build
None
$(WorkspacePath)
make %s.clean
make %s.build
None
$(WorkspacePath)
"""
root_node.append(ET.fromstring(settingstemplate % (module_name, module_name, module_name, module_name)))
self.write_pretty_xml(root_node, os.path.join(self.gbuildparser.builddir, module_name, '%s.project' % module_name))
def write_pretty_xml(self, node, file_path):
xml_str = ET.tostring(node, encoding='unicode')
pretty_str = minidom.parseString(xml_str).toprettyxml(encoding='utf-8')
with open(file_path, 'w') as f:
f.write(pretty_str.decode())
class DebugIntegrationGenerator(IdeIntegrationGenerator):
def __init__(self, gbuildparser, ide):
IdeIntegrationGenerator.__init__(self, gbuildparser, ide)
def emit(self):
print(self.gbuildparser.srcdir)
print(self.gbuildparser.builddir)
for lib in self.gbuildparser.libs:
print(lib)
for exe in self.gbuildparser.exes:
print(exe)
for test in self.gbuildparser.tests:
print(test)
class VimIntegrationGenerator(IdeIntegrationGenerator):
def __init__(self, gbuildparser, ide):
IdeIntegrationGenerator.__init__(self, gbuildparser, ide)
def emit(self):
global_list = []
for lib in self.gbuildparser.libs | self.gbuildparser.tests | self.gbuildparser.exes:
entries = []
for file in lib.cxxobjects:
filePath = os.path.join(self.gbuildparser.srcdir, file) + ".cxx"
entry = {'directory': lib.location, 'file': filePath, 'command': self.generateCommand(lib, filePath)}
entries.append(entry)
global_list.extend(entries)
with open(os.path.join(self.gbuildparser.builddir, 'compile_commands.json'), 'w') as export_file:
json.dump(global_list, export_file)
def generateCommand(self, lib, file):
command = 'clang++ -Wall'
for key, value in lib.defs.items():
command += ' -D'
command += key
if value is not None:
command += '='
command += value
for include in lib.include:
command += ' -I'
command += include
for isystem in lib.include_sys:
command += ' -isystem '
command += isystem
for cxxflag in lib.cxxflags:
command += ' '
command += cxxflag
command += ' -c '
command += file
return command
class KdevelopIntegrationGenerator(IdeIntegrationGenerator):
def encode_int(self, i):
temp = '%08x' % i
return '\\x%s\\x%s\\x%s\\x%s' % (temp[0:2], temp[2:4], temp[4:6], temp[6:8])
def encode_string(self, string):
result = self.encode_int(len(string) * 2)
for c in string.encode('utf-16-be'):
if c in range(32, 126):
result += chr(c)
else:
result += '\\x%02x' % c
return result
def generate_buildsystemconfigtool(self, configid, tool, args, exe, typenr):
return KdevelopIntegrationGenerator.buildsystemconfigtooltemplate % {'configid': configid, 'tool': tool,
'args': args, 'exe': exe, 'typenr': typenr}
buildsystemconfigtooltemplate = """
[CustomBuildSystem][BuildConfig%(configid)d][Tool%(tool)s]
Arguments=%(args)s
Enabled=true
Environment=
Executable=%(exe)s
Type=%(typenr)d
"""
def generate_buildsystemconfig(self, configid, moduledir, builddir, title, buildparms=''):
result = KdevelopIntegrationGenerator.buildsystemconfigtemplate % {'configid': configid, 'builddir': builddir,
'title': title}
result += self.generate_buildsystemconfigtool(configid, 'Clean', 'clean %s' % buildparms,
self.gbuildparser.makecmd, 3)
result += self.generate_buildsystemconfigtool(configid, 'Build', 'all %s' % buildparms,
self.gbuildparser.makecmd, 0)
return result
buildsystemconfigtemplate = """
[CustomBuildSystem][BuildConfig%(configid)d]
BuildDir=file://%(builddir)s
Title=%(title)s
"""
def generate_buildsystem(self, moduledir):
result = KdevelopIntegrationGenerator.buildsystemtemplate % {'defaultconfigid': 0}
result += self.generate_buildsystemconfig(0, moduledir, moduledir, 'Module Build -- Release')
result += self.generate_buildsystemconfig(1, moduledir, self.gbuildparser.builddir, 'Full Build -- Release')
result += self.generate_buildsystemconfig(2, moduledir, moduledir, 'Module Build -- Debug', 'debug=T')
result += self.generate_buildsystemconfig(3, moduledir, self.gbuildparser.builddir, 'Full Build -- Debug',
'debug=T')
return result
buildsystemtemplate = """
[CustomBuildSystem]
CurrentConfiguration=BuildConfig%(defaultconfigid)d
"""
def generate_launch(self, launchid, launchname, executablepath, args, workdir):
return KdevelopIntegrationGenerator.launchtemplate % {'launchid': launchid, 'launchname': launchname,
'executablepath': executablepath, 'args': args,
'workdir': workdir}
launchtemplate = """
[Launch][Launch Configuration %(launchid)d]
Configured Launch Modes=execute
Configured Launchers=nativeAppLauncher
Name=%(launchname)s
Type=Native Application
[Launch][Launch Configuration %(launchid)d][Data]
Arguments=%(args)s
Dependencies=@Variant(\\x00\\x00\\x00\\t\\x00\\x00\\x00\\x00\\x00)
Dependency Action=Nothing
EnvironmentGroup=default
Executable=file://%(executablepath)s
External Terminal=konsole --noclose --workdir %%workdir -e %%exe
Project Target=
Use External Terminal=false
Working Directory=file://%(workdir)s
isExecutable=true
"""
def generate_launches(self, moduledir):
launches = ','.join(['Launch Configuration %d' % i for i in range(7)])
result = KdevelopIntegrationGenerator.launchestemplate % {'launches': launches}
result += self.generate_launch(0, 'Local tests -- quick tests (unitcheck)', self.gbuildparser.makecmd,
'unitcheck', moduledir)
result += self.generate_launch(1, 'Local tests -- slow tests (unitcheck, slowcheck, screenshot)', self.gbuildparser.makecmd,
'unitcheck slowcheck screenshot', moduledir)
result += self.generate_launch(2, 'Local tests -- integration tests (unitcheck, slowcheck, screenshot, subsequentcheck)',
self.gbuildparser.makecmd, 'unitcheck slowcheck screenshot subsequentcheck', moduledir)
result += self.generate_launch(3, 'Global tests -- quick tests (unitcheck)', self.gbuildparser.makecmd,
'unitcheck', self.gbuildparser.builddir)
result += self.generate_launch(4, 'Global tests -- slow tests (unitcheck, slowcheck, screenshot)',
self.gbuildparser.makecmd, 'unitcheck slowcheck screenshot', self.gbuildparser.builddir)
result += self.generate_launch(5, 'Global tests -- integration tests (unitcheck, slowcheck, screenshot, subsequentcheck)',
self.gbuildparser.makecmd, 'unitcheck slowcheck screenshot subsequentcheck',
self.gbuildparser.builddir)
result += self.generate_launch(6, 'Run LibreOffice',
os.path.join(self.gbuildparser.instdir, 'program/soffice.bin'), '',
self.gbuildparser.instdir)
return result
launchestemplate = """
[Launch]
Launch Configurations=%(launches)s
"""
def write_modulebeef(self, moduledir, modulename):
beefdir = os.path.join(moduledir, '.kdev4')
os.mkdir(beefdir)
beeffile = open(os.path.join(beefdir, 'Module_%s.kdev4' % modulename), 'w')
beeffile.write(self.generate_buildsystem(moduledir))
beeffile.write(self.generate_launches(moduledir))
beeffile.close()
def write_modulestub(self, moduledir, modulename):
stubfile = open(os.path.join(moduledir, 'Module_%s.kdev4' % modulename), 'w')
stubfile.write(KdevelopIntegrationGenerator.modulestubtemplate % {'modulename': modulename,
'builditem': self.encode_string(
'Module_%s' % modulename)})
stubfile.close()
modulestubtemplate = """
[Buildset]
BuildItems=@Variant(\\x00\\x00\\x00\\t\\x00\\x00\\x00\\x00\\x01\\x00\\x00\\x00\\x0b\\x00\\x00\\x00\\x00\\x01%(builditem)s)
[Project]
Name=Module_%(modulename)s
Manager=KDevCustomBuildSystem
VersionControl=kdevgit
"""
def write_includepaths(self, path):
includedirfile = open(os.path.join(path, '.kdev_include_paths'), 'w')
include = set()
for target in self.gbuildparser.target_by_path[path]:
include |= set(target.include)
includedirfile.write('\n'.join(include))
includedirfile.close()
def __init__(self, gbuildparser, ide):
IdeIntegrationGenerator.__init__(self, gbuildparser, ide)
def emit(self):
for path in self.gbuildparser.target_by_path:
self.write_includepaths(path)
for location in self.gbuildparser.target_by_location:
for f in os.listdir(location):
if f.endswith('.kdev4'):
try:
os.remove(os.path.join(location, f))
except OSError:
shutil.rmtree(os.path.join(location, f))
for location in self.gbuildparser.target_by_location:
modulename = os.path.split(location)[1]
self.write_modulestub(location, modulename)
self.write_modulebeef(location, modulename)
class XcodeIntegrationGenerator(IdeIntegrationGenerator):
def indent(self, file, level):
if level == 0:
return
for i in range(0, level):
file.write(' ')
def write_object(self, object, file, indent):
if isinstance(object, int):
file.write('%d' % object)
elif isinstance(object, str) and not re.search('[^A-Za-z0-9_]', object):
file.write('%s' % object)
elif isinstance(object, str):
file.write('"%s"' % object)
elif isinstance(object, dict):
self.write_dict(object, file, indent)
# Write a dictionary out as an "old-style (NeXT) ASCII plist"
def write_dict(self, dict, file, indent):
file.write('{')
file.write('\n')
for key in sorted(dict.keys()):
self.indent(file, indent + 1)
file.write('%s = ' % key)
self.write_object(dict[key], file, indent + 1)
file.write(';\n')
self.indent(file, indent)
file.write('}')
def write_dict_to_plist(self, dict, file):
file.write('// !$*UTF8*$!\n')
self.write_dict(dict, file, 0)
def get_product_type(self, modulename):
if modulename in self.gbuildparser.libs:
return 'com.apple.product-type.library.dynamic'
elif modulename in self.gbuildparser.exes:
return 'com.apple.product-type.something'
counter = 0
def generate_id(self):
XcodeIntegrationGenerator.counter = XcodeIntegrationGenerator.counter + 1
return str('X%07x' % XcodeIntegrationGenerator.counter)
def generate_build_phases(self, modulename):
result = [self.sourcesBuildPhaseId]
return result
def generate_root_object(self, modulename):
result = {'isa': 'PBXProject',
'attributes': {'LastUpgradeCheck': '0500',
'ORGANIZATIONNAME': 'LibreOffice'},
'buildConfigurationList': self.generate_id(),
'compatibilityVersion': 'Xcode 3.2',
'hasScannedForEncodings': 0,
'knownRegions': ['en'],
'mainGroup': self.mainGroupId,
'productRefGroup': self.productRefGroupId,
'projectDirPath': '',
'projectRoot': '',
'targets': self.targetId}
return result
def generate_target(self, modulename):
result = {'isa': 'PBXNativeTarget',
'buildConfigurationList': self.generate_id(),
'buildPhases': self.generate_build_phases(modulename),
'buildRules': [],
'dependencies': [],
'name': modulename,
'productName': modulename,
'productReference': self.productReferenceId,
'productType': self.get_product_type(modulename)}
return result
def generate_main_group(self, modulename):
result = {'isa': 'PBXGroup',
'children': [self.subMainGroupId, self.productGroupId],
'sourceTree': ''}
return result
def generate_sub_main_children(self, modulename):
return {}
def generate_sub_main_group(self, modulename):
result = {'isa': 'PBXGroup',
'children': self.generate_sub_main_children(modulename),
'path': modulename,
'sourceTree': ''}
return result
def generate_product_group(self, modulename):
result = {'isa': 'PBXGroup',
'children': [self.productReferenceId],
'name': 'Products',
'sourceTree': ''}
return result
def build_source_list(self, module):
self.sourceRefList = {}
self.sourceList = {}
for i in module.cxxobjects:
ref = self.generate_id()
self.sourceList[self.generate_id()] = ref
self.sourceRefList[ref] = {'lastKnownFileType': 'sourcecode.cpp.cpp',
'path': i + '.cxx',
'sourceTree': ''}
def generate_sources_build_phase(self, modulename):
result = {'isa': 'PBXSourcesBuildPhase',
'buildActionMask': 2147483647,
'files': self.sourceList.keys(),
'runOnlyForDeploymentPostprocessing': 0}
return result
def generate_project(self, target):
self.rootObjectId = self.generate_id()
self.mainGroupId = self.generate_id()
self.subMainGroupId = self.generate_id()
self.productReferenceId = self.generate_id()
self.productRefGroupId = self.generate_id()
self.productGroupId = self.generate_id()
self.targetId = self.generate_id()
self.build_source_list(target)
self.sourcesBuildPhaseId = self.generate_id()
objects = {self.rootObjectId: self.generate_root_object(target),
self.targetId: self.generate_target(target),
self.mainGroupId: self.generate_main_group(target),
self.subMainGroupId: self.generate_sub_main_group(target),
self.productGroupId: self.generate_product_group(target),
self.sourcesBuildPhaseId: self.generate_sources_build_phase(target)
}
for i in self.sourceList.keys():
ref = self.sourceList[i]
objects[i] = {'isa': 'PBXBuildFile',
'fileRef': ref}
objects[ref] = {'isa': 'PBXFileReference',
'lastKnownFileType': self.sourceRefList[ref]['lastKnownFileType'],
'path': self.sourceRefList[ref]['path']}
project = {'archiveVersion': 1,
'classes': {},
'objectVersion': 46,
'objects': objects,
'rootObject': self.rootObjectId}
return project
# For some reverse-engineered documentation on the project.pbxproj format,
# see http://www.monobjc.net/xcode-project-file-format.html .
def write_xcodeproj(self, moduledir, target):
xcodeprojdir = os.path.join(moduledir, '%s.xcodeproj' % target.target_name())
try:
os.mkdir(xcodeprojdir)
except:
pass
self.write_dict_to_plist(self.generate_project(target),
open(os.path.join(xcodeprojdir, 'project.pbxproj'), 'w'))
def __init__(self, gbuildparser, ide):
IdeIntegrationGenerator.__init__(self, gbuildparser, ide)
def emit(self):
self.rootlocation = './'
for location in self.gbuildparser.target_by_location:
# module = location.split('/')[-1]
# module_directory = os.path.join(self.rootlocation, module)
for target in self.gbuildparser.target_by_location[location]:
# project_path = os.path.join(module_directory, '%s.pbxroj' % target.target_name())
self.write_xcodeproj(location, target)
class VisualStudioIntegrationGenerator(IdeIntegrationGenerator):
def __init__(self, gbuildparser, ide):
IdeIntegrationGenerator.__init__(self, gbuildparser, ide)
self.toolset = os.environ['VCTOOLSET']
if os.environ['CPUNAME'] == 'X86_64':
self.platform = 'x64'
elif os.environ['CPUNAME'] == 'AARCH64':
self.platform = 'ARM'
else: # 'INTEL'
self.platform = 'Win32'
self.solution_directory = self.gbuildparser.builddir
self.configurations = {
'Build': {
'build': self.module_make_command('%(target)s'),
'clean': self.module_make_command('%(target)s.clean'),
'rebuild': self.module_make_command('%(target)s.clean %(target)s')
},
'Unit Tests': {
'build': self.module_make_command('unitcheck'),
'clean': self.module_make_command('clean'),
'rebuild': self.module_make_command('clean unitcheck'),
},
'Integration tests': {
'build': self.module_make_command('unitcheck slowcheck screenshot subsequentcheck'),
'clean': self.module_make_command('clean'),
'rebuild': self.module_make_command('clean unitcheck slowcheck screenshot subsequentcheck')
}
}
def module_make_command(self, targets):
return '%(sh)s -c "PATH=\\"/bin:$PATH\\";BUILDDIR=\\"%(builddir)s\\" %(makecmd)s -rsC %(location)s ' + targets + '"'
class Project:
def __init__(self, guid, target, project_path):
self.guid = guid
self.target = target
self.path = project_path
# Check if the target is empty (no source files would be added to project file)
@staticmethod
def should_skip(target):
return not target.cxxobjects and not target.cxxclrobjects and not target.cobjects
def emit(self):
all_projects = []
for location, targets in self.gbuildparser.target_by_location.items():
projects = []
module = location.split('/')[-1]
module_directory = os.path.join(self.solution_directory, module)
for target in targets:
if self.should_skip(target):
print(' %s: no files to add, skipping' % target.target_name())
continue
project_path = os.path.join(module_directory, '%s.vcxproj' % target.target_name())
project_guid = self.write_project(project_path, target)
p = VisualStudioIntegrationGenerator.Project(project_guid, target, project_path)
projects.append(p)
self.write_solution(os.path.join(module_directory, '%s.sln' % module), projects)
all_projects += projects
self.write_solution(os.path.join(self.solution_directory, 'LibreOffice.sln'), all_projects)
# this enables Python GDB pretty printers when debugging a WSL Linux build from VS
MIEngine_options_path = os.path.join(gbuildparser.srcdir, 'solenv/vs/Microsoft.MIEngine.Options.xml')
shutil.copy(MIEngine_options_path, self.solution_directory)
@staticmethod
def gen_guid(category, name):
return str(uuid.uuid5(uuid.NAMESPACE_URL, 'vnd.libreoffice.vs-ide-integration:' + category + '/' + urllib.parse.quote(name))).upper()
nmake_project_guid = '8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942'
nmake_folder_guid = '2150E333-8FDC-42A3-9474-1A3956D46DE8'
def get_dependency_libs(self, linked_libs, library_projects):
dependency_libs = {}
for linked_lib in linked_libs:
for library_project in library_projects:
if library_project.target.name == linked_lib:
dependency_libs[library_project.guid] = library_project
return dependency_libs
def write_solution(self, solution_path, projects):
print('Solution %s:' % os.path.splitext(os.path.basename(solution_path))[0], end='')
if not projects:
print(' no projects, skipping')
return
library_projects = [project for project in projects if project.target in self.gbuildparser.libs]
static_library_projects = [project for project in projects if project.target in self.gbuildparser.static_libs]
test_projects = [project for project in projects if project.target in self.gbuildparser.tests]
executable_projects = [project for project in projects if project.target in self.gbuildparser.exes]
with open(solution_path, 'w') as f:
f.write('Microsoft Visual Studio Solution File, Format Version 12.00\n')
for project in projects:
target = project.target
print(' %s' % target.target_name(), end='')
proj_path = os.path.relpath(project.path, os.path.abspath(os.path.dirname(solution_path)))
f.write('Project("{%s}") = "%s", "%s", "{%s}"\n' %
(VisualStudioIntegrationGenerator.nmake_project_guid,
target.short_name(), proj_path, project.guid))
libs_in_solution = self.get_dependency_libs(target.linked_libs,
library_projects)
libs_in_solution |= self.get_dependency_libs(target.linked_static_libs,
static_library_projects)
if libs_in_solution:
f.write('\tProjectSection(ProjectDependencies) = postProject\n')
for lib_guid in libs_in_solution.keys():
f.write('\t\t{%(guid)s} = {%(guid)s}\n' % {'guid': lib_guid})
f.write('\tEndProjectSection\n')
f.write('EndProject\n')
f.write('Project("{%s}") = "Utility", "Utility", "{6778240E-8B6B-47A0-AC31-7E7A257B97E6}"\n' %
(VisualStudioIntegrationGenerator.nmake_folder_guid))
f.write('\tProjectSection(SolutionItems) = preProject\n')
# The natvis file gives pretty-printed variable values when debugging
natvis_path = os.path.join(gbuildparser.srcdir, 'solenv/vs/LibreOffice.natvis')
f.write('\t\t%(natvis)s = %(natvis)s\n' % {'natvis': natvis_path})
# The natstepfilter file allows to skip specific functions when stepping into in debugging
natstepfilter_path = os.path.join(gbuildparser.srcdir, 'solenv/vs/LibreOffice.natstepfilter')
f.write('\t\t%(natstepfilter)s = %(natstepfilter)s\n' % {'natstepfilter': natstepfilter_path})
f.write('\tEndProjectSection\n')
f.write('EndProject\n')
# Folders to group tests/libraries/executables
nmake_tests_guid = 'CF544F7B-9D02-4D83-8370-5887851209B7'
nmake_libraries_guid = 'C624F43D-616C-4627-B58F-F5C2047B7BDC'
nmake_static_libraries_guid = 'EB2CD1D4-7C29-4232-9B1E-9FB1FE62D61C'
nmake_executables_guid = '1CD80999-9FA9-4BA9-B4D9-6E33035CF648'
f.write('Project("{%s}") = "Tests", "Tests", "{%s}"\n' %
(VisualStudioIntegrationGenerator.nmake_folder_guid, nmake_tests_guid))
f.write('EndProject\n')
f.write('Project("{%s}") = "Libraries", "Libraries", "{%s}"\n' %
(VisualStudioIntegrationGenerator.nmake_folder_guid, nmake_libraries_guid))
f.write('EndProject\n')
f.write('Project("{%s}") = "StaticLibraries", "StaticLibraries", "{%s}"\n' %
(VisualStudioIntegrationGenerator.nmake_folder_guid, nmake_static_libraries_guid))
f.write('EndProject\n')
f.write('Project("{%s}") = "Executables", "Executables", "{%s}"\n' %
(VisualStudioIntegrationGenerator.nmake_folder_guid, nmake_executables_guid))
f.write('EndProject\n')
# end Folders to group tests/libraries/executables
f.write('Global\n')
platform = self.platform
f.write('\tGlobalSection(SolutionConfigurationPlatforms) = preSolution\n')
for cfg in self.configurations:
f.write('\t\t%(cfg)s|%(platform)s = %(cfg)s|%(platform)s\n' % {'cfg': cfg, 'platform': platform})
f.write('\tEndGlobalSection\n')
# Group projects to folders
f.write('\tGlobalSection(NestedProjects) = preSolution\n')
for project in test_projects:
f.write('\t\t{%s} = {%s}\n' % (project.guid, nmake_tests_guid))
for project in library_projects:
f.write('\t\t{%s} = {%s}\n' % (project.guid, nmake_libraries_guid))
for project in static_library_projects:
f.write('\t\t{%s} = {%s}\n' % (project.guid, nmake_static_libraries_guid))
for project in executable_projects:
f.write('\t\t{%s} = {%s}\n' % (project.guid, nmake_executables_guid))
f.write('\tEndGlobalSection\n')
# end Group projects to folders
f.write('\tGlobalSection(ProjectConfigurationPlatforms) = postSolution\n')
# Specifies project configurations for solution configuration
for project in projects:
for cfg in self.configurations:
params = {'guid': project.guid, 'sol_cfg': cfg, 'proj_cfg': cfg, 'platform': platform}
f.write('\t\t{%(guid)s}.%(sol_cfg)s|%(platform)s.ActiveCfg = %(proj_cfg)s|%(platform)s\n' % params)
# Build.0 is basically 'Build checkbox' in configuration manager
f.write('\t\t{%(guid)s}.%(sol_cfg)s|%(platform)s.Build.0 = %(proj_cfg)s|%(platform)s\n' % params)
f.write('\tEndGlobalSection\n')
f.write('EndGlobal\n')
print('')
@staticmethod
def to_long_names(shortnames):
if platform == "cygwin":
return (subprocess.check_output(["cygpath", "-wal"] + shortnames).decode("utf-8", "strict").rstrip()).split("\n")
else:
return shortnames
# Unescape the values: \"tklo.dll\" => "tklo.dll"
escapepattern = re.compile(r'\\(.)')
@staticmethod
def defs_list(defs):
defines_list = []
# List defines
for key, value in defs.items():
define = key
if value is not None:
define += '=' + VisualStudioIntegrationGenerator.escapepattern.sub(r'\1', value).replace('""', '"')
defines_list.append(define)
return defines_list
ns = 'http://schemas.microsoft.com/developer/msbuild/2003'
@classmethod
def add_objects(cls, objects, root_path, ext, target, parent_node, flags = ''):
for obj in objects:
objfile = os.path.join(root_path, obj) + ext
if os.path.isfile(objfile):
obj_node = ET.SubElement(parent_node, '{%s}ClCompile' % cls.ns, Include=objfile)
if flags:
obj_additional_options_node = ET.SubElement(obj_node, '{%s}AdditionalOptions' % cls.ns)
obj_additional_options_node.text = flags
else:
print('Source %s in project %s does not exist' % (objfile, target.target_name()))
def write_project(self, project_path, target):
# See info at http://blogs.msdn.com/b/visualstudio/archive/2010/05/14/a-guide-to-vcxproj-and-props-file-structure.aspx
folder = os.path.dirname(project_path)
if not os.path.exists(folder):
os.makedirs(folder)
project_guid = self.gen_guid('project', target.short_name())
cxxflags = ' '.join(target.cxxflags)
cxxclrflags = ' '.join(target.cxxclrflags)
cflags = ' '.join(target.cflags)
ET.register_namespace('', self.ns)
proj_node = ET.Element('{%s}Project' % self.ns, DefaultTargets='Build', ToolsVersion='4.0')
proj_confs_node = ET.SubElement(proj_node, '{%s}ItemGroup' % self.ns, Label='ProjectConfigurations')
platform = self.platform
for configuration in self.configurations:
proj_conf_node = ET.SubElement(proj_confs_node,
'{%s}ProjectConfiguration' % self.ns,
Include='%s|%s' % (configuration, platform))
conf_node = ET.SubElement(proj_conf_node, '{%s}Configuration' % self.ns)
conf_node.text = configuration
platform_node = ET.SubElement(proj_conf_node, '{%s}Platform' % self.ns)
platform_node.text = platform
globals_node = ET.SubElement(proj_node, '{%s}PropertyGroup' % self.ns, Label='Globals')
proj_guid_node = ET.SubElement(globals_node, '{%s}ProjectGuid' % self.ns)
proj_guid_node.text = '{%s}' % project_guid
proj_keyword_node = ET.SubElement(globals_node, '{%s}Keyword' % self.ns)
proj_keyword_node.text = 'MakeFileProj'
proj_name_node = ET.SubElement(globals_node, '{%s}ProjectName' % self.ns)
proj_name_node.text = target.short_name()
ET.SubElement(proj_node, '{%s}Import' % self.ns, Project='$(VCTargetsPath)\\Microsoft.Cpp.Default.props')
for configuration in self.configurations:
conf_node = ET.SubElement(proj_node, '{%s}PropertyGroup' % self.ns, Label="Configuration",
Condition="'$(Configuration)|$(Platform)'=='%s|%s'" % (configuration, platform))
# Type of project used by the MSBuild to determine build process, see Microsoft.Makefile.targets
conf_type_node = ET.SubElement(conf_node, '{%s}ConfigurationType' % self.ns)
conf_type_node.text = 'Makefile'
# This defines the version of Visual Studio which can show next to project names in the Solution Explorer
platform_toolset_node = ET.SubElement(conf_node, '{%s}PlatformToolset' % self.ns)
platform_toolset_node.text = self.toolset
ET.SubElement(proj_node, '{%s}Import' % self.ns, Project='$(VCTargetsPath)\\Microsoft.Cpp.props')
ET.SubElement(proj_node, '{%s}ImportGroup' % self.ns, Label='ExtensionSettings')
for configuration in self.configurations:
prop_sheets_node = ET.SubElement(proj_node, '{%s}ImportGroup' % self.ns, Label='Configuration',
Condition="'$(Configuration)|$(Platform)'=='%s|%s'" % (configuration, platform))
ET.SubElement(prop_sheets_node, '{%s}Import' % self.ns,
Project='$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props',
Condition="exists('$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props')",
Label='LocalAppDataPlatform')
ET.SubElement(proj_node, '{%s}PropertyGroup' % self.ns, Label='UserMacros')
# VS IDE (at least "Peek definition") is allergic to paths like "C:/PROGRA~2/WI3CF2~1/10/Include/10.0.14393.0/um"; see
# https://developercommunity.visualstudio.com/content/problem/139659/vc-peek-definition-fails-to-navigate-to-windows-ki.html
# We need to convert to long paths here. Do this once, since it's time-consuming operation.
include_path_node_text = ';'.join(self.to_long_names(target.include))
for cfg_name, cfg_targets in self.configurations.items():
conf_node = ET.SubElement(proj_node, '{%s}PropertyGroup' % self.ns,
Condition="'$(Configuration)|$(Platform)'=='%s|%s'" % (cfg_name, platform))
nmake_params = {
'sh': os.path.join(self.gbuildparser.binpath, 'dash.exe'),
'builddir': self.gbuildparser.builddir,
'location': target.location,
'makecmd': self.gbuildparser.makecmd,
'target': target.target_name()}
nmake_build_node = ET.SubElement(conf_node, '{%s}NMakeBuildCommandLine' % self.ns)
nmake_build_node.text = cfg_targets['build'] % nmake_params
nmake_clean_node = ET.SubElement(conf_node, '{%s}NMakeCleanCommandLine' % self.ns)
nmake_clean_node.text = cfg_targets['clean'] % nmake_params
nmake_rebuild_node = ET.SubElement(conf_node, '{%s}NMakeReBuildCommandLine' % self.ns)
nmake_rebuild_node.text = cfg_targets['rebuild'] % nmake_params
nmake_output_node = ET.SubElement(conf_node, '{%s}NMakeOutput' % self.ns)
nmake_output_node.text = os.path.join(self.gbuildparser.workdir, 'LinkTarget', target.link_target)
nmake_defs_node = ET.SubElement(conf_node, '{%s}NMakePreprocessorDefinitions' % self.ns)
nmake_defs_node.text = ';'.join(self.defs_list(target.defs) + ['$(NMakePreprocessorDefinitions)'])
nmake_debug_command_node = ET.SubElement(conf_node, '{%s}LocalDebuggerCommand' % self.ns)
nmake_debug_command_node.text = os.path.join(self.gbuildparser.instdir, 'program', 'soffice.bin')
nmake_debug_flavor_node = ET.SubElement(conf_node, '{%s}DebuggerFlavor' % self.ns)
nmake_debug_flavor_node.text = 'WindowsLocalDebugger'
include_path_node = ET.SubElement(conf_node, '{%s}IncludePath' % self.ns)
include_path_node.text = include_path_node_text
additional_options_node = ET.SubElement(conf_node, '{%s}AdditionalOptions' % self.ns)
additional_options_node.text = cxxflags
ET.SubElement(proj_node, '{%s}ItemDefinitionGroup' % self.ns)
cxxobjects_node = ET.SubElement(proj_node, '{%s}ItemGroup' % self.ns)
self.add_objects(target.cxxobjects, self.gbuildparser.srcdir, '.cxx', target, cxxobjects_node)
self.add_objects(target.cxxclrobjects, self.gbuildparser.srcdir, '.cxx', target, cxxobjects_node, cxxclrflags)
cobjects_node = ET.SubElement(proj_node, '{%s}ItemGroup' % self.ns)
self.add_objects(target.cobjects, self.gbuildparser.srcdir, '.c', target, cobjects_node, cflags)
ET.SubElement(proj_node, '{%s}Import' % self.ns, Project='$(VCTargetsPath)\\Microsoft.Cpp.targets')
ET.SubElement(proj_node, '{%s}ImportGroup' % self.ns, Label='ExtensionTargets')
self.write_pretty_xml(proj_node, project_path)
self.write_filters(project_path + '.filters',
os.path.join(self.gbuildparser.srcdir, os.path.basename(target.location)),
[cxx_node.get('Include') for cxx_node in cxxobjects_node.findall('{%s}ClCompile' % self.ns)],
[c_node.get('Include') for c_node in cobjects_node.findall('{%s}ClCompile' % self.ns)])
return project_guid
def get_filter(self, module_dir, proj_file):
return '\\'.join(os.path.relpath(proj_file, module_dir).split('/')[:-1])
def get_subfilters(self, proj_filter):
parts = proj_filter.split('\\')
subfilters = set([proj_filter]) if proj_filter else set()
for i in range(1, len(parts)):
subfilters.add('\\'.join(parts[:i]))
return subfilters
def write_pretty_xml(self, node, file_path):
xml_str = ET.tostring(node, encoding='unicode')
pretty_str = minidom.parseString(xml_str).toprettyxml(encoding='utf-8')
with open(file_path, 'w') as f:
f.write(pretty_str.decode())
def add_nodes(self, files_node, module_dir, tag, project_files):
filters = set()
for project_file in project_files:
file_node = ET.SubElement(files_node, tag, Include=project_file)
if os.path.commonprefix([module_dir, project_file]) == module_dir:
project_filter = self.get_filter(module_dir, project_file)
filter_node = ET.SubElement(file_node, '{%s}Filter' % self.ns)
filter_node.text = project_filter
filters |= self.get_subfilters(project_filter)
return filters
def write_filters(self, filters_path, module_dir, cxx_files, c_files):
ET.register_namespace('', self.ns)
proj_node = ET.Element('{%s}Project' % self.ns, ToolsVersion='4.0')
filters = set()
compiles_node = ET.SubElement(proj_node, '{%s}ItemGroup' % self.ns)
filters |= self.add_nodes(compiles_node, module_dir, '{%s}ClCompile' % self.ns, cxx_files)
filters |= self.add_nodes(compiles_node, module_dir, '{%s}ClCompile' % self.ns, c_files)
filters_node = ET.SubElement(proj_node, '{%s}ItemGroup' % self.ns)
for proj_filter in filters:
filter_node = ET.SubElement(filters_node, '{%s}Filter' % self.ns, Include=proj_filter)
self.write_pretty_xml(proj_node, filters_path)
class QtCreatorIntegrationGenerator(IdeIntegrationGenerator):
def __init__(self, gbuildparser, ide):
IdeIntegrationGenerator.__init__(self, gbuildparser, ide)
self.target_by_location = {}
for target in self.gbuildparser.libs | self.gbuildparser.exes | self.gbuildparser.tests:
if target.location not in self.target_by_location:
self.target_by_location[target.location] = set()
self.target_by_location[target.location] |= set([target])
self._do_log = False # set to 'True' to activate log of QtCreatorIntegrationGenerator
if self._do_log:
qtlog_path = os.path.abspath('../qtlog_.txt')
self.qtlog = open(qtlog_path, 'w')
def _log(self, message):
if self._do_log:
self.qtlog.write(message)
def log_close(self):
if self._do_log:
self.qtlog.close()
def generate_build_configs(self, lib_folder):
module_folder = os.path.join(self.base_folder, lib_folder)
xml = ""
# In QtCreator UI, build configs are listed alphabetically,
# so it can be different from the creation order.
# So we prefix the names with the index.
xml += QtCreatorIntegrationGenerator.build_configs_template % {
'index': '0',
'base_folder': module_folder,
'arg': "",
'name': "1-Build %s" % lib_folder,
}
xml += QtCreatorIntegrationGenerator.build_configs_template % {
'index': '1',
'base_folder': module_folder,
'arg': "unitcheck",
'name': "2-Local tests -- quick tests (unitcheck)",
}
xml += QtCreatorIntegrationGenerator.build_configs_template % {
'index': '2',
'base_folder': module_folder,
'arg': "unitcheck slowcheck screenshot",
'name': "3-Local tests -- slow tests (unitcheck, slowcheck, screenshot)",
}
xml += QtCreatorIntegrationGenerator.build_configs_template % {
'index': '3',
'base_folder': module_folder,
'arg': "unitcheck slowcheck screenshot subsequentcheck",
'name': "4-Local tests -- integration tests (unitcheck, slowcheck, screenshot, subsequentcheck)",
}
xml += QtCreatorIntegrationGenerator.build_configs_template % {
'index': '4',
'base_folder': self.base_folder,
'arg': "unitcheck",
'name': "5-Global tests -- quick tests (unitcheck)",
}
xml += QtCreatorIntegrationGenerator.build_configs_template % {
'index': '5',
'base_folder': self.base_folder,
'arg': "unitcheck slowcheck screenshot",
'name': "6-Global tests -- slow tests (unitcheck, slowcheck, screenshot)",
}
xml += QtCreatorIntegrationGenerator.build_configs_template % {
'index': '6',
'base_folder': self.base_folder,
'arg': "unitcheck slowcheck screenshot subsequentcheck",
'name': "7-Global tests -- integration tests (unitcheck, slowcheck, screenshot, subsequentcheck)",
}
xml += QtCreatorIntegrationGenerator.build_configs_template % {
'index': '7',
'base_folder': self.base_folder,
'arg': "",
'name': "8-Global build -- nocheck",
}
xml += QtCreatorIntegrationGenerator.build_configs_template % {
'index': '8',
'base_folder': self.base_folder,
'arg': "",
'name': "9-Global build",
}
xml += QtCreatorIntegrationGenerator.build_configs_count_template % {
'nb': '9',
}
return xml
def generate_meta_build_configs(self):
xml = ""
# In QtCreator UI, build configs are listed alphabetically,
# so it can be different from the creation order.
# So we prefix the names with the index.
xml += QtCreatorIntegrationGenerator.build_configs_template % {
'index': '0',
'base_folder': self.base_folder,
'arg': "",
'name': "01-Global Build",
}
xml += QtCreatorIntegrationGenerator.build_configs_template % {
'index': '1',
'base_folder': self.base_folder,
'arg': "unitcheck",
'name': "02-Global tests -- quick tests (unitcheck)",
}
xml += QtCreatorIntegrationGenerator.build_configs_template % {
'index': '2',
'base_folder': self.base_folder,
'arg': "unitcheck slowcheck screenshot",
'name': "03-Global tests -- slow tests (unitcheck, slowcheck, screenshot)",
}
xml += QtCreatorIntegrationGenerator.build_configs_template % {
'index': '3',
'base_folder': self.base_folder,
'arg': "unitcheck slowcheck screenshot subsequentcheck",
'name': "04-Global tests -- integration tests (unitcheck, slowcheck, screenshot, subsequentcheck)",
}
xml += QtCreatorIntegrationGenerator.build_configs_template % {
'index': '4',
'base_folder': self.base_folder,
'arg': "perfcheck",
'name': "05-Global tests -- performance tests (perfcheck)",
}
xml += QtCreatorIntegrationGenerator.build_configs_template % {
'index': '5',
'base_folder': self.base_folder,
'arg': "check",
'name': "06-Global tests -- tests (check)",
}
xml += QtCreatorIntegrationGenerator.build_configs_template % {
'index': '6',
'base_folder': self.base_folder,
'arg': "",
'name': "07-Global build -- nocheck",
}
xml += QtCreatorIntegrationGenerator.build_configs_template % {
'index': '7',
'base_folder': self.base_folder,
'arg': "build-l10n-only",
'name': "08-Global build -- build-l10n-only",
}
xml += QtCreatorIntegrationGenerator.build_configs_template % {
'index': '8',
'base_folder': self.base_folder,
'arg': "build-non-l10n-only",
'name': "09-Global build -- build-non-l10n-only",
}
xml += QtCreatorIntegrationGenerator.build_configs_template % {
'index': '9',
'base_folder': self.base_folder,
'arg': "clean",
'name': "10-Global build -- clean",
}
xml += QtCreatorIntegrationGenerator.build_configs_template % {
'index': '10',
'base_folder': self.base_folder,
'arg': "clean-build",
'name': "11-Global build -- clean-build",
}
xml += QtCreatorIntegrationGenerator.build_configs_template % {
'index': '11',
'base_folder': self.base_folder,
'arg': "clean-host",
'name': "12-Global build -- clean-host",
}
xml += QtCreatorIntegrationGenerator.build_configs_count_template % {
'nb': '12',
}
return xml
# By default, QtCreator creates 2 BuildStepList : "Build" and "Clean"
# but the "clean" can be empty.
build_configs_template = """
%(base_folder)s
true
Make
Qt4ProjectManager.MakeStep
-w
-r
false
%(arg)s
1
Build
ProjectExplorer.BuildSteps.Build
1
false
%(name)s
Qt4ProjectManager.Qt4BuildConfiguration
%(index)s
true
"""
build_configs_count_template = """
%(nb)s
"""
def generate_deploy_configs(self, lib_folder):
xml = QtCreatorIntegrationGenerator.deploy_configs_template % {}
return xml
deploy_configs_template = """
0
Deploy
ProjectExplorer.BuildSteps.Deploy
1
Deploy locally
ProjectExplorer.DefaultDeployConfiguration
1
"""
def generate_run_configs(self, lib_folder):
if platform == 'darwin':
loexec = "%s/instdir/LibreOfficeDev.app/Contents/MacOS/soffice" % self.base_folder
else:
loexec = "%s/instdir/program/soffice.bin" % self.base_folder
xml = QtCreatorIntegrationGenerator.run_configs_template % {
'loexec': loexec,
'workdir': self.base_folder
}
return xml
run_configs_template = """
false
false
false
false
true
0.01
10
true
1
25
1
true
false
true
valgrind
0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
%(loexec)s
false
%(workdir)s
Run libreoffice/instdir/program/soffice
ProjectExplorer.CustomExecutableRunConfiguration
3768
false
true
false
false
true
1
"""
def generate_pro_shared_content(self, lib_folder):
build_configs = self.generate_build_configs(lib_folder)
deploy_configs = self.generate_deploy_configs(lib_folder)
run_configs = self.generate_run_configs(lib_folder)
xml = QtCreatorIntegrationGenerator.pro_shared_template % {
'build_configs': build_configs,
'deploy_configs': deploy_configs,
'run_configs': run_configs,
}
return xml
def generate_meta_pro_shared_content(self):
build_configs = self.generate_meta_build_configs()
deploy_configs = self.generate_deploy_configs("")
run_configs = self.generate_run_configs("")
xml = QtCreatorIntegrationGenerator.pro_shared_template % {
'build_configs': build_configs,
'deploy_configs': deploy_configs,
'run_configs': run_configs,
}
return xml
pro_shared_template = """
ProjectExplorer.Project.ActiveTarget
0
ProjectExplorer.Project.EditorSettings
true
false
true
Cpp
CppGlobal
QmlJS
QmlJSGlobal
2
UTF-8
false
4
false
80
true
true
1
true
false
1
true
0
8
true
1
true
true
true
false
ProjectExplorer.Project.PluginSettings
ProjectExplorer.Project.Target.0
Desktop
Desktop
{0701de51-c96e-4e4f-85c3-e70b223c5076}
0
0
0
%(build_configs)s
%(deploy_configs)s
%(run_configs)s
ProjectExplorer.Project.TargetCount
1
ProjectExplorer.Project.Updater.EnvironmentId
{5abcafed-86f6-49f6-b1cb-380fadd21211}
ProjectExplorer.Project.Updater.FileVersion
15
"""
def get_file_path(self, src_file, ext_choices):
path = os.path.join(self.gbuildparser.srcdir, src_file)
for ext in ext_choices:
full_path = path + ext
if os.path.isfile(full_path):
return full_path
return ""
def get_source_path(self, src_file):
return self.get_file_path(src_file, (".cxx", ".cpp", ".c", ".mm"))
def get_header_path(self, src_file):
return self.get_file_path(src_file, (".hxx", ".hpp", ".h"))
def build_data_libs(self):
self.data_libs = {}
all_libs = self.gbuildparser.libs | self.gbuildparser.exes | self.gbuildparser.tests
for lib in all_libs:
self._log("\nlibrary : %s, loc=%s" % (lib.short_name(), lib.location))
lib_name = os.path.basename(lib.location)
lib_folder = os.path.relpath(lib.location, self.base_folder)
def lopath(path):
if platform == "cygwin":
# absolute paths from GbuildToJson are Windows paths,
# so convert everything to such ones
abs_path = path
if not ntpath.isabs(abs_path):
abs_path = ntpath.join(self.gbuildparser.srcdir, path)
return abs_path.replace('\\', '/')
return os.path.abspath(path)
defines_list = []
sources_list = []
objcxx_sources_list = []
includepath_list = []
# The explicit headers list is not mandatory :
# QtCreator just needs 'include_path_list' to find all headers files.
# But files listed in 'header_list' will be shown
# in a specific "Headers" folder in QtCreator's Project panel.
# We will list here only headers files of current lib.
headers_list = []
objcxx_headers_list = []
for file_ in lib.cxxobjects:
# the file has no extension : search it
# self._log("\n file : %s" % file_)
path = self.get_source_path(file_)
if path:
sources_list.append(lopath(path))
# few cxxobject files have a header beside
path = self.get_header_path(file_)
if path:
headers_list.append(lopath(path))
for file_ in lib.objcxxobjects:
# the file has no extension: search it
path = self.get_source_path(file_)
if path:
objcxx_sources_list.append(lopath(path))
# several objcxxobject files have a header beside
path = self.get_header_path(file_)
if path:
objcxx_headers_list.append(lopath(path))
cxxstdversionflag = ''
for cxxflag in lib.cxxflags:
# extract flag for C++ standard version
if cxxflag.startswith('-std'):
cxxstdversionflag = cxxflag
# List all include paths
for hdir in (lib.include + lib.include_sys):
hf_lopath = lopath(hdir)
includepath_list.append(hf_lopath)
# List headers files from current lib
for hdir in lib.include:
if hdir.startswith(lib.location):
for dirpath, _, files in os.walk(hdir):
for hf in files:
if hf.endswith(('.h', '.hxx', '.hpp', '.hrc')):
hf_lopath = lopath(os.path.join(dirpath, hf))
headers_list.append(hf_lopath)
# List defines
for key, value in lib.defs.items():
define = key
if value is not None:
define += '=' + value
defines_list.append(define)
# All data are prepared, store them for the lib.
if lib_folder in self.data_libs:
self.data_libs[lib_folder]['sources'] |= set(sources_list)
self.data_libs[lib_folder]['headers'] |= set(headers_list)
self.data_libs[lib_folder]['objcxx_sources'] |= set(objcxx_sources_list)
self.data_libs[lib_folder]['objcxx_headers'] |= set(objcxx_headers_list)
self.data_libs[lib_folder]['cxxstdversionflag'] = cxxstdversionflag
self.data_libs[lib_folder]['includepath'] |= set(includepath_list)
self.data_libs[lib_folder]['defines'] |= set(defines_list)
else:
self.data_libs[lib_folder] = {
'sources': set(sources_list),
'headers': set(headers_list),
'objcxx_sources': set(objcxx_sources_list),
'objcxx_headers': set(objcxx_headers_list),
'cxxstdversionflag': cxxstdversionflag,
'includepath': set(includepath_list),
'defines': set(defines_list),
'loc': lib.location,
'name': lib_name
}
def emit(self):
mode = 'w+'
self.base_folder = self.gbuildparser.builddir
# for .pro files, we must explicitly list all files (.c, .h)
# so we can't reuse directly the same method than for kde integration.
self.build_data_libs()
# subdirs for the meta .pro file
subdirs_meta_pro = []
subdirs_list = self.data_libs.keys()
# Now we can create Qt files
for lib_folder in subdirs_list:
sources_list = sorted(self.data_libs[lib_folder]['sources'])
headers_list = sorted(self.data_libs[lib_folder]['headers'])
objcxx_sources_list = sorted(self.data_libs[lib_folder]['objcxx_sources'])
objcxx_headers_list = sorted(self.data_libs[lib_folder]['objcxx_headers'])
cxxstdversionflag = self.data_libs[lib_folder]['cxxstdversionflag']
includepath_list = sorted(self.data_libs[lib_folder]['includepath'])
defines_list = sorted(self.data_libs[lib_folder]['defines'])
lib_loc = self.data_libs[lib_folder]['loc']
lib_name = self.data_libs[lib_folder]['name']
sources = " \\\n".join(sources_list)
headers = " \\\n".join(headers_list)
objcxx_sources = " \\\n".join(objcxx_sources_list)
objcxx_headers = " \\\n".join(objcxx_headers_list)
includepath = " \\\n".join(includepath_list)
defines = " \\\n".join(defines_list)
# strip '-std=' or '-std:' prefix
assert(isinstance(cxxstdversionflag, str) and cxxstdversionflag.startswith('-std'))
cxxstdversion = cxxstdversionflag[5:]
# create .pro file
relative_subdir_path = os.path.relpath(lib_loc, self.gbuildparser.srcdir)
subdirs_meta_pro.append(relative_subdir_path)
qt_pro_file = os.path.join(self.base_folder, relative_subdir_path, lib_name + '.pro')
try:
content = QtCreatorIntegrationGenerator.pro_template % {'sources': sources, 'headers': headers, 'cxxstdversionflag': cxxstdversionflag,
'cxxstdversion': cxxstdversion, 'objcxx_sources': objcxx_sources,
'objcxx_headers': objcxx_headers, 'includepath': includepath, 'defines': defines}
with open(qt_pro_file, mode) as fpro:
fpro.write(content)
self._log("created %s\n" % qt_pro_file)
except Exception as e:
print("ERROR : creating pro file=" + qt_pro_file, file=sys.stderr)
print(e, file=sys.stderr)
temp = traceback.format_exc() # .decode('utf8')
print(temp, file=sys.stderr)
print("\n\n", file=sys.stderr)
# create .pro.shared file
qt_pro_shared_file = os.path.join(self.base_folder, relative_subdir_path, lib_name + '.pro.shared')
try:
with open(qt_pro_shared_file, mode) as fproshared:
fproshared.write(self.generate_pro_shared_content(lib_folder))
self._log("created %s\n" % qt_pro_shared_file)
except Exception as e:
print("ERROR : creating pro.shared file=" + qt_pro_shared_file, file=sys.stderr)
print(e, file=sys.stderr)
temp = traceback.format_exc()
print(temp, file=sys.stderr)
print("\n\n", file=sys.stderr)
# create meta .pro file (lists all sub projects)
qt_meta_pro_file = os.path.join(self.base_folder, 'lo.pro')
try:
subdirs = " \\\n".join(sorted(subdirs_meta_pro))
content = QtCreatorIntegrationGenerator.pro_meta_template % {'subdirs': subdirs}
with open(qt_meta_pro_file, 'w+') as fmpro:
fmpro.write(content)
except Exception as e:
print("ERROR : creating lo.pro file=" + qt_meta_pro_file, file=sys.stderr)
print(e, file=sys.stderr)
temp = traceback.format_exc()
print(temp, file=sys.stderr)
print("\n\n", file=sys.stderr)
# create meta .pro.shared file
qt_meta_pro_shared_file = os.path.join(self.base_folder, 'lo.pro.shared')
try:
with open(qt_meta_pro_shared_file, mode) as fmproshared:
fmproshared.write(self.generate_meta_pro_shared_content())
self._log("created %s\n" % qt_meta_pro_shared_file)
except Exception as e:
print("ERROR : creating lo.pro.shared file=" + qt_meta_pro_shared_file, file=sys.stderr)
print(e, file=sys.stderr)
temp = traceback.format_exc()
print(temp, file=sys.stderr)
print("\n\n", file=sys.stderr)
self.log_close()
pro_template = """TEMPLATE = lib
CONFIG += console
CONFIG -= app_bundle
CONFIG -= qt
CONFIG += %(cxxstdversion)s
QMAKE_CXXFLAGS += %(cxxstdversionflag)s
INCLUDEPATH += %(includepath)s
SOURCES += %(sources)s
HEADERS += %(headers)s
OBJECTIVE_SOURCES += %(objcxx_sources)s
OBJECTIVE_HEADERS += %(objcxx_headers)s
DEFINES += %(defines)s
"""
pro_meta_template = """TEMPLATE = subdirs
SUBDIRS = %(subdirs)s
"""
def get_options():
parser = argparse.ArgumentParser(
description='LibreOffice gbuild IDE project generator')
parser.add_argument('--ide', dest='ide', required=True,
help='the IDE to generate project files for')
parser.add_argument('--make', dest='makecmd', required=True,
help='the command to execute make')
return parser.parse_args()
if __name__ == '__main__':
args = get_options()
# FIXME: Hack
if args.makecmd == 'make':
args.makecmd = '/usr/bin/make'
paths = {}
generators = {
'codelite': CodeliteIntegrationGenerator,
'eclipsecdt': EclipseCDTIntegrationGenerator,
'kdevelop': KdevelopIntegrationGenerator,
'xcode': XcodeIntegrationGenerator,
'vs': VisualStudioIntegrationGenerator,
'vim': VimIntegrationGenerator,
'debug': DebugIntegrationGenerator,
'qtcreator': QtCreatorIntegrationGenerator,
}
if args.ide not in generators.keys():
print("Invalid ide. valid values are %s" % ','.join(generators.keys()))
sys.exit(1)
gbuildparser = GbuildParser(args.makecmd).parse()
generators[args.ide](gbuildparser, args.ide).emit()
print("Successfully created the project files.")
# Local Variables:
# indent-tabs-mode: nil
# End:
#
# vim: set et sw=4 ts=4: