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).
32 EXCLUDE_MODULE = False
38 # System includes: oox, sal, sd, svl, vcl
44 # module.library : (min, system, module, local), best time
45 'accessibility.acc' : ( 4, EXCLUDE, INCLUDE, INCLUDE), # 7.8
46 'basctl.basctl' : ( 3, EXCLUDE, INCLUDE, EXCLUDE), # 11.9
47 'basegfx.basegfx' : ( 3, EXCLUDE, EXCLUDE, INCLUDE), # 3.8
48 'basic.sb' : ( 2, EXCLUDE, EXCLUDE, INCLUDE), # 10.7
49 'chart2.chartcontroller' : ( 6, EXCLUDE, INCLUDE, INCLUDE), # 18.4
50 'chart2.chartcore' : ( 3, EXCLUDE, EXCLUDE, INCLUDE), # 22.5
51 'comphelper.comphelper' : ( 4, EXCLUDE, INCLUDE, INCLUDE), # 7.6
52 'configmgr.configmgr' : ( 6, EXCLUDE, INCLUDE, INCLUDE), # 6.0
53 'connectivity.ado' : ( 2, EXCLUDE, EXCLUDE, EXCLUDE), # 6.4
54 'connectivity.calc' : ( 2, EXCLUDE, EXCLUDE, EXCLUDE), # 4.6
55 'connectivity.dbase' : ( 2, EXCLUDE, INCLUDE, INCLUDE), # 5.2
56 'connectivity.dbpool2' : ( 5, EXCLUDE, INCLUDE, EXCLUDE), # 3.0
57 'connectivity.dbtools' : ( 2, EXCLUDE, EXCLUDE, INCLUDE), # 0.8
58 'connectivity.file' : ( 2, EXCLUDE, INCLUDE, EXCLUDE), # 5.1
59 'connectivity.firebird_sdbc' : ( 2, EXCLUDE, EXCLUDE, EXCLUDE), # 5.1
60 'connectivity.flat' : ( 2, EXCLUDE, INCLUDE, INCLUDE), # 4.6
61 'connectivity.mysql' : ( 4, EXCLUDE, INCLUDE, EXCLUDE), # 3.4
62 'connectivity.odbc' : ( 2, EXCLUDE, EXCLUDE, INCLUDE), # 5.0
63 'connectivity.postgresql-sdbc-impl' : ( 3, EXCLUDE, EXCLUDE, EXCLUDE), # 6.7
64 'cppcanvas.cppcanvas' : (11, EXCLUDE, INCLUDE, INCLUDE), # 4.8
65 'cppuhelper.cppuhelper' : ( 3, EXCLUDE, EXCLUDE, EXCLUDE), # 4.6
66 'cui.cui' : ( 8, EXCLUDE, INCLUDE, EXCLUDE), # 19.7
67 'dbaccess.dba' : ( 6, EXCLUDE, INCLUDE, INCLUDE), # 13.8
68 'dbaccess.dbaxml' : ( 2, EXCLUDE, EXCLUDE, EXCLUDE), # 6.5
69 'dbaccess.dbu' : (12, EXCLUDE, EXCLUDE, EXCLUDE), # 23.6
70 'dbaccess.sdbt' : ( 1, EXCLUDE, INCLUDE, EXCLUDE), # 2.9
71 'desktop.deployment' : ( 3, EXCLUDE, EXCLUDE, EXCLUDE), # 6.1
72 'desktop.deploymentgui' : ( 3, EXCLUDE, EXCLUDE, EXCLUDE), # 5.7
73 'desktop.deploymentmisc' : ( 3, EXCLUDE, EXCLUDE, EXCLUDE), # 3.4
74 'desktop.sofficeapp' : ( 6, EXCLUDE, INCLUDE, INCLUDE), # 6.5
75 'drawinglayer.drawinglayer' : ( 4, EXCLUDE, EXCLUDE, EXCLUDE), # 7.4
76 'editeng.editeng' : ( 5, EXCLUDE, INCLUDE, EXCLUDE), # 13.0
77 'forms.frm' : ( 2, EXCLUDE, EXCLUDE, EXCLUDE), # 14.2
78 'framework.fwe' : (10, EXCLUDE, INCLUDE, EXCLUDE), # 5.5
79 'framework.fwi' : ( 9, EXCLUDE, INCLUDE, EXCLUDE), # 3.4
80 'framework.fwk' : ( 7, EXCLUDE, INCLUDE, INCLUDE), # 14.8
81 'framework.fwl' : ( 5, EXCLUDE, INCLUDE, INCLUDE), # 5.1
82 'hwpfilter.hwp' : ( 3, EXCLUDE, INCLUDE, INCLUDE), # 6.0
83 'lotuswordpro.lwpft' : ( 2, EXCLUDE, EXCLUDE, EXCLUDE), # 11.6
84 'oox.oox' : ( 6, EXCLUDE, EXCLUDE, INCLUDE), # 28.2
85 'package.package2' : ( 3, EXCLUDE, INCLUDE, INCLUDE), # 4.5
86 'package.xstor' : ( 2, EXCLUDE, INCLUDE, EXCLUDE), # 3.8
87 'reportdesign.rpt' : ( 9, EXCLUDE, INCLUDE, INCLUDE), # 9.4
88 'reportdesign.rptui' : ( 4, EXCLUDE, INCLUDE, INCLUDE), # 13.1
89 'reportdesign.rptxml' : ( 2, EXCLUDE, EXCLUDE, INCLUDE), # 7.6
90 'sal.sal' : ( 2, EXCLUDE, EXCLUDE, INCLUDE), # 4.2
91 'sc.sc' : (12, EXCLUDE, INCLUDE, INCLUDE), # 92.6
92 'sc.scfilt' : ( 4, EXCLUDE, EXCLUDE, INCLUDE), # 39.9
93 'sc.scui' : ( 1, EXCLUDE, EXCLUDE, INCLUDE), # 15.0
94 'sc.vbaobj' : ( 1, EXCLUDE, EXCLUDE, INCLUDE), # 17.3
95 'sd.sd' : ( 4, EXCLUDE, EXCLUDE, INCLUDE), # 47.4
96 'sd.sdui' : ( 4, EXCLUDE, INCLUDE, INCLUDE), # 9.4
97 'sdext.PresentationMinimizer' : ( 2, EXCLUDE, INCLUDE, INCLUDE), # 4.1
98 'sdext.PresenterScreen' : ( 2, EXCLUDE, INCLUDE, EXCLUDE), # 7.1
99 'sfx2.sfx' : ( 3, EXCLUDE, EXCLUDE, EXCLUDE), # 27.4
100 'slideshow.slideshow' : ( 4, EXCLUDE, INCLUDE, EXCLUDE), # 10.8
101 'sot.sot' : ( 5, EXCLUDE, EXCLUDE, INCLUDE), # 3.1
102 'starmath.sm' : ( 5, EXCLUDE, EXCLUDE, INCLUDE), # 10.9
103 'svgio.svgio' : ( 8, EXCLUDE, EXCLUDE, INCLUDE), # 4.3
104 'emfio.emfio' : ( 8, EXCLUDE, EXCLUDE, INCLUDE), # 4.3
105 'svl.svl' : ( 6, EXCLUDE, EXCLUDE, EXCLUDE), # 7.6
106 'svtools.svt' : ( 4, EXCLUDE, INCLUDE, EXCLUDE), # 17.6
107 'svx.svx' : ( 3, EXCLUDE, EXCLUDE, INCLUDE), # 20.7
108 'svx.svxcore' : ( 7, EXCLUDE, INCLUDE, EXCLUDE), # 37.0
109 'sw.msword' : ( 4, EXCLUDE, INCLUDE, INCLUDE), # 22.4
110 'sw.sw' : ( 7, EXCLUDE, EXCLUDE, INCLUDE), # 129.6
111 'sw.swui' : ( 3, EXCLUDE, INCLUDE, INCLUDE), # 26.1
112 'sw.vbaswobj' : ( 4, EXCLUDE, INCLUDE, INCLUDE), # 13.1
113 'tools.tl' : ( 5, EXCLUDE, EXCLUDE, EXCLUDE), # 4.2
114 'unotools.utl' : ( 3, EXCLUDE, EXCLUDE, INCLUDE), # 7.0
115 'unoxml.unoxml' : ( 1, EXCLUDE, EXCLUDE, EXCLUDE), # 4.6
116 'uui.uui' : ( 4, EXCLUDE, EXCLUDE, EXCLUDE), # 4.9
117 'vbahelper.msforms' : ( 3, EXCLUDE, INCLUDE, INCLUDE), # 5.2
118 'vbahelper.vbahelper' : ( 3, EXCLUDE, EXCLUDE, INCLUDE), # 7.0
119 'vcl.vcl' : ( 6, EXCLUDE, INCLUDE, INCLUDE), # 35.7
120 'writerfilter.writerfilter' : ( 5, EXCLUDE, EXCLUDE, EXCLUDE), # 19.7/27.3
121 'xmloff.xo' : ( 7, EXCLUDE, INCLUDE, INCLUDE), # 22.1
122 'xmloff.xof' : ( 1, EXCLUDE, EXCLUDE, INCLUDE), # 4.4
123 'xmlscript.xmlscript' : ( 4, EXCLUDE, EXCLUDE, INCLUDE), # 3.6
124 'xmlsecurity.xmlsecurity' : ( 6, EXCLUDE, INCLUDE, INCLUDE), # 5.1
125 'xmlsecurity.xsec_xmlsec' : ( 2, EXCLUDE, INCLUDE, INCLUDE), # 4.4
126 'xmlsecurity.xsec_gpg' : ( 2, EXCLUDE, INCLUDE, INCLUDE), # ?
129 def remove_rare(raw, min_use=-1):
130 """ Remove headers not commonly included.
131 The minimum threshold is min_use.
133 # The minimum number of times a header
134 # must be included to be in the PCH.
135 min_use = min_use if min_use >= 0 else CUTOFF
138 if not raw or not len(raw):
144 for x in range(1, len(inc)):
160 def process_list(list, callable):
161 """ Given a list and callable
162 we pass each entry through
163 the callable and only add to
164 the output if not blank.
169 if line and len(line):
173 def find_files(path, recurse=True):
175 for root, dir, files in os.walk(path):
176 list += map(lambda x: os.path.join(root, x), files)
179 def get_filename(line):
180 """ Strips the line from the
181 '#include' and angled brakets
182 and return the filename only.
184 if not len(line) or line[0] != '#':
186 return re.sub(r'(.*#include\s*)<(.*)>(.*)', r'\2', line)
188 def is_c_runtime(inc, root, module):
189 """ Heuristic-based detection of C/C++
191 They are all-lowercase, with .h or
192 no extension, filename only.
193 Try to check that they are not LO headers.
195 inc = get_filename(inc)
197 if inc.endswith('.hxx') or inc.endswith('.hpp'):
200 if inc.endswith('.h') and inc.startswith( 'config_' ):
206 if c == '.' and not inc.endswith('.h'):
211 if os.path.isfile( os.path.join(root, module, 'inc', inc)):
217 """ There are two forms of includes,
218 those with <> and "".
219 Technically, the difference is that
220 the compiler can use an internal
221 representation for an angled include,
222 such that it doesn't have to be a file.
223 For our purposes, there is no difference.
224 Here, we convert everything to angled.
226 if not raw or not len(raw):
231 return re.sub(r'(.*#include\s*)\"(.*)\"(.*)', r'#include <\2>', raw)
233 class Filter_Local(object):
234 """ Filter headers local to a module.
235 allow_public: allows include/module/file.hxx
236 #include <module/file.hxx>
237 allow_module: allows module/inc/file.hxx
239 allow_locals: allows module/source/file.hxx and
240 module/source/inc/file.hxx
243 def __init__(self, root, module, allow_public=True, allow_module=True, allow_locals=True):
246 self.allow_public = allow_public
247 self.allow_module = allow_module
248 self.allow_locals = allow_locals
249 self.public_prefix = '<' + self.module + '/'
251 all = find_files(os.path.join(root, module))
252 self.module_includes = []
254 mod_prefix = module + '/inc/'
257 self.module_includes.append(i)
259 self.locals.append(i)
261 def is_public(self, line):
262 return self.public_prefix in line
264 def is_module(self, line):
265 """ Returns True if in module/inc/... """
266 filename = get_filename(line)
267 for i in self.module_includes:
268 if i.endswith(filename):
272 def is_local(self, line):
273 """ Returns True if in module/source/... """
274 filename = get_filename(line)
275 for i in self.locals:
276 if i.endswith(filename):
280 def is_external(self, line):
281 return is_c_runtime(line, self.root, self.module) and \
282 not self.is_public(line) and \
283 not self.is_module(line) and \
284 not self.is_local(line)
286 def find_local_file(self, line):
287 """ Finds the header file in the module dir,
288 but doesn't validate.
290 filename = get_filename(line)
291 for i in self.locals:
292 if i.endswith(filename):
294 for i in self.module_includes:
295 if i.endswith(filename):
299 def proc(self, line):
300 assert line and len(line)
301 assert line[0] != '<' and line[0] != '#'
303 filename = get_filename(line)
305 # Local with relative path.
306 if filename.startswith('..'):
307 # Exclude for now as we don't have cxx path.
310 # Locals are included first (by the compiler).
311 if self.is_local(filename):
312 return line if self.allow_locals and '/inc/' in filename else ''
314 # Module headers are next.
315 if self.is_module(filename):
316 return line if self.allow_module else ''
318 # Public headers are last.
319 if self.is_public(line):
320 return line if self.allow_public else ''
322 # Leave out potentially unrelated files local
323 # to some other module we can't include directly.
324 if '/' not in filename and not self.is_external(filename):
330 def filter_ignore(line, module):
331 """ Filters includes from known
333 Expects sanitized input.
335 assert line and len(line)
337 # Always include files without extension.
341 # Extract filenames for ease of comparison.
342 line = get_filename(line)
344 # Filter out all files that are not normal headers.
345 if not line.endswith('.h') and \
346 not line.endswith('.hxx') and \
347 not line.endswith('.hpp') and \
348 not line.endswith('.hdl'):
352 'LibreOfficeKit/LibreOfficeKitEnums.h', # Needs special directives
353 'LibreOfficeKit/LibreOfficeKitTypes.h', # Needs special directives
354 'jerror.h', # c++ unfriendly
355 'jpeglib.h', # c++ unfriendly
356 'boost/spirit/include/classic_core.hpp', # depends on BOOST_SPIRIT_DEBUG
357 'svtools/editimplementation.hxx' # no direct include
360 if module == 'accessibility':
362 # STR_SVT_ACC_LISTENTRY_SELCTED_STATE redefined from svtools.hrc
363 'accessibility/extended/textwindowaccessibility.hxx',
365 if module == 'basic':
367 'basic/vbahelper.hxx',
369 if module == 'connectivity':
371 'com/sun/star/beans/PropertyAttribute.hpp', # OPTIONAL defined via objbase.h
372 'com/sun/star/sdbcx/Privilege.hpp', # DELETE defined via objbase.h
376 'progress.hxx', # special directives
377 'scslots.hxx', # special directives
381 'sdgslots.hxx', # special directives
382 'sdslots.hxx', # special directives
386 'sfx2/recentdocsview.hxx', # Redefines ApplicationType defined in objidl.h
387 'sfx2/sidebar/Sidebar.hxx',
388 'sfx2/sidebar/UnoSidebar.hxx',
389 'sfxslots.hxx', # externally defined types
393 'sysformats.hxx', # Windows headers
397 'accmgr.hxx', # redefines ImplAccelList
400 'opengl/gdiimpl.hxx',
402 'openglgdiimpl', # ReplaceTextA
404 'salinst.hxx', # GetDefaultPrinterA
405 'salprn.hxx', # SetPrinterDataA
407 'vcl/oldprintadaptor.hxx',
408 'vcl/opengl/OpenGLContext.hxx',
409 'vcl/opengl/OpenGLHelper.hxx', # Conflicts with X header on *ix
411 'vcl/prntypes.hxx', # redefines Orientation from filter/jpeg/Exif.hxx
414 if module == 'xmloff':
416 'SchXMLExport.hxx', # SchXMLAutoStylePoolP.hxx not found
417 'SchXMLImport.hxx', # enums redefined in draw\sdxmlimp_impl.hxx
418 'XMLEventImportHelper.hxx', # NameMap redefined in XMLEventExport.hxx
419 'xmloff/XMLEventExport.hxx', # enums redefined
421 if module == 'xmlsecurity':
424 'xmlsecurity/xmlsec-wrapper.h',
426 if module == 'external/pdfium':
428 'third_party/freetype/include/pstables.h',
430 if module == 'external/clucene':
437 'CLucene/LuceneThreads.h',
438 'CLucene/config/_threads.h'
441 for i in ignore_list:
442 if line.startswith(i):
444 if i[0] == '*' and line.endswith(i[1:]):
446 if i[-1] == '*' and line.startswith(i[:-1]):
451 def fixup(includes, module):
452 """ Here we add any headers
453 necessary in the pch.
454 These could be known to be very
455 common but for technical reasons
456 left out of the pch by this generator.
457 Or, they could be missing from the
458 source files where they are used
459 (probably because they had been
460 in the old pch, they were missed).
461 Also, these could be headers
462 that make the build faster but
463 aren't added automatically.
467 # Add a space to exclude from
469 line = ' #include <{}>'.format(inc)
476 if module == 'basctl':
477 if 'basslots.hxx' in includes:
478 append('sfx2/msg.hxx')
481 # if 'scslots.hxx' in includes:
482 # append('sfx2/msg.hxx')
485 def sort_by_category(list, root, module, filter_local):
486 """ Move all 'system' headers first.
487 Core files of osl, rtl, sal, next.
488 Everything non-module-specific third.
489 Last, module-specific headers.
497 prefix = '<' + module + '/'
499 if is_c_runtime(i, root, module):
503 elif prefix in i or not '/' in i:
505 elif '<sal/' in i or '<vcl/' in i:
507 elif '<osl/' in i or '<rtl/' in i:
508 if module == "sal": # osl and rtl are also part of sal
512 # Headers from another module that is closely tied to the module.
513 elif module == 'sc' and '<formula' in i:
519 out += [ "#if PCH_LEVEL >= 1" ]
522 out += [ "#endif // PCH_LEVEL >= 1" ]
523 out += [ "#if PCH_LEVEL >= 2" ]
525 out += [ "#endif // PCH_LEVEL >= 2" ]
526 out += [ "#if PCH_LEVEL >= 3" ]
528 out += [ "#endif // PCH_LEVEL >= 3" ]
529 out += [ "#if PCH_LEVEL >= 4" ]
531 out += [ "#endif // PCH_LEVEL >= 4" ]
534 def parse_makefile(groups, lines, lineno, lastif, ifstack):
537 ingeneratedobjects = False
540 os_cond_re = re.compile('(ifeq|ifneq)\s*\(\$\(OS\)\,(\w*)\)')
543 if line.startswith('if'):
546 # Correction if first line is an if.
547 lineno = parse_makefile(groups, lines, lineno, line, ifstack+1)
551 while lineno + 1 < len(lines):
553 line = lines[lineno].strip()
554 line = line.rstrip('\\').strip()
555 #print('line #{}: {}'.format(lineno, line))
561 ingeneratedobjects = False
562 elif 'add_exception_objects' in line or \
563 'add_cxxobject' in line:
566 #if ifstack and not SILENT:
567 #sys.stderr.write('Sources in a conditional, ignoring for now.\n')
568 elif 'add_generated_exception_objects' in line or \
569 'add_generated_cxxobject' in line:
570 ingeneratedobjects = True
571 elif 'set_generated_cxx_suffix' in line:
572 suffix_re = re.compile('.*set_generated_cxx_suffix,[^,]*,([^)]*).*')
573 match = suffix_re.match(line)
575 suffix = match.group(1)
576 elif line.startswith('if'):
577 lineno = parse_makefile(groups, lines, lineno, line, ifstack+1)
579 elif line.startswith('endif'):
583 elif line.startswith('else'):
585 elif inobjects or ingeneratedobjects:
586 if EXCLUDE_SYSTEM and ifstack:
588 file = line + '.' + suffix
589 if ',' in line or '(' in line or ')' in line:
590 #print('passing: ' + line)
591 pass # $if() probably, or something similar
595 if 'filter' in lastif:
596 # We can't grok filter, yet.
598 match = os_cond_re.match(lastif)
600 # We only support OS conditionals.
602 in_out = match.group(1)
603 osname = match.group(2) if match else ''
604 if (in_out == 'ifneq' and not inelse) or \
605 (in_out == 'ifeq' and inelse):
606 osname = '!' + osname
608 if osname not in groups:
610 if ingeneratedobjects:
611 file = WORKDIR + '/' + file
612 groups[osname].append(file)
616 def process_makefile(root, module, libname):
617 """ Parse a gmake makefile and extract
618 source filenames from it.
621 makefile = 'Library_{}.mk'.format(libname)
622 filename = os.path.join(os.path.join(root, module), makefile)
623 if not os.path.isfile(filename):
624 makefile = 'StaticLibrary_{}.mk'.format(libname)
625 filename = os.path.join(os.path.join(root, module), makefile)
626 if not os.path.isfile(filename):
627 sys.stderr.write('Error: Module {} has no makefile at {}.'.format(module, filename))
629 groups = {'':[], 'ANDROID':[], 'iOS':[], 'WNT':[], 'LINUX':[], 'MACOSX':[]}
631 with open(filename, 'r') as f:
632 lines = f.readlines()
633 groups = parse_makefile(groups, lines, lineno=0, lastif=None, ifstack=0)
637 def is_allowed_if(line, module):
638 """ Check whether the given #if condition
639 is allowed for the given module or whether
640 its block should be ignored.
643 # remove trailing comments
644 line = re.sub(r'(.*) *//.*', r'\1', line)
647 # Our sources always build with LIBO_INTERNAL_ONLY.
648 if line == "#if defined LIBO_INTERNAL_ONLY" or line == "#ifdef LIBO_INTERNAL_ONLY":
650 if module == "external/skia":
651 # We always set these.
652 if line == "#ifdef SK_VULKAN" or line == "#if SK_SUPPORT_GPU":
656 def process_source(root, module, filename, maxdepth=0):
657 """ Process a source file to extract
659 For now, skip on compiler directives.
660 maxdepth is used when processing headers
661 which typically have protecting ifndef.
669 with open(filename, 'r') as f:
672 if line.startswith('#if'):
673 if is_allowed_if(line, module):
674 allowed_ifs.append(True)
677 allowed_ifs.append(False)
680 elif line.startswith('#endif'):
682 if allowed_ifs[ ifdepth ]:
686 del allowed_ifs[ ifdepth ]
687 elif line.startswith('#include'):
688 if ifdepth - ifsallowed <= maxdepth:
689 line = sanitize(line)
691 line = get_filename(line)
692 if line and len(line):
693 raw_includes.append(line)
695 sys.stderr.write('#include in {} : {}\n'.format(lastif, line))
699 def explode(root, module, includes, tree, filter_local, recurse):
700 incpath = os.path.join(root, 'include')
703 filename = get_filename(inc)
704 if filename in tree or len(filter_local.proc(filename)) == 0:
708 # Module or Local header.
709 filepath = filter_local.find_local_file(inc)
711 #print('trying loc: ' + filepath)
712 incs = process_source(root, module, filepath, maxdepth=1)
713 incs = map(get_filename, incs)
714 incs = process_list(incs, lambda x: filter_ignore(x, module))
715 incs = process_list(incs, filter_local.proc)
716 tree[filename] = incs
718 tree = explode(root, module, incs, tree, filter_local, recurse)
719 #print('{} => {}'.format(filepath, tree[filename]))
726 filepath = os.path.join(incpath, filename)
727 #print('trying pub: ' + filepath)
728 incs = process_source(root, module, filepath, maxdepth=1)
729 incs = map(get_filename, incs)
730 incs = process_list(incs, lambda x: filter_ignore(x, module))
731 incs = process_list(incs, filter_local.proc)
732 tree[filename] = incs
734 tree = explode(root, module, incs, tree, filter_local, recurse)
735 #print('{} => {}'.format(filepath, tree[filename]))
740 # Failed, but remember to avoid searching again.
745 def make_command_line():
747 # Remove command line flags and
748 # use internal flags.
749 for i in range(len(args)-1, 0, -1):
750 if args[i].startswith('--'):
753 args.append('--cutoff=' + str(CUTOFF))
755 args.append('--exclude:system')
757 args.append('--include:system')
759 args.append('--exclude:module')
761 args.append('--include:module')
763 args.append('--exclude:local')
765 args.append('--include:local')
767 return ' '.join(args)
769 def generate_includes(includes):
770 """Generates the include lines of the pch.
773 for osname, group in includes.items():
783 lines.append('#if {}defined({})'.format(not_eq, osname))
789 lines.append('#endif')
793 def generate(includes, libname, filename, module):
795 """/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
797 * This file is part of the LibreOffice project.
799 * This Source Code Form is subject to the terms of the Mozilla Public
800 * License, v. 2.0. If a copy of the MPL was not distributed with this
801 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
805 This file has been autogenerated by update_pch.sh. It is possible to edit it
806 manually (such as when an include file has been moved/renamed/removed). All such
807 manual changes will be rewritten by the next run of update_pch.sh (which presumably
808 also fixes all possible problems, so it's usually better to use it).
813 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
817 with open(filename, 'w') as f:
819 f.write('\n Generated on {} using:\n {}\n'.format(
820 datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
821 make_command_line()))
822 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(
825 # sal needs this for rand_s()
826 if module == 'sal' and libname == 'sal':
829 #if !defined _CRT_RAND_S
841 # Some libraries pull windows headers that aren't self contained.
842 if (module == 'connectivity' and libname == 'ado') or \
843 (module == 'xmlsecurity' and libname == 'xsec_xmlsec'):
845 // Cleanup windows header macro pollution.
846 #if defined(_WIN32) && defined(WINAPI)
847 # include <postwin.h>
855 def remove_from_tree(filename, tree):
856 # Remove this file, if top-level.
857 incs = tree.pop(filename, [])
859 tree = remove_from_tree(i, tree)
861 # Also remove if included from another.
862 for (k, v) in tree.items():
868 def tree_to_list(includes, filename, tree):
869 if filename in includes:
871 includes.append(filename)
872 #incs = tree.pop(filename, [])
873 incs = tree[filename] if filename in tree else []
875 tree_to_list(includes, i, tree)
879 def promote(includes):
880 """ Common library headers are heavily
881 referenced, even if they are included
883 Here we separate them to promote
884 their inclusion in the final pch.
888 if inc.startswith('boost') or \
889 inc.startswith('sal') or \
890 inc.startswith('osl') or \
891 inc.startswith('rtl'):
895 def make_pch_filename(root, module, libname):
896 """ PCH files are stored here:
897 <root>/<module>/inc/pch/precompiled_<libname>.hxx
900 path = os.path.join(root, module)
901 path = os.path.join(path, 'inc')
902 path = os.path.join(path, 'pch')
903 path = os.path.join(path, 'precompiled_' + libname + '.hxx')
909 global EXCLUDE_MODULE
911 global EXCLUDE_SYSTEM
915 if os.getenv('WORKDIR'):
916 WORKDIR = os.getenv('WORKDIR')
920 libname = sys.argv[2]
921 header = make_pch_filename(root, module, libname)
923 if not os.path.exists(os.path.join(root, module)):
924 raise Exception('Error: module [{}] not found.'.format(module))
926 key = '{}.{}'.format(module, libname)
928 # Load the module-specific defaults.
929 CUTOFF = DEFAULTS[key][0]
930 EXCLUDE_SYSTEM = DEFAULTS[key][1]
931 EXCLUDE_MODULE = DEFAULTS[key][2]
932 EXCLUDE_LOCAL = DEFAULTS[key][3]
935 for x in range(3, len(sys.argv)):
937 if i.startswith('--cutoff='):
938 CUTOFF = int(i.split('=')[1])
939 elif i.startswith('--exclude:'):
940 cat = i.split(':')[1]
942 EXCLUDE_MODULE = True
945 elif cat == 'system':
946 EXCLUDE_SYSTEM = True
947 elif i.startswith('--include:'):
948 cat = i.split(':')[1]
950 EXCLUDE_MODULE = False
952 EXCLUDE_LOCAL = False
953 elif cat == 'system':
954 EXCLUDE_SYSTEM = False
955 elif i == '--silent':
960 sys.stderr.write('Unknown option [{}].'.format(i))
963 filter_local = Filter_Local(root, module, \
964 not EXCLUDE_MODULE, \
968 groups = process_makefile(root, module, libname)
971 for osname, group in groups.items():
976 for filename in group:
977 includes += process_source(root, module, filename)
979 # Save unique top-level includes.
980 unique = set(includes)
981 promoted = promote(unique)
984 includes = remove_rare(includes)
985 includes = process_list(includes, lambda x: filter_ignore(x, module))
986 includes = process_list(includes, filter_local.proc)
988 # Remove the already included ones.
992 # Explode the excluded ones.
993 tree = {i:[] for i in includes}
994 tree = explode(root, module, unique, tree, filter_local, not EXCLUDE_MODULE)
996 # Remove the already included ones from the tree.
998 filename = get_filename(inc)
999 tree = remove_from_tree(filename, tree)
1002 for (k, v) in tree.items():
1003 extra += tree_to_list([], k, tree)
1005 promoted += promote(extra)
1006 promoted = process_list(promoted, lambda x: filter_ignore(x, module))
1007 promoted = process_list(promoted, filter_local.proc)
1008 promoted = set(promoted)
1009 # If a promoted header includes others, remove the rest.
1010 for (k, v) in tree.items():
1014 includes += [x for x in promoted]
1016 extra = remove_rare(extra)
1017 extra = process_list(extra, lambda x: filter_ignore(x, module))
1018 extra = process_list(extra, filter_local.proc)
1021 includes = [x for x in set(includes)]
1022 fixes = fixup(includes, module)
1023 fixes = map(lambda x: '#include <' + x + '>', fixes)
1025 includes = map(lambda x: '#include <' + x + '>', includes)
1026 sorted = sort_by_category(includes, root, module, filter_local)
1027 includes = list(fixes) + sorted
1034 groups[osname] = includes
1038 # Open the old pch and compare its contents
1039 # with new includes.
1040 # Clobber only if they are different.
1041 with open(header, 'r') as f:
1042 old_pch_lines = [x.strip() for x in f.readlines()]
1043 new_lines = generate_includes(groups)
1044 # Find the first include in the old pch.
1046 for i in range(len(old_pch_lines)):
1047 if old_pch_lines[i].startswith('#include') or old_pch_lines[i].startswith('#if PCH_LEVEL'):
1050 # Clobber if there is a mismatch.
1051 if force_update or start < 0 or (len(old_pch_lines) - start < len(new_lines)):
1052 generate(new_lines, libname, header, module)
1055 for i in range(len(new_lines)):
1056 if new_lines[i] != old_pch_lines[start + i]:
1057 generate(new_lines, libname, header, module)
1060 # Identical, but see if new pch removed anything.
1061 for i in range(start + len(new_lines), len(old_pch_lines)):
1062 if '#include' in old_pch_lines[i]:
1063 generate(new_lines, libname, header, module)
1069 if __name__ == '__main__':
1070 """ Process all the includes in a Module
1071 to make into a PCH file.
1072 Run without arguments for unittests,
1076 if len(sys.argv) >= 3:
1080 print('Usage: {} <Module name> <Library name> [options]'.format(sys.argv[0]))
1081 print(' Always run from the root of LO repository.\n')
1083 print(' --cutoff=<count> - Threshold to excluding headers.')
1084 print(' --exclude:<category> - Exclude category-specific headers.')
1085 print(' --include:<category> - Include category-specific headers.')
1086 print(' --force - Force updating the pch even when nothing changes.')
1087 print(' Categories:')
1088 print(' module - Headers in /inc directory of a module.')
1089 print(' local - Headers local to a source file.')
1090 print(' system - Platform-specific headers.')
1091 print(' --silent - print only errors.')
1092 print('\nRunning unit-tests...')
1095 class TestMethods(unittest.TestCase):
1097 def test_sanitize(self):
1098 self.assertEqual(sanitize('#include "blah/file.cxx"'),
1099 '#include <blah/file.cxx>')
1100 self.assertEqual(sanitize(' #include\t"blah/file.cxx" '),
1101 '#include <blah/file.cxx>')
1102 self.assertEqual(sanitize(' '),
1105 def test_filter_ignore(self):
1106 self.assertEqual(filter_ignore('blah/file.cxx', 'mod'),
1108 self.assertEqual(filter_ignore('vector', 'mod'),
1110 self.assertEqual(filter_ignore('file.cxx', 'mod'),
1113 def test_remove_rare(self):
1114 self.assertEqual(remove_rare([]),
1117 class TestMakefileParser(unittest.TestCase):
1120 global EXCLUDE_SYSTEM
1121 EXCLUDE_SYSTEM = False
1123 def test_parse_singleline_eval(self):
1124 source = "$(eval $(call gb_Library_Library,sal))"
1125 lines = source.split('\n')
1127 groups = parse_makefile(groups, lines, 0, None, 0)
1128 self.assertEqual(len(groups), 1)
1129 self.assertEqual(len(groups['']), 0)
1131 def test_parse_multiline_eval(self):
1132 source = """$(eval $(call gb_Library_set_include,sal,\\
1134 -I$(SRCDIR)/sal/inc \\
1137 lines = source.split('\n')
1139 groups = parse_makefile(groups, lines, 0, None, 0)
1140 self.assertEqual(len(groups), 1)
1141 self.assertEqual(len(groups['']), 0)
1143 def test_parse_multiline_eval_with_if(self):
1144 source = """$(eval $(call gb_Library_add_defs,sal,\\
1145 $(if $(filter $(OS),iOS), \\
1146 -DNO_CHILD_PROCESSES \\
1150 lines = source.split('\n')
1152 groups = parse_makefile(groups, lines, 0, None, 0)
1153 self.assertEqual(len(groups), 1)
1154 self.assertEqual(len(groups['']), 0)
1156 def test_parse_multiline_add_with_if(self):
1157 source = """$(eval $(call gb_Library_add_exception_objects,sal,\\
1159 $(if $(filter DESKTOP,$(BUILD_TYPE)), sal/osl/unx/salinit) \\
1162 lines = source.split('\n')
1164 groups = parse_makefile(groups, lines, 0, None, 0)
1165 self.assertEqual(len(groups), 1)
1166 self.assertEqual(len(groups['']), 1)
1167 self.assertEqual(groups[''][0], 'sal/osl/unx/time.cxx')
1169 def test_parse_if_else(self):
1170 source = """ifeq ($(OS),MACOSX)
1171 $(eval $(call gb_Library_add_exception_objects,sal,\\
1175 $(eval $(call gb_Library_add_exception_objects,sal,\\
1176 sal/osl/unx/uunxapi \\
1180 lines = source.split('\n')
1182 groups = parse_makefile(groups, lines, 0, None, 0)
1183 self.assertEqual(len(groups), 3)
1184 self.assertEqual(len(groups['']), 0)
1185 self.assertEqual(len(groups['MACOSX']), 1)
1186 self.assertEqual(len(groups['!MACOSX']), 1)
1187 self.assertEqual(groups['MACOSX'][0], 'sal/osl/mac/mac.cxx')
1188 self.assertEqual(groups['!MACOSX'][0], 'sal/osl/unx/uunxapi.cxx')
1190 def test_parse_nested_if(self):
1191 source = """ifeq ($(OS),MACOSX)
1192 $(eval $(call gb_Library_add_exception_objects,sal,\\
1196 $(eval $(call gb_Library_add_exception_objects,sal,\\
1197 sal/osl/unx/uunxapi \\
1201 $(eval $(call gb_Library_add_exception_objects,sal,\\
1202 sal/textenc/context \\
1207 lines = source.split('\n')
1209 groups = parse_makefile(groups, lines, 0, None, 0)
1210 self.assertEqual(len(groups), 4)
1211 self.assertEqual(len(groups['']), 0)
1212 self.assertEqual(len(groups['MACOSX']), 1)
1213 self.assertEqual(len(groups['!MACOSX']), 1)
1214 self.assertEqual(len(groups['LINUX']), 1)
1215 self.assertEqual(groups['MACOSX'][0], 'sal/osl/mac/mac.cxx')
1216 self.assertEqual(groups['!MACOSX'][0], 'sal/osl/unx/uunxapi.cxx')
1217 self.assertEqual(groups['LINUX'][0], 'sal/textenc/context.cxx')
1219 def test_parse_exclude_system(self):
1220 source = """ifeq ($(OS),MACOSX)
1221 $(eval $(call gb_Library_add_exception_objects,sal,\\
1225 $(eval $(call gb_Library_add_exception_objects,sal,\\
1226 sal/osl/unx/uunxapi \\
1230 $(eval $(call gb_Library_add_exception_objects,sal,\\
1231 sal/textenc/context \\
1236 global EXCLUDE_SYSTEM
1237 EXCLUDE_SYSTEM = True
1239 lines = source.split('\n')
1241 groups = parse_makefile(groups, lines, 0, None, 0)
1242 self.assertEqual(len(groups), 1)
1243 self.assertEqual(len(groups['']), 0)
1245 def test_parse_filter(self):
1246 source = """ifneq ($(filter $(OS),MACOSX iOS),)
1247 $(eval $(call gb_Library_add_exception_objects,sal,\\
1248 sal/osl/unx/osxlocale \\
1252 # Filter is still unsupported.
1253 lines = source.split('\n')
1255 groups = parse_makefile(groups, lines, 0, None, 0)
1256 self.assertEqual(len(groups), 1)
1257 self.assertEqual(len(groups['']), 0)
1261 # vim: set et sw=4 ts=4 expandtab: