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