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 の処理の前半を追ってみました。
続きもお楽しみに!