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
483 for i in ignore_list:
484 if line.startswith(i):
486 if i[0] == '*' and line.endswith(i[1:]):
488 if i[-1] == '*' and line.startswith(i[:-1]):
493 def fixup(includes, module):
494 """ Here we add any headers
495 necessary in the pch.
496 These could be known to be very
497 common but for technical reasons
498 left out of the pch by this generator.
499 Or, they could be missing from the
500 source files where they are used
501 (probably because they had been
502 in the old pch, they were missed).
503 Also, these could be headers
504 that make the build faster but
505 aren't added automatically.
509 # Add a space to exclude from
511 line = ' #include <{}>'.format(inc)
518 append('sal/config.h')
520 if module == 'basctl':
521 if 'basslots.hxx' in includes:
522 append('sfx2/msg.hxx')
525 # if 'scslots.hxx' in includes:
526 # append('sfx2/msg.hxx')
529 def sort_by_category(list, root, module, filter_local):
530 """ Move all 'system' headers first.
531 Core files of osl, rtl, sal, next.
532 Everything non-module-specific third.
533 Last, module-specific headers.
541 prefix = '<' + module + '/'
543 if 'sal/config.h' in i:
544 continue # added unconditionally in fixup
545 if is_c_runtime(i, root, module):
549 elif prefix in i or not '/' in i:
551 elif '<sal/' in i or '<vcl/' in i:
553 elif '<osl/' in i or '<rtl/' in i:
554 if module == "sal": # osl and rtl are also part of sal
558 # Headers from another module that is closely tied to the module.
559 elif module == 'sc' and '<formula' in i:
565 out += [ "#if PCH_LEVEL >= 1" ]
568 out += [ "#endif // PCH_LEVEL >= 1" ]
569 out += [ "#if PCH_LEVEL >= 2" ]
571 out += [ "#endif // PCH_LEVEL >= 2" ]
572 out += [ "#if PCH_LEVEL >= 3" ]
574 out += [ "#endif // PCH_LEVEL >= 3" ]
575 out += [ "#if PCH_LEVEL >= 4" ]
577 out += [ "#endif // PCH_LEVEL >= 4" ]
580 def parse_makefile(groups, lines, lineno, lastif, ifstack):
583 ingeneratedobjects = False
586 os_cond_re = re.compile(r'(ifeq|ifneq)\s*\(\$\(OS\)\,(\w*)\)')
589 if line.startswith('if'):
592 # Correction if first line is an if.
593 lineno = parse_makefile(groups, lines, lineno, line, ifstack+1)
597 while lineno + 1 < len(lines):
599 line = lines[lineno].strip()
600 line = line.rstrip('\\').strip()
601 #print('line #{}: {}'.format(lineno, line))
607 ingeneratedobjects = False
608 elif 'add_exception_objects' in line or \
609 'add_cxxobject' in line:
612 #if ifstack and not SILENT:
613 #sys.stderr.write('Sources in a conditional, ignoring for now.\n')
614 elif 'add_generated_exception_objects' in line or \
615 'add_generated_cxxobject' in line:
616 ingeneratedobjects = True
617 elif 'set_generated_cxx_suffix' in line:
618 suffix_re = re.compile('.*set_generated_cxx_suffix,[^,]*,([^)]*).*')
619 match = suffix_re.match(line)
621 suffix = match.group(1)
622 elif line.startswith('if'):
623 lineno = parse_makefile(groups, lines, lineno, line, ifstack+1)
625 elif line.startswith('endif'):
629 elif line.startswith('else'):
631 elif inobjects or ingeneratedobjects:
632 if EXCLUDE_SYSTEM and ifstack:
634 file = line + '.' + suffix
635 if ',' in line or '(' in line or ')' in line or file.startswith('-'):
636 #print('passing: ' + line)
637 pass # $if() probably, or something similar
641 if 'filter' in lastif:
642 # We can't grok filter, yet.
644 match = os_cond_re.match(lastif)
646 # We only support OS conditionals.
648 in_out = match.group(1)
649 osname = match.group(2) if match else ''
650 if (in_out == 'ifneq' and not inelse) or \
651 (in_out == 'ifeq' and inelse):
652 osname = '!' + osname
654 if osname not in groups:
656 if ingeneratedobjects:
657 file = WORKDIR + '/' + file
658 groups[osname].append(file)
662 def process_makefile(root, module, libname):
663 """ Parse a gmake makefile and extract
664 source filenames from it.
667 makefile = 'Library_{}.mk'.format(libname)
668 filename = os.path.join(os.path.join(root, module), makefile)
669 if not os.path.isfile(filename):
670 makefile = 'StaticLibrary_{}.mk'.format(libname)
671 filename = os.path.join(os.path.join(root, module), makefile)
672 if not os.path.isfile(filename):
673 sys.stderr.write('Error: Module {} has no makefile at {}.'.format(module, filename))
675 groups = {'':[], 'ANDROID':[], 'iOS':[], 'WNT':[], 'LINUX':[], 'MACOSX':[]}
677 with open(filename, 'r') as f:
678 lines = f.readlines()
679 groups = parse_makefile(groups, lines, lineno=0, lastif=None, ifstack=0)
683 def is_allowed_if(line, module):
684 """ Check whether the given #if condition
685 is allowed for the given module or whether
686 its block should be ignored.
689 # remove trailing comments
690 line = re.sub(r'(.*) *//.*', r'\1', line)
693 # Our sources always build with LIBO_INTERNAL_ONLY.
694 if line == "#if defined LIBO_INTERNAL_ONLY" or line == "#ifdef LIBO_INTERNAL_ONLY":
696 # We use PCHs only for C++.
697 if line == "#if defined(__cplusplus)" or line == "#if defined __cplusplus":
699 # Debug-specific code, it shouldn't hurt including it unconditionally.
700 if line == "#ifdef DBG_UTIL" or line == "#if OSL_DEBUG_LEVEL > 0":
702 if module == "external/skia":
703 # We always set these.
704 if line == "#ifdef SK_VULKAN" or line == "#if SK_SUPPORT_GPU":
708 def process_source(root, module, filename, maxdepth=0):
709 """ Process a source file to extract
711 For now, skip on compiler directives.
712 maxdepth is used when processing headers
713 which typically have protecting ifndef.
721 with open(filename, 'r') as f:
724 if line.startswith('#if'):
725 if is_allowed_if(line, module):
726 allowed_ifs.append(True)
729 allowed_ifs.append(False)
732 elif line.startswith('#endif'):
734 if allowed_ifs[ ifdepth ]:
738 del allowed_ifs[ ifdepth ]
739 elif line.startswith('#pragma once'):
740 # maxdepth == 1 means we are parsing a header file
741 # and are allowed one #ifdef block (the include guard),
742 # but in the #pragma once case do not allow that
745 elif line.startswith('#include'):
746 if ifdepth - ifsallowed <= maxdepth:
747 line = sanitize(line)
749 line = get_filename(line)
750 if line and len(line):
751 raw_includes.append(line)
753 sys.stderr.write('#include in {} : {}\n'.format(lastif, line))
757 def explode(root, module, includes, tree, filter_local, recurse):
758 incpath = os.path.join(root, 'include')
761 filename = get_filename(inc)
762 if filename in tree or len(filter_local.proc(filename)) == 0:
766 # Module or Local header.
767 filepath = filter_local.find_local_file(inc)
769 #print('trying loc: ' + filepath)
770 incs = process_source(root, module, filepath, maxdepth=1)
771 incs = map(get_filename, incs)
772 incs = process_list(incs, lambda x: filter_ignore(x, module))
773 incs = process_list(incs, filter_local.proc)
774 tree[filename] = incs
776 tree = explode(root, module, incs, tree, filter_local, recurse)
777 #print('{} => {}'.format(filepath, tree[filename]))
784 filepath = os.path.join(incpath, filename)
785 #print('trying pub: ' + filepath)
786 incs = process_source(root, module, filepath, maxdepth=1)
787 incs = map(get_filename, incs)
788 incs = process_list(incs, lambda x: filter_ignore(x, module))
789 incs = process_list(incs, filter_local.proc)
790 tree[filename] = incs
792 tree = explode(root, module, incs, tree, filter_local, recurse)
793 #print('{} => {}'.format(filepath, tree[filename]))
798 # Failed, but remember to avoid searching again.
803 def make_command_line():
805 # Remove command line flags and
806 # use internal flags.
807 for i in range(len(args)-1, 0, -1):
808 if args[i].startswith('--'):
811 args.append('--cutoff=' + str(CUTOFF))
813 args.append('--exclude:system')
815 args.append('--include:system')
817 args.append('--exclude:module')
819 args.append('--include:module')
821 args.append('--exclude:local')
823 args.append('--include:local')
825 return ' '.join(args)
827 def generate_includes(includes):
828 """Generates the include lines of the pch.
831 for osname, group in includes.items():
841 lines.append('#if {}defined({})'.format(not_eq, osname))
847 lines.append('#endif')
851 def generate(includes, libname, filename, module):
853 """/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
855 * This file is part of the LibreOffice project.
857 * This Source Code Form is subject to the terms of the Mozilla Public
858 * License, v. 2.0. If a copy of the MPL was not distributed with this
859 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
863 This file has been autogenerated by update_pch.sh. It is possible to edit it
864 manually (such as when an include file has been moved/renamed/removed). All such
865 manual changes will be rewritten by the next run of update_pch.sh (which presumably
866 also fixes all possible problems, so it's usually better to use it).
871 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
875 with open(filename, 'w') as f:
877 f.write('\n Generated on {} using:\n {}\n'.format(
878 datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
879 make_command_line()))
880 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(
883 # sal needs this for rand_s()
884 if module == 'sal' and libname == 'sal':
887 #if !defined _CRT_RAND_S
899 # Some libraries pull windows headers that aren't self contained.
900 if (module == 'connectivity' and libname == 'ado') or \
901 (module == 'xmlsecurity' and libname == 'xsec_xmlsec'):
903 // Cleanup windows header macro pollution.
904 #if defined(_WIN32) && defined(WINAPI)
913 def remove_from_tree(filename, tree):
914 # Remove this file, if top-level.
915 incs = tree.pop(filename, [])
917 tree = remove_from_tree(i, tree)
919 # Also remove if included from another.
920 for (k, v) in tree.items():
926 def tree_to_list(includes, filename, tree):
927 if filename in includes:
929 includes.append(filename)
930 #incs = tree.pop(filename, [])
931 incs = tree[filename] if filename in tree else []
933 tree_to_list(includes, i, tree)
937 def promote(includes):
938 """ Common library headers are heavily
939 referenced, even if they are included
941 Here we separate them to promote
942 their inclusion in the final pch.
946 if inc.startswith('boost') or \
947 inc.startswith('sal') or \
948 inc.startswith('osl') or \
949 inc.startswith('rtl'):
953 def make_pch_filename(root, module, libname):
954 """ PCH files are stored here:
955 <root>/<module>/inc/pch/precompiled_<libname>.hxx
958 path = os.path.join(root, module)
959 path = os.path.join(path, 'inc')
960 path = os.path.join(path, 'pch')
961 path = os.path.join(path, 'precompiled_' + libname + '.hxx')
967 global EXCLUDE_MODULE
969 global EXCLUDE_SYSTEM
973 if os.getenv('WORKDIR'):
974 WORKDIR = os.getenv('WORKDIR')
978 libname = sys.argv[2]
979 header = make_pch_filename(root, module, libname)
981 if not os.path.exists(os.path.join(root, module)):
982 raise Exception('Error: module [{}] not found.'.format(module))
984 key = '{}.{}'.format(module, libname)
986 # Load the module-specific defaults.
987 CUTOFF = DEFAULTS[key][0]
988 EXCLUDE_SYSTEM = DEFAULTS[key][1]
989 EXCLUDE_MODULE = DEFAULTS[key][2]
990 EXCLUDE_LOCAL = DEFAULTS[key][3]
993 for x in range(3, len(sys.argv)):
995 if i.startswith('--cutoff='):
996 CUTOFF = int(i.split('=')[1])
997 elif i.startswith('--exclude:'):
998 cat = i.split(':')[1]
1000 EXCLUDE_MODULE = True
1001 elif cat == 'local':
1002 EXCLUDE_LOCAL = True
1003 elif cat == 'system':
1004 EXCLUDE_SYSTEM = True
1005 elif i.startswith('--include:'):
1006 cat = i.split(':')[1]
1008 EXCLUDE_MODULE = False
1009 elif cat == 'local':
1010 EXCLUDE_LOCAL = False
1011 elif cat == 'system':
1012 EXCLUDE_SYSTEM = False
1013 elif i == '--silent':
1015 elif i == '--force':
1018 sys.stderr.write('Unknown option [{}].'.format(i))
1021 filter_local = Filter_Local(root, module, \
1022 not EXCLUDE_MODULE, \
1026 groups = process_makefile(root, module, libname)
1029 for osname, group in groups.items():
1034 for filename in group:
1035 includes += process_source(root, module, filename)
1037 # Save unique top-level includes.
1038 unique = set(includes)
1039 promoted = promote(unique)
1042 includes = remove_rare(includes)
1043 includes = process_list(includes, lambda x: filter_ignore(x, module))
1044 includes = process_list(includes, filter_local.proc)
1046 # Remove the already included ones.
1047 for inc in includes:
1050 # Explode the excluded ones.
1051 tree = {i:[] for i in includes}
1052 tree = explode(root, module, unique, tree, filter_local, not EXCLUDE_MODULE)
1054 # Remove the already included ones from the tree.
1055 for inc in includes:
1056 filename = get_filename(inc)
1057 tree = remove_from_tree(filename, tree)
1060 for (k, v) in tree.items():
1061 extra += tree_to_list([], k, tree)
1063 promoted += promote(extra)
1064 promoted = process_list(promoted, lambda x: filter_ignore(x, module))
1065 promoted = process_list(promoted, filter_local.proc)
1066 promoted = set(promoted)
1067 # If a promoted header includes others, remove the rest.
1068 for (k, v) in tree.items():
1072 includes += [x for x in promoted]
1074 extra = remove_rare(extra)
1075 extra = process_list(extra, lambda x: filter_ignore(x, module))
1076 extra = process_list(extra, filter_local.proc)
1079 includes = [x for x in set(includes)]
1080 fixes = fixup(includes, module)
1081 fixes = map(lambda x: '#include <' + x + '>', fixes)
1083 includes = map(lambda x: '#include <' + x + '>', includes)
1084 sorted = sort_by_category(includes, root, module, filter_local)
1085 includes = list(fixes) + sorted
1092 groups[osname] = includes
1096 # Open the old pch and compare its contents
1097 # with new includes.
1098 # Clobber only if they are different.
1099 with open(header, 'r') as f:
1100 old_pch_lines = [x.strip() for x in f.readlines()]
1101 new_lines = generate_includes(groups)
1102 # Find the first include in the old pch.
1104 for i in range(len(old_pch_lines)):
1105 if old_pch_lines[i].startswith('#include') or old_pch_lines[i].startswith('#if PCH_LEVEL'):
1108 # Clobber if there is a mismatch.
1109 if force_update or start < 0 or (len(old_pch_lines) - start < len(new_lines)):
1110 generate(new_lines, libname, header, module)
1113 for i in range(len(new_lines)):
1114 if new_lines[i] != old_pch_lines[start + i]:
1115 generate(new_lines, libname, header, module)
1118 # Identical, but see if new pch removed anything.
1119 for i in range(start + len(new_lines), len(old_pch_lines)):
1120 if '#include' in old_pch_lines[i]:
1121 generate(new_lines, libname, header, module)
1125 # Use exit code 2 to distinguish it from exit code 1 used e.g. when an exception occurs.
1128 if __name__ == '__main__':
1129 """ Process all the includes in a Module
1130 to make into a PCH file.
1131 Run without arguments for unittests,
1135 if len(sys.argv) >= 3:
1139 print('Usage: {} <Module name> <Library name> [options]'.format(sys.argv[0]))
1140 print(' Always run from the root of LO repository.\n')
1142 print(' --cutoff=<count> - Threshold to excluding headers.')
1143 print(' --exclude:<category> - Exclude category-specific headers.')
1144 print(' --include:<category> - Include category-specific headers.')
1145 print(' --force - Force updating the pch even when nothing changes.')
1146 print(' Categories:')
1147 print(' module - Headers in /inc directory of a module.')
1148 print(' local - Headers local to a source file.')
1149 print(' system - Platform-specific headers.')
1150 print(' --silent - print only errors.')
1151 print('\nRunning unit-tests...')
1154 class TestMethods(unittest.TestCase):
1156 def test_sanitize(self):
1157 self.assertEqual(sanitize('#include "blah/file.cxx"'),
1158 '#include <blah/file.cxx>')
1159 self.assertEqual(sanitize(' #include\t"blah/file.cxx" '),
1160 '#include <blah/file.cxx>')
1161 self.assertEqual(sanitize(' '),
1164 def test_filter_ignore(self):
1165 self.assertEqual(filter_ignore('blah/file.cxx', 'mod'),
1167 self.assertEqual(filter_ignore('vector', 'mod'),
1169 self.assertEqual(filter_ignore('file.cxx', 'mod'),
1172 def test_remove_rare(self):
1173 self.assertEqual(remove_rare([]),
1176 class TestMakefileParser(unittest.TestCase):
1179 global EXCLUDE_SYSTEM
1180 EXCLUDE_SYSTEM = False
1182 def test_parse_singleline_eval(self):
1183 source = "$(eval $(call gb_Library_Library,sal))"
1184 lines = source.split('\n')
1186 groups = parse_makefile(groups, lines, 0, None, 0)
1187 self.assertEqual(len(groups), 1)
1188 self.assertEqual(len(groups['']), 0)
1190 def test_parse_multiline_eval(self):
1191 source = """$(eval $(call gb_Library_set_include,sal,\\
1193 -I$(SRCDIR)/sal/inc \\
1196 lines = source.split('\n')
1198 groups = parse_makefile(groups, lines, 0, None, 0)
1199 self.assertEqual(len(groups), 1)
1200 self.assertEqual(len(groups['']), 0)
1202 def test_parse_multiline_eval_with_if(self):
1203 source = """$(eval $(call gb_Library_add_defs,sal,\\
1204 $(if $(filter $(OS),iOS), \\
1205 -DNO_CHILD_PROCESSES \\
1209 lines = source.split('\n')
1211 groups = parse_makefile(groups, lines, 0, None, 0)
1212 self.assertEqual(len(groups), 1)
1213 self.assertEqual(len(groups['']), 0)
1215 def test_parse_multiline_add_with_if(self):
1216 source = """$(eval $(call gb_Library_add_exception_objects,sal,\\
1218 $(if $(filter DESKTOP,$(BUILD_TYPE)), sal/osl/unx/salinit) \\
1221 lines = source.split('\n')
1223 groups = parse_makefile(groups, lines, 0, None, 0)
1224 self.assertEqual(len(groups), 1)
1225 self.assertEqual(len(groups['']), 1)
1226 self.assertEqual(groups[''][0], 'sal/osl/unx/time.cxx')
1228 def test_parse_if_else(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 lines = source.split('\n')
1241 groups = parse_makefile(groups, lines, 0, None, 0)
1242 self.assertEqual(len(groups), 3)
1243 self.assertEqual(len(groups['']), 0)
1244 self.assertEqual(len(groups['MACOSX']), 1)
1245 self.assertEqual(len(groups['!MACOSX']), 1)
1246 self.assertEqual(groups['MACOSX'][0], 'sal/osl/mac/mac.cxx')
1247 self.assertEqual(groups['!MACOSX'][0], 'sal/osl/unx/uunxapi.cxx')
1249 def test_parse_nested_if(self):
1250 source = """ifeq ($(OS),MACOSX)
1251 $(eval $(call gb_Library_add_exception_objects,sal,\\
1255 $(eval $(call gb_Library_add_exception_objects,sal,\\
1256 sal/osl/unx/uunxapi \\
1260 $(eval $(call gb_Library_add_exception_objects,sal,\\
1261 sal/textenc/context \\
1266 lines = source.split('\n')
1268 groups = parse_makefile(groups, lines, 0, None, 0)
1269 self.assertEqual(len(groups), 4)
1270 self.assertEqual(len(groups['']), 0)
1271 self.assertEqual(len(groups['MACOSX']), 1)
1272 self.assertEqual(len(groups['!MACOSX']), 1)
1273 self.assertEqual(len(groups['LINUX']), 1)
1274 self.assertEqual(groups['MACOSX'][0], 'sal/osl/mac/mac.cxx')
1275 self.assertEqual(groups['!MACOSX'][0], 'sal/osl/unx/uunxapi.cxx')
1276 self.assertEqual(groups['LINUX'][0], 'sal/textenc/context.cxx')
1278 def test_parse_exclude_system(self):
1279 source = """ifeq ($(OS),MACOSX)
1280 $(eval $(call gb_Library_add_exception_objects,sal,\\
1284 $(eval $(call gb_Library_add_exception_objects,sal,\\
1285 sal/osl/unx/uunxapi \\
1289 $(eval $(call gb_Library_add_exception_objects,sal,\\
1290 sal/textenc/context \\
1295 global EXCLUDE_SYSTEM
1296 EXCLUDE_SYSTEM = True
1298 lines = source.split('\n')
1300 groups = parse_makefile(groups, lines, 0, None, 0)
1301 self.assertEqual(len(groups), 1)
1302 self.assertEqual(len(groups['']), 0)
1304 def test_parse_filter(self):
1305 source = """ifneq ($(filter $(OS),MACOSX iOS),)
1306 $(eval $(call gb_Library_add_exception_objects,sal,\\
1307 sal/osl/unx/osxlocale \\
1311 # Filter is still unsupported.
1312 lines = source.split('\n')
1314 groups = parse_makefile(groups, lines, 0, None, 0)
1315 self.assertEqual(len(groups), 1)
1316 self.assertEqual(len(groups['']), 0)
1320 # vim: set et sw=4 ts=4 expandtab: