Colibre: tdf#148029 Remove dark area in dark variant
[LibreOffice.git] / bin / update_pch
blob1cb5cb9979a6604da047fa587ff1b3edc5aa93ec
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/.
11 """
12 This script generates precompiled headers for a given
13 module and library.
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).
24 """
26 import sys
27 import re
28 import os
29 import unittest
30 import glob
32 CUTOFF = 1
33 EXCLUDE_MODULE = False
34 EXCLUDE_LOCAL = False
35 EXCLUDE_SYSTEM = True
36 SILENT = False
37 WORKDIR = 'workdir'
39 # System includes: oox, sal, sd, svl, vcl
41 INCLUDE = False
42 EXCLUDE = True
43 DEFAULTS = \
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.
130     """
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
135     out = []
136     if not raw or not len(raw):
137         return out
139     inc = sorted(raw)
140     last = inc[0]
141     count = 1
142     for x in range(1, len(inc)):
143         i = inc[x]
144         if i == last:
145             count += 1
146         else:
147             if count >= min_use:
148                 out.append(last)
149             last = i
150             count = 1
152     # Last group.
153     if count >= min_use:
154         out.append(last)
156     return out
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.
163     """
164     out = []
165     for i in list:
166         line = callable(i)
167         if line and len(line):
168             out.append(line)
169     return out
171 def find_files(path, recurse=True):
172     list = []
173     for root, dir, files in os.walk(path):
174         list += map(lambda x: os.path.join(root, x), files)
175     return list
177 def get_filename(line):
178     """ Strips the line from the
179         '#include' and angled brackets
180         and return the filename only.
181     """
182     if not len(line) or line[0] != '#':
183         return line
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++
188         runtime headers.
189         They are all-lowercase, with .h or
190         no extension, filename only.
191         Try to check that they are not LO headers.
192     """
193     inc = get_filename(inc)
195     if inc.endswith('.hxx') or inc.endswith('.hpp'):
196         return False
198     if inc.endswith('.h') and inc.startswith( 'config_' ):
199         return False
201     hasdot = False
202     for c in inc:
203         if c == '/':
204             return False
205         if c == '.' and not inc.endswith('.h'):
206             return False
207         if c == '.':
208             hasdot = True
209         if c.isupper():
210             return False
211     if not hasdot: # <memory> etc.
212         return True
214     if glob.glob(os.path.join(root, module, '**', inc), recursive=True):
215         return False;
217     return True
219 def sanitize(raw):
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.
228     """
229     if not raw or not len(raw):
230         return ''
231     raw = raw.strip()
232     if not len(raw):
233         return ''
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
241                       #include <file.hxx>
242         allow_locals: allows module/source/file.hxx and
243                              module/source/inc/file.hxx
244                       #include <file.hxx>
245     """
246     def __init__(self, root, module, allow_public=True, allow_module=True, allow_locals=True):
247         self.root = root
248         self.module = module
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 = []
256         self.locals = []
257         mod_prefix = module + '/inc/'
258         for i in all:
259             if mod_prefix in i:
260                 self.module_includes.append(i)
261             else:
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):
272                 return True
273         return False
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):
280                 return True
281         return False
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.
292         """
293         filename = get_filename(line)
294         for i in self.locals:
295             if i.endswith(filename):
296                 return i
297         for i in self.module_includes:
298             if i.endswith(filename):
299                 return i
300         return None
302     def proc(self, line):
303         assert line and len(line)
305         if line[0] == '#':
306             if not SILENT:
307                 sys.stderr.write('unhandled #include : {}\n'.format(line))
308             return ''
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.
317             return ''
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:
325                 return ''
326             elif '/inc/' in filename:
327                 return filename
328             elif glob.glob(os.path.join(self.root, self.module, '**', 'inc', filename), recursive=True):
329                 return filename
330             else:
331                 return ''
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):
344             return ''
346         # Unfiltered.
347         return line
349 def filter_ignore(line, module):
350     """ Filters includes from known
351         problematic ones.
352         Expects sanitized input.
353     """
354     assert line and len(line)
356     # Always include files without extension.
357     if '.' not in line:
358         return line
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'):
368        return ''
370     ignore_list = [
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
376         ]
378     if module == 'basic':
379         ignore_list += [
380             'basic/vbahelper.hxx',
381             ]
382     if module == 'connectivity':
383         ignore_list += [
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
387             'adoint.h',
388             'adoctint.h',
389             ]
390     if module == 'sc':
391         ignore_list += [
392             'progress.hxx', # special directives
393             'scslots.hxx',  # special directives
394            ]
395     if module == 'sd':
396         ignore_list += [
397             'sdgslots.hxx', # special directives
398             'sdslots.hxx',  # special directives
399            ]
400     if module == 'sfx2':
401         ignore_list += [
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
406             ]
407     if module == 'sot':
408         ignore_list += [
409             'sysformats.hxx',   # Windows headers
410             ]
411     if module == 'vcl':
412         ignore_list += [
413             'accmgr.hxx',   # redefines ImplAccelList
414             'image.h',
415             'jobset.h',
416             'opengl/gdiimpl.hxx',
417             'opengl/salbmp.hxx',
418             'openglgdiimpl',   # ReplaceTextA
419             'printdlg.hxx',
420             'salinst.hxx',  # GetDefaultPrinterA
421             'salprn.hxx',   # SetPrinterDataA
422             'vcl/jobset.hxx',
423             'vcl/oldprintadaptor.hxx',
424             'vcl/opengl/OpenGLContext.hxx',
425             'vcl/opengl/OpenGLHelper.hxx',  # Conflicts with X header on *ix
426             'vcl/print.hxx',
427             'vcl/prntypes.hxx', # redefines Orientation from filter/jpeg/Exif.hxx
428             'vcl/sysdata.hxx',
429             ]
430     if module == 'xmloff':
431         ignore_list += [
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
436             ]
437     if module == 'xmlsecurity':
438         ignore_list += [
439             'xmlsec/*',
440             'xmlsecurity/xmlsec-wrapper.h',
441             ]
442     if module == 'external/pdfium':
443         ignore_list += [
444             'third_party/freetype/include/pstables.h',
445             ]
446     if module == 'external/clucene':
447         ignore_list += [
448             '_bufferedstream.h',
449             '_condition.h',
450             '_gunichartables.h',
451             '_threads.h',
452             'error.h',
453             'CLucene/LuceneThreads.h',
454             'CLucene/config/_threads.h',
455             ]
456     if module == 'external/skia':
457         ignore_list += [
458             'skcms_internal.h',
459             'zlib.h', # causes crc32 conflict
460             'dirent.h', # unix-specific
461             'pthread.h',
462             'unistd.h',
463             'sys/stat.h',
464             'ft2build.h',
465             'fontconfig/fontconfig.h',
466             'GL/glx.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',
472             'src/opts/',
473             'src/core/SkCubicSolver.h',
474             'src/sksl/SkSLCPP.h',
475             'src/gpu/vk/GrVkAMDMemoryAllocator.h',
476             'src/gpu/GrUtil.h',
477             'src/sksl/', # conflict between SkSL::Expression and SkSL::dsl::Expression
478             'include/sksl/',
479             'src/gpu/vk/',
480             'include/gpu/vk'
481             ]
482     if module == 'external/zxing':
483         ignore_list += [
484             'rss/ODRSSExpandedBinaryDecoder.h'
485             ]
487     for i in ignore_list:
488         if line.startswith(i):
489             return ''
490         if i[0] == '*' and line.endswith(i[1:]):
491             return ''
492         if i[-1] == '*' and line.startswith(i[:-1]):
493             return ''
495     return line
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.
510     """
511     fixes = []
512     def append(inc):
513         # Add a space to exclude from
514         # ignore bisecting.
515         line = ' #include <{}>'.format(inc)
516         try:
517             i = fixes.index(inc)
518             fixes[i] = inc
519         except:
520             fixes.append(inc)
522     append('sal/config.h')
524     if module == 'basctl':
525         if 'basslots.hxx' in includes:
526             append('sfx2/msg.hxx')
528     #if module == 'sc':
529     #    if 'scslots.hxx' in includes:
530     #        append('sfx2/msg.hxx')
531     return fixes
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.
538     """
539     sys = []
540     boo = []
541     cor = []
542     rst = []
543     mod = []
545     prefix = '<' + module + '/'
546     for i in list:
547         if 'sal/config.h' in i:
548             continue # added unconditionally in fixup
549         if is_c_runtime(i, root, module):
550             sys.append(i)
551         elif '<boost/' in i:
552             boo.append(i)
553         elif prefix in i or not '/' in i:
554             mod.append(i)
555         elif '<sal/' in i or '<vcl/' in i:
556             cor.append(i)
557         elif '<osl/' in i or '<rtl/' in i:
558             if module == "sal": # osl and rtl are also part of sal
559                 mod.append(i)
560             else:
561                 cor.append(i)
562         # Headers from another module that is closely tied to the module.
563         elif module == 'sc' and '<formula' in i:
564             mod.append(i)
565         else:
566             rst.append(i)
568     out = []
569     out += [ "#if PCH_LEVEL >= 1" ]
570     out += sorted(sys)
571     out += sorted(boo)
572     out += [ "#endif // PCH_LEVEL >= 1" ]
573     out += [ "#if PCH_LEVEL >= 2" ]
574     out += sorted(cor)
575     out += [ "#endif // PCH_LEVEL >= 2" ]
576     out += [ "#if PCH_LEVEL >= 3" ]
577     out += sorted(rst)
578     out += [ "#endif // PCH_LEVEL >= 3" ]
579     out += [ "#if PCH_LEVEL >= 4" ]
580     out += sorted(mod)
581     out += [ "#endif // PCH_LEVEL >= 4" ]
582     return out
584 def parse_makefile(groups, lines, lineno, lastif, ifstack):
586     inobjects = False
587     ingeneratedobjects = False
588     inelse = False
589     suffix = 'cxx'
590     os_cond_re = re.compile(r'(ifeq|ifneq)\s*\(\$\(OS\)\,(\w*)\)')
592     line = lines[lineno]
593     if line.startswith('if'):
594         lastif = line
595         if ifstack == 0:
596             # Correction if first line is an if.
597             lineno = parse_makefile(groups, lines, lineno, line, ifstack+1)
598     else:
599         lineno -= 1
601     while lineno + 1 < len(lines):
602         lineno += 1
603         line = lines[lineno].strip()
604         line = line.rstrip('\\').strip()
605         #print('line #{}: {}'.format(lineno, line))
606         if len(line) == 0:
607             continue
609         if line == '))':
610             inobjects = False
611             ingeneratedobjects = False
612         elif 'add_exception_objects' in line or \
613              'add_cxxobject' in line:
614              inobjects = True
615              #print('inobjects')
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)
624             if match:
625                 suffix = match.group(1)
626         elif line.startswith('if'):
627             lineno = parse_makefile(groups, lines, lineno, line, ifstack+1)
628             continue
629         elif line.startswith('endif'):
630             if ifstack:
631                 return lineno
632             continue
633         elif line.startswith('else'):
634             inelse = True
635         elif inobjects or ingeneratedobjects:
636             if EXCLUDE_SYSTEM and ifstack:
637                 continue
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
642             else:
643                 osname = ''
644                 if lastif:
645                     if 'filter' in lastif:
646                         # We can't grok filter, yet.
647                         continue
648                     match = os_cond_re.match(lastif)
649                     if not match:
650                         # We only support OS conditionals.
651                         continue
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:
659                     groups[osname] = []
660                 if ingeneratedobjects:
661                     file = WORKDIR + '/' + file
662                 groups[osname].append(file)
664     return groups
666 def process_makefile(root, module, libname):
667     """ Parse a gmake makefile and extract
668         source filenames from it.
669     """
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)
685     return groups
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.
691     """
693     # remove trailing comments
694     line = re.sub(r'(.*) *//.*', r'\1', line)
695     line = line.strip()
697     # Our sources always build with LIBO_INTERNAL_ONLY.
698     if line == "#if defined LIBO_INTERNAL_ONLY" or line == "#ifdef LIBO_INTERNAL_ONLY":
699         return True
700     # We use PCHs only for C++.
701     if line == "#if defined(__cplusplus)" or line == "#if defined __cplusplus":
702         return True
703     # Debug-specific code, it shouldn't hurt including it unconditionally.
704     if line == "#ifdef DBG_UTIL" or line == "#if OSL_DEBUG_LEVEL > 0":
705         return True
706     if module == "external/skia":
707         # We always set these.
708         if line == "#ifdef SK_VULKAN" or line == "#if SK_SUPPORT_GPU":
709             return True
710     return False
712 def process_source(root, module, filename, maxdepth=0):
713     """ Process a source file to extract
714         included headers.
715         For now, skip on compiler directives.
716         maxdepth is used when processing headers
717         which typically have protecting ifndef.
718     """
720     ifdepth = 0
721     lastif = ''
722     raw_includes = []
723     allowed_ifs = []
724     ifsallowed = 0
725     with open(filename, 'r') as f:
726         for line in f:
727             line = line.strip()
728             if line.startswith('#if'):
729                 if is_allowed_if(line, module):
730                     allowed_ifs.append(True)
731                     ifsallowed += 1
732                 else:
733                     allowed_ifs.append(False)
734                     lastif = line
735                 ifdepth += 1
736             elif line.startswith('#endif'):
737                 ifdepth -= 1
738                 if allowed_ifs[ ifdepth ]:
739                     ifsallowed -= 1
740                 else:
741                     lastif = '#if'
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
747                 assert maxdepth == 1
748                 maxdepth = 0
749             elif line.startswith('#include'):
750                 if ifdepth - ifsallowed <= maxdepth:
751                     line = sanitize(line)
752                     if line:
753                         line = get_filename(line)
754                     if line and len(line):
755                         raw_includes.append(line)
756                 elif not SILENT:
757                     sys.stderr.write('#include in {} : {}\n'.format(lastif, line))
759     return raw_includes
761 def explode(root, module, includes, tree, filter_local, recurse):
762     incpath = os.path.join(root, 'include')
764     for inc in includes:
765         filename = get_filename(inc)
766         if filename in tree or len(filter_local.proc(filename)) == 0:
767             continue
769         try:
770             # Module or Local header.
771             filepath = filter_local.find_local_file(inc)
772             if filepath:
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
779                 if recurse:
780                     tree = explode(root, module, incs, tree, filter_local, recurse)
781                 #print('{} => {}'.format(filepath, tree[filename]))
782                 continue
783         except:
784             pass
786         try:
787             # Public header.
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
795             if recurse:
796                 tree = explode(root, module, incs, tree, filter_local, recurse)
797             #print('{} => {}'.format(filepath, tree[filename]))
798             continue
799         except:
800             pass
802         # Failed, but remember to avoid searching again.
803         tree[filename] = []
805     return tree
807 def make_command_line():
808     args = sys.argv[:]
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('--'):
813             args.pop(i)
815     args.append('--cutoff=' + str(CUTOFF))
816     if EXCLUDE_SYSTEM:
817         args.append('--exclude:system')
818     else:
819         args.append('--include:system')
820     if EXCLUDE_MODULE:
821         args.append('--exclude:module')
822     else:
823         args.append('--include:module')
824     if EXCLUDE_LOCAL:
825         args.append('--exclude:local')
826     else:
827         args.append('--include:local')
829     return ' '.join(args)
831 def generate_includes(includes):
832     """Generates the include lines of the pch.
833     """
834     lines = []
835     for osname, group in includes.items():
836         if not len(group):
837             continue
839         if len(osname):
840             not_eq = ''
841             if osname[0] == '!':
842                 not_eq = '!'
843                 osname = osname[1:]
844             lines.append('')
845             lines.append('#if {}defined({})'.format(not_eq, osname))
847         for i in group:
848             lines.append(i)
850         if len(osname):
851             lines.append('#endif')
853     return lines
855 def generate(includes, libname, filename, module):
856     header = \
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/.
864  */
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).
873     footer = \
875 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
877     import datetime
879     with open(filename, 'w') as f:
880         f.write(header)
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(
885                 filename, module))
887         # sal needs this for rand_s()
888         if module == 'sal' and libname == 'sal':
889             sal_define = """
890 #if defined(_WIN32)
891 #define _CRT_RAND_S
892 #endif
894             f.write(sal_define)
896         # Dump the headers.
897         f.write('\n')
898         for i in includes:
899             f.write(i + '\n')
901         # Some libraries pull windows headers that aren't self contained.
902         if (module == 'connectivity' and libname == 'ado') or \
903            (module == 'xmlsecurity' and libname == 'xsec_xmlsec'):
904             ado_define = """
905 // Cleanup windows header macro pollution.
906 #if defined(_WIN32) && defined(WINAPI)
907 #include <postwin.h>
908 #undef RGB
909 #endif
911             f.write(ado_define)
913         f.write(footer)
915 def remove_from_tree(filename, tree):
916     # Remove this file, if top-level.
917     incs = tree.pop(filename, [])
918     for i in incs:
919         tree = remove_from_tree(i, tree)
921     # Also remove if included from another.
922     for (k, v) in tree.items():
923         if filename in v:
924             v.remove(filename)
926     return tree
928 def tree_to_list(includes, filename, tree):
929     if filename in includes:
930         return includes
931     includes.append(filename)
932     #incs = tree.pop(filename, [])
933     incs = tree[filename] if filename in tree else []
934     for i in incs:
935         tree_to_list(includes, i, tree)
937     return includes
939 def promote(includes):
940     """ Common library headers are heavily
941         referenced, even if they are included
942         from a few places.
943         Here we separate them to promote
944         their inclusion in the final pch.
945     """
946     promo = []
947     for inc in includes:
948         if inc.startswith('boost') or \
949            inc.startswith('sal') or \
950            inc.startswith('osl') or \
951            inc.startswith('rtl'):
952             promo.append(inc)
953     return promo
955 def make_pch_filename(root, module, libname):
956     """ PCH files are stored here:
957         <root>/<module>/inc/pch/precompiled_<libname>.hxx
958     """
960     path = os.path.join(root, module)
961     path = os.path.join(path, 'inc')
962     path = os.path.join(path, 'pch')
963     path = os.path.join(path, 'precompiled_' + libname + '.hxx')
964     return path
966 def main():
968     global CUTOFF
969     global EXCLUDE_MODULE
970     global EXCLUDE_LOCAL
971     global EXCLUDE_SYSTEM
972     global SILENT
973     global WORKDIR
975     if os.getenv('WORKDIR'):
976         WORKDIR = os.getenv('WORKDIR')
978     root = '.'
979     module = sys.argv[1]
980     libname = sys.argv[2]
981     header = make_pch_filename(root, module, libname)
983     if not os.path.exists(os.path.join(root, module)):
984         raise Exception('Error: module [{}] not found.'.format(module))
986     key = '{}.{}'.format(module, libname)
987     if key in DEFAULTS:
988         # Load the module-specific defaults.
989         CUTOFF = DEFAULTS[key][0]
990         EXCLUDE_SYSTEM = DEFAULTS[key][1]
991         EXCLUDE_MODULE = DEFAULTS[key][2]
992         EXCLUDE_LOCAL = DEFAULTS[key][3]
994     force_update = False
995     for x in range(3, len(sys.argv)):
996         i = sys.argv[x]
997         if i.startswith('--cutoff='):
998             CUTOFF = int(i.split('=')[1])
999         elif i.startswith('--exclude:'):
1000             cat = i.split(':')[1]
1001             if cat == 'module':
1002                 EXCLUDE_MODULE = True
1003             elif cat == 'local':
1004                 EXCLUDE_LOCAL = True
1005             elif cat == 'system':
1006                 EXCLUDE_SYSTEM = True
1007         elif i.startswith('--include:'):
1008             cat = i.split(':')[1]
1009             if cat == 'module':
1010                 EXCLUDE_MODULE = False
1011             elif cat == 'local':
1012                 EXCLUDE_LOCAL = False
1013             elif cat == 'system':
1014                 EXCLUDE_SYSTEM = False
1015         elif i == '--silent':
1016             SILENT = True
1017         elif i == '--force':
1018             force_update = True
1019         else:
1020             sys.stderr.write('Unknown option [{}].'.format(i))
1021             return 1
1023     filter_local = Filter_Local(root, module, \
1024                                 not EXCLUDE_MODULE, \
1025                                 not EXCLUDE_LOCAL)
1027     # Read input.
1028     groups = process_makefile(root, module, libname)
1030     generic = []
1031     for osname, group in groups.items():
1032         if not len(group):
1033             continue
1035         includes = []
1036         for filename in group:
1037             includes += process_source(root, module, filename)
1039         # Save unique top-level includes.
1040         unique = set(includes)
1041         promoted = promote(unique)
1043         # Process includes.
1044         includes = remove_rare(includes)
1045         includes = process_list(includes, lambda x: filter_ignore(x, module))
1046         includes = process_list(includes, filter_local.proc)
1048         # Remove the already included ones.
1049         for inc in includes:
1050             unique.discard(inc)
1052         # Explode the excluded ones.
1053         tree = {i:[] for i in includes}
1054         tree = explode(root, module, unique, tree, filter_local, not EXCLUDE_MODULE)
1056         # Remove the already included ones from the tree.
1057         for inc in includes:
1058             filename = get_filename(inc)
1059             tree = remove_from_tree(filename, tree)
1061         extra = []
1062         for (k, v) in tree.items():
1063             extra += tree_to_list([], k, tree)
1065         promoted += promote(extra)
1066         promoted = process_list(promoted, lambda x: filter_ignore(x, module))
1067         promoted = process_list(promoted, filter_local.proc)
1068         promoted = set(promoted)
1069         # If a promoted header includes others, remove the rest.
1070         for (k, v) in tree.items():
1071             if k in promoted:
1072                 for i in v:
1073                     promoted.discard(i)
1074         includes += [x for x in promoted]
1076         extra = remove_rare(extra)
1077         extra = process_list(extra, lambda x: filter_ignore(x, module))
1078         extra = process_list(extra, filter_local.proc)
1079         includes += extra
1081         includes = [x for x in set(includes)]
1082         fixes = fixup(includes, module)
1083         fixes = map(lambda x: '#include <' + x + '>', fixes)
1085         includes = map(lambda x: '#include <' + x + '>', includes)
1086         sorted = sort_by_category(includes, root, module, filter_local)
1087         includes = list(fixes) + sorted
1089         if len(osname):
1090             for i in generic:
1091                 if i in includes:
1092                     includes.remove(i)
1094         groups[osname] = includes
1095         if not len(osname):
1096             generic = includes
1098     # Open the old pch and compare its contents
1099     # with new includes.
1100     # Clobber only if they are different.
1101     with open(header, 'r') as f:
1102         old_pch_lines = [x.strip() for x in f.readlines()]
1103         new_lines = generate_includes(groups)
1104         # Find the first include in the old pch.
1105         start = -1
1106         for i in range(len(old_pch_lines)):
1107             if old_pch_lines[i].startswith('#include') or old_pch_lines[i].startswith('#if PCH_LEVEL'):
1108                 start = i
1109                 break
1110         # Clobber if there is a mismatch.
1111         if force_update or start < 0 or (len(old_pch_lines) - start < len(new_lines)):
1112             generate(new_lines, libname, header, module)
1113             return 0
1114         else:
1115             for i in range(len(new_lines)):
1116                 if new_lines[i] != old_pch_lines[start + i]:
1117                     generate(new_lines, libname, header, module)
1118                     return 0
1119             else:
1120                 # Identical, but see if new pch removed anything.
1121                 for i in range(start + len(new_lines), len(old_pch_lines)):
1122                     if '#include' in old_pch_lines[i]:
1123                         generate(new_lines, libname, header, module)
1124                         return 0
1126     # Didn't update.
1127     # Use exit code 2 to distinguish it from exit code 1 used e.g. when an exception occurs.
1128     return 2
1130 if __name__ == '__main__':
1131     """ Process all the includes in a Module
1132         to make into a PCH file.
1133         Run without arguments for unittests,
1134         and to see usage.
1135     """
1137     if len(sys.argv) >= 3:
1138         status = main()
1139         sys.exit(status)
1141     print('Usage: {} <Module name> <Library name> [options]'.format(sys.argv[0]))
1142     print('    Always run from the root of LO repository.\n')
1143     print('    Options:')
1144     print('    --cutoff=<count> - Threshold to excluding headers.')
1145     print('    --exclude:<category> - Exclude category-specific headers.')
1146     print('    --include:<category> - Include category-specific headers.')
1147     print('    --force - Force updating the pch even when nothing changes.')
1148     print('    Categories:')
1149     print('         module - Headers in /inc directory of a module.')
1150     print('         local  - Headers local to a source file.')
1151     print('         system - Platform-specific headers.')
1152     print('    --silent - print only errors.')
1153     print('\nRunning unit-tests...')
1156 class TestMethods(unittest.TestCase):
1158     def test_sanitize(self):
1159         self.assertEqual(sanitize('#include "blah/file.cxx"'),
1160                                 '#include <blah/file.cxx>')
1161         self.assertEqual(sanitize('  #include\t"blah/file.cxx" '),
1162                                 '#include <blah/file.cxx>')
1163         self.assertEqual(sanitize('  '),
1164                                 '')
1166     def test_filter_ignore(self):
1167         self.assertEqual(filter_ignore('blah/file.cxx', 'mod'),
1168                                      '')
1169         self.assertEqual(filter_ignore('vector', 'mod'),
1170                                      'vector')
1171         self.assertEqual(filter_ignore('file.cxx', 'mod'),
1172                                      '')
1174     def test_remove_rare(self):
1175         self.assertEqual(remove_rare([]),
1176                                 [])
1178 class TestMakefileParser(unittest.TestCase):
1180     def setUp(self):
1181         global EXCLUDE_SYSTEM
1182         EXCLUDE_SYSTEM = False
1184     def test_parse_singleline_eval(self):
1185         source = "$(eval $(call gb_Library_Library,sal))"
1186         lines = source.split('\n')
1187         groups = {'':[]}
1188         groups = parse_makefile(groups, lines, 0, None, 0)
1189         self.assertEqual(len(groups), 1)
1190         self.assertEqual(len(groups['']), 0)
1192     def test_parse_multiline_eval(self):
1193         source = """$(eval $(call gb_Library_set_include,sal,\\
1194         $$(INCLUDE) \\
1195         -I$(SRCDIR)/sal/inc \\
1198         lines = source.split('\n')
1199         groups = {'':[]}
1200         groups = parse_makefile(groups, lines, 0, None, 0)
1201         self.assertEqual(len(groups), 1)
1202         self.assertEqual(len(groups['']), 0)
1204     def test_parse_multiline_eval_with_if(self):
1205         source = """$(eval $(call gb_Library_add_defs,sal,\\
1206         $(if $(filter $(OS),iOS), \\
1207                 -DNO_CHILD_PROCESSES \\
1208         ) \\
1211         lines = source.split('\n')
1212         groups = {'':[]}
1213         groups = parse_makefile(groups, lines, 0, None, 0)
1214         self.assertEqual(len(groups), 1)
1215         self.assertEqual(len(groups['']), 0)
1217     def test_parse_multiline_add_with_if(self):
1218         source = """$(eval $(call gb_Library_add_exception_objects,sal,\\
1219         sal/osl/unx/time \\
1220         $(if $(filter DESKTOP,$(BUILD_TYPE)), sal/osl/unx/salinit) \\
1223         lines = source.split('\n')
1224         groups = {'':[]}
1225         groups = parse_makefile(groups, lines, 0, None, 0)
1226         self.assertEqual(len(groups), 1)
1227         self.assertEqual(len(groups['']), 1)
1228         self.assertEqual(groups[''][0], 'sal/osl/unx/time.cxx')
1230     def test_parse_if_else(self):
1231         source = """ifeq ($(OS),MACOSX)
1232 $(eval $(call gb_Library_add_exception_objects,sal,\\
1233         sal/osl/mac/mac \\
1235 else
1236 $(eval $(call gb_Library_add_exception_objects,sal,\\
1237         sal/osl/unx/uunxapi \\
1239 endif
1241         lines = source.split('\n')
1242         groups = {'':[]}
1243         groups = parse_makefile(groups, lines, 0, None, 0)
1244         self.assertEqual(len(groups), 3)
1245         self.assertEqual(len(groups['']), 0)
1246         self.assertEqual(len(groups['MACOSX']), 1)
1247         self.assertEqual(len(groups['!MACOSX']), 1)
1248         self.assertEqual(groups['MACOSX'][0], 'sal/osl/mac/mac.cxx')
1249         self.assertEqual(groups['!MACOSX'][0], 'sal/osl/unx/uunxapi.cxx')
1251     def test_parse_nested_if(self):
1252         source = """ifeq ($(OS),MACOSX)
1253 $(eval $(call gb_Library_add_exception_objects,sal,\\
1254         sal/osl/mac/mac \\
1256 else
1257 $(eval $(call gb_Library_add_exception_objects,sal,\\
1258         sal/osl/unx/uunxapi \\
1261 ifeq ($(OS),LINUX)
1262 $(eval $(call gb_Library_add_exception_objects,sal,\\
1263         sal/textenc/context \\
1265 endif
1266 endif
1268         lines = source.split('\n')
1269         groups = {'':[]}
1270         groups = parse_makefile(groups, lines, 0, None, 0)
1271         self.assertEqual(len(groups), 4)
1272         self.assertEqual(len(groups['']), 0)
1273         self.assertEqual(len(groups['MACOSX']), 1)
1274         self.assertEqual(len(groups['!MACOSX']), 1)
1275         self.assertEqual(len(groups['LINUX']), 1)
1276         self.assertEqual(groups['MACOSX'][0], 'sal/osl/mac/mac.cxx')
1277         self.assertEqual(groups['!MACOSX'][0], 'sal/osl/unx/uunxapi.cxx')
1278         self.assertEqual(groups['LINUX'][0], 'sal/textenc/context.cxx')
1280     def test_parse_exclude_system(self):
1281         source = """ifeq ($(OS),MACOSX)
1282 $(eval $(call gb_Library_add_exception_objects,sal,\\
1283         sal/osl/mac/mac \\
1285 else
1286 $(eval $(call gb_Library_add_exception_objects,sal,\\
1287         sal/osl/unx/uunxapi \\
1290 ifeq ($(OS),LINUX)
1291 $(eval $(call gb_Library_add_exception_objects,sal,\\
1292         sal/textenc/context \\
1294 endif
1295 endif
1297         global EXCLUDE_SYSTEM
1298         EXCLUDE_SYSTEM = True
1300         lines = source.split('\n')
1301         groups = {'':[]}
1302         groups = parse_makefile(groups, lines, 0, None, 0)
1303         self.assertEqual(len(groups), 1)
1304         self.assertEqual(len(groups['']), 0)
1306     def test_parse_filter(self):
1307         source = """ifneq ($(filter $(OS),MACOSX iOS),)
1308 $(eval $(call gb_Library_add_exception_objects,sal,\\
1309         sal/osl/unx/osxlocale \\
1311 endif
1313         # Filter is still unsupported.
1314         lines = source.split('\n')
1315         groups = {'':[]}
1316         groups = parse_makefile(groups, lines, 0, None, 0)
1317         self.assertEqual(len(groups), 1)
1318         self.assertEqual(len(groups['']), 0)
1320 unittest.main()
1322 # vim: set et sw=4 ts=4 expandtab: