/* * mrustc "minicargo" (minimal cargo clone) * - By John Hodge (Mutabah) * * manifest.cpp * - Cargo.toml manifest loading and manipulation code */ #include "manifest.h" #include "toml.h" #include "debug.h" #include "path.h" #include #include #include // toupper #include "repository.h" #include "cfg.hpp" // TODO: Extract this from the target at runtime (by invoking the compiler on the passed target) #ifdef _WIN32 # define TARGET_NAME "i586-windows-msvc" #elif defined(__NetBSD__) # define TARGET_NAME "x86_64-unknown-netbsd" #else # define TARGET_NAME "x86_64-unknown-linux-gnu" #endif static ::std::vector<::std::shared_ptr> g_loaded_manifests; PackageManifest::PackageManifest() { } namespace { void target_edit_from_kv(PackageTarget& target, TomlKeyValue& kv, unsigned base_idx); } PackageManifest PackageManifest::load_from_toml(const ::std::string& path) { PackageManifest rv; rv.m_manifest_path = path; auto package_dir = ::helpers::path(path).parent(); TomlFile toml_file(path); for(auto key_val : toml_file) { assert(key_val.path.size() > 0); DEBUG(key_val.path << " = " << key_val.value); const auto& section = key_val.path[0]; if( section == "package" ) { assert(key_val.path.size() > 1); const auto& key = key_val.path[1]; if( key == "name" ) { if(rv.m_name != "" ) { // TODO: Warn/error throw ::std::runtime_error("Package name set twice"); } rv.m_name = key_val.value.as_string(); if(rv.m_name == "") { // TODO: Error throw ::std::runtime_error("Package name cannot be empty"); } } else if( key == "version" ) { try { rv.m_version = PackageVersion::from_string(key_val.value.as_string()); } catch(const ::std::invalid_argument& e) { throw ::std::runtime_error(format("Unable to parse package verison in '", path, "' - ", e.what())); } } else if( key == "build" ) { if(rv.m_build_script != "" ) { // TODO: Warn/error throw ::std::runtime_error("Build script path set twice"); } if( key_val.value.m_type == TomlValue::Type::Boolean ) { if( key_val.value.as_bool() ) throw ::std::runtime_error("Build script set to 'true'?"); rv.m_build_script = "-"; } else { rv.m_build_script = key_val.value.as_string(); } if(rv.m_build_script == "") { // TODO: Error throw ::std::runtime_error("Build script path cannot be empty"); } } else if( key == "authors" || key == "description" || key == "homepage" || key == "documentation" || key == "repository" || key == "readme" || key == "categories" || key == "keywords" || key == "license" ) { // Informational only, ignore } else if( key == "exclude" || key == "include" ) { // Packaging } else if( key == "metadata" ) { // Unknown. } else if( key == "links" ) { if(rv.m_links != "" ) { // TODO: Warn/error throw ::std::runtime_error("Package 'links' attribute set twice"); } rv.m_links = key_val.value.as_string(); } else if( key == "autotests" ) { // TODO: Fix the outer makefile so it doesn't need `foo-test` // to be created. //rv.m_create_auto_test = key_val.value.as_bool(); } else if( key == "autobenches" ) { //rv.m_create_auto_bench = key_val.value.as_bool(); } else { // Unknown value in `package` ::std::cerr << "WARNING: Unknown key `" + key + "` in [package]" << ::std::endl; } } else if( section == "lib" ) { // 1. Find (and add if needed) the `lib` descriptor auto it = ::std::find_if(rv.m_targets.begin(), rv.m_targets.end(), [](const auto& x){ return x.m_type == PackageTarget::Type::Lib; }); if(it == rv.m_targets.end()) it = rv.m_targets.insert(it, PackageTarget { PackageTarget::Type::Lib }); // 2. Parse from the key-value pair target_edit_from_kv(*it, key_val, 1); } else if( section == "bin" ) { assert(key_val.path.size() > 1); unsigned idx = ::std::stoi( key_val.path[1] ); auto it = ::std::find_if(rv.m_targets.begin(), rv.m_targets.end(), [&idx](const auto& x) { return x.m_type == PackageTarget::Type::Bin && idx-- == 0; }); if (it == rv.m_targets.end()) it = rv.m_targets.insert(it, PackageTarget{ PackageTarget::Type::Bin }); target_edit_from_kv(*it, key_val, 2); } else if (section == "test") { assert(key_val.path.size() > 1); unsigned idx = ::std::stoi(key_val.path[1]); auto it = ::std::find_if(rv.m_targets.begin(), rv.m_targets.end(), [&idx](const auto& x) { return x.m_type == PackageTarget::Type::Test && idx-- == 0; }); if (it == rv.m_targets.end()) it = rv.m_targets.insert(it, PackageTarget{ PackageTarget::Type::Test }); target_edit_from_kv(*it, key_val, 2); } else if (section == "bench") { assert(key_val.path.size() > 1); unsigned idx = ::std::stoi(key_val.path[1]); auto it = ::std::find_if(rv.m_targets.begin(), rv.m_targets.end(), [&idx](const auto& x) { return x.m_type == PackageTarget::Type::Bench && idx-- == 0; }); if (it == rv.m_targets.end()) it = rv.m_targets.insert(it, PackageTarget{ PackageTarget::Type::Bench }); target_edit_from_kv(*it, key_val, 2); } else if( section == "dependencies" || section == "build-dependencies" || section == "dev-dependencies" ) { ::std::vector& dep_list = section == "dependencies" ? rv.m_dependencies : section == "build-dependencies" ? rv.m_build_dependencies : /*section == "dev-dependencies" ? */ rv.m_dev_dependencies /*: throw ""*/ ; assert(key_val.path.size() > 1); const auto& depname = key_val.path[1]; // Find/create dependency descriptor auto it = ::std::find_if(dep_list.begin(), dep_list.end(), [&](const auto& x) { return x.m_name == depname; }); bool was_added = (it == dep_list.end()); if( was_added ) { it = dep_list.insert(it, PackageRef{ depname }); } it->fill_from_kv(was_added, key_val, 2); } else if( section == "patch" ) { //const auto& repo = key_val.path[1]; TODO("Support repository patches"); } else if( section == "profile" ) { // TODO: Various profiles (debug, release, ...) } else if( section == "target" ) { // TODO: Target opts? if( key_val.path.size() < 3 ) { throw ::std::runtime_error("Expected at least three path components in `[target.{...}]`"); } const auto& cfg = key_val.path[1]; const auto& real_section = key_val.path[2]; // Check if `cfg` currently applies. // - It can be a target spec, or a cfg(foo) same as rustc bool success; if( cfg.substr(0, 4) == "cfg(" ) { success = Cfg_Check(cfg.c_str()); } else { // It's a target name success = (cfg == TARGET_NAME); } // If so, parse as if the path was `real_section....` if( success ) { if( real_section == "dependencies" ) { assert(key_val.path.size() > 3); const auto& depname = key_val.path[3]; // Find/create dependency descriptor auto it = ::std::find_if(rv.m_dependencies.begin(), rv.m_dependencies.end(), [&](const auto& x) { return x.m_name == depname; }); bool was_added = (it == rv.m_dependencies.end()); if( was_added ) { it = rv.m_dependencies.insert(it, PackageRef{ depname }); } it->fill_from_kv(was_added, key_val, 4); } else if( real_section == "dev-dependencies" ) { // TODO: Developemnt (test/bench) deps } else { TODO("Unknown manifest section for target - " << real_section); } } } else if( section == "features" ) { const auto& name = key_val.path[1]; // TODO: Features ::std::vector<::std::string>* list; if(name == "default") { list = &rv.m_default_features; } else { auto tmp = rv.m_features.insert(::std::make_pair(name, ::std::vector<::std::string>())); list = &tmp.first->second; } for(const auto& sv : key_val.value.m_sub_values) { list->push_back( sv.as_string() ); } } else if( section == "workspace" ) { // TODO: Workspaces? } // crates.io metadata else if( section == "badges" ) { } else { // Unknown manifest section ::std::cerr << "WARNING: Unknown manifest section `" + section + "`" << ::std::endl; // TODO: Prevent this from firing multiple times in a row. } } if( rv.m_name == "" ) { throw ::std::runtime_error(format("Manifest file ",path," doesn't specify a package name")); } // Default targets // - If there's no library section, but src/lib.rs exists, add one if( ! ::std::any_of(rv.m_targets.begin(), rv.m_targets.end(), [](const auto& x){ return x.m_type == PackageTarget::Type::Lib; }) ) { // No library, add one pointing to lib.rs if( ::std::ifstream(package_dir / "src" / "lib.rs").good() ) { DEBUG("- Implicit library"); rv.m_targets.push_back(PackageTarget { PackageTarget::Type::Lib }); } } // - If there's no binary section, but src/main.rs exists, add one if( ! ::std::any_of(rv.m_targets.begin(), rv.m_targets.end(), [](const auto& x){ return x.m_type == PackageTarget::Type::Bin; }) ) { // No library, add one pointing to lib.rs if( ::std::ifstream(package_dir / "src" / "main.rs").good() ) { DEBUG("- Implicit binary"); rv.m_targets.push_back(PackageTarget { PackageTarget::Type::Bin }); } } // Default target names for(auto& tgt : rv.m_targets) { if(tgt.m_path == "") { switch(tgt.m_type) { case PackageTarget::Type::Lib: tgt.m_path = "src/lib.rs"; break; case PackageTarget::Type::Bin: if(tgt.m_name == "") { tgt.m_path = "src/main.rs"; } else { // TODO: Error if both exist // TODO: More complex search rules tgt.m_path = ::helpers::path("src") / "bin" / tgt.m_name.c_str() + ".rs"; if( !::std::ifstream(package_dir / tgt.m_path).good() ) tgt.m_path = ::helpers::path("src") / "bin" / tgt.m_name.c_str() / "main.rs"; //if( !::std::ifstream(package_dir / tgt.m_path).good() ) // throw ::std::runtime_error(format("Unable to find source file for ", tgt.m_name, " - ", package_dir / tgt.m_path)); } break; case PackageTarget::Type::Test: case PackageTarget::Type::Bench: // no defaults break; case PackageTarget::Type::Example: TODO("Default/implicit path for examples"); } } if(tgt.m_name == "") { tgt.m_name.reserve(rv.m_name.size()); for(auto c : rv.m_name) tgt.m_name += (c == '-' ? '_' : c); } } // If there's a lib target, add a test target using the same path { auto it = ::std::find_if(rv.m_targets.begin(), rv.m_targets.end(), [&](const auto& t) { return t.m_type == PackageTarget::Type::Lib; }); if( it != rv.m_targets.end() ) { auto path = it->m_path; auto name = it->m_name + "-test"; rv.m_targets.push_back(PackageTarget { PackageTarget::Type::Test }); rv.m_targets.back().m_name = name; rv.m_targets.back().m_path = path; } } for(const auto& dep : rv.m_dependencies) { if( dep.m_optional ) { rv.m_features.insert(::std::make_pair( dep.m_name, ::std::vector<::std::string>() )); } } // Auto-detect the build script if required if( rv.m_build_script == "-" ) { // Explicitly disabled `[package] build = false` rv.m_build_script = ""; } else if( rv.m_build_script == "" ) { // Not set, check for a "build.rs" file if( ::std::ifstream( package_dir / "build.rs").good() ) { DEBUG("- Implicit build.rs"); rv.m_build_script = "build.rs"; } } else { // Explicitly set } return rv; } namespace { void target_edit_from_kv(PackageTarget& target, TomlKeyValue& kv, unsigned base_idx) { const auto& key = kv.path[base_idx]; if(key == "name") { assert(kv.path.size() == base_idx+1); target.m_name = kv.value.as_string(); } else if(key == "path") { assert(kv.path.size() == base_idx + 1); target.m_path = kv.value.as_string(); } else if(key == "test") { assert(kv.path.size() == base_idx + 1); target.m_enable_test = kv.value.as_bool(); } else if( key == "doctest" ) { assert(kv.path.size() == base_idx + 1); target.m_enable_doctest = kv.value.as_bool(); } else if( key == "bench" ) { assert(kv.path.size() == base_idx + 1); target.m_enable_bench = kv.value.as_bool(); } else if( key == "doc" ) { assert(kv.path.size() == base_idx + 1); target.m_enable_doc = kv.value.as_bool(); } else if( key == "plugin" ) { assert(kv.path.size() == base_idx + 1); target.m_is_plugin = kv.value.as_bool(); } else if( key == "proc-macro" ) { assert(kv.path.size() == base_idx + 1); target.m_is_proc_macro = kv.value.as_bool(); } else if( key == "harness" ) { assert(kv.path.size() == base_idx + 1); target.m_is_own_harness = kv.value.as_bool(); } else if( key == "crate-type" ) { //assert_kv_size(kv, base_idx + 1); //assert_type(kv, base_idx + 1); assert(kv.path.size() == base_idx + 1); if( !target.m_crate_types.empty() ) { // TODO: Error, multiple instances } for(const auto& sv : kv.value.m_sub_values) { const auto& s = sv.as_string(); if(s == "rlib") { target.m_crate_types.push_back(PackageTarget::CrateType::rlib); } else if(s == "dylib") { target.m_crate_types.push_back(PackageTarget::CrateType::dylib); } // TODO: Other crate types else { throw ::std::runtime_error(format("Unknown crate type - ", s)); } } } else if( key == "required-features" ) { assert(kv.path.size() == base_idx + 1); for(const auto& sv : kv.value.m_sub_values) { target.m_required_features.push_back( sv.as_string() ); } } else { throw ::std::runtime_error( ::format("TODO: Handle target option `", key, "`") ); } } } void PackageRef::fill_from_kv(bool was_added, const TomlKeyValue& key_val, size_t base_idx) { if( key_val.path.size() == base_idx ) { // Shorthand, picks a version from the package repository if(!was_added) { throw ::std::runtime_error(::format("ERROR: Duplicate dependency `", this->m_name, "`")); } const auto& version_spec_str = key_val.value.as_string(); try { this->m_version = PackageVersionSpec::from_string(version_spec_str); } catch(const ::std::invalid_argument& e) { throw ::std::runtime_error(format("Unable to parse dependency verison for ", this->m_name, " - ", e.what())); } } else { // (part of a) Full dependency specification const auto& attr = key_val.path[base_idx]; if( attr == "path" ) { assert(key_val.path.size() == base_idx+1); // Set path specification of the named depenency this->m_path = key_val.value.as_string(); } else if( attr == "git" ) { // Load from git repo. TODO("Support git dependencies"); } else if( attr == "branch" ) { // Specify git branch TODO("Support git dependencies (branch)"); } else if( attr == "version" ) { assert(key_val.path.size() == base_idx+1); // Parse version specifier try { this->m_version = PackageVersionSpec::from_string(key_val.value.as_string()); } catch(const ::std::invalid_argument& e) { throw ::std::runtime_error(format("Unable to parse dependency verison for '", this->m_name, "' - ", e.what())); } } else if( attr == "optional" ) { assert(key_val.path.size() == base_idx+1); this->m_optional = key_val.value.as_bool(); } else if( attr == "default-features" ) { assert(key_val.path.size() == base_idx+1); this->m_use_default_features = key_val.value.as_bool(); } else if( attr == "features" ) { for(const auto& sv : key_val.value.m_sub_values) { this->m_features.push_back( sv.as_string() ); } } else { // TODO: Error throw ::std::runtime_error(::format("ERROR: Unkown depencency attribute `", attr, "` on dependency `", this->m_name, "`")); } } } bool PackageManifest::has_library() const { auto it = ::std::find_if(m_targets.begin(), m_targets.end(), [](const auto& x) { return x.m_type == PackageTarget::Type::Lib; }); return it != m_targets.end(); } const PackageTarget& PackageManifest::get_library() const { auto it = ::std::find_if(m_targets.begin(), m_targets.end(), [](const auto& x) { return x.m_type == PackageTarget::Type::Lib; }); if (it == m_targets.end()) { throw ::std::runtime_error(::format("Package '", m_name, "' doesn't have a library")); } return *it; } void PackageManifest::set_features(const ::std::vector<::std::string>& features, bool enable_default) { TRACE_FUNCTION_F(m_name << " [" << features << "] " << enable_default); size_t start = m_active_features.size(); // 1. Install default features. if(enable_default) { DEBUG("Including default features [" << m_default_features << "]"); for(const auto& feat : m_default_features) { auto it = ::std::find(m_active_features.begin(), m_active_features.end(), feat); if(it != m_active_features.end()) { continue ; } m_active_features.push_back(feat); } } auto add_feature = [&](const auto& feat) { auto it = ::std::find(m_active_features.begin(), m_active_features.end(), feat); if(it != m_active_features.end()) { DEBUG("`" << feat << "` already active"); } else { m_active_features.push_back(feat); } }; for(const auto& feat : features) { add_feature(feat); } for(size_t i = start; i < m_active_features.size(); i ++) { const auto featname = m_active_features[i]; // Look up this feature auto it = m_features.find(featname); if( it != m_features.end() ) { DEBUG("Activating feature " << featname << " = [" << it->second << "]"); for(const auto& sub_feat : it->second) { auto slash_pos = sub_feat.find('/'); if( slash_pos != ::std::string::npos ) { ::std::string depname = sub_feat.substr(0, slash_pos); ::std::string depfeat = sub_feat.substr(slash_pos+1); DEBUG("Activate feature '" << depfeat << "' from dependency '" << depname << "'"); auto it2 = ::std::find_if(m_dependencies.begin(), m_dependencies.end(), [&](const auto& x){ return x.m_name == depname; }); if(it2 != m_dependencies.end()) { it2->m_features.push_back(depfeat); // TODO: Does this need to be set again? } } else { add_feature(sub_feat); } } } { auto it2 = ::std::find_if(m_dependencies.begin(), m_dependencies.end(), [&](const auto& x){ return x.m_name == featname; }); if(it2 != m_dependencies.end()) { it2->m_optional_enabled = true; } } { auto it2 = ::std::find_if(m_build_dependencies.begin(), m_build_dependencies.end(), [&](const auto& x){ return x.m_name == featname; }); if(it2 != m_build_dependencies.end()) { it2->m_optional_enabled = true; } } { auto it2 = ::std::find_if(m_dev_dependencies.begin(), m_dev_dependencies.end(), [&](const auto& x){ return x.m_name == featname; }); if(it2 != m_dev_dependencies.end()) { it2->m_optional_enabled = true; } } } // Return true if any features were activated //return start < m_active_features.size(); } void PackageManifest::load_dependencies(Repository& repo, bool include_build, bool include_dev) { TRACE_FUNCTION_F(m_name); DEBUG("Loading depencencies for " << m_name); auto base_path = ::helpers::path(m_manifest_path).parent(); // 2. Recursively load dependency manifests for(auto& dep : m_dependencies) { if( dep.m_optional && !dep.m_optional_enabled ) { continue ; } dep.load_manifest(repo, base_path, include_build); } // Load build deps if there's a build script AND build scripts are enabled if( m_build_script != "" && include_build ) { DEBUG("- Build dependencies"); for(auto& dep : m_build_dependencies) { if( dep.m_optional && !dep.m_optional_enabled ) { continue ; } dep.load_manifest(repo, base_path, include_build); } } // Load dev dependencies if the caller has indicated they should be if( include_dev ) { DEBUG("- Dev dependencies"); for(auto& dep : m_dev_dependencies) { if( dep.m_optional && !dep.m_optional_enabled ) { continue ; } dep.load_manifest(repo, base_path, include_build); } } } void PackageManifest::load_build_script(const ::std::string& path) { ::std::ifstream is( path ); if( !is.good() ) throw ::std::runtime_error("Unable to open build script file '" + path + "'"); BuildScriptOutput rv; while( !is.eof() ) { ::std::string line; is >> line; if( line.compare(0, 5+1, "cargo:") == 0 ) { size_t start = 5+1; size_t eq_pos = line.find_first_of('='); ::helpers::string_view key { line.c_str() + start, eq_pos - start }; ::helpers::string_view value { line.c_str() + eq_pos + 1, line.size() - eq_pos - 1 }; if( key == "minicargo-pre-build" ) { rv.pre_build_commands.push_back(value); } // cargo:rustc-link-search=foo/bar/baz else if( key == "rustc-link-search" ) { // Check for an = (otherwise default to native) ::std::string value_str = value; auto pos = value_str.find_first_of('='); const char* type = "native"; ::std::string name; if(pos == ::std::string::npos) { name = ::std::move(value_str); } else { name = value_str.substr(pos+1); auto ty_str = value_str.substr(0, pos); if( ty_str == "native" ) { type = "native"; } else { throw ::std::runtime_error(::format("TODO: rustc-link-search ", ty_str)); } } rv.rustc_link_search.push_back(::std::make_pair(type, ::std::move(name))); } // cargo:rustc-link-lib=mysql else if( key == "rustc-link-lib" ) { // Check for an = (otherwise default to dynamic) ::std::string value_str = value; auto pos = value_str.find_first_of('='); const char* type = "static"; ::std::string name; if(pos == ::std::string::npos) { name = ::std::move(value_str); } else { name = value_str.substr(pos+1); auto ty_str = value_str.substr(0, pos); if( ty_str == "static" ) { type = "static"; } else if( ty_str == "dylib" ) { type = "dynamic"; } else { throw ::std::runtime_error(::format("TODO: rustc-link-lib ", ty_str)); } } rv.rustc_link_lib.push_back(::std::make_pair(type, ::std::move(name))); } // cargo:rustc-cfg=foo else if( key == "rustc-cfg" ) { // TODO: Validate rv.rustc_cfg.push_back( value ); } // cargo:rustc-flags=-l foo else if( key == "rustc-flags" ) { // Split on space, then push each. throw ::std::runtime_error("TODO: rustc-flags"); } // cargo:rustc-env=FOO=BAR else if( key == "rustc-env" ) { rv.rustc_env.push_back( value ); } // cargo:rerun-if-changed=foo.rs else if( key == "rerun-if-changed" ) { DEBUG("TODO: '" << key << "' = '" << value << "'"); } // - Ignore else { if( this->m_links != "" && line.find_first_of('-') == ::std::string::npos ) { ::std::string varname; varname += "DEP_"; for(auto c : this->m_links) varname += ::std::toupper(c); varname += "_"; for(auto c : key) varname += ::std::toupper(c); rv.downstream_env.push_back(::std::make_pair( varname, static_cast<::std::string>(value) )); } else { DEBUG("TODO: '" << key << "' = '" << value << "'"); } } } } m_build_script_output = rv; } void PackageRef::load_manifest(Repository& repo, const ::helpers::path& base_path, bool include_build_deps) { TRACE_FUNCTION_F(this->m_name); if( !m_manifest ) { // If the path isn't set, check for: // - Git (checkout and use) // - Version and repository (check vendored, check cache, download into cache) if( !m_manifest && this->has_git() ) { DEBUG("Load dependency " << this->name() << " from git"); throw "TODO: Git"; } if( !m_manifest && !this->get_version().m_bounds.empty() ) { DEBUG("Load dependency " << this->name() << " from repo"); m_manifest = repo.find(this->name(), this->get_version()); } if( !m_manifest && this->has_path() ) { DEBUG("Load dependency " << m_name << " from path " << m_path); // Search for a copy of this already loaded auto path = base_path / ::helpers::path(m_path) / "Cargo.toml"; if( ::std::ifstream(path.str()).good() ) { try { m_manifest = repo.from_path(path); } catch(const ::std::exception& e) { throw ::std::runtime_error( format("Error loading manifest '", path, "' - ", e.what()) ); } } } if( !m_manifest ) { throw ::std::runtime_error(::format( "Unable to find a manifest for ", this->name(), ":", this->get_version() )); } } m_manifest->set_features(this->m_features, this->m_use_default_features); m_manifest->load_dependencies(repo, include_build_deps); } PackageVersion PackageVersion::from_string(const ::std::string& s) { PackageVersion rv; ::std::istringstream iss { s }; iss >> rv.major; iss.get(); iss >> rv.minor; if( iss.get() != EOF ) { iss >> rv.patch; } return rv; } PackageVersionSpec PackageVersionSpec::from_string(const ::std::string& s) { struct H { static unsigned parse_i(const ::std::string& istr, size_t& pos) { char* out_ptr = nullptr; long rv = ::std::strtol(istr.c_str() + pos, &out_ptr, 10); if( out_ptr == istr.c_str() + pos ) throw ::std::invalid_argument(::format("Failed to parse integer from '", istr.c_str() + pos, "'")); pos = out_ptr - istr.c_str(); return rv; } }; PackageVersionSpec rv; size_t pos = 0; do { while( pos < s.size() && isblank(s[pos]) ) pos ++; if(pos == s.size()) break ; // - Special case for wildcard if( s[pos] == '*' ) { rv.m_bounds.push_back(PackageVersionSpec::Bound { PackageVersionSpec::Bound::Type::GreaterEqual, PackageVersion {} }); while( pos < s.size() && isblank(s[pos]) ) pos ++; if(pos == s.size()) break ; continue ; } auto ty = PackageVersionSpec::Bound::Type::Compatible; switch(s[pos]) { case '^': // Default, compatible pos ++; break; case '~': ty = PackageVersionSpec::Bound::Type::MinorCompatible; pos ++; break; case '=': ty = PackageVersionSpec::Bound::Type::Equal; pos ++; break; case '>': pos ++; switch(s[pos]) { case '=': pos ++; ty = PackageVersionSpec::Bound::Type::GreaterEqual; break; default: ty = PackageVersionSpec::Bound::Type::Greater; break; } break; case '<': pos ++; switch(s[pos]) { case '=': pos ++; ty = PackageVersionSpec::Bound::Type::LessEqual; break; default: ty = PackageVersionSpec::Bound::Type::Less; break; } break; default: break; } while( pos < s.size() && isblank(s[pos]) ) pos ++; if( pos == s.size() ) throw ::std::runtime_error("Bad version string - Expected version number"); PackageVersion v; v.major = H::parse_i(s, pos); if( s[pos] == '.' ) { pos ++; v.minor = H::parse_i(s, pos); if(s[pos] == '.') { pos ++; v.patch = H::parse_i(s, pos); if( pos < s.size() && s[pos] == '-' ) { // Save tag (sequence of dot-seprated alpha-numeric identifiers) auto tag_start = pos+1; do { // Could check the format, but meh. pos ++; } while(pos < s.size() && !isblank(s[pos]) && s[pos] != ',' ); //v.tag = ::std::string(s.c_str() + tag_start, s.c_str() + pos); } } else { v.patch = 0; } } else { // NOTE: This changes the behaviour of ~ rules to be bounded on the major version instead if( ty == PackageVersionSpec::Bound::Type::MinorCompatible ) ty = PackageVersionSpec::Bound::Type::Compatible; v.minor = 0; v.patch = 0; } rv.m_bounds.push_back(PackageVersionSpec::Bound { ty, v }); while( pos < s.size() && isblank(s[pos]) ) pos ++; if(pos == s.size()) break ; } while(pos < s.size() && s[pos++] == ','); if( pos != s.size() ) throw ::std::runtime_error(::format( "Bad version string '", s, "', pos=", pos )); return rv; } bool PackageVersionSpec::accepts(const PackageVersion& v) const { for(const auto& b : m_bounds) { switch(b.ty) { case Bound::Type::Compatible: // ^ rules are >= specified, and < next major/breaking if( !(v >= b.ver) ) return false; if( !(v < b.ver.next_breaking()) ) return false; break; case Bound::Type::MinorCompatible: // ~ rules are >= specified, and < next minor if( !(v >= b.ver) ) return false; if( !(v < b.ver.next_minor()) ) return false; break; case Bound::Type::GreaterEqual: if( !(v >= b.ver) ) return false; break; case Bound::Type::Greater: if( !(v > b.ver) ) return false; break; case Bound::Type::Equal: if( v != b.ver ) return false; break; case Bound::Type::LessEqual: if( !(v <= b.ver) ) return false; break; case Bound::Type::Less: if( !(v < b.ver) ) return false; break; } } return true; }