From cc7216e60cd6893974e687599682c5e6233e9b69 Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Wed, 14 Mar 2018 14:29:43 +0200 Subject: Initial implementation of new command --- bdep/init.cxx | 180 +++++++++++++++++------------- bdep/init.hxx | 21 ++++ bdep/new-types.hxx | 21 +++- bdep/new.cxx | 317 ++++++++++++++++++++++++++++++++++++++++++++++++++++- bdep/project.cli | 30 ++--- bdep/utility.hxx | 10 ++ bdep/utility.txx | 44 ++++++++ 7 files changed, 528 insertions(+), 95 deletions(-) diff --git a/bdep/init.cxx b/bdep/init.cxx index a86079f..f41a62b 100644 --- a/bdep/init.cxx +++ b/bdep/init.cxx @@ -16,6 +16,99 @@ using namespace std; namespace bdep { + shared_ptr + cmd_init_config (const configuration_name_options& o, + const dir_path& prj, + database& db, + const dir_path& cfg, + bool ca, bool cc) + { + const char* m (!ca ? "--config-create" : + !cc ? "--config-add" : + nullptr); + + if (m == nullptr) + fail << "both --config-add and --config-create specified"; + + optional name; + if (size_t n = o.config_name ().size ()) + { + if (n > 1) + fail << "multiple configuration names specified for " << m; + + name = o.config_name ()[0]; + } + + optional id; + if (size_t n = o.config_id ().size ()) + { + if (n > 1) + fail << "multiple configuration ids specified for " << m; + + id = o.config_id ()[0]; + } + + return ca + ? cmd_config_add (prj, + db, + cfg, + move (name), + nullopt /* default */, // @@ TODO: --[no]-default + move (id)) + : nullptr; // @@ TODO: create + } + + void + cmd_init (const common_options& o, + const dir_path& prj, + database& db, + const configurations& cfgs, + const package_locations& pkgs) + { + // We do each configuration in a separate transaction so that our state + // reflects the bpkg configuration as closely as possible. + // + for (const shared_ptr& c: cfgs) + { + transaction t (db.begin ()); + + // Add project repository to the configuration. Note that we don't fetch + // it since sync is going to do it anyway. + // + run_bpkg (o, + "add", + "-d", c->path, + "--type", "dir", + prj); + + for (const package_location& p: pkgs) + { + if (find_if (c->packages.begin (), + c->packages.end (), + [&p] (const package_state& s) + { + return p.name == s.name; + }) != c->packages.end ()) + { + if (verb) + info << "package " << p.name << " is already initialized " + << "in configuration " << *c; + + continue; + } + + c->packages.push_back (package_state {p.name}); + } + + db.update (c); + t.commit (); + + //@@ --no-sync for some reason? + // + cmd_sync (o, prj, c); + } + } + int cmd_init (const cmd_init_options& o, cli::scanner&) { @@ -68,40 +161,14 @@ namespace bdep configurations cfgs; if (ca || cc) { - const char* m (!ca ? "--config-create" : - !cc ? "--config-add" : - nullptr); - - if (m == nullptr) - fail << "both --config-add and --config-create specified"; - - optional name; - if (size_t n = o.config_name ().size ()) - { - if (n > 1) - fail << "multiple configuration names specified for " << m; - - name = o.config_name ()[0]; - } - - optional id; - if (size_t n = o.config_id ().size ()) - { - if (n > 1) - fail << "multiple configuration ids specified for " << m; - - id = o.config_id ()[0]; - } - cfgs.push_back ( - ca - ? cmd_config_add (prj, - db, - o.config_add (), - move (name), - nullopt /* default */, // @@ TODO: --[no]-default - move (id)) - : nullptr); // @@ TODO: create + cmd_init_config ( + o, + prj, + db, + ca ? o.config_add () : o.config_create (), + ca, + cc)); // Fall through. } @@ -116,52 +183,11 @@ namespace bdep t.commit (); } - // Initialize each package in each configuration skipping those that are - // already initialized. Do each configuration in a separate transaction so - // that our state reflects the bpkg configuration as closely as possible. - // Then synchronize each configuration. + // Initialize each package in each configuration. // - for (const shared_ptr& c: cfgs) - { - transaction t (db.begin ()); - - // Add project repository to the configuration. Note that we don't fetch - // it since sync is going to do it anyway. - // - run_bpkg (o, - "add", - "-d", c->path, - "--type", "dir", - prj); - - for (const package_location& p: pp.packages) - { - if (find_if (c->packages.begin (), - c->packages.end (), - [&p] (const package_state& s) - { - return p.name == s.name; - }) != c->packages.end ()) - { - if (verb) - info << "package " << p.name << " is already initialized " - << "in configuration " << *c; - - continue; - } - - c->packages.push_back (package_state {p.name}); - } - - db.update (c); - t.commit (); - - //@@ --no-sync for some reason? - // - cmd_sync (o, prj, c); - } + cmd_init (o, prj, db, cfgs, pp.packages); - //@@ TODO: print project/package(s) being initialized. + //@@ TODO: print project/package(s) being initialized? (analog to new?) return 0; } diff --git a/bdep/init.hxx b/bdep/init.hxx index f9a5181..f3397ff 100644 --- a/bdep/init.hxx +++ b/bdep/init.hxx @@ -8,10 +8,31 @@ #include #include +#include #include namespace bdep { + // Handle --config-create/add. + // + shared_ptr + cmd_init_config (const configuration_name_options&, + const dir_path& prj, + database&, + const dir_path& cfg, + bool config_add_specified, + bool config_create_specified); + + // Initialize each package in each configuration skipping those that are + // already initialized. Then synchronize each configuration. + // + void + cmd_init (const common_options&, + const dir_path& prj, + database&, + const configurations&, + const package_locations&); + int cmd_init (const cmd_init_options&, cli::scanner& args); } diff --git a/bdep/new-types.hxx b/bdep/new-types.hxx index 2612f62..0b2a4c6 100644 --- a/bdep/new-types.hxx +++ b/bdep/new-types.hxx @@ -5,6 +5,8 @@ #ifndef BDEP_NEW_TYPES_HXX #define BDEP_NEW_TYPES_HXX +#include + namespace bdep { // We could have defined cmd_new_*_options in a separate .cli file, include @@ -22,7 +24,7 @@ namespace bdep typename BARE = cmd_new_bare_options> struct cmd_new_type_template { - enum type_type {exe, lib, bare} type; + enum type_type {exe = 0, lib, bare} type; // Note: used as index. operator type_type () const {return type;} @@ -36,6 +38,21 @@ namespace bdep // Default is bare with no options. // cmd_new_type_template (): type (bare) {bare_opt = BARE ();} + + friend ostream& + operator<< (ostream& os, const cmd_new_type_template& t) + { + using type = cmd_new_type_template; + + switch (t) + { + case type::exe: return os << "executable"; + case type::lib: return os << "library"; + case type::bare: return os << "bare"; + } + + return os; + } }; using cmd_new_type = cmd_new_type_template<>; @@ -49,7 +66,7 @@ namespace bdep typename CXX = cmd_new_cxx_options> struct cmd_new_lang_template { - enum lang_type {c, cxx} lang; + enum lang_type {c = 0, cxx} lang; // Note: used as index. operator lang_type () const {return lang;} diff --git a/bdep/new.cxx b/bdep/new.cxx index f2975c8..7d7b9ec 100644 --- a/bdep/new.cxx +++ b/bdep/new.cxx @@ -4,8 +4,12 @@ #include +#include +#include #include +#include + using namespace std; namespace bdep @@ -18,7 +22,318 @@ namespace bdep { tracer trace ("new"); - //@@ TODO: validate options (cpp/cxx, -A/-C, etc). + // Validate type options. + // + const type& t (o.type ()); + + // Validate language options. + // + const lang& l (o.lang ()); + + switch (l) + { + case lang::c: + { + break; + } + case lang::cxx: + { + auto& o (l.cxx_opt); + + if (o.cpp () && o.cxx ()) + fail << "'cxx' and 'cpp' are mutually exclusive c++ options"; + + break; + } + } + + // Validate argument. + // + string n (args.more () ? args.next () : ""); + if (n.empty ()) + fail << "project name argument expected"; + + //@@ TODO: verify valid package name (put the helper in libbpkg). + + if (o.type () == type::lib && n.compare (0, 3, "lib") != 0) + fail << "library name does not start with 'lib'"; + + dir_path prj (n); + prj.complete (); + + // If the directory already exists, make sure it is empty. Otherwise + // create it. + // + if (!exists (prj)) + mk (prj); + else if (!empty (prj)) + fail << "directory " << prj << " already exists"; + + // Initialize the git repository. Do it before writing anything ourselves + // in case it fails. + // + if (!o.no_git ()) + run ("git", "init", "-q", prj); + + path f; // File currently being written. + try + { + ofdstream os; + + // manifest + // + os.open (f = prj / "manifest"); + os << ": 1" << endl + << "name: " << n << endl + << "version: 0.1.0-a.0.z" << endl + << "summary: new " << t << " project" << endl + << "license: proprietary" << endl + << "url: https://example.org/" << n << endl + << "email: you@example.org" << endl + << "depends: * build2 >= 0.7.0-" << endl + << "depends: * bpkg >= 0.7.0-" << endl; + os.close (); + + // build/ + // + dir_path bd (dir_path (prj) /= "build"); + mk (bd); + + // build/bootstrap.build + // + os.open (f = bd / "bootstrap.build"); + os << "project = " << n << endl + << endl + << "using version" << endl + << "using config" << endl + << "using test" << endl + << "using dist" << endl + << "using install" << endl; + os.close (); + + // build/root.build + // + os.open (f = bd / "root.build"); + switch (l) + { + case lang::c: + { + // @@ TODO: 'latest' in c.std. + // + os //<< "c.std = latest" << endl + //<< endl + << "using c" << endl + << endl + << "h{*}: extension = h" << endl + << "c{*}: extension = c" << endl + << endl + << "c.poptions =+ \"-I$out_root\" \"-I$src_root\"" << endl; + break; + } + case lang::cxx: + { + const char* s (l.cxx_opt.cpp () ? "pp" : "xx"); + + os << "cxx.std = latest" << endl + << endl + << "using cxx" << endl + << endl + << "hxx{*}: extension = h" << s << endl + << "ixx{*}: extension = i" << s << endl + << "txx{*}: extension = t" << s << endl + << "cxx{*}: extension = c" << s << endl + << endl + << "cxx.poptions =+ \"-I$out_root\" \"-I$src_root\"" << endl; + break; + } + } + os.close (); + + // build/.gitignore + // + if (!o.no_git ()) + { + os.open (f = bd / ".gitignore"); + os << "config.build" << endl + << "bootstrap/out-root.build" << endl; + os.close (); + } + + // buildfile + // + os.open (f = prj / "buildfile"); + os << "./: {*/ -build/} file{manifest}" << endl; + os.close (); + + // .gitignore + // + if (!o.no_git ()) + { + os.open (f = prj / ".gitignore"); + os << "# Compiler/linker output." << endl + << "#" << endl + << "*.d" << endl + << "*.t" << endl + << "*.i" << endl + << "*.ii" << endl + << "*.o" << endl + << "*.obj" << endl + << "*.so" << endl + << "*.dll" << endl + << "*.a" << endl + << "*.lib" << endl + << "*.exp" << endl + << "*.pdb" << endl + << "*.ilk" << endl + << "*.exe" << endl + << "*.exe.dlls/" << endl + << "*.exe.manifest" << endl + << "*.pc" << endl; + os.close (); + } + + // / (source subdirectory). + // + dir_path sd (dir_path (prj) /= n); + + if (t != type::bare) + { + mk (sd); + os.open (f = sd / "buildfile"); + } + + switch (t) + { + case type::exe: + { + switch (l) + { + case lang::c: + { + // buildfile + // + os << "exe{" << n << "}: {h c}{*}" << endl; + os.close (); + + // .c + // + os.open (f = sd / n + ".c"); + os << "#include " << endl + << endl + << "int main ()" << endl + << "{" << endl + << " printf (\"Hello, World!\\n\");" << endl + << " return 0;" << endl + << "}" << endl; + os.close (); + break; + } + case lang::cxx: + { + // buildfile + // + os << "exe{" << n << "}: {hxx ixx txx cxx}{*}" << endl; + os.close (); + + const char* s (l.cxx_opt.cpp () ? "pp" : "xx"); + + // .c(xx|pp) + // + os.open (f = sd / n + ".c" + s); + os << "#include " << endl + << endl + << "int main ()" << endl + << "{" << endl + << " std::cout << \"Hello, World!\" << std::endl;" << endl + << "}" << endl; + os.close (); + break; + } + } + break; + } + case type::lib: + { + switch (l) + { + case lang::c: + { + // buildfile + // + os << "lib{" << n << "}: {h c}{*}" << endl; + os.close (); + + //@@ TODO + + break; + } + case lang::cxx: + { + // buildfile + // + os << "lib{" << n << "}: {hxx ixx txx cxx}{*}" << endl; + os.close (); + + //@@ TODO + + break; + } + } + break; + } + case type::bare: + { + break; + } + } + } + catch (const io_error& e) + { + fail << "unable to write " << f << ": " << e; + } + + if (verb) + text << "created new " << t << " project " << n << " in " << prj; + + // --no-init + // + bool ca (o.config_add_specified ()); + bool cc (o.config_create_specified ()); + + if (o.no_init ()) + { + if (ca) fail << "both --no-init and --config-add specified"; + if (cc) fail << "both --no-init and --config-create specified"; + + return 0; + } + + // Create .bdep/. + // + { + dir_path d (prj / bdep_dir); + mk (prj / bdep_dir); + } + + // Everything else requires a database. + // + database db (open (prj, trace, true /* create */)); + + if (ca || cc) + { + configurations cfgs { + cmd_init_config ( + o, + prj, + db, + ca ? o.config_add () : o.config_create (), + ca, + cc)}; + + package_locations pkgs {{n, dir_path ()}}; // project == package + + cmd_init (o, prj, db, cfgs, pkgs); + } return 0; } diff --git a/bdep/project.cli b/bdep/project.cli index 7680d99..c3f2e29 100644 --- a/bdep/project.cli +++ b/bdep/project.cli @@ -8,10 +8,16 @@ include ; namespace bdep { - // Common options for commands that accept @. + // Common options for commands that accept --config-id and @. // class configuration_name_options: common_options { + vector --config-id + { + "", + "Specify the build configuration as an id." + }; + // Storage for configuration names specified as @. // // Note that we leave it undocumented so that it's not mentioned in @@ -25,28 +31,22 @@ namespace bdep // class project_options: configuration_name_options { - bool --all|-a - { - "Use all build configurations." - } - - dir_paths --config|-c + dir_paths --directory|-d { "", - "Specify the build configuration to use as a directory." + "Assume project/package is in the specified directory rather than in the + current working directory." } - vector --config-id + bool --all|-a { - "", - "Specify the build configuration to use as an id." - }; + "Use all build configurations." + } - dir_paths --directory|-d + dir_paths --config|-c { "", - "Assume project/package is in the specified directory rather than in the - current working directory." + "Specify the build configuration as a directory." } }; } diff --git a/bdep/utility.hxx b/bdep/utility.hxx index d8a4983..f3e2c73 100644 --- a/bdep/utility.hxx +++ b/bdep/utility.hxx @@ -88,6 +88,16 @@ namespace bdep void rm (const path&, uint16_t verbosity = 3); + // Run a process. + // + template + process + start (I&& in, O&& out, E&& err, const P& prog, A&&... args); + + template + void + run (const P& prog, A&&... args); + // Run the bpkg process. // class common_options; diff --git a/bdep/utility.txx b/bdep/utility.txx index b796f69..9e94196 100644 --- a/bdep/utility.txx +++ b/bdep/utility.txx @@ -12,6 +12,50 @@ namespace bdep { + template + process + start (I&& in, O&& out, E&& err, const P& prog, A&&... args) + { + try + { + return process_start_callback ( + [] (const char* const args[], size_t n) + { + if (verb >= 2) + print_process (args, n); + }, + forward (in), + forward (out), + forward (err), + prog, + forward (args)...); + } + catch (const process_error& e) + { + fail << "unable to execute " << prog << ": " << e << endf; + } + } + + template + void + run (const P& prog, A&&... args) + { + process pr (start (0 /* stdin */, + 1 /* stdout */, + 2 /* stderr */, + prog, + forward (args)...)); + if (!pr.wait ()) + { + const process_exit& e (*pr.exit); + + if (e.normal ()) + throw failed (); // Assume the child issued diagnostics. + + fail << "process " << prog << " " << e; + } + } + // *_bpkg() // template -- cgit v1.1