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/.
12 This script generates precompiled headers for a given
15 Given a gmake makefile that belongs to some LO module:
16 1) Process the makefile to find source files (process_makefile).
17 2) For every source file, find all includes (process_source).
18 3) Uncommon and rare includes are filtered (remove_rare).
19 4) Conflicting headers are excluded (filter_ignore).
20 5) Local files to the source are excluded (Filter_Local).
21 6) Fixup missing headers that sources expect (fixup).
22 7) The resulting includes are sorted by category (sort_by_category).
23 8) The pch file is generated (generate).
33 EXCLUDE_MODULE = False
39 # System includes: oox, sal, sd, svl, vcl
45 # module.library : (min, system, module, local), best time
46 'accessibility.acc' : ( 4, EXCLUDE, INCLUDE, INCLUDE), # 7.8
47 'basctl.basctl' : ( 3, EXCLUDE, INCLUDE, EXCLUDE), # 11.9
48 'basegfx.basegfx' : ( 3, EXCLUDE, EXCLUDE, INCLUDE), # 3.8
49 'basic.sb' : ( 2, EXCLUDE, EXCLUDE, INCLUDE), # 10.7
50 'chart2.chartcontroller' : ( 6, EXCLUDE, INCLUDE, INCLUDE), # 18.4
51 'chart2.chartcore' : ( 3, EXCLUDE, EXCLUDE, INCLUDE), # 22.5
52 'comphelper.comphelper' : ( 4, EXCLUDE, INCLUDE, INCLUDE), # 7.6
53 'configmgr.configmgr' : ( 6, EXCLUDE, INCLUDE, INCLUDE), # 6.0
54 'connectivity.ado' : ( 2, EXCLUDE, EXCLUDE, EXCLUDE), # 6.4
55 'connectivity.calc' : ( 2, EXCLUDE, EXCLUDE, EXCLUDE), # 4.6
56 'connectivity.dbase' : ( 2, EXCLUDE, INCLUDE, INCLUDE), # 5.2
57 'connectivity.dbpool2' : ( 5, EXCLUDE, INCLUDE, EXCLUDE), # 3.0
58 'connectivity.dbtools' : ( 2, EXCLUDE, EXCLUDE, INCLUDE), # 0.8
59 'connectivity.file' : ( 2, EXCLUDE, INCLUDE, EXCLUDE), # 5.1
60 'connectivity.firebird_sdbc' : ( 2, EXCLUDE, EXCLUDE, EXCLUDE), # 5.1
61 'connectivity.flat' : ( 2, EXCLUDE, INCLUDE, INCLUDE), # 4.6
62 'connectivity.mysql' : ( 4, EXCLUDE, INCLUDE, EXCLUDE), # 3.4
63 'connectivity.odbc' : ( 2, EXCLUDE, EXCLUDE, INCLUDE), # 5.0
64 'connectivity.postgresql-sdbc-impl' : ( 3, EXCLUDE, EXCLUDE, EXCLUDE), # 6.7
65 'cppcanvas.cppcanvas' : (11, EXCLUDE, INCLUDE, INCLUDE), # 4.8
66 'cppuhelper.cppuhelper' : ( 3, EXCLUDE, EXCLUDE, EXCLUDE), # 4.6
67 'cui.cui' : ( 8, EXCLUDE, INCLUDE, EXCLUDE), # 19.7
68 'dbaccess.dba' : ( 6, EXCLUDE, INCLUDE, INCLUDE), # 13.8
69 'dbaccess.dbaxml' : ( 2, EXCLUDE, EXCLUDE, EXCLUDE), # 6.5
70 'dbaccess.dbu' : (12, EXCLUDE, EXCLUDE, EXCLUDE), # 23.6
71 'dbaccess.sdbt' : ( 1, EXCLUDE, INCLUDE, EXCLUDE), # 2.9
72 'desktop.deployment' : ( 3, EXCLUDE, EXCLUDE, EXCLUDE), # 6.1
73 'desktop.deploymentgui' : ( 3, EXCLUDE, EXCLUDE, EXCLUDE), # 5.7
74 'desktop.deploymentmisc' : ( 3, EXCLUDE, EXCLUDE, EXCLUDE), # 3.4
75 'desktop.sofficeapp' : ( 6, EXCLUDE, INCLUDE, INCLUDE), # 6.5
76 'drawinglayer.drawinglayer' : ( 4, EXCLUDE, EXCLUDE, EXCLUDE), # 7.4
77 'editeng.editeng' : ( 5, EXCLUDE, INCLUDE, EXCLUDE), # 13.0
78 'forms.frm' : ( 2, EXCLUDE, EXCLUDE, EXCLUDE), # 14.2
79 'framework.fwk' : ( 7, EXCLUDE, INCLUDE, INCLUDE), # 14.8
80 'hwpfilter.hwp' : ( 3, EXCLUDE, INCLUDE, INCLUDE), # 6.0
81 'lotuswordpro.lwpft' : ( 2, EXCLUDE, EXCLUDE, EXCLUDE), # 11.6
82 'oox.oox' : ( 6, EXCLUDE, EXCLUDE, INCLUDE), # 28.2
83 'package.package2' : ( 3, EXCLUDE, INCLUDE, INCLUDE), # 4.5
84 'package.xstor' : ( 2, EXCLUDE, INCLUDE, EXCLUDE), # 3.8
85 'reportdesign.rpt' : ( 9, EXCLUDE, INCLUDE, INCLUDE), # 9.4
86 'reportdesign.rptui' : ( 4, EXCLUDE, INCLUDE, INCLUDE), # 13.1
87 'reportdesign.rptxml' : ( 2, EXCLUDE, EXCLUDE, INCLUDE), # 7.6
88 'sal.sal' : ( 2, EXCLUDE, EXCLUDE, INCLUDE), # 4.2
89 'sc.sc' : (12, EXCLUDE, INCLUDE, INCLUDE), # 92.6
90 'sc.scfilt' : ( 4, EXCLUDE, EXCLUDE, INCLUDE), # 39.9
91 'sc.scui' : ( 1, EXCLUDE, EXCLUDE, INCLUDE), # 15.0
92 'sc.vbaobj' : ( 1, EXCLUDE, EXCLUDE, INCLUDE), # 17.3
93 'sd.sd' : ( 4, EXCLUDE, EXCLUDE, INCLUDE), # 47.4
94 'sd.sdui' : ( 4, EXCLUDE, INCLUDE, INCLUDE), # 9.4
95 'sdext.PresentationMinimizer' : ( 2, EXCLUDE, INCLUDE, INCLUDE), # 4.1
96 'sdext.PresenterScreen' : ( 2, EXCLUDE, INCLUDE, EXCLUDE), # 7.1
97 'sfx2.sfx' : ( 3, EXCLUDE, EXCLUDE, EXCLUDE), # 27.4
98 'slideshow.slideshow' : ( 4, EXCLUDE, INCLUDE, EXCLUDE), # 10.8
99 'sot.sot' : ( 5, EXCLUDE, EXCLUDE, INCLUDE), # 3.1
100 'starmath.sm' : ( 5, EXCLUDE, EXCLUDE, INCLUDE), # 10.9
101 'svgio.svgio' : ( 8, EXCLUDE, EXCLUDE, INCLUDE), # 4.3
102 'emfio.emfio' : ( 8, EXCLUDE, EXCLUDE, INCLUDE), # 4.3
103 'svl.svl' : ( 6, EXCLUDE, EXCLUDE, EXCLUDE), # 7.6
104 'svtools.svt' : ( 4, EXCLUDE, INCLUDE, EXCLUDE), # 17.6
105 'svx.svx' : ( 3, EXCLUDE, EXCLUDE, INCLUDE), # 20.7
106 'svx.svxcore' : ( 7, EXCLUDE, INCLUDE, EXCLUDE), # 37.0
107 'sw.msword' : ( 4, EXCLUDE, INCLUDE, INCLUDE), # 22.4
108 'sw.sw' : ( 7, EXCLUDE, EXCLUDE, INCLUDE), # 129.6
109 'sw.swui' : ( 3, EXCLUDE, INCLUDE, INCLUDE), # 26.1
110 'sw.vbaswobj' : ( 4, EXCLUDE, INCLUDE, INCLUDE), # 13.1
111 'tools.tl' : ( 5, EXCLUDE, EXCLUDE, EXCLUDE), # 4.2
112 'unotools.utl' : ( 3, EXCLUDE, EXCLUDE, INCLUDE), # 7.0
113 'unoxml.unoxml' : ( 1, EXCLUDE, EXCLUDE, EXCLUDE), # 4.6
114 'uui.uui' : ( 4, EXCLUDE, EXCLUDE, EXCLUDE), # 4.9
115 'vbahelper.msforms' : ( 3, EXCLUDE, INCLUDE, INCLUDE), # 5.2
116 'vbahelper.vbahelper' : ( 3, EXCLUDE, EXCLUDE, INCLUDE), # 7.0
117 'vcl.vcl' : ( 6, EXCLUDE, INCLUDE, INCLUDE), # 35.7
118 'writerfilter.writerfilter' : ( 5, EXCLUDE, EXCLUDE, EXCLUDE), # 19.7/27.3
119 'xmloff.xo' : ( 7, EXCLUDE, INCLUDE, INCLUDE), # 22.1
120 'xmloff.xof' : ( 1, EXCLUDE, EXCLUDE, INCLUDE), # 4.4
121 'xmlscript.xmlscript' : ( 4, EXCLUDE, EXCLUDE, INCLUDE), # 3.6
122 'xmlsecurity.xmlsecurity' : ( 6, EXCLUDE, INCLUDE, INCLUDE), # 5.1
123 'xmlsecurity.xsec_xmlsec' : ( 2, EXCLUDE, INCLUDE, INCLUDE), # 4.4
124 'xmlsecurity.xsec_gpg' : ( 2, EXCLUDE, INCLUDE, INCLUDE), # ?
127 def remove_rare(raw, min_use=-1):
128 """ Remove headers not commonly included.
129 The minimum threshold is min_use.
131 # The minimum number of times a header
132 # must be included to be in the PCH.
133 min_use = min_use if min_use >= 0 else CUTOFF
136 if not raw or not len(raw):
142 for x in range(1, len(inc)):
158 def process_list(list, callable):
159 """ Given a list and callable
160 we pass each entry through
161 the callable and only add to
162 the output if not blank.
167 if line and len(line):
171 def find_files(path, recurse=True):
173 for root, dir, files in os.walk(path):
174 list += map(lambda x: os.path.join(root, x), files)
177 def get_filename(line):
178 """ Strips the line from the
179 '#include' and angled brakets
180 and return the filename only.
182 if not len(line) or line[0] != '#':
184 return re.sub(r'(.*#include\s*)<(.*)>(.*)', r'\2', line)
186 def is_c_runtime(inc, root, module):
187 """ Heuristic-based detection of C/C++
189 They are all-lowercase, with .h or
190 no extension, filename only.
191 Try to check that they are not LO headers.
193 inc = get_filename(inc)
195 if inc.endswith('.hxx') or inc.endswith('.hpp'):
198 if inc.endswith('.h') and inc.startswith( 'config_' ):
205 if c == '.' and not inc.endswith('.h'):
211 if not hasdot: # <memory> etc.
214 if glob.glob(os.path.join(root, module, '**', inc), recursive=True):
220 """ There are two forms of includes,
221 those with <> and "".
222 Technically, the difference is that
223 the compiler can use an internal
224 representation for an angled include,
225 such that it doesn't have to be a file.
226 For our purposes, there is no difference.
227 Here, we convert everything to angled.
229 if not raw or not len(raw):
234 return re.sub(r'(.*#include\s*)\"(.*)\"(.*)', r'#include <\2>', raw)
236 class Filter_Local(object):
237 """ Filter headers local to a module.
238 allow_public: allows include/module/file.hxx
239 #include <module/file.hxx>
240 allow_module: allows module/inc/file.hxx
242 allow_locals: allows module/source/file.hxx and
243 module/source/inc/file.hxx
246 def __init__(self, root, module, allow_public=True, allow_module=True, allow_locals=True):
249 self.allow_public = allow_public
250 self.allow_module = allow_module
251 self.allow_locals = allow_locals
252 self.public_prefix = '<' + self.module + '/'
254 all = find_files(os.path.join(root, module))
255 self.module_includes = []
257 mod_prefix = module + '/inc/'
260 self.module_includes.append(i)
262 self.locals.append(i)
264 def is_public(self, line):
265 return self.public_prefix in line
267 def is_module(self, line):
268 """ Returns True if in module/inc/... """
269 filename = get_filename(line)
270 for i in self.module_includes:
271 if i.endswith(filename):
275 def is_local(self, line):
276 """ Returns True if in module/source/... """
277 filename = get_filename(line)
278 for i in self.locals:
279 if i.endswith(filename):
283 def is_external(self, line):
284 return is_c_runtime(line, self.root, self.module) and \
285 not self.is_public(line) and \
286 not self.is_module(line) and \
287 not self.is_local(line)
289 def find_local_file(self, line):
290 """ Finds the header file in the module dir,
291 but doesn't validate.
293 filename = get_filename(line)
294 for i in self.locals:
295 if i.endswith(filename):
297 for i in self.module_includes:
298 if i.endswith(filename):
302 def proc(self, line):
303 assert line and len(line)
307 sys.stderr.write('unhandled #include : {}\n'.format(line))
310 assert line[0] != '<' and line[0] != '#'
312 filename = get_filename(line)
314 # Local with relative path.
315 if filename.startswith('..'):
316 # Exclude for now as we don't have cxx path.
319 # Locals are included first (by the compiler).
320 if self.is_local(filename):
321 # Use only locals that are in some /inc/ directory (either in <module>/inc or
322 # somewhere under <module>/source/**/inc/, compilations use -I for these paths
323 # and headers elsewhere would not be found when compiling the PCH.
324 if not self.allow_locals:
326 elif '/inc/' in filename:
328 elif glob.glob(os.path.join(self.root, self.module, '**', 'inc', filename), recursive=True):
333 # Module headers are next.
334 if self.is_module(filename):
335 return line if self.allow_module else ''
337 # Public headers are last.
338 if self.is_public(line):
339 return line if self.allow_public else ''
341 # Leave out potentially unrelated files local
342 # to some other module we can't include directly.
343 if '/' not in filename and not self.is_external(filename):
349 def filter_ignore(line, module):
350 """ Filters includes from known
352 Expects sanitized input.
354 assert line and len(line)
356 # Always include files without extension.
360 # Extract filenames for ease of comparison.
361 line = get_filename(line)
363 # Filter out all files that are not normal headers.
364 if not line.endswith('.h') and \
365 not line.endswith('.hxx') and \
366 not line.endswith('.hpp') and \
367 not line.endswith('.hdl'):
371 'LibreOfficeKit/LibreOfficeKitEnums.h', # Needs special directives
372 'LibreOfficeKit/LibreOfficeKitTypes.h', # Needs special directives
373 'jerror.h', # c++ unfriendly
374 'jpeglib.h', # c++ unfriendly
375 'boost/spirit/include/classic_core.hpp' # depends on BOOST_SPIRIT_DEBUG
378 if module == 'basic':
380 'basic/vbahelper.hxx',
382 if module == 'connectivity':
384 'com/sun/star/beans/PropertyAttribute.hpp', # OPTIONAL defined via objbase.h
385 'com/sun/star/sdbcx/Privilege.hpp', # DELETE defined via objbase.h
386 'ado/*' , # some strange type conflict because of Window's adoctint.h
392 'progress.hxx', # special directives
393 'scslots.hxx', # special directives
397 'sdgslots.hxx', # special directives
398 'sdslots.hxx', # special directives
402 'sfx2/recentdocsview.hxx', # Redefines ApplicationType defined in objidl.h
403 'sfx2/sidebar/Sidebar.hxx',
404 'sfx2/sidebar/UnoSidebar.hxx',
405 'sfxslots.hxx', # externally defined types
409 'sysformats.hxx', # Windows headers
413 'accmgr.hxx', # redefines ImplAccelList
416 'opengl/gdiimpl.hxx',
418 'openglgdiimpl', # ReplaceTextA
420 'salinst.hxx', # GetDefaultPrinterA
421 'salprn.hxx', # SetPrinterDataA
423 'vcl/oldprintadaptor.hxx',
424 'vcl/opengl/OpenGLContext.hxx',
425 'vcl/opengl/OpenGLHelper.hxx', # Conflicts with X header on *ix
427 'vcl/prntypes.hxx', # redefines Orientation from filter/jpeg/Exif.hxx
430 if module == 'xmloff':
432 'SchXMLExport.hxx', # SchXMLAutoStylePoolP.hxx not found
433 'SchXMLImport.hxx', # enums redefined in draw\sdxmlimp_impl.hxx
434 'XMLEventImportHelper.hxx', # NameMap redefined in XMLEventExport.hxx
435 'xmloff/XMLEventExport.hxx', # enums redefined
437 if module == 'xmlsecurity':
440 'xmlsecurity/xmlsec-wrapper.h',
442 if module == 'external/pdfium':
444 'third_party/freetype/include/pstables.h',
446 if module == 'external/clucene':
453 'CLucene/LuceneThreads.h',
454 'CLucene/config/_threads.h',
456 if module == 'external/skia':
459 'zlib.h', # causes crc32 conflict
460 'dirent.h', # unix-specific
465 'fontconfig/fontconfig.h',
467 'src/Transform_inl.h',
468 'src/c/sk_c_from_to.h',
469 'src/c/sk_types_priv.h',
470 'src/core/SkBlitBWMaskTemplate.h',
471 'src/sfnt/SkSFNTHeader.h',
473 'src/core/SkCubicSolver.h',
474 'src/sksl/SkSLCPP.h',
475 'src/gpu/vk/GrVkAMDMemoryAllocator.h',
480 for i in ignore_list:
481 if line.startswith(i):
483 if i[0] == '*' and line.endswith(i[1:]):
485 if i[-1] == '*' and line.startswith(i[:-1]):
490 def fixup(includes, module):
491 """ Here we add any headers
492 necessary in the pch.
493 These could be known to be very
494 common but for technical reasons
495 left out of the pch by this generator.
496 Or, they could be missing from the
497 source files where they are used
498 (probably because they had been
499 in the old pch, they were missed).
500 Also, these could be headers
501 that make the build faster but
502 aren't added automatically.
506 # Add a space to exclude from
508 line = ' #include <{}>'.format(inc)
515 append('sal/config.h')
517 if module == 'basctl':
518 if 'basslots.hxx' in includes:
519 append('sfx2/msg.hxx')
522 # if 'scslots.hxx' in includes:
523 # append('sfx2/msg.hxx')
526 def sort_by_category(list, root, module, filter_local):
527 """ Move all 'system' headers first.
528 Core files of osl, rtl, sal, next.
529 Everything non-module-specific third.
530 Last, module-specific headers.
538 prefix = '<' + module + '/'
540 if 'sal/config.h' in i:
541 continue # added unconditionally in fixup
542 if is_c_runtime(i, root, module):
546 elif prefix in i or not '/' in i:
548 elif '<sal/' in i or '<vcl/' in i:
550 elif '<osl/' in i or '<rtl/' in i:
551 if module == "sal": # osl and rtl are also part of sal
555 # Headers from another module that is closely tied to the module.
556 elif module == 'sc' and '<formula' in i:
562 out += [ "#if PCH_LEVEL >= 1" ]
565 out += [ "#endif // PCH_LEVEL >= 1" ]
566 out += [ "#if PCH_LEVEL >= 2" ]
568 out += [ "#endif // PCH_LEVEL >= 2" ]
569 out += [ "#if PCH_LEVEL >= 3" ]
571 out += [ "#endif // PCH_LEVEL >= 3" ]
572 out += [ "#if PCH_LEVEL >= 4" ]
574 out += [ "#endif // PCH_LEVEL >= 4" ]
577 def parse_makefile(groups, lines, lineno, lastif, ifstack):
580 ingeneratedobjects = False
583 os_cond_re = re.compile(r'(ifeq|ifneq)\s*\(\$\(OS\)\,(\w*)\)')
586 if line.startswith('if'):
589 # Correction if first line is an if.
590 lineno = parse_makefile(groups, lines, lineno, line, ifstack+1)
594 while lineno + 1 < len(lines):
596 line = lines[lineno].strip()
597 line = line.rstrip('\\').strip()
598 #print('line #{}: {}'.format(lineno, line))
604 ingeneratedobjects = False
605 elif 'add_exception_objects' in line or \
606 'add_cxxobject' in line:
609 #if ifstack and not SILENT:
610 #sys.stderr.write('Sources in a conditional, ignoring for now.\n')
611 elif 'add_generated_exception_objects' in line or \
612 'add_generated_cxxobject' in line:
613 ingeneratedobjects = True
614 elif 'set_generated_cxx_suffix' in line:
615 suffix_re = re.compile('.*set_generated_cxx_suffix,[^,]*,([^)]*).*')
616 match = suffix_re.match(line)
618 suffix = match.group(1)
619 elif line.startswith('if'):
620 lineno = parse_makefile(groups, lines, lineno, line, ifstack+1)
622 elif line.startswith('endif'):
626 elif line.startswith('else'):
628 elif inobjects or ingeneratedobjects:
629 if EXCLUDE_SYSTEM and ifstack:
631 file = line + '.' + suffix
632 if ',' in line or '(' in line or ')' in line or file.startswith('-'):
633 #print('passing: ' + line)
634 pass # $if() probably, or something similar
638 if 'filter' in lastif:
639 # We can't grok filter, yet.
641 match = os_cond_re.match(lastif)
643 # We only support OS conditionals.
645 in_out = match.group(1)
646 osname = match.group(2) if match else ''
647 if (in_out == 'ifneq' and not inelse) or \
648 (in_out == 'ifeq' and inelse):
649 osname = '!' + osname
651 if osname not in groups:
653 if ingeneratedobjects:
654 file = WORKDIR + '/' + file
655 groups[osname].append(file)
659 def process_makefile(root, module, libname):
660 """ Parse a gmake makefile and extract
661 source filenames from it.
664 makefile = 'Library_{}.mk'.format(libname)
665 filename = os.path.join(os.path.join(root, module), makefile)
666 if not os.path.isfile(filename):
667 makefile = 'StaticLibrary_{}.mk'.format(libname)
668 filename = os.path.join(os.path.join(root, module), makefile)
669 if not os.path.isfile(filename):
670 sys.stderr.write('Error: Module {} has no makefile at {}.'.format(module, filename))
672 groups = {'':[], 'ANDROID':[], 'iOS':[], 'WNT':[], 'LINUX':[], 'MACOSX':[]}
674 with open(filename, 'r') as f:
675 lines = f.readlines()
676 groups = parse_makefile(groups, lines, lineno=0, lastif=None, ifstack=0)
680 def is_allowed_if(line, module):
681 """ Check whether the given #if condition
682 is allowed for the given module or whether
683 its block should be ignored.
686 # remove trailing comments
687 line = re.sub(r'(.*) *//.*', r'\1', line)
690 # Our sources always build with LIBO_INTERNAL_ONLY.
691 if line == "#if defined LIBO_INTERNAL_ONLY" or line == "#ifdef LIBO_INTERNAL_ONLY":
693 # We use PCHs only for C++.
694 if line == "#if defined(__cplusplus)" or line == "#if defined __cplusplus":
696 # Debug-specific code, it shouldn't hurt including it unconditionally.
697 if line == "#ifdef DBG_UTIL" or line == "#if OSL_DEBUG_LEVEL > 0":
699 if module == "external/skia":
700 # We always set these.
701 if line == "#ifdef SK_VULKAN" or line == "#if SK_SUPPORT_GPU":
705 def process_source(root, module, filename, maxdepth=0):
706 """ Process a source file to extract
708 For now, skip on compiler directives.
709 maxdepth is used when processing headers
710 which typically have protecting ifndef.
718 with open(filename, 'r') as f:
721 if line.startswith('#if'):
722 if is_allowed_if(line, module):
723 allowed_ifs.append(True)
726 allowed_ifs.append(False)
729 elif line.startswith('#endif'):
731 if allowed_ifs[ ifdepth ]:
735 del allowed_ifs[ ifdepth ]
736 elif line.startswith('#pragma once'):
737 # maxdepth == 1 means we are parsing a header file
738 # and are allowed one #ifdef block (the include guard),
739 # but in the #pragma once case do not allow that
742 elif line.startswith('#include'):
743 if ifdepth - ifsallowed <= maxdepth:
744 line = sanitize(line)
746 line = get_filename(line)
747 if line and len(line):
748 raw_includes.append(line)
750 sys.stderr.write('#include in {} : {}\n'.format(lastif, line))
754 def explode(root, module, includes, tree, filter_local, recurse):
755 incpath = os.path.join(root, 'include')
758 filename = get_filename(inc)
759 if filename in tree or len(filter_local.proc(filename)) == 0:
763 # Module or Local header.
764 filepath = filter_local.find_local_file(inc)
766 #print('trying loc: ' + filepath)
767 incs = process_source(root, module, filepath, maxdepth=1)
768 incs = map(get_filename, incs)
769 incs = process_list(incs, lambda x: filter_ignore(x, module))
770 incs = process_list(incs, filter_local.proc)
771 tree[filename] = incs
773 tree = explode(root, module, incs, tree, filter_local, recurse)
774 #print('{} => {}'.format(filepath, tree[filename]))
781 filepath = os.path.join(incpath, filename)
782 #print('trying pub: ' + filepath)
783 incs = process_source(root, module, filepath, maxdepth=1)
784 incs = map(get_filename, incs)
785 incs = process_list(incs, lambda x: filter_ignore(x, module))
786 incs = process_list(incs, filter_local.proc)
787 tree[filename] = incs
789 tree = explode(root, module, incs, tree, filter_local, recurse)
790 #print('{} => {}'.format(filepath, tree[filename]))
795 # Failed, but remember to avoid searching again.
800 def make_command_line():
802 # Remove command line flags and
803 # use internal flags.
804 for i in range(len(args)-1, 0, -1):
805 if args[i].startswith('--'):
808 args.append('--cutoff=' + str(CUTOFF))
810 args.append('--exclude:system')
812 args.append('--include:system')
814 args.append('--exclude:module')
816 args.append('--include:module')
818 args.append('--exclude:local')
820 args.append('--include:local')
822 return ' '.join(args)
824 def generate_includes(includes):
825 """Generates the include lines of the pch.
828 for osname, group in includes.items():
838 lines.append('#if {}defined({})'.format(not_eq, osname))
844 lines.append('#endif')
848 def generate(includes, libname, filename, module):
850 """/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
852 * This file is part of the LibreOffice project.
854 * This Source Code Form is subject to the terms of the Mozilla Public
855 * License, v. 2.0. If a copy of the MPL was not distributed with this
856 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
860 This file has been autogenerated by update_pch.sh. It is possible to edit it
861 manually (such as when an include file has been moved/renamed/removed). All such
862 manual changes will be rewritten by the next run of update_pch.sh (which presumably
863 also fixes all possible problems, so it's usually better to use it).
868 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
872 with open(filename, 'w') as f:
874 f.write('\n Generated on {} using:\n {}\n'.format(
875 datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
876 make_command_line()))
877 f.write('\n If after updating build fails, use the following command to locate conflicting headers:\n ./bin/update_pch_bisect {} "make {}.build" --find-conflicts\n*/\n'.format(
880 # sal needs this for rand_s()
881 if module == 'sal' and libname == 'sal':
884 #if !defined _CRT_RAND_S
896 # Some libraries pull windows headers that aren't self contained.
897 if (module == 'connectivity' and libname == 'ado') or \
898 (module == 'xmlsecurity' and libname == 'xsec_xmlsec'):
900 // Cleanup windows header macro pollution.
901 #if defined(_WIN32) && defined(WINAPI)
910 def remove_from_tree(filename, tree):
911 # Remove this file, if top-level.
912 incs = tree.pop(filename, [])
914 tree = remove_from_tree(i, tree)
916 # Also remove if included from another.
917 for (k, v) in tree.items():
923 def tree_to_list(includes, filename, tree):
924 if filename in includes:
926 includes.append(filename)
927 #incs = tree.pop(filename, [])
928 incs = tree[filename] if filename in tree else []
930 tree_to_list(includes, i, tree)
934 def promote(includes):
935 """ Common library headers are heavily
936 referenced, even if they are included
938 Here we separate them to promote
939 their inclusion in the final pch.
943 if inc.startswith('boost') or \
944 inc.startswith('sal') or \
945 inc.startswith('osl') or \
946 inc.startswith('rtl'):
950 def make_pch_filename(root, module, libname):
951 """ PCH files are stored here:
952 <root>/<module>/inc/pch/precompiled_<libname>.hxx
955 path = os.path.join(root, module)
956 path = os.path.join(path, 'inc')
957 path = os.path.join(path, 'pch')
958 path = os.path.join(path, 'precompiled_' + libname + '.hxx')
964 global EXCLUDE_MODULE
966 global EXCLUDE_SYSTEM
970 if os.getenv('WORKDIR'):
971 WORKDIR = os.getenv('WORKDIR')
975 libname = sys.argv[2]
976 header = make_pch_filename(root, module, libname)
978 if not os.path.exists(os.path.join(root, module)):
979 raise Exception('Error: module [{}] not found.'.format(module))
981 key = '{}.{}'.format(module, libname)
983 # Load the module-specific defaults.
984 CUTOFF = DEFAULTS[key][0]
985 EXCLUDE_SYSTEM = DEFAULTS[key][1]
986 EXCLUDE_MODULE = DEFAULTS[key][2]
987 EXCLUDE_LOCAL = DEFAULTS[key][3]
990 for x in range(3, len(sys.argv)):
992 if i.startswith('--cutoff='):
993 CUTOFF = int(i.split('=')[1])
994 elif i.startswith('--exclude:'):
995 cat = i.split(':')[1]
997 EXCLUDE_MODULE = True
1000 elif cat == 'system':
1001 EXCLUDE_SYSTEM = True
1002 elif i.startswith('--include:'):
1003 cat = i.split(':')[1]
1005 EXCLUDE_MODULE = False
1006 elif cat == 'local':
1007 EXCLUDE_LOCAL = False
1008 elif cat == 'system':
1009 EXCLUDE_SYSTEM = False
1010 elif i == '--silent':
1012 elif i == '--force':
1015 sys.stderr.write('Unknown option [{}].'.format(i))
1018 filter_local = Filter_Local(root, module, \
1019 not EXCLUDE_MODULE, \
1023 groups = process_makefile(root, module, libname)
1026 for osname, group in groups.items():
1031 for filename in group:
1032 includes += process_source(root, module, filename)
1034 # Save unique top-level includes.
1035 unique = set(includes)
1036 promoted = promote(unique)
1039 includes = remove_rare(includes)
1040 includes = process_list(includes, lambda x: filter_ignore(x, module))
1041 includes = process_list(includes, filter_local.proc)
1043 # Remove the already included ones.
1044 for inc in includes:
1047 # Explode the excluded ones.
1048 tree = {i:[] for i in includes}
1049 tree = explode(root, module, unique, tree, filter_local, not EXCLUDE_MODULE)
1051 # Remove the already included ones from the tree.
1052 for inc in includes:
1053 filename = get_filename(inc)
1054 tree = remove_from_tree(filename, tree)
1057 for (k, v) in tree.items():
1058 extra += tree_to_list([], k, tree)
1060 promoted += promote(extra)
1061 promoted = process_list(promoted, lambda x: filter_ignore(x, module))
1062 promoted = process_list(promoted, filter_local.proc)
1063 promoted = set(promoted)
1064 # If a promoted header includes others, remove the rest.
1065 for (k, v) in tree.items():
1069 includes += [x for x in promoted]
1071 extra = remove_rare(extra)
1072 extra = process_list(extra, lambda x: filter_ignore(x, module))
1073 extra = process_list(extra, filter_local.proc)
1076 includes = [x for x in set(includes)]
1077 fixes = fixup(includes, module)
1078 fixes = map(lambda x: '#include <' + x + '>', fixes)
1080 includes = map(lambda x: '#include <' + x + '>', includes)
1081 sorted = sort_by_category(includes, root, module, filter_local)
1082 includes = list(fixes) + sorted
1089 groups[osname] = includes
1093 # Open the old pch and compare its contents
1094 # with new includes.
1095 # Clobber only if they are different.
1096 with open(header, 'r') as f:
1097 old_pch_lines = [x.strip() for x in f.readlines()]
1098 new_lines = generate_includes(groups)
1099 # Find the first include in the old pch.
1101 for i in range(len(old_pch_lines)):
1102 if old_pch_lines[i].startswith('#include') or old_pch_lines[i].startswith('#if PCH_LEVEL'):
1105 # Clobber if there is a mismatch.
1106 if force_update or start < 0 or (len(old_pch_lines) - start < len(new_lines)):
1107 generate(new_lines, libname, header, module)
1110 for i in range(len(new_lines)):
1111 if new_lines[i] != old_pch_lines[start + i]:
1112 generate(new_lines, libname, header, module)
1115 # Identical, but see if new pch removed anything.
1116 for i in range(start + len(new_lines), len(old_pch_lines)):
1117 if '#include' in old_pch_lines[i]:
1118 generate(new_lines, libname, header, module)
1122 # Use exit code 2 to distinguish it from exit code 1 used e.g. when an exception occurs.
1125 if __name__ == '__main__':
1126 """ Process all the includes in a Module
1127 to make into a PCH file.
1128 Run without arguments for unittests,
1132 if len(sys.argv) >= 3:
1136 print('Usage: {} <Module name> <Library name> [options]'.format(sys.argv[0]))
1137 print(' Always run from the root of LO repository.\n')
1139 print(' --cutoff=<count> - Threshold to excluding headers.')
1140 print(' --exclude:<category> - Exclude category-specific headers.')
1141 print(' --include:<category> - Include category-specific headers.')
1142 print(' --force - Force updating the pch even when nothing changes.')
1143 print(' Categories:')
1144 print(' module - Headers in /inc directory of a module.')
1145 print(' local - Headers local to a source file.')
1146 print(' system - Platform-specific headers.')
1147 print(' --silent - print only errors.')
1148 print('\nRunning unit-tests...')
1151 class TestMethods(unittest.TestCase):
1153 def test_sanitize(self):
1154 self.assertEqual(sanitize('#include "blah/file.cxx"'),
1155 '#include <blah/file.cxx>')
1156 self.assertEqual(sanitize(' #include\t"blah/file.cxx" '),
1157 '#include <blah/file.cxx>')
1158 self.assertEqual(sanitize(' '),
1161 def test_filter_ignore(self):
1162 self.assertEqual(filter_ignore('blah/file.cxx', 'mod'),
1164 self.assertEqual(filter_ignore('vector', 'mod'),
1166 self.assertEqual(filter_ignore('file.cxx', 'mod'),
1169 def test_remove_rare(self):
1170 self.assertEqual(remove_rare([]),
1173 class TestMakefileParser(unittest.TestCase):
1176 global EXCLUDE_SYSTEM
1177 EXCLUDE_SYSTEM = False
1179 def test_parse_singleline_eval(self):
1180 source = "$(eval $(call gb_Library_Library,sal))"
1181 lines = source.split('\n')
1183 groups = parse_makefile(groups, lines, 0, None, 0)
1184 self.assertEqual(len(groups), 1)
1185 self.assertEqual(len(groups['']), 0)
1187 def test_parse_multiline_eval(self):
1188 source = """$(eval $(call gb_Library_set_include,sal,\\
1190 -I$(SRCDIR)/sal/inc \\
1193 lines = source.split('\n')
1195 groups = parse_makefile(groups, lines, 0, None, 0)
1196 self.assertEqual(len(groups), 1)
1197 self.assertEqual(len(groups['']), 0)
1199 def test_parse_multiline_eval_with_if(self):
1200 source = """$(eval $(call gb_Library_add_defs,sal,\\
1201 $(if $(filter $(OS),iOS), \\
1202 -DNO_CHILD_PROCESSES \\
1206 lines = source.split('\n')
1208 groups = parse_makefile(groups, lines, 0, None, 0)
1209 self.assertEqual(len(groups), 1)
1210 self.assertEqual(len(groups['']), 0)
1212 def test_parse_multiline_add_with_if(self):
1213 source = """$(eval $(call gb_Library_add_exception_objects,sal,\\
1215 $(if $(filter DESKTOP,$(BUILD_TYPE)), sal/osl/unx/salinit) \\
1218 lines = source.split('\n')
1220 groups = parse_makefile(groups, lines, 0, None, 0)
1221 self.assertEqual(len(groups), 1)
1222 self.assertEqual(len(groups['']), 1)
1223 self.assertEqual(groups[''][0], 'sal/osl/unx/time.cxx')
1225 def test_parse_if_else(self):
1226 source = """ifeq ($(OS),MACOSX)
1227 $(eval $(call gb_Library_add_exception_objects,sal,\\
1231 $(eval $(call gb_Library_add_exception_objects,sal,\\
1232 sal/osl/unx/uunxapi \\
1236 lines = source.split('\n')
1238 groups = parse_makefile(groups, lines, 0, None, 0)
1239 self.assertEqual(len(groups), 3)
1240 self.assertEqual(len(groups['']), 0)
1241 self.assertEqual(len(groups['MACOSX']), 1)
1242 self.assertEqual(len(groups['!MACOSX']), 1)
1243 self.assertEqual(groups['MACOSX'][0], 'sal/osl/mac/mac.cxx')
1244 self.assertEqual(groups['!MACOSX'][0], 'sal/osl/unx/uunxapi.cxx')
1246 def test_parse_nested_if(self):
1247 source = """ifeq ($(OS),MACOSX)
1248 $(eval $(call gb_Library_add_exception_objects,sal,\\
1252 $(eval $(call gb_Library_add_exception_objects,sal,\\
1253 sal/osl/unx/uunxapi \\
1257 $(eval $(call gb_Library_add_exception_objects,sal,\\
1258 sal/textenc/context \\
1263 lines = source.split('\n')
1265 groups = parse_makefile(groups, lines, 0, None, 0)
1266 self.assertEqual(len(groups), 4)
1267 self.assertEqual(len(groups['']), 0)
1268 self.assertEqual(len(groups['MACOSX']), 1)
1269 self.assertEqual(len(groups['!MACOSX']), 1)
1270 self.assertEqual(len(groups['LINUX']), 1)
1271 self.assertEqual(groups['MACOSX'][0], 'sal/osl/mac/mac.cxx')
1272 self.assertEqual(groups['!MACOSX'][0], 'sal/osl/unx/uunxapi.cxx')
1273 self.assertEqual(groups['LINUX'][0], 'sal/textenc/context.cxx')
1275 def test_parse_exclude_system(self):
1276 source = """ifeq ($(OS),MACOSX)
1277 $(eval $(call gb_Library_add_exception_objects,sal,\\
1281 $(eval $(call gb_Library_add_exception_objects,sal,\\
1282 sal/osl/unx/uunxapi \\
1286 $(eval $(call gb_Library_add_exception_objects,sal,\\
1287 sal/textenc/context \\
1292 global EXCLUDE_SYSTEM
1293 EXCLUDE_SYSTEM = True
1295 lines = source.split('\n')
1297 groups = parse_makefile(groups, lines, 0, None, 0)
1298 self.assertEqual(len(groups), 1)
1299 self.assertEqual(len(groups['']), 0)
1301 def test_parse_filter(self):
1302 source = """ifneq ($(filter $(OS),MACOSX iOS),)
1303 $(eval $(call gb_Library_add_exception_objects,sal,\\
1304 sal/osl/unx/osxlocale \\
1308 # Filter is still unsupported.
1309 lines = source.split('\n')
1311 groups = parse_makefile(groups, lines, 0, None, 0)
1312 self.assertEqual(len(groups), 1)
1313 self.assertEqual(len(groups['']), 0)
1317 # vim: set et sw=4 ts=4 expandtab: