パソコンでプログラミングしよう ウィンドウズC++プログラミング環境の構築
1.6.3.6(15)
ソースコード(Code::Blocksの*No Compiler*コンパイラ定義)

Code::Blocksのコンパイラ定義の一つである*No Compiler*の挙動をソースコードから解析する。

本サイトCode::Blocksプロジェクトの_Dummyターゲットはこの*No Compiler*を利用してCommand onlyビルドターゲットタイプの外部ツール自動実行を実現する。以下に*No Compiler*の挙動解析にあたって参照したソースコードを示す。最初にコンパイラ定義の構築を解析し、次にコンパイルコマンド実行について解析する。

ダウンロードリンク

ソースコード(コンパイラ定義の構築)(抜粋)

Code::Blocksは複数のコンパイラ定義をコンパイラクラスインスタンスとしてCompilerFactoryクラス(include\compilerfactory.h)スタティックメンバ変数配列(CompilerFactory::Compilers)に登録保持する。CompilerFactory::Compilersの要素はCompilerクラスインスタンスポインタで、CompilerクラスはCompilerMINGW、CompilerXMLなどの派生クラスで実装される。

覚え書き
Compiler派生クラスはCompilerXXXの形式で命名されるがCompilerGCCは例外で注意されたい。CompilerGCCはコンパイラ操作を実装するCode::BlocksプラグインとしてcbPluginクラスを継承するもので、コンパイラ定義するCompiler派生クラスではない。

コンパイラ定義はビルトインコンパイラとXMLコンパイラに大別される。ビルトインコンパイラはCompilerを継承する専用クラスのインスタンスで、例えば(mingw-w64を含む)MinGWはCompilerMINGWクラスインスタンスである。ビルトイン専用クラスのそれぞれのインスタンスは初期にただ一つが登録される。XMLコンパイラはCompilerXMLクラスインスタンスで、定義されたディレクトリ内のXMLデータファイルを読み込み構築する。初期においても複数のXMLデータファイルが存在し、それに従い複数のクラスインスタンスが登録される。これらのオプションデフォルト値はそれぞれおいて専用のXMLデータファイルが供給する。ユーザーはデフォルト値をユーザー設定値へ変更できるし、任意のインスタンスのコピーを作成してカスタマイズもできる。これらユーザー操作はCode::Blocks設定ファイル(C:\Users\user\AppData\Roaming\CodeBlocks\default.conf)に保存される。

Code::Blocks起動時にコンパイラ操作を実装するCompilerGCCプラグインはCompilerGCC::OnAttach(plugins\compilergcc\compilergcc.cpp:317)で初期化する。

void CompilerGCC::OnAttach()
{
...
DoRegisterCompilers();
...
LoadOptions();
...
}

CompilerGCC::DoRegisterCompilersが全てのコンパイラ定義をCompiler派生クラスインスタンスとして登録し、それぞれのオプションデフォルト値をロードする。CompilerGCC::LoadOptionsが全てのコンパイラ定義に対しそれぞれのオプションユーザー設定値をロードする。

コンパイラ定義構築とデフォルト値のロード

最初にCompilerGCC::DoRegisterCompilers(plugins\compilergcc\compilergcc.cpp:845)を説明する。

void CompilerGCC::DoRegisterCompilers()
{
...
// register built-in compilers
CompilerFactory::RegisterCompiler(new CompilerMINGW);
if (platform::windows || nonPlatComp)
{
CompilerFactory::RegisterCompiler(new CompilerMSVC);
...
}
...
// register pure XML compilers
// user paths first
wxDir dir;
wxString filename;
wxArrayString compilers;
wxString path = ConfigManager::GetFolder(sdDataUser) + wxT("/compilers/");
if (wxDirExists(path) && dir.Open(path))
{
bool ok = dir.GetFirst(&filename, wxT("compiler_*.xml"), wxDIR_FILES);
while (ok)
{
compilers.Add(path + filename);
ok = dir.GetNext(&filename);
}
}
// global paths next
path = ConfigManager::GetFolder(sdDataGlobal) + wxT("/compilers/");
if (wxDirExists(path) && dir.Open(path))
{
...
}
for (size_t i = 0; i < compilers.GetCount(); ++i)
{
wxXmlDocument compiler;
if (!compiler.Load(compilers[i]) || compiler.GetRoot()->GetName() != wxT("CodeBlocks_compiler"))
Manager::Get()->GetLogManager()->Log(_("Error: Invalid Code::Blocks compiler definition '") + compilers[i] + wxT("'."));
else
{
bool val = true;
wxString test;
if (!nonPlatComp && compiler.GetRoot()->GetAttribute(wxT("platform"), &test))
{
if (test == wxT("windows"))
val = platform::windows;
...
}
if (val)
CompilerFactory::RegisterCompiler(
new CompilerXML(compiler.GetRoot()->GetAttribute(wxT("name"), wxEmptyString),
compiler.GetRoot()->GetAttribute(wxT("id"), wxEmptyString),
compilers[i]));
}
}
// register (if any) user-copies of built-in compilers
CompilerFactory::RegisterUserCompilers();
}

最初にビルトインコンパイラが登録される。例えばCompilerMINGWクラスインスタンスはコンストラクタデフォルト実引数により名前"GNU GCC Compiler"とID文字列"gcc"で登録される。次にCode::Blocksのユーザーディレクトリ(C:\Users\user\AppData\Roaming\CodeBlocks\share\codeblocks\compilers)とシステムディレクトリ(C:\Program Files (x86)\CodeBlocks\share\CodeBlocks\compilers)を走査し、ファイル名マッチング(compiler_*.xml)したXMLファイルからデータを読み込みCompilerXMLクラスインスタンスを構築して登録する。特に重要なのはシステムディレクトリのcompiler_null.xmlで、名前"*No Compiler*"でID文字列"null"となるCompilerXMLインスタンスを作成する。CompilerMINGWなどのビルトインコンパイラクラスおよびCompierXMLはコンストラクタ内でCompiler::Reset(sdk\compilerfactory.cpp:157)を介してCompiler::LoadDefaultOptions(sdk\compier.cpp:809)をコールしてオプションデフォルト値をロードする。最後にCompilerFactory::RegisterUserCompilers(sdk\compilerfactory.cpp:146)でユーザーが既存インスタンスからコピー作成したインスタンスを登録する。

void CompilerFactory::RegisterUserCompilers()
{
ConfigManager* cfg = Manager::Get()->GetConfigManager(_T("compiler"));
wxArrayString paths = cfg->EnumerateSubPaths(_T("/user_sets"));
for (unsigned int i = 0; i < paths.GetCount(); ++i)
{
wxString base = _T("/user_sets/") + paths[i];
wxString parent = cfg->Read(base + _T("/parent"), wxEmptyString);
if (!parent.IsEmpty())
{
Compiler* compiler = GetCompiler(parent);
wxString name = cfg->Read(base + _T("/name"), wxEmptyString);
CreateCompilerCopy(compiler, name);
}
}
}

Manager::Get()->GetConfigManageで取得するCode::Blocks設定ファイルもXMLデータで、compiler/user_setsノードを走査して子ノード毎に.../parent(コピー元コンパイラ定義のID文字列)からコピー元インスタンスポインタを取得し、インスタンスポインタと.../name(名前)を用いてCompilerFactory::CreateCompilerCopy(sdk\compilerfactory.cpp:163)でコピーを登録する。

Compiler* CompilerFactory::CreateCompilerCopy(Compiler* compiler, const wxString& newName)
{
...
Compiler* newC = compiler->CreateCopy();
if (!newName.IsEmpty())
{
Compiler::m_CompilerIDs.Remove(newC->GetID());
newC->SetName(newName);
newC->m_ID = newName;
newC->MakeValidID();
}
newC->ReloadOptions();
RegisterCompiler(newC);
newC->LoadSettings(_T("/user_sets"));
return newC; // return the index for the new compiler
}

Compiler::CreateCopy(include\compiler.h:389)は純粋仮想関数で各派生クラスがコピーコンストラクタをコールしてコピーインスタンスのポインタを返す。コピーインスタンスは既にユニークなID文字列を持つが、与えられた名前からCompiler::MakeValidID(sdk\compier.cpp:261)で再作成したものに置き換える。コピーインスタンスはコピーコンストラクタでコピー元オプションデフォルト値をコピーしているが、さらにCompiler::ReloadOptions(sdk\compiler.cpp:174)を介してコールするCompiler::LoadDefaultOptions(sdk\compier.cpp:809)で自身のデフォルト値ロードを試みる。この値は更新されたID文字列で検索されるXMLデータファイルから読み込みコピーしたデフォルト値を上書きするが、そういったXMLデータファイルは恐らく存在せず上書きはスキップされる。コピーインスタンスを登録してからCompiler::LoadSettings(sdk\compier.cpp:584)がコピーインスタンスのユーザー設定値をロードする。

覚え書き
コンパイラクラスコンストラクタのコールするCompiler::Resetはオプションデフォルト値をロードした後、コンパイラオプション(メンバ変数m_CompilerOptions)、リンカオプション(m_LikerOptions)、リンクライブラリ(m_LinkLIbs)、プレビルドステップ(m_CmdsBefore)、ポストビルドステップ(m_CmdsAfter)をクリアする一方で、ここのCompiler::ReloadOptionsはこれらをクリアしない。後にコールされるCompilerFactory::LoadSettingsは全てのコンパイラ定義でCompiler::LoadSettingsをコールするため、ここのCompiler::LoadSettingsコールは冗長に見える。何であれ最終的にCompilerFactory::LoadSettingsがユーザー設定値を間違いなくロードするため、これら全て懸念に及ばない。

ビルトインクラスコンストラクタ、CompilerXMLコンストラクタおよびユーザーコピーインスタンスがオプションデフォルト値をロードするCompiler::LoadDefaultOptions(sdk\compier.cpp:809)を示す。

void Compiler::LoadDefaultOptions(const wxString& name, int recursion)
{
wxXmlDocument options;
wxString doc = ConfigManager::LocateDataFile(wxT("compilers/options_") + name + wxT(".xml"), sdDataUser | sdDataGlobal);
wxXmlNode* node = options.GetRoot()->GetChildren();
...
if (!options.Load(doc))
{
...
return
}
...
wxString baseKey = GetParentID().IsEmpty() ? wxT("/sets") : wxT("/user_sets");
ConfigManager* cfg = Manager::Get()->GetConfigManager(wxT("compiler"));
wxString cmpKey;
...
if (...)
cmpKey.Printf(wxT("%s/%s"), baseKey.c_str(), m_ID.c_str());
...
while (node)
{
const wxString value = node->GetAttribute(wxT("value"), wxEmptyString);
if (node->GetName() == wxT("if") && node->GetChildren())
{
...
}
else if (node->GetName() == wxT("Program"))
{
wxString prog = node->GetAttribute(wxT("name"), wxEmptyString);
if (prog == wxT("C"))
{
m_Programs.C = cfg->Read(cmpKey + wxT("/c_compiler"), value);
...
}
else if (prog == wxT("CPP"))
{
m_Programs.CPP = cfg->Read(cmpKey + wxT("/cpp_compiler"), value);
...
}
...
}
else if (node->GetName() == wxT("Switch"))
{
...
}
else if (node->GetName() == wxT("Category") && node->GetChildren())
{
...
}
else if (node->GetName() == wxT("Option"))
{
...
}
else if (node->GetName() == wxT("Command"))
{
wxString cmd = node->GetAttribute(wxT("name"), wxEmptyString);
wxString unEscape = value;
unEscape.Replace(wxT("\\n"), wxT("\n")); // a single tool can support multiple commands
CompilerTool tool(unEscape, node->GetAttribute(wxT("ext"), wxEmptyString),
node->GetAttribute(wxT("gen"), wxEmptyString));
CommandType cmdTp = ctCount;
if (cmd == wxT("CompileObject"))
cmdTp = ctCompileObjectCmd;
else if (cmd == wxT("GenDependencies"))
cmdTp = ctGenDependenciesCmd;
...
if (cmdTp != ctCount)
{
bool assigned = false;
CompilerToolsVector& tools = m_Commands[cmdTp];
for (size_t i = 0; i < tools.size(); ++i)
{
if (tools[i].extensions == tool.extensions)
{
tools[i] = tool;
assigned = true;
break;
}
}
if (!assigned)
tools.push_back(tool);
}
}
else if (node->GetName() == wxT("Sort"))
{
...
}
else if (node->GetName() == wxT("Common"))
{
...
}
while (!node->GetNext() && depth > 0)
{
...
}
node = node->GetNext();
}
...
}

値はCode::Blocksインストールディレクトリ階層下にあるXMLデータファイル(例えばC:\Program Files (x86)\CodeBlocks\share\CodeBlocks\compilers\options_gcc.xml)から取得する。例えばProgramノードからコンパイラなどの実行ファイル名を読み込み、Commandノードからは各処理を定義するコマンド文字列を読み込む。例としてGNU GCC Compiler(options_gcc.xml)と*No Compiler*(options_null.xml)のコマンド文字列を比較する。

覚え書き
ProgramノードのみCode::Blocks設定ファイルの値を優先するが、先に述べたように最後にCompilerFactory::LoadSettingsがCompiler::LoadSettingsをコールするのでこれも冗長だろう。
タイプ 処理概要 GNU GCC Compiler *No Compielr*
CompileObject コンパイル $compiler $options $includes -c $file -o $object 未定義
GenDependencies インクルードファイル依存情報取得 $compiler -MM $options -MF $dep_object -MT $object $includes $file 未定義
CompileResource リソースファイルコンパイル $rescomp $res_includes $res_options -J rc -O coff -i $file -o $resource_output 未定義
LinkExe デスクトップアプリケーションリンク $linker $libdirs -o $exe_output $link_objects $link_resobjects $link_options $libs -mwindows 未定義
LinkConsoleExe コンソールアプリケーションリンク $linker $libdirs -o $exe_output $link_objects $link_resobjects $link_options $libs 未定義
LinkDynamic ダイナミックライブラリリンク $linker -shared -Wl,--output-def=$def_output -Wl,--out-implib=$static_output -Wl,--dll $libdirs $link_objects $link_resobjects -o $exe_output $link_options $libs 未定義
LinkStatic スタティックライブラリリンク cmd /c if exist $static_output del $static_output\n$lib_linker -r -s $static_output $link_objects 未定義
LinkNative ネイティブバイナリリンク $linker $libdirs -o $exe_output $link_objects $link_resobjects $link_options $libs -Wl,--subsystem,native 未定義

コマンド文字列は各コマンドに対応するインデックス値(ctCompileObjectCmdなど)でメンバ変数配列m_Commandsに記憶される。配列の要素はベクトル(std::vector<CompilerTool>)でファイル拡張子別に複数定義できるが、例示したGNU GCC Compilerでは全ての拡張子に共通で各ベクトルは要素数1である。*No Compilerでは全て未定義でm_Commandsの各ベクトルは空である。コマンド文字列は$compilerなどのマクロ文字列を含み、デフォルト値あるいはCompiler::LoadSettingsで読み込まれた値に置き換えられて実行する。

覚え書き
コンパイラはCode::Blocksエディタのコード補完データベース作成でも起動されるがここのコマンド文字列は使用していない。コード補完データベースが起動するコンパイラは文字列IDにgccあるいはmsvcを含むものに限定され(NativeParser::AddCompilerPredefinedMacros(plugins\codecompletion\nativeparser.cpp:1969))、*No Compiler*が誤って起動される事は無い。

ユーザー設定値のロード

次にCompilerGCC::LoadOptions(plugins\compilergcc\compilergcc.cpp:845)を説明する。

void CompilerGCC::LoadOptions()
{
// load compiler sets
CompilerFactory::LoadSettings();
}

CompilerFactory::LoadSettings(sdk\compilerfactory.cpp:267)を以下に示す。

void CompilerFactory::LoadSettings()
{
...
for (size_t i = 0; i < Compilers.GetCount(); ++i)
{
wxString baseKey = Compilers[i]->GetParentID().IsEmpty() ? _T("/sets") : _T("/user_sets");
Compilers[i]->LoadSettings(baseKey);
...
}
...
}

コピー元のID文字列有無によりCode::Blocks設定ファイルのベースノードを変更してCompiler::LoadSettings(sdk\compiler.cpp:584)をコールする。

void Compiler::LoadSettings(const wxString& baseKey)
{
...
ConfigManager* cfg = Manager::Get()->GetConfigManager(_T("compiler"));
...
if (cfg->Exists(tmp + _T("/name")))
{
...
}
else // it's OK to use new style
tmp.Printf(_T("%s/%s"), baseKey.c_str(), m_ID.c_str());
...
m_Name = cfg->Read(tmp + _T("/name"), m_Name);
m_MasterPath = cfg->Read(tmp + _T("/master_path"), m_MasterPath);
m_ExtraPaths = MakeUniqueArray(GetArrayFromString(cfg->Read(tmp + _T("/extra_paths"), _T("")), _T(";")), true);
m_Programs.C = cfg->Read(tmp + _T("/c_compiler"), m_Programs.C);
m_Programs.CPP = cfg->Read(tmp + _T("/cpp_compiler"), m_Programs.CPP);
...
SetCompilerOptions (GetArrayFromString(cfg->Read(tmp + _T("/compiler_options"), wxEmptyString)));
...
for (int i = 0; i < ctCount; ++i)
{
wxArrayString keys = cfg->EnumerateSubPaths(tmp + _T("/macros/") + CommandTypeDescriptions[i]);
for (size_t n = 0; n < keys.size(); ++n)
{
unsigned long index = 0;
if (keys[n].Mid(4).ToULong(&index)) // skip 'tool'
{
while (index >= m_Commands[i].size())
m_Commands[i].push_back(CompilerTool());
CompilerTool& tool = m_Commands[i][index];
wxString key = wxString::Format(_T("%s/macros/%s/tool%lu/"), tmp.c_str(), CommandTypeDescriptions[i].c_str(), index);
tool.command = cfg->Read(key + _T("command"));
tool.extensions = cfg->ReadArrayString(key + _T("extensions"));
tool.generatedFiles = cfg->ReadArrayString(key + _T("generatedFiles"));
}
}
}
m_Switches.includeDirs = cfg->Read(tmp + _T("/switches/includes"), m_Switches.includeDirs);
m_Switches.libDirs = cfg->Read(tmp + _T("/switches/libs"), m_Switches.libDirs);
...
...
}

例えばビルトインコンパイラ"GNU GCC Compiler"はID文字列"gcc"を持ち、そのC++コンパイラ実行ファイル名はcompiler/sets/gcc/cpp_compilerノードから読み込まれる。このようにCompiler::LoadDefaultOptionsデフォルト値のほとんどをCode::Blocks設定ファイルの値で上書きできるが、これらユーザー設定値は[Settings|Compiler]の[Compiler settings]ダイアログ[Global compiler settings]で入力された値であり、例えば.../macrosノード階層下に保存されるコマンド文字列はダイアログに入力手段が無くデフォルト値を上書きする事はほぼあり得ない。

ソースコード(コンパイルコマンド実行)(抜粋)

[Build|Build]などでビルドを要求すると、CompilerGCC::DoBuild(plugins\compilergcc\compilergcc.cpp:2633)がコールされる。

int CompilerGCC::DoBuild(bool clean, bool build)
{
BuildJobTarget bj = GetNextJob();
...
ProjectBuildTarget* bt = bj.project->GetBuildTarget(bj.targetName);
...
if (!bt || !CompilerValid(bt).isValid)
return -2;
BuildStateManagement();
return 0;
}

CompilerGCC::CompilerValidがビルドターゲットのコンパイラ妥当性を確認する。Compiler::BuildStateManagementがコンパイルコマンド文字列を生成しコマンドキューに追加する。

ビルドターゲットコンパイラ妥当性の確認

ビルドターゲットコンパイラの妥当性はCompilerGCC::CompilerValid(plugins\compilergcc\compilergcc.cpp:1635)を介してCompiler::IsValie(sdk\compiler.cpp:190)が確認する。

bool Compiler::IsValid()
{
...
wxString tmp = m_MasterPath + _T("/bin/") + m_Programs.C;
MacrosManager *macros = Manager::Get()->GetMacrosManager();
macros->ReplaceMacros(tmp);
m_Valid = wxFileExists(tmp);
...
return m_Valid;
}

妥当性はC言語コンパイラ実行ファイルが実在するか否かで確認する。

コンパイルを意図的に行わないとしてコンパイラ*No Compiler*を特定のビルドターゲットに設定しても、デフォルトのままでは実行ファイルインストールディレクトリが– No Compiler –、C言語コンパイラを含む全ての実行ファイル名が空白でチェックにパスしない。少なくとも*No Compiler*のC言語実行ファイル名が実在のファイルとなるようインストールディレクトリを含めてダミー入力しておく必要があるが、後述のコンパイルコマンド文字列生成で説明するように実際にこのファイルが実行される事は無い。

コンパイルコマンド文字列の生成

コンパイルコマンド文字列はCompilerGCC::BuildStateManagement(plugins\compilergcc\compilergcc.cpp:2308)が生成してコマンドキューに追加する。

void CompilerGCC::BuildStateManagement()
{
...
ProjectBuildTarget* bt = m_pBuildingProject->GetBuildTarget(GetTargetIndexFromName(m_pBuildingProject, m_BuildingTargetName));
...
m_BuildState = m_NextBuildState;
wxArrayString cmds;
switch (m_NextBuildState)
{
case bsProjectPreBuild:
{
...
}
case bsTargetPreBuild:
{
...
}
case bsTargetClean:
{
...
}
case bsTargetBuild:
{
...
if ( UseMake(m_pBuildingProject) )
{
...
}
else
cmds = dc.GetCompileCommands(bt);
...
}
case bsTargetPostBuild:
{
...
}
case bsProjectPostBuild:
{
...
}
case bsProjectDone:
{
...
}
case bsTargetDone: // fall-through
case bsNone: // fall-through
default:
break;
}
m_NextBuildState = GetNextStateBasedOnJob();
AddToCommandQueue(cmds);
Manager::Yield();
}

コンパイルコマンドはDirectCommands::GetCompileCommands(plugins\compilergcc\directcommands.cpp:452)、DirectCommands::GetTargetCompileCommands(plugins\compilergcc\directcommands.cpp:473)を介してDirectCommands::GetCompileFileCommand(plugins\compilergcc\directcommands.cpp:194)が生成する。

wxArrayString DirectCommands::GetCompileFileCommand(ProjectBuildTarget* target, ProjectFile* pf) const
{
wxArrayString ret;
...
Compiler* compiler = target
? CompilerFactory::GetCompiler(target->GetCompilerID())
: m_pCompiler;
...
wxString compiler_cmd;
if (!is_header || compiler->GetSwitches().supportsPCH)
{
const CompilerTool* tool = compiler->GetCompilerTool(is_resource ? ctCompileResourceCmd : ctCompileObjectCmd, pf->file.GetExt());
if (...)
...
else if (tool)
compiler_cmd = tool->command;
else
compiler_cmd = wxEmptyString;
...
m_pGenerator->GenerateCommandLine(compiler_cmd,
target,
pf,
source_file,
object,
pfd.object_file_flat,
pfd.dep_file);
}
if (!is_header && compiler_cmd.IsEmpty())
{
ret.Add(COMPILER_SIMPLE_LOG + _("Skipping file (no compiler program set): ") + pfd.source_file_native );
return ret;
}
...
AddCommandsToArray(compiler_cmd, ret);
...
return ret;
}

与えられたビルドターゲットのコンパイラ定義からCompiler::GetCompilerTool(sdk\compiler.cpp:334)がCompilerToolインスタンスのポインタを以下のように取得する。CompilerクラスmCommandsメンバ変数配列にインデックス値(ctCompileObjectCmdなど)を与えて得るstd::vector<CommandTool>ベクトルをファイル拡張子で探索し、見つかればそのポインタを返し、見つからない場合はNULLを返す。以降においてビルトインコンパイラGNU GCC Compiler(ID文字列gcc)と*No Compiler*(ID文字列null)のケースを比較する。GNU GCC Compilerは対応するベクトル要素を持ちそのポインタを返すが、*No Compiler*は空ベクトルでNULLを返す。前者はcompiler_cmdにマクロ文字列を含むコマンド文字列を代入し、後者は空文字列となる。前者compiler_cmdはCompilerCommandGenerator::GenerateCommandLine(sdk\compilercommandgenerator.cpp:207)でマクロ文字列をオプションデフォルト値あるいはユーザー設定値に置換するが、後者は空文字列のままである。前者compiler_cmdはそのまま実行コマンドとして返しコンパイルを実行するが、後者は空文字列チェックで実行スキップメッセージ(Skipping file...)出力コマンドを返しコンパイルを実行しない。