From f9ebe2d1e920df001be2dd543a63677f8728f53d Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Thu, 8 Mar 2018 16:50:59 +0200 Subject: Handle command line project/packages discovery --- bdep/configuration.cli | 2 +- bdep/diagnostics.cxx | 2 + bdep/diagnostics.hxx | 7 +- bdep/init.cli | 17 +++++ bdep/init.cxx | 14 +++- bdep/project.cxx | 200 +++++++++++++++++++++++++++++++++++++++++++++++++ bdep/project.hxx | 33 ++++++++ bdep/utility.cxx | 8 +- bdep/utility.hxx | 60 ++++++++++++--- bdep/utility.txx | 112 +++++++++++++++++++++++++++ 10 files changed, 436 insertions(+), 19 deletions(-) create mode 100644 bdep/project.cxx create mode 100644 bdep/project.hxx create mode 100644 bdep/utility.txx diff --git a/bdep/configuration.cli b/bdep/configuration.cli index 190845c..e6e911a 100644 --- a/bdep/configuration.cli +++ b/bdep/configuration.cli @@ -16,7 +16,7 @@ namespace bdep { dir_paths --config|-c { - "", + "", "Specify the build configuration to use as a directory." } diff --git a/bdep/diagnostics.cxx b/bdep/diagnostics.cxx index e60ebc8..f596e49 100644 --- a/bdep/diagnostics.cxx +++ b/bdep/diagnostics.cxx @@ -7,6 +7,8 @@ #include #include // operator<<(ostream, process_arg) +#include + using namespace std; using namespace butl; diff --git a/bdep/diagnostics.hxx b/bdep/diagnostics.hxx index 8c3b3d2..366b6c0 100644 --- a/bdep/diagnostics.hxx +++ b/bdep/diagnostics.hxx @@ -7,8 +7,7 @@ #include -#include -#include +#include // Note: not namespace bdep { @@ -157,7 +156,9 @@ namespace bdep epilogue_, type_, name_, - location (forward (f), forward (l), forward (c))); + location (std::forward (f), + std::forward (l), + std::forward (c))); } protected: diff --git a/bdep/init.cli b/bdep/init.cli index 3861b8b..7eeeb6b 100644 --- a/bdep/init.cli +++ b/bdep/init.cli @@ -60,5 +60,22 @@ namespace bdep class cmd_init_options: configuration_options { "\h|INIT OPTIONS|" + + bool --empty|-E + { + "Initialize an empty build configuration set." + } + + dir_path --config-add|-A + { + "", + "Add an existing build configuration ." + } + + dir_path --config-create|-C + { + "", + "Create a new build configuration in ." + } }; } diff --git a/bdep/init.cxx b/bdep/init.cxx index 3e7998f..1e850b8 100644 --- a/bdep/init.cxx +++ b/bdep/init.cxx @@ -4,6 +4,7 @@ #include +#include #include using namespace std; @@ -15,10 +16,17 @@ namespace bdep { tracer trace ("init"); - //@@ TODO: validate project/config options for subcommands. + //@@ TODO: validate project/config options for sub-modes. + //@@ TODO: print project/package(s) being initialized. - for (const string& n: o.config_name ()) - text << n; + project_packages pp ( + find_project_packages (o, + o.empty () /* ignore_packages */)); + + text << pp.project; + + for (const dir_path& d: pp.packages) + text << " " << (pp.project / d); return 0; } diff --git a/bdep/project.cxx b/bdep/project.cxx new file mode 100644 index 0000000..b7c1775 --- /dev/null +++ b/bdep/project.cxx @@ -0,0 +1,200 @@ +// file : bdep/project.cxx -*- C++ -*- +// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#include + +#include + +#include + +using namespace std; + +namespace bdep +{ + // Given a directory which can a project root, a package root, or one of + // their subdirectories, return the absolute project (first) and relative + // package (second) directories. The package directory may be absent if the + // given directory is not within a package root or empty if the project and + // package roots are the same. + // + struct project_package + { + dir_path project; + optional package; + }; + + static project_package + find_project_packages (const dir_path& start) + { + dir_path prj; + optional pkg; + + dir_path d (start); + d.complete (); + d.normalize (); + for (; !d.empty (); d = d.directory ()) + { + // Ignore errors when checking for file existence since we may be + // iterating over directories past any reasonable project boundaries. + // + if (exists (d / manifest_file, true)) + { + if (pkg) + { + fail << "multiple package manifests between " << start + << " and project root" << + info << "first manifest is in " << *pkg << + info << "second manifest is in " << d; + } + + pkg = d; + + // Fall through (can also be the project root). + } + + // Check for configurations.manifest first since a simple project will + // have no packages.manifest + // + if (exists (d / configurations_file, true) || + exists (d / packages_file, true)) + { + prj = move (d); + break; + } + } + + if (prj.empty ()) + { + if (!pkg) + fail << start << " is no a (sub)directory of a package or project"; + + // Project and package are the same. + // + prj = move (*pkg); + pkg = dir_path (); + } + else if (pkg) + pkg = pkg->leaf (prj); + + return project_package {move (prj), move (pkg)}; + } + + project_packages + find_project_packages (const project_options& po, bool ignore_packages) + { + project_packages r; + + if (po.directory_specified ()) + { + for (const dir_path& d: po.directory ()) + { + project_package p (find_project_packages (d)); + + // We only work on one project at a time. + // + if (r.project.empty ()) + { + r.project = move (p.project); + } + else if (r.project != p.project) + { + fail << "multiple project directories specified" << + info << r.project << + info << p.project; + } + + if (!ignore_packages && p.package) + { + // Suppress duplicate packages. + // + if (find (r.packages.begin (), + r.packages.end (), + *p.package) == r.packages.end ()) + { + r.packages.push_back (move (*p.package)); + } + } + } + } + else + { + project_package p (find_project_packages (path::current_directory ())); + + r.project = move (p.project); + + if (!ignore_packages && p.package) + { + r.packages.push_back (move (*p.package)); + } + } + + if (!ignore_packages) + { + // If exists, load packages.manifest from the project root and either + // verify that the discovered packages are in it or, if nothing was + // discovered, use it as the source for the package list. + // + path f (r.project / packages_file); + + if (exists (f)) + { + using bpkg::package_manifest; + using bpkg::dir_package_manifests; + + auto ms (parse_manifest (f, "packages")); + + // While an empty repository is legal, in our case it doesn't make + // much sense and will just further complicate things. + // + if (ms.empty ()) + fail << "no packages listed in " << f; + + // Convert the package location from POSIX to the host form and make + // sure the current directory is represented as an empty path. + // + auto location = [] (const package_manifest& m) + { + assert (m.location); + dir_path d (path_cast (*m.location)); + d.normalize (false /* actualize */, true /* cur_empty */); + return d; + }; + + if (r.packages.empty ()) + { + for (package_manifest& m: ms) + r.packages.push_back (location (m)); + } + else + { + // It could be costly to normalize the location for each + // comparison. We, however, do not expect more than a handful of + // packages so we are probably ok. + // + for (const dir_path& pd: r.packages) + { + if (find_if (ms.begin (), + ms.end (), + [&pd, &location] (const package_manifest& m) + { + return pd == location (m); + }) == ms.end ()) + { + fail << "package directory " << pd << " not listed in " << f; + } + } + } + } + else + { + // If packages.manifest does not exist, then this must be a simple + // project. + // + assert (r.packages.size () == 1 && r.packages[0].empty ()); + } + } + + return r; + } +} diff --git a/bdep/project.hxx b/bdep/project.hxx new file mode 100644 index 0000000..6a748d5 --- /dev/null +++ b/bdep/project.hxx @@ -0,0 +1,33 @@ +// file : bdep/project.hxx -*- C++ -*- +// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef BDEP_PROJECT_HXX +#define BDEP_PROJECT_HXX + +#include +#include + +#include + +namespace bdep +{ + // Given project_options (and CWD) locate the packages and their project. + // The result is the absolute and normalized project directory and a vector + // of relative (to the project directory) package directories (which will be + // empty if ignore_packages is true). + // + // Note that if the package directory is the same as project, then the + // package directory will be empty (and not ./). + // + struct project_packages + { + dir_path project; + dir_paths packages; + }; + + project_packages + find_project_packages (const project_options&, bool ignore_packages = false); +} + +#endif // BDEP_PROJECT_HXX diff --git a/bdep/utility.cxx b/bdep/utility.cxx index cb005a0..edfdc41 100644 --- a/bdep/utility.cxx +++ b/bdep/utility.cxx @@ -16,10 +16,14 @@ using namespace butl; namespace bdep { - const string empty_string; - const path empty_path; + const string empty_string; + const path empty_path; const dir_path empty_dir_path; + const path manifest_file ("manifest"); + const path packages_file ("packages.manifest"); + const path configurations_file ("configurations.manifest"); + bool exists (const path& f, bool ignore_error) { diff --git a/bdep/utility.hxx b/bdep/utility.hxx index 48f168f..8bca172 100644 --- a/bdep/utility.hxx +++ b/bdep/utility.hxx @@ -5,11 +5,12 @@ #ifndef BDEP_UTILITY_HXX #define BDEP_UTILITY_HXX -#include // make_shared() -#include // to_string() -#include // move(), forward(), declval(), make_pair() -#include // assert() -#include // make_move_iterator() +#include // make_shared() +#include // to_string() +#include // move(), forward(), declval(), make_pair() +#include // assert() +#include // make_move_iterator() +#include // find(), find_if() #include @@ -31,6 +32,9 @@ namespace bdep using std::make_move_iterator; using std::to_string; + using std::find; + using std::find_if; + // // using butl::casecmp; @@ -46,10 +50,21 @@ namespace bdep // Empty string and path. // - extern const string empty_string; - extern const path empty_path; + extern const string empty_string; + extern const path empty_path; extern const dir_path empty_dir_path; + // Widely-used paths. + // + extern const path manifest_file; // manifest + extern const path packages_file; // packages.manifest + extern const path configurations_file; // configurations.manifest + + // Directory extracted from argv[0] (i.e., this process' recall directory) + // or empty if there is none. Can be used as a search fallback. + // + extern dir_path exec_dir; + // Filesystem. // bool @@ -70,10 +85,35 @@ namespace bdep void rm (const path&, uint16_t verbosity = 3); - // Directory extracted from argv[0] (i.e., this process' recall directory) - // or empty if there is none. Can be used as a search fallback. + // Manifest parsing and serialization. // - extern dir_path exec_dir; + // For parsing, if path is '-', then read from stdin. + // + template + T + parse_manifest (const path&, + const char* what, + bool ignore_unknown = false); + + template + T + parse_manifest (istream&, + const string& name, + const char* what, + bool ignore_unknown = false); + + template + void + serialize_manifest (const T&, const path&, const char* what); + + template + void + serialize_manifest (const T&, + ostream&, + const string& name, + const char* what); } +#include + #endif // BDEP_UTILITY_HXX diff --git a/bdep/utility.txx b/bdep/utility.txx new file mode 100644 index 0000000..38f0e35 --- /dev/null +++ b/bdep/utility.txx @@ -0,0 +1,112 @@ +// file : bdep/utility.txx -*- C++ -*- +// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#include // cin + +#include + +#include +#include + +#include + +namespace bdep +{ + // *_manifest() + // + template + T + parse_manifest (const path& f, const char* what, bool iu) + { + using namespace butl; + + try + { + if (f.string () == "-") + return parse_manifest (std::cin, "stdin", what, iu); + + if (!file_exists (f)) + fail << what << " manifest file " << f << " does not exist"; + + ifdstream ifs (f); + return parse_manifest (ifs, f.string (), what, iu); + } + catch (const system_error& e) // EACCES, etc. + { + fail << "unable to access " << what << " manifest " << f << ": " << e + << endf; + } + } + + template + T + parse_manifest (istream& is, const string& name, const char* what, bool iu) + { + using namespace butl; + + try + { + manifest_parser p (is, name); + return T (p, iu); + } + catch (const manifest_parsing& e) + { + fail << "invalid " << what << " manifest: " << name << ':' + << e.line << ':' << e.column << ": " << e.description << endf; + } + catch (const io_error& e) + { + fail << "unable to read " << what << " manifest " << name << ": " << e + << endf; + } + } + + template + void + serialize_manifest (const T& m, const path& f, const char* what) + { + using namespace std; + using namespace butl; + + try + { + ofdstream ofs (f, ios::binary); + auto_rmfile arm (f); // Try to remove on failure ignoring errors. + + serialize_manifest (m, ofs, f.string (), what); + + ofs.close (); + arm.cancel (); + } + catch (const system_error& e) // EACCES, etc. + { + fail << "unable to access " << what << " manifest " << f << ": " << e; + } + } + + template + void + serialize_manifest (const T& m, + ostream& os, + const string& name, + const char* what) + { + using namespace butl; + + try + { + manifest_serializer s (os, name); + m.serialize (s); + return; + } + catch (const manifest_serialization& e) + { + fail << "invalid " << what << " manifest: " << e.description; + } + catch (const io_error& e) + { + fail << "unable to write " << what << " manifest " << name << ": " << e; + } + } +} -- cgit v1.1