Qt Lite の仕組み: qmake 編 (2)
前回 は、qmake の初段の qt.pro について解説しました。
今回からはいよいよ本丸の qt_configure.prf の解説になります。
qmake の専用言語がたくさん出てきますが、言語自体の解説はしませんので、詳しいところまで知りたい方はオンラインドキュメントの qmake Manual や日本で唯一の qmake の書籍 qmake 入門 をご覧ください。
大量の関数の実装
v5.12.4 時点で、qt_configure.prf は 2520 行になりますが、4/5 は様々な関数の実装になっています。
置換関数
- qtConfScalarOrList
- qtConfGetNextCommandlineArg
- qtConfPeekNextCommandlineArg
- qtConfToolchainSupportsFlag
- qtConfFindInPathList
- qtConfFindInPath
- qtConfPkgConfigEnv
- qtConfPkgConfig
- qtSystemQuote
- qtConfPrepareArgs
- qtGccSysrootifiedPath
- qtGccSysrootifiedPaths
- qtConfGetTestSourceList
- qtConfGetTestIncludes
- qtConfLibraryArgs
- qtConfAllLibraryArgs
- qtConfEvaluate
- qtConfEvaluateSingleExpression
- qtConfEvaluateSubExpression
- qtIsFeatureEnabled
- qtIsFeatureDisabled
- qtConfCheckSingleCondition
- qtConfPadCols
- qtConfCollectFeatures
- qtConfFindFirstAvailableFeature
テスト関数
- qtConfAddReport
- qtConfAddNote
- qtConfAddWarning
- qtConfAddError
- qtConfFatalError
- qtConfCommandlineSetInput
- qtConfCommandline_boolean
- qtConfCommandline_void
- qtConfCommandline_enum
- qtConfValidateValue
- qtConfCommandline_string
- qtConfCommandline_optionalString
- qtConfCommandline_addString
- qtConfCommandline_redo
- qtConfParseCommandLine
- qtConfTest_compilerSupportsFlag
- qtConfTest_linkerSupportsFlag
- qtConfPkgConfigPackageExists
- qtConfSetupLibraries
- qtConfResolveLibs
- qtConfResolveAllLibs
- qtConfResolvePathLibs
- qtConfResolvePathIncs
- qtConfLibrary_inline
- qtConfLibrary_makeSpec
- qtConfLibrary_pkgConfig
- qtConfTest_getPkgConfigVariable
- qtConfExportLibrary
- qtConfHandleLibrary
- qtConfTest_library
- qtConfTestPrepare_compile
- qtConfPrepareCompileTestSource
- qtConfTest_compile
- qtConfTest_files
- logn
- qtLogTestIntro
- qtLogTestResult
- qtConfSaveResult
- qtConfLoadResult
- qtConfIsBoolean
- qtConfSetupTestTypeDeps
- qtConfEnsureTestTypeDepsOne
- qtConfEnsureTestTypeDeps
- qtRunSingleTest
- qtConfHaveModule
- qtConfCheckFeature
- qtConfCheckModuleCondition
- qtConfProcessFeatures
- qtConfReportPadded
- qtConfReport_featureList
- qtConfReport_firstAvailableFeature
- qtConfReport_feature
- qtConfReport_note
- qtConfReport_warning
- qtConfReport_error
- qtConfReport_fatal
- qtConfCreateReportRecurse
- qtConfProcessEarlyChecks
- qtConfCreateReport
- qtConfCreateSummary
- qtConfPrintReport
- qtConfCheckErrors
- qtConfOutput_libraryPaths
- qtConfOutputVar
- qtConfExtendVar
- qtConfOutputVarHelper
- qtConfOutput_varAssign
- qtConfOutput_varAppend
- qtConfOutput_varRemove
- qtConfOutputConfigVar
- qtConfOutput_publicQtConfig
- qtConfOutput_publicConfig
- qtConfOutput_privateConfig
- qtConfOutputSetDefine
- qtConfOutput_define
- qtConfOutput_feature
- qtConfSetModuleName
- qtConfSetupModuleOutputs
- qtConfOutput_publicFeature
- qtConfOutput_privateFeature
- qtConfProcessOneOutput
- qtConfProcessOutput
実際の処理
2245行目あたり から実際の処理がはじまります。
# # tie it all together # ...
処理対象のディレクトリの一覧の作成
qt.pro で SUBDIRS に指定されたディレクトリを走査し、configure.json を探します。それが含まれているディレクトリでは、後ほど必要な処理を行います。
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
}
この結果、 configsToProcess という変数に qt qtbase qtdeclarative qt3d qtwayland qtquickcontrols2 のような配列が入ります(どのサブモジュールのコードが存在しているかによります)。
このような仕組みにすることにより、Qt の各モジュールで各自の設定を保持することができるようになっています。これまでのように、qtbase が qtmultimedia や qt3d の設定を知る必要はなくなりました。
configure.pri/configure.json の処理
続いて、そのconfigsToProcess に対して以下の処理を行います。
- configure.json のパース
- configure.pri が存在していた場合には読み込む
- configure.json に “libraries” が含まれていた場合、qtConfSetupLibraries を実行する
- configure.json に“subconfigs” が含まれたいた場合、configsToProcess に追加
QMAKE_POST_CONFIGURE =
config.builtins.dir = $$PWD/data
configsToProcess = builtins $$configsToProcess
allConfigs =
for(ever) {
isEmpty(configsToProcess): \
break()
thisConfig = $$take_first(configsToProcess)
currentConfig = config.$$thisConfig
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 = $${thisConfig}_$$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
}
# 'builtins' is used for command line parsing and test type dependency
# injection, but its features must not be processed regularly.
allModuleConfigs = $$member(allConfigs, 1, -1)
allModuleConfigs には、config.qt, config.qtbase, config.qtbase_corelib, config.qtbase_gui などといった値が入ります。
コマンドラインオプションの確認
次は、qtConfParseCommandLine を実行します。
コマンドラインオプションは、各 configure.json の “commandline”: {…} の中で定義するようになっています。各 configure.json(≒ 各モジュール)で必要なオプションを自由に定義することが可能です。
この時点でエラーが発生した際には、qtConfCheckErrors() にて終了をします。
-list-features 対応
configure にこのオプションが渡された際には、各 configure.json の “features” セクションで定義されている feature を走査し、“purpose” 情報があるもの の一覧を表示し、処理を終了します。
!isEmpty(config.input.list-features) {
all_ft =
for (currentConfig, allModuleConfigs) {
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()
}
-list-libraries 対応
同様に、各 configure.json の “libraries” セクションの ライブラリ を走査し、モジュール毎に依存する外部ライブラリの一覧を表示し、処理を終了します。
!isEmpty(config.input.list-libraries) {
logn()
for (currentConfig, allModuleConfigs) {
!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()
}
ログとオプションの保存
ここまでのログ(config.log)と、configure に渡されたオプション(config.opt)を保存します。
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)")
config.log は configure の詳細を知りたいときに割とよく確認するファイルで、config.opt は ./config.status で configure を再度実行する際にオプションとして読み込まれます。
early report
この時点で、前述の allModuleConfigs に対して、今後使われる予定の変数の初期化 と、各 configure.json の “earlyReport”: [ … ] の処理を行います。
earlyReport の処理は qtConfCreateReportRecurse() で行われています。
各 earlyReport の condition を評価し、該当した場合には message を表示します。その後、各 type に該当する qtConfReport_{type} を実行します。qtConfReport_fatal() や qtConfReport_error() などが定義されています。
for (currentConfig, allModuleConfigs) {
qtConfSetModuleName()
qtConfSetupModuleOutputs()
# do early checks, mainly to validate the command line
qtConfProcessEarlyChecks()
}
qtConfCheckErrors()
この時点でエラーになった場合は、ここで処理を終了します。
まとめ
conifgure の本体である qt_configure.prf の処理の前半を追ってみました。
続きもお楽しみに!