#! /usr/bin/env python3
# -*- Mode: python; tab-width: 4; indent-tabs-mode: t -*-
java.lang.NullPointerException
# This file is part of the LibreOffice project.
java.lang.NullPointerException
# This Source Code Form is subject to the terms of the Mozilla
Public
# License, v. 2.0.
If a copy of the MPL was
not distributed with
this
# file, You can obtain one at http:
//mozilla.org/MPL/2.0/.
java.lang.NullPointerException
"""
This script generates precompiled headers
for a given
module
and library.
Given a gmake makefile that belongs to some LO module:
1) Process the makefile to find source files (process_makefile).
2)
For every source file, find all includes (process_source).
3) Uncommon
and rare includes are filtered (remove_rare).
4) Conflicting headers are excluded (filter_ignore).
5) Local files to the source are excluded (Filter_Local).
6) Fixup missing headers that sources expect (fixup).
7) The resulting includes are sorted by category (sort_by_category).
8) The pch file is generated (generate).
"""
import sys
import re
import os
import unittest
import glob
CUTOFF = 1
EXCLUDE_MODULE =
False
EXCLUDE_LOCAL =
False
EXCLUDE_SYSTEM =
True
SILENT =
False
WORKDIR =
'workdir'
# System includes: oox, sal, sd, svl, vcl
INCLUDE =
False
EXCLUDE =
True
DEFAULTS = \
{
# module.library : (min, system, module, local), best time
'accessibility.acc' : ( 4, EXCLUDE, INCLUDE, INCLUDE),
# 7.8
'basctl.basctl' : ( 3, EXCLUDE, INCLUDE, EXCLUDE),
# 11.9
'basegfx.basegfx' : ( 3, EXCLUDE, EXCLUDE, INCLUDE),
# 3.8
'basic.sb' : ( 2, EXCLUDE, EXCLUDE, INCLUDE),
# 10.7
'chart2.chart2' : ( 3, EXCLUDE, EXCLUDE, INCLUDE),
# 18.4
'comphelper.comphelper' : ( 4, EXCLUDE, INCLUDE, INCLUDE),
# 7.6
'configmgr.configmgr' : ( 6, EXCLUDE, INCLUDE, INCLUDE),
# 6.0
'connectivity.ado' : ( 2, EXCLUDE, EXCLUDE, EXCLUDE),
# 6.4
'connectivity.calc' : ( 2, EXCLUDE, EXCLUDE, EXCLUDE),
# 4.6
'connectivity.dbase' : ( 2, EXCLUDE, INCLUDE, INCLUDE),
# 5.2
'connectivity.dbpool2' : ( 5, EXCLUDE, INCLUDE, EXCLUDE),
# 3.0
'connectivity.dbtools' : ( 2, EXCLUDE, EXCLUDE, INCLUDE),
# 0.8
'connectivity.file' : ( 2, EXCLUDE, INCLUDE, EXCLUDE),
# 5.1
'connectivity.firebird_sdbc' : ( 2, EXCLUDE, EXCLUDE, EXCLUDE),
# 5.1
'connectivity.flat' : ( 2, EXCLUDE, INCLUDE, INCLUDE),
# 4.6
'connectivity.mysql' : ( 4, EXCLUDE, INCLUDE, EXCLUDE),
# 3.4
'connectivity.odbc' : ( 2, EXCLUDE, EXCLUDE, INCLUDE),
# 5.0
'connectivity.postgresql-sdbc-impl' : ( 3, EXCLUDE, EXCLUDE, EXCLUDE),
# 6.7
'cppcanvas.cppcanvas' : (11, EXCLUDE, INCLUDE, INCLUDE),
# 4.8
'cppuhelper.cppuhelper' : ( 3, EXCLUDE, EXCLUDE, EXCLUDE),
# 4.6
'cui.cui' : ( 8, EXCLUDE, INCLUDE, EXCLUDE),
# 19.7
'dbaccess.dba' : ( 6, EXCLUDE, INCLUDE, INCLUDE),
# 13.8
'dbaccess.dbaxml' : ( 2, EXCLUDE, EXCLUDE, EXCLUDE),
# 6.5
'dbaccess.dbu' : (12, EXCLUDE, EXCLUDE, EXCLUDE),
# 23.6
'dbaccess.sdbt' : ( 1, EXCLUDE, INCLUDE, EXCLUDE),
# 2.9
'desktop.deployment' : ( 3, EXCLUDE, EXCLUDE, EXCLUDE),
# 6.1
'desktop.deploymentgui' : ( 3, EXCLUDE, EXCLUDE, EXCLUDE),
# 5.7
'desktop.deploymentmisc' : ( 3, EXCLUDE, EXCLUDE, EXCLUDE),
# 3.4
'desktop.sofficeapp' : ( 6, EXCLUDE, INCLUDE, INCLUDE),
# 6.5
'docmodel.docmodel' : ( 3, EXCLUDE, EXCLUDE, INCLUDE),
# 3.8
'drawinglayer.drawinglayer' : ( 4, EXCLUDE, EXCLUDE, EXCLUDE),
# 7.4
'editeng.editeng' : ( 5, EXCLUDE, INCLUDE, EXCLUDE),
# 13.0
'forms.frm' : ( 2, EXCLUDE, EXCLUDE, EXCLUDE),
# 14.2
'framework.fwk' : ( 7, EXCLUDE, INCLUDE, INCLUDE),
# 14.8
'hwpfilter.hwp' : ( 3, EXCLUDE, INCLUDE, INCLUDE),
# 6.0
'lotuswordpro.lwpft' : ( 2, EXCLUDE, EXCLUDE, EXCLUDE),
# 11.6
'oox.oox' : ( 6, EXCLUDE, EXCLUDE, INCLUDE),
# 28.2
'package.package2' : ( 3, EXCLUDE, INCLUDE, INCLUDE),
# 4.5
'package.xstor' : ( 2, EXCLUDE, INCLUDE, EXCLUDE),
# 3.8
'reportdesign.rpt' : ( 9, EXCLUDE, INCLUDE, INCLUDE),
# 9.4
'reportdesign.rptui' : ( 4, EXCLUDE, INCLUDE, INCLUDE),
# 13.1
'reportdesign.rptxml' : ( 2, EXCLUDE, EXCLUDE, INCLUDE),
# 7.6
'sal.sal' : ( 2, EXCLUDE, EXCLUDE, INCLUDE),
# 4.2
'sc.sc' : (12, EXCLUDE, INCLUDE, INCLUDE),
# 92.6
'sc.scfilt' : ( 4, EXCLUDE, EXCLUDE, INCLUDE),
# 39.9
'sc.scui' : ( 1, EXCLUDE, EXCLUDE, INCLUDE),
# 15.0
'sc.vbaobj' : ( 1, EXCLUDE, EXCLUDE, INCLUDE),
# 17.3
'sd.sd' : ( 4, EXCLUDE, EXCLUDE, INCLUDE),
# 47.4
'sd.sdui' : ( 4, EXCLUDE, INCLUDE, INCLUDE),
# 9.4
'sdext.PresentationMinimizer' : ( 2, EXCLUDE, INCLUDE, INCLUDE),
# 4.1
'sdext.PresenterScreen' : ( 2, EXCLUDE, INCLUDE, EXCLUDE),
# 7.1
'sfx2.sfx' : ( 3, EXCLUDE, EXCLUDE, EXCLUDE),
# 27.4
'slideshow.slideshow' : ( 4, EXCLUDE, INCLUDE, EXCLUDE),
# 10.8
'sot.sot' : ( 5, EXCLUDE, EXCLUDE, INCLUDE),
# 3.1
'starmath.sm' : ( 5, EXCLUDE, EXCLUDE, INCLUDE),
# 10.9
'svgio.svgio' : ( 8, EXCLUDE, EXCLUDE, INCLUDE),
# 4.3
'emfio.emfio' : ( 8, EXCLUDE, EXCLUDE, INCLUDE),
# 4.3
'svl.svl' : ( 6, EXCLUDE, EXCLUDE, EXCLUDE),
# 7.6
'svtools.svt' : ( 4, EXCLUDE, INCLUDE, EXCLUDE),
# 17.6
'svx.svx' : ( 3, EXCLUDE, EXCLUDE, INCLUDE),
# 20.7
'svx.svxcore' : ( 7, EXCLUDE, INCLUDE, EXCLUDE),
# 37.0
'sw.msword' : ( 4, EXCLUDE, INCLUDE, INCLUDE),
# 22.4
'sw.sw' : ( 7, EXCLUDE, EXCLUDE, INCLUDE),
# 129.6
'sw.swui' : ( 3, EXCLUDE, INCLUDE, INCLUDE),
# 26.1
'sw.vbaswobj' : ( 4, EXCLUDE, INCLUDE, INCLUDE),
# 13.1
'sw.sw_writerfilter' : ( 5, EXCLUDE, EXCLUDE, EXCLUDE),
# 19.7/27.3
'tools.tl' : ( 5, EXCLUDE, EXCLUDE, EXCLUDE),
# 4.2
'unotools.utl' : ( 3, EXCLUDE, EXCLUDE, INCLUDE),
# 7.0
'unoxml.unoxml' : ( 1, EXCLUDE, EXCLUDE, EXCLUDE),
# 4.6
'uui.uui' : ( 4, EXCLUDE, EXCLUDE, EXCLUDE),
# 4.9
'vbahelper.msforms' : ( 3, EXCLUDE, INCLUDE, INCLUDE),
# 5.2
'vbahelper.vbahelper' : ( 3, EXCLUDE, EXCLUDE, INCLUDE),
# 7.0
'vcl.vcl' : ( 6, EXCLUDE, INCLUDE, INCLUDE),
# 35.7
'xmloff.xo' : ( 7, EXCLUDE, INCLUDE, INCLUDE),
# 22.1
'xmloff.xof' : ( 1, EXCLUDE, EXCLUDE, INCLUDE),
# 4.4
'xmlscript.xmlscript' : ( 4, EXCLUDE, EXCLUDE, INCLUDE),
# 3.6
'xmlsecurity.xmlsecurity' : ( 6, EXCLUDE, INCLUDE, INCLUDE),
# 5.1
'xmlsecurity.xsec_xmlsec' : ( 2, EXCLUDE, INCLUDE, INCLUDE),
# 4.4
'xmlsecurity.xsec_gpg' : ( 2, EXCLUDE, INCLUDE, INCLUDE),
# ?
}
def remove_rare(raw, min_use=-1):
""" Remove headers not commonly included.
The minimum threshold is min_use.
"""
# The minimum number of times a header
# must be included to be in the PCH.
min_use = min_use
if min_use >= 0
else CUTOFF
out = []
if not raw
or not len(raw):
return out
inc = sorted(raw)
last = inc[0]
count = 1
for x in range(1, len(inc)):
i = inc[x]
if i == last:
count += 1
else:
if count >= min_use:
out.append(last)
last = i
count = 1
# Last group.
if count >= min_use:
out.append(last)
return out
def process_list(list, callable):
""" Given a list and callable
we pass each entry through
the callable
and only add to
the output
if not blank.
"""
out = []
for i in list:
line = callable(i)
if line
and len(line):
out.append(line)
return out
def find_files(path, recurse=
True):
list = []
for root, dir, files in os.walk(path):
list += map(lambda x: os.path.join(root, x), files)
return list
def get_filename(line):
""" Strips the line from the
'#include' and angled brackets
and return the filename only.
"""
if not len(line)
or line[0] !=
'#':
return line
return re.sub(r
'(.*#include\s*)<(.*)>(.*)', r
'\2', line)
def is_c_runtime(inc, root, module):
""" Heuristic-based detection of C/C++
runtime headers.
They are all-lowercase, with .h
or
no extension, filename only.
Try to check that they are
not LO headers.
"""
inc = get_filename(inc)
if inc.endswith(
'.hxx')
or inc.endswith(
'.hpp'):
return False
if inc.endswith(
'.h')
and inc.startswith(
'config_' ):
return False
hasdot =
False
for c in inc:
if c ==
'/':
return False
if c ==
'.' and not inc.endswith(
'.h'):
return False
if c ==
'.':
hasdot =
True
if c.isupper():
return False
if not hasdot:
# <memory> etc.
return True
if glob.glob(os.path.join(root, module,
'**', inc), recursive=
True):
return False;
return True
def sanitize(raw):
""" There are two forms of includes,
those with <>
and "".
Technically, the difference is that
the compiler can use an internal
representation
for an angled include,
such that it doesn
't have to be a file.
For our purposes, there is no difference.
Here, we convert everything to angled.
"""
if not raw
or not len(raw):
return ''
raw = raw.strip()
if not len(raw):
return ''
return re.sub(r
'(.*#include\s*)\"(.*)\"(.*)', r
'#include <\2>', raw)
class Filter_Local(object):
""" Filter headers local to a module.
allow_public: allows include/module/file.hxx
#include <module/file.hxx>
allow_module: allows module/inc/file.hxx
#include <file.hxx>
allow_locals: allows module/source/file.hxx
and
module/source/inc/file.hxx
#include <file.hxx>
"""
def __init__(self, root, module, allow_public=
True, allow_module=
True, allow_locals=
True):
self.root = root
self.module = module
self.allow_public = allow_public
self.allow_module = allow_module
self.allow_locals = allow_locals
self.public_prefix =
'<' + self.module +
'/'
all = find_files(os.path.join(root, module))
self.module_includes = []
self.locals = []
mod_prefix = module +
'/inc/'
for i in all:
if mod_prefix in i:
self.module_includes.append(i)
else:
self.locals.append(i)
def is_public(self, line):
return self.public_prefix in line
def is_module(self, line):
""" Returns True if in module/inc/... """
filename = get_filename(line)
for i in self.module_includes:
if i.endswith(filename):
return True
return False
def is_local(self, line):
""" Returns True if in module/source/... """
filename = get_filename(line)
for i in self.locals:
if i.endswith(filename):
return True
return False
def is_external(self, line):
return is_c_runtime(line, self.root, self.module)
and \
not self.is_public(line)
and \
not self.is_module(line)
and \
not self.is_local(line)
def find_local_file(self, line):
""" Finds the header file in the module dir,
but doesn
't validate.
"""
filename = get_filename(line)
for i in self.locals:
if i.endswith(filename):
return i
for i in self.module_includes:
if i.endswith(filename):
return i
return None
def proc(self, line):
assert line
and len(line)
if line[0] ==
'#':
if not SILENT:
sys.stderr.write(
'unhandled #include : {}\n'.format(line))
return ''
assert line[0] !=
'<' and line[0] !=
'#'
filename = get_filename(line)
# Local with relative path.
if filename.startswith(
'..'):
# Exclude
for now as we don
't have cxx path.
return ''
# Locals are included first (by the compiler).
if self.is_local(filename):
# Use only locals that are in some /inc/ directory (either in <module>/inc
or
# somewhere under <module>/source
/**/inc/, compilations use -I for these paths
# and headers elsewhere would
not be found when compiling the PCH.
if not self.allow_locals:
return ''
elif
'/inc/' in filename:
return filename
elif glob.glob(os.path.join(self.root, self.module,
'**',
'inc', filename), recursive=
True):
return filename
else:
return ''
# Module headers are next.
if self.is_module(filename):
return line
if self.allow_module
else ''
# Public headers are last.
if self.is_public(line):
return line
if self.allow_public
else ''
# Leave out potentially unrelated files local
# to some other module we can
't include directly.
if '/' not in filename
and not self.is_external(filename):
return ''
# Unfiltered.
return line
def filter_ignore(line, module):
""" Filters includes from known
problematic ones.
Expects sanitized input.
"""
assert line
and len(line)
# Always include files without extension.
if '.' not in line:
return line
# Extract filenames
for ease of comparison.
line = get_filename(line)
# Filter out all files that are
not normal headers.
if not line.endswith(
'.h')
and \
not line.endswith(
'.hxx')
and \
not line.endswith(
'.hpp')
and \
not line.endswith(
'.hdl'):
return ''
ignore_list = [
'LibreOfficeKit/LibreOfficeKitEnums.h',
# Needs special directives
'LibreOfficeKit/LibreOfficeKitTypes.h',
# Needs special directives
'jerror.h',
# c++ unfriendly
'jpeglib.h',
# c++ unfriendly
'boost/spirit/include/classic_core.hpp' # depends on BOOST_SPIRIT_DEBUG
]
if module ==
'basic':
ignore_list += [
'basic/vbahelper.hxx',
]
if module ==
'connectivity':
ignore_list += [
'com/sun/star/beans/PropertyAttribute.hpp',
# OPTIONAL
defined via objbase.h
'com/sun/star/sdbcx/Privilege.hpp',
# DELETE defined via objbase.h
'ado/*' , # some strange type conflict because of Window's adoctint.h
'adoint.h',
'adoctint.h',
]
if module ==
'sc':
ignore_list += [
'progress.hxx',
# special directives
'scslots.hxx',
# special directives
]
if module ==
'sd':
ignore_list += [
'sdgslots.hxx',
# special directives
'sdslots.hxx',
# special directives
]
if module ==
'sfx2':
ignore_list += [
'sfx2/recentdocsview.hxx',
# Redefines ApplicationType
defined in objidl.h
'sfx2/sidebar/Sidebar.hxx',
'sfx2/sidebar/UnoSidebar.hxx',
'sfxslots.hxx',
# externally
defined types
]
if module ==
'sot':
ignore_list += [
'sysformats.hxx',
# Windows headers
]
if module ==
'vcl':
ignore_list += [
'accmgr.hxx',
# redefines ImplAccelList
'image.h',
'jobset.h',
'opengl/gdiimpl.hxx',
'opengl/salbmp.hxx',
'openglgdiimpl',
# ReplaceTextA
'printdlg.hxx',
'salinst.hxx',
# GetDefaultPrinterA
'salprn.hxx',
# SetPrinterDataA
'vcl/jobset.hxx',
'vcl/oldprintadaptor.hxx',
'vcl/opengl/OpenGLContext.hxx',
'vcl/opengl/OpenGLHelper.hxx',
# Conflicts with X header on *ix
'vcl/print.hxx',
'vcl/prntypes.hxx',
# redefines Orientation from filter/jpeg/Exif.hxx
'vcl/sysdata.hxx',
]
if module ==
'xmloff':
ignore_list += [
'SchXMLExport.hxx',
# SchXMLAutoStylePoolP.hxx
not found
'SchXMLImport.hxx',
# enums redefined in draw\sdxmlimp_impl.hxx
'XMLEventImportHelper.hxx',
# NameMap redefined in XMLEventExport.hxx
'xmloff/XMLEventExport.hxx',
# enums redefined
]
if module ==
'xmlsecurity':
ignore_list += [
'xmlsec/*',
]
if module ==
'external/pdfium':
ignore_list += [
'third_party/freetype/include/pstables.h',
]
if module ==
'external/clucene':
ignore_list += [
'_bufferedstream.h',
'_condition.h',
'_gunichartables.h',
'_threads.h',
'error.h',
'CLucene/LuceneThreads.h',
'CLucene/config/_threads.h',
]
if module ==
'external/skia':
ignore_list += [
'skcms_internal.h',
'zlib.h',
# causes crc32 conflict
'dirent.h',
# unix-specific
'pthread.h',
'unistd.h',
'sys/stat.h',
'ft2build.h',
'fontconfig/fontconfig.h',
'GL/glx.h',
'src/Transform_inl.h',
'src/c/sk_c_from_to.h',
'src/c/sk_types_priv.h',
'src/core/SkBlitBWMaskTemplate.h',
'src/sfnt/SkSFNTHeader.h',
'src/opts/',
'src/core/SkCubicSolver.h',
'src/sksl/SkSLCPP.h',
'src/gpu/vk/GrVkAMDMemoryAllocator.h',
'src/gpu/GrUtil.h',
'src/sksl/',
# conflict between SkSL::Expression
and SkSL::dsl::Expression
'include/sksl/',
'src/gpu/vk/',
'include/gpu/vk'
]
if module ==
'external/zxing':
ignore_list += [
'rss/ODRSSExpandedBinaryDecoder.h'
]
for i in ignore_list:
if line.startswith(i):
return ''
if i[0] ==
'*' and line.endswith(i[1:]):
return ''
if i[-1] ==
'*' and line.startswith(i[:-1]):
return ''
return line
def fixup(includes, module):
""" Here we add any headers
necessary in the pch.
These could be known to be very
common but
for technical reasons
left out of the pch by
this generator.
Or, they could be missing from the
source files where they are used
(probably because they had been
in the old pch, they were missed).
Also, these could be headers
that make the build faster but
aren
't added automatically.
"""
fixes = []
def append(inc):
# Add a space to exclude from
# ignore bisecting.
line =
' #include <{}>'.format(inc)
try:
i = fixes.index(inc)
fixes[i] = inc
except:
fixes.append(inc)
append(
'sal/config.h')
if module ==
'basctl':
if 'basslots.hxx' in includes:
append(
'sfx2/msg.hxx')
#if module ==
'sc':
# if 'scslots.hxx' in includes:
# append(
'sfx2/msg.hxx')
return fixes
def sort_by_category(list, root, module, filter_local):
""" Move all 'system' headers first.
Core files of osl, rtl, sal, next.
Everything non-module-specific third.
Last, module-specific headers.
"""
sys = []
boo = []
cor = []
rst = []
mod = []
prefix =
'<' + module +
'/'
for i in list:
if 'sal/config.h' in i:
continue # added unconditionally in fixup
if is_c_runtime(i, root, module):
sys.append(i)
elif
'<boost/' in i:
boo.append(i)
elif prefix in i
or not '/' in i:
mod.append(i)
elif
'<sal/' in i
or '<vcl/' in i:
cor.append(i)
elif
'<osl/' in i
or '<rtl/' in i:
if module ==
"sal":
# osl
and rtl are also part of sal
mod.append(i)
else:
cor.append(i)
# Headers from another module that is closely tied to the module.
elif module ==
'sc' and '<formula' in i:
mod.append(i)
else:
rst.append(i)
out = []
out += [
"#if PCH_LEVEL >= 1" ]
out += sorted(sys)
out += sorted(boo)
out += [
"#endif // PCH_LEVEL >= 1" ]
out += [
"#if PCH_LEVEL >= 2" ]
out += sorted(cor)
out += [
"#endif // PCH_LEVEL >= 2" ]
out += [
"#if PCH_LEVEL >= 3" ]
out += sorted(rst)
out += [
"#endif // PCH_LEVEL >= 3" ]
out += [
"#if PCH_LEVEL >= 4" ]
out += sorted(mod)
out += [
"#endif // PCH_LEVEL >= 4" ]
return out
def parse_makefile(groups, lines, lineno, lastif, ifstack):
inobjects =
False
ingeneratedobjects =
False
inelse =
False
suffix =
'cxx'
os_cond_re = re.compile(r
'(ifeq|ifneq)\s*\(\$\(OS\)\,(\w*)\)')
line = lines[lineno]
if line.startswith(
'if'):
lastif = line
if ifstack == 0:
# Correction
if first line is an
if.
lineno = parse_makefile(groups, lines, lineno, line, ifstack+1)
else:
lineno -= 1
while lineno + 1 < len(lines):
lineno += 1
line = lines[lineno].strip()
line = line.rstrip(
'\\').strip()
#print(
'line #{}: {}'.format(lineno, line))
if len(line) == 0:
continue
if line ==
'))':
inobjects =
False
ingeneratedobjects =
False
elif
'add_exception_objects' in line
or \
'add_cxxobject' in line:
inobjects =
True
#print(
'inobjects')
#if ifstack
and not SILENT:
#sys.stderr.write(
'Sources in a conditional, ignoring for now.\n')
elif
'add_generated_exception_objects' in line
or \
'add_generated_cxxobject' in line:
ingeneratedobjects =
True
elif
'set_generated_cxx_suffix' in line:
suffix_re = re.compile(
'.*set_generated_cxx_suffix,[^,]*,([^)]*).*')
match = suffix_re.match(line)
if match:
suffix = match.group(1)
elif line.startswith(
'if'):
lineno = parse_makefile(groups, lines, lineno, line, ifstack+1)
continue
elif line.startswith(
'endif'):
if ifstack:
return lineno
continue
elif line.startswith(
'else'):
inelse =
True
elif inobjects
or ingeneratedobjects:
if EXCLUDE_SYSTEM
and ifstack:
continue
file = line +
'.' + suffix
if ',' in line
or '(' in line
or ')' in line
or file.startswith(
'-'):
#print(
'passing: ' + line)
pass
# $
if() probably,
or something similar
else:
osname =
''
if lastif:
if 'filter' in lastif:
# We can
't grok filter, yet.
continue
match = os_cond_re.match(lastif)
if not match:
# We only support OS conditionals.
continue
in_out = match.group(1)
osname = match.group(2)
if match
else ''
if (in_out ==
'ifneq' and not inelse)
or \
(in_out ==
'ifeq' and inelse):
osname =
'!' + osname
if osname
not in groups:
groups[osname] = []
if ingeneratedobjects:
file = WORKDIR +
'/' + file
groups[osname].append(file)
return groups
def process_makefile(root, module, libname):
""" Parse a gmake makefile and extract
source filenames from it.
"""
makefile =
'Library_{}.mk'.format(libname)
filename = os.path.join(os.path.join(root, module), makefile)
if not os.path.isfile(filename):
makefile =
'StaticLibrary_{}.mk'.format(libname)
filename = os.path.join(os.path.join(root, module), makefile)
if not os.path.isfile(filename):
sys.stderr.write(
'Error: Module {} has no makefile at {}.'.format(module, filename))
groups = {
'':[],
'ANDROID':[],
'iOS':[],
'WNT':[],
'LINUX':[],
'MACOSX':[]}
with open(filename,
'r') as f:
lines = f.readlines()
groups = parse_makefile(groups, lines, lineno=0, lastif=None, ifstack=0)
return groups
def is_allowed_if(line, module):
""" Check whether the given #if condition
is allowed
for the given module
or whether
its block should be ignored.
"""
# remove trailing comments
line = re.sub(r
'(.*) *//.*', r'\1', line)
line = line.strip()
# Our sources always build with LIBO_INTERNAL_ONLY.
if line ==
"#if defined LIBO_INTERNAL_ONLY" or line ==
"#ifdef LIBO_INTERNAL_ONLY":
return True
# We use PCHs only
for C++.
if line ==
"#if defined(__cplusplus)" or line ==
"#if defined __cplusplus":
return True
# Debug-specific code, it shouldn
't hurt including it unconditionally.
if line ==
"#ifdef DBG_UTIL" or line ==
"#if OSL_DEBUG_LEVEL > 0":
return True
if module ==
"external/skia":
# We always set these.
if line ==
"#ifdef SK_VULKAN" or line ==
"#if SK_GANESH":
return True
return False
def process_source(root, module, filename, maxdepth=0):
""" Process a source file to extract
included headers.
For now, skip on compiler directives.
maxdepth is used when processing headers
which typically have protecting ifndef.
"""
ifdepth = 0
lastif =
''
raw_includes = []
allowed_ifs = []
ifsallowed = 0
with open(filename,
'r') as f:
for line in f:
line = line.strip()
if line.startswith(
'#if'):
if is_allowed_if(line, module):
allowed_ifs.append(
True)
ifsallowed += 1
else:
allowed_ifs.append(
False)
lastif = line
ifdepth += 1
elif line.startswith(
'#endif'):
ifdepth -= 1
if allowed_ifs[ ifdepth ]:
ifsallowed -= 1
else:
lastif =
'#if'
del allowed_ifs[ ifdepth ]
elif line.startswith(
'#pragma once'):
# maxdepth == 1 means we are parsing a header file
# and are allowed one
#ifdef block (the include guard),
# but in the
#pragma once
case do not allow that
assert maxdepth == 1
maxdepth = 0
elif line.startswith(
'#include'):
if ifdepth - ifsallowed <= maxdepth:
line = sanitize(line)
if line:
line = get_filename(line)
if line
and len(line):
raw_includes.append(line)
elif
not SILENT:
sys.stderr.write(
'#include in {} : {}\n'.format(lastif, line))
return raw_includes
def explode(root, module, includes, tree, filter_local, recurse):
incpath = os.path.join(root,
'include')
for inc in includes:
filename = get_filename(inc)
if filename in tree
or len(filter_local.proc(filename)) == 0:
continue
try:
# Module
or Local header.
filepath = filter_local.find_local_file(inc)
if filepath:
#print(
'trying loc: ' + filepath)
incs = process_source(root, module, filepath, maxdepth=1)
incs = map(get_filename, incs)
incs = process_list(incs, lambda x: filter_ignore(x, module))
incs = process_list(incs, filter_local.proc)
tree[filename] = incs
if recurse:
tree = explode(root, module, incs, tree, filter_local, recurse)
#print(
'{} => {}'.format(filepath, tree[filename]))
continue
except:
pass
try:
# Public header.
filepath = os.path.join(incpath, filename)
#print(
'trying pub: ' + filepath)
incs = process_source(root, module, filepath, maxdepth=1)
incs = map(get_filename, incs)
incs = process_list(incs, lambda x: filter_ignore(x, module))
incs = process_list(incs, filter_local.proc)
tree[filename] = incs
if recurse:
tree = explode(root, module, incs, tree, filter_local, recurse)
#print(
'{} => {}'.format(filepath, tree[filename]))
continue
except:
pass
# Failed, but remember to avoid searching again.
tree[filename] = []
return tree
def make_command_line():
args = sys.argv[:]
# Remove command line flags
and
# use internal flags.
for i in range(len(args)-1, 0, -1):
if args[i].startswith(
'--'):
args.pop(i)
args.append(
'--cutoff=' + str(CUTOFF))
if EXCLUDE_SYSTEM:
args.append(
'--exclude:system')
else:
args.append(
'--include:system')
if EXCLUDE_MODULE:
args.append(
'--exclude:module')
else:
args.append(
'--include:module')
if EXCLUDE_LOCAL:
args.append(
'--exclude:local')
else:
args.append(
'--include:local')
return ' '.join(args)
def generate_includes(includes):
"""Generates the include lines of the pch.
"""
lines = []
for osname, group in includes.items():
if not len(group):
continue
if len(osname):
not_eq =
''
if osname[0] ==
'!':
not_eq =
'!'
osname = osname[1:]
lines.append(
'')
lines.append(
'#if {}defined({})'.format(
not_eq, osname))
for i in group:
lines.append(i)
if len(osname):
lines.append(
'#endif')
return lines
def generate(includes, libname, filename, module):
header = \
"""/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
* This file is part of the LibreOffice project.
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
/*
This file has been autogenerated by update_pch.sh. It is possible to edit it
manually (such as when an include file has been moved/renamed/removed). All such
manual changes will be rewritten by the next run of update_pch.sh (which presumably
also fixes all possible problems, so it's usually better to use it).
"""
footer = \
"""
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
"""
import datetime
with open(filename,
'w') as f:
f.write(header)
f.write(
'\n Generated on {} using:\n {}\n'.format(
datetime.datetime.now().strftime(
"%Y-%m-%d %H:%M:%S"),
make_command_line()))
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(
filename, module))
# sal needs
this for rand_s()
if module ==
'sal' and libname ==
'sal':
sal_define =
"""
#if defined(_WIN32)
#define _CRT_RAND_S
#endif
"""
f.write(sal_define)
# Dump the headers.
f.write(
'\n')
for i in includes:
f.write(i +
'\n')
# Some libraries pull windows headers that aren
't self contained.
if (module ==
'connectivity' and libname ==
'ado')
or \
(module ==
'xmlsecurity' and libname ==
'xsec_xmlsec'):
ado_define =
"""
// Cleanup windows header macro pollution.
#if defined(_WIN32) &&
defined(WINAPI)
#include <postwin.h>
#undef RGB
#endif
"""
f.write(ado_define)
f.write(footer)
def remove_from_tree(filename, tree):
# Remove
this file,
if top-level.
incs = tree.pop(filename, [])
for i in incs:
tree = remove_from_tree(i, tree)
# Also remove
if included from another.
for (k, v) in tree.items():
if filename in v:
v.remove(filename)
return tree
def tree_to_list(includes, filename, tree):
if filename in includes:
return includes
includes.append(filename)
#incs = tree.pop(filename, [])
incs = tree[filename]
if filename in tree
else []
for i in incs:
tree_to_list(includes, i, tree)
return includes
def promote(includes):
""" Common library headers are heavily
referenced, even
if they are included
from a few places.
Here we separate them to promote
their inclusion in the final pch.
"""
promo = []
for inc in includes:
if inc.startswith(
'boost')
or \
inc.startswith(
'sal')
or \
inc.startswith(
'osl')
or \
inc.startswith(
'rtl'):
promo.append(inc)
return promo
def make_pch_filename(root, module, libname):
""" PCH files are stored here:
<root>/<module>/inc/pch/precompiled_<libname>.hxx
"""
path = os.path.join(root, module)
path = os.path.join(path,
'inc')
path = os.path.join(path,
'pch')
path = os.path.join(path,
'precompiled_' + libname +
'.hxx')
return path
def main():
global CUTOFF
global EXCLUDE_MODULE
global EXCLUDE_LOCAL
global EXCLUDE_SYSTEM
global SILENT
global WORKDIR
if os.getenv(
'WORKDIR'):
WORKDIR = os.getenv(
'WORKDIR')
root =
'.'
module = sys.argv[1]
libname = sys.argv[2]
header = make_pch_filename(root, module, libname)
if not os.path.exists(os.path.join(root, module)):
raise Exception(
'Error: module [{}] not found.'.format(module))
key =
'{}.{}'.format(module, libname)
if key in DEFAULTS:
# Load the module-specific defaults.
CUTOFF = DEFAULTS[key][0]
EXCLUDE_SYSTEM = DEFAULTS[key][1]
EXCLUDE_MODULE = DEFAULTS[key][2]
EXCLUDE_LOCAL = DEFAULTS[key][3]
force_update =
False
for x in range(3, len(sys.argv)):
i = sys.argv[x]
if i.startswith(
'--cutoff='):
CUTOFF =
int(i.split(
'=')[1])
elif i.startswith(
'--exclude:'):
cat = i.split(
':')[1]
if cat ==
'module':
EXCLUDE_MODULE =
True
elif cat ==
'local':
EXCLUDE_LOCAL =
True
elif cat ==
'system':
EXCLUDE_SYSTEM =
True
elif i.startswith(
'--include:'):
cat = i.split(
':')[1]
if cat ==
'module':
EXCLUDE_MODULE =
False
elif cat ==
'local':
EXCLUDE_LOCAL =
False
elif cat ==
'system':
EXCLUDE_SYSTEM =
False
elif i ==
'--silent':
SILENT =
True
elif i ==
'--force':
force_update =
True
else:
sys.stderr.write(
'Unknown option [{}].'.format(i))
return 1
filter_local = Filter_Local(root, module, \
not EXCLUDE_MODULE, \
not EXCLUDE_LOCAL)
# Read input.
groups = process_makefile(root, module, libname)
generic = []
for osname, group in groups.items():
if not len(group):
continue
includes = []
for filename in group:
includes += process_source(root, module, filename)
# Save unique top-level includes.
unique = set(includes)
promoted = promote(unique)
# Process includes.
includes = remove_rare(includes)
includes = process_list(includes, lambda x: filter_ignore(x, module))
includes = process_list(includes, filter_local.proc)
# Remove the already included ones.
for inc in includes:
unique.discard(inc)
# Explode the excluded ones.
tree = {i:[]
for i in includes}
tree = explode(root, module, unique, tree, filter_local,
not EXCLUDE_MODULE)
# Remove the already included ones from the tree.
for inc in includes:
filename = get_filename(inc)
tree = remove_from_tree(filename, tree)
extra = []
for (k, v) in tree.items():
extra += tree_to_list([], k, tree)
promoted += promote(extra)
promoted = process_list(promoted, lambda x: filter_ignore(x, module))
promoted = process_list(promoted, filter_local.proc)
promoted = set(promoted)
# If a promoted header includes others, remove the rest.
for (k, v) in tree.items():
if k in promoted:
for i in v:
promoted.discard(i)
includes += [x
for x in promoted]
extra = remove_rare(extra)
extra = process_list(extra, lambda x: filter_ignore(x, module))
extra = process_list(extra, filter_local.proc)
includes += extra
includes = [x
for x in set(includes)]
fixes = fixup(includes, module)
fixes = map(lambda x:
'#include <' + x +
'>', fixes)
includes = map(lambda x:
'#include <' + x +
'>', includes)
sorted = sort_by_category(includes, root, module, filter_local)
includes = list(fixes) + sorted
if len(osname):
for i in generic:
if i in includes:
includes.remove(i)
groups[osname] = includes
if not len(osname):
generic = includes
# Open the old pch
and compare its contents
# with
new includes.
# Clobber only
if they are different.
with open(header,
'r') as f:
old_pch_lines = [x.strip()
for x in f.readlines()]
new_lines = generate_includes(groups)
# Find the first include in the old pch.
start = -1
for i in range(len(old_pch_lines)):
if old_pch_lines[i].startswith(
'#include')
or old_pch_lines[i].startswith(
'#if PCH_LEVEL'):
start = i
break
# Clobber
if there is a mismatch.
if force_update
or start < 0
or (len(old_pch_lines) - start < len(new_lines)):
generate(new_lines, libname, header, module)
return 0
else:
for i in range(len(new_lines)):
if new_lines[i] != old_pch_lines[start + i]:
generate(new_lines, libname, header, module)
return 0
else:
# Identical, but see
if new pch removed anything.
for i in range(start + len(new_lines), len(old_pch_lines)):
if '#include' in old_pch_lines[i]:
generate(new_lines, libname, header, module)
return 0
# Didn
't update.
# Use
exit code 2 to distinguish it from
exit code 1 used e.g. when an exception occurs.
return 2
if __name__ ==
'__main__':
""" Process all the includes in a Module
to make into a PCH file.
Run without arguments
for unittests,
and to see usage.
"""
if len(sys.argv) >= 3:
status = main()
sys.
exit(status)
print(
'Usage: {} <Module name> <Library name> [options]'.format(sys.argv[0]))
print(
' Always run from the root of LO repository.\n')
print(
' Options:')
print(
' --cutoff=<count> - Threshold to excluding headers.')
print(
' --exclude:<category> - Exclude category-specific headers.')
print(
' --include:<category> - Include category-specific headers.')
print(
' --force - Force updating the pch even when nothing changes.')
print(
' Categories:')
print(
' module - Headers in /inc directory of a module.')
print(
' local - Headers local to a source file.')
print(
' system - Platform-specific headers.')
print(
' --silent - print only errors.')
print(
'\nRunning unit-tests...')
class TestMethods(unittest.TestCase):
def test_sanitize(self):
self.assertEqual(sanitize(
'#include "blah/file.cxx"'),
'#include <blah/file.cxx>')
self.assertEqual(sanitize(
' #include\t"blah/file.cxx" '),
'#include <blah/file.cxx>')
self.assertEqual(sanitize(
' '),
'')
def test_filter_ignore(self):
self.assertEqual(filter_ignore(
'blah/file.cxx',
'mod'),
'')
self.assertEqual(filter_ignore(
'vector',
'mod'),
'vector')
self.assertEqual(filter_ignore(
'file.cxx',
'mod'),
'')
def test_remove_rare(self):
self.assertEqual(remove_rare([]),
[])
class TestMakefileParser(unittest.TestCase):
def setUp(self):
global EXCLUDE_SYSTEM
EXCLUDE_SYSTEM =
False
def test_parse_singleline_eval(self):
source =
"$(eval $(call gb_Library_Library,sal))"
lines = source.split(
'\n')
groups = {
'':[]}
groups = parse_makefile(groups, lines, 0, None, 0)
self.assertEqual(len(groups), 1)
self.assertEqual(len(groups[
'']), 0)
def test_parse_multiline_eval(self):
source =
"""$(eval $(call gb_Library_set_include,sal,\\
$$(INCLUDE) \\
-I$(SRCDIR)/sal/inc \\
))
"""
lines = source.split(
'\n')
groups = {
'':[]}
groups = parse_makefile(groups, lines, 0, None, 0)
self.assertEqual(len(groups), 1)
self.assertEqual(len(groups[
'']), 0)
def test_parse_multiline_eval_with_if(self):
source =
"""$(eval $(call gb_Library_add_defs,sal,\\
$(
if $(filter $(OS),iOS), \\
-DNO_CHILD_PROCESSES \\
) \\
))
"""
lines = source.split(
'\n')
groups = {
'':[]}
groups = parse_makefile(groups, lines, 0, None, 0)
self.assertEqual(len(groups), 1)
self.assertEqual(len(groups[
'']), 0)
def test_parse_multiline_add_with_if(self):
source =
"""$(eval $(call gb_Library_add_exception_objects,sal,\\
sal/osl/unx/time \\
$(
if $(filter DESKTOP,$(BUILD_TYPE)), sal/osl/unx/salinit) \\
))
"""
lines = source.split(
'\n')
groups = {
'':[]}
groups = parse_makefile(groups, lines, 0, None, 0)
self.assertEqual(len(groups), 1)
self.assertEqual(len(groups[
'']), 1)
self.assertEqual(groups[
''][0],
'sal/osl/unx/time.cxx')
def test_parse_if_else(self):
source =
"""ifeq ($(OS),MACOSX)
$(eval $(call gb_Library_add_exception_objects,sal,\\
sal/osl/mac/mac \\
))
else
$(eval $(call gb_Library_add_exception_objects,sal,\\
sal/osl/unx/uunxapi \\
))
endif
"""
lines = source.split(
'\n')
groups = {
'':[]}
groups = parse_makefile(groups, lines, 0, None, 0)
self.assertEqual(len(groups), 3)
self.assertEqual(len(groups[
'']), 0)
self.assertEqual(len(groups[
'MACOSX']), 1)
self.assertEqual(len(groups[
'!MACOSX']), 1)
self.assertEqual(groups[
'MACOSX'][0],
'sal/osl/mac/mac.cxx')
self.assertEqual(groups[
'!MACOSX'][0],
'sal/osl/unx/uunxapi.cxx')
def test_parse_nested_if(self):
source =
"""ifeq ($(OS),MACOSX)
$(eval $(call gb_Library_add_exception_objects,sal,\\
sal/osl/mac/mac \\
))
else
$(eval $(call gb_Library_add_exception_objects,sal,\\
sal/osl/unx/uunxapi \\
))
ifeq ($(OS),LINUX)
$(eval $(call gb_Library_add_exception_objects,sal,\\
sal/textenc/context \\
))
endif
endif
"""
lines = source.split(
'\n')
groups = {
'':[]}
groups = parse_makefile(groups, lines, 0, None, 0)
self.assertEqual(len(groups), 4)
self.assertEqual(len(groups[
'']), 0)
self.assertEqual(len(groups[
'MACOSX']), 1)
self.assertEqual(len(groups[
'!MACOSX']), 1)
self.assertEqual(len(groups[
'LINUX']), 1)
self.assertEqual(groups[
'MACOSX'][0],
'sal/osl/mac/mac.cxx')
self.assertEqual(groups[
'!MACOSX'][0],
'sal/osl/unx/uunxapi.cxx')
self.assertEqual(groups[
'LINUX'][0],
'sal/textenc/context.cxx')
def test_parse_exclude_system(self):
source =
"""ifeq ($(OS),MACOSX)
$(eval $(call gb_Library_add_exception_objects,sal,\\
sal/osl/mac/mac \\
))
else
$(eval $(call gb_Library_add_exception_objects,sal,\\
sal/osl/unx/uunxapi \\
))
ifeq ($(OS),LINUX)
$(eval $(call gb_Library_add_exception_objects,sal,\\
sal/textenc/context \\
))
endif
endif
"""
global EXCLUDE_SYSTEM
EXCLUDE_SYSTEM =
True
lines = source.split(
'\n')
groups = {
'':[]}
groups = parse_makefile(groups, lines, 0, None, 0)
self.assertEqual(len(groups), 1)
self.assertEqual(len(groups[
'']), 0)
def test_parse_filter(self):
source =
"""ifneq ($(filter $(OS),MACOSX iOS),)
$(eval $(call gb_Library_add_exception_objects,sal,\\
sal/osl/unx/osxlocale \\
))
endif
"""
# Filter is still unsupported.
lines = source.split(
'\n')
groups = {
'':[]}
groups = parse_makefile(groups, lines, 0, None, 0)
self.assertEqual(len(groups), 1)
self.assertEqual(len(groups[
'']), 0)
unittest.main()
# vim: set et sw=4 ts=4 expandtab: