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',
479 for i in ignore_list:
480 if line.startswith(i):
482 if i[0] == '*' and line.endswith(i[1:]):
484 if i[-1] == '*' and line.startswith(i[:-1]):
489 def fixup(includes, module):
490 """ Here we add any headers
491 necessary in the pch.
492 These could be known to be very
493 common but for technical reasons
494 left out of the pch by this generator.
495 Or, they could be missing from the
496 source files where they are used
497 (probably because they had been
498 in the old pch, they were missed).
499 Also, these could be headers
500 that make the build faster but
501 aren't added automatically.
505 # Add a space to exclude from
507 line = ' #include <{}>'.format(inc)
514 if module == 'basctl':
515 if 'basslots.hxx' in includes:
516 append('sfx2/msg.hxx')
519 # if 'scslots.hxx' in includes:
520 # append('sfx2/msg.hxx')
523 def sort_by_category(list, root, module, filter_local):
524 """ Move all 'system' headers first.
525 Core files of osl, rtl, sal, next.
526 Everything non-module-specific third.
527 Last, module-specific headers.
535 prefix = '<' + module + '/'
537 if is_c_runtime(i, root, module):
541 elif prefix in i or not '/' in i:
543 elif '<sal/' in i or '<vcl/' in i:
545 elif '<osl/' in i or '<rtl/' in i:
546 if module == "sal": # osl and rtl are also part of sal
550 # Headers from another module that is closely tied to the module.
551 elif module == 'sc' and '<formula' in i:
557 out += [ "#if PCH_LEVEL >= 1" ]
560 out += [ "#endif // PCH_LEVEL >= 1" ]
561 out += [ "#if PCH_LEVEL >= 2" ]
563 out += [ "#endif // PCH_LEVEL >= 2" ]
564 out += [ "#if PCH_LEVEL >= 3" ]
566 out += [ "#endif // PCH_LEVEL >= 3" ]
567 out += [ "#if PCH_LEVEL >= 4" ]
569 out += [ "#endif // PCH_LEVEL >= 4" ]
572 def parse_makefile(groups, lines, lineno, lastif, ifstack):
575 ingeneratedobjects = False
578 os_cond_re = re.compile(r'(ifeq|ifneq)\s*\(\$\(OS\)\,(\w*)\)')
581 if line.startswith('if'):
584 # Correction if first line is an if.
585 lineno = parse_makefile(groups, lines, lineno, line, ifstack+1)
589 while lineno + 1 < len(lines):
591 line = lines[lineno].strip()
592 line = line.rstrip('\\').strip()
593 #print('line #{}: {}'.format(lineno, line))
599 ingeneratedobjects = False
600 elif 'add_exception_objects' in line or \
601 'add_cxxobject' in line:
604 #if ifstack and not SILENT:
605 #sys.stderr.write('Sources in a conditional, ignoring for now.\n')
606 elif 'add_generated_exception_objects' in line or \
607 'add_generated_cxxobject' in line:
608 ingeneratedobjects = True
609 elif 'set_generated_cxx_suffix' in line:
610 suffix_re = re.compile('.*set_generated_cxx_suffix,[^,]*,([^)]*).*')
611 match = suffix_re.match(line)
613 suffix = match.group(1)
614 elif line.startswith('if'):
615 lineno = parse_makefile(groups, lines, lineno, line, ifstack+1)
617 elif line.startswith('endif'):
621 elif line.startswith('else'):
623 elif inobjects or ingeneratedobjects:
624 if EXCLUDE_SYSTEM and ifstack:
626 file = line + '.' + suffix
627 if ',' in line or '(' in line or ')' in line or file.startswith('-'):
628 #print('passing: ' + line)
629 pass # $if() probably, or something similar
633 if 'filter' in lastif:
634 # We can't grok filter, yet.
636 match = os_cond_re.match(lastif)
638 # We only support OS conditionals.
640 in_out = match.group(1)
641 osname = match.group(2) if match else ''
642 if (in_out == 'ifneq' and not inelse) or \
643 (in_out == 'ifeq' and inelse):
644 osname = '!' + osname
646 if osname not in groups:
648 if ingeneratedobjects:
649 file = WORKDIR + '/' + file
650 groups[osname].append(file)
654 def process_makefile(root, module, libname):
655 """ Parse a gmake makefile and extract
656 source filenames from it.
659 makefile = 'Library_{}.mk'.format(libname)
660 filename = os.path.join(os.path.join(root, module), makefile)
661 if not os.path.isfile(filename):
662 makefile = 'StaticLibrary_{}.mk'.format(libname)
663 filename = os.path.join(os.path.join(root, module), makefile)
664 if not os.path.isfile(filename):
665 sys.stderr.write('Error: Module {} has no makefile at {}.'.format(module, filename))
667 groups = {'':[], 'ANDROID':[], 'iOS':[], 'WNT':[], 'LINUX':[], 'MACOSX':[]}
669 with open(filename, 'r') as f:
670 lines = f.readlines()
671 groups = parse_makefile(groups, lines, lineno=0, lastif=None, ifstack=0)
675 def is_allowed_if(line, module):
676 """ Check whether the given #if condition
677 is allowed for the given module or whether
678 its block should be ignored.
681 # remove trailing comments
682 line = re.sub(r'(.*) *//.*', r'\1', line)
685 # Our sources always build with LIBO_INTERNAL_ONLY.
686 if line == "#if defined LIBO_INTERNAL_ONLY" or line == "#ifdef LIBO_INTERNAL_ONLY":
688 if module == "external/skia":
689 # We always set these.
690 if line == "#ifdef SK_VULKAN" or line == "#if SK_SUPPORT_GPU":
694 def process_source(root, module, filename, maxdepth=0):
695 """ Process a source file to extract
697 For now, skip on compiler directives.
698 maxdepth is used when processing headers
699 which typically have protecting ifndef.
707 with open(filename, 'r') as f:
710 if line.startswith('#if'):
711 if is_allowed_if(line, module):
712 allowed_ifs.append(True)
715 allowed_ifs.append(False)
718 elif line.startswith('#endif'):
720 if allowed_ifs[ ifdepth ]:
724 del allowed_ifs[ ifdepth ]
725 elif line.startswith('#include'):
726 if ifdepth - ifsallowed <= maxdepth:
727 line = sanitize(line)
729 line = get_filename(line)
730 if line and len(line):
731 raw_includes.append(line)
733 sys.stderr.write('#include in {} : {}\n'.format(lastif, line))
737 def explode(root, module, includes, tree, filter_local, recurse):
738 incpath = os.path.join(root, 'include')
741 filename = get_filename(inc)
742 if filename in tree or len(filter_local.proc(filename)) == 0:
746 # Module or Local header.
747 filepath = filter_local.find_local_file(inc)
749 #print('trying loc: ' + filepath)
750 incs = process_source(root, module, filepath, maxdepth=1)
751 incs = map(get_filename, incs)
752 incs = process_list(incs, lambda x: filter_ignore(x, module))
753 incs = process_list(incs, filter_local.proc)
754 tree[filename] = incs
756 tree = explode(root, module, incs, tree, filter_local, recurse)
757 #print('{} => {}'.format(filepath, tree[filename]))
764 filepath = os.path.join(incpath, filename)
765 #print('trying pub: ' + filepath)
766 incs = process_source(root, module, filepath, maxdepth=1)
767 incs = map(get_filename, incs)
768 incs = process_list(incs, lambda x: filter_ignore(x, module))
769 incs = process_list(incs, filter_local.proc)
770 tree[filename] = incs
772 tree = explode(root, module, incs, tree, filter_local, recurse)
773 #print('{} => {}'.format(filepath, tree[filename]))
778 # Failed, but remember to avoid searching again.
783 def make_command_line():
785 # Remove command line flags and
786 # use internal flags.
787 for i in range(len(args)-1, 0, -1):
788 if args[i].startswith('--'):
791 args.append('--cutoff=' + str(CUTOFF))
793 args.append('--exclude:system')
795 args.append('--include:system')
797 args.append('--exclude:module')
799 args.append('--include:module')
801 args.append('--exclude:local')
803 args.append('--include:local')
805 return ' '.join(args)
807 def generate_includes(includes):
808 """Generates the include lines of the pch.
811 for osname, group in includes.items():
821 lines.append('#if {}defined({})'.format(not_eq, osname))
827 lines.append('#endif')
831 def generate(includes, libname, filename, module):
833 """/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
835 * This file is part of the LibreOffice project.
837 * This Source Code Form is subject to the terms of the Mozilla Public
838 * License, v. 2.0. If a copy of the MPL was not distributed with this
839 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
843 This file has been autogenerated by update_pch.sh. It is possible to edit it
844 manually (such as when an include file has been moved/renamed/removed). All such
845 manual changes will be rewritten by the next run of update_pch.sh (which presumably
846 also fixes all possible problems, so it's usually better to use it).
851 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
855 with open(filename, 'w') as f:
857 f.write('\n Generated on {} using:\n {}\n'.format(
858 datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
859 make_command_line()))
860 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(
863 # sal needs this for rand_s()
864 if module == 'sal' and libname == 'sal':
867 #if !defined _CRT_RAND_S
879 # Some libraries pull windows headers that aren't self contained.
880 if (module == 'connectivity' and libname == 'ado') or \
881 (module == 'xmlsecurity' and libname == 'xsec_xmlsec'):
883 // Cleanup windows header macro pollution.
884 #if defined(_WIN32) && defined(WINAPI)
893 def remove_from_tree(filename, tree):
894 # Remove this file, if top-level.
895 incs = tree.pop(filename, [])
897 tree = remove_from_tree(i, tree)
899 # Also remove if included from another.
900 for (k, v) in tree.items():
906 def tree_to_list(includes, filename, tree):
907 if filename in includes:
909 includes.append(filename)
910 #incs = tree.pop(filename, [])
911 incs = tree[filename] if filename in tree else []
913 tree_to_list(includes, i, tree)
917 def promote(includes):
918 """ Common library headers are heavily
919 referenced, even if they are included
921 Here we separate them to promote
922 their inclusion in the final pch.
926 if inc.startswith('boost') or \
927 inc.startswith('sal') or \
928 inc.startswith('osl') or \
929 inc.startswith('rtl'):
933 def make_pch_filename(root, module, libname):
934 """ PCH files are stored here:
935 <root>/<module>/inc/pch/precompiled_<libname>.hxx
938 path = os.path.join(root, module)
939 path = os.path.join(path, 'inc')
940 path = os.path.join(path, 'pch')
941 path = os.path.join(path, 'precompiled_' + libname + '.hxx')
947 global EXCLUDE_MODULE
949 global EXCLUDE_SYSTEM
953 if os.getenv('WORKDIR'):
954 WORKDIR = os.getenv('WORKDIR')
958 libname = sys.argv[2]
959 header = make_pch_filename(root, module, libname)
961 if not os.path.exists(os.path.join(root, module)):
962 raise Exception('Error: module [{}] not found.'.format(module))
964 key = '{}.{}'.format(module, libname)
966 # Load the module-specific defaults.
967 CUTOFF = DEFAULTS[key][0]
968 EXCLUDE_SYSTEM = DEFAULTS[key][1]
969 EXCLUDE_MODULE = DEFAULTS[key][2]
970 EXCLUDE_LOCAL = DEFAULTS[key][3]
973 for x in range(3, len(sys.argv)):
975 if i.startswith('--cutoff='):
976 CUTOFF = int(i.split('=')[1])
977 elif i.startswith('--exclude:'):
978 cat = i.split(':')[1]
980 EXCLUDE_MODULE = True
983 elif cat == 'system':
984 EXCLUDE_SYSTEM = True
985 elif i.startswith('--include:'):
986 cat = i.split(':')[1]
988 EXCLUDE_MODULE = False
990 EXCLUDE_LOCAL = False
991 elif cat == 'system':
992 EXCLUDE_SYSTEM = False
993 elif i == '--silent':
998 sys.stderr.write('Unknown option [{}].'.format(i))
1001 filter_local = Filter_Local(root, module, \
1002 not EXCLUDE_MODULE, \
1006 groups = process_makefile(root, module, libname)
1009 for osname, group in groups.items():
1014 for filename in group:
1015 includes += process_source(root, module, filename)
1017 # Save unique top-level includes.
1018 unique = set(includes)
1019 promoted = promote(unique)
1022 includes = remove_rare(includes)
1023 includes = process_list(includes, lambda x: filter_ignore(x, module))
1024 includes = process_list(includes, filter_local.proc)
1026 # Remove the already included ones.
1027 for inc in includes:
1030 # Explode the excluded ones.
1031 tree = {i:[] for i in includes}
1032 tree = explode(root, module, unique, tree, filter_local, not EXCLUDE_MODULE)
1034 # Remove the already included ones from the tree.
1035 for inc in includes:
1036 filename = get_filename(inc)
1037 tree = remove_from_tree(filename, tree)
1040 for (k, v) in tree.items():
1041 extra += tree_to_list([], k, tree)
1043 promoted += promote(extra)
1044 promoted = process_list(promoted, lambda x: filter_ignore(x, module))
1045 promoted = process_list(promoted, filter_local.proc)
1046 promoted = set(promoted)
1047 # If a promoted header includes others, remove the rest.
1048 for (k, v) in tree.items():
1052 includes += [x for x in promoted]
1054 extra = remove_rare(extra)
1055 extra = process_list(extra, lambda x: filter_ignore(x, module))
1056 extra = process_list(extra, filter_local.proc)
1059 includes = [x for x in set(includes)]
1060 fixes = fixup(includes, module)
1061 fixes = map(lambda x: '#include <' + x + '>', fixes)
1063 includes = map(lambda x: '#include <' + x + '>', includes)
1064 sorted = sort_by_category(includes, root, module, filter_local)
1065 includes = list(fixes) + sorted
1072 groups[osname] = includes
1076 # Open the old pch and compare its contents
1077 # with new includes.
1078 # Clobber only if they are different.
1079 with open(header, 'r') as f:
1080 old_pch_lines = [x.strip() for x in f.readlines()]
1081 new_lines = generate_includes(groups)
1082 # Find the first include in the old pch.
1084 for i in range(len(old_pch_lines)):
1085 if old_pch_lines[i].startswith('#include') or old_pch_lines[i].startswith('#if PCH_LEVEL'):
1088 # Clobber if there is a mismatch.
1089 if force_update or start < 0 or (len(old_pch_lines) - start < len(new_lines)):
1090 generate(new_lines, libname, header, module)
1093 for i in range(len(new_lines)):
1094 if new_lines[i] != old_pch_lines[start + i]:
1095 generate(new_lines, libname, header, module)
1098 # Identical, but see if new pch removed anything.
1099 for i in range(start + len(new_lines), len(old_pch_lines)):
1100 if '#include' in old_pch_lines[i]:
1101 generate(new_lines, libname, header, module)
1107 if __name__ == '__main__':
1108 """ Process all the includes in a Module
1109 to make into a PCH file.
1110 Run without arguments for unittests,
1114 if len(sys.argv) >= 3:
1118 print('Usage: {} <Module name> <Library name> [options]'.format(sys.argv[0]))
1119 print(' Always run from the root of LO repository.\n')
1121 print(' --cutoff=<count> - Threshold to excluding headers.')
1122 print(' --exclude:<category> - Exclude category-specific headers.')
1123 print(' --include:<category> - Include category-specific headers.')
1124 print(' --force - Force updating the pch even when nothing changes.')
1125 print(' Categories:')
1126 print(' module - Headers in /inc directory of a module.')
1127 print(' local - Headers local to a source file.')
1128 print(' system - Platform-specific headers.')
1129 print(' --silent - print only errors.')
1130 print('\nRunning unit-tests...')
1133 class TestMethods(unittest.TestCase):
1135 def test_sanitize(self):
1136 self.assertEqual(sanitize('#include "blah/file.cxx"'),
1137 '#include <blah/file.cxx>')
1138 self.assertEqual(sanitize(' #include\t"blah/file.cxx" '),
1139 '#include <blah/file.cxx>')
1140 self.assertEqual(sanitize(' '),
1143 def test_filter_ignore(self):
1144 self.assertEqual(filter_ignore('blah/file.cxx', 'mod'),
1146 self.assertEqual(filter_ignore('vector', 'mod'),
1148 self.assertEqual(filter_ignore('file.cxx', 'mod'),
1151 def test_remove_rare(self):
1152 self.assertEqual(remove_rare([]),
1155 class TestMakefileParser(unittest.TestCase):
1158 global EXCLUDE_SYSTEM
1159 EXCLUDE_SYSTEM = False
1161 def test_parse_singleline_eval(self):
1162 source = "$(eval $(call gb_Library_Library,sal))"
1163 lines = source.split('\n')
1165 groups = parse_makefile(groups, lines, 0, None, 0)
1166 self.assertEqual(len(groups), 1)
1167 self.assertEqual(len(groups['']), 0)
1169 def test_parse_multiline_eval(self):
1170 source = """$(eval $(call gb_Library_set_include,sal,\\
1172 -I$(SRCDIR)/sal/inc \\
1175 lines = source.split('\n')
1177 groups = parse_makefile(groups, lines, 0, None, 0)
1178 self.assertEqual(len(groups), 1)
1179 self.assertEqual(len(groups['']), 0)
1181 def test_parse_multiline_eval_with_if(self):
1182 source = """$(eval $(call gb_Library_add_defs,sal,\\
1183 $(if $(filter $(OS),iOS), \\
1184 -DNO_CHILD_PROCESSES \\
1188 lines = source.split('\n')
1190 groups = parse_makefile(groups, lines, 0, None, 0)
1191 self.assertEqual(len(groups), 1)
1192 self.assertEqual(len(groups['']), 0)
1194 def test_parse_multiline_add_with_if(self):
1195 source = """$(eval $(call gb_Library_add_exception_objects,sal,\\
1197 $(if $(filter DESKTOP,$(BUILD_TYPE)), sal/osl/unx/salinit) \\
1200 lines = source.split('\n')
1202 groups = parse_makefile(groups, lines, 0, None, 0)
1203 self.assertEqual(len(groups), 1)
1204 self.assertEqual(len(groups['']), 1)
1205 self.assertEqual(groups[''][0], 'sal/osl/unx/time.cxx')
1207 def test_parse_if_else(self):
1208 source = """ifeq ($(OS),MACOSX)
1209 $(eval $(call gb_Library_add_exception_objects,sal,\\
1213 $(eval $(call gb_Library_add_exception_objects,sal,\\
1214 sal/osl/unx/uunxapi \\
1218 lines = source.split('\n')
1220 groups = parse_makefile(groups, lines, 0, None, 0)
1221 self.assertEqual(len(groups), 3)
1222 self.assertEqual(len(groups['']), 0)
1223 self.assertEqual(len(groups['MACOSX']), 1)
1224 self.assertEqual(len(groups['!MACOSX']), 1)
1225 self.assertEqual(groups['MACOSX'][0], 'sal/osl/mac/mac.cxx')
1226 self.assertEqual(groups['!MACOSX'][0], 'sal/osl/unx/uunxapi.cxx')
1228 def test_parse_nested_if(self):
1229 source = """ifeq ($(OS),MACOSX)
1230 $(eval $(call gb_Library_add_exception_objects,sal,\\
1234 $(eval $(call gb_Library_add_exception_objects,sal,\\
1235 sal/osl/unx/uunxapi \\
1239 $(eval $(call gb_Library_add_exception_objects,sal,\\
1240 sal/textenc/context \\
1245 lines = source.split('\n')
1247 groups = parse_makefile(groups, lines, 0, None, 0)
1248 self.assertEqual(len(groups), 4)
1249 self.assertEqual(len(groups['']), 0)
1250 self.assertEqual(len(groups['MACOSX']), 1)
1251 self.assertEqual(len(groups['!MACOSX']), 1)
1252 self.assertEqual(len(groups['LINUX']), 1)
1253 self.assertEqual(groups['MACOSX'][0], 'sal/osl/mac/mac.cxx')
1254 self.assertEqual(groups['!MACOSX'][0], 'sal/osl/unx/uunxapi.cxx')
1255 self.assertEqual(groups['LINUX'][0], 'sal/textenc/context.cxx')
1257 def test_parse_exclude_system(self):
1258 source = """ifeq ($(OS),MACOSX)
1259 $(eval $(call gb_Library_add_exception_objects,sal,\\
1263 $(eval $(call gb_Library_add_exception_objects,sal,\\
1264 sal/osl/unx/uunxapi \\
1268 $(eval $(call gb_Library_add_exception_objects,sal,\\
1269 sal/textenc/context \\
1274 global EXCLUDE_SYSTEM
1275 EXCLUDE_SYSTEM = True
1277 lines = source.split('\n')
1279 groups = parse_makefile(groups, lines, 0, None, 0)
1280 self.assertEqual(len(groups), 1)
1281 self.assertEqual(len(groups['']), 0)
1283 def test_parse_filter(self):
1284 source = """ifneq ($(filter $(OS),MACOSX iOS),)
1285 $(eval $(call gb_Library_add_exception_objects,sal,\\
1286 sal/osl/unx/osxlocale \\
1290 # Filter is still unsupported.
1291 lines = source.split('\n')
1293 groups = parse_makefile(groups, lines, 0, None, 0)
1294 self.assertEqual(len(groups), 1)
1295 self.assertEqual(len(groups['']), 0)
1299 # vim: set et sw=4 ts=4 expandtab: