Breeze: tdf#143300 add criss-cross border
[LibreOffice.git] / bin / update_pch
blobe47a97994bb26b6cd3028dce8e82fdfb47f69035
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 brakets
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/dsl/',
478             ]
480     for i in ignore_list:
481         if line.startswith(i):
482             return ''
483         if i[0] == '*' and line.endswith(i[1:]):
484             return ''
485         if i[-1] == '*' and line.startswith(i[:-1]):
486             return ''
488     return line
490 def fixup(includes, module):
491     """ Here we add any headers
492         necessary in the pch.
493         These could be known to be very
494         common but for technical reasons
495         left out of the pch by this generator.
496         Or, they could be missing from the
497         source files where they are used
498         (probably because they had been
499         in the old pch, they were missed).
500         Also, these could be headers
501         that make the build faster but
502         aren't added automatically.
503     """
504     fixes = []
505     def append(inc):
506         # Add a space to exclude from
507         # ignore bisecting.
508         line = ' #include <{}>'.format(inc)
509         try:
510             i = fixes.index(inc)
511             fixes[i] = inc
512         except:
513             fixes.append(inc)
515     append('sal/config.h')
517     if module == 'basctl':
518         if 'basslots.hxx' in includes:
519             append('sfx2/msg.hxx')
521     #if module == 'sc':
522     #    if 'scslots.hxx' in includes:
523     #        append('sfx2/msg.hxx')
524     return fixes
526 def sort_by_category(list, root, module, filter_local):
527     """ Move all 'system' headers first.
528         Core files of osl, rtl, sal, next.
529         Everything non-module-specific third.
530         Last, module-specific headers.
531     """
532     sys = []
533     boo = []
534     cor = []
535     rst = []
536     mod = []
538     prefix = '<' + module + '/'
539     for i in list:
540         if 'sal/config.h' in i:
541             continue # added unconditionally in fixup
542         if is_c_runtime(i, root, module):
543             sys.append(i)
544         elif '<boost/' in i:
545             boo.append(i)
546         elif prefix in i or not '/' in i:
547             mod.append(i)
548         elif '<sal/' in i or '<vcl/' in i:
549             cor.append(i)
550         elif '<osl/' in i or '<rtl/' in i:
551             if module == "sal": # osl and rtl are also part of sal
552                 mod.append(i)
553             else:
554                 cor.append(i)
555         # Headers from another module that is closely tied to the module.
556         elif module == 'sc' and '<formula' in i:
557             mod.append(i)
558         else:
559             rst.append(i)
561     out = []
562     out += [ "#if PCH_LEVEL >= 1" ]
563     out += sorted(sys)
564     out += sorted(boo)
565     out += [ "#endif // PCH_LEVEL >= 1" ]
566     out += [ "#if PCH_LEVEL >= 2" ]
567     out += sorted(cor)
568     out += [ "#endif // PCH_LEVEL >= 2" ]
569     out += [ "#if PCH_LEVEL >= 3" ]
570     out += sorted(rst)
571     out += [ "#endif // PCH_LEVEL >= 3" ]
572     out += [ "#if PCH_LEVEL >= 4" ]
573     out += sorted(mod)
574     out += [ "#endif // PCH_LEVEL >= 4" ]
575     return out
577 def parse_makefile(groups, lines, lineno, lastif, ifstack):
579     inobjects = False
580     ingeneratedobjects = False
581     inelse = False
582     suffix = 'cxx'
583     os_cond_re = re.compile(r'(ifeq|ifneq)\s*\(\$\(OS\)\,(\w*)\)')
585     line = lines[lineno]
586     if line.startswith('if'):
587         lastif = line
588         if ifstack == 0:
589             # Correction if first line is an if.
590             lineno = parse_makefile(groups, lines, lineno, line, ifstack+1)
591     else:
592         lineno -= 1
594     while lineno + 1 < len(lines):
595         lineno += 1
596         line = lines[lineno].strip()
597         line = line.rstrip('\\').strip()
598         #print('line #{}: {}'.format(lineno, line))
599         if len(line) == 0:
600             continue
602         if line == '))':
603             inobjects = False
604             ingeneratedobjects = False
605         elif 'add_exception_objects' in line or \
606              'add_cxxobject' in line:
607              inobjects = True
608              #print('inobjects')
609              #if ifstack and not SILENT:
610                 #sys.stderr.write('Sources in a conditional, ignoring for now.\n')
611         elif 'add_generated_exception_objects' in line or \
612              'add_generated_cxxobject' in line:
613              ingeneratedobjects = True
614         elif 'set_generated_cxx_suffix' in line:
615             suffix_re = re.compile('.*set_generated_cxx_suffix,[^,]*,([^)]*).*')
616             match = suffix_re.match(line)
617             if match:
618                 suffix = match.group(1)
619         elif line.startswith('if'):
620             lineno = parse_makefile(groups, lines, lineno, line, ifstack+1)
621             continue
622         elif line.startswith('endif'):
623             if ifstack:
624                 return lineno
625             continue
626         elif line.startswith('else'):
627             inelse = True
628         elif inobjects or ingeneratedobjects:
629             if EXCLUDE_SYSTEM and ifstack:
630                 continue
631             file = line + '.' + suffix
632             if ',' in line or '(' in line or ')' in line or file.startswith('-'):
633                 #print('passing: ' + line)
634                 pass # $if() probably, or something similar
635             else:
636                 osname = ''
637                 if lastif:
638                     if 'filter' in lastif:
639                         # We can't grok filter, yet.
640                         continue
641                     match = os_cond_re.match(lastif)
642                     if not match:
643                         # We only support OS conditionals.
644                         continue
645                     in_out = match.group(1)
646                     osname = match.group(2) if match else ''
647                     if (in_out == 'ifneq' and not inelse) or \
648                        (in_out == 'ifeq' and inelse):
649                         osname = '!' + osname
651                 if osname not in groups:
652                     groups[osname] = []
653                 if ingeneratedobjects:
654                     file = WORKDIR + '/' + file
655                 groups[osname].append(file)
657     return groups
659 def process_makefile(root, module, libname):
660     """ Parse a gmake makefile and extract
661         source filenames from it.
662     """
664     makefile = 'Library_{}.mk'.format(libname)
665     filename = os.path.join(os.path.join(root, module), makefile)
666     if not os.path.isfile(filename):
667         makefile = 'StaticLibrary_{}.mk'.format(libname)
668         filename = os.path.join(os.path.join(root, module), makefile)
669         if not os.path.isfile(filename):
670             sys.stderr.write('Error: Module {} has no makefile at {}.'.format(module, filename))
672     groups = {'':[], 'ANDROID':[], 'iOS':[], 'WNT':[], 'LINUX':[], 'MACOSX':[]}
674     with open(filename, 'r') as f:
675         lines = f.readlines()
676         groups = parse_makefile(groups, lines, lineno=0, lastif=None, ifstack=0)
678     return groups
680 def is_allowed_if(line, module):
681     """ Check whether the given #if condition
682         is allowed for the given module or whether
683         its block should be ignored.
684     """
686     # remove trailing comments
687     line = re.sub(r'(.*) *//.*', r'\1', line)
688     line = line.strip()
690     # Our sources always build with LIBO_INTERNAL_ONLY.
691     if line == "#if defined LIBO_INTERNAL_ONLY" or line == "#ifdef LIBO_INTERNAL_ONLY":
692         return True
693     # We use PCHs only for C++.
694     if line == "#if defined(__cplusplus)" or line == "#if defined __cplusplus":
695         return True
696     # Debug-specific code, it shouldn't hurt including it unconditionally.
697     if line == "#ifdef DBG_UTIL" or line == "#if OSL_DEBUG_LEVEL > 0":
698         return True
699     if module == "external/skia":
700         # We always set these.
701         if line == "#ifdef SK_VULKAN" or line == "#if SK_SUPPORT_GPU":
702             return True
703     return False
705 def process_source(root, module, filename, maxdepth=0):
706     """ Process a source file to extract
707         included headers.
708         For now, skip on compiler directives.
709         maxdepth is used when processing headers
710         which typically have protecting ifndef.
711     """
713     ifdepth = 0
714     lastif = ''
715     raw_includes = []
716     allowed_ifs = []
717     ifsallowed = 0
718     with open(filename, 'r') as f:
719         for line in f:
720             line = line.strip()
721             if line.startswith('#if'):
722                 if is_allowed_if(line, module):
723                     allowed_ifs.append(True)
724                     ifsallowed += 1
725                 else:
726                     allowed_ifs.append(False)
727                     lastif = line
728                 ifdepth += 1
729             elif line.startswith('#endif'):
730                 ifdepth -= 1
731                 if allowed_ifs[ ifdepth ]:
732                     ifsallowed -= 1
733                 else:
734                     lastif = '#if'
735                 del allowed_ifs[ ifdepth ]
736             elif line.startswith('#pragma once'):
737                 # maxdepth == 1 means we are parsing a header file
738                 # and are allowed one #ifdef block (the include guard),
739                 # but in the #pragma once case do not allow that
740                 assert maxdepth == 1
741                 maxdepth = 0
742             elif line.startswith('#include'):
743                 if ifdepth - ifsallowed <= maxdepth:
744                     line = sanitize(line)
745                     if line:
746                         line = get_filename(line)
747                     if line and len(line):
748                         raw_includes.append(line)
749                 elif not SILENT:
750                     sys.stderr.write('#include in {} : {}\n'.format(lastif, line))
752     return raw_includes
754 def explode(root, module, includes, tree, filter_local, recurse):
755     incpath = os.path.join(root, 'include')
757     for inc in includes:
758         filename = get_filename(inc)
759         if filename in tree or len(filter_local.proc(filename)) == 0:
760             continue
762         try:
763             # Module or Local header.
764             filepath = filter_local.find_local_file(inc)
765             if filepath:
766                 #print('trying loc: ' + filepath)
767                 incs = process_source(root, module, filepath, maxdepth=1)
768                 incs = map(get_filename, incs)
769                 incs = process_list(incs, lambda x: filter_ignore(x, module))
770                 incs = process_list(incs, filter_local.proc)
771                 tree[filename] = incs
772                 if recurse:
773                     tree = explode(root, module, incs, tree, filter_local, recurse)
774                 #print('{} => {}'.format(filepath, tree[filename]))
775                 continue
776         except:
777             pass
779         try:
780             # Public header.
781             filepath = os.path.join(incpath, filename)
782             #print('trying pub: ' + filepath)
783             incs = process_source(root, module, filepath, maxdepth=1)
784             incs = map(get_filename, incs)
785             incs = process_list(incs, lambda x: filter_ignore(x, module))
786             incs = process_list(incs, filter_local.proc)
787             tree[filename] = incs
788             if recurse:
789                 tree = explode(root, module, incs, tree, filter_local, recurse)
790             #print('{} => {}'.format(filepath, tree[filename]))
791             continue
792         except:
793             pass
795         # Failed, but remember to avoid searching again.
796         tree[filename] = []
798     return tree
800 def make_command_line():
801     args = sys.argv[:]
802     # Remove command line flags and
803     # use internal flags.
804     for i in range(len(args)-1, 0, -1):
805         if args[i].startswith('--'):
806             args.pop(i)
808     args.append('--cutoff=' + str(CUTOFF))
809     if EXCLUDE_SYSTEM:
810         args.append('--exclude:system')
811     else:
812         args.append('--include:system')
813     if EXCLUDE_MODULE:
814         args.append('--exclude:module')
815     else:
816         args.append('--include:module')
817     if EXCLUDE_LOCAL:
818         args.append('--exclude:local')
819     else:
820         args.append('--include:local')
822     return ' '.join(args)
824 def generate_includes(includes):
825     """Generates the include lines of the pch.
826     """
827     lines = []
828     for osname, group in includes.items():
829         if not len(group):
830             continue
832         if len(osname):
833             not_eq = ''
834             if osname[0] == '!':
835                 not_eq = '!'
836                 osname = osname[1:]
837             lines.append('')
838             lines.append('#if {}defined({})'.format(not_eq, osname))
840         for i in group:
841             lines.append(i)
843         if len(osname):
844             lines.append('#endif')
846     return lines
848 def generate(includes, libname, filename, module):
849     header = \
850 """/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
852  * This file is part of the LibreOffice project.
854  * This Source Code Form is subject to the terms of the Mozilla Public
855  * License, v. 2.0. If a copy of the MPL was not distributed with this
856  * file, You can obtain one at http://mozilla.org/MPL/2.0/.
857  */
860  This file has been autogenerated by update_pch.sh. It is possible to edit it
861  manually (such as when an include file has been moved/renamed/removed). All such
862  manual changes will be rewritten by the next run of update_pch.sh (which presumably
863  also fixes all possible problems, so it's usually better to use it).
866     footer = \
868 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
870     import datetime
872     with open(filename, 'w') as f:
873         f.write(header)
874         f.write('\n Generated on {} using:\n {}\n'.format(
875                 datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
876                 make_command_line()))
877         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(
878                 filename, module))
880         # sal needs this for rand_s()
881         if module == 'sal' and libname == 'sal':
882             sal_define = """
883 #if defined(_WIN32)
884 #if !defined _CRT_RAND_S
885 #define _CRT_RAND_S
886 #endif
887 #endif
889             f.write(sal_define)
891         # Dump the headers.
892         f.write('\n')
893         for i in includes:
894             f.write(i + '\n')
896         # Some libraries pull windows headers that aren't self contained.
897         if (module == 'connectivity' and libname == 'ado') or \
898            (module == 'xmlsecurity' and libname == 'xsec_xmlsec'):
899             ado_define = """
900 // Cleanup windows header macro pollution.
901 #if defined(_WIN32) && defined(WINAPI)
902 #include <postwin.h>
903 #undef RGB
904 #endif
906             f.write(ado_define)
908         f.write(footer)
910 def remove_from_tree(filename, tree):
911     # Remove this file, if top-level.
912     incs = tree.pop(filename, [])
913     for i in incs:
914         tree = remove_from_tree(i, tree)
916     # Also remove if included from another.
917     for (k, v) in tree.items():
918         if filename in v:
919             v.remove(filename)
921     return tree
923 def tree_to_list(includes, filename, tree):
924     if filename in includes:
925         return includes
926     includes.append(filename)
927     #incs = tree.pop(filename, [])
928     incs = tree[filename] if filename in tree else []
929     for i in incs:
930         tree_to_list(includes, i, tree)
932     return includes
934 def promote(includes):
935     """ Common library headers are heavily
936         referenced, even if they are included
937         from a few places.
938         Here we separate them to promote
939         their inclusion in the final pch.
940     """
941     promo = []
942     for inc in includes:
943         if inc.startswith('boost') or \
944            inc.startswith('sal') or \
945            inc.startswith('osl') or \
946            inc.startswith('rtl'):
947             promo.append(inc)
948     return promo
950 def make_pch_filename(root, module, libname):
951     """ PCH files are stored here:
952         <root>/<module>/inc/pch/precompiled_<libname>.hxx
953     """
955     path = os.path.join(root, module)
956     path = os.path.join(path, 'inc')
957     path = os.path.join(path, 'pch')
958     path = os.path.join(path, 'precompiled_' + libname + '.hxx')
959     return path
961 def main():
963     global CUTOFF
964     global EXCLUDE_MODULE
965     global EXCLUDE_LOCAL
966     global EXCLUDE_SYSTEM
967     global SILENT
968     global WORKDIR
970     if os.getenv('WORKDIR'):
971         WORKDIR = os.getenv('WORKDIR')
973     root = '.'
974     module = sys.argv[1]
975     libname = sys.argv[2]
976     header = make_pch_filename(root, module, libname)
978     if not os.path.exists(os.path.join(root, module)):
979         raise Exception('Error: module [{}] not found.'.format(module))
981     key = '{}.{}'.format(module, libname)
982     if key in DEFAULTS:
983         # Load the module-specific defaults.
984         CUTOFF = DEFAULTS[key][0]
985         EXCLUDE_SYSTEM = DEFAULTS[key][1]
986         EXCLUDE_MODULE = DEFAULTS[key][2]
987         EXCLUDE_LOCAL = DEFAULTS[key][3]
989     force_update = False
990     for x in range(3, len(sys.argv)):
991         i = sys.argv[x]
992         if i.startswith('--cutoff='):
993             CUTOFF = int(i.split('=')[1])
994         elif i.startswith('--exclude:'):
995             cat = i.split(':')[1]
996             if cat == 'module':
997                 EXCLUDE_MODULE = True
998             elif cat == 'local':
999                 EXCLUDE_LOCAL = True
1000             elif cat == 'system':
1001                 EXCLUDE_SYSTEM = True
1002         elif i.startswith('--include:'):
1003             cat = i.split(':')[1]
1004             if cat == 'module':
1005                 EXCLUDE_MODULE = False
1006             elif cat == 'local':
1007                 EXCLUDE_LOCAL = False
1008             elif cat == 'system':
1009                 EXCLUDE_SYSTEM = False
1010         elif i == '--silent':
1011             SILENT = True
1012         elif i == '--force':
1013             force_update = True
1014         else:
1015             sys.stderr.write('Unknown option [{}].'.format(i))
1016             return 1
1018     filter_local = Filter_Local(root, module, \
1019                                 not EXCLUDE_MODULE, \
1020                                 not EXCLUDE_LOCAL)
1022     # Read input.
1023     groups = process_makefile(root, module, libname)
1025     generic = []
1026     for osname, group in groups.items():
1027         if not len(group):
1028             continue
1030         includes = []
1031         for filename in group:
1032             includes += process_source(root, module, filename)
1034         # Save unique top-level includes.
1035         unique = set(includes)
1036         promoted = promote(unique)
1038         # Process includes.
1039         includes = remove_rare(includes)
1040         includes = process_list(includes, lambda x: filter_ignore(x, module))
1041         includes = process_list(includes, filter_local.proc)
1043         # Remove the already included ones.
1044         for inc in includes:
1045             unique.discard(inc)
1047         # Explode the excluded ones.
1048         tree = {i:[] for i in includes}
1049         tree = explode(root, module, unique, tree, filter_local, not EXCLUDE_MODULE)
1051         # Remove the already included ones from the tree.
1052         for inc in includes:
1053             filename = get_filename(inc)
1054             tree = remove_from_tree(filename, tree)
1056         extra = []
1057         for (k, v) in tree.items():
1058             extra += tree_to_list([], k, tree)
1060         promoted += promote(extra)
1061         promoted = process_list(promoted, lambda x: filter_ignore(x, module))
1062         promoted = process_list(promoted, filter_local.proc)
1063         promoted = set(promoted)
1064         # If a promoted header includes others, remove the rest.
1065         for (k, v) in tree.items():
1066             if k in promoted:
1067                 for i in v:
1068                     promoted.discard(i)
1069         includes += [x for x in promoted]
1071         extra = remove_rare(extra)
1072         extra = process_list(extra, lambda x: filter_ignore(x, module))
1073         extra = process_list(extra, filter_local.proc)
1074         includes += extra
1076         includes = [x for x in set(includes)]
1077         fixes = fixup(includes, module)
1078         fixes = map(lambda x: '#include <' + x + '>', fixes)
1080         includes = map(lambda x: '#include <' + x + '>', includes)
1081         sorted = sort_by_category(includes, root, module, filter_local)
1082         includes = list(fixes) + sorted
1084         if len(osname):
1085             for i in generic:
1086                 if i in includes:
1087                     includes.remove(i)
1089         groups[osname] = includes
1090         if not len(osname):
1091             generic = includes
1093     # Open the old pch and compare its contents
1094     # with new includes.
1095     # Clobber only if they are different.
1096     with open(header, 'r') as f:
1097         old_pch_lines = [x.strip() for x in f.readlines()]
1098         new_lines = generate_includes(groups)
1099         # Find the first include in the old pch.
1100         start = -1
1101         for i in range(len(old_pch_lines)):
1102             if old_pch_lines[i].startswith('#include') or old_pch_lines[i].startswith('#if PCH_LEVEL'):
1103                 start = i
1104                 break
1105         # Clobber if there is a mismatch.
1106         if force_update or start < 0 or (len(old_pch_lines) - start < len(new_lines)):
1107             generate(new_lines, libname, header, module)
1108             return 0
1109         else:
1110             for i in range(len(new_lines)):
1111                 if new_lines[i] != old_pch_lines[start + i]:
1112                     generate(new_lines, libname, header, module)
1113                     return 0
1114             else:
1115                 # Identical, but see if new pch removed anything.
1116                 for i in range(start + len(new_lines), len(old_pch_lines)):
1117                     if '#include' in old_pch_lines[i]:
1118                         generate(new_lines, libname, header, module)
1119                         return 0
1121     # Didn't update.
1122     # Use exit code 2 to distinguish it from exit code 1 used e.g. when an exception occurs.
1123     return 2
1125 if __name__ == '__main__':
1126     """ Process all the includes in a Module
1127         to make into a PCH file.
1128         Run without arguments for unittests,
1129         and to see usage.
1130     """
1132     if len(sys.argv) >= 3:
1133         status = main()
1134         sys.exit(status)
1136     print('Usage: {} <Module name> <Library name> [options]'.format(sys.argv[0]))
1137     print('    Always run from the root of LO repository.\n')
1138     print('    Options:')
1139     print('    --cutoff=<count> - Threshold to excluding headers.')
1140     print('    --exclude:<category> - Exclude category-specific headers.')
1141     print('    --include:<category> - Include category-specific headers.')
1142     print('    --force - Force updating the pch even when nothing changes.')
1143     print('    Categories:')
1144     print('         module - Headers in /inc directory of a module.')
1145     print('         local  - Headers local to a source file.')
1146     print('         system - Platform-specific headers.')
1147     print('    --silent - print only errors.')
1148     print('\nRunning unit-tests...')
1151 class TestMethods(unittest.TestCase):
1153     def test_sanitize(self):
1154         self.assertEqual(sanitize('#include "blah/file.cxx"'),
1155                                 '#include <blah/file.cxx>')
1156         self.assertEqual(sanitize('  #include\t"blah/file.cxx" '),
1157                                 '#include <blah/file.cxx>')
1158         self.assertEqual(sanitize('  '),
1159                                 '')
1161     def test_filter_ignore(self):
1162         self.assertEqual(filter_ignore('blah/file.cxx', 'mod'),
1163                                      '')
1164         self.assertEqual(filter_ignore('vector', 'mod'),
1165                                      'vector')
1166         self.assertEqual(filter_ignore('file.cxx', 'mod'),
1167                                      '')
1169     def test_remove_rare(self):
1170         self.assertEqual(remove_rare([]),
1171                                 [])
1173 class TestMakefileParser(unittest.TestCase):
1175     def setUp(self):
1176         global EXCLUDE_SYSTEM
1177         EXCLUDE_SYSTEM = False
1179     def test_parse_singleline_eval(self):
1180         source = "$(eval $(call gb_Library_Library,sal))"
1181         lines = source.split('\n')
1182         groups = {'':[]}
1183         groups = parse_makefile(groups, lines, 0, None, 0)
1184         self.assertEqual(len(groups), 1)
1185         self.assertEqual(len(groups['']), 0)
1187     def test_parse_multiline_eval(self):
1188         source = """$(eval $(call gb_Library_set_include,sal,\\
1189         $$(INCLUDE) \\
1190         -I$(SRCDIR)/sal/inc \\
1193         lines = source.split('\n')
1194         groups = {'':[]}
1195         groups = parse_makefile(groups, lines, 0, None, 0)
1196         self.assertEqual(len(groups), 1)
1197         self.assertEqual(len(groups['']), 0)
1199     def test_parse_multiline_eval_with_if(self):
1200         source = """$(eval $(call gb_Library_add_defs,sal,\\
1201         $(if $(filter $(OS),iOS), \\
1202                 -DNO_CHILD_PROCESSES \\
1203         ) \\
1206         lines = source.split('\n')
1207         groups = {'':[]}
1208         groups = parse_makefile(groups, lines, 0, None, 0)
1209         self.assertEqual(len(groups), 1)
1210         self.assertEqual(len(groups['']), 0)
1212     def test_parse_multiline_add_with_if(self):
1213         source = """$(eval $(call gb_Library_add_exception_objects,sal,\\
1214         sal/osl/unx/time \\
1215         $(if $(filter DESKTOP,$(BUILD_TYPE)), sal/osl/unx/salinit) \\
1218         lines = source.split('\n')
1219         groups = {'':[]}
1220         groups = parse_makefile(groups, lines, 0, None, 0)
1221         self.assertEqual(len(groups), 1)
1222         self.assertEqual(len(groups['']), 1)
1223         self.assertEqual(groups[''][0], 'sal/osl/unx/time.cxx')
1225     def test_parse_if_else(self):
1226         source = """ifeq ($(OS),MACOSX)
1227 $(eval $(call gb_Library_add_exception_objects,sal,\\
1228         sal/osl/mac/mac \\
1230 else
1231 $(eval $(call gb_Library_add_exception_objects,sal,\\
1232         sal/osl/unx/uunxapi \\
1234 endif
1236         lines = source.split('\n')
1237         groups = {'':[]}
1238         groups = parse_makefile(groups, lines, 0, None, 0)
1239         self.assertEqual(len(groups), 3)
1240         self.assertEqual(len(groups['']), 0)
1241         self.assertEqual(len(groups['MACOSX']), 1)
1242         self.assertEqual(len(groups['!MACOSX']), 1)
1243         self.assertEqual(groups['MACOSX'][0], 'sal/osl/mac/mac.cxx')
1244         self.assertEqual(groups['!MACOSX'][0], 'sal/osl/unx/uunxapi.cxx')
1246     def test_parse_nested_if(self):
1247         source = """ifeq ($(OS),MACOSX)
1248 $(eval $(call gb_Library_add_exception_objects,sal,\\
1249         sal/osl/mac/mac \\
1251 else
1252 $(eval $(call gb_Library_add_exception_objects,sal,\\
1253         sal/osl/unx/uunxapi \\
1256 ifeq ($(OS),LINUX)
1257 $(eval $(call gb_Library_add_exception_objects,sal,\\
1258         sal/textenc/context \\
1260 endif
1261 endif
1263         lines = source.split('\n')
1264         groups = {'':[]}
1265         groups = parse_makefile(groups, lines, 0, None, 0)
1266         self.assertEqual(len(groups), 4)
1267         self.assertEqual(len(groups['']), 0)
1268         self.assertEqual(len(groups['MACOSX']), 1)
1269         self.assertEqual(len(groups['!MACOSX']), 1)
1270         self.assertEqual(len(groups['LINUX']), 1)
1271         self.assertEqual(groups['MACOSX'][0], 'sal/osl/mac/mac.cxx')
1272         self.assertEqual(groups['!MACOSX'][0], 'sal/osl/unx/uunxapi.cxx')
1273         self.assertEqual(groups['LINUX'][0], 'sal/textenc/context.cxx')
1275     def test_parse_exclude_system(self):
1276         source = """ifeq ($(OS),MACOSX)
1277 $(eval $(call gb_Library_add_exception_objects,sal,\\
1278         sal/osl/mac/mac \\
1280 else
1281 $(eval $(call gb_Library_add_exception_objects,sal,\\
1282         sal/osl/unx/uunxapi \\
1285 ifeq ($(OS),LINUX)
1286 $(eval $(call gb_Library_add_exception_objects,sal,\\
1287         sal/textenc/context \\
1289 endif
1290 endif
1292         global EXCLUDE_SYSTEM
1293         EXCLUDE_SYSTEM = True
1295         lines = source.split('\n')
1296         groups = {'':[]}
1297         groups = parse_makefile(groups, lines, 0, None, 0)
1298         self.assertEqual(len(groups), 1)
1299         self.assertEqual(len(groups['']), 0)
1301     def test_parse_filter(self):
1302         source = """ifneq ($(filter $(OS),MACOSX iOS),)
1303 $(eval $(call gb_Library_add_exception_objects,sal,\\
1304         sal/osl/unx/osxlocale \\
1306 endif
1308         # Filter is still unsupported.
1309         lines = source.split('\n')
1310         groups = {'':[]}
1311         groups = parse_makefile(groups, lines, 0, None, 0)
1312         self.assertEqual(len(groups), 1)
1313         self.assertEqual(len(groups['']), 0)
1315 unittest.main()
1317 # vim: set et sw=4 ts=4 expandtab: