Implement a more direct headersclean check

Test each include file directly, instead of doing a large #include. This
verifies that each header is compilable on its own. One big advantage of
doing it via a special compiler in qmake is that we skip pre-compiled
headers, which has hidden build errors in the past.

This solution is implemented by making syncqt produce a second list of
headers.  This list is the same as the list of headers in the source
code to be installed, minus the headers that declare themselves to be
unclean, via the pragma:
  #pragma qt_sync_skip_header_check

This mechanism is applied only for public libraries (skipping
QtPlatformSupport, an internal_module).

This test is enabled only for -developer-builds of Qt because it
increases the compilation time.

On QtTest: the library only links to QtCore, but it has two headers that
provide inline-only functionality by including QtGui and QtWidgets
headers (namely, qtest_gui.h and qtest_widget.h). If those two modules
aren't getting compiled due to -no-gui or -no-widgets to configure, we
need to remove the respective headers from the list of headers to be
checked. If they are being built, then we need to make QtTest's build
wait for the headers to be generated and that happens when qmake is
first run inside the src/gui and src/widgets directories.

Change-Id: I57d64bd697a92367c8464c073a42e4d142a9a15f
Reviewed-by: Oswald Buddenhagen <oswald.buddenhagen@theqtcompany.com>
This commit is contained in:
Thiago Macieira 2012-12-22 13:12:02 -08:00
parent 964ccc5853
commit 6668f5becf
8 changed files with 138 additions and 12 deletions

View File

@ -190,8 +190,9 @@ sub shouldMasterInclude {
}
######################################################################
# Syntax: classNames(iheader)
# Syntax: classNames(iheader, clean)
# Params: iheader, string, filename to parse for classname "symlinks"
# (out) clean, boolean, will be set to false if the header isn't clean
#
# Purpose: Scans through iheader to find all classnames that should be
# synced into library's include structure.
@ -199,7 +200,8 @@ sub shouldMasterInclude {
######################################################################
sub classNames {
my @ret;
my ($iheader) = @_;
my ($iheader, $clean) = @_;
$$clean = 1;
my $ihdrbase = basename($iheader);
my $classname = $classnames{$ihdrbase};
@ -212,6 +214,7 @@ sub classNames {
chomp $line;
chop $line if ($line =~ /\r$/);
if($line =~ /^\#/) {
$$clean = 0 if ($line =~ m/^#pragma qt_sync_skip_header_check/);
return @ret if($line =~ m/^#pragma qt_sync_stop_processing/);
push(@ret, $1) if($line =~ m/^#pragma qt_class\(([^)]*)\)[\r\n]*$/);
$line = 0;
@ -828,6 +831,7 @@ foreach my $lib (@modules_to_sync) {
my $pri_install_pfiles = "";
my $pri_install_qpafiles = "";
my $pri_injections = "";
my $pri_clean_files = "";
my $libcapitals = uc($lib);
my $master_contents =
@ -929,9 +933,10 @@ foreach my $lib (@modules_to_sync) {
}
}
my $clean_header;
my $iheader = $subdir . "/" . $header;
$iheader =~ s/^\Q$basedir\E/$out_basedir/ if ($shadow);
my @classes = $public_header && (!$minimal && $is_qt) ? classNames($iheader) : ();
my @classes = $public_header && (!$minimal && $is_qt) ? classNames($iheader, \$clean_header) : ();
if($showonly) {
print "$header [$lib]\n";
foreach(@classes) {
@ -980,6 +985,7 @@ foreach my $lib (@modules_to_sync) {
$injection .= ":$class";
}
$pri_install_files.= "$pri_install_iheader ";;
$pri_clean_files .= "$pri_install_iheader " if ($clean_header);
}
elsif ($qpa_header) {
$pri_install_qpafiles.= "$pri_install_iheader ";;
@ -1120,6 +1126,7 @@ foreach my $lib (@modules_to_sync) {
$headers_pri_contents .= "SYNCQT.HEADER_CLASSES = $pri_install_classes\n";
$headers_pri_contents .= "SYNCQT.PRIVATE_HEADER_FILES = $pri_install_pfiles\n";
$headers_pri_contents .= "SYNCQT.QPA_HEADER_FILES = $pri_install_qpafiles\n";
$headers_pri_contents .= "SYNCQT.CLEAN_HEADER_FILES = $pri_clean_files\n";
$headers_pri_contents .= "SYNCQT.INJECTIONS = $pri_injections\n";
my $headers_pri_file = "$out_basedir/include/$lib/headers.pri";
writeFile($headers_pri_file, $headers_pri_contents, $lib, "headers.pri file");

20
configure vendored
View File

@ -762,6 +762,7 @@ QPA_PLATFORM_GUARD=yes
CFG_CXX11=auto
CFG_DIRECTWRITE=no
CFG_WERROR=auto
CFG_HEADERSCLEAN=auto
CFG_QREAL=double
OPT_MAC_SDK=
COMMERCIAL_USER=ask
@ -2240,6 +2241,13 @@ while [ "$#" -gt 0 ]; do
UNKNOWN_OPT=yes
fi
;;
headersclean)
if [ "$VAL" = "yes" ] || [ "$VAL" = "no" ]; then
CFG_HEADERSCLEAN="$VAL"
else
UNKNOWN_OPT=yes
fi
;;
xkb-config-root)
CFG_XKB_CONFIG_ROOT="$VAL"
;;
@ -6712,8 +6720,16 @@ if [ "$CFG_DEV" = "yes" ]; then
if [ "$CFG_WERROR" != "no" ]; then
QMAKE_CONFIG="$QMAKE_CONFIG warnings_are_errors"
fi
elif [ "$CFG_WERROR" = "yes" ]; then
QMAKE_CONFIG="$QMAKE_CONFIG warnings_are_errors"
if [ "$CFG_HEADERSCLEAN" != "no" ]; then
QMAKE_CONFIG="$QMAKE_CONFIG headersclean"
fi
else
if [ "$CFG_WERROR" = "yes" ]; then
QMAKE_CONFIG="$QMAKE_CONFIG warnings_are_errors"
fi
if [ "$CFG_HEADERSCLEAN" = "yes" ]; then
QMAKE_CONFIG="$QMAKE_CONFIG headersclean"
fi
fi
cat >>"$QTCONFIG.tmp" <<EOF

View File

@ -0,0 +1,2 @@
// Used when we need to compile with no source code
// (controls are in the command-line)

View File

@ -60,10 +60,6 @@ MODULE_DEFINES = $$MODULE_DEFINE $$MODULE_DEFINES
load(qt_module_pris)
INCLUDEPATH *= $$eval(QT.$${MODULE}.includes) $$eval(QT.$${MODULE}_private.includes)
!no_module_headers: load(qt_module_headers)
#other
TEMPLATE = lib
DESTDIR = $$eval(QT.$${MODULE_ID}.libs)
@ -73,6 +69,11 @@ CONFIG += qmake_cache target_qt
QMAKE_DOCS_TARGETDIR = qt$${MODULE}
load(qt_common)
!no_module_headers: load(qt_module_headers)
INCLUDEPATH *= $$eval(QT.$${MODULE}.includes) $$eval(QT.$${MODULE}_private.includes)
# If Qt was configured with -debug-and-release then build the module the same way
# - unless this is a host library
!host_build:if(win32|mac):!macx-xcode {
@ -175,7 +176,6 @@ android: CONFIG += qt_android_deps
load(qt_installs)
load(qt_targets)
load(qt_common)
# this builds on top of qt_common
unix|mingw {

View File

@ -76,3 +76,89 @@ SYNCQT.HEADER_FILES += $$MODULE_MASTER_DEPS_HEADER
}
CONFIG += qt_install_headers
headersclean:!internal_module {
# Make sure that the header compiles with our strict options
hcleanDEFS = -DQT_NO_CAST_TO_ASCII=1 \
-DQT_NO_CAST_FROM_ASCII=1 \
-DQT_STRICT_ITERATORS \
-DQT_NO_URL_CAST_FROM_STRING=1 \
-DQT_NO_CAST_FROM_BYTEARRAY=1 \
-DQT_NO_KEYWORDS=1 \
-DQT_USE_FAST_CONCATENATION \
-DQT_USE_FAST_OPERATOR_PLUS \
-Dsignals=int \
-Dslots=int \
-Demit=public: \
-Dforeach=public: \
-Dforever=public:
gcc {
# Turn on some extra warnings not found in -Wall -Wextra.
# Common to GCC, Clang and ICC (and other compilers that masquerade as GCC):
hcleanFLAGS = -Wall -Wextra -Werror \
-Woverloaded-virtual -Wshadow -Wundef \
-Wnon-virtual-dtor -Wpointer-arith -Wformat-security \
-Wno-long-long -Wno-variadic-macros -pedantic-errors
intel_icc {
# these warnings are disabled because explicit constructors with zero or
# multiple arguments are permitted in C++11:
# 2304: non-explicit constructor with single argument may cause implicit type conversion
# 2305: declaration of 'explicit' constructor without a single argument is redundant
hcleanFLAGS += -wd2304,2305
greaterThan(QT_ICC_MAJOR_VERSION, 13) {
# ICC 14+ has a bug with -Wshadow, emitting it for cases where there's no shadowing
# (issue ID 0000698329, task DPD200245740)
hcleanFLAGS -= -Wshadow
}
} else {
hcleanFLAGS += -Wchar-subscripts
!contains(QT_ARCH, arm):!contains(QT_ARCH, mips): \
hcleanFLAGS += -Wcast-align
}
# Use strict mode C++11 or C++98, with no GNU extensions (see -pedantic-errors above).
# The module might set CONFIG += c++11, but it might also change QMAKE_CXXFLAGS_CXX11
# or the module (or the mkspec) can set the C++11 flag on QMAKE_CXXFLAGS
# (or QMAKE_CXXFLAGS_{RELEASE,DEBUG} but that's unlikely).
c++11:contains(QMAKE_CXXFLAGS_CXX11, -std=gnu++11) {
hcleanFLAGS += -std=c++11
} else: contains(QMAKE_CXXFLAGS, -std=gnu++11) {
hcleanFLAGS += -std=c++11
} else: c++11:contains(QMAKE_CXXFLAGS_CXX11, -std=gnu++0x) {
hcleanFLAGS += -std=c++0x
} else: contains(QMAKE_CXXFLAGS, -std=gnu++0x) {
hcleanFLAGS += -std=c++0x
} else: !c++11:!contains(QMAKE_CXXFLAGS, -std=c++0x):!contains(QMAKE_CXXFLAGS, -std=c++11) {
hcleanFLAGS += -std=c++98
}
hcleanCOMMAND = $$QMAKE_CXX -c $(CXXFLAGS) $$hcleanFLAGS $(INCPATH) $$hcleanDEFS -xc++ ${QMAKE_FILE_IN} -o ${QMAKE_FILE_OUT}
} else: win32-msvc2013 {
# 4180: qualifier applied to function type has no meaning; ignored
# 4458: declaration of 'identifier' hides class member
# -Za enables strict standards behavior, but we can't add it because
# <windows.h> and <GL.h> violate the standards.
hcleanFLAGS = -WX -W3 -wd4180 -wd4458
hcleanCOMMAND = $$QMAKE_CXX -c $(CXXFLAGS) $$hcleanFLAGS $(INCPATH) $$hcleanDEFS -FI${QMAKE_FILE_IN} -Fo${QMAKE_FILE_OUT} \
$$[QT_INSTALL_DATA/src]/mkspecs/features/data/dummy.cpp
}
!isEmpty(hcleanCOMMAND):if(!contains(QT_CONFIG, debug_and_release)|CONFIG(release, debug|release)) {
header_check.dependency_type = TYPE_C
header_check.CONFIG += no_link
header_check.output = ${QMAKE_VAR_OBJECTS_DIR}header_${QMAKE_FILE_BASE}$${first(QMAKE_EXT_OBJ)}
header_check.input = SYNCQT.CLEAN_HEADER_FILES
header_check.variable_out = PRE_TARGETDEPS
header_check.name = headercheck ${QMAKE_FILE_IN}
header_check.commands = $$hcleanCOMMAND
silent:header_check.commands = @echo compiling[header] ${QMAKE_FILE_IN} && $$hcleanCOMMAND
QMAKE_EXTRA_COMPILERS += header_check
SYNCQT.CLEAN_HEADER_FILES -= $$HEADERSCLEAN_EXCLUDE
}
unset(hcleanCOMMAND)
unset(hcleanFLAGS)
unset(hcleanDEFS)
}

View File

@ -81,7 +81,7 @@ src_network.depends = src_corelib
src_testlib.subdir = $$PWD/testlib
src_testlib.target = sub-testlib
src_testlib.depends = src_corelib # src_gui & src_widgets are not build-depends
src_testlib.depends = src_corelib # testlib links only to corelib, but see below for the headers
src_3rdparty_pcre.subdir = $$PWD/3rdparty/pcre
src_3rdparty_pcre.target = sub-3rdparty-pcre
@ -166,10 +166,12 @@ contains(QT_CONFIG, concurrent):SUBDIRS += src_concurrent
SUBDIRS += src_gui src_platformsupport src_platformheaders
contains(QT_CONFIG, opengl(es2)?):SUBDIRS += src_openglextensions
src_plugins.depends += src_gui src_platformsupport src_platformheaders
src_testlib.depends += src_gui # if QtGui is enabled, QtTest requires QtGui's headers
!contains(QT_CONFIG, no-widgets) {
SUBDIRS += src_tools_uic src_widgets
TOOLS += src_tools_uic
src_plugins.depends += src_widgets
src_testlib.depends += src_widgets # if QtWidgets is enabled, QtTest requires QtWidgets's headers
contains(QT_CONFIG, opengl(es2)?) {
SUBDIRS += src_opengl
src_plugins.depends += src_opengl

View File

@ -97,4 +97,9 @@ mac {
}
}
# Exclude these headers from the clean check if their dependencies aren't
# being built
contains(QT_CONFIG, no-widgets): HEADERSCLEAN_EXCLUDE += qtest_widgets.h
contains(QT_CONFIG, no-gui): HEADERSCLEAN_EXCLUDE += qtest_gui.h
load(qt_module)

View File

@ -1,7 +1,7 @@
/****************************************************************************
**
** Copyright (C) 2015 The Qt Company Ltd.
** Copyright (C) 2013 Intel Corporation
** Copyright (C) 2014 Intel Corporation
** Contact: http://www.qt.io/licensing/
**
** This file is part of the tools applications of the Qt Toolkit.
@ -927,6 +927,10 @@ void Configure::parseCmdLine()
dictionary[ "WERROR" ] = "yes";
} else if (configCmdLine.at(i) == "-no-warnings-are-errors") {
dictionary[ "WERROR" ] = "no";
} else if (configCmdLine.at(i) == "-no-headersclean") {
dictionary[ "HEADERSCLEAN" ] = "no";
} else if (configCmdLine.at(i) == "-headersclean") {
dictionary[ "HEADERSCLEAN" ] = "yes";
} else if (configCmdLine.at(i) == "-no-eventfd") {
dictionary[ "QT_EVENTFD" ] = "no";
} else if (configCmdLine.at(i) == "-eventfd") {
@ -1459,9 +1463,13 @@ void Configure::parseCmdLine()
qtConfig << "private_tests";
if (dictionary["WERROR"] != "no")
qmakeConfig << "warnings_are_errors";
if (dictionary["HEADERSCLEAN"] != "no")
qmakeConfig << "headersclean";
} else {
if (dictionary["WERROR"] == "yes")
qmakeConfig << "warnings_are_errors";
if (dictionary["HEADERSCLEAN"] == "yes")
qmakeConfig << "headersclean";
}
if (dictionary["FORCE_ASSERTS"] == "yes")