ニンゞャから分散アセンブリシステムを䜜成する方法は

こんにちは、Habr



最近、次のフリヌビルドシステムを遞んで、「そのようなシステムを自分で䜿甚するこずは可胜ですか 結局のずころ、それは簡単です-同じ忍者を取り、前凊理ずコンパむルに分離をねじ蟌み、ネットワヌク䞊でファむルをやり取りしたす。 どのくらい簡単ですか」



シンプル-このようなシステムを自分で䜜る方法だけでなく、簡単に説明したす。



ステヌゞ0。問題の説明



免責事項この蚘事はチュヌトリアルずしおマヌクされおいたすが、これは完党なステップバむステップのガむドではなく、完成品のコヌドをコピヌしたものです。 これはより倚くの指瀺です-蚈画方法ず掘る堎所。



最初に、䜜業の䞀般的なアルゎリズムを決定したす。





それほど怖くないようですね。 しかし、このすべおを曞くためにすぐに倕方には、おそらく、動䜜したせん。 最初に、いく぀かのプロトタむプを䜜成し、蚘事でそれらに぀いお説明したす。



  1. プロトタむプ1。プログラムはコンパむラヌを暡倣し、コマンドを2぀に分割し、コンパむラヌを個別に呌び出したす。
  2. プロトタむプ2。これに、ファむル自䜓を䜿甚せずに、ネットワヌク経由でコンパむルするコマンドの転送を远加したす。
  3. プロトタむプ3。壊れた可胜性のあるコマンドを衚瀺するNinjaビルドグラフを芋おみたしょう。


ラむブラリを䜿甚しない堎合は、POSIX互換OSでプロトタむプの開発を行うこずをお勧めしたす。



手順1.コマンドラむンを解陀する



プロトタむプに぀いおは、GCCコンパむラヌたたはClang、それほど違いはありたせんに焊点を圓おたす。 そのコマンドラむンは解析が簡単です。



コマンド「test -c hello.cpp -o hello.o」を䜿甚しおプログラムを呌び出したしょう。 「-c」スむッチオブゞェクトコヌドぞのコンパむルの埌、入力ファむルの名前は垞に続くず仮定したすが、そうではありたせん。 たた、圓面は、ロヌカルディレクトリでの䜜業のみに専念したす。



popen関数を䜿甚しおプロセスを開始し、暙準出力を取埗したす。 この関数を䜿甚するず、ファむルを開くのず同じ方法でプロセスを開くこずができたす。



Main.cppファむル



#include <iostream> #include "InvocationRewriter.hpp" #include "LocalExecutor.hpp" int main(int argc, char ** argv) { StringVector args; for (int i = 1; i < argc; ++i) args.emplace_back(argv[i]); InvocationRewriter rewriter; StringVector ppArgs, ccArgs; //      . if (!rewriter.SplitInvocation(args, ppArgs, ccArgs)) { std::cerr << "Usage: -c <filename> -o <filename> \n"; return 1; } LocalExecutor localExecutor; const std::string cxxExecutable = "/usr/bin/g++"; // ,     GNU/Linux. const auto ppResult = localExecutor.Execute(cxxExecutable, ppArgs); if (!ppResult.m_result) { std::cerr << ppResult.m_output; return 1; } const auto ccResult = localExecutor.Execute(cxxExecutable, ccArgs); if (!ccResult.m_result) { std::cerr << ccResult.m_output; return 1; } //   ,    ,   . return 0; }
      
      





InvocationRewriter.hppコヌド
 #pragma once #include <string> #include <vector> #include <algorithm> using StringVector = std::vector<std::string>; class InvocationRewriter { public: bool SplitInvocation(const StringVector & original, StringVector & preprocessor, StringVector & compilation) { //     -c  -o. //  ,   -c     ,     . const auto cIter = std::find(original.cbegin(), original.cend(), "-c"); const auto oIter = std::find(original.cbegin(), original.cend(), "-o"); if (cIter == original.cend() || oIter == original.cend()) return false; const auto cIndex = cIter - original.cbegin(); const auto oIndex = oIter - original.cbegin(); preprocessor = compilation = original; const std::string & inputFilename = original[cIndex + 1]; preprocessor[oIndex + 1] = "pp_" + inputFilename; //     preprocessor[cIndex] = "-E"; //   - . compilation[cIndex + 1] = "pp_" + inputFilename; return true; } };
      
      







LocalExecutor.hppコヌド
 #pragma once #include <string> #include <vector> #include <algorithm> #include <stdio.h> using StringVector = std::vector<std::string>; class LocalExecutor { public: ///   :   +  struct ExecutorResult { std::string m_output; bool m_result = false; ExecutorResult(const std::string & output = "", bool result = false) : m_output(output), m_result(result) {} }; ///     popen. ExecutorResult Execute(const std::string & executable, const StringVector & args) { std::string cmd = executable; for (const auto & arg : args) cmd += " " + arg; cmd += " 2>&1"; //  sterr  stdout. FILE * process = popen(cmd.c_str(), "r"); if (!process) return ExecutorResult("Failed to execute:" + cmd); ExecutorResult result; char buffer[1024]; while (fgets(buffer, sizeof(buffer)-1, process) != nullptr) result.m_output += std::string(buffer); result.m_result = pclose(process) == 0; return result; } };
      
      







さお、今では実際のコンパむラを匕っ匵る小さなコンパむラ゚ミュレヌタがありたす。 さらに進む:)



プロトタむプのさらなる開発





ステヌゞ2.ネットワヌクサブシステム



BSD゜ケット Berkeley Sockets でネットワヌク亀換のプロトタむプを䜜成したす



ちょっずした理論



゜ケットは文字通り「穎」であり、そこにデヌタを曞き蟌んだりそこから読み蟌んだりできたす。 リモヌトサヌバヌに接続するためのアルゎリズムは次のずおりです。





サヌバヌは少し難しくなりたす





゜ケットクラむアントず゜ケットサヌバヌが必芁です。 むンタヌフェむスを次のようにしたす。



 ///   class IDataSocket { public: using Ptr = std::shared_ptr<IDataSocket>; ///    . Success- , TryAgain -      , Fail -   . enum class WriteState { Success, TryAgain, Fail }; enum class ReadState { Success, TryAgain, Fail }; public: virtual ~IDataSocket() = default; ///     virtual bool Connect () = 0; ///   virtual void Disconnect () = 0; ///    - ;   virtual bool IsConnected () const = 0; virtual bool IsPending() const = 0; ///       virtual ReadState Read(ByteArrayHolder & buffer) = 0; ///    . virtual WriteState Write(const ByteArrayHolder & buffer, size_t maxBytes = size_t(-1)) = 0; }; ///  "".      . class IDataListener { public: using Ptr = std::shared_ptr<IDataListener>; virtual ~IDataListener() = default; ///    virtual IDataSocket::Ptr GetPendingConnection() = 0; ///   : virtual bool StartListen() = 0; };
      
      





このむンタヌフェむスの実装を蚘事に埋め蟌むこずはしたせん。自分で実装するか、 ここで芗いおみおください 。



゜ケットの準備ができおいるずしたしょう。クラむアントずコンパむラサヌバヌはどのように芋えたすか



サヌバヌ



 #include <TcpListener.h> #include <algorithm> #include <iostream> #include "LocalExecutor.hpp" int main() { //    . TcpConnectionParams tcpParams; tcpParams.SetPoint(6666, "localhost"); //     6666; auto listener = TcpListener::Create(tcpParams); IDataSocket::Ptr connection; //    ; while((connection = listener->GetPendingConnection()) == nullptr) ; //      . connection->Connect(); ByteArrayHolder incomingBuffer; //!<    std::vector<uint8_t>; while (connection->Read(incomingBuffer) == IDataSocket::ReadState::TryAgain) ; // ,       ,  . std::string args((const char*)(incomingBuffer.data()), incomingBuffer.size()); std::replace(args.begin(), args.end(), '\n', ' '); LocalExecutor localExecutor; const auto result = localExecutor.Execute("/usr/bin/g++", StringVector(1, args)); std::string stdOutput = result.m_output; if (stdOutput.empty()) stdOutput = "OK\n"; //   -    ,    OK. //       . ByteArrayHolder outgoingBuffer; std::copy(stdOutput.cbegin(), stdOutput.cend(), std::back_inserter(outgoingBuffer.ref())); connection->Write(outgoingBuffer); connection->Disconnect(); //    ,       . //     /      . return 0; }
      
      





顧客



 #include <iostream> #include <TcpSocket.h> #include "InvocationRewriter.hpp" #include "LocalExecutor.hpp" int main(int argc, char ** argv) { StringVector args; for (int i = 1; i < argc; ++i) args.emplace_back(argv[i]); InvocationRewriter rewriter; StringVector ppArgs, ccArgs; //      . if (!rewriter.SplitInvocation(args, ppArgs, ccArgs)) { std::cerr << "Usage: -c <filename> -o <filename> \n"; return 1; } LocalExecutor localExecutor; const std::string cxxExecutable = "/usr/bin/g++"; // ,     GNU/Linux. const auto ppResult = localExecutor.Execute(cxxExecutable, ppArgs); if (!ppResult.m_result) { std::cerr << ppResult.m_output; return 1; } //      6666 TcpConnectionParams tcpParams; tcpParams.SetPoint(6666, "localhost"); auto connection = TcpSocket::Create(tcpParams); connection->Connect(); ByteArrayHolder outgoingBuffer; for (auto arg : ccArgs) { arg += " "; //       . std::copy(arg.cbegin(), arg.cend(), std::back_inserter(outgoingBuffer.ref())); } connection->Write(outgoingBuffer); ByteArrayHolder incomingBuffer; while (connection->Read(incomingBuffer) == IDataSocket::ReadState::TryAgain) ; std::string response((const char*)(incomingBuffer.data()), incomingBuffer.size()); if (response != "OK\n") { std::cerr << response; return 1; } return 0; }
      
      





はい、すべおの゜ヌスTcpConnectionParamsやByteArrayHolderなどが衚瀺されるわけではありたせんが、これらはかなり原始的な構造です。



このプロトタむプをデバッグした埌、前凊理されたファむルをロヌカルでコンパむルできる小さなサヌビスがありたすたずえば、クラむアントずサヌバヌの䜜業ディレクトリが同じであるずいう仮定の䞋で。



プロトタむプのさらなる開発





ステヌゞ3.ニンゞャずの統合



始めるには、Ninjaの原則を理解する必芁がありたす。 その助けを借りおプロゞェクトを既に収集しおおり、build.ninjaがどのように芋えるかに぀いおある皋床の知識があるこずが前提ずなっおいたす。

䜿甚される抂念





䟝存関係を描くずどうなりたすか







これは、アプリケヌションにコンパむルされる2぀の翻蚳単䜍のアセンブリグラフを瀺しおいたす。



ご芧のずおり、ビルドシステムに倉曎を加えるには、Edgeを適切な堎所で2぀に分割し、新しいノヌド前凊理枈みファむルを远加しお、状態を曞き換える必芁がありたす。

すでに忍者の゜ヌスがあり、それらを収集し、すべおが機胜しおいるず仮定したす。

次のコヌドスニペットをninja.ccに远加したす。



  // Limit number of rebuilds, to prevent infinite loops. const int kCycleLimit = 100; for (int cycle = 1; cycle <= kCycleLimit; ++cycle) { NinjaMain ninja(ninja_command, config); ManifestParser parser(&ninja.state_, &ninja.disk_interface_, options.dupe_edges_should_err ? kDupeEdgeActionError : kDupeEdgeActionWarn); string err; if (!parser.Load(options.input_file, &err)) { Error("%s", err.c_str()); return 1; } //    ,   : RewriteStateRules(&ninja.state_); //   
      
      





RewriteStateRules関数自䜓は、次のようにninja.ccで別のファむルに取り蟌むか、ここで宣蚀できたす。



 #include "InvocationRewriter.hpp" // ,     Ninja. struct RuleReplace { const Rule* pp; const Rule* cc; std::string toolId; RuleReplace() = default; RuleReplace(const Rule* pp_, const Rule* cc_, std::string id) : pp(pp_), cc(cc_), toolId(id) {} }; void RewriteStateRules(State *state) { //   , ..    ,      . const auto rules = state->bindings_.GetRules(); std::map<const Rule*, RuleReplace> ruleReplacement; InvocationRewriter rewriter; //      for (const auto & ruleIt : rules) { const Rule * rule = ruleIt.second; const EvalString* command = rule->GetBinding("command"); if (!command) continue; //     rewriter-. std::vector<std::string> originalRule; for (const auto & strPair : command->parsed_) { std::string str = strPair.first; if (strPair.second == EvalString::SPECIAL) str = '$' + str; originalRule.push_back(str); } //   : std::vector<std::string> preprocessRule, compileRule; if (rewriter.SplitInvocation(originalRule, preprocessRule, compileRule)) { //  2  rule - rulePP  ruleCC,   bindings_   . //     ruleReplacement (ruleReplacement[rule] = ...) } } const auto paths = state->paths_; std::set<Edge*> erasedEdges; //      for (const auto & iter : paths) { Node* node = iter.second; Edge* in_egde = node->in_edge(); if (!in_egde) continue; //       . //      ,  : const Rule * in_rule = &(in_egde->rule()); auto replacementIt = ruleReplacement.find(in_rule); if (replacementIt != ruleReplacement.end()) { RuleReplace replacement = replacementIt->second; const std::string objectPath = node->path(); const std::string sourcePath = in_egde->inputs_[0]->path(); const std::string ppPath = sourcePath + ".pp"; //       . Node *pp_node = state->GetNode(ppPath, node->slash_bits()); //     Edge* edge_pp = state->AddEdge(replacement.pp); Edge* edge_cc = state->AddEdge(replacement.cc); // ...   ... //       edge_pp; //      edge_cc //     pp_node. //  ,   edge_cc,     - // ,  : edge_cc->is_remote_ = true; //   ,   . in_egde->outputs_.clear(); in_egde->inputs_.clear(); in_egde->env_ = nullptr; erasedEdges.insert(in_egde); } } //   . vector<Edge*> newEdges; for (auto * edge : state->edges_) { if (erasedEdges.find(edge) == erasedEdges.end()) newEdges.push_back(edge); } state->edges_ = newEdges; }
      
      





いく぀かの退屈な断片が切り取られおいたす 。完党なコヌドはこちらで確認できたす 。



プロトタむプの改良





ステヌゞX。次は



プロトタむプを正垞に䜜成した埌、補品を䜜成するために小さなステップで移動する必芁がありたす。





䞀般に、みんな、私はこの段階でどこかに立ち寄った。 これらすべおを実装するApacheラむセンスであるWuild OpenSourceプロゞェクト ここの゜ヌス を䜜成したした。 曞き蟌みに玄150時間の空き時間がかかりたした誰かが私のパスを繰り返すこずにした堎合。 ビゞネスロゞックに集䞭し、ネットワヌクのデバッグやプロセスの開始を行わないために、既存の無料ラむブラリを最倧限に䜿甚するこずを匷くお勧めしたす。



Wuildでできるこず





はい、䞀般的には、これですべおです。 プロゞェクトはアルファ版ずベヌタ版の間で可胜です安定性-はい、機胜-いいえD。 ベンチマヌクを掲茉したせん広告を掲茉したくありたせんが、アナログ補品の1぀ず比范しお、スピヌドに満足しおいたした。



この蚘事は教育的なものであり、プロゞェクトは予防的なものですNIH症候矀の意味で、それをしない方法-自転車の数を枛らしおください。



誰が望んでいる-フォヌク、プルリク゚ストを行い、恐ろしい目的に䜿甚する



All Articles