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 'docmodel.docmodel' : ( 3, EXCLUDE, EXCLUDE, INCLUDE), # 3.8
77 'drawinglayer.drawinglayer' : ( 4, EXCLUDE, EXCLUDE, EXCLUDE), # 7.4
78 'editeng.editeng' : ( 5, EXCLUDE, INCLUDE, EXCLUDE), # 13.0
79 'forms.frm' : ( 2, EXCLUDE, EXCLUDE, EXCLUDE), # 14.2
80 'framework.fwk' : ( 7, EXCLUDE, INCLUDE, INCLUDE), # 14.8
81 'hwpfilter.hwp' : ( 3, EXCLUDE, INCLUDE, INCLUDE), # 6.0
82 'lotuswordpro.lwpft' : ( 2, EXCLUDE, EXCLUDE, EXCLUDE), # 11.6
83 'oox.oox' : ( 6, EXCLUDE, EXCLUDE, INCLUDE), # 28.2
84 'package.package2' : ( 3, EXCLUDE, INCLUDE, INCLUDE), # 4.5
85 'package.xstor' : ( 2, EXCLUDE, INCLUDE, EXCLUDE), # 3.8
86 'reportdesign.rpt' : ( 9, EXCLUDE, INCLUDE, INCLUDE), # 9.4
87 'reportdesign.rptui' : ( 4, EXCLUDE, INCLUDE, INCLUDE), # 13.1
88 'reportdesign.rptxml' : ( 2, EXCLUDE, EXCLUDE, INCLUDE), # 7.6
89 'sal.sal' : ( 2, EXCLUDE, EXCLUDE, INCLUDE), # 4.2
90 'sc.sc' : (12, EXCLUDE, INCLUDE, INCLUDE), # 92.6
91 'sc.scfilt' : ( 4, EXCLUDE, EXCLUDE, INCLUDE), # 39.9
92 'sc.scui' : ( 1, EXCLUDE, EXCLUDE, INCLUDE), # 15.0
93 'sc.vbaobj' : ( 1, EXCLUDE, EXCLUDE, INCLUDE), # 17.3
94 'sd.sd' : ( 4, EXCLUDE, EXCLUDE, INCLUDE), # 47.4
95 'sd.sdui' : ( 4, EXCLUDE, INCLUDE, INCLUDE), # 9.4
96 'sdext.PresentationMinimizer' : ( 2, EXCLUDE, INCLUDE, INCLUDE), # 4.1
97 'sdext.PresenterScreen' : ( 2, EXCLUDE, INCLUDE, EXCLUDE), # 7.1
98 'sfx2.sfx' : ( 3, EXCLUDE, EXCLUDE, EXCLUDE), # 27.4
99 'slideshow.slideshow' : ( 4, EXCLUDE, INCLUDE, EXCLUDE), # 10.8
100 'sot.sot' : ( 5, EXCLUDE, EXCLUDE, INCLUDE), # 3.1
101 'starmath.sm' : ( 5, EXCLUDE, EXCLUDE, INCLUDE), # 10.9
102 'svgio.svgio' : ( 8, EXCLUDE, EXCLUDE, INCLUDE), # 4.3
103 'emfio.emfio' : ( 8, EXCLUDE, EXCLUDE, INCLUDE), # 4.3
104 'svl.svl' : ( 6, EXCLUDE, EXCLUDE, EXCLUDE), # 7.6
105 'svtools.svt' : ( 4, EXCLUDE, INCLUDE, EXCLUDE), # 17.6
106 'svx.svx' : ( 3, EXCLUDE, EXCLUDE, INCLUDE), # 20.7
107 'svx.svxcore' : ( 7, EXCLUDE, INCLUDE, EXCLUDE), # 37.0
108 'sw.msword' : ( 4, EXCLUDE, INCLUDE, INCLUDE), # 22.4
109 'sw.sw' : ( 7, EXCLUDE, EXCLUDE, INCLUDE), # 129.6
110 'sw.swui' : ( 3, EXCLUDE, INCLUDE, INCLUDE), # 26.1
111 'sw.vbaswobj' : ( 4, EXCLUDE, INCLUDE, INCLUDE), # 13.1
112 'tools.tl' : ( 5, EXCLUDE, EXCLUDE, EXCLUDE), # 4.2
113 'unotools.utl' : ( 3, EXCLUDE, EXCLUDE, INCLUDE), # 7.0
114 'unoxml.unoxml' : ( 1, EXCLUDE, EXCLUDE, EXCLUDE), # 4.6
115 'uui.uui' : ( 4, EXCLUDE, EXCLUDE, EXCLUDE), # 4.9
116 'vbahelper.msforms' : ( 3, EXCLUDE, INCLUDE, INCLUDE), # 5.2
117 'vbahelper.vbahelper' : ( 3, EXCLUDE, EXCLUDE, INCLUDE), # 7.0
118 'vcl.vcl' : ( 6, EXCLUDE, INCLUDE, INCLUDE), # 35.7
119 'writerfilter.writerfilter' : ( 5, EXCLUDE, EXCLUDE, EXCLUDE), # 19.7/27.3
120 'xmloff.xo' : ( 7, EXCLUDE, INCLUDE, INCLUDE), # 22.1
121 'xmloff.xof' : ( 1, EXCLUDE, EXCLUDE, INCLUDE), # 4.4
122 'xmlscript.xmlscript' : ( 4, EXCLUDE, EXCLUDE, INCLUDE), # 3.6
123 'xmlsecurity.xmlsecurity' : ( 6, EXCLUDE, INCLUDE, INCLUDE), # 5.1
124 'xmlsecurity.xsec_xmlsec' : ( 2, EXCLUDE, INCLUDE, INCLUDE), # 4.4
125 'xmlsecurity.xsec_gpg' : ( 2, EXCLUDE, INCLUDE, INCLUDE), # ?
128 def remove_rare(raw, min_use=-1):
129 """ Remove headers not commonly included.
130 The minimum threshold is min_use.
132 # The minimum number of times a header
133 # must be included to be in the PCH.
134 min_use = min_use if min_use >= 0 else CUTOFF
137 if not raw or not len(raw):
143 for x in range(1, len(inc)):
159 def process_list(list, callable):
160 """ Given a list and callable
161 we pass each entry through
162 the callable and only add to
163 the output if not blank.
168 if line and len(line):
172 def find_files(path, recurse=True):
174 for root, dir, files in os.walk(path):
175 list += map(lambda x: os.path.join(root, x), files)
178 def get_filename(line):
179 """ Strips the line from the
180 '#include' and angled brackets
181 and return the filename only.
183 if not len(line) or line[0] != '#':
185 return re.sub(r'(.*#include\s*)<(.*)>(.*)', r'\2', line)
187 def is_c_runtime(inc, root, module):
188 """ Heuristic-based detection of C/C++
190 They are all-lowercase, with .h or
191 no extension, filename only.
192 Try to check that they are not LO headers.
194 inc = get_filename(inc)
196 if inc.endswith('.hxx') or inc.endswith('.hpp'):
199 if inc.endswith('.h') and inc.startswith( 'config_' ):
206 if c == '.' and not inc.endswith('.h'):
212 if not hasdot: # <memory> etc.
215 if glob.glob(os.path.join(root, module, '**', inc), recursive=True):
221 """ There are two forms of includes,
222 those with <> and "".
223 Technically, the difference is that
224 the compiler can use an internal
225 representation for an angled include,
226 such that it doesn't have to be a file.
227 For our purposes, there is no difference.
228 Here, we convert everything to angled.
230 if not raw or not len(raw):
235 return re.sub(r'(.*#include\s*)\"(.*)\"(.*)', r'#include <\2>', raw)
237 class Filter_Local(object):
238 """ Filter headers local to a module.
239 allow_public: allows include/module/file.hxx
240 #include <module/file.hxx>
241 allow_module: allows module/inc/file.hxx
243 allow_locals: allows module/source/file.hxx and
244 module/source/inc/file.hxx
247 def __init__(self, root, module, allow_public=True, allow_module=True, allow_locals=True):
250 self.allow_public = allow_public
251 self.allow_module = allow_module
252 self.allow_locals = allow_locals
253 self.public_prefix = '<' + self.module + '/'
255 all = find_files(os.path.join(root, module))
256 self.module_includes = []
258 mod_prefix = module + '/inc/'
261 self.module_includes.append(i)
263 self.locals.append(i)
265 def is_public(self, line):
266 return self.public_prefix in line
268 def is_module(self, line):
269 """ Returns True if in module/inc/... """
270 filename = get_filename(line)
271 for i in self.module_includes:
272 if i.endswith(filename):
276 def is_local(self, line):
277 """ Returns True if in module/source/... """
278 filename = get_filename(line)
279 for i in self.locals:
280 if i.endswith(filename):
284 def is_external(self, line):
285 return is_c_runtime(line, self.root, self.module) and \
286 not self.is_public(line) and \
287 not self.is_module(line) and \
288 not self.is_local(line)
290 def find_local_file(self, line):
291 """ Finds the header file in the module dir,
292 but doesn't validate.
294 filename = get_filename(line)
295 for i in self.locals:
296 if i.endswith(filename):
298 for i in self.module_includes:
299 if i.endswith(filename):
303 def proc(self, line):
304 assert line and len(line)
308 sys.stderr.write('unhandled #include : {}\n'.format(line))
311 assert line[0] != '<' and line[0] != '#'
313 filename = get_filename(line)
315 # Local with relative path.
316 if filename.startswith('..'):
317 # Exclude for now as we don't have cxx path.
320 # Locals are included first (by the compiler).
321 if self.is_local(filename):
322 # Use only locals that are in some /inc/ directory (either in <module>/inc or
323 # somewhere under <module>/source/**/inc/, compilations use -I for these paths
324 # and headers elsewhere would not be found when compiling the PCH.
325 if not self.allow_locals:
327 elif '/inc/' in filename:
329 elif glob.glob(os.path.join(self.root, self.module, '**', 'inc', filename), recursive=True):
334 # Module headers are next.
335 if self.is_module(filename):
336 return line if self.allow_module else ''
338 # Public headers are last.
339 if self.is_public(line):
340 return line if self.allow_public else ''
342 # Leave out potentially unrelated files local
343 # to some other module we can't include directly.
344 if '/' not in filename and not self.is_external(filename):
350 def filter_ignore(line, module):
351 """ Filters includes from known
353 Expects sanitized input.
355 assert line and len(line)
357 # Always include files without extension.
361 # Extract filenames for ease of comparison.
362 line = get_filename(line)
364 # Filter out all files that are not normal headers.
365 if not line.endswith('.h') and \
366 not line.endswith('.hxx') and \
367 not line.endswith('.hpp') and \
368 not line.endswith('.hdl'):
372 'LibreOfficeKit/LibreOfficeKitEnums.h', # Needs special directives
373 'LibreOfficeKit/LibreOfficeKitTypes.h', # Needs special directives
374 'jerror.h', # c++ unfriendly
375 'jpeglib.h', # c++ unfriendly
376 'boost/spirit/include/classic_core.hpp' # depends on BOOST_SPIRIT_DEBUG
379 if module == 'basic':
381 'basic/vbahelper.hxx',
383 if module == 'connectivity':
385 'com/sun/star/beans/PropertyAttribute.hpp', # OPTIONAL defined via objbase.h
386 'com/sun/star/sdbcx/Privilege.hpp', # DELETE defined via objbase.h
387 'ado/*' , # some strange type conflict because of Window's adoctint.h
393 'progress.hxx', # special directives
394 'scslots.hxx', # special directives
398 'sdgslots.hxx', # special directives
399 'sdslots.hxx', # special directives
403 'sfx2/recentdocsview.hxx', # Redefines ApplicationType defined in objidl.h
404 'sfx2/sidebar/Sidebar.hxx',
405 'sfx2/sidebar/UnoSidebar.hxx',
406 'sfxslots.hxx', # externally defined types
410 'sysformats.hxx', # Windows headers
414 'accmgr.hxx', # redefines ImplAccelList
417 'opengl/gdiimpl.hxx',
419 'openglgdiimpl', # ReplaceTextA
421 'salinst.hxx', # GetDefaultPrinterA
422 'salprn.hxx', # SetPrinterDataA
424 'vcl/oldprintadaptor.hxx',
425 'vcl/opengl/OpenGLContext.hxx',
426 'vcl/opengl/OpenGLHelper.hxx', # Conflicts with X header on *ix
428 'vcl/prntypes.hxx', # redefines Orientation from filter/jpeg/Exif.hxx
431 if module == 'xmloff':
433 'SchXMLExport.hxx', # SchXMLAutoStylePoolP.hxx not found
434 'SchXMLImport.hxx', # enums redefined in draw\sdxmlimp_impl.hxx
435 'XMLEventImportHelper.hxx', # NameMap redefined in XMLEventExport.hxx
436 'xmloff/XMLEventExport.hxx', # enums redefined
438 if module == 'xmlsecurity':
441 'xmlsecurity/xmlsec-wrapper.h',
443 if module == 'external/pdfium':
445 'third_party/freetype/include/pstables.h',
447 if module == 'external/clucene':
454 'CLucene/LuceneThreads.h',
455 'CLucene/config/_threads.h',
457 if module == 'external/skia':
460 'zlib.h', # causes crc32 conflict
461 'dirent.h', # unix-specific
466 'fontconfig/fontconfig.h',
468 'src/Transform_inl.h',
469 'src/c/sk_c_from_to.h',
470 'src/c/sk_types_priv.h',
471 'src/core/SkBlitBWMaskTemplate.h',
472 'src/sfnt/SkSFNTHeader.h',
474 'src/core/SkCubicSolver.h',
475 'src/sksl/SkSLCPP.h',
476 'src/gpu/vk/GrVkAMDMemoryAllocator.h',
478 'src/sksl/', # conflict between SkSL::Expression and SkSL::dsl::Expression
483 if module == 'external/zxing':
485 'rss/ODRSSExpandedBinaryDecoder.h'
488 for i in ignore_list:
489 if line.startswith(i):
491 if i[0] == '*' and line.endswith(i[1:]):
493 if i[-1] == '*' and line.startswith(i[:-1]):
498 def fixup(includes, module):
499 """ Here we add any headers
500 necessary in the pch.
501 These could be known to be very
502 common but for technical reasons
503 left out of the pch by this generator.
504 Or, they could be missing from the
505 source files where they are used
506 (probably because they had been
507 in the old pch, they were missed).
508 Also, these could be headers
509 that make the build faster but
510 aren't added automatically.
514 # Add a space to exclude from
516 line = ' #include <{}>'.format(inc)
523 append('sal/config.h')
525 if module == 'basctl':
526 if 'basslots.hxx' in includes:
527 append('sfx2/msg.hxx')
530 # if 'scslots.hxx' in includes:
531 # append('sfx2/msg.hxx')
534 def sort_by_category(list, root, module, filter_local):
535 """ Move all 'system' headers first.
536 Core files of osl, rtl, sal, next.
537 Everything non-module-specific third.
538 Last, module-specific headers.
546 prefix = '<' + module + '/'
548 if 'sal/config.h' in i:
549 continue # added unconditionally in fixup
550 if is_c_runtime(i, root, module):
554 elif prefix in i or not '/' in i:
556 elif '<sal/' in i or '<vcl/' in i:
558 elif '<osl/' in i or '<rtl/' in i:
559 if module == "sal": # osl and rtl are also part of sal
563 # Headers from another module that is closely tied to the module.
564 elif module == 'sc' and '<formula' in i:
570 out += [ "#if PCH_LEVEL >= 1" ]
573 out += [ "#endif // PCH_LEVEL >= 1" ]
574 out += [ "#if PCH_LEVEL >= 2" ]
576 out += [ "#endif // PCH_LEVEL >= 2" ]
577 out += [ "#if PCH_LEVEL >= 3" ]
579 out += [ "#endif // PCH_LEVEL >= 3" ]
580 out += [ "#if PCH_LEVEL >= 4" ]
582 out += [ "#endif // PCH_LEVEL >= 4" ]
585 def parse_makefile(groups, lines, lineno, lastif, ifstack):
588 ingeneratedobjects = False
591 os_cond_re = re.compile(r'(ifeq|ifneq)\s*\(\$\(OS\)\,(\w*)\)')
594 if line.startswith('if'):
597 # Correction if first line is an if.
598 lineno = parse_makefile(groups, lines, lineno, line, ifstack+1)
602 while lineno + 1 < len(lines):
604 line = lines[lineno].strip()
605 line = line.rstrip('\\').strip()
606 #print('line #{}: {}'.format(lineno, line))
612 ingeneratedobjects = False
613 elif 'add_exception_objects' in line or \
614 'add_cxxobject' in line:
617 #if ifstack and not SILENT:
618 #sys.stderr.write('Sources in a conditional, ignoring for now.\n')
619 elif 'add_generated_exception_objects' in line or \
620 'add_generated_cxxobject' in line:
621 ingeneratedobjects = True
622 elif 'set_generated_cxx_suffix' in line:
623 suffix_re = re.compile('.*set_generated_cxx_suffix,[^,]*,([^)]*).*')
624 match = suffix_re.match(line)
626 suffix = match.group(1)
627 elif line.startswith('if'):
628 lineno = parse_makefile(groups, lines, lineno, line, ifstack+1)
630 elif line.startswith('endif'):
634 elif line.startswith('else'):
636 elif inobjects or ingeneratedobjects:
637 if EXCLUDE_SYSTEM and ifstack:
639 file = line + '.' + suffix
640 if ',' in line or '(' in line or ')' in line or file.startswith('-'):
641 #print('passing: ' + line)
642 pass # $if() probably, or something similar
646 if 'filter' in lastif:
647 # We can't grok filter, yet.
649 match = os_cond_re.match(lastif)
651 # We only support OS conditionals.
653 in_out = match.group(1)
654 osname = match.group(2) if match else ''
655 if (in_out == 'ifneq' and not inelse) or \
656 (in_out == 'ifeq' and inelse):
657 osname = '!' + osname
659 if osname not in groups:
661 if ingeneratedobjects:
662 file = WORKDIR + '/' + file
663 groups[osname].append(file)
667 def process_makefile(root, module, libname):
668 """ Parse a gmake makefile and extract
669 source filenames from it.
672 makefile = 'Library_{}.mk'.format(libname)
673 filename = os.path.join(os.path.join(root, module), makefile)
674 if not os.path.isfile(filename):
675 makefile = 'StaticLibrary_{}.mk'.format(libname)
676 filename = os.path.join(os.path.join(root, module), makefile)
677 if not os.path.isfile(filename):
678 sys.stderr.write('Error: Module {} has no makefile at {}.'.format(module, filename))
680 groups = {'':[], 'ANDROID':[], 'iOS':[], 'WNT':[], 'LINUX':[], 'MACOSX':[]}
682 with open(filename, 'r') as f:
683 lines = f.readlines()
684 groups = parse_makefile(groups, lines, lineno=0, lastif=None, ifstack=0)
688 def is_allowed_if(line, module):
689 """ Check whether the given #if condition
690 is allowed for the given module or whether
691 its block should be ignored.
694 # remove trailing comments
695 line = re.sub(r'(.*) *//.*', r'\1', line)
698 # Our sources always build with LIBO_INTERNAL_ONLY.
699 if line == "#if defined LIBO_INTERNAL_ONLY" or line == "#ifdef LIBO_INTERNAL_ONLY":
701 # We use PCHs only for C++.
702 if line == "#if defined(__cplusplus)" or line == "#if defined __cplusplus":
704 # Debug-specific code, it shouldn't hurt including it unconditionally.
705 if line == "#ifdef DBG_UTIL" or line == "#if OSL_DEBUG_LEVEL > 0":
707 if module == "external/skia":
708 # We always set these.
709 if line == "#ifdef SK_VULKAN" or line == "#if SK_SUPPORT_GPU":
713 def process_source(root, module, filename, maxdepth=0):
714 """ Process a source file to extract
716 For now, skip on compiler directives.
717 maxdepth is used when processing headers
718 which typically have protecting ifndef.
726 with open(filename, 'r') as f:
729 if line.startswith('#if'):
730 if is_allowed_if(line, module):
731 allowed_ifs.append(True)
734 allowed_ifs.append(False)
737 elif line.startswith('#endif'):
739 if allowed_ifs[ ifdepth ]:
743 del allowed_ifs[ ifdepth ]
744 elif line.startswith('#pragma once'):
745 # maxdepth == 1 means we are parsing a header file
746 # and are allowed one #ifdef block (the include guard),
747 # but in the #pragma once case do not allow that
750 elif line.startswith('#include'):
751 if ifdepth - ifsallowed <= maxdepth:
752 line = sanitize(line)
754 line = get_filename(line)
755 if line and len(line):
756 raw_includes.append(line)
758 sys.stderr.write('#include in {} : {}\n'.format(lastif, line))
762 def explode(root, module, includes, tree, filter_local, recurse):
763 incpath = os.path.join(root, 'include')
766 filename = get_filename(inc)
767 if filename in tree or len(filter_local.proc(filename)) == 0:
771 # Module or Local header.
772 filepath = filter_local.find_local_file(inc)
774 #print('trying loc: ' + filepath)
775 incs = process_source(root, module, filepath, maxdepth=1)
776 incs = map(get_filename, incs)
777 incs = process_list(incs, lambda x: filter_ignore(x, module))
778 incs = process_list(incs, filter_local.proc)
779 tree[filename] = incs
781 tree = explode(root, module, incs, tree, filter_local, recurse)
782 #print('{} => {}'.format(filepath, tree[filename]))
789 filepath = os.path.join(incpath, filename)
790 #print('trying pub: ' + filepath)
791 incs = process_source(root, module, filepath, maxdepth=1)
792 incs = map(get_filename, incs)
793 incs = process_list(incs, lambda x: filter_ignore(x, module))
794 incs = process_list(incs, filter_local.proc)
795 tree[filename] = incs
797 tree = explode(root, module, incs, tree, filter_local, recurse)
798 #print('{} => {}'.format(filepath, tree[filename]))
803 # Failed, but remember to avoid searching again.
808 def make_command_line():
810 # Remove command line flags and
811 # use internal flags.
812 for i in range(len(args)-1, 0, -1):
813 if args[i].startswith('--'):
816 args.append('--cutoff=' + str(CUTOFF))
818 args.append('--exclude:system')
820 args.append('--include:system')
822 args.append('--exclude:module')
824 args.append('--include:module')
826 args.append('--exclude:local')
828 args.append('--include:local')
830 return ' '.join(args)
832 def generate_includes(includes):
833 """Generates the include lines of the pch.
836 for osname, group in includes.items():
846 lines.append('#if {}defined({})'.format(not_eq, osname))
852 lines.append('#endif')
856 def generate(includes, libname, filename, module):
858 """/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
860 * This file is part of the LibreOffice project.
862 * This Source Code Form is subject to the terms of the Mozilla Public
863 * License, v. 2.0. If a copy of the MPL was not distributed with this
864 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
868 This file has been autogenerated by update_pch.sh. It is possible to edit it
869 manually (such as when an include file has been moved/renamed/removed). All such
870 manual changes will be rewritten by the next run of update_pch.sh (which presumably
871 also fixes all possible problems, so it's usually better to use it).
876 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
880 with open(filename, 'w') as f:
882 f.write('\n Generated on {} using:\n {}\n'.format(
883 datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
884 make_command_line()))
885 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(
888 # sal needs this for rand_s()
889 if module == 'sal' and libname == 'sal':
902 # Some libraries pull windows headers that aren't self contained.
903 if (module == 'connectivity' and libname == 'ado') or \
904 (module == 'xmlsecurity' and libname == 'xsec_xmlsec'):
906 // Cleanup windows header macro pollution.
907 #if defined(_WIN32) && defined(WINAPI)
916 def remove_from_tree(filename, tree):
917 # Remove this file, if top-level.
918 incs = tree.pop(filename, [])
920 tree = remove_from_tree(i, tree)
922 # Also remove if included from another.
923 for (k, v) in tree.items():
929 def tree_to_list(includes, filename, tree):
930 if filename in includes:
932 includes.append(filename)
933 #incs = tree.pop(filename, [])
934 incs = tree[filename] if filename in tree else []
936 tree_to_list(includes, i, tree)
940 def promote(includes):
941 """ Common library headers are heavily
942 referenced, even if they are included
944 Here we separate them to promote
945 their inclusion in the final pch.
949 if inc.startswith('boost') or \
950 inc.startswith('sal') or \
951 inc.startswith('osl') or \
952 inc.startswith('rtl'):
956 def make_pch_filename(root, module, libname):
957 """ PCH files are stored here:
958 <root>/<module>/inc/pch/precompiled_<libname>.hxx
961 path = os.path.join(root, module)
962 path = os.path.join(path, 'inc')
963 path = os.path.join(path, 'pch')
964 path = os.path.join(path, 'precompiled_' + libname + '.hxx')
970 global EXCLUDE_MODULE
972 global EXCLUDE_SYSTEM
976 if os.getenv('WORKDIR'):
977 WORKDIR = os.getenv('WORKDIR')
981 libname = sys.argv[2]
982 header = make_pch_filename(root, module, libname)
984 if not os.path.exists(os.path.join(root, module)):
985 raise Exception('Error: module [{}] not found.'.format(module))
987 key = '{}.{}'.format(module, libname)
989 # Load the module-specific defaults.
990 CUTOFF = DEFAULTS[key][0]
991 EXCLUDE_SYSTEM = DEFAULTS[key][1]
992 EXCLUDE_MODULE = DEFAULTS[key][2]
993 EXCLUDE_LOCAL = DEFAULTS[key][3]
996 for x in range(3, len(sys.argv)):
998 if i.startswith('--cutoff='):
999 CUTOFF = int(i.split('=')[1])
1000 elif i.startswith('--exclude:'):
1001 cat = i.split(':')[1]
1003 EXCLUDE_MODULE = True
1004 elif cat == 'local':
1005 EXCLUDE_LOCAL = True
1006 elif cat == 'system':
1007 EXCLUDE_SYSTEM = True
1008 elif i.startswith('--include:'):
1009 cat = i.split(':')[1]
1011 EXCLUDE_MODULE = False
1012 elif cat == 'local':
1013 EXCLUDE_LOCAL = False
1014 elif cat == 'system':
1015 EXCLUDE_SYSTEM = False
1016 elif i == '--silent':
1018 elif i == '--force':
1021 sys.stderr.write('Unknown option [{}].'.format(i))
1024 filter_local = Filter_Local(root, module, \
1025 not EXCLUDE_MODULE, \
1029 groups = process_makefile(root, module, libname)
1032 for osname, group in groups.items():
1037 for filename in group:
1038 includes += process_source(root, module, filename)
1040 # Save unique top-level includes.
1041 unique = set(includes)
1042 promoted = promote(unique)
1045 includes = remove_rare(includes)
1046 includes = process_list(includes, lambda x: filter_ignore(x, module))
1047 includes = process_list(includes, filter_local.proc)
1049 # Remove the already included ones.
1050 for inc in includes:
1053 # Explode the excluded ones.
1054 tree = {i:[] for i in includes}
1055 tree = explode(root, module, unique, tree, filter_local, not EXCLUDE_MODULE)
1057 # Remove the already included ones from the tree.
1058 for inc in includes:
1059 filename = get_filename(inc)
1060 tree = remove_from_tree(filename, tree)
1063 for (k, v) in tree.items():
1064 extra += tree_to_list([], k, tree)
1066 promoted += promote(extra)
1067 promoted = process_list(promoted, lambda x: filter_ignore(x, module))
1068 promoted = process_list(promoted, filter_local.proc)
1069 promoted = set(promoted)
1070 # If a promoted header includes others, remove the rest.
1071 for (k, v) in tree.items():
1075 includes += [x for x in promoted]
1077 extra = remove_rare(extra)
1078 extra = process_list(extra, lambda x: filter_ignore(x, module))
1079 extra = process_list(extra, filter_local.proc)
1082 includes = [x for x in set(includes)]
1083 fixes = fixup(includes, module)
1084 fixes = map(lambda x: '#include <' + x + '>', fixes)
1086 includes = map(lambda x: '#include <' + x + '>', includes)
1087 sorted = sort_by_category(includes, root, module, filter_local)
1088 includes = list(fixes) + sorted
1095 groups[osname] = includes
1099 # Open the old pch and compare its contents
1100 # with new includes.
1101 # Clobber only if they are different.
1102 with open(header, 'r') as f:
1103 old_pch_lines = [x.strip() for x in f.readlines()]
1104 new_lines = generate_includes(groups)
1105 # Find the first include in the old pch.
1107 for i in range(len(old_pch_lines)):
1108 if old_pch_lines[i].startswith('#include') or old_pch_lines[i].startswith('#if PCH_LEVEL'):
1111 # Clobber if there is a mismatch.
1112 if force_update or start < 0 or (len(old_pch_lines) - start < len(new_lines)):
1113 generate(new_lines, libname, header, module)
1116 for i in range(len(new_lines)):
1117 if new_lines[i] != old_pch_lines[start + i]:
1118 generate(new_lines, libname, header, module)
1121 # Identical, but see if new pch removed anything.
1122 for i in range(start + len(new_lines), len(old_pch_lines)):
1123 if '#include' in old_pch_lines[i]:
1124 generate(new_lines, libname, header, module)
1128 # Use exit code 2 to distinguish it from exit code 1 used e.g. when an exception occurs.
1131 if __name__ == '__main__':
1132 """ Process all the includes in a Module
1133 to make into a PCH file.
1134 Run without arguments for unittests,
1138 if len(sys.argv) >= 3:
1142 print('Usage: {} <Module name> <Library name> [options]'.format(sys.argv[0]))
1143 print(' Always run from the root of LO repository.\n')
1145 print(' --cutoff=<count> - Threshold to excluding headers.')
1146 print(' --exclude:<category> - Exclude category-specific headers.')
1147 print(' --include:<category> - Include category-specific headers.')
1148 print(' --force - Force updating the pch even when nothing changes.')
1149 print(' Categories:')
1150 print(' module - Headers in /inc directory of a module.')
1151 print(' local - Headers local to a source file.')
1152 print(' system - Platform-specific headers.')
1153 print(' --silent - print only errors.')
1154 print('\nRunning unit-tests...')
1157 class TestMethods(unittest.TestCase):
1159 def test_sanitize(self):
1160 self.assertEqual(sanitize('#include "blah/file.cxx"'),
1161 '#include <blah/file.cxx>')
1162 self.assertEqual(sanitize(' #include\t"blah/file.cxx" '),
1163 '#include <blah/file.cxx>')
1164 self.assertEqual(sanitize(' '),
1167 def test_filter_ignore(self):
1168 self.assertEqual(filter_ignore('blah/file.cxx', 'mod'),
1170 self.assertEqual(filter_ignore('vector', 'mod'),
1172 self.assertEqual(filter_ignore('file.cxx', 'mod'),
1175 def test_remove_rare(self):
1176 self.assertEqual(remove_rare([]),
1179 class TestMakefileParser(unittest.TestCase):
1182 global EXCLUDE_SYSTEM
1183 EXCLUDE_SYSTEM = False
1185 def test_parse_singleline_eval(self):
1186 source = "$(eval $(call gb_Library_Library,sal))"
1187 lines = source.split('\n')
1189 groups = parse_makefile(groups, lines, 0, None, 0)
1190 self.assertEqual(len(groups), 1)
1191 self.assertEqual(len(groups['']), 0)
1193 def test_parse_multiline_eval(self):
1194 source = """$(eval $(call gb_Library_set_include,sal,\\
1196 -I$(SRCDIR)/sal/inc \\
1199 lines = source.split('\n')
1201 groups = parse_makefile(groups, lines, 0, None, 0)
1202 self.assertEqual(len(groups), 1)
1203 self.assertEqual(len(groups['']), 0)
1205 def test_parse_multiline_eval_with_if(self):
1206 source = """$(eval $(call gb_Library_add_defs,sal,\\
1207 $(if $(filter $(OS),iOS), \\
1208 -DNO_CHILD_PROCESSES \\
1212 lines = source.split('\n')
1214 groups = parse_makefile(groups, lines, 0, None, 0)
1215 self.assertEqual(len(groups), 1)
1216 self.assertEqual(len(groups['']), 0)
1218 def test_parse_multiline_add_with_if(self):
1219 source = """$(eval $(call gb_Library_add_exception_objects,sal,\\
1221 $(if $(filter DESKTOP,$(BUILD_TYPE)), sal/osl/unx/salinit) \\
1224 lines = source.split('\n')
1226 groups = parse_makefile(groups, lines, 0, None, 0)
1227 self.assertEqual(len(groups), 1)
1228 self.assertEqual(len(groups['']), 1)
1229 self.assertEqual(groups[''][0], 'sal/osl/unx/time.cxx')
1231 def test_parse_if_else(self):
1232 source = """ifeq ($(OS),MACOSX)
1233 $(eval $(call gb_Library_add_exception_objects,sal,\\
1237 $(eval $(call gb_Library_add_exception_objects,sal,\\
1238 sal/osl/unx/uunxapi \\
1242 lines = source.split('\n')
1244 groups = parse_makefile(groups, lines, 0, None, 0)
1245 self.assertEqual(len(groups), 3)
1246 self.assertEqual(len(groups['']), 0)
1247 self.assertEqual(len(groups['MACOSX']), 1)
1248 self.assertEqual(len(groups['!MACOSX']), 1)
1249 self.assertEqual(groups['MACOSX'][0], 'sal/osl/mac/mac.cxx')
1250 self.assertEqual(groups['!MACOSX'][0], 'sal/osl/unx/uunxapi.cxx')
1252 def test_parse_nested_if(self):
1253 source = """ifeq ($(OS),MACOSX)
1254 $(eval $(call gb_Library_add_exception_objects,sal,\\
1258 $(eval $(call gb_Library_add_exception_objects,sal,\\
1259 sal/osl/unx/uunxapi \\
1263 $(eval $(call gb_Library_add_exception_objects,sal,\\
1264 sal/textenc/context \\
1269 lines = source.split('\n')
1271 groups = parse_makefile(groups, lines, 0, None, 0)
1272 self.assertEqual(len(groups), 4)
1273 self.assertEqual(len(groups['']), 0)
1274 self.assertEqual(len(groups['MACOSX']), 1)
1275 self.assertEqual(len(groups['!MACOSX']), 1)
1276 self.assertEqual(len(groups['LINUX']), 1)
1277 self.assertEqual(groups['MACOSX'][0], 'sal/osl/mac/mac.cxx')
1278 self.assertEqual(groups['!MACOSX'][0], 'sal/osl/unx/uunxapi.cxx')
1279 self.assertEqual(groups['LINUX'][0], 'sal/textenc/context.cxx')
1281 def test_parse_exclude_system(self):
1282 source = """ifeq ($(OS),MACOSX)
1283 $(eval $(call gb_Library_add_exception_objects,sal,\\
1287 $(eval $(call gb_Library_add_exception_objects,sal,\\
1288 sal/osl/unx/uunxapi \\
1292 $(eval $(call gb_Library_add_exception_objects,sal,\\
1293 sal/textenc/context \\
1298 global EXCLUDE_SYSTEM
1299 EXCLUDE_SYSTEM = True
1301 lines = source.split('\n')
1303 groups = parse_makefile(groups, lines, 0, None, 0)
1304 self.assertEqual(len(groups), 1)
1305 self.assertEqual(len(groups['']), 0)
1307 def test_parse_filter(self):
1308 source = """ifneq ($(filter $(OS),MACOSX iOS),)
1309 $(eval $(call gb_Library_add_exception_objects,sal,\\
1310 sal/osl/unx/osxlocale \\
1314 # Filter is still unsupported.
1315 lines = source.split('\n')
1317 groups = parse_makefile(groups, lines, 0, None, 0)
1318 self.assertEqual(len(groups), 1)
1319 self.assertEqual(len(groups['']), 0)
1323 # vim: set et sw=4 ts=4 expandtab: