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 brackets
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',
477 'src/sksl/', # conflict between SkSL::Expression and SkSL::dsl::Expression
482 if module == 'external/zxing':
484 'rss/ODRSSExpandedBinaryDecoder.h'
487 for i in ignore_list:
488 if line.startswith(i):
490 if i[0] == '*' and line.endswith(i[1:]):
492 if i[-1] == '*' and line.startswith(i[:-1]):
497 def fixup(includes, module):
498 """ Here we add any headers
499 necessary in the pch.
500 These could be known to be very
501 common but for technical reasons
502 left out of the pch by this generator.
503 Or, they could be missing from the
504 source files where they are used
505 (probably because they had been
506 in the old pch, they were missed).
507 Also, these could be headers
508 that make the build faster but
509 aren't added automatically.
513 # Add a space to exclude from
515 line = ' #include <{}>'.format(inc)
522 append('sal/config.h')
524 if module == 'basctl':
525 if 'basslots.hxx' in includes:
526 append('sfx2/msg.hxx')
529 # if 'scslots.hxx' in includes:
530 # append('sfx2/msg.hxx')
533 def sort_by_category(list, root, module, filter_local):
534 """ Move all 'system' headers first.
535 Core files of osl, rtl, sal, next.
536 Everything non-module-specific third.
537 Last, module-specific headers.
545 prefix = '<' + module + '/'
547 if 'sal/config.h' in i:
548 continue # added unconditionally in fixup
549 if is_c_runtime(i, root, module):
553 elif prefix in i or not '/' in i:
555 elif '<sal/' in i or '<vcl/' in i:
557 elif '<osl/' in i or '<rtl/' in i:
558 if module == "sal": # osl and rtl are also part of sal
562 # Headers from another module that is closely tied to the module.
563 elif module == 'sc' and '<formula' in i:
569 out += [ "#if PCH_LEVEL >= 1" ]
572 out += [ "#endif // PCH_LEVEL >= 1" ]
573 out += [ "#if PCH_LEVEL >= 2" ]
575 out += [ "#endif // PCH_LEVEL >= 2" ]
576 out += [ "#if PCH_LEVEL >= 3" ]
578 out += [ "#endif // PCH_LEVEL >= 3" ]
579 out += [ "#if PCH_LEVEL >= 4" ]
581 out += [ "#endif // PCH_LEVEL >= 4" ]
584 def parse_makefile(groups, lines, lineno, lastif, ifstack):
587 ingeneratedobjects = False
590 os_cond_re = re.compile(r'(ifeq|ifneq)\s*\(\$\(OS\)\,(\w*)\)')
593 if line.startswith('if'):
596 # Correction if first line is an if.
597 lineno = parse_makefile(groups, lines, lineno, line, ifstack+1)
601 while lineno + 1 < len(lines):
603 line = lines[lineno].strip()
604 line = line.rstrip('\\').strip()
605 #print('line #{}: {}'.format(lineno, line))
611 ingeneratedobjects = False
612 elif 'add_exception_objects' in line or \
613 'add_cxxobject' in line:
616 #if ifstack and not SILENT:
617 #sys.stderr.write('Sources in a conditional, ignoring for now.\n')
618 elif 'add_generated_exception_objects' in line or \
619 'add_generated_cxxobject' in line:
620 ingeneratedobjects = True
621 elif 'set_generated_cxx_suffix' in line:
622 suffix_re = re.compile('.*set_generated_cxx_suffix,[^,]*,([^)]*).*')
623 match = suffix_re.match(line)
625 suffix = match.group(1)
626 elif line.startswith('if'):
627 lineno = parse_makefile(groups, lines, lineno, line, ifstack+1)
629 elif line.startswith('endif'):
633 elif line.startswith('else'):
635 elif inobjects or ingeneratedobjects:
636 if EXCLUDE_SYSTEM and ifstack:
638 file = line + '.' + suffix
639 if ',' in line or '(' in line or ')' in line or file.startswith('-'):
640 #print('passing: ' + line)
641 pass # $if() probably, or something similar
645 if 'filter' in lastif:
646 # We can't grok filter, yet.
648 match = os_cond_re.match(lastif)
650 # We only support OS conditionals.
652 in_out = match.group(1)
653 osname = match.group(2) if match else ''
654 if (in_out == 'ifneq' and not inelse) or \
655 (in_out == 'ifeq' and inelse):
656 osname = '!' + osname
658 if osname not in groups:
660 if ingeneratedobjects:
661 file = WORKDIR + '/' + file
662 groups[osname].append(file)
666 def process_makefile(root, module, libname):
667 """ Parse a gmake makefile and extract
668 source filenames from it.
671 makefile = 'Library_{}.mk'.format(libname)
672 filename = os.path.join(os.path.join(root, module), makefile)
673 if not os.path.isfile(filename):
674 makefile = 'StaticLibrary_{}.mk'.format(libname)
675 filename = os.path.join(os.path.join(root, module), makefile)
676 if not os.path.isfile(filename):
677 sys.stderr.write('Error: Module {} has no makefile at {}.'.format(module, filename))
679 groups = {'':[], 'ANDROID':[], 'iOS':[], 'WNT':[], 'LINUX':[], 'MACOSX':[]}
681 with open(filename, 'r') as f:
682 lines = f.readlines()
683 groups = parse_makefile(groups, lines, lineno=0, lastif=None, ifstack=0)
687 def is_allowed_if(line, module):
688 """ Check whether the given #if condition
689 is allowed for the given module or whether
690 its block should be ignored.
693 # remove trailing comments
694 line = re.sub(r'(.*) *//.*', r'\1', line)
697 # Our sources always build with LIBO_INTERNAL_ONLY.
698 if line == "#if defined LIBO_INTERNAL_ONLY" or line == "#ifdef LIBO_INTERNAL_ONLY":
700 # We use PCHs only for C++.
701 if line == "#if defined(__cplusplus)" or line == "#if defined __cplusplus":
703 # Debug-specific code, it shouldn't hurt including it unconditionally.
704 if line == "#ifdef DBG_UTIL" or line == "#if OSL_DEBUG_LEVEL > 0":
706 if module == "external/skia":
707 # We always set these.
708 if line == "#ifdef SK_VULKAN" or line == "#if SK_SUPPORT_GPU":
712 def process_source(root, module, filename, maxdepth=0):
713 """ Process a source file to extract
715 For now, skip on compiler directives.
716 maxdepth is used when processing headers
717 which typically have protecting ifndef.
725 with open(filename, 'r') as f:
728 if line.startswith('#if'):
729 if is_allowed_if(line, module):
730 allowed_ifs.append(True)
733 allowed_ifs.append(False)
736 elif line.startswith('#endif'):
738 if allowed_ifs[ ifdepth ]:
742 del allowed_ifs[ ifdepth ]
743 elif line.startswith('#pragma once'):
744 # maxdepth == 1 means we are parsing a header file
745 # and are allowed one #ifdef block (the include guard),
746 # but in the #pragma once case do not allow that
749 elif line.startswith('#include'):
750 if ifdepth - ifsallowed <= maxdepth:
751 line = sanitize(line)
753 line = get_filename(line)
754 if line and len(line):
755 raw_includes.append(line)
757 sys.stderr.write('#include in {} : {}\n'.format(lastif, line))
761 def explode(root, module, includes, tree, filter_local, recurse):
762 incpath = os.path.join(root, 'include')
765 filename = get_filename(inc)
766 if filename in tree or len(filter_local.proc(filename)) == 0:
770 # Module or Local header.
771 filepath = filter_local.find_local_file(inc)
773 #print('trying loc: ' + filepath)
774 incs = process_source(root, module, filepath, maxdepth=1)
775 incs = map(get_filename, incs)
776 incs = process_list(incs, lambda x: filter_ignore(x, module))
777 incs = process_list(incs, filter_local.proc)
778 tree[filename] = incs
780 tree = explode(root, module, incs, tree, filter_local, recurse)
781 #print('{} => {}'.format(filepath, tree[filename]))
788 filepath = os.path.join(incpath, filename)
789 #print('trying pub: ' + filepath)
790 incs = process_source(root, module, filepath, maxdepth=1)
791 incs = map(get_filename, incs)
792 incs = process_list(incs, lambda x: filter_ignore(x, module))
793 incs = process_list(incs, filter_local.proc)
794 tree[filename] = incs
796 tree = explode(root, module, incs, tree, filter_local, recurse)
797 #print('{} => {}'.format(filepath, tree[filename]))
802 # Failed, but remember to avoid searching again.
807 def make_command_line():
809 # Remove command line flags and
810 # use internal flags.
811 for i in range(len(args)-1, 0, -1):
812 if args[i].startswith('--'):
815 args.append('--cutoff=' + str(CUTOFF))
817 args.append('--exclude:system')
819 args.append('--include:system')
821 args.append('--exclude:module')
823 args.append('--include:module')
825 args.append('--exclude:local')
827 args.append('--include:local')
829 return ' '.join(args)
831 def generate_includes(includes):
832 """Generates the include lines of the pch.
835 for osname, group in includes.items():
845 lines.append('#if {}defined({})'.format(not_eq, osname))
851 lines.append('#endif')
855 def generate(includes, libname, filename, module):
857 """/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
859 * This file is part of the LibreOffice project.
861 * This Source Code Form is subject to the terms of the Mozilla Public
862 * License, v. 2.0. If a copy of the MPL was not distributed with this
863 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
867 This file has been autogenerated by update_pch.sh. It is possible to edit it
868 manually (such as when an include file has been moved/renamed/removed). All such
869 manual changes will be rewritten by the next run of update_pch.sh (which presumably
870 also fixes all possible problems, so it's usually better to use it).
875 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
879 with open(filename, 'w') as f:
881 f.write('\n Generated on {} using:\n {}\n'.format(
882 datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
883 make_command_line()))
884 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(
887 # sal needs this for rand_s()
888 if module == 'sal' and libname == 'sal':
891 #if !defined _CRT_RAND_S
903 # Some libraries pull windows headers that aren't self contained.
904 if (module == 'connectivity' and libname == 'ado') or \
905 (module == 'xmlsecurity' and libname == 'xsec_xmlsec'):
907 // Cleanup windows header macro pollution.
908 #if defined(_WIN32) && defined(WINAPI)
917 def remove_from_tree(filename, tree):
918 # Remove this file, if top-level.
919 incs = tree.pop(filename, [])
921 tree = remove_from_tree(i, tree)
923 # Also remove if included from another.
924 for (k, v) in tree.items():
930 def tree_to_list(includes, filename, tree):
931 if filename in includes:
933 includes.append(filename)
934 #incs = tree.pop(filename, [])
935 incs = tree[filename] if filename in tree else []
937 tree_to_list(includes, i, tree)
941 def promote(includes):
942 """ Common library headers are heavily
943 referenced, even if they are included
945 Here we separate them to promote
946 their inclusion in the final pch.
950 if inc.startswith('boost') or \
951 inc.startswith('sal') or \
952 inc.startswith('osl') or \
953 inc.startswith('rtl'):
957 def make_pch_filename(root, module, libname):
958 """ PCH files are stored here:
959 <root>/<module>/inc/pch/precompiled_<libname>.hxx
962 path = os.path.join(root, module)
963 path = os.path.join(path, 'inc')
964 path = os.path.join(path, 'pch')
965 path = os.path.join(path, 'precompiled_' + libname + '.hxx')
971 global EXCLUDE_MODULE
973 global EXCLUDE_SYSTEM
977 if os.getenv('WORKDIR'):
978 WORKDIR = os.getenv('WORKDIR')
982 libname = sys.argv[2]
983 header = make_pch_filename(root, module, libname)
985 if not os.path.exists(os.path.join(root, module)):
986 raise Exception('Error: module [{}] not found.'.format(module))
988 key = '{}.{}'.format(module, libname)
990 # Load the module-specific defaults.
991 CUTOFF = DEFAULTS[key][0]
992 EXCLUDE_SYSTEM = DEFAULTS[key][1]
993 EXCLUDE_MODULE = DEFAULTS[key][2]
994 EXCLUDE_LOCAL = DEFAULTS[key][3]
997 for x in range(3, len(sys.argv)):
999 if i.startswith('--cutoff='):
1000 CUTOFF = int(i.split('=')[1])
1001 elif i.startswith('--exclude:'):
1002 cat = i.split(':')[1]
1004 EXCLUDE_MODULE = True
1005 elif cat == 'local':
1006 EXCLUDE_LOCAL = True
1007 elif cat == 'system':
1008 EXCLUDE_SYSTEM = True
1009 elif i.startswith('--include:'):
1010 cat = i.split(':')[1]
1012 EXCLUDE_MODULE = False
1013 elif cat == 'local':
1014 EXCLUDE_LOCAL = False
1015 elif cat == 'system':
1016 EXCLUDE_SYSTEM = False
1017 elif i == '--silent':
1019 elif i == '--force':
1022 sys.stderr.write('Unknown option [{}].'.format(i))
1025 filter_local = Filter_Local(root, module, \
1026 not EXCLUDE_MODULE, \
1030 groups = process_makefile(root, module, libname)
1033 for osname, group in groups.items():
1038 for filename in group:
1039 includes += process_source(root, module, filename)
1041 # Save unique top-level includes.
1042 unique = set(includes)
1043 promoted = promote(unique)
1046 includes = remove_rare(includes)
1047 includes = process_list(includes, lambda x: filter_ignore(x, module))
1048 includes = process_list(includes, filter_local.proc)
1050 # Remove the already included ones.
1051 for inc in includes:
1054 # Explode the excluded ones.
1055 tree = {i:[] for i in includes}
1056 tree = explode(root, module, unique, tree, filter_local, not EXCLUDE_MODULE)
1058 # Remove the already included ones from the tree.
1059 for inc in includes:
1060 filename = get_filename(inc)
1061 tree = remove_from_tree(filename, tree)
1064 for (k, v) in tree.items():
1065 extra += tree_to_list([], k, tree)
1067 promoted += promote(extra)
1068 promoted = process_list(promoted, lambda x: filter_ignore(x, module))
1069 promoted = process_list(promoted, filter_local.proc)
1070 promoted = set(promoted)
1071 # If a promoted header includes others, remove the rest.
1072 for (k, v) in tree.items():
1076 includes += [x for x in promoted]
1078 extra = remove_rare(extra)
1079 extra = process_list(extra, lambda x: filter_ignore(x, module))
1080 extra = process_list(extra, filter_local.proc)
1083 includes = [x for x in set(includes)]
1084 fixes = fixup(includes, module)
1085 fixes = map(lambda x: '#include <' + x + '>', fixes)
1087 includes = map(lambda x: '#include <' + x + '>', includes)
1088 sorted = sort_by_category(includes, root, module, filter_local)
1089 includes = list(fixes) + sorted
1096 groups[osname] = includes
1100 # Open the old pch and compare its contents
1101 # with new includes.
1102 # Clobber only if they are different.
1103 with open(header, 'r') as f:
1104 old_pch_lines = [x.strip() for x in f.readlines()]
1105 new_lines = generate_includes(groups)
1106 # Find the first include in the old pch.
1108 for i in range(len(old_pch_lines)):
1109 if old_pch_lines[i].startswith('#include') or old_pch_lines[i].startswith('#if PCH_LEVEL'):
1112 # Clobber if there is a mismatch.
1113 if force_update or start < 0 or (len(old_pch_lines) - start < len(new_lines)):
1114 generate(new_lines, libname, header, module)
1117 for i in range(len(new_lines)):
1118 if new_lines[i] != old_pch_lines[start + i]:
1119 generate(new_lines, libname, header, module)
1122 # Identical, but see if new pch removed anything.
1123 for i in range(start + len(new_lines), len(old_pch_lines)):
1124 if '#include' in old_pch_lines[i]:
1125 generate(new_lines, libname, header, module)
1129 # Use exit code 2 to distinguish it from exit code 1 used e.g. when an exception occurs.
1132 if __name__ == '__main__':
1133 """ Process all the includes in a Module
1134 to make into a PCH file.
1135 Run without arguments for unittests,
1139 if len(sys.argv) >= 3:
1143 print('Usage: {} <Module name> <Library name> [options]'.format(sys.argv[0]))
1144 print(' Always run from the root of LO repository.\n')
1146 print(' --cutoff=<count> - Threshold to excluding headers.')
1147 print(' --exclude:<category> - Exclude category-specific headers.')
1148 print(' --include:<category> - Include category-specific headers.')
1149 print(' --force - Force updating the pch even when nothing changes.')
1150 print(' Categories:')
1151 print(' module - Headers in /inc directory of a module.')
1152 print(' local - Headers local to a source file.')
1153 print(' system - Platform-specific headers.')
1154 print(' --silent - print only errors.')
1155 print('\nRunning unit-tests...')
1158 class TestMethods(unittest.TestCase):
1160 def test_sanitize(self):
1161 self.assertEqual(sanitize('#include "blah/file.cxx"'),
1162 '#include <blah/file.cxx>')
1163 self.assertEqual(sanitize(' #include\t"blah/file.cxx" '),
1164 '#include <blah/file.cxx>')
1165 self.assertEqual(sanitize(' '),
1168 def test_filter_ignore(self):
1169 self.assertEqual(filter_ignore('blah/file.cxx', 'mod'),
1171 self.assertEqual(filter_ignore('vector', 'mod'),
1173 self.assertEqual(filter_ignore('file.cxx', 'mod'),
1176 def test_remove_rare(self):
1177 self.assertEqual(remove_rare([]),
1180 class TestMakefileParser(unittest.TestCase):
1183 global EXCLUDE_SYSTEM
1184 EXCLUDE_SYSTEM = False
1186 def test_parse_singleline_eval(self):
1187 source = "$(eval $(call gb_Library_Library,sal))"
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_eval(self):
1195 source = """$(eval $(call gb_Library_set_include,sal,\\
1197 -I$(SRCDIR)/sal/inc \\
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['']), 0)
1206 def test_parse_multiline_eval_with_if(self):
1207 source = """$(eval $(call gb_Library_add_defs,sal,\\
1208 $(if $(filter $(OS),iOS), \\
1209 -DNO_CHILD_PROCESSES \\
1213 lines = source.split('\n')
1215 groups = parse_makefile(groups, lines, 0, None, 0)
1216 self.assertEqual(len(groups), 1)
1217 self.assertEqual(len(groups['']), 0)
1219 def test_parse_multiline_add_with_if(self):
1220 source = """$(eval $(call gb_Library_add_exception_objects,sal,\\
1222 $(if $(filter DESKTOP,$(BUILD_TYPE)), sal/osl/unx/salinit) \\
1225 lines = source.split('\n')
1227 groups = parse_makefile(groups, lines, 0, None, 0)
1228 self.assertEqual(len(groups), 1)
1229 self.assertEqual(len(groups['']), 1)
1230 self.assertEqual(groups[''][0], 'sal/osl/unx/time.cxx')
1232 def test_parse_if_else(self):
1233 source = """ifeq ($(OS),MACOSX)
1234 $(eval $(call gb_Library_add_exception_objects,sal,\\
1238 $(eval $(call gb_Library_add_exception_objects,sal,\\
1239 sal/osl/unx/uunxapi \\
1243 lines = source.split('\n')
1245 groups = parse_makefile(groups, lines, 0, None, 0)
1246 self.assertEqual(len(groups), 3)
1247 self.assertEqual(len(groups['']), 0)
1248 self.assertEqual(len(groups['MACOSX']), 1)
1249 self.assertEqual(len(groups['!MACOSX']), 1)
1250 self.assertEqual(groups['MACOSX'][0], 'sal/osl/mac/mac.cxx')
1251 self.assertEqual(groups['!MACOSX'][0], 'sal/osl/unx/uunxapi.cxx')
1253 def test_parse_nested_if(self):
1254 source = """ifeq ($(OS),MACOSX)
1255 $(eval $(call gb_Library_add_exception_objects,sal,\\
1259 $(eval $(call gb_Library_add_exception_objects,sal,\\
1260 sal/osl/unx/uunxapi \\
1264 $(eval $(call gb_Library_add_exception_objects,sal,\\
1265 sal/textenc/context \\
1270 lines = source.split('\n')
1272 groups = parse_makefile(groups, lines, 0, None, 0)
1273 self.assertEqual(len(groups), 4)
1274 self.assertEqual(len(groups['']), 0)
1275 self.assertEqual(len(groups['MACOSX']), 1)
1276 self.assertEqual(len(groups['!MACOSX']), 1)
1277 self.assertEqual(len(groups['LINUX']), 1)
1278 self.assertEqual(groups['MACOSX'][0], 'sal/osl/mac/mac.cxx')
1279 self.assertEqual(groups['!MACOSX'][0], 'sal/osl/unx/uunxapi.cxx')
1280 self.assertEqual(groups['LINUX'][0], 'sal/textenc/context.cxx')
1282 def test_parse_exclude_system(self):
1283 source = """ifeq ($(OS),MACOSX)
1284 $(eval $(call gb_Library_add_exception_objects,sal,\\
1288 $(eval $(call gb_Library_add_exception_objects,sal,\\
1289 sal/osl/unx/uunxapi \\
1293 $(eval $(call gb_Library_add_exception_objects,sal,\\
1294 sal/textenc/context \\
1299 global EXCLUDE_SYSTEM
1300 EXCLUDE_SYSTEM = True
1302 lines = source.split('\n')
1304 groups = parse_makefile(groups, lines, 0, None, 0)
1305 self.assertEqual(len(groups), 1)
1306 self.assertEqual(len(groups['']), 0)
1308 def test_parse_filter(self):
1309 source = """ifneq ($(filter $(OS),MACOSX iOS),)
1310 $(eval $(call gb_Library_add_exception_objects,sal,\\
1311 sal/osl/unx/osxlocale \\
1315 # Filter is still unsupported.
1316 lines = source.split('\n')
1318 groups = parse_makefile(groups, lines, 0, None, 0)
1319 self.assertEqual(len(groups), 1)
1320 self.assertEqual(len(groups['']), 0)
1324 # vim: set et sw=4 ts=4 expandtab: