From 79640be325c333d77b4078d37f7668b74d5682e3 Mon Sep 17 00:00:00 2001 From: Karen Arutyunov Date: Sat, 29 Apr 2017 23:23:07 +0300 Subject: Add hxx extension for headers and lib prefix for library dirs --- bbot/.gitignore | 1 - bbot/build-config | 53 --- bbot/build-config.cxx | 125 ------ bbot/buildfile | 37 -- bbot/export | 41 -- bbot/manifest | 297 -------------- bbot/manifest.cxx | 988 ---------------------------------------------- bbot/version.in | 48 --- build/export.build | 4 +- build/root.build | 2 +- buildfile | 2 +- libbbot/.gitignore | 1 + libbbot/build-config.cxx | 125 ++++++ libbbot/build-config.hxx | 53 +++ libbbot/buildfile | 37 ++ libbbot/export.hxx | 41 ++ libbbot/manifest.cxx | 988 ++++++++++++++++++++++++++++++++++++++++++++++ libbbot/manifest.hxx | 297 ++++++++++++++ libbbot/version.hxx.in | 48 +++ tests/.gitignore | 2 + tests/buildtab/buildfile | 4 +- tests/buildtab/driver.cxx | 2 +- tests/manifest/buildfile | 4 +- tests/manifest/driver.cxx | 2 +- 24 files changed, 1602 insertions(+), 1600 deletions(-) delete mode 100644 bbot/.gitignore delete mode 100644 bbot/build-config delete mode 100644 bbot/build-config.cxx delete mode 100644 bbot/buildfile delete mode 100644 bbot/export delete mode 100644 bbot/manifest delete mode 100644 bbot/manifest.cxx delete mode 100644 bbot/version.in create mode 100644 libbbot/.gitignore create mode 100644 libbbot/build-config.cxx create mode 100644 libbbot/build-config.hxx create mode 100644 libbbot/buildfile create mode 100644 libbbot/export.hxx create mode 100644 libbbot/manifest.cxx create mode 100644 libbbot/manifest.hxx create mode 100644 libbbot/version.hxx.in diff --git a/bbot/.gitignore b/bbot/.gitignore deleted file mode 100644 index 088eda4..0000000 --- a/bbot/.gitignore +++ /dev/null @@ -1 +0,0 @@ -version diff --git a/bbot/build-config b/bbot/build-config deleted file mode 100644 index d7a07d9..0000000 --- a/bbot/build-config +++ /dev/null @@ -1,53 +0,0 @@ -// file : bbot/build-config -*- C++ -*- -// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#ifndef BBOT_BUILD_CONFIG -#define BBOT_BUILD_CONFIG - -#include -#include -#include - -#include -#include -#include -#include - -#include -#include - -namespace bbot -{ - // Build configuration matching specific machine names. Used by bbot - // controllers. - // - struct build_config - { - std::string machine_pattern; // Machine name pattern. - std::string name; // Configuration name. - - butl::optional target; - - std::vector vars; - }; - - using build_configs = std::vector; - - // Parse buildtab stream or file. Throw tab_parsing on parsing error, - // ios::failure on the underlying OS error. - // - // buildtab consists of lines in the following format: - // - // [] [] - // - using butl::tab_parsing; - - LIBBBOT_EXPORT build_configs - parse_buildtab (std::istream&, const std::string& name); - - LIBBBOT_EXPORT build_configs - parse_buildtab (const butl::path&); -} - -#endif // BBOT_BUILD_CONFIG diff --git a/bbot/build-config.cxx b/bbot/build-config.cxx deleted file mode 100644 index 0ee00e9..0000000 --- a/bbot/build-config.cxx +++ /dev/null @@ -1,125 +0,0 @@ -// file : bbot/build-config.cxx -*- C++ -*- -// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#include - -#include -#include // size_t -#include // move() -#include // invalid_argument - -#include -#include -#include - -#include // task_manifest::check_config() - -using namespace std; -using namespace butl; - -namespace bbot -{ - LIBBBOT_EXPORT build_configs - parse_buildtab (istream& is, const string& name) - { - build_configs r; - tab_parser parser (is, name); - - tab_fields tl; - while (!(tl = parser.next ()).empty ()) - { - size_t n (tl.size ()); // Fields count. - size_t i (0); // The field currently being processed. - - // Throw tab_parsing for the field currently being processed. If i == n - // then we refer to the end-of-line column (presumably reporting a missed - // field). - // - auto bad_line = [&name, &tl, &i, n] (const string& d) - { - // Offset beyond the end-of-line is meaningless. - // - assert (i <= n); - - throw tab_parsing (name, - tl.line, - i == n - ? tl.end_column - : tl[i].column, - d); - }; - - build_config config; - config.machine_pattern = move (tl[i++].value); - - // Configuration name field is a required one. - // - if (i == n) - bad_line ("no configuration name found"); - - config.name = move (tl[i].value); - - // Make sure the name is unique. - // - for (const auto& c: r) - if (c.name == config.name) - bad_line ("duplicate configuration name"); - - // If there is no target nor configuration variables then save the - // configuration and proceed with the next line. - // - if (++i == n) - { - r.emplace_back (move (config)); - continue; - } - - // If the third field doesn't contain '=' character, then we will treat - // it as a target. - // - if (tl[i].value.find ('=') == string::npos) - { - try - { - config.target = target_triplet (tl[i].value); - } - catch (const invalid_argument& e) - { - bad_line (e.what ()); - } - - ++i; - } - - try - { - for (; i < n; ++i) - { - task_manifest::check_config (tl[i].value); - config.vars.emplace_back (move (tl[i].value)); - } - } - catch (const invalid_argument& e) - { - bad_line (e.what ()); - } - - // Save the configuration. - // - r.emplace_back (move (config)); - } - - return r; - } - - build_configs - parse_buildtab (const path& p) - { - ifdstream ifs (p); - build_configs r (parse_buildtab (ifs, p.string ())); - - ifs.close (); // Throws on failure. - return r; - } -} diff --git a/bbot/buildfile b/bbot/buildfile deleted file mode 100644 index 1c7da48..0000000 --- a/bbot/buildfile +++ /dev/null @@ -1,37 +0,0 @@ -# file : bbot/buildfile -# copyright : Copyright (c) 2014-2017 Code Synthesis Ltd -# license : MIT; see accompanying LICENSE file - -import int_libs = libbutl%lib{butl} libbpkg%lib{bpkg} - -lib{bbot}: \ -{hxx cxx}{ build-config } \ -{hxx }{ export } \ -{hxx cxx}{ manifest } \ -{hxx }{ version } \ - $int_libs - -hxx{version}: in{version} $src_root/file{manifest} -hxx{version}: dist = true - -# For pre-releases use the complete version to make sure they cannot be used -# in place of another pre-release or the final version. -# -if $version.pre_release - lib{bbot}: bin.lib.version = @"-$version.project_id" -else - lib{bbot}: bin.lib.version = @"-$version.major.$version.minor" - -cxx.poptions =+ "-I$out_root" "-I$src_root" -obja{*}: cxx.poptions += -DLIBBBOT_STATIC_BUILD -objs{*}: cxx.poptions += -DLIBBBOT_SHARED_BUILD - -lib{bbot}: cxx.export.poptions = "-I$out_root" "-I$src_root" -liba{bbot}: cxx.export.poptions += -DLIBBBOT_STATIC -libs{bbot}: cxx.export.poptions += -DLIBBBOT_SHARED - -lib{bbot}: cxx.export.libs = $int_libs - -# Install into the bbot/ subdirectory of, say, /usr/include/. -# -install.include = $install.include/bbot/ diff --git a/bbot/export b/bbot/export deleted file mode 100644 index 6947752..0000000 --- a/bbot/export +++ /dev/null @@ -1,41 +0,0 @@ -// file : bbot/export -*- C++ -*- -// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#ifndef BBOT_EXPORT -#define BBOT_EXPORT - -// Normally we don't export class templates (but do complete specializations), -// inline functions, and classes with only inline member functions. Exporting -// classes that inherit from non-exported/imported bases (e.g., std::string) -// will end up badly. The only known workarounds are to not inherit or to not -// export. Also, MinGW GCC doesn't like seeing non-exported function being -// used before their inline definition. The workaround is to reorder code. In -// the end it's all trial and error. - -#if defined(LIBBBOT_STATIC) // Using static. -# define LIBBBOT_EXPORT -#elif defined(LIBBBOT_STATIC_BUILD) // Building static. -# define LIBBBOT_EXPORT -#elif defined(LIBBBOT_SHARED) // Using shared. -# ifdef _WIN32 -# define LIBBBOT_EXPORT __declspec(dllimport) -# else -# define LIBBBOT_EXPORT -# endif -#elif defined(LIBBBOT_SHARED_BUILD) // Building shared. -# ifdef _WIN32 -# define LIBBBOT_EXPORT __declspec(dllexport) -# else -# define LIBBBOT_EXPORT -# endif -#else -// If none of the above macros are defined, then we assume we are being used -// by some third-party build system that cannot/doesn't signal the library -// type. Note that this fallback works for both static and shared but in case -// of shared will be sub-optimal compared to having dllimport. -// -# define LIBBBOT_EXPORT // Using static or shared. -#endif - -#endif // BBOT_EXPORT diff --git a/bbot/manifest b/bbot/manifest deleted file mode 100644 index 115d9b6..0000000 --- a/bbot/manifest +++ /dev/null @@ -1,297 +0,0 @@ -// file : bbot/manifest -*- C++ -*- -// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#ifndef BBOT_MANIFEST -#define BBOT_MANIFEST - -#include -#include -#include - -#include -#include -#include - -#include // version, repository_location - -#include -#include - -namespace bbot -{ - using strings = std::vector; - - class LIBBBOT_EXPORT machine_header_manifest - { - public: - std::string id; - std::string name; - std::string summary; - - machine_header_manifest (std::string i, std::string n, std::string s) - : id (std::move (i)), name (std::move (n)), summary (std::move (s)) {} - - public: - machine_header_manifest () = default; // VC export. - machine_header_manifest (butl::manifest_parser&, - bool ignore_unknown = false); - machine_header_manifest (butl::manifest_parser&, - butl::manifest_name_value start, - bool ignore_unknown = false); - - void - serialize (butl::manifest_serializer&) const; - }; - - using machine_header_manifests = std::vector; - - class LIBBBOT_EXPORT task_request_manifest - { - public: - std::string agent; - - // Agent's public key SHA256 fingerprint. - // - // @@ How the fingerpring for openssl public key will be produced? Seems - // there is no "standard" for it. Possibly we will use the following - // command result (plain SHA256). - // - // $ cat key.pub | openssl sha256 - // - butl::optional fingerprint; - - machine_header_manifests machines; - - task_request_manifest (std::string a, - butl::optional f, - machine_header_manifests m) - : agent (std::move (a)), - fingerprint (std::move (f)), - machines (std::move (m)) {} - - public: - task_request_manifest () = default; // VC export. - task_request_manifest (butl::manifest_parser&, - bool ignore_unknown = false); - - void - serialize (butl::manifest_serializer&) const; - }; - - class LIBBBOT_EXPORT task_manifest - { - public: - // Package to build. - // - std::string name; - bpkg::version version; - bpkg::repository_location repository; // Remote or absolute. - - // The SHA256 repositories certificates fingerprints to trust. The special - // 'yes' value can be specified instead of fingerprint (in which case all - // repositories will be trusted without authentication). - // - strings trust; - - // Build machine to use for building the package. - // - std::string machine; - - // Default for the machine if absent. - // - butl::optional target; - - // Build system configuration variables (in addition to build environment - // configuration variables). - // Note: could be quoted. - // - strings config; - - strings - unquoted_config () const; - - task_manifest (std::string nm, - bpkg::version vr, - bpkg::repository_location rl, - strings tr, - std::string mn, - butl::optional tg, - strings cf) - : name (std::move (nm)), - version (std::move (vr)), - repository (std::move (rl)), - trust (tr), - machine (std::move (mn)), - target (std::move (tg)), - config (std::move (cf)) {} - - public: - task_manifest () = default; // VC export. - task_manifest (butl::manifest_parser&, bool ignore_unknown = false); - task_manifest (butl::manifest_parser&, - butl::manifest_name_value start, - bool ignore_unknown = false); - - void - serialize (butl::manifest_serializer&) const; - - // Check that a string has the name=value format. The name must not - // contain spaces. Throw invalid_argument if the string doesn't conform to - // these rules. - // - static void - check_config (const std::string&); - }; - - class LIBBBOT_EXPORT task_response_manifest - { - public: - // If empty then no task available. - // - std::string session; - - // Challenge, result url and task are absent if session is empty. - // - butl::optional challenge; - butl::optional result_url; - butl::optional task; - - task_response_manifest (std::string s, - butl::optional c, - butl::optional u, - butl::optional t) - : session (std::move (s)), - challenge (std::move (c)), - result_url (std::move (u)), - task (std::move (t)) {} - - public: - task_response_manifest () = default; // VC export. - task_response_manifest (butl::manifest_parser&, - bool ignore_unknown = false); - - void - serialize (butl::manifest_serializer&) const; - }; - - // Build task or operation result status. - // - enum class result_status: std::uint8_t - { - // The order of the enumerators is arranged so that their integral values - // indicate whether one "overrides" the other in the "merge" operator| - // (see below). - // - success, - warning, - error, - abort, - abnormal - }; - - LIBBBOT_EXPORT std::string - to_string (result_status); - - LIBBBOT_EXPORT result_status - to_result_status (const std::string&); // May throw invalid_argument. - - inline std::ostream& - operator<< (std::ostream& os, result_status s) {return os << to_string (s);} - - inline result_status& - operator|= (result_status& l, result_status r) - { - if (static_cast (r) > static_cast (l)) - l = r; - return l; - } - - // Return true if the result is "bad", that is, error or worse. - // - inline bool - operator! (result_status s) - { - return static_cast (s) >= - static_cast (result_status::error); - } - - struct operation_result - { - std::string operation; // "configure", "update", "test", etc. - result_status status; - std::string log; - }; - - using operation_results = std::vector; - - class LIBBBOT_EXPORT result_manifest - { - public: - // Built package. - // - // If the version is 0 (which signifies a stub package that cannot be - // possibly built) then both name and version are "unknown". This is used - // by the worker to signal abnormal termination before being able to - // obtain the package name/version. - // - std::string name; - bpkg::version version; - - result_status status; - - // Ordered (ascending) by operation value. May not contain all the - // operations if the task failed in the middle, but should have no gaps - // (operation can not start unless all previous ones succeeded). - // - operation_results results; - - result_manifest (std::string n, - bpkg::version v, - result_status s, - operation_results r) - : name (std::move (n)), - version (std::move (v)), - status (s), - results (std::move (r)) {} - - public: - result_manifest () = default; // VC export. - result_manifest (butl::manifest_parser&, bool ignore_unknown = false); - result_manifest (butl::manifest_parser&, - butl::manifest_name_value start, - bool ignore_unknown = false); - - void - serialize (butl::manifest_serializer&) const; - }; - - class LIBBBOT_EXPORT result_request_manifest - { - public: - std::string session; // The task response session. - - // The answer to challenge in the task response. - // - butl::optional challenge; - - result_manifest result; - - result_request_manifest (std::string s, - butl::optional c, - result_manifest r) - : session (std::move (s)), - challenge (std::move (c)), - result (std::move (r)) {} - - public: - result_request_manifest () = default; // VC export. - result_request_manifest (butl::manifest_parser&, - bool ignore_unknown = false); - - void - serialize (butl::manifest_serializer&) const; - }; -} - -#endif // BBOT_MANIFEST diff --git a/bbot/manifest.cxx b/bbot/manifest.cxx deleted file mode 100644 index eab563b..0000000 --- a/bbot/manifest.cxx +++ /dev/null @@ -1,988 +0,0 @@ -// file : bbot/manifest.cxx -*- C++ -*- -// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#include - -#include -#include -#include // isxdigit() -#include -#include -#include // size_t -#include // move() -#include // uint64_t -#include // invalid_argument - -#include // digit() -#include -#include -#include -#include - -using namespace std; -using namespace butl; -using namespace bpkg; - -namespace bbot -{ - using parser = manifest_parser; - using parsing = manifest_parsing; - using serializer = manifest_serializer; - using serialization = manifest_serialization; - using name_value = manifest_name_value; - - using strings = vector; - - // result_status - // - string - to_string (result_status s) - { - switch (s) - { - case result_status::success: return "success"; - case result_status::warning: return "warning"; - case result_status::error: return "error"; - case result_status::abort: return "abort"; - case result_status::abnormal: return "abnormal"; - } - - assert (false); - return string (); - } - - result_status - to_result_status (const string& s) - { - if (s == "success") return result_status::success; - else if (s == "warning") return result_status::warning; - else if (s == "error") return result_status::error; - else if (s == "abort") return result_status::abort; - else if (s == "abnormal") return result_status::abnormal; - else throw invalid_argument ("invalid result status '" + s + "'"); - } - - // Utility functions - // - inline static bool - valid_sha256 (const string& s) noexcept - { - if (s.size () != 64) - return false; - - for (const auto& c: s) - { - if ((c < 'a' || c > 'f' ) && !digit (c)) - return false; - } - - return true; - } - - inline static bool - valid_fingerprint (const string& f) noexcept - { - size_t n (f.size ()); - if (n != 32 * 3 - 1) - return false; - - for (size_t i (0); i < n; ++i) - { - char c (f[i]); - if ((i + 1) % 3 == 0) - { - if (c != ':') - return false; - } - else if (!isxdigit (c)) - return false; - } - - return true; - } - - // machine_header_manifest - // - machine_header_manifest:: - machine_header_manifest (parser& p, bool iu) - : machine_header_manifest (p, p.next (), iu) - { - // Make sure this is the end. - // - name_value nv (p.next ()); - if (!nv.empty ()) - throw parsing (p.name (), nv.name_line, nv.name_column, - "single machine header manifest expected"); - } - - machine_header_manifest:: - machine_header_manifest (parser& p, name_value nv, bool iu) - { - auto bad_name = [&p, &nv] (const string& d) - { - throw parsing (p.name (), nv.name_line, nv.name_column, d); - }; - - auto bad_value = [&p, &nv] (const string& d) - { - throw parsing (p.name (), nv.value_line, nv.value_column, d); - }; - - // Make sure this is the start and we support the version. - // - if (!nv.name.empty ()) - bad_name ("start of machine header manifest expected"); - - if (nv.value != "1") - bad_value ("unsupported format version"); - - for (nv = p.next (); !nv.empty (); nv = p.next ()) - { - string& n (nv.name); - string& v (nv.value); - - if (n == "id") - { - if (!id.empty ()) - bad_name ("machine id redefinition"); - - if (v.empty ()) - bad_value ("empty machine id"); - - id = move (v); - } - else if (n == "name") - { - if (!name.empty ()) - bad_name ("machine name redefinition"); - - if (v.empty ()) - bad_value ("empty machine name"); - - name = move (v); - } - else if (n == "summary") - { - if (!summary.empty ()) - bad_name ("machine summary redefinition"); - - if (v.empty ()) - bad_value ("empty machine summary"); - - summary = move (v); - } - else if (!iu) - bad_name ("unknown name '" + n + "' in machine header manifest"); - } - - // Verify all non-optional values were specified. - // - if (id.empty ()) - bad_value ("no machine id specified"); - - if (name.empty ()) - bad_value ("no machine name specified"); - - if (summary.empty ()) - bad_value ("no machine summary specified"); - } - - void machine_header_manifest:: - serialize (serializer& s) const - { - // @@ Should we check that all non-optional values are specified and all - // values are valid? - // - s.next ("", "1"); // Start of manifest. - s.next ("id", id); - s.next ("name", name); - s.next ("summary", summary); - s.next ("", ""); // End of manifest. - } - - // task_request_manifest - // - task_request_manifest:: - task_request_manifest (parser& p, bool iu) - { - name_value nv (p.next ()); - - auto bad_name = [&p, &nv] (const string& d) - { - throw parsing (p.name (), nv.name_line, nv.name_column, d); - }; - - auto bad_value = [&p, &nv] (const string& d) - { - throw parsing (p.name (), nv.value_line, nv.value_column, d); - }; - - // Make sure this is the start and we support the version. - // - if (!nv.name.empty ()) - bad_name ("start of task request manifest expected"); - - if (nv.value != "1") - bad_value ("unsupported format version"); - - // Parse the task request manifest. - // - for (nv = p.next (); !nv.empty (); nv = p.next ()) - { - string& n (nv.name); - string& v (nv.value); - - if (n == "agent") - { - if (!agent.empty ()) - bad_name ("task request agent redefinition"); - - if (v.empty ()) - bad_value ("empty task request agent"); - - agent = move (v); - } - else if (n == "fingerprint") - { - if (fingerprint) - bad_name ("task request fingerprint redefinition"); - - if (!valid_sha256 (v)) - bad_value ("invalid task request fingerprint"); - - fingerprint = move (v); - } - else if (!iu) - bad_name ("unknown name '" + n + "' in task request manifest"); - } - - // Verify all non-optional values were specified. - // - if (agent.empty ()) - bad_value ("no task request agent specified"); - - // Parse machine header manifests. - // - for (nv = p.next (); !nv.empty (); nv = p.next ()) - machines.emplace_back (machine_header_manifest (p, nv, iu)); - - if (machines.empty ()) - bad_value ("no task request machines specified"); - } - - void task_request_manifest:: - serialize (serializer& s) const - { - // @@ Should we check that all non-optional values are specified and all - // values are valid? - // - s.next ("", "1"); // Start of manifest. - s.next ("agent", agent); - - if (fingerprint) - s.next ("fingerprint", *fingerprint); - - s.next ("", ""); // End of manifest. - - for (const machine_header_manifest& m: machines) - m.serialize (s); - - s.next ("", ""); // End of stream. - } - - // task_manifest - // - task_manifest:: - task_manifest (parser& p, bool iu) - : task_manifest (p, p.next (), iu) - { - // Make sure this is the end. - // - name_value nv (p.next ()); - if (!nv.empty ()) - throw parsing (p.name (), nv.name_line, nv.name_column, - "single task manifest expected"); - } - - task_manifest:: - task_manifest (parser& p, name_value nv, bool iu) - { - auto bad_name = [&p, &nv] (const string& d) - { - throw parsing (p.name (), nv.name_line, nv.name_column, d); - }; - - // Offsets are used to tie an error to the specific position inside a - // manifest value (possibly a multiline one). - // - auto bad_value = [&p, &nv] ( - const string& d, uint64_t column_offset = 0, uint64_t line_offset = 0) - { - throw parsing (p.name (), - nv.value_line + line_offset, - (line_offset == 0 ? nv.value_column : 1) + column_offset, - d); - }; - - // Make sure this is the start and we support the version. - // - if (!nv.name.empty ()) - bad_name ("start of task manifest expected"); - - if (nv.value != "1") - bad_value ("unsupported format version"); - - // Parse the task manifest. - // - for (nv = p.next (); !nv.empty (); nv = p.next ()) - { - string& n (nv.name); - string& v (nv.value); - - if (n == "name") - { - if (!name.empty ()) - bad_name ("task package name redefinition"); - - if (v.empty ()) - bad_value ("empty task package name"); - - name = move (v); - } - else if (n == "version") - { - if (!version.empty ()) - bad_name ("task package version redefinition"); - - try - { - version = bpkg::version (move (v)); - } - catch (const invalid_argument& e) - { - bad_value (string ("invalid task package version: ") + e.what ()); - } - - // Versions like 1.2.3- are forbidden in manifest as intended to be - // used for version constrains rather than actual releases. - // - if (version.release && version.release->empty ()) - bad_value ("invalid task package version release"); - } - else if (n == "repository") - { - if (!repository.empty ()) - bad_name ("task repository redefinition"); - - if (v.empty ()) - bad_value ("empty task repository"); - - try - { - // Call remote/absolute repository location constructor (throws - // invalid_argument for relative location). - // - repository = repository_location (move (v)); - } - catch (const invalid_argument& e) - { - bad_value (string ("invalid task repository: ") + e.what ()); - } - } - else if (n == "trust") - { - if (v != "yes" && !valid_fingerprint (v)) - bad_value ("invalid repository certificate fingerprint"); - - trust.emplace_back (move (v)); - } - else if (n == "machine") - { - if (!machine.empty ()) - bad_name ("task machine redefinition"); - - if (v.empty ()) - bad_value ("empty task machine"); - - machine = move (v); - } - else if (n == "target") - { - if (target) - bad_name ("task target redefinition"); - - try - { - target = target_triplet (v); - } - catch (const invalid_argument& e) - { - bad_value (string ("invalid task target: ") + e.what ()); - } - } - else if (n == "config") - { - if (!config.empty ()) - bad_name ("task configuration redefinition"); - - // Note that when reporting errors we combine the manifest value - // position with the respective field and error positions. - // - try - { - istringstream is (v); - tab_parser parser (is, ""); - - // Here we naturally support multiline config manifest. - // - tab_fields tl; - while (!(tl = parser.next ()).empty ()) - { - for (auto& tf: tl) - { - try - { - check_config (tf.value); - } - catch (const invalid_argument& e) - { - bad_value (string ("invalid task configuration: ") + e.what (), - tf.column - 1, - tl.line - 1); - } - - config.emplace_back (move (tf.value)); - } - } - } - catch (const tab_parsing& e) - { - bad_value ("invalid task configuration: " + e.description, - e.column - 1, - e.line - 1); - } - - if (config.empty ()) - bad_value ("empty task configuration"); - } - else if (!iu) - bad_name ("unknown name '" + n + "' in task manifest"); - } - - // Verify all non-optional values were specified. - // - if (name.empty ()) - bad_value ("no task package name specified"); - - if (version.empty ()) - bad_value ("no task package version specified"); - - if (repository.empty ()) - bad_value ("no task repository specified"); - - if (machine.empty ()) - bad_value ("no task machine specified"); - } - - void task_manifest:: - serialize (serializer& s) const - { - // @@ Should we check that all non-optional values are specified and all - // values are valid? - // - s.next ("", "1"); // Start of manifest. - s.next ("name", name); - s.next ("version", version.string ()); - s.next ("repository", repository.string ()); - - for (const auto& v: trust) - s.next ("trust", v); - - s.next ("machine", machine); - - if (target) - s.next ("target", target->string ()); - - // Recompose config string as a space-separated variable list, - // - if (!config.empty ()) - { - string v; - for (auto b (config.cbegin ()), i (b), e (config.cend ()); i != e; ++i) - { - if (i != b) - v += ' '; - - v += *i; - } - - s.next ("config", v); - } - - s.next ("", ""); // End of manifest. - } - - strings task_manifest:: - unquoted_config () const - { - return string_parser::unquote (config); - } - - void task_manifest:: - check_config (const string& s) - { - auto i (s.begin ()); - auto e (s.end ()); - - // Iterate until the variable name end and check that it contains no - // whitespaces. - // - for (; i != e; ++i) - { - char c (*i); - - if (c == ' ' || c == '\t') // Whitespace in name. - throw invalid_argument ("expected variable assignment"); - else if (c == '=') - break; - } - - if (i == e) - throw invalid_argument ("no variable value"); - } - - // task_response_manifest - // - task_response_manifest:: - task_response_manifest (parser& p, bool iu) - { - name_value nv (p.next ()); - - auto bad_name = [&p, &nv] (const string& d) - { - throw parsing (p.name (), nv.name_line, nv.name_column, d); - }; - - auto bad_value = [&p, &nv] (const string& d) - { - throw parsing (p.name (), nv.value_line, nv.value_column, d); - }; - - // Make sure this is the start and we support the version. - // - if (!nv.name.empty ()) - bad_name ("start of task response manifest expected"); - - if (nv.value != "1") - bad_value ("unsupported format version"); - - // Parse the task response manifest. - // - // Note that we need to distinguish an empty and absent session. - // - optional sess; - for (nv = p.next (); !nv.empty (); nv = p.next ()) - { - string& n (nv.name); - string& v (nv.value); - - if (n == "session") - { - if (sess) - bad_name ("task response session redefinition"); - - sess = move (v); - } - else if (n == "challenge") - { - if (challenge) - bad_name ("task response challenge redefinition"); - - if (v.empty ()) - bad_value ("empty task response challenge"); - - challenge = move (v); - } - else if (n == "result-url") - { - if (result_url) - bad_name ("task response result url redefinition"); - - if (v.empty ()) - bad_value ("empty task response result url"); - - result_url = move (v); - } - else if (!iu) - bad_name ("unknown name '" + n + "' in task response manifest"); - } - - // Verify all non-optional values were specified, and all values are - // expected. - // - if (!sess) - bad_value ("no task response session specified"); - - session = move (*sess); - - // If session is not empty then the challenge may, and the result url - // must, be present, otherwise they shouldn't. - // - if (!session.empty ()) - { - if (!result_url) - bad_value ("no task response result url specified"); - } - else - { - if (challenge) - bad_value ("unexpected task response challenge"); - - if (result_url) - bad_value ("unexpected task response result url"); - } - - // If session is not empty then the task manifest must follow, otherwise it - // shouldn't. - // - nv = p.next (); - - if (!session.empty ()) - { - if (nv.empty ()) - bad_value ("task manifest expected"); - - task = task_manifest (p, nv, iu); - - nv = p.next (); - } - - // Make sure this is the end. - // - if (!nv.empty ()) - throw parsing (p.name (), nv.name_line, nv.name_column, - "single task response manifest expected"); - } - - void task_response_manifest:: - serialize (serializer& s) const - { - // @@ Should we check that all non-optional values are specified and all - // values are valid? - // - s.next ("", "1"); // Start of manifest. - s.next ("session", session); - - if (challenge) - s.next ("challenge", *challenge); - - if (result_url) - s.next ("result-url", *result_url); - - s.next ("", ""); // End of manifest. - - if (task) - task->serialize (s); - - s.next ("", ""); // End of stream. - } - - // result_manifest - // - result_manifest:: - result_manifest (parser& p, bool iu) - : result_manifest (p, p.next (), iu) - { - // Make sure this is the end. - // - name_value nv (p.next ()); - if (!nv.empty ()) - throw parsing (p.name (), nv.name_line, nv.name_column, - "single result manifest expected"); - } - - result_manifest:: - result_manifest (parser& p, name_value nv, bool iu) - { - auto bad_name = [&p, &nv] (const string& d) - { - throw parsing (p.name (), nv.name_line, nv.name_column, d); - }; - - auto bad_value = [&p, &nv] (const string& d, size_t offset = 0) - { - throw parsing (p.name (), nv.value_line, nv.value_column + offset, d); - }; - - auto result_stat = - [&bad_value] (const string& v, const string& what) -> result_status - { - try - { - return to_result_status (v); - } - catch (const invalid_argument&) - { - bad_value ("invalid " + what); - } - - // Can't be here. Would be redundant if it were possible to declare - // lambda with the [[noreturn]] attribute. Note that GCC (non-portably) - // supports that. - // - return result_status::abnormal; - }; - - // Make sure this is the start and we support the version. - // - if (!nv.name.empty ()) - bad_name ("start of result manifest expected"); - - if (nv.value != "1") - bad_value ("unsupported format version"); - - // Parse the result manifest. - // - optional stat; - - // Number of parsed *-log values. Also denotes the next expected log type. - // - size_t nlog (0); - - for (nv = p.next (); !nv.empty (); nv = p.next ()) - { - string& n (nv.name); - string& v (nv.value); - - if (n == "name") - { - if (!name.empty ()) - bad_name ("result package name redefinition"); - - if (v.empty ()) - bad_value ("empty result package name"); - - name = move (v); - } - else if (n == "version") - { - if (!version.empty ()) - bad_name ("result package version redefinition"); - - try - { - version = bpkg::version (move (v)); - } - catch (const invalid_argument& e) - { - bad_value (string ("invalid result package version: ") + e.what ()); - } - - // Versions like 1.2.3- are forbidden in manifest as intended to be - // used for version constrains rather than actual releases. - // - if (version.release && version.release->empty ()) - bad_value ("invalid result package version release"); - } - else if (n == "status") - { - if (stat) - bad_name ("result status redefinition"); - - stat = result_stat (v, "result status"); - } - else - { - size_t nn (n.size ()); // Name length. - - // Note: returns false if nothing preceeds a suffix. - // - auto suffix = [&n, nn] (const char* s, size_t sn) -> bool - { - return nn > sn && n.compare (nn - sn, sn, s) == 0; - }; - - size_t sn; - if (suffix ("-status", sn = 7)) - { - if (!stat) - bad_name ("result status must appear first"); - - if (nlog > 0) // Some logs have already been parsed. - bad_name (n + " after operations logs"); - - string op (n, 0, nn - sn); - - // Make sure the operation result status is not redefined. - // - for (const auto& r: results) - { - if (r.operation == op) - bad_name ("result " + n + " redefinition"); - } - - // Add the operation result (log will come later). - // - results.push_back ({move (op), result_stat (v, n), string ()}); - } - else if (suffix ("-log", sn = 4)) - { - string op (n, 0, nn - sn); - - // Check that specifically this operation log is expected. - // - if (nlog >= results.size ()) - bad_name ("unexpected " + n); - - if (results[nlog].operation != op) - bad_name (results[nlog].operation + "-log is expected"); - - // Save operation log. - // - results[nlog++].log = move (v); - } - else if (!iu) - bad_name ("unknown name '" + n + "' in result manifest"); - } - } - - // Verify all non-optional values were specified. - // - if (name.empty ()) - bad_value ("no result package name specified"); - - if (version.empty ()) - bad_value ("no result package version specified"); - - if (!stat) - bad_value ("no result status specified"); - - // @@ Checking that the result status is consistent with operations - // statuses is a bit hairy, so let's postpone for now. - // - status = move (*stat); - - // Check that we have log for every operation result status. - // - if (nlog < results.size ()) - bad_name ("no result " + results[nlog].operation + "-log specified"); - } - - void result_manifest:: - serialize (serializer& s) const - { - // @@ Should we check that all non-optional values are specified and all - // values are valid? - // - s.next ("", "1"); // Start of manifest. - s.next ("name", name); - s.next ("version", version.string ()); - s.next ("status", to_string (status)); - - // Serialize *-status values. - // - for (const auto& r: results) - s.next (r.operation + "-status", to_string (r.status)); - - // Serialize *-log values. - // - for (const auto& r: results) - s.next (r.operation + "-log", r.log); - - s.next ("", ""); // End of manifest. - } - - // result_request_manifest - // - result_request_manifest:: - result_request_manifest (parser& p, bool iu) - { - name_value nv (p.next ()); - - auto bad_name = [&p, &nv] (const string& d) - { - throw parsing (p.name (), nv.name_line, nv.name_column, d); - }; - - auto bad_value = [&p, &nv] (const string& d) - { - throw parsing (p.name (), nv.value_line, nv.value_column, d); - }; - - // Make sure this is the start and we support the version. - // - if (!nv.name.empty ()) - bad_name ("start of result request manifest expected"); - - if (nv.value != "1") - bad_value ("unsupported format version"); - - // Parse the result request manifest. - // - for (nv = p.next (); !nv.empty (); nv = p.next ()) - { - string& n (nv.name); - string& v (nv.value); - - if (n == "session") - { - if (!session.empty ()) - bad_name ("result request session redefinition"); - - if (v.empty ()) - bad_value ("empty result request session"); - - session = move (v); - } - else if (n == "challenge") - { - if (challenge) - bad_name ("result request challenge redefinition"); - - if (v.empty ()) - bad_value ("empty result request challenge"); - - challenge = move (v); - } - else if (!iu) - bad_name ("unknown name '" + n + "' in result request manifest"); - } - - // Verify all non-optional values were specified. - // - if (session.empty ()) - bad_value ("no result request session specified"); - - nv = p.next (); - if (nv.empty ()) - bad_value ("result manifest expected"); - - result = result_manifest (p, nv, iu); - - // Make sure this is the end. - // - nv = p.next (); - if (!nv.empty ()) - throw parsing (p.name (), nv.name_line, nv.name_column, - "single result request manifest expected"); - } - - void result_request_manifest:: - serialize (serializer& s) const - { - // @@ Should we check that all non-optional values are specified and all - // values are valid? - // - s.next ("", "1"); // Start of manifest. - s.next ("session", session); - - if (challenge) - s.next ("challenge", *challenge); - - s.next ("", ""); // End of manifest. - - result.serialize (s); - s.next ("", ""); // End of stream. - } -} diff --git a/bbot/version.in b/bbot/version.in deleted file mode 100644 index 41c4ce6..0000000 --- a/bbot/version.in +++ /dev/null @@ -1,48 +0,0 @@ -// file : bbot/version.in -*- C++ -*- -// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#ifndef LIBBBOT_VERSION // Note: using the version macro itself. - -// Note: using build2 standard versioning scheme. The numeric version format -// is AAABBBCCCDDDE where: -// -// AAA - major version number -// BBB - minor version number -// CCC - bugfix version number -// DDD - alpha / beta (DDD + 500) version number -// E - final (0) / snapshot (1) -// -// When DDDE is not 0, 1 is subtracted from AAABBBCCC. For example: -// -// Version AAABBBCCCDDDE -// -// 0.1.0 0000010000000 -// 0.1.2 0000010010000 -// 1.2.3 0010020030000 -// 2.2.0-a.1 0020019990010 -// 3.0.0-b.2 0029999995020 -// 2.2.0-a.1.z 0020019990011 -// -#define LIBBBOT_VERSION $libbbot.version.project_number$ULL -#define LIBBBOT_VERSION_STR "$libbbot.version.project$" -#define LIBBBOT_VERSION_ID "$libbbot.version.project_id$" - -#define LIBBBOT_VERSION_MAJOR $libbbot.version.major$ -#define LIBBBOT_VERSION_MINOR $libbbot.version.minor$ -#define LIBBBOT_VERSION_PATCH $libbbot.version.patch$ - -#define LIBBBOT_PRE_RELEASE $libbbot.version.pre_release$ - -#define LIBBBOT_SNAPSHOT $libbbot.version.snapshot_sn$ULL -#define LIBBBOT_SNAPSHOT_ID "$libbbot.version.snapshot_id$" - -#include - -$libbutl.check(LIBBUTL_VERSION, LIBBUTL_SNAPSHOT)$ - -#include - -$libbpkg.check(LIBBPKG_VERSION, LIBBPKG_SNAPSHOT)$ - -#endif // LIBBBOT_VERSION diff --git a/build/export.build b/build/export.build index 013e46f..66f5d7d 100644 --- a/build/export.build +++ b/build/export.build @@ -4,7 +4,7 @@ $out_root/: { - include bbot/ + include libbbot/ } -export $out_root/bbot/lib{bbot} +export $out_root/libbbot/lib{bbot} diff --git a/build/root.build b/build/root.build index af2c962..196d2a3 100644 --- a/build/root.build +++ b/build/root.build @@ -6,7 +6,7 @@ cxx.std = latest using cxx -hxx{*}: extension = +hxx{*}: extension = hxx ixx{*}: extension = ixx txx{*}: extension = txx cxx{*}: extension = cxx diff --git a/buildfile b/buildfile index b3d84fa..d5927ec 100644 --- a/buildfile +++ b/buildfile @@ -2,7 +2,7 @@ # copyright : Copyright (c) 2014-2017 Code Synthesis Ltd # license : MIT; see accompanying LICENSE file -./: bbot/ tests/ doc{INSTALL LICENSE NEWS README version} file{manifest} +./: libbbot/ tests/ doc{INSTALL LICENSE NEWS README version} file{manifest} doc{version}: file{manifest} # Generated by the version module. doc{version}: dist = true diff --git a/libbbot/.gitignore b/libbbot/.gitignore new file mode 100644 index 0000000..426db9e --- /dev/null +++ b/libbbot/.gitignore @@ -0,0 +1 @@ +version.hxx diff --git a/libbbot/build-config.cxx b/libbbot/build-config.cxx new file mode 100644 index 0000000..d902d42 --- /dev/null +++ b/libbbot/build-config.cxx @@ -0,0 +1,125 @@ +// file : libbbot/build-config.cxx -*- C++ -*- +// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#include + +#include +#include // size_t +#include // move() +#include // invalid_argument + +#include +#include +#include + +#include // task_manifest::check_config() + +using namespace std; +using namespace butl; + +namespace bbot +{ + LIBBBOT_EXPORT build_configs + parse_buildtab (istream& is, const string& name) + { + build_configs r; + tab_parser parser (is, name); + + tab_fields tl; + while (!(tl = parser.next ()).empty ()) + { + size_t n (tl.size ()); // Fields count. + size_t i (0); // The field currently being processed. + + // Throw tab_parsing for the field currently being processed. If i == n + // then we refer to the end-of-line column (presumably reporting a missed + // field). + // + auto bad_line = [&name, &tl, &i, n] (const string& d) + { + // Offset beyond the end-of-line is meaningless. + // + assert (i <= n); + + throw tab_parsing (name, + tl.line, + i == n + ? tl.end_column + : tl[i].column, + d); + }; + + build_config config; + config.machine_pattern = move (tl[i++].value); + + // Configuration name field is a required one. + // + if (i == n) + bad_line ("no configuration name found"); + + config.name = move (tl[i].value); + + // Make sure the name is unique. + // + for (const auto& c: r) + if (c.name == config.name) + bad_line ("duplicate configuration name"); + + // If there is no target nor configuration variables then save the + // configuration and proceed with the next line. + // + if (++i == n) + { + r.emplace_back (move (config)); + continue; + } + + // If the third field doesn't contain '=' character, then we will treat + // it as a target. + // + if (tl[i].value.find ('=') == string::npos) + { + try + { + config.target = target_triplet (tl[i].value); + } + catch (const invalid_argument& e) + { + bad_line (e.what ()); + } + + ++i; + } + + try + { + for (; i < n; ++i) + { + task_manifest::check_config (tl[i].value); + config.vars.emplace_back (move (tl[i].value)); + } + } + catch (const invalid_argument& e) + { + bad_line (e.what ()); + } + + // Save the configuration. + // + r.emplace_back (move (config)); + } + + return r; + } + + build_configs + parse_buildtab (const path& p) + { + ifdstream ifs (p); + build_configs r (parse_buildtab (ifs, p.string ())); + + ifs.close (); // Throws on failure. + return r; + } +} diff --git a/libbbot/build-config.hxx b/libbbot/build-config.hxx new file mode 100644 index 0000000..8a2e2d7 --- /dev/null +++ b/libbbot/build-config.hxx @@ -0,0 +1,53 @@ +// file : libbbot/build-config.hxx -*- C++ -*- +// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef LIBBBOT_BUILD_CONFIG_HXX +#define LIBBBOT_BUILD_CONFIG_HXX + +#include +#include +#include + +#include +#include +#include +#include + +#include +#include + +namespace bbot +{ + // Build configuration matching specific machine names. Used by bbot + // controllers. + // + struct build_config + { + std::string machine_pattern; // Machine name pattern. + std::string name; // Configuration name. + + butl::optional target; + + std::vector vars; + }; + + using build_configs = std::vector; + + // Parse buildtab stream or file. Throw tab_parsing on parsing error, + // ios::failure on the underlying OS error. + // + // buildtab consists of lines in the following format: + // + // [] [] + // + using butl::tab_parsing; + + LIBBBOT_EXPORT build_configs + parse_buildtab (std::istream&, const std::string& name); + + LIBBBOT_EXPORT build_configs + parse_buildtab (const butl::path&); +} + +#endif // LIBBBOT_BUILD_CONFIG_HXX diff --git a/libbbot/buildfile b/libbbot/buildfile new file mode 100644 index 0000000..c143032 --- /dev/null +++ b/libbbot/buildfile @@ -0,0 +1,37 @@ +# file : libbbot/buildfile +# copyright : Copyright (c) 2014-2017 Code Synthesis Ltd +# license : MIT; see accompanying LICENSE file + +import int_libs = libbutl%lib{butl} libbpkg%lib{bpkg} + +lib{bbot}: \ +{hxx cxx}{ build-config } \ +{hxx }{ export } \ +{hxx cxx}{ manifest } \ +{hxx }{ version } \ + $int_libs + +hxx{version}: in{version} $src_root/file{manifest} +hxx{version}: dist = true + +# For pre-releases use the complete version to make sure they cannot be used +# in place of another pre-release or the final version. +# +if $version.pre_release + lib{bbot}: bin.lib.version = @"-$version.project_id" +else + lib{bbot}: bin.lib.version = @"-$version.major.$version.minor" + +cxx.poptions =+ "-I$out_root" "-I$src_root" +obja{*}: cxx.poptions += -DLIBBBOT_STATIC_BUILD +objs{*}: cxx.poptions += -DLIBBBOT_SHARED_BUILD + +lib{bbot}: cxx.export.poptions = "-I$out_root" "-I$src_root" +liba{bbot}: cxx.export.poptions += -DLIBBBOT_STATIC +libs{bbot}: cxx.export.poptions += -DLIBBBOT_SHARED + +lib{bbot}: cxx.export.libs = $int_libs + +# Install into the libbbot/ subdirectory of, say, /usr/include/. +# +install.include = $install.include/libbbot/ diff --git a/libbbot/export.hxx b/libbbot/export.hxx new file mode 100644 index 0000000..eef4cf7 --- /dev/null +++ b/libbbot/export.hxx @@ -0,0 +1,41 @@ +// file : libbbot/export.hxx -*- C++ -*- +// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef LIBBBOT_EXPORT_HXX +#define LIBBBOT_EXPORT_HXX + +// Normally we don't export class templates (but do complete specializations), +// inline functions, and classes with only inline member functions. Exporting +// classes that inherit from non-exported/imported bases (e.g., std::string) +// will end up badly. The only known workarounds are to not inherit or to not +// export. Also, MinGW GCC doesn't like seeing non-exported function being +// used before their inline definition. The workaround is to reorder code. In +// the end it's all trial and error. + +#if defined(LIBBBOT_STATIC) // Using static. +# define LIBBBOT_EXPORT +#elif defined(LIBBBOT_STATIC_BUILD) // Building static. +# define LIBBBOT_EXPORT +#elif defined(LIBBBOT_SHARED) // Using shared. +# ifdef _WIN32 +# define LIBBBOT_EXPORT __declspec(dllimport) +# else +# define LIBBBOT_EXPORT +# endif +#elif defined(LIBBBOT_SHARED_BUILD) // Building shared. +# ifdef _WIN32 +# define LIBBBOT_EXPORT __declspec(dllexport) +# else +# define LIBBBOT_EXPORT +# endif +#else +// If none of the above macros are defined, then we assume we are being used +// by some third-party build system that cannot/doesn't signal the library +// type. Note that this fallback works for both static and shared but in case +// of shared will be sub-optimal compared to having dllimport. +// +# define LIBBBOT_EXPORT // Using static or shared. +#endif + +#endif // LIBBBOT_EXPORT_HXX diff --git a/libbbot/manifest.cxx b/libbbot/manifest.cxx new file mode 100644 index 0000000..3ea9d19 --- /dev/null +++ b/libbbot/manifest.cxx @@ -0,0 +1,988 @@ +// file : libbbot/manifest.cxx -*- C++ -*- +// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#include + +#include +#include +#include // isxdigit() +#include +#include +#include // size_t +#include // move() +#include // uint64_t +#include // invalid_argument + +#include // digit() +#include +#include +#include +#include + +using namespace std; +using namespace butl; +using namespace bpkg; + +namespace bbot +{ + using parser = manifest_parser; + using parsing = manifest_parsing; + using serializer = manifest_serializer; + using serialization = manifest_serialization; + using name_value = manifest_name_value; + + using strings = vector; + + // result_status + // + string + to_string (result_status s) + { + switch (s) + { + case result_status::success: return "success"; + case result_status::warning: return "warning"; + case result_status::error: return "error"; + case result_status::abort: return "abort"; + case result_status::abnormal: return "abnormal"; + } + + assert (false); + return string (); + } + + result_status + to_result_status (const string& s) + { + if (s == "success") return result_status::success; + else if (s == "warning") return result_status::warning; + else if (s == "error") return result_status::error; + else if (s == "abort") return result_status::abort; + else if (s == "abnormal") return result_status::abnormal; + else throw invalid_argument ("invalid result status '" + s + "'"); + } + + // Utility functions + // + inline static bool + valid_sha256 (const string& s) noexcept + { + if (s.size () != 64) + return false; + + for (const auto& c: s) + { + if ((c < 'a' || c > 'f' ) && !digit (c)) + return false; + } + + return true; + } + + inline static bool + valid_fingerprint (const string& f) noexcept + { + size_t n (f.size ()); + if (n != 32 * 3 - 1) + return false; + + for (size_t i (0); i < n; ++i) + { + char c (f[i]); + if ((i + 1) % 3 == 0) + { + if (c != ':') + return false; + } + else if (!isxdigit (c)) + return false; + } + + return true; + } + + // machine_header_manifest + // + machine_header_manifest:: + machine_header_manifest (parser& p, bool iu) + : machine_header_manifest (p, p.next (), iu) + { + // Make sure this is the end. + // + name_value nv (p.next ()); + if (!nv.empty ()) + throw parsing (p.name (), nv.name_line, nv.name_column, + "single machine header manifest expected"); + } + + machine_header_manifest:: + machine_header_manifest (parser& p, name_value nv, bool iu) + { + auto bad_name = [&p, &nv] (const string& d) + { + throw parsing (p.name (), nv.name_line, nv.name_column, d); + }; + + auto bad_value = [&p, &nv] (const string& d) + { + throw parsing (p.name (), nv.value_line, nv.value_column, d); + }; + + // Make sure this is the start and we support the version. + // + if (!nv.name.empty ()) + bad_name ("start of machine header manifest expected"); + + if (nv.value != "1") + bad_value ("unsupported format version"); + + for (nv = p.next (); !nv.empty (); nv = p.next ()) + { + string& n (nv.name); + string& v (nv.value); + + if (n == "id") + { + if (!id.empty ()) + bad_name ("machine id redefinition"); + + if (v.empty ()) + bad_value ("empty machine id"); + + id = move (v); + } + else if (n == "name") + { + if (!name.empty ()) + bad_name ("machine name redefinition"); + + if (v.empty ()) + bad_value ("empty machine name"); + + name = move (v); + } + else if (n == "summary") + { + if (!summary.empty ()) + bad_name ("machine summary redefinition"); + + if (v.empty ()) + bad_value ("empty machine summary"); + + summary = move (v); + } + else if (!iu) + bad_name ("unknown name '" + n + "' in machine header manifest"); + } + + // Verify all non-optional values were specified. + // + if (id.empty ()) + bad_value ("no machine id specified"); + + if (name.empty ()) + bad_value ("no machine name specified"); + + if (summary.empty ()) + bad_value ("no machine summary specified"); + } + + void machine_header_manifest:: + serialize (serializer& s) const + { + // @@ Should we check that all non-optional values are specified and all + // values are valid? + // + s.next ("", "1"); // Start of manifest. + s.next ("id", id); + s.next ("name", name); + s.next ("summary", summary); + s.next ("", ""); // End of manifest. + } + + // task_request_manifest + // + task_request_manifest:: + task_request_manifest (parser& p, bool iu) + { + name_value nv (p.next ()); + + auto bad_name = [&p, &nv] (const string& d) + { + throw parsing (p.name (), nv.name_line, nv.name_column, d); + }; + + auto bad_value = [&p, &nv] (const string& d) + { + throw parsing (p.name (), nv.value_line, nv.value_column, d); + }; + + // Make sure this is the start and we support the version. + // + if (!nv.name.empty ()) + bad_name ("start of task request manifest expected"); + + if (nv.value != "1") + bad_value ("unsupported format version"); + + // Parse the task request manifest. + // + for (nv = p.next (); !nv.empty (); nv = p.next ()) + { + string& n (nv.name); + string& v (nv.value); + + if (n == "agent") + { + if (!agent.empty ()) + bad_name ("task request agent redefinition"); + + if (v.empty ()) + bad_value ("empty task request agent"); + + agent = move (v); + } + else if (n == "fingerprint") + { + if (fingerprint) + bad_name ("task request fingerprint redefinition"); + + if (!valid_sha256 (v)) + bad_value ("invalid task request fingerprint"); + + fingerprint = move (v); + } + else if (!iu) + bad_name ("unknown name '" + n + "' in task request manifest"); + } + + // Verify all non-optional values were specified. + // + if (agent.empty ()) + bad_value ("no task request agent specified"); + + // Parse machine header manifests. + // + for (nv = p.next (); !nv.empty (); nv = p.next ()) + machines.emplace_back (machine_header_manifest (p, nv, iu)); + + if (machines.empty ()) + bad_value ("no task request machines specified"); + } + + void task_request_manifest:: + serialize (serializer& s) const + { + // @@ Should we check that all non-optional values are specified and all + // values are valid? + // + s.next ("", "1"); // Start of manifest. + s.next ("agent", agent); + + if (fingerprint) + s.next ("fingerprint", *fingerprint); + + s.next ("", ""); // End of manifest. + + for (const machine_header_manifest& m: machines) + m.serialize (s); + + s.next ("", ""); // End of stream. + } + + // task_manifest + // + task_manifest:: + task_manifest (parser& p, bool iu) + : task_manifest (p, p.next (), iu) + { + // Make sure this is the end. + // + name_value nv (p.next ()); + if (!nv.empty ()) + throw parsing (p.name (), nv.name_line, nv.name_column, + "single task manifest expected"); + } + + task_manifest:: + task_manifest (parser& p, name_value nv, bool iu) + { + auto bad_name = [&p, &nv] (const string& d) + { + throw parsing (p.name (), nv.name_line, nv.name_column, d); + }; + + // Offsets are used to tie an error to the specific position inside a + // manifest value (possibly a multiline one). + // + auto bad_value = [&p, &nv] ( + const string& d, uint64_t column_offset = 0, uint64_t line_offset = 0) + { + throw parsing (p.name (), + nv.value_line + line_offset, + (line_offset == 0 ? nv.value_column : 1) + column_offset, + d); + }; + + // Make sure this is the start and we support the version. + // + if (!nv.name.empty ()) + bad_name ("start of task manifest expected"); + + if (nv.value != "1") + bad_value ("unsupported format version"); + + // Parse the task manifest. + // + for (nv = p.next (); !nv.empty (); nv = p.next ()) + { + string& n (nv.name); + string& v (nv.value); + + if (n == "name") + { + if (!name.empty ()) + bad_name ("task package name redefinition"); + + if (v.empty ()) + bad_value ("empty task package name"); + + name = move (v); + } + else if (n == "version") + { + if (!version.empty ()) + bad_name ("task package version redefinition"); + + try + { + version = bpkg::version (move (v)); + } + catch (const invalid_argument& e) + { + bad_value (string ("invalid task package version: ") + e.what ()); + } + + // Versions like 1.2.3- are forbidden in manifest as intended to be + // used for version constrains rather than actual releases. + // + if (version.release && version.release->empty ()) + bad_value ("invalid task package version release"); + } + else if (n == "repository") + { + if (!repository.empty ()) + bad_name ("task repository redefinition"); + + if (v.empty ()) + bad_value ("empty task repository"); + + try + { + // Call remote/absolute repository location constructor (throws + // invalid_argument for relative location). + // + repository = repository_location (move (v)); + } + catch (const invalid_argument& e) + { + bad_value (string ("invalid task repository: ") + e.what ()); + } + } + else if (n == "trust") + { + if (v != "yes" && !valid_fingerprint (v)) + bad_value ("invalid repository certificate fingerprint"); + + trust.emplace_back (move (v)); + } + else if (n == "machine") + { + if (!machine.empty ()) + bad_name ("task machine redefinition"); + + if (v.empty ()) + bad_value ("empty task machine"); + + machine = move (v); + } + else if (n == "target") + { + if (target) + bad_name ("task target redefinition"); + + try + { + target = target_triplet (v); + } + catch (const invalid_argument& e) + { + bad_value (string ("invalid task target: ") + e.what ()); + } + } + else if (n == "config") + { + if (!config.empty ()) + bad_name ("task configuration redefinition"); + + // Note that when reporting errors we combine the manifest value + // position with the respective field and error positions. + // + try + { + istringstream is (v); + tab_parser parser (is, ""); + + // Here we naturally support multiline config manifest. + // + tab_fields tl; + while (!(tl = parser.next ()).empty ()) + { + for (auto& tf: tl) + { + try + { + check_config (tf.value); + } + catch (const invalid_argument& e) + { + bad_value (string ("invalid task configuration: ") + e.what (), + tf.column - 1, + tl.line - 1); + } + + config.emplace_back (move (tf.value)); + } + } + } + catch (const tab_parsing& e) + { + bad_value ("invalid task configuration: " + e.description, + e.column - 1, + e.line - 1); + } + + if (config.empty ()) + bad_value ("empty task configuration"); + } + else if (!iu) + bad_name ("unknown name '" + n + "' in task manifest"); + } + + // Verify all non-optional values were specified. + // + if (name.empty ()) + bad_value ("no task package name specified"); + + if (version.empty ()) + bad_value ("no task package version specified"); + + if (repository.empty ()) + bad_value ("no task repository specified"); + + if (machine.empty ()) + bad_value ("no task machine specified"); + } + + void task_manifest:: + serialize (serializer& s) const + { + // @@ Should we check that all non-optional values are specified and all + // values are valid? + // + s.next ("", "1"); // Start of manifest. + s.next ("name", name); + s.next ("version", version.string ()); + s.next ("repository", repository.string ()); + + for (const auto& v: trust) + s.next ("trust", v); + + s.next ("machine", machine); + + if (target) + s.next ("target", target->string ()); + + // Recompose config string as a space-separated variable list, + // + if (!config.empty ()) + { + string v; + for (auto b (config.cbegin ()), i (b), e (config.cend ()); i != e; ++i) + { + if (i != b) + v += ' '; + + v += *i; + } + + s.next ("config", v); + } + + s.next ("", ""); // End of manifest. + } + + strings task_manifest:: + unquoted_config () const + { + return string_parser::unquote (config); + } + + void task_manifest:: + check_config (const string& s) + { + auto i (s.begin ()); + auto e (s.end ()); + + // Iterate until the variable name end and check that it contains no + // whitespaces. + // + for (; i != e; ++i) + { + char c (*i); + + if (c == ' ' || c == '\t') // Whitespace in name. + throw invalid_argument ("expected variable assignment"); + else if (c == '=') + break; + } + + if (i == e) + throw invalid_argument ("no variable value"); + } + + // task_response_manifest + // + task_response_manifest:: + task_response_manifest (parser& p, bool iu) + { + name_value nv (p.next ()); + + auto bad_name = [&p, &nv] (const string& d) + { + throw parsing (p.name (), nv.name_line, nv.name_column, d); + }; + + auto bad_value = [&p, &nv] (const string& d) + { + throw parsing (p.name (), nv.value_line, nv.value_column, d); + }; + + // Make sure this is the start and we support the version. + // + if (!nv.name.empty ()) + bad_name ("start of task response manifest expected"); + + if (nv.value != "1") + bad_value ("unsupported format version"); + + // Parse the task response manifest. + // + // Note that we need to distinguish an empty and absent session. + // + optional sess; + for (nv = p.next (); !nv.empty (); nv = p.next ()) + { + string& n (nv.name); + string& v (nv.value); + + if (n == "session") + { + if (sess) + bad_name ("task response session redefinition"); + + sess = move (v); + } + else if (n == "challenge") + { + if (challenge) + bad_name ("task response challenge redefinition"); + + if (v.empty ()) + bad_value ("empty task response challenge"); + + challenge = move (v); + } + else if (n == "result-url") + { + if (result_url) + bad_name ("task response result url redefinition"); + + if (v.empty ()) + bad_value ("empty task response result url"); + + result_url = move (v); + } + else if (!iu) + bad_name ("unknown name '" + n + "' in task response manifest"); + } + + // Verify all non-optional values were specified, and all values are + // expected. + // + if (!sess) + bad_value ("no task response session specified"); + + session = move (*sess); + + // If session is not empty then the challenge may, and the result url + // must, be present, otherwise they shouldn't. + // + if (!session.empty ()) + { + if (!result_url) + bad_value ("no task response result url specified"); + } + else + { + if (challenge) + bad_value ("unexpected task response challenge"); + + if (result_url) + bad_value ("unexpected task response result url"); + } + + // If session is not empty then the task manifest must follow, otherwise it + // shouldn't. + // + nv = p.next (); + + if (!session.empty ()) + { + if (nv.empty ()) + bad_value ("task manifest expected"); + + task = task_manifest (p, nv, iu); + + nv = p.next (); + } + + // Make sure this is the end. + // + if (!nv.empty ()) + throw parsing (p.name (), nv.name_line, nv.name_column, + "single task response manifest expected"); + } + + void task_response_manifest:: + serialize (serializer& s) const + { + // @@ Should we check that all non-optional values are specified and all + // values are valid? + // + s.next ("", "1"); // Start of manifest. + s.next ("session", session); + + if (challenge) + s.next ("challenge", *challenge); + + if (result_url) + s.next ("result-url", *result_url); + + s.next ("", ""); // End of manifest. + + if (task) + task->serialize (s); + + s.next ("", ""); // End of stream. + } + + // result_manifest + // + result_manifest:: + result_manifest (parser& p, bool iu) + : result_manifest (p, p.next (), iu) + { + // Make sure this is the end. + // + name_value nv (p.next ()); + if (!nv.empty ()) + throw parsing (p.name (), nv.name_line, nv.name_column, + "single result manifest expected"); + } + + result_manifest:: + result_manifest (parser& p, name_value nv, bool iu) + { + auto bad_name = [&p, &nv] (const string& d) + { + throw parsing (p.name (), nv.name_line, nv.name_column, d); + }; + + auto bad_value = [&p, &nv] (const string& d, size_t offset = 0) + { + throw parsing (p.name (), nv.value_line, nv.value_column + offset, d); + }; + + auto result_stat = + [&bad_value] (const string& v, const string& what) -> result_status + { + try + { + return to_result_status (v); + } + catch (const invalid_argument&) + { + bad_value ("invalid " + what); + } + + // Can't be here. Would be redundant if it were possible to declare + // lambda with the [[noreturn]] attribute. Note that GCC (non-portably) + // supports that. + // + return result_status::abnormal; + }; + + // Make sure this is the start and we support the version. + // + if (!nv.name.empty ()) + bad_name ("start of result manifest expected"); + + if (nv.value != "1") + bad_value ("unsupported format version"); + + // Parse the result manifest. + // + optional stat; + + // Number of parsed *-log values. Also denotes the next expected log type. + // + size_t nlog (0); + + for (nv = p.next (); !nv.empty (); nv = p.next ()) + { + string& n (nv.name); + string& v (nv.value); + + if (n == "name") + { + if (!name.empty ()) + bad_name ("result package name redefinition"); + + if (v.empty ()) + bad_value ("empty result package name"); + + name = move (v); + } + else if (n == "version") + { + if (!version.empty ()) + bad_name ("result package version redefinition"); + + try + { + version = bpkg::version (move (v)); + } + catch (const invalid_argument& e) + { + bad_value (string ("invalid result package version: ") + e.what ()); + } + + // Versions like 1.2.3- are forbidden in manifest as intended to be + // used for version constrains rather than actual releases. + // + if (version.release && version.release->empty ()) + bad_value ("invalid result package version release"); + } + else if (n == "status") + { + if (stat) + bad_name ("result status redefinition"); + + stat = result_stat (v, "result status"); + } + else + { + size_t nn (n.size ()); // Name length. + + // Note: returns false if nothing preceeds a suffix. + // + auto suffix = [&n, nn] (const char* s, size_t sn) -> bool + { + return nn > sn && n.compare (nn - sn, sn, s) == 0; + }; + + size_t sn; + if (suffix ("-status", sn = 7)) + { + if (!stat) + bad_name ("result status must appear first"); + + if (nlog > 0) // Some logs have already been parsed. + bad_name (n + " after operations logs"); + + string op (n, 0, nn - sn); + + // Make sure the operation result status is not redefined. + // + for (const auto& r: results) + { + if (r.operation == op) + bad_name ("result " + n + " redefinition"); + } + + // Add the operation result (log will come later). + // + results.push_back ({move (op), result_stat (v, n), string ()}); + } + else if (suffix ("-log", sn = 4)) + { + string op (n, 0, nn - sn); + + // Check that specifically this operation log is expected. + // + if (nlog >= results.size ()) + bad_name ("unexpected " + n); + + if (results[nlog].operation != op) + bad_name (results[nlog].operation + "-log is expected"); + + // Save operation log. + // + results[nlog++].log = move (v); + } + else if (!iu) + bad_name ("unknown name '" + n + "' in result manifest"); + } + } + + // Verify all non-optional values were specified. + // + if (name.empty ()) + bad_value ("no result package name specified"); + + if (version.empty ()) + bad_value ("no result package version specified"); + + if (!stat) + bad_value ("no result status specified"); + + // @@ Checking that the result status is consistent with operations + // statuses is a bit hairy, so let's postpone for now. + // + status = move (*stat); + + // Check that we have log for every operation result status. + // + if (nlog < results.size ()) + bad_name ("no result " + results[nlog].operation + "-log specified"); + } + + void result_manifest:: + serialize (serializer& s) const + { + // @@ Should we check that all non-optional values are specified and all + // values are valid? + // + s.next ("", "1"); // Start of manifest. + s.next ("name", name); + s.next ("version", version.string ()); + s.next ("status", to_string (status)); + + // Serialize *-status values. + // + for (const auto& r: results) + s.next (r.operation + "-status", to_string (r.status)); + + // Serialize *-log values. + // + for (const auto& r: results) + s.next (r.operation + "-log", r.log); + + s.next ("", ""); // End of manifest. + } + + // result_request_manifest + // + result_request_manifest:: + result_request_manifest (parser& p, bool iu) + { + name_value nv (p.next ()); + + auto bad_name = [&p, &nv] (const string& d) + { + throw parsing (p.name (), nv.name_line, nv.name_column, d); + }; + + auto bad_value = [&p, &nv] (const string& d) + { + throw parsing (p.name (), nv.value_line, nv.value_column, d); + }; + + // Make sure this is the start and we support the version. + // + if (!nv.name.empty ()) + bad_name ("start of result request manifest expected"); + + if (nv.value != "1") + bad_value ("unsupported format version"); + + // Parse the result request manifest. + // + for (nv = p.next (); !nv.empty (); nv = p.next ()) + { + string& n (nv.name); + string& v (nv.value); + + if (n == "session") + { + if (!session.empty ()) + bad_name ("result request session redefinition"); + + if (v.empty ()) + bad_value ("empty result request session"); + + session = move (v); + } + else if (n == "challenge") + { + if (challenge) + bad_name ("result request challenge redefinition"); + + if (v.empty ()) + bad_value ("empty result request challenge"); + + challenge = move (v); + } + else if (!iu) + bad_name ("unknown name '" + n + "' in result request manifest"); + } + + // Verify all non-optional values were specified. + // + if (session.empty ()) + bad_value ("no result request session specified"); + + nv = p.next (); + if (nv.empty ()) + bad_value ("result manifest expected"); + + result = result_manifest (p, nv, iu); + + // Make sure this is the end. + // + nv = p.next (); + if (!nv.empty ()) + throw parsing (p.name (), nv.name_line, nv.name_column, + "single result request manifest expected"); + } + + void result_request_manifest:: + serialize (serializer& s) const + { + // @@ Should we check that all non-optional values are specified and all + // values are valid? + // + s.next ("", "1"); // Start of manifest. + s.next ("session", session); + + if (challenge) + s.next ("challenge", *challenge); + + s.next ("", ""); // End of manifest. + + result.serialize (s); + s.next ("", ""); // End of stream. + } +} diff --git a/libbbot/manifest.hxx b/libbbot/manifest.hxx new file mode 100644 index 0000000..615c4f9 --- /dev/null +++ b/libbbot/manifest.hxx @@ -0,0 +1,297 @@ +// file : libbbot/manifest.hxx -*- C++ -*- +// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef LIBBBOT_MANIFEST_HXX +#define LIBBBOT_MANIFEST_HXX + +#include +#include +#include + +#include +#include +#include + +#include // version, repository_location + +#include +#include + +namespace bbot +{ + using strings = std::vector; + + class LIBBBOT_EXPORT machine_header_manifest + { + public: + std::string id; + std::string name; + std::string summary; + + machine_header_manifest (std::string i, std::string n, std::string s) + : id (std::move (i)), name (std::move (n)), summary (std::move (s)) {} + + public: + machine_header_manifest () = default; // VC export. + machine_header_manifest (butl::manifest_parser&, + bool ignore_unknown = false); + machine_header_manifest (butl::manifest_parser&, + butl::manifest_name_value start, + bool ignore_unknown = false); + + void + serialize (butl::manifest_serializer&) const; + }; + + using machine_header_manifests = std::vector; + + class LIBBBOT_EXPORT task_request_manifest + { + public: + std::string agent; + + // Agent's public key SHA256 fingerprint. + // + // @@ How the fingerpring for openssl public key will be produced? Seems + // there is no "standard" for it. Possibly we will use the following + // command result (plain SHA256). + // + // $ cat key.pub | openssl sha256 + // + butl::optional fingerprint; + + machine_header_manifests machines; + + task_request_manifest (std::string a, + butl::optional f, + machine_header_manifests m) + : agent (std::move (a)), + fingerprint (std::move (f)), + machines (std::move (m)) {} + + public: + task_request_manifest () = default; // VC export. + task_request_manifest (butl::manifest_parser&, + bool ignore_unknown = false); + + void + serialize (butl::manifest_serializer&) const; + }; + + class LIBBBOT_EXPORT task_manifest + { + public: + // Package to build. + // + std::string name; + bpkg::version version; + bpkg::repository_location repository; // Remote or absolute. + + // The SHA256 repositories certificates fingerprints to trust. The special + // 'yes' value can be specified instead of fingerprint (in which case all + // repositories will be trusted without authentication). + // + strings trust; + + // Build machine to use for building the package. + // + std::string machine; + + // Default for the machine if absent. + // + butl::optional target; + + // Build system configuration variables (in addition to build environment + // configuration variables). + // Note: could be quoted. + // + strings config; + + strings + unquoted_config () const; + + task_manifest (std::string nm, + bpkg::version vr, + bpkg::repository_location rl, + strings tr, + std::string mn, + butl::optional tg, + strings cf) + : name (std::move (nm)), + version (std::move (vr)), + repository (std::move (rl)), + trust (tr), + machine (std::move (mn)), + target (std::move (tg)), + config (std::move (cf)) {} + + public: + task_manifest () = default; // VC export. + task_manifest (butl::manifest_parser&, bool ignore_unknown = false); + task_manifest (butl::manifest_parser&, + butl::manifest_name_value start, + bool ignore_unknown = false); + + void + serialize (butl::manifest_serializer&) const; + + // Check that a string has the name=value format. The name must not + // contain spaces. Throw invalid_argument if the string doesn't conform to + // these rules. + // + static void + check_config (const std::string&); + }; + + class LIBBBOT_EXPORT task_response_manifest + { + public: + // If empty then no task available. + // + std::string session; + + // Challenge, result url and task are absent if session is empty. + // + butl::optional challenge; + butl::optional result_url; + butl::optional task; + + task_response_manifest (std::string s, + butl::optional c, + butl::optional u, + butl::optional t) + : session (std::move (s)), + challenge (std::move (c)), + result_url (std::move (u)), + task (std::move (t)) {} + + public: + task_response_manifest () = default; // VC export. + task_response_manifest (butl::manifest_parser&, + bool ignore_unknown = false); + + void + serialize (butl::manifest_serializer&) const; + }; + + // Build task or operation result status. + // + enum class result_status: std::uint8_t + { + // The order of the enumerators is arranged so that their integral values + // indicate whether one "overrides" the other in the "merge" operator| + // (see below). + // + success, + warning, + error, + abort, + abnormal + }; + + LIBBBOT_EXPORT std::string + to_string (result_status); + + LIBBBOT_EXPORT result_status + to_result_status (const std::string&); // May throw invalid_argument. + + inline std::ostream& + operator<< (std::ostream& os, result_status s) {return os << to_string (s);} + + inline result_status& + operator|= (result_status& l, result_status r) + { + if (static_cast (r) > static_cast (l)) + l = r; + return l; + } + + // Return true if the result is "bad", that is, error or worse. + // + inline bool + operator! (result_status s) + { + return static_cast (s) >= + static_cast (result_status::error); + } + + struct operation_result + { + std::string operation; // "configure", "update", "test", etc. + result_status status; + std::string log; + }; + + using operation_results = std::vector; + + class LIBBBOT_EXPORT result_manifest + { + public: + // Built package. + // + // If the version is 0 (which signifies a stub package that cannot be + // possibly built) then both name and version are "unknown". This is used + // by the worker to signal abnormal termination before being able to + // obtain the package name/version. + // + std::string name; + bpkg::version version; + + result_status status; + + // Ordered (ascending) by operation value. May not contain all the + // operations if the task failed in the middle, but should have no gaps + // (operation can not start unless all previous ones succeeded). + // + operation_results results; + + result_manifest (std::string n, + bpkg::version v, + result_status s, + operation_results r) + : name (std::move (n)), + version (std::move (v)), + status (s), + results (std::move (r)) {} + + public: + result_manifest () = default; // VC export. + result_manifest (butl::manifest_parser&, bool ignore_unknown = false); + result_manifest (butl::manifest_parser&, + butl::manifest_name_value start, + bool ignore_unknown = false); + + void + serialize (butl::manifest_serializer&) const; + }; + + class LIBBBOT_EXPORT result_request_manifest + { + public: + std::string session; // The task response session. + + // The answer to challenge in the task response. + // + butl::optional challenge; + + result_manifest result; + + result_request_manifest (std::string s, + butl::optional c, + result_manifest r) + : session (std::move (s)), + challenge (std::move (c)), + result (std::move (r)) {} + + public: + result_request_manifest () = default; // VC export. + result_request_manifest (butl::manifest_parser&, + bool ignore_unknown = false); + + void + serialize (butl::manifest_serializer&) const; + }; +} + +#endif // LIBBBOT_MANIFEST_HXX diff --git a/libbbot/version.hxx.in b/libbbot/version.hxx.in new file mode 100644 index 0000000..6fca06c --- /dev/null +++ b/libbbot/version.hxx.in @@ -0,0 +1,48 @@ +// file : libbbot/version.hxx.in -*- C++ -*- +// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef LIBBBOT_VERSION // Note: using the version macro itself. + +// Note: using build2 standard versioning scheme. The numeric version format +// is AAABBBCCCDDDE where: +// +// AAA - major version number +// BBB - minor version number +// CCC - bugfix version number +// DDD - alpha / beta (DDD + 500) version number +// E - final (0) / snapshot (1) +// +// When DDDE is not 0, 1 is subtracted from AAABBBCCC. For example: +// +// Version AAABBBCCCDDDE +// +// 0.1.0 0000010000000 +// 0.1.2 0000010010000 +// 1.2.3 0010020030000 +// 2.2.0-a.1 0020019990010 +// 3.0.0-b.2 0029999995020 +// 2.2.0-a.1.z 0020019990011 +// +#define LIBBBOT_VERSION $libbbot.version.project_number$ULL +#define LIBBBOT_VERSION_STR "$libbbot.version.project$" +#define LIBBBOT_VERSION_ID "$libbbot.version.project_id$" + +#define LIBBBOT_VERSION_MAJOR $libbbot.version.major$ +#define LIBBBOT_VERSION_MINOR $libbbot.version.minor$ +#define LIBBBOT_VERSION_PATCH $libbbot.version.patch$ + +#define LIBBBOT_PRE_RELEASE $libbbot.version.pre_release$ + +#define LIBBBOT_SNAPSHOT $libbbot.version.snapshot_sn$ULL +#define LIBBBOT_SNAPSHOT_ID "$libbbot.version.snapshot_id$" + +#include + +$libbutl.check(LIBBUTL_VERSION, LIBBUTL_SNAPSHOT)$ + +#include + +$libbpkg.check(LIBBPKG_VERSION, LIBBPKG_SNAPSHOT)$ + +#endif // LIBBBOT_VERSION diff --git a/tests/.gitignore b/tests/.gitignore index e54525b..2e508a9 100644 --- a/tests/.gitignore +++ b/tests/.gitignore @@ -1 +1,3 @@ driver +test/ +test-*/ diff --git a/tests/buildtab/buildfile b/tests/buildtab/buildfile index 466c8ad..3da3faa 100644 --- a/tests/buildtab/buildfile +++ b/tests/buildtab/buildfile @@ -4,6 +4,6 @@ import libs = libbutl%lib{butl} -exe{driver}: cxx{driver} ../../bbot/lib{bbot} $libs test{testscript} +exe{driver}: cxx{driver} ../../libbbot/lib{bbot} $libs test{testscript} -include ../../bbot/ +include ../../libbbot/ diff --git a/tests/buildtab/driver.cxx b/tests/buildtab/driver.cxx index c3e3a60..28c48fb 100644 --- a/tests/buildtab/driver.cxx +++ b/tests/buildtab/driver.cxx @@ -8,7 +8,7 @@ #include // operator<<(ostream,exception) -#include +#include using namespace std; using namespace butl; diff --git a/tests/manifest/buildfile b/tests/manifest/buildfile index b9af8ef..15e0b59 100644 --- a/tests/manifest/buildfile +++ b/tests/manifest/buildfile @@ -4,6 +4,6 @@ import libs = libbutl%lib{butl} -exe{driver}: cxx{driver} ../../bbot/lib{bbot} $libs test{*} +exe{driver}: cxx{driver} ../../libbbot/lib{bbot} $libs test{*} -include ../../bbot/ +include ../../libbbot/ diff --git a/tests/manifest/driver.cxx b/tests/manifest/driver.cxx index 16240a5..fb58b27 100644 --- a/tests/manifest/driver.cxx +++ b/tests/manifest/driver.cxx @@ -11,7 +11,7 @@ #include #include -#include +#include using namespace std; using namespace butl; -- cgit v1.1