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 の処理の前半を追ってみました。

続きもお楽しみに!

あわせて読みたい