QT_CONFIGURE_REPORT = QT_CONFIGURE_NOTES = QT_CONFIGURE_WARNINGS = QT_CONFIGURE_ERRORS = defineTest(qtConfAddReport) { QT_CONFIGURE_REPORT += "$$join(1, $$escape_expand(\\n))" export(QT_CONFIGURE_REPORT) } defineTest(qtConfAddNote) { QT_CONFIGURE_NOTES += "Note: $$join(1, $$escape_expand(\\n))" export(QT_CONFIGURE_NOTES) } defineTest(qtConfAddWarning) { QT_CONFIGURE_WARNINGS += "WARNING: $$join(1, $$escape_expand(\\n))" export(QT_CONFIGURE_WARNINGS) } defineTest(qtConfAddError) { QT_CONFIGURE_ERRORS += "ERROR: $$join(1, $$escape_expand(\\n))" export(QT_CONFIGURE_ERRORS) equals(2, log):qt_conf_tests_allowed { CONFIG += mention_config_log export(CONFIG) } } defineTest(qtConfFatalError) { qtConfAddError($$1, $$2) qtConfPrintReport() error() } # Return a string list for the specified JSON path, which may be either a # single string or an array of strings. # Note that this returns a variable name, so it can be directly iterated over. defineReplace(qtConfScalarOrList) { defined($$1, var): return($$1) vals = $$list() for (i, $${1}._KEYS_): \ $$vals += $$eval($${1}.$$i) export($$vals) return($$vals) } defineTest(qtConfCommandlineSetInput) { arg = $${1} val = $${2} !isEmpty($${currentConfig}.commandline.options.$${arg}.name): \ arg = $$eval($${currentConfig}.commandline.options.$${arg}.name) config.input.$$arg = $$val export(config.input.$$arg) } defineReplace(qtConfGetNextCommandlineArg) { c = $$take_first(QMAKE_EXTRA_ARGS) export(QMAKE_EXTRA_ARGS) return($$c) } defineReplace(qtConfPeekNextCommandlineArg) { return($$first(QMAKE_EXTRA_ARGS)) } defineTest(qtConfCommandline_boolean) { opt = $${1} val = $${2} isEmpty(val): val = yes !equals(val, yes):!equals(val, no) { qtConfAddError("Invalid value given for boolean command line option '$$opt'.") return() } qtConfCommandlineSetInput($$opt, $$val) } defineTest(qtConfCommandline_void) { opt = $${1} val = $${2} !isEmpty(val) { qtConfAddError("Command line option '$$opt' expects no argument ('$$val' given).") return() } val = $$eval($${currentConfig}.commandline.options.$${opt}.value) isEmpty(val): val = yes qtConfCommandlineSetInput($$opt, $$val) } defineTest(qtConfCommandline_enum) { opt = $${1} val = $${2} isEmpty(val): val = yes # validate and map value mapped = $$eval($${currentConfig}.commandline.options.$${opt}.values.$${val}) isEmpty(mapped) { # just a list of allowed values for (i, $${currentConfig}.commandline.options.$${opt}.values._KEYS_) { equals($${currentConfig}.commandline.options.$${opt}.values.$${i}, $$val) { mapped = $$val break() } } } isEmpty(mapped) { qtConfAddError("Invalid value '$$val' supplied to command line option '$$opt'.") return() } qtConfCommandlineSetInput($$opt, $$mapped) } defineTest(qtConfValidateValue) { opt = $${1} val = $${2} validValues = $$eval($${currentConfig}.commandline.options.$${opt}.values._KEYS_) isEmpty(validValues): \ return(true) for (i, validValues) { equals($${currentConfig}.commandline.options.$${opt}.values.$${i}, $$val): \ return(true) } qtConfAddError("Invalid value '$$val' supplied to command line option '$$opt'.") return(false) } defineTest(qtConfCommandline_string) { opt = $${1} val = $${2} nextok = $${3} isEmpty(val):$$nextok: val = $$qtConfGetNextCommandlineArg() # Note: Arguments which are variable assignments are legit here. contains(val, "^-.*")|isEmpty(val) { qtConfAddError("No value supplied to command line option '$$opt'.") return() } !qtConfValidateValue($$opt, $$val): \ return() qtConfCommandlineSetInput($$opt, $$val) } defineTest(qtConfCommandline_optionalString) { opt = $${1} val = $${2} nextok = $${3} isEmpty(val) { $$nextok: val = $$qtConfPeekNextCommandlineArg() contains(val, "^-.*|[A-Z_]+=.*")|isEmpty(val): \ val = "yes" else: \ val = $$qtConfGetNextCommandlineArg() } !qtConfValidateValue($$opt, $$val): \ return() qtConfCommandlineSetInput($$opt, $$val) } defineTest(qtConfCommandline_addString) { opt = $${1} val = $${2} nextok = $${3} isEmpty(val):$$nextok: val = $$qtConfGetNextCommandlineArg() # Note: Arguments which are variable assignments are legit here. contains(val, "^-.*")|isEmpty(val) { qtConfAddError("No value supplied to command line option '$$opt'.") return() } !qtConfValidateValue($$opt, $$val): \ return() !isEmpty($${currentConfig}.commandline.options.$${opt}.name): \ opt = $$eval($${currentConfig}.commandline.options.$${opt}.name) config.input.$$opt += $$val export(config.input.$$opt) } defineTest(qtConfCommandline_redo) { !exists($$OUT_PWD/config.opt) { qtConfAddError("No config.opt present - cannot redo configuration.") return() } QMAKE_EXTRA_REDO_ARGS = $$cat($$OUT_PWD/config.opt, lines) export(QMAKE_EXTRA_REDO_ARGS) # just for config.log QMAKE_EXTRA_ARGS = $$QMAKE_EXTRA_REDO_ARGS $$QMAKE_EXTRA_ARGS export(QMAKE_EXTRA_ARGS) QMAKE_REDO_CONFIG = true export(QMAKE_REDO_CONFIG) } defineTest(qtConfParseCommandLine) { customCalls = for (cc, allConfigs) { custom = $$eval($${cc}.commandline.custom) !isEmpty(custom) { customCall = qtConfCommandline_$$custom !defined($$customCall, test): \ error("Custom command line callback '$$custom' is undefined.") customCalls += $$customCall } } for (ever) { c = $$qtConfGetNextCommandlineArg() isEmpty(c): break() didCustomCall = false for (customCall, customCalls) { $${customCall}($$c) { didCustomCall = true break() } } $$didCustomCall: \ next() contains(c, "([A-Z_]+)=(.*)") { opt = $$replace(c, "^([A-Z_]+)=(.*)", "\\1") val = $$replace(c, "^([A-Z_]+)=(.*)", "\\2") for (cc, allConfigs) { var = $$eval($${cc}.commandline.assignments.$${opt}) !isEmpty(var): \ break() } isEmpty(var) { qtConfAddError("Assigning unknown variable '$$opt' on command line.") return() } config.input.$$var = $$val export(config.input.$$var) next() } # parse out opt and val nextok = false contains(c, "^--?enable-(.*)") { opt = $$replace(c, "^--?enable-(.*)", "\\1") val = yes } else: contains(c, "^--?(disable|no)-(.*)") { opt = $$replace(c, "^--?(disable|no)-(.*)", "\\2") val = no } else: contains(c, "^--([^=]+)=(.*)") { opt = $$replace(c, "^--([^=]+)=(.*)", "\\1") val = $$replace(c, "^--([^=]+)=(.*)", "\\2") } else: contains(c, "^--(.*)") { opt = $$replace(c, "^--(.*)", "\\1") val = } else: contains(c, "^-(.*)") { opt = $$replace(c, "^-(.*)", "\\1") val = nextok = true for (cc, allConfigs) { type = $$eval($${cc}.commandline.options.$${opt}) !isEmpty(type): break() type = $$eval($${cc}.commandline.options.$${opt}.type) !isEmpty(type): break() } isEmpty(type):contains(opt, "(qt|system)-.*") { val = $$replace(opt, "(qt|system)-(.*)", "\\1") opt = $$replace(opt, "(qt|system)-(.*)", "\\2") } } else { qtConfAddError("Invalid command line parameter '$$c'.") return() } for (cc, allConfigs) { type = $$eval($${cc}.commandline.options.$${opt}) isEmpty(type): \ type = $$eval($${cc}.commandline.options.$${opt}.type) isEmpty(type) { # no match in the regular options, try matching the prefixes for (p, $${cc}.commandline.prefix._KEYS_) { e = "^-$${p}(.*)" contains(c, $$e) { opt = $$eval($${cc}.commandline.prefix.$${p}) val = $$replace(c, $$e, "\\1") nextok = true type = "addString" break() } } } !isEmpty(type) { currentConfig = $$cc break() } } # handle builtin [-no]-feature-xxx isEmpty(type):contains(opt, "feature-(.*)") { opt ~= s,^feature-,, found = false for (cc, allConfigs) { contains($${cc}.features._KEYS_, $$opt) { found = true break() } } !$$found { qtConfAddError("Enabling/Disabling unknown feature '$$opt'.") return() } # this is a boolean enabling/disabling the corresponding feature type = boolean } isEmpty(type) { qtConfAddError("Unknown command line option '$$c'.") return() } call = "qtConfCommandline_$${type}" !defined($$call, test): \ error("Command line option '$$c' has unknown type '$$type'.") # now that we have opt and value, process it $${call}($$opt, $$val, $$nextok) } } defineReplace(qtConfToolchainSupportsFlag) { test_out_dir = $$OUT_PWD/$$basename(QMAKE_CONFIG_TESTS_DIR) test_cmd_base = "$$QMAKE_CD $$system_quote($$system_path($$test_out_dir)) &&" conftest = "int main() { return 0; }" write_file("$$test_out_dir/conftest.cpp", conftest)|error() qtRunLoggedCommand("$$test_cmd_base $$QMAKE_CXX $${1} -o conftest-out conftest.cpp"): \ return(true) return(false) } defineTest(qtConfTest_compilerSupportsFlag) { flag = $$eval($${1}.flag) return($$qtConfToolchainSupportsFlag($$flag)) } defineTest(qtConfTest_linkerSupportsFlag) { flag = $$eval($${1}.flag) use_gold_linker: \ LFLAGS = -fuse-ld=gold return($$qtConfToolchainSupportsFlag($$LFLAGS "-Wl,$$flag")) } defineReplace(qtConfFindInPathList) { for (dir, 2) { exists("$$dir/$${1}"): \ return("$$dir/$${1}") } return() } defineReplace(qtConfFindInPath) { ensurePathEnv() return($$qtConfFindInPathList($$1, $$2 $$QMAKE_PATH_ENV)) } defineReplace(qtConfPkgConfigEnv) { env = !isEmpty(PKG_CONFIG_SYSROOT_DIR): env = "$${SETENV_PFX}PKG_CONFIG_SYSROOT_DIR=$${PKG_CONFIG_SYSROOT_DIR}$${SETENV_SFX} " !isEmpty(PKG_CONFIG_LIBDIR): env = "$$env$${SETENV_PFX}PKG_CONFIG_LIBDIR=$${PKG_CONFIG_LIBDIR}$${SETENV_SFX} " return($$env) } defineReplace(qtConfPkgConfig) { host = $$1 isEmpty(host): host = false $$host { pkg_config = $$qtConfFindInPath("pkg-config") } else { pkg_config = "$$qtConfPkgConfigEnv()$$PKG_CONFIG_EXECUTABLE" } return($$pkg_config) } defineTest(qtConfPkgConfigPackageExists) { isEmpty(1)|isEmpty(2): \ return(false) !qtRunLoggedCommand("$${1} --exists --silence-errors $${2}"): \ return(false) return(true) } defineReplace(qtSystemQuote) { args = for (a, 1): \ args += $$system_quote($$a) return($$args) } defineReplace(qtConfPrepareArgs) { return($$qtSystemQuote($$split(1))) } defineTest(qtConfSetupLibraries) { asspfx = $${currentConfig}.commandline.assignments for (l, $${currentConfig}.libraries._KEYS_) { lpfx = $${currentConfig}.libraries.$${l} # 'export' may be omitted, in which case it falls back to the library's name !defined($${lpfx}.export, var) { $${lpfx}.export = $$l export($${lpfx}.export) } # 'export' may also be empty, but we need a derived identifier alias = $$eval($${lpfx}.export) isEmpty(alias): alias = $$l $${lpfx}.alias = $$alias export($${lpfx}.alias) # make it easy to refer to the library by its export name. $${currentConfig}.exports._KEYS_ += $$alias $${currentConfig}.exports.$$alias += $$l export($${currentConfig}.exports.$$alias) isEmpty($${lpfx}.sources._KEYS_): \ error("Library $$l defines no sources") for (s, $${lpfx}.sources._KEYS_) { spfx = $${lpfx}.sources.$${s} # link back to parent object $${spfx}.library = $$l export($${spfx}.library) # a plain string is transformed into a structure isEmpty($${spfx}._KEYS_) { $${spfx}.libs = $$eval($${spfx}) export($${spfx}.libs) } # if the type is missing (implicitly in the case of plain strings), assume 'inline' isEmpty($${spfx}.type) { $${spfx}.type = inline export($${spfx}.type) } } } $${currentConfig}.exports._KEYS_ = $$unique($${currentConfig}.exports._KEYS_) export($${currentConfig}.exports._KEYS_) for (alias, $${currentConfig}.exports._KEYS_) { ua = $$upper($$alias) $${asspfx}._KEYS_ += \ $${ua}_PREFIX $${ua}_INCDIR $${ua}_LIBDIR \ $${ua}_LIBS $${ua}_LIBS_DEBUG $${ua}_LIBS_RELEASE uapfx = $${asspfx}.$${ua} $${uapfx}_PREFIX = $${alias}.prefix $${uapfx}_INCDIR = $${alias}.incdir $${uapfx}_LIBDIR = $${alias}.libdir $${uapfx}_LIBS = $${alias}.libs $${uapfx}_LIBS_DEBUG = $${alias}.libs.debug $${uapfx}_LIBS_RELEASE = $${alias}.libs.release export($${uapfx}_PREFIX) export($${uapfx}_INCDIR) export($${uapfx}_LIBDIR) export($${uapfx}_LIBS) export($${uapfx}_LIBS_DEBUG) export($${uapfx}_LIBS_RELEASE) } export($${asspfx}._KEYS_) # reverse mapping for assignments on command line. for (a, $${asspfx}._KEYS_) { apfx = $${asspfx}.$${a} ra = config.commandline.rev_assignments.$$eval($$apfx) $$ra = $$a export($$ra) } } # the library is specified inline in a 'libs' field. # overrides from the command line are accepted. defineTest(qtConfLibrary_inline) { lib = $$eval($${1}.library) !defined($${1}.libs, var):isEmpty($${1}.builds._KEYS_): \ error("'inline' source in library '$$lib' specifies neither 'libs' nor 'builds'.") # if multiple libraries provide the same export, it makes sense # to make them recognize the same input variables. input = $$eval($${2}.alias) # build-specific direct libs. overwrites inline libs. vars = any = false all = true for (b, $$list(debug release)) { iv = $${input}.libs.$${b} vars += $$eval(config.commandline.rev_assignments.$${iv}) defined(config.input.$${iv}, var) { $${1}.builds.$${b} = $$eval(config.input.$${iv}) export($${1}.builds.$${b}) $${1}.builds._KEYS_ *= $${b} any = true } else { all = false } } $$any { !$$all { qtConfAddError("Either none or all of $$join(vars, ", ", [, ]) must be specified.") return(false) } export($${1}.builds._KEYS_) # we also reset the generic libs, to avoid surprises. $${1}.libs = export($${1}.libs) } # direct libs. overwrites inline libs. defined(config.input.$${input}.libs, var) { $${1}.libs = $$eval(config.input.$${input}.libs) export($${1}.libs) } # prefix. prepends to (possibly overwritten) inline libs. prefix = $$val_escape(config.input.$${input}.prefix) !isEmpty(prefix) { $${1}.includedir = $$prefix/include export($${1}.includedir) $${1}.libs = "-L$$prefix/lib $$eval($${1}.libs)" export($${1}.libs) } incdir = $$val_escape(config.input.$${input}.incdir) !isEmpty(incdir) { $${1}.includedir = $$incdir export($${1}.includedir) } libdir = $$val_escape(config.input.$${input}.libdir) !isEmpty(libdir) { $${1}.libs = "-L$$libdir $$eval($${1}.libs)" export($${1}.libs) } return(true) } # the library is provided by the qmake spec. # this source type cannot fail. defineTest(qtConfLibrary_makeSpec) { spec = $$eval($${1}.spec) isEmpty(spec): \ error("makeSpec source in library '$$eval($${1}.library)' does not specify 'spec'.") $${1}.includedir = "$$val_escape(QMAKE_INCDIR_$$spec)" export($${1}.includedir) libs = for (l, QMAKE_LIBDIR_$$spec): \ libs += -L$$l libs += $$eval(QMAKE_LIBS_$$spec) $${1}.libs = "$$val_escape(libs)" export($${1}.libs) # the library definition is always in scope, so no point in exporting it. $${1}.export = false export($${1}.export) return(true) } # the library is found via pkg-config. defineTest(qtConfLibrary_pkgConfig) { pkg_config = $$qtConfPkgConfig($$eval($${1}.host)) isEmpty(pkg_config) { qtLog("pkg-config use disabled globally.") return(false) } args = $$qtConfPrepareArgs($$eval($${1}.args)) !qtConfPkgConfigPackageExists($$pkg_config, $$args) { qtLog("pkg-config did not find package.") return(false) } qtRunLoggedCommand("$$pkg_config --modversion $$args", version)|return(false) qtRunLoggedCommand("$$pkg_config --libs-only-L $$args", libpaths)|return(false) qtRunLoggedCommand("$$pkg_config --libs-only-l $$args", libs)|return(false) qtRunLoggedCommand("$$pkg_config --cflags $$args", $${1}.cflags)|return(false) version ~= s/[^0-9.].*$// $${1}.version = $$first(version) export($${1}.version) libpaths += $$libs $${1}.libs = "$$libpaths" export($${1}.libs) return(true) } defineTest(qtConfTest_getPkgConfigVariable) { pkg_config = $$qtConfPkgConfig($$eval($${1}.host)) isEmpty(pkg_config): \ return(false) args = $$qtConfPrepareArgs($$eval($${1}.pkg-config-args)) !qtConfPkgConfigPackageExists($$pkg_config, $$args): \ return(false) variable = $$eval($${1}.pkg-config-variable) qtRunLoggedCommand("$$pkg_config --variable=$$variable $$args", $${1}.value)|return(false) export($${1}.value) $${1}.cache += value export($${1}.cache) return(true) } defineReplace(qtConfLibraryArgs) { qmake_args = libs = $$eval($${1}.libs) !isEmpty(libs): \ qmake_args += $$system_quote(LIBS += $$libs) for (b, $${1}.builds._KEYS_): \ qmake_args += $$system_quote(LIBS_$$upper($$b) += $$eval($${1}.builds.$${b})) includedir = $$eval($${1}.includedir) !isEmpty(includedir): \ qmake_args += $$system_quote(INCLUDEPATH *= $$includedir) cflags = $$eval($${1}.cflags) !isEmpty(cflags): \ qmake_args += $$system_quote(QMAKE_CFLAGS += $$cflags) $$system_quote(QMAKE_CXXFLAGS += $$cflags) return($$qmake_args) } defineTest(qtConfExportLibrary) { lpfx = $${currentConfig}.libraries.$$1 alias = $$eval($${lpfx}.alias) $${currentConfig}.found.$$alias = $$1 export($${currentConfig}.found.$$alias) name = $$eval($${lpfx}.export) isEmpty(name): return() spfx = $${lpfx}.sources.$$eval($${lpfx}.source) !$$qtConfEvaluate($$eval($${spfx}.export)): return() output = privatePro eval(libs = $$eval($${spfx}.libs)) eval(cflags = $$eval($${spfx}.cflags)) eval(includes = $$eval($${spfx}.includedir)) # Split $$cflags into stuff that goes into DEFINES, INCLUDEPATH, and other stuff. defines = ignored = for (i, cflags) { contains(i, "-I.*") { i ~= s/^-I// includes += $$i } else: contains(i, "-D.*") { i ~= s/^-D// defines += $$i } else { # Sometimes, pkg-config files or *-config scripts include other flags # we really don't need and shouldn't add (pg_config is really bad). ignored += $$i } } !isEmpty(ignored): \ qtConfAddNote("Dropped compiler flags '$$ignored' when detecting library '$$name'.") NAME = $$upper($$name) # LIBS is emitted even if empty, as this allows the library to be "seen". qtConfOutputVar(assign, $$output, QMAKE_LIBS_$$NAME, $$libs) for (b, $${spfx}.builds._KEYS_) { eval(blibs = $$eval($${spfx}.builds.$${b})) qtConfOutputVar(assign, $$output, QMAKE_LIBS_$${NAME}_$$upper($$b), $$blibs) } !isEmpty(defines): qtConfOutputVar(assign, $$output, QMAKE_DEFINES_$$NAME, $$defines) !isEmpty(includes): qtConfOutputVar(assign, $$output, QMAKE_INCDIR_$$NAME, $$includes) !isEmpty($${currentConfig}.module): \ qtConfExtendVar($$output, "QT.$${currentModule}_private.libraries", $$name) } defineTest(qtConfHandleLibrary) { lpfx = $${currentConfig}.libraries.$$1 defined($${lpfx}.result, var): return() alias = $$eval($${lpfx}.alias) !isEmpty($${currentConfig}.found.$$alias) { # this happening indicates a logic error in the conditions # of the feature(s) referring to this library. # note that this does not look across module boundaries, as # multiple modules may know the same libraries; de-duplication # happens via the cache (obviously, this assumes identical # definitions and logic). error("A library exporting '$$alias' was already found.") } qtConfEnsureTestTypeDeps("library") !qtConfTestPrepare_compile($$lpfx) { $${lpfx}.result = false export($${lpfx}.result) return() } use_args = $$eval($${lpfx}.literal_args) qtConfLoadResult($${lpfx}, $$1) { $$eval($${lpfx}.result): \ qtConfExportLibrary($$1) return() } qtLogTestIntro($${lpfx}, "looking for library $${1}") result = false for (s, $${lpfx}.sources._KEYS_) { spfx = $${lpfx}.sources.$${s} t = $$eval($${spfx}.type) call = qtConfLibrary_$$t !defined($$call, test): \ error("Library $${1} source $${s} has unknown type '$$t'") qtLog("Trying source $$s (type $$t) of library $${1} ...") cond = $$eval($${spfx}.condition) !$$qtConfEvaluate($$cond) { qtLog(" => source failed condition '$$cond'.") next() } !$${call}($$spfx, $$lpfx) { qtLog(" => source produced no result.") next() } # if the library defines a test, use it to verify the source. !isEmpty($${lpfx}.test)|!isEmpty($${lpfx}.test._KEYS_) { $${lpfx}.literal_args = $$qtConfLibraryArgs($$spfx) $$use_args $${lpfx}.host = $$eval($${spfx}.host) !qtConfTest_compile($$lpfx) { qtLog(" => source failed verification.") next() } } qtLog(" => source accepted.") $${lpfx}.cache += source for (v, $$list(libs includedir cflags version export)): \ $${lpfx}.cache += sources.$${s}.$${v} for (b, $${spfx}.builds._KEYS_): \ $${lpfx}.cache += sources.$${s}.builds.$${b} $${lpfx}.source = $$s export($${lpfx}.source) # immediately output the library as well. qtConfExportLibrary($$1) result = true break() } qtLogTestResult($${lpfx}, $$result) $${lpfx}.result = $$result export($${lpfx}.result) qtConfSaveResult($${lpfx}, $$1) } # This is a fake test type for the test dependency system. defineTest(qtConfTest_library) { error("The test type 'library' may not be instantiated.") } defineTest(qtConfTestPrepare_compile) { !isEmpty($${1}.use._KEYS_) { uses = for (k, $${1}.use._KEYS_) { use = $$eval($${1}.use.$${k}.lib) isEmpty(use): \ error("'use' entry $$k in test $$1 lacks 'lib' field.") !$$qtConfEvaluate($$eval($${1}.use.$${k}.condition)): \ next() uses += $$use } } else { uses = $$split($${1}.use) } for (u, uses) { libConfig = exports = $$eval($${currentConfig}.exports.$$u) !isEmpty(exports) { # using a local library by exported name. ru = $$eval($${currentConfig}.found.$$u) !isEmpty(ru) { # if it was already found, all is good. u = $$ru } else: count(exports, 1) { # otherwise, if there is only one option, ensure it's resolved. u = $$exports qtConfHandleLibrary($$u) } else { # otherwise, verify that all options were resolved. for (x, exports) { isEmpty($${currentConfig}.libraries.$${x}.result) { # the higher-level logic is in the features, which we cannot # infer from here. so the only option is failing. error("Test $$1 refers to yet unresolved library export '$$u'") } } return(false) } libConfig = $$currentConfig } else: contains($${currentConfig}.libraries._KEYS_, $$u) { # using a local library by real name. this should be the exception. qtConfHandleLibrary($$u) libConfig = $$currentConfig } else { for (d, QMAKE_LIBRARY_DEPS) { exports = $$eval($${d}.exports.$$u) !isEmpty(exports) { # using a foreign library by exported name. # foreign libraries may be external (if they are from a different # repository and the build is modular), and using these by real # name is impossible. so for consistency, uses by real name are # limited to local libraries. ru = $$eval($${d}.found.$$u) !isEmpty(ru) { u = $$ru libConfig = $$d break() } for (x, exports) { isEmpty($${d}.libraries.$${x}.result): \ error("Test $$1 refers to unresolved library export '$$u' in '$$d'") } return(false) } } } isEmpty(libConfig) { nu = $$upper($$u) !defined(QMAKE_LIBS_$$nu, var): \ error("Test $$1 tries to use undeclared library '$$u'") # using an external library by exported name. $${1}.literal_args += $$system_quote(QMAKE_USE += $$u) } else { lpfx = $${libConfig}.libraries.$${u} isEmpty($${lpfx}.source): \ return(false) $${1}.literal_args += $$qtConfLibraryArgs($${lpfx}.sources.$$eval($${lpfx}.source)) } } export($${1}.literal_args) return(true) } defineTest(qtConfPrepareCompileTestSource) { test_dir = $$2 test_lang = $$eval($${1}.lang) isEmpty(test_lang): test_lang = "c++" equals(test_lang, "c++"): suffix = "cpp" else: equals(test_lang, "c"): suffix = "c" else: equals(test_lang, "objc"): suffix = "m" else: equals(test_lang, "objc++"): suffix = "mm" else: error("Unknown language '$$test_lang' in compile test $$1") # Create source code contents = "/* Generated by configure */" # Custom code before includes for (ent, $$qtConfScalarOrList($${1}.head)): \ contents += $$ent # Includes for (ent, $$qtConfScalarOrList($${1}.include)): \ contents += "$${LITERAL_HASH}include <$$ent>" # Custom code after includes for (ent, $$qtConfScalarOrList($${1}.tail)): \ contents += $$ent # And finally the custom code inside main() contents += \ "int main(int argc, char **argv)" \ "{" \ " (void)argc; (void)argv;" \ " /* BEGIN TEST: */" for (ent, $$qtConfScalarOrList($${1}.main)): \ contents += " $$ent" contents += \ " /* END TEST */" \ " return 0;" \ "}" write_file($$test_dir/main.$$suffix, contents)|error() # Create stub .pro file contents = "SOURCES = main.$$suffix" # Custom project code for (ent, $$qtConfScalarOrList($${1}.qmake)): \ contents += $$ent write_file($$test_dir/$$basename(test_dir).pro, contents)|error() } defineTest(qtConfTest_compile) { test = $$eval($${1}.test) host = $$eval($${1}.host) isEmpty(host): host = false test_base_out_dir = $$OUT_PWD/$$basename(QMAKE_CONFIG_TESTS_DIR) isEmpty(test) { test_dir = $$test_base_out_dir/$$section(1, ".", -1) test_out_dir = $$test_dir qtConfPrepareCompileTestSource($${1}.test, $$test_dir) } else { test_dir = $$QMAKE_CONFIG_TESTS_DIR/$$test test_out_dir = $$test_base_out_dir/$$test !isEmpty($${1}.pro): \ test_dir = $$test_dir/$$eval($${1}.pro) } test_cmd_base = "$$QMAKE_CD $$system_quote($$system_path($$test_out_dir)) &&" qmake_args = $$qtConfPkgConfigEnv()$$system_quote($$system_path($$QMAKE_QMAKE)) !isEmpty(QMAKE_QTCONF): \ qmake_args += -qtconf $$system_quote($$QMAKE_QTCONF) # Disable qmake features which are typically counterproductive for tests qmake_args += "\"CONFIG -= qt debug_and_release app_bundle lib_bundle\"" # allow tests to behave differently depending on the type of library # being built (shared/static). e.g. see config.tests/unix/icu shared: \ qmake_configs = "shared" else: \ qmake_configs = "static" use_gold_linker: \ qmake_configs += "use_gold_linker" # disable warnings from the builds, since they're just noise at this point. qmake_configs += "warn_off" # add console to the CONFIG variable when running the tests, so that they # can work with a regular main() entry point on Windows. qmake_configs += "console" # for platforms with multiple architectures (macOS, iOS, tvOS, watchOS), # make sure tests are only built for a single architecture qmake_configs += "single_arch" qmake_args += "\"CONFIG += $$qmake_configs\"" !$$host|!cross_compile { # On WinRT we need to change the entry point as we cannot create windows # applications winrt: \ qmake_args += " \"QMAKE_LFLAGS += /ENTRY:main\"" # add compiler flags, these are set for the target and should not be applied to host tests !isEmpty(EXTRA_DEFINES): \ qmake_args += $$system_quote(DEFINES += $$val_escape(EXTRA_DEFINES)) !isEmpty(EXTRA_LIBDIR) \ qmake_args += $$system_quote(QMAKE_LIBDIR += $$val_escape(EXTRA_LIBDIR)) !isEmpty(EXTRA_FRAMEWORKPATH) \ qmake_args += $$system_quote(QMAKE_FRAMEWORKPATH += $$val_escape(EXTRA_FRAMEWORKPATH)) !isEmpty(EXTRA_INCLUDEPATH): \ qmake_args += $$system_quote(INCLUDEPATH += $$val_escape(EXTRA_INCLUDEPATH)) qmake_args += $$EXTRA_QMAKE_ARGS } # make sure to make this the last override (because of -early) cross_compile { # must be done before loading default_pre.prf. qmake_args += -early "\"CONFIG += cross_compile\"" } # Clean up after previous run exists($$test_out_dir/Makefile): \ QMAKE_MAKE = "$$QMAKE_MAKE clean && $$QMAKE_MAKE" mkpath($$test_out_dir)|error() write_file($$test_base_out_dir/.qmake.cache)|error() # add possible command line args qmake_args += $$qtConfPrepareArgs($$eval($${1}.args)) $$eval($${1}.literal_args) qtRunLoggedCommand("$$test_cmd_base $$qmake_args $$system_quote($$test_dir)") { qtRunLoggedCommand("$$test_cmd_base $$QMAKE_MAKE"): \ return(true) } return(false) } defineTest(qtConfTest_verifySpec) { qtConfTest_compile($$1): return(true) qtConfFatalError("Cannot compile a minimal program. The toolchain or QMakeSpec is broken.", log) } defineTest(qtConfTest_files) { for(i, $${1}.files._KEYS_) { f = $$eval($${1}.files.$${i}) qtLog("Searching for file $${f}.") contains(f, ".*\.h") { file = $$qtConfFindInPathList($$f, $$EXTRA_INCLUDEPATH $$QMAKE_DEFAULT_INCDIRS) } else: contains(f, ".*\.(lib|so|a)") { file = $$qtConfFindInPathList($$f, $$EXTRA_LIBDIR $$QMAKE_DEFAULT_LIBDIRS) } else { # assume we're looking for an executable file = $$qtConfFindInPath($$f, $$EXTRA_PATH) } isEmpty(file) { qtLog(" Not found."); return(false) } qtLog(" Found at $${file}.") } return(true) } defineTest(logn) { log("$${1}$$escape_expand(\\n)") } defineTest(qtLogTestIntro) { label = $$eval($${1}.label) isEmpty(label): return() log("Checking for $${label}... ") $$QMAKE_CONFIG_VERBOSE: log("$$escape_expand(\\n)") write_file($$QMAKE_CONFIG_LOG, 2, append) } defineTest(qtLogTestResult) { isEmpty($${1}.label): return() !isEmpty($${1}.log) { field = $$eval($${1}.log) log_msg = $$eval($${1}.$$field) msg = "test $$1 gave result $$log_msg" } else: $${2} { log_msg = yes msg = "test $$1 succeeded" } else { log_msg = no msg = "test $$1 FAILED" } $$QMAKE_CONFIG_VERBOSE: log_msg = $$msg logn("$$log_msg") write_file($$QMAKE_CONFIG_LOG, msg, append) } defineTest(qtConfSaveResult) { equals($${1}.cache, -): \ return() keys = result $$eval($${1}.cache) cont = "cache.$${2}._KEYS_ = $$keys" cache.$${2}._KEYS_ = $$keys export(cache.$${2}._KEYS_) for (k, keys) { cont += "cache.$${2}.$${k} = $$val_escape($${1}.$${k})" cache.$${2}.$${k} = $$eval($${1}.$${k}) export(cache.$${2}.$${k}) } write_file($$QMAKE_CONFIG_CACHE, cont, append)|error() } defineTest(qtConfLoadResult) { equals(QMAKE_CONFIG_CACHE_USE, none): \ return(false) isEmpty(cache.$${2}._KEYS_): \ return(false) equals(QMAKE_CONFIG_CACHE_USE, positive):!$$eval(cache.$${2}.result): \ return(false) for (k, cache.$${2}._KEYS_) { $${1}.$${k} = $$eval(cache.$${2}.$${k}) export($${1}.$${k}) } return(true) } defineTest(qtConfIsBoolean) { equals(1, "true")|equals(1, "false"): \ return(true) return(false) } defineTest(qtConfSetupTestTypeDeps) { for (tt, $${currentConfig}.testTypeDependencies._KEYS_) { !defined(qtConfTest_$${tt}, test): \ error("Declaring dependency for undefined test type '$$tt'.") for (f, $${currentConfig}.testTypeDependencies.$${tt}._KEYS_) { feature = $$eval($${currentConfig}.testTypeDependencies.$${tt}.$${f}) isEmpty($${currentConfig}.features.$${feature}._KEYS_): \ error("Test type '$$tt' depends on undefined feature '$$feature'.") } } # Test type aliasing means that one test type's callback is called by # another test type's callback. Put differently, one callback forwards # the call to another one. The former representation is more natural # (and concise) to write, while the latter is more efficient to process. # Hence, this function inverts the mapping. for (tt, $${currentConfig}.testTypeAliases._KEYS_) { !defined(qtConfTest_$${tt}, test): \ error("Aliasing undefined test type '$$tt'.") for (tta, $${currentConfig}.testTypeAliases.$${tt}._KEYS_) { type = $$eval($${currentConfig}.testTypeAliases.$${tt}.$${tta}) !defined(qtConfTest_$${type}, test): \ error("Aliasing '$$tt' to undefined test type '$$type'.") $${currentConfig}.testTypeForwards.$${type} += $$tt export($${currentConfig}.testTypeForwards.$${type}) } } } defineTest(qtConfEnsureTestTypeDeps) { depsn = $${currentConfig}.testTypeDependencies.$${1}._KEYS_ !isEmpty($$depsn) { for (dep, $$depsn) { feature = $$eval($${currentConfig}.testTypeDependencies.$${1}.$${dep}) !qtConfCheckFeature($$feature): \ error("Test type '$$1' depends on non-emitted feature $${feature}.") } $$depsn = export($$depsn) } fwdsn = $${currentConfig}.testTypeForwards.$${1} !isEmpty($$fwdsn) { for (fwd, $$fwdsn): \ qtConfEnsureTestTypeDeps($$fwd) $$fwdsn = export($$fwdsn) } } defineTest(qtRunSingleTest) { tpfx = $${currentConfig}.tests.$${1} defined($${tpfx}.result, var): \ return() type = $$eval($${tpfx}.type) call = "qtConfTest_$$type" !defined($$call, test): \ error("Configure test $${1} refers to nonexistent type $$type") qtConfEnsureTestTypeDeps($$type) preCall = "qtConfTestPrepare_$$type" defined($$preCall, test):!$${preCall}($${tpfx}) { $${tpfx}.result = false export($${tpfx}.result) # don't cache the result; the pre-deps have their own caches. return() } # note: we do this only after resolving the dependencies and the # preparation (which may resolve libraries), so that caching does # not alter the execution order (and thus the output). qtConfLoadResult($${tpfx}, $$1): \ return() qtLogTestIntro($${tpfx}, "executing config test $${1}") result = false $${call}($${tpfx}): result = true qtLogTestResult($${tpfx}, $$result) $${tpfx}.result = $$result export($${tpfx}.result) qtConfSaveResult($${tpfx}, $$1) } defineTest(qtConfHaveModule) { module = $$replace(1, -, _) !isEmpty(QT.$${module}.skip):$$eval(QT.$${module}.skip): \ return(false) !isEmpty(QT.$${module}.name): \ return(true) return(false) } defineReplace(qtConfEvaluate) { isEmpty(1): return(true) 1 ~= s/$$escape_expand(\\t)/ /g 1 ~= s/$$escape_expand(\\r)//g 1 ~= s/$$escape_expand(\\n) */ /g expr = $${1} expr ~= s/&&/ && /g expr ~= s/\|\|/ || /g expr ~= s/!/ ! /g expr ~= s/\\(/ ( /g expr ~= s/\\)/ ) /g expr ~= s/ *== */==/g expr ~= s/ *! = */!=/g expr_list = $$eval($$list($$expr)) return($$qtConfEvaluateSubExpression($${1}, $$expr_list, 0)) } defineReplace(qtConfEvaluateSingleExpression) { e = $${2} equals(e, true) { result = true } else: equals(e, false) { result = false } else: contains(e, "^[0-9]+$") { # numbers result = $$e } else: contains(e, "^'.*'$") { # quoted literals result = $$replace(e, "^'(.*)'$", "\\1") } else: contains(e, "^tests\..*") { !qt_conf_tests_allowed: \ error("Expression '$${1}' refers to a test, which is not allowed at this stage of configuring.") test = $$section(e, ".", 1, 1) var = $$section(e, ".", 2, -1) isEmpty(var): \ var = result !contains($${currentConfig}.tests._KEYS_, $$test): \ error("Unknown test object $${test} in expression '$${1}'.") qtRunSingleTest($$test) result = $$eval($${currentConfig}.tests.$${test}.$${var}) } else: contains(e, "^libs\..*") { !qt_conf_tests_allowed: \ error("Expression '$${1}' refers to a library, which is not allowed at this stage of configuring.") lib = $$section(e, ".", 1, 1) var = $$section(e, ".", 2, -1) isEmpty(var): \ var = result !contains($${currentConfig}.libraries._KEYS_, $$lib): \ error("Unknown library object $${lib} in expression '$${1}'.") qtConfHandleLibrary($$lib) !defined($${currentConfig}.libraries.$${lib}.$${var}, var): \ var = sources.$$eval($${currentConfig}.libraries.$${lib}.source).$$var result = $$eval($${currentConfig}.libraries.$${lib}.$${var}) } else: contains(e, "^features\..*") { feature = $$section(e, ".", 1, 1) var = $$section(e, ".", 2, -1) isEmpty(var): \ var = available !contains($${currentConfig}.features._KEYS_, $$feature) { # this is basically a copy of what qtConfig() in qt_build_config.prf # does, but we produce a nicer error message. for (module, QMAKE_CONFIG_DEPS) { contains(QT.$${module}.enabled_features, $$feature): \ result = true else: contains(QT.$${module}.disabled_features, $$feature): \ result = false else: \ next() !equals(var, available): \ error("Expression '$$1' is accessing field '$$var' of non-local feature $${feature}.") return($$result) } error("Unknown feature object $${feature} in expression '$${1}'.") } !qtConfCheckFeature($$feature): \ error("Expression '$$1' is accessing non-emitted feature $${feature}.") result = $$eval($${currentConfig}.features.$${feature}.$${var}) } else: contains(e, "^config\..*") { var = $$replace(e, "^config\.", "") result = false contains(CONFIG, $$var): result = true } else: contains(e, "^module\..*") { var = $$replace(e, "^module\.", "") result = false qtConfHaveModule($$var): result = true } else: contains(e, "^arch\..*") { var = $$replace(e, "^arch\.", "") result = false isEmpty(QT_ARCH): \ qtConfCheckFeature(architecture) contains(QT_ARCH, $$var): result = true } else: contains(e, "^subarch\..*") { var = $$replace(e, "^subarch\.", "") result = false isEmpty(QT_ARCH): \ qtConfCheckFeature(architecture) contains(QT_CPU_FEATURES.$$QT_ARCH, $$var): result = true } else: contains(e, "^input\..*") { result = $$eval(config.$$e) } else: contains(e, "^var\..*") { var = $$replace(e, "^var\.", "") result = $$eval($$var) } else: contains(e, "^call\..*") { call = $$replace(e, "^call\.", "qtConfFunc_") !defined($$call, replace): \ error("Call $$call referenced in expression '$${1}' does not exist") eval(result = \$\$"$$call"()) } else { error("Unrecognized token $$e in expression '$${1}'") } return($$result) } defineReplace(qtConfEvaluateSubExpression) { expr_list = $${2} result = true negate = false runSubExpression = false nesting_level = 0 for (n, $${3}..$$num_add($$size(expr_list), -1)) { e = $$member(expr_list, $$n) $$runSubExpression { runSubExpression = false result = $$qtConfEvaluateSubExpression($${1}, $$expr_list, $$n) } else: isEqual(e, "(") { isEqual(nesting_level, 0): runSubExpression = true nesting_level = $$num_add($$nesting_level, 1) next() } else: isEqual(e, ")") { nesting_level = $$num_add($$nesting_level, -1) lessThan(nesting_level, 0): break() next() } else: greaterThan(nesting_level, 0) { next() } else: isEqual(e, "!") { negate = true next() } else: isEqual(e, "&&") { !qtConfIsBoolean($$result): \ error("Left hand side of && is non-boolean value '$$result' in expression '$${1}'") !$$result: return(false) } else: isEqual(e, "||") { !qtConfIsBoolean($$result): \ error("Left hand side of || is non-boolean value '$$result' in expression '$${1}'") $$result: return(true) } else { contains(e, ".*==.*") { lhs = $$qtConfEvaluateSingleExpression($${1}, $$replace(e, "==.*", "")) rhs = $$qtConfEvaluateSingleExpression($${1}, $$replace(e, ".*==", "")) result = false equals(lhs, $$rhs): result = true } else: contains(e, ".*!=.*") { lhs = $$qtConfEvaluateSingleExpression($${1}, $$replace(e, "!=.*", "")) rhs = $$qtConfEvaluateSingleExpression($${1}, $$replace(e, ".*!=", "")) result = false !equals(lhs, $$rhs): result = true } else { result = $$qtConfEvaluateSingleExpression($${1}, $$e) } } $$negate { !qtConfIsBoolean($$result): \ error("Attempting to negate a non-boolean value '$$result' in expression '$${1}'") $$result: \ result = false else: \ result = true negate = false } } return($$result) } defineReplace(qtIsFeatureEnabled) { enable = $$eval($${currentConfig}.features.$${1}.enable) !isEmpty(enable) { $$qtConfEvaluate($$enable): \ return(true) } else { equals(config.input.$${1}, "yes"): \ return(true) } return(false) } defineReplace(qtIsFeatureDisabled) { disable = $$eval($${currentConfig}.features.$${1}.disable) !isEmpty(disable) { $$qtConfEvaluate($$disable): \ return(true) } else { equals(config.input.$${1}, "no"): \ return(true) } return(false) } defineReplace(qtConfCheckSingleCondition) { result = $$qtConfEvaluate($$2) !qtConfIsBoolean($$result): \ error("Evaluation of condition '$$2' yielded non-boolean value '$$result' in feature '$${1}'.") !$$result { $${3} { qtConfAddError("Feature '$${1}' was enabled, but the pre-condition '$$2' failed.", log) $$result = true } } return($$result) } defineTest(qtConfCheckFeature) { fpfx = $${currentConfig}.features.$${1} available = $$eval($${fpfx}.available) !isEmpty(available): return(true) # skip features that will not get emitted anyway emitIf = $$qtConfEvaluate($$eval($${fpfx}.emitIf)) enabled = $$qtIsFeatureEnabled($$1) disabled = $$qtIsFeatureDisabled($$1) !$$emitIf { $$enabled|$$disabled: \ qtConfAddWarning("Feature $${1} is insignificant in this configuration, ignoring related command line option(s).") return(false) } $$disabled { result = false } else: !$$enabled:!$$qtConfEvaluate($$eval($${fpfx}.autoDetect)) { # feature not auto-detected and not explicitly enabled result = false } else { result = true for (condition, $$qtConfScalarOrList($${fpfx}.condition)) { result = $$qtConfCheckSingleCondition($$1, $$condition, $$enabled) !$$result: break() } } $${fpfx}.available = $$result export($${fpfx}.available) for (i, $${fpfx}.output._KEYS_): \ qtConfProcessOneOutput($${1}, $$i) return(true) } defineTest(qtConfCheckModuleCondition) { QT.$${currentModule}.skip = false !$$qtConfEvaluate($$eval($${currentConfig}.condition)): \ QT.$${currentModule}.skip = true export(QT.$${currentModule}.skip) # ensure qtConfHaveModule() works QT.$${currentModule}.name = - export(QT.$${currentModule}.name) } defineTest(qtConfProcessFeatures) { for (feature, $${currentConfig}.features._KEYS_): \ qtConfCheckFeature($$feature) } # # reporting # defineReplace(qtConfPadCols) { pad = $$num_add($$str_size($$2), -$$str_size($${1})) lessThan(pad, 0): pad = 0 return("$$1 $$str_member($$2, 0, $$pad) $$3") } defineTest(qtConfReportPadded) { qtConfAddReport($$qtConfPadCols($$1, "........................................", $$2)) } defineReplace(qtConfCollectFeatures) { l = for (feature, $$list($${1})) { $$eval($${currentConfig}.features.$${feature}.available): \ l += $$eval($${currentConfig}.features.$${feature}.label) } isEmpty(l): return("") return($$join(l, ' ')) } defineTest(qtConfReport_featureList) { qtConfReportPadded($${1}, $$qtConfCollectFeatures($${2})) } defineReplace(qtConfFindFirstAvailableFeature) { for (feature, $$list($${1})) { isEmpty($${currentConfig}.features.$${feature}._KEYS_): \ error("Asking for a report on undefined feature $${2}.") $$eval($${currentConfig}.features.$${feature}.available): \ return($$eval($${currentConfig}.features.$${feature}.label)) } return("") } defineTest(qtConfReport_firstAvailableFeature) { qtConfReportPadded($${1}, $$qtConfFindFirstAvailableFeature($${2})) } defineTest(qtConfReport_feature) { !contains($${currentConfig}.features._KEYS_, $$2): \ error("Asking for a report on undefined feature $${2}.") # hide report for not emitted features isEmpty($${currentConfig}.features.$${2}.available): \ return() $$eval($${currentConfig}.features.$${2}.available) { result = "yes" !isEmpty(3): result = "$${3}" } else { result = "no" !isEmpty(4): result = "$${4}" } text = $$eval($${currentConfig}.features.$${2}.label) qtConfReportPadded($${1}$$text, $$result) } defineTest(qtConfReport_note) { qtConfAddNote($${1}) } defineTest(qtConfReport_warning) { qtConfAddWarning($${1}) } defineTest(qtConfReport_error) { qtConfAddError($${1}, log) } defineTest(qtConfReport_fatal) { qtConfFatalError($${1}) } defineTest(qtConfCreateReportRecurse) { equals(2, false) { indent = "" recurse = false } else { indent = $${2} recurse = true } keys = $$eval($${1}._KEYS_) for (n, keys) { entry = $${1}.$$n subKeys = $$eval($${entry}._KEYS_) contains(subKeys, condition) { condition = $$eval($${entry}.condition) r = $$qtConfEvaluate($$condition) !qtConfIsBoolean($$r): \ error("Evaluation of condition '$$condition' in report entry $${entry} yielded non-boolean value '$$r'.") !$$r: next() } contains(subKeys, "section") { !$$recurse: \ error("Report type 'section' is not allowed in '$$1'.") section = $$eval($${entry}.section) qtConfAddReport("$$indent$$section:") qtConfCreateReportRecurse("$${entry}.entries", "$$indent ") } else: !isEmpty($${entry}) { feature = $$eval($${entry}) qtConfReport_feature($$indent, $$feature) } else { text = $$eval($${entry}.message) isEmpty($${entry}.type): \ error("Report entry $${entry} doesn't define a type.") r = "qtConfReport_$$eval($${entry}.type)" !defined($$r, test): \ error("Undefined report type $$eval($${entry}.type) used in report entry $${entry}.") args = $$eval($${entry}.args) $${r}($$indent$${text}, $$args) } } } defineTest(qtConfProcessEarlyChecks) { qtConfCreateReportRecurse($${currentConfig}.earlyReport, false) } defineTest(qtConfCreateReport) { qtConfCreateReportRecurse($${currentConfig}.report, false) } defineTest(qtConfCreateSummary) { qtConfCreateReportRecurse($${currentConfig}.summary, "") } defineTest(qtConfPrintReport) { blocks = \ "$$join(QT_CONFIGURE_REPORT, $$escape_expand(\\n))" \ "$$join(QT_CONFIGURE_NOTES, $$escape_expand(\\n\\n))" \ "$$join(QT_CONFIGURE_WARNINGS, $$escape_expand(\\n\\n))" !isEmpty(QT_CONFIGURE_ERRORS) { blocks += "$$join(QT_CONFIGURE_ERRORS, $$escape_expand(\\n\\n))" mention_config_log:!$$QMAKE_CONFIG_VERBOSE: \ blocks += "Check config.log for details." } blocks = "$$join(blocks, $$escape_expand(\\n\\n))" logn($$blocks) !isEmpty(QT_CONFIGURE_ERRORS):!equals(config.input.continue, yes): \ error() write_file($$OUT_PWD/config.summary, blocks)|error() } defineTest(qtConfCheckErrors) { !isEmpty(QT_CONFIGURE_ERRORS):!equals(config.input.continue, yes): \ qtConfPrintReport() } # # output generation # # qtConfOutputVar(modifier, output, name, value) defineTest(qtConfOutputVar) { modifier = $$1 output = $$2 name = $$3 value = $$val_escape(4) defined($${currentConfig}.output.$${output}.assign.$${name}, var): \ error("Trying to overwrite assigned variable '$$name' in '$$output' using modifier '$$modifier'.") equals(modifier, assign) { !isEmpty($${currentConfig}.output.$${output}.append.$${name})|!isEmpty($${currentConfig}.output.$${output}.remove.$${name}): \ error("Trying to assign variable '$$name' in '$$output', which has already appended or removed parts.") $${currentConfig}.output.$${output}.assign.$${name} = $$value } else: equals(modifier, append) { contains($${currentConfig}.output.$${output}.remove.$${name}, $$value): \ error("Trying to append removed '$$value' to variable '$$name' in '$$output'.") $${currentConfig}.output.$${output}.append.$${name} += $$value } else: equals(modifier, remove) { contains($${currentConfig}.output.$${output}.append.$${name}, $$value): \ error("Trying to remove appended '$$value' to variable '$$name' in '$$output'.") $${currentConfig}.output.$${output}.remove.$${name} += $$value } else { error("Invalid modifier '$$modifier' passed to qtConfOutputVar.") } $${currentConfig}.output.$${output}.$${modifier}._KEYS_ *= $${name} export($${currentConfig}.output.$${output}.$${modifier}.$${name}) export($${currentConfig}.output.$${output}.$${modifier}._KEYS_) } # qtConfExtendVar(output, name, value) defineTest(qtConfExtendVar) { output = $$1 name = $$2 value = $$val_escape(3) !defined($${currentConfig}.output.$${output}.assign.$${name}, var): \ error("Trying to extend undefined variable '$$name' in '$$output'.") $${currentConfig}.output.$${output}.assign.$${name} += $$value export($${currentConfig}.output.$${output}.assign.$${name}) } defineTest(qtConfOutputVarHelper) { !isEmpty($${2}.public):$$eval($${2}.public) { output = "publicPro" } else { output = "privatePro" } negative = $$eval($${2}.negative) isEmpty(negative): negative = false equals(3, $$negative): return() name = $$eval($${2}.name) isEmpty(name): \ error("Output type 'var$$title($$1)' used in feature '$$eval($${2}.feature)' without a 'name' entry.") value = $$qtConfEvaluate($$eval($${2}.value)) !isEmpty($${2}.eval):$$qtConfEvaluate($$eval($${2}.eval)): \ eval(value = $$value) qtConfOutputVar($$1, $$output, $$name, $$value) equals(output, "publicPro"):!isEmpty($${currentConfig}.module): \ qtConfExtendVar($$output, "QT.$${currentModule}.exports", $$name) } defineTest(qtConfOutput_varAssign) { qtConfOutputVarHelper(assign, $$1, $$2) } defineTest(qtConfOutput_varAppend) { qtConfOutputVarHelper(append, $$1, $$2) } defineTest(qtConfOutput_varRemove) { qtConfOutputVarHelper(remove, $$1, $$2) } defineTest(qtConfOutputConfigVar) { pro = $$3 var = $$4 modular = $$5 negative = $$eval($${1}.negative) isEmpty(negative): negative = false equals(2, $$negative): return() val = $$eval($${1}.name) isEmpty(val) { val = $$eval($${1}.feature) $$negative: val = no-$$val } isEmpty($${currentConfig}.module)|!$$modular: \ qtConfOutputVar(append, $$pro, $$var, $$val) else: \ qtConfExtendVar($$pro, "QT.$${currentModule}.$$var", $$val) } defineTest(qtConfOutput_publicQtConfig) { qtConfOutputConfigVar($$1, $$2, "publicPro", "QT_CONFIG", true) } defineTest(qtConfOutput_publicConfig) { !isEmpty($${currentConfig}.module): \ error("Cannot use output type 'publicConfig' in module-local feature '$$eval($${1}.feature)'.") qtConfOutputConfigVar($$1, $$2, "publicPro", "CONFIG", false) } defineTest(qtConfOutput_privateConfig) { qtConfOutputConfigVar($$1, $$2, "privatePro", "CONFIG", false) } defineTest(qtConfOutputSetDefine) { $${currentConfig}.output.$${1}.$${2} = $${3} $${currentConfig}.output.$${1}._KEYS_ *= $${2} export($${currentConfig}.output.$${1}.$${2}) export($${currentConfig}.output.$${1}._KEYS_) } defineTest(qtConfOutput_define) { output = publicHeader define = $$eval($${1}.name) value = $$qtConfEvaluate($$eval($${1}.value)) isEmpty(define): \ error("Output type 'define' used in feature '$$eval($${1}.feature)' without a 'name' entry.") negative = $$eval($${1}.negative) isEmpty(negative): negative = false equals(2, $$negative): return() qtConfOutputSetDefine($$output, $$define, $$value) } defineTest(qtConfOutput_feature) { name = "$$eval($${1}.name)" isEmpty(name): \ name = $$eval($${1}.feature) $${2} { isEmpty($${currentConfig}.module): \ qtConfOutputVar(append, "publicPro", "QT_CONFIG", $$name) else: \ qtConfExtendVar("publicPro", "QT.$${currentModule}.QT_CONFIG", $$name) } else { f = $$upper($$replace(name, -, _)) qtConfOutputSetDefine("publicHeader", "QT_NO_$$f") } } defineTest(qtConfSetModuleName) { currentModule = $$eval($${currentConfig}.module) isEmpty(currentModule): \ currentModule = global export(currentModule) } defineTest(qtConfSetupModuleOutputs) { qtConfOutputVar(assign, "publicPro", "QT.$${currentModule}.enabled_features", ) qtConfOutputVar(assign, "publicPro", "QT.$${currentModule}.disabled_features", ) qtConfOutputVar(assign, "privatePro", "QT.$${currentModule}_private.enabled_features", ) qtConfOutputVar(assign, "privatePro", "QT.$${currentModule}_private.disabled_features", ) !isEmpty($${currentConfig}.module) { qtConfOutputVar(assign, "publicPro", "QT.$${currentModule}.QT_CONFIG", ) qtConfOutputVar(assign, "publicPro", "QT.$${currentModule}.exports", ) qtConfOutputVar(assign, "privatePro", "QT.$${currentModule}_private.libraries", ) } } defineTest(qtConfOutput_publicFeature) { name = "$$eval($${1}.name)" isEmpty(name): \ name = $$eval($${1}.feature) feature = $$replace(name, [-+.], _) $${2} { qtConfExtendVar("publicPro", "QT.$${currentModule}.enabled_features", $$name) QT.$${currentModule}.enabled_features += $$name export(QT.$${currentModule}.enabled_features) qtConfOutputSetDefine("publicHeader", "QT_FEATURE_$$feature", 1) } else { qtConfExtendVar("publicPro", "QT.$${currentModule}.disabled_features", $$name) QT.$${currentModule}.disabled_features += $$name export(QT.$${currentModule}.disabled_features) qtConfOutputSetDefine("publicHeader", "QT_FEATURE_$$feature", -1) } } defineTest(qtConfOutput_privateFeature) { name = "$$eval($${1}.name)" isEmpty(name): \ name = $$eval($${1}.feature) feature = $$replace(name, [-+.], _) $${2} { qtConfExtendVar("privatePro", "QT.$${currentModule}_private.enabled_features", $$name) QT.$${currentModule}_private.enabled_features += $$name export(QT.$${currentModule}_private.enabled_features) qtConfOutputSetDefine("privateHeader", "QT_FEATURE_$$feature", 1) } else { qtConfExtendVar("privatePro", "QT.$${currentModule}_private.disabled_features", $$name) QT.$${currentModule}_private.disabled_features += $$name export(QT.$${currentModule}_private.disabled_features) qtConfOutputSetDefine("privateHeader", "QT_FEATURE_$$feature", -1) } } defineTest(qtConfProcessOneOutput) { feature = $${1} fpfx = $${currentConfig}.features.$${feature} opfx = $${fpfx}.output.$${2} call = $$eval($${opfx}.type) isEmpty(call) { # output is just a string, not an object call = $$eval($$opfx) } !defined("qtConfOutput_$$call", test): \ error("Undefined type '$$call' in output '$$2' of feature '$$feature'.") !$$qtConfEvaluate($$eval($${opfx}.condition)): \ return() $${opfx}.feature = $$feature qtConfOutput_$${call}($$opfx, $$eval($${fpfx}.available)) } defineTest(qtConfProcessOutput) { !contains($${currentConfig}._KEYS_, "features"): \ return() basedir = $$shadowed($$eval($${currentConfig}.dir)) module = $$eval($${currentConfig}.module) # write it to the output files !defined($${currentConfig}.files._KEYS_, var) { # set defaults that should work for most Qt modules isEmpty(module): \ error("Neither module nor files section specified in configuration file.") $${currentConfig}.files._KEYS_ = publicPro privatePro publicHeader privateHeader $${currentConfig}.files.publicPro = qt$${module}-config.pri $${currentConfig}.files.privatePro = qt$${module}-config.pri # sic! $${currentConfig}.files.publicHeader = qt$${module}-config.h $${currentConfig}.files.privateHeader = qt$${module}-config_p.h } for (type, $${currentConfig}.files._KEYS_) { contains(type, ".*Pro") { for (k, $${currentConfig}.output.$${type}.assign._KEYS_): \ $${currentConfig}.output.$$type += "$$k = $$eval($${currentConfig}.output.$${type}.assign.$$k)" for (k, $${currentConfig}.output.$${type}.remove._KEYS_): \ $${currentConfig}.output.$$type += "$$k -= $$eval($${currentConfig}.output.$${type}.remove.$$k)" for (k, $${currentConfig}.output.$${type}.append._KEYS_): \ $${currentConfig}.output.$$type += "$$k += $$eval($${currentConfig}.output.$${type}.append.$$k)" } else: contains(type, ".*Header") { for (define, $${currentConfig}.output.$${type}._KEYS_) { value = $$eval($${currentConfig}.output.$${type}.$${define}) $${currentConfig}.output.$$type += "$${LITERAL_HASH}define $$define $$value" } } content = $$eval($${currentConfig}.output.$${type}) !isEmpty(module): \ call = qtConfOutputPostProcess_$${module}_$${type} else: \ call = qtConfOutputPostProcess_$${type} defined($$call, replace): \ eval(content = \$\$"$$call"(\$\$content)) file = $$eval($${currentConfig}.files.$${type}) fileCont.$$file += $$content fileCont._KEYS_ *= $$file } for (file, fileCont._KEYS_): \ write_file($$basedir/$$file, fileCont.$$file)|error() } # # tie it all together # !isEmpty(_QMAKE_SUPER_CACHE_):!equals(OUT_PWD, $$dirname(_QMAKE_SUPER_CACHE_)) { # sub-repo within a top-level build; no need to configure anything. !isEmpty(QMAKE_EXTRA_ARGS) { # sub-projects don't get the extra args passed down automatically, # so we can use their presence to detect misguided attempts to # configure the repositories separately. # caveat: a plain qmake call is indistinguishable from a recursion # (by design), so we cannot detect this case. error("You cannot configure $$TARGET separately within a top-level build.") } return() } config.$${TARGET}.dir = $$_PRO_FILE_PWD_ cfgs = $$TARGET !isEmpty(_QMAKE_SUPER_CACHE_) { for (s, SUBDIRS) { config.$${s}.dir = $$_PRO_FILE_PWD_/$${s} cfgs += $$s } } configsToProcess = for (c, cfgs) { s = $$eval(config.$${c}.dir) exists($$s/configure.json): \ configsToProcess += $$c } isEmpty(configsToProcess) { !isEmpty(QMAKE_EXTRA_ARGS): \ error("This module does not accept configure command line arguments.") return() } load(configure_base) QMAKE_POST_CONFIGURE = config.builtins.dir = $$PWD/data configsToProcess = builtins $$configsToProcess allConfigs = for(ever) { isEmpty(configsToProcess): \ break() currentConfig = config.$$take_first(configsToProcess) thisDir = $$eval($${currentConfig}.dir) jsonFile = $$thisDir/configure.json priFile = $$thisDir/configure.pri # load configuration data configure_data = $$cat($$jsonFile, blob) !parseJson(configure_data, $$currentConfig): \ error("Invalid or non-existent file $${jsonFile}.") exists($$priFile): \ !include($$priFile): error() # only configs which contain more than just subconfigs are saved for later. $${currentConfig}._KEYS_ -= subconfigs !isEmpty($${currentConfig}._KEYS_) { allConfigs += $$currentConfig contains($${currentConfig}._KEYS_, libraries) { qtConfSetupLibraries() # this ensures that references in QMAKE_LIBRARY_DEPS are unique. qtConfSetModuleName() ex = $$eval(config.modules.$${currentModule}) !isEmpty(ex): \ error("Module $$currentModule is claimed by both $$currentConfig and $${ex}.") config.modules.$${currentModule} = $$currentConfig } } # prepend all subconfigs to files to keep a depth first search order subconfigs = for(n, $${currentConfig}.subconfigs._KEYS_) { subconfig = $$eval($${currentConfig}.subconfigs.$${n}) name = $$basename(subconfig) ex = $$eval(config.$${name}.dir) !isEmpty(ex): \ error("Basename clash between $$thisDir/$$subconfig and $${ex}.") config.$${name}.dir = $$thisDir/$$subconfig subconfigs += $$name } configsToProcess = $$subconfigs $$configsToProcess } QMAKE_SAVED_ARGS = $$QMAKE_EXTRA_ARGS QMAKE_REDO_CONFIG = false qtConfParseCommandLine() qtConfCheckErrors() !isEmpty(config.input.list-features) { all_ft = for (currentConfig, allConfigs) { for (k, $${currentConfig}.features._KEYS_) { pp = $$eval($${currentConfig}.features.$${k}.purpose) !isEmpty(pp) { pfx = $$eval($${currentConfig}.features.$${k}.section) !isEmpty(pfx): pfx = "$$pfx: " all_ft += $$qtConfPadCols($$k, ".......................", \ $$pfx$$section(pp, $$escape_expand(\\n), 0, 0)) } } } all_ft = $$sorted(all_ft) logn() for (ft, all_ft): \ logn($$ft) error() } !isEmpty(config.input.list-libraries) { logn() for (currentConfig, allConfigs) { !isEmpty($${currentConfig}.exports._KEYS_) { !isEmpty($${currentConfig}.module): \ logn($$eval($${currentConfig}.module):) else: \ logn($$section(currentConfig, ., -1):) all_xp = for (xport, $${currentConfig}.exports._KEYS_) { libs = $$eval($${currentConfig}.exports.$$xport) isEqual($${currentConfig}.libraries.$$first(libs).export, "") { # not isEmpty()! !isEmpty(config.input.verbose): \ all_xp += "$$xport!" } else { out = "$$xport" !isEmpty(config.input.verbose):!isEqual(xport, $$libs): \ out += "($$libs)" all_xp += "$$out" } } all_xp = $$sorted(all_xp) all_xp ~= s,^([^!]*)!$,(\\1),g for (xp, all_xp): \ logn(" $$xp") } } error() } QMAKE_CONFIG_VERBOSE = $$eval(config.input.verbose) isEmpty(QMAKE_CONFIG_VERBOSE): \ QMAKE_CONFIG_VERBOSE = false QMAKE_CONFIG_LOG = $$OUT_PWD/config.log write_file($$QMAKE_CONFIG_LOG, "") qtLog("Command line: $$qtSystemQuote($$QMAKE_SAVED_ARGS)") $$QMAKE_REDO_CONFIG: \ qtLog("config.opt: $$qtSystemQuote($$QMAKE_EXTRA_REDO_ARGS)") for (currentConfig, allConfigs) { qtConfSetModuleName() qtConfSetupModuleOutputs() # do early checks, mainly to validate the command line qtConfProcessEarlyChecks() } qtConfCheckErrors() QMAKE_CONFIG_CACHE = $$OUT_PWD/config.cache QMAKE_CONFIG_CACHE_USE = $$eval(config.input.cache_use) cache_recheck = $$eval(config.input.cache_recheck) equals(cache_recheck, yes) { QMAKE_CONFIG_CACHE_USE = positive cache_recheck = } isEmpty(QMAKE_CONFIG_CACHE_USE): \ QMAKE_CONFIG_CACHE_USE = all !equals(QMAKE_CONFIG_CACHE_USE, none) { include($$QMAKE_CONFIG_CACHE, , true) # this crudely determines when to discard the cache. this also catches the case # of no cache being there in the first place. !equals(cache.platform, $$[QMAKE_SPEC])|!equals(cache.xplatform, $$[QMAKE_XSPEC]) { QMAKE_CONFIG_CACHE_USE = none } else: !isEmpty(cache_recheck) { for (cr, $$list($$split(cache_recheck, ","))) { !isEmpty(cache.$${cr}._KEYS_) { cache.$${cr}._KEYS_ = } else { qtConfAddWarning("Attempting to discard non-cached result '$$cr'.") } } } } equals(QMAKE_CONFIG_CACHE_USE, none) { cont = \ "cache.platform = $$[QMAKE_SPEC]" \ "cache.xplatform = $$[QMAKE_XSPEC]" write_file($$QMAKE_CONFIG_CACHE, cont) } CONFIG += qt_conf_tests_allowed logn() logn("Running configuration tests...") for (currentConfig, allConfigs) { tdir = $$eval($${currentConfig}.testDir) isEmpty(tdir): tdir = config.tests QMAKE_CONFIG_TESTS_DIR = $$absolute_path($$tdir, $$eval($${currentConfig}.dir)) qtConfSetModuleName() qtConfSetupTestTypeDeps() # correctly setup dependencies QMAKE_CONFIG_DEPS = global global_private QMAKE_LIBRARY_DEPS = $$eval(config.modules.global) !isEmpty($${currentConfig}.module) { for (d, $${currentConfig}.depends._KEYS_) { dep = $$replace($${currentConfig}.depends.$$d, -private$, _private) gdep = $$replace(dep, _private$, ) dep *= $$gdep QMAKE_CONFIG_DEPS += $$dep !isEqual(gdep, $$dep): \ # libraries are in the private module. QMAKE_LIBRARY_DEPS += $$eval(config.modules.$$gdep) } } qtConfCheckModuleCondition() qtConfHaveModule($$currentModule) { # process all features qtConfProcessFeatures() } else { qtConfOutputVar(assign, "privatePro", "QT.$${currentModule}.skip", "true") } # generate files and reports qtConfProcessOutput() qtConfHaveModule($$currentModule) { qtConfCreateReport() qtConfCreateSummary() } else { QT_CONFIGURE_SKIPPED_MODULES += " $$currentModule" } } !isEmpty(QT_CONFIGURE_SKIPPED_MODULES): \ qtConfAddNote("The following modules are not being compiled in this configuration:" $$QT_CONFIGURE_SKIPPED_MODULES) logn("Done running configuration tests.") logn() !$$QMAKE_REDO_CONFIG { write_file($$OUT_PWD/config.opt, QMAKE_SAVED_ARGS)|error() } # these come from the pri files loaded above. for (p, QMAKE_POST_CONFIGURE): \ eval($$p) logn("Configure summary:") logn() qtConfPrintReport() # final notes for the user logn() logn("Qt is now configured for building. Just run '$$QMAKE_MAKE_NAME'.") pfx = $$[QT_INSTALL_PREFIX] equals(pfx, $$[QT_INSTALL_PREFIX/get]) { logn("Once everything is built, Qt is installed.") logn("You should NOT run '$$QMAKE_MAKE_NAME install'.") logn("Note that this build cannot be deployed to other machines or devices.") } else { logn("Once everything is built, you must run '$$QMAKE_MAKE_NAME install'.") logn("Qt will be installed into '$$system_path($$pfx)'.") } logn() logn("Prior to reconfiguration, make sure you remove any leftovers from") logn("the previous build.") logn()