Rewrite qmake's exclusive-build feature

We used to compute the default exclusive build directory, eg 'debug', at
configure time, and then set OBJECTS_DIR, MOC_DIR, etc to include this
hard-coded default exclusive build directory. We then had to run a post-
process step where we replaced the 'debug' part with the current actual
exclusive build pass, eg 'release', resulting in long-standing bugs such
as QTBUG-491 where we end up replacing parts of the build output dirs
that were not part of the original exclusive build directory.

We now set the OBJECTS_DIR, MOC_DIR, etc defaults in configure like
before, but they do not include any exclusive-build information. The
exclusive build directory is handled as a separate step in default_post
where we adjust all entries in QMAKE_DIR_REPLACE to be exclusive
directories.

For backwards compatibility the new exclusive build behavior is only
enabled for variables named by QMAKE_DIR_REPLACE_SANE, which for Qt
itself applies globally to everything but DESTDIR, and for libs and
tools also applies to DESTDIR. The reason for leaving out DESTDIR in
the general case is because many tests and examples assume the old
behavior for DESTDIR. A side effect of including all the other
variables for Qt libs and tools is that the PCH output dir will be
uniformly set, which has been an issue on Windows in the past.

The addExclusiveBuilds function now takes two or more arguments,
each argument being the key for an exclusive build, which can be
customized eg. using $$key.{name,target,dir_affix}. Passing more
than two arguments results in three/four/etc-way exclusive builds,
eg debug/release/profile. Exclusive builds can also be combined, eg
static/shared + debug/release by making two calls to the function.

We also handle individual targets of combined exclusive builds,
eg static/shared + debug/release, meaning it is possible to run
'make debug' to build both static-debug and shared-debug.

Task-number: QTBUG-491
Change-Id: I02841dbbd065ac07d413dfb45cfcfe4c013674ac
Reviewed-by: Oswald Buddenhagen <oswald.buddenhagen@digia.com>
This commit is contained in:
Tor Arne Vestbø 2013-10-08 19:41:16 +02:00 committed by The Qt Project
parent 25650923b1
commit c760d2dbfd
9 changed files with 206 additions and 124 deletions

17
configure vendored
View File

@ -5895,18 +5895,10 @@ if [ "$CFG_OPENGL" = "es2" ]; then
QT_CONFIG="$QT_CONFIG opengles2"
fi
# build up the variables for output
if [ "$CFG_DEBUG" = "yes" ]; then
QMAKE_OUTDIR="${QMAKE_OUTDIR}debug"
elif [ "$CFG_DEBUG" = "no" ]; then
QMAKE_OUTDIR="${QMAKE_OUTDIR}release"
fi
if [ "$CFG_SHARED" = "yes" ]; then
QMAKE_OUTDIR="${QMAKE_OUTDIR}-shared"
QT_CONFIG="$QT_CONFIG shared"
QTCONFIG_CONFIG="$QTCONFIG_CONFIG shared"
elif [ "$CFG_SHARED" = "no" ]; then
QMAKE_OUTDIR="${QMAKE_OUTDIR}-static"
QT_CONFIG="$QT_CONFIG static"
QTCONFIG_CONFIG="$QTCONFIG_CONFIG static"
fi
@ -5916,15 +5908,6 @@ QMAKE_CONFIG="$QMAKE_CONFIG qpa"
QT_CONFIG="$QT_CONFIG qpa"
QTCONFIG_CONFIG="$QTCONFIG_CONFIG qpa"
if [ "$XPLATFORM_MINGW" != "yes" ]; then
# Do not set this here for Windows. Let qmake do it so
# debug and release precompiled headers are kept separate.
QMakeVar set PRECOMPILED_DIR ".pch/$QMAKE_OUTDIR"
fi
QMakeVar set OBJECTS_DIR ".obj/$QMAKE_OUTDIR"
QMakeVar set MOC_DIR ".moc/$QMAKE_OUTDIR"
QMakeVar set RCC_DIR ".rcc/$QMAKE_OUTDIR"
QMakeVar set UI_DIR ".uic/$QMAKE_OUTDIR"
if [ "$CFG_LARGEFILE" = "yes" ] && [ "$XPLATFORM_MINGW" != "yes" ]; then
QMAKE_CONFIG="$QMAKE_CONFIG largefile"
fi

View File

@ -6,6 +6,8 @@ contains(TEMPLATE, ".*(lib|app)"):CONFIG += have_target
load(resolve_config)
exclusive_builds: load(exclusive_builds_post)
# If the TARGET looks like a path, split it into DESTDIR and the resulting TARGET
target_dir_part = $$dirname(TARGET)
!isEmpty(target_dir_part) {

View File

@ -1,92 +1,37 @@
# fixExclusiveOutputDirs(1config, 2config)
# Change all output paths that references 2config to have the string 1config in them
defineTest(fixExclusiveOutputDirs) {
firstBuild = $$1
secondBuild = $$2
count(ARGS, 2, greaterThan):isEqual($$list($$lower($$3)), false):appendFirstBuild = false
else:appendFirstBuild = true
isEmpty(QMAKE_DIR_REPLACE):QMAKE_DIR_REPLACE += OBJECTS_DIR MOC_DIR RCC_DIR PRECOMPILED_DIR
lessThan(firstBuild, $$secondBuild):eval($${firstBuild}_and_$${secondBuild}_target:QMAKE_DIR_REPLACE += DESTDIR)
else:eval($${secondBuild}_and_$${firstBuild}_target:QMAKE_DIR_REPLACE += DESTDIR)
for(fix, QMAKE_DIR_REPLACE) {
isEmpty($$fix)|isEqual($$fix, .) {
eval($$fix = $${firstBuild})
} else:contains($$list($$first($$fix)), .*$${secondBuild}.*) {
eval($$fix ~= s/$${secondBuild}/$${firstBuild}/gi)
} else:isEqual(appendFirstBuild, true):!contains($$list($$first($$fix)), .*$${firstBuild}.*) {
contains($$list($${first($$fix)}), .*/$):eval($$fix = $${first($$fix)}$${firstBuild})
else:eval($$fix = $${first($$fix)}-$${firstBuild})
}
export($$fix)
}
return(true)
}
# addExclusiveBuilds(1config, 1name, 2config, 2name)
# Adds two BUILDS which are exclusive to each other.
defineTest(addExclusiveBuilds) {
firstBuild = $$1
firstBuildName = $$2
secondBuild = $$3
secondBuildName = $$4
lessThan(ARGC, 2): \
error("addExclusiveBuilds() requires at least two arguments")
contains(TEMPLATE, subdirs) {
eval(sub_$${firstBuildName}.target = $$firstBuild)
export(sub_$${firstBuildName}.target)
eval(sub_$${firstBuildName}.CONFIG = recursive)
export(sub_$${firstBuildName}.CONFIG)
eval(sub_$${secondBuildName}.target = $$secondBuild)
export(sub_$${secondBuildName}.target)
eval(sub_$${secondBuildName}.CONFIG = recursive)
export(sub_$${secondBuildName}.CONFIG)
QMAKE_EXTRA_TARGETS += sub_$${firstBuildName} sub_$${secondBuildName}
export(QMAKE_EXTRA_TARGETS)
} else:!build_pass {
first_BUILDS =
second_BUILDS =
suffix_BUILDS = Build
!$$join(ARGS, _and_):!fix_output_dirs: \
return(true)
isEmpty(BUILDS): BUILDPERMUTATIONS = $$suffix_BUILDS
else: BUILDPERMUTATIONS = $$BUILDS
for(permutation, BUILDPERMUTATIONS) {
permutation ~= s/$${suffix_BUILDS}$//
isEmpty(permutation): permutationName =
else: permutationName = -$$permutation
# Makefile target rule
eval($${firstBuildName}$${permutation}.target = $${firstBuild}$$lower($${permutationName}))
export($${firstBuildName}$${permutation}.target)
# IDE name
eval($${firstBuildName}$${permutation}.name = $${firstBuildName}$${permutationName})
export($${firstBuildName}$${permutation}.name)
# prl import CONFIG option
eval($${firstBuildName}$${permutation}.PRL_CONFIG = $${firstBuild}$${permutation})
export($${firstBuildName}$${permutation}.PRL_CONFIG)
# Individual CONFIG option
eval($${firstBuildName}$${permutation}.CONFIG = $${firstBuild} $${firstBuildName}Build $$eval($${permutation}.CONFIG))
export($${firstBuildName}$${permutation}.CONFIG)
eval($${secondBuildName}$${permutation}.target = $${secondBuild}$$lower($${permutationName}))
export($${secondBuildName}$${permutation}.target)
eval($${secondBuildName}$${permutation}.name = $${secondBuildName}$${permutationName})
export($${secondBuildName}$${permutation}.name)
eval($${secondBuildName}$${permutation}.PRL_CONFIG = $${secondBuild}$${permutation})
export($${secondBuildName}$${permutation}.PRL_CONFIG)
eval($${secondBuildName}$${permutation}.CONFIG = $${secondBuild} $${secondBuildName}Build $$eval($${permutation}.CONFIG))
export($${secondBuildName}$${permutation}.CONFIG)
first_BUILDS += $${firstBuildName}$${permutation}
second_BUILDS += $${secondBuildName}$${permutation}
for(build, ARGS) {
isEmpty($${build}.name) {
$${build}.name = $$title($$build)
export($${build}.name)
}
isEmpty($${build}.target) {
$${build}.target = $$lower($$build)
export($${build}.target)
}
isEmpty($${build}.dir_affix) {
$${build}.dir_affix = $$lower($$build)
export($${build}.dir_affix)
}
# A mutual exclusive block.
CONFIG($${firstBuild}, $${firstBuild}|$${secondBuild}): BUILDS = $$first_BUILDS $$second_BUILDS
else: BUILDS = $$second_BUILDS $$first_BUILDS
export(BUILDS)
} else {
eval($${firstBuildName}Build:fixExclusiveOutputDirs($$firstBuild, $$secondBuild, false))
eval($${secondBuildName}Build:fixExclusiveOutputDirs($$secondBuild, $$firstBuild, false))
$${build}.exclusive = $$ARGS
export($${build}.exclusive)
QMAKE_EXCLUSIVE_BUILDS += $$build
}
CONFIG *= exclusive_builds
export(CONFIG)
export(QMAKE_EXCLUSIVE_BUILDS)
return(true)
}
# Default directories to process
QMAKE_DIR_REPLACE = OBJECTS_DIR MOC_DIR RCC_DIR PRECOMPILED_DIR DESTDIR

View File

@ -0,0 +1,160 @@
contains(TEMPLATE, subdirs) {
for(build, QMAKE_EXCLUSIVE_BUILDS) {
prepareRecursiveTarget($$build)
QMAKE_EXTRA_TARGETS += $$build
}
} else {
# Recursively combines a list of exclusive builds into combinations
# of non-exclusive builds (separated by a ':' character), eg the
# list [debug, release, static, shared] will result in the four
# combinations [debug:static, debug:shared, release:static,
# release:shared].
defineReplace(combineExclusiveBuilds) {
permutationBuilds = $$1
existingBuilds = $$2
isEmpty(permutationBuilds): \
# Exit-condition, no more recursing
return($$existingBuilds)
# Choose the first build of the permutations and use the set of exclusive
# builds associated with that build as the list of existing builds. This
# partitions the permutations into one set of exclusive builds + the rest
# of the unknown permutations.
newExistingBuilds = $$eval($$first(permutationBuilds).exclusive)
permutationBuilds -= $$newExistingBuilds
# Recursively compute the combination of these two sets
recursiveCombination = $$combineExclusiveBuilds($$permutationBuilds, $$newExistingBuilds)
isEmpty(existingBuilds): \
# No need to combine further
return($$recursiveCombination)
result =
for(existingBuild, existingBuilds) {
for(combination, recursiveCombination): \
result += "$${existingBuild}:$${combination}"
}
return($$result)
}
buildCombinations = $$combineExclusiveBuilds($$QMAKE_EXCLUSIVE_BUILDS)
for(combination, buildCombinations) {
builds = $$split(combination, :)
key =
config =
target =
priority =
for(build, builds) {
key = $${key}$$eval($${build}.name)
config *= $$eval($${build}.CONFIG) $${build} $$eval($${build}.name)Build
target += $$eval($${build}.target)
# If a build has been prioritized through CONFIG we prefer that
CONFIG($$build, $$join($${build}.exclusive, |)): \
priority += 1
}
$${key}.name = $$key
$${key}.target = $$join(target, -)
$${key}.CONFIG = $$config
$${key}.builds = $$builds
BUILDS.$$size(priority) += $$key
# Add makefile targets for each exclusive build that will aggregate all targets
# that include the exclusive build. This matches the targets in the SUBDIR files
# so that you can recursivly build a single exclusive build.
!build_pass:count(builds, 1, >) {
for(build, builds) {
$${build}.depends += $$eval($${key}.target)
QMAKE_EXTRA_TARGETS *= $$build
}
}
}
BUILDS =
priority =
for(ever) {
# Keep the order in BUILDS matching the priority from CONFIG, so that the first
# entry in BUILDS will be the first/default target when not CONFIG(build_all).
BUILDS = $$eval(BUILDS.$$size(priority)) $$BUILDS
count(BUILDS, $$size(buildCombinations), >=): break()
priority += 1
}
build_pass|fix_output_dirs {
!build_pass {
# The builds are sorted by priority based on the current config
# so choosing the first one gives us the most appropriate build.
BUILD_PASS = $$first(BUILDS)
}
for(dir, QMAKE_DIR_REPLACE) {
# Limit builds to ones that should affect the current $$dir
builds =
for(build, $${BUILD_PASS}.builds) {
equals(dir, DESTDIR) {
!$$join($${build}.exclusive, _and_)_target: \
next()
}
builds += $$build
}
isEmpty(builds): \
next()
affixes =
for(build, builds): \
affixes += $$eval($${build}.dir_affix)
full_dir_affix = $$join(affixes, -)
isEmpty($$dir)|isEqual($$dir, .) {
# Use affix directly
$$dir = $$full_dir_affix
next()
}
contains(QMAKE_DIR_REPLACE_SANE, $$dir) {
# Suffix output dir
$$dir = $$clean_path($$eval($$dir)/$$full_dir_affix)
} else {
# "Compatibility mode" with QTBUG-491
for(build, builds) {
did_replace = false
build_affix = $$eval($${build}.dir_affix)
for(exclusive, $${build}.exclusive) {
equals(exclusive, $$build): \
next()
exclusive_affix = $$eval($${exclusive}.dir_affix)
contains($$dir, .*$${exclusive_affix}.*) {
$$dir ~= s/$${exclusive_affix}/$${build_affix}/gi
did_replace = true
}
}
$$did_replace: next()
# Append (as subdir or as suffix)
!build_pass {
dir_affix = $$eval($${build}.dir_affix)
!contains($$dir, .*$${dir_affix}.*) {
contains($$dir, .*/$) {
# Subdir
$$dir = $$eval($$dir)$$dir_affix
} else {
# Suffix
$$dir = $$eval($$dir)-$${dir_affix}
}
}
}
}
}
}
}
}

View File

@ -20,6 +20,14 @@
debug(1, "Not loading qmodule.pri twice")
}
PRECOMPILED_DIR = .pch
OBJECTS_DIR = .obj
MOC_DIR = .moc
RCC_DIR = .rcc
UI_DIR = .uic
QMAKE_DIR_REPLACE_SANE = PRECOMPILED_DIR OBJECTS_DIR MOC_DIR RCC_DIR UI_DIR
# force_independent can be set externally. prefix_build not.
!exists($$[QT_HOST_DATA]/.qmake.cache): \
CONFIG += prefix_build force_independent

View File

@ -9,6 +9,9 @@
# We mean it.
#
QMAKE_DIR_REPLACE_SANE += DESTDIR
CONFIG -= debug_and_release_target
contains(QT_CONFIG, c++11): CONFIG += c++11
contains(TEMPLATE, .*lib) {
# module and plugins

View File

@ -22,14 +22,8 @@ CONFIG(static, static|shared) {
contains(TEMPLATE, ".*lib"): CONFIG += dll
}
static_and_shared {
!macx-xcode: addExclusiveBuilds(static, Static, shared, Shared)
} else: fix_output_dirs {
static: \
fixExclusiveOutputDirs(static, shared)
else: \
fixExclusiveOutputDirs(shared, static)
}
!macx-xcode: \
addExclusiveBuilds(shared, static)
CONFIG(debug, debug|release): \
CONFIG -= release
@ -37,14 +31,7 @@ else: \
CONFIG -= debug
!macx-xcode {
debug_and_release {
addExclusiveBuilds(debug, Debug, release, Release)
} else: fix_output_dirs {
debug: \
fixExclusiveOutputDirs(debug, release)
else: \
fixExclusiveOutputDirs(release, debug)
}
addExclusiveBuilds(debug, release)
} else {
# The Xcode generator always generates project files with
# debug and release configurations, regardless of whether

View File

@ -11,6 +11,8 @@
load(resolve_config)
exclusive_builds: load(exclusive_builds_post)
QMAKE_RESOLVED_TARGET = $$absolute_path($$DESTDIR, $$OUT_PWD)/
win32 {

View File

@ -2613,7 +2613,6 @@ void Configure::generateOutputVars()
if (dictionary[ "FORCEDEBUGINFO" ] == "yes")
qmakeConfig += "force_debug_info";
qmakeConfig += dictionary[ "BUILD" ];
dictionary[ "QMAKE_OUTDIR" ] = dictionary[ "BUILD" ];
if (buildParts.isEmpty()) {
buildParts = defaultBuildParts;
@ -2638,9 +2637,6 @@ void Configure::generateOutputVars()
qmakeVars += "QMAKE_QT_VERSION_OVERRIDE = " + version.left(version.indexOf('.'));
version.remove(QLatin1Char('.'));
}
dictionary[ "QMAKE_OUTDIR" ] += "_shared";
} else {
dictionary[ "QMAKE_OUTDIR" ] += "_static";
}
if (dictionary[ "ACCESSIBILITY" ] == "yes")
@ -2771,10 +2767,6 @@ void Configure::generateOutputVars()
if (dictionary.contains("XQMAKESPEC") && dictionary[ "XQMAKESPEC" ].startsWith("linux"))
qtConfig += "rpath";
qmakeVars += QString("OBJECTS_DIR = ") + formatPath(".obj/" + dictionary["QMAKE_OUTDIR"]);
qmakeVars += QString("MOC_DIR = ") + formatPath(".moc/" + dictionary["QMAKE_OUTDIR"]);
qmakeVars += QString("RCC_DIR = ") + formatPath(".rcc/" + dictionary["QMAKE_OUTDIR"]);
if (!qmakeDefines.isEmpty())
qmakeVars += QString("DEFINES += ") + qmakeDefines.join(' ');
if (!qmakeIncludes.isEmpty())