現在、複数のサブモジュールで構成されるプロジェクトに取り組んでいます。各サブモジュールは、独自のリポジトリで開発および単体テストされています。すべてのサブモジュールは、1つのメインプロジェクトリポジトリに統合する必要があります。
さらに、私はいくつかの「サポート機能」モジュールを持っています。これらのモジュールは、たとえばロギングツールやデータベースに接続するためのツールを実装しています。サポート機能モジュールは、ほとんどのサブモジュールで使用されます。サブモジュールは必ずしも同じライフサイクルで開発される必要はないため、すべてのサブモジュールが同じバージョンのサポート機能モジュールを使用するわけではない場合があります。サポート機能モジュールも独自のリポジトリで開発され、単体テストが行われます。
つまり、私は全体として次のようなプロジェクト構造を持っています。
以前は、1つのmain-project-repositoryを作成し、すべてのサブモジュールとsupport-functionsモジュールのすべてのヘッダーとソースファイル、およびtest-source-filesをこのリポジトリにコピーして、すべて一緒にコンパイルしていました。 Logging-Toolの異なるバージョンをサポートしたいのであれば、両方のバージョンをコンパイルできるように名前を変更したかもしれません。
今私の質問は:2019の最先端の統合技術でこれをどのように行うのですか?各サブモジュールとsupport-function-toolをライブラリにコンパイルし、バイナリ統合を行う必要がありますか?または、すべてのサブモジュールを使用してプロジェクトを簡単にデバッグできるソースコード統合を実行できますか?しかし、ソースコードの統合を行う場合、異なるバージョンの重複するサポート機能モジュールをどのように処理できますか?
使用するクールなテクニック/ベストプラクティスまたはツールはありますか? 「google repo」コマンドの大きな可能性はすでに知っています。これはおそらく、メインプロジェクトリポジトリの良い助けになるでしょう。
誰かが私を助けてくれることを願っていますか、それともどこで読み続けるかについてのヒントを教えてくれますか?
よろしく、anon1234
追伸:非常に単純なコード例を表示するには:Loggerクラスがあります。
これはバージョン4.0では次のように見えます
#ifndef LOGGER_H
#define LOGGER_H
#include <iostream>
using namespace std;
class Logger {
public:
Logger() {}
void log(string val_) {
cout << "DEBUG: " << val_ << endl;
}
};
#endif // LOGGER_H
そして、このようなバージョン5.0では
#ifndef LOGGER_H
#define LOGGER_H
#include <iostream>
using namespace std;
class Logger {
public:
Logger() {}
void log(string val_) {
cout << val_ << endl;
}
};
#endif // LOGGER_H
SubmoduleAは次のようになります。
#ifndef ModuleA_H
#define ModuleA_H
#include "Logger.h"
class ModuleA {
public:
ModuleA() {}
void doSmth() {
Logger l;
l.log("ModuleA is doing smth");
}
};
#endif // ModuleA_H
SubmoduleBは次のようになります。
#ifndef ModuleB_H
#define ModuleB_H
#include "Logger.h"
class ModuleB {
public:
ModuleB() {}
void doSmth() {
Logger l;
l.log("ModuleB is doing smth");
}
};
#endif // ModuleB_H
SubmoduleCは次のようになります
#ifndef MODULEC_H
#define MODULEC_H
#include "Logger.h"
class ModuleC {
public:
ModuleC() {}
void doSmth(){
Logger l;
l.log("ModuleC is doing smth");
}
};
#endif // MODULEC_H
互換性のないロガー実装は、同じ名前であるためリンクできません。これにより、すべての問題を解決する簡単なソリューションが得られます。
互換性のないバージョンには異なる名前を付ける必要があります。
C++では、これらのアプローチを個別の名前空間に配置できるため、このアプローチを比較的簡単に実行できます。 logging4::Logger
対logging5::Logger
。使用サイトでは、using logging = logging4
のような名前空間エイリアスを実行できます。これにより、バージョン間の変更がソース互換である限り、サブモジュールでコードの1行を変更することにより、ログモジュールのバージョンアップグレードを実行できます。
可能な限り、依存関係はヘッダーファイルではなく実装ファイルからのみ参照する必要があります。それ以外の場合、これらの上流の依存関係は下流のソフトウェアの依存関係にもなり、インクルードパスもバージョン管理されている場合にのみ機能します。
また、すべての宣言は名前空間にある必要があり、トップレベルにあることはありません。インクルードガードもバージョン付きの名前を使用する必要があります。可能であれば、非標準の#pragma once
を使用してください。
すべての依存関係のソースコードを直接含める単一のモノレポを使用すると、別の、ただし間違いなく単純なバージョン管理戦略が提案されます。依存関係の何かを変更した場合、依存するコードをすぐに変更して、プロジェクト全体を変更することもできます。常に動作します。個別の開発サイクルはありませんが、すべてが多かれ少なかれ一緒に開発されます。ほとんどの場合、移行の進行中に機能の切り替えまたは互換性インターフェイスを使用する可能性があります。
これをコンパイルする方法(静的リンク、動的リンク)は関係ありません。重要な部分は、ファイルパスと名前空間を適切にバージョン管理することです。
logger4/Logger.h
:
#pragma once
#include <iostream>
namespace logger4 {
class Logger {
public:
Logger() {}
void log(string msg) {
std::cout << "DEBUG: " << msg << std::endl;
}
};
}
logger5/Logger.h
:
#pragma once
#include <iostream>
namespace logger5 {
class Logger {
public:
Logger() {}
void log(string msg) {
std::cout << msg << std::endl;
}
};
}
ロガーバージョン4に直接依存します。
submodule_a/Module.h
:
#pragma once
#include "logger4/Logger.h"
namespace submodule_a {
using logger = logger4;
class Module {
public:
Module() {}
void doSmth() {
logger::Logger l;
l.log("ModuleA is doing smth");
}
};
}
Loggerバージョン5に依存しますが、.cppファイル内の依存関係のみを指定して、パブリックインターフェイスから依存関係を削除します。
submodule_b/Module.h
:
#pragma once
namespace submodule_b {
class Module {
public:
Module() {}
void doSmth();
};
}
submodule_b/Module.cpp
:
#include "Module.h"
#include "logger5/Logger.h"
// only visible in current compilation unit
using Logger = logger5::Logger;
namespace submodule_b {
void Module::doSmth() {
Logger l;
l.log("ModuleA is doing smth");
}
}