From 99fe901f2148b3f9ba6569fa92c1c88deef1dff5 Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Thu, 18 Oct 2018 11:27:13 +0200 Subject: Add binless option to bdep-new c++ language parameter This allows creation of a simpler buildfile for header-only (and, in the future, for module-only) libraries. --- bdep/new.cli | 5 +- bdep/new.cxx | 281 +++++++++++++++++++++++++++++++++------------------ tests/new.testscript | 24 +++++ 3 files changed, 209 insertions(+), 101 deletions(-) diff --git a/bdep/new.cli b/bdep/new.cli index 688f14c..5026d74 100644 --- a/bdep/new.cli +++ b/bdep/new.cli @@ -121,7 +121,9 @@ namespace bdep \cb{.mxx} source file extensions (default). \cb{cpp} \- Use the \cb{.cpp}, \cb{.hpp}, \cb{.ipp}, \cb{.tpp}, and - \cb{.mpp} source file extensions.|| + \cb{.mpp} source file extensions. + + \cb{binless} \- Create a header-only library.|| The project version control system can be specified with the \c{\b{--vcs}|\b{-s}} option. Valid values for this option and their @@ -174,6 +176,7 @@ namespace bdep { bool cpp; bool cxx; + bool binless; }; // --vcs options diff --git a/bdep/new.cxx b/bdep/new.cxx index cff0d48..e97f0fb 100644 --- a/bdep/new.cxx +++ b/bdep/new.cxx @@ -82,6 +82,9 @@ namespace bdep if (o.cpp () && o.cxx ()) fail << "'cxx' and 'cpp' are mutually exclusive c++ options"; + if (o.binless () && t != type::lib) + fail << "'binless' is only valid for libraries"; + break; } } @@ -415,15 +418,17 @@ namespace bdep // os.open (f = bd / "root.build"); - const char* x (nullptr); // Language module/source target type. - const char* h (nullptr); // Header target type. - const char* hs (nullptr); // All header target types. - string es; // Source file extension suffix (pp, xx). + string m; // Language module. + string x; // Source target type. + string h; // Header target type. + string hs; // All header target types. + string es; // Source file extension suffix (pp, xx). switch (l) { case lang::c: { + m = "c"; x = "c"; h = "h"; hs = "h"; @@ -440,6 +445,7 @@ namespace bdep } case lang::cxx: { + m = "cxx"; x = "cxx"; h = "hxx"; hs = "hxx ixx txx"; @@ -457,11 +463,11 @@ namespace bdep } } - if ((itest || utest) && x != nullptr) + if ((itest || utest) && !m.empty ()) os << endl << "# The test target for cross-testing (running tests under Wine, etc)." << endl << "#" << endl - << "test.target = $" << x << ".target" << endl; + << "test.target = $" << m << ".target" << endl; os.close (); @@ -584,7 +590,7 @@ namespace bdep << "}" << endl; os << endl - << x << ".poptions =+ \"-I$out_root\" \"-I$src_root\"" << endl; + << m << ".poptions =+ \"-I$out_root\" \"-I$src_root\"" << endl; os.close (); // /.gitignore @@ -668,16 +674,16 @@ namespace bdep } case type::lib: { - string m; // Macro prefix. + string mp; // Macro prefix. transform ( - b.begin (), b.end (), back_inserter (m), + b.begin (), b.end (), back_inserter (mp), [] (char c) { return (c == '-' || c == '+' || c == '.') ? '_' : ucase (c); }); string hdr; // API header name. - string exp; // Export header name. + string exp; // Export header name (empty if binless). string ver; // Version header name. switch (l) @@ -701,7 +707,7 @@ namespace bdep << "// stream. On success, return the number of character printed." << endl << "// On failure, set errno and return a negative value." << endl << "//" << endl - << m << "_SYMEXPORT int" << endl + << mp << "_SYMEXPORT int" << endl << "say_hello (FILE *, const char *name);" << endl; os.close (); @@ -727,6 +733,41 @@ namespace bdep break; } case lang::cxx: + if (l.cxx_opt.binless ()) + { + hdr = s + ".h" + es; + ver = "version.h" + es; + + // .h(xx|pp) + // + os.open (f = sd / hdr); + os << "#pragma once" << endl + << endl + << "#include " << endl + << "#include " << endl + << "#include " << endl + << endl + << "namespace " << id << endl + << "{" << endl + << " // Print a greeting for the specified name into the specified" << endl + << " // stream. Throw std::invalid_argument if the name is empty." << endl + << " //" << endl + << " inline void" << endl + << " say_hello (std::ostream& o, const std::string& name)" << endl + << " {" << endl + << " using namespace std;" << endl + << endl + << " if (name.empty ())" << endl + << " throw invalid_argument (\"empty name\");" << endl + << endl + << " o << \"Hello, \" << name << '!' << endl;" << endl + << " }" << endl + << "}" << endl; + os.close (); + + break; + } + else { hdr = s + ".h" + es; exp = "export.h" + es; @@ -747,9 +788,8 @@ namespace bdep << " // Print a greeting for the specified name into the specified" << endl << " // stream. Throw std::invalid_argument if the name is empty." << endl << " //" << endl - << " " << m << "_SYMEXPORT void" << endl - << " say_hello (std::ostream&, " << - "const std::string& name);" << endl + << " " << mp << "_SYMEXPORT void" << endl + << " say_hello (std::ostream&, const std::string& name);" << endl << "}" << endl; os.close (); @@ -781,45 +821,48 @@ namespace bdep // export.h[??] // - os.open (f = sd / exp); - os << "#pragma once" << endl - << endl; - if (l == lang::cxx) + if (!exp.empty ()) { - os << "// Normally we don't export class templates (but do complete specializations)," << endl - << "// inline functions, and classes with only inline member functions. Exporting" << endl - << "// classes that inherit from non-exported/imported bases (e.g., std::string)" << endl - << "// will end up badly. The only known workarounds are to not inherit or to not" << endl - << "// export. Also, MinGW GCC doesn't like seeing non-exported functions being" << endl - << "// used before their inline definition. The workaround is to reorder code. In" << endl - << "// the end it's all trial and error." << endl - << endl; + os.open (f = sd / exp); + os << "#pragma once" << endl + << endl; + if (l == lang::cxx) + { + os << "// Normally we don't export class templates (but do complete specializations)," << endl + << "// inline functions, and classes with only inline member functions. Exporting" << endl + << "// classes that inherit from non-exported/imported bases (e.g., std::string)" << endl + << "// will end up badly. The only known workarounds are to not inherit or to not" << endl + << "// export. Also, MinGW GCC doesn't like seeing non-exported functions being" << endl + << "// used before their inline definition. The workaround is to reorder code. In" << endl + << "// the end it's all trial and error." << endl + << endl; + } + os << "#if defined(" << mp << "_STATIC) // Using static." << endl + << "# define " << mp << "_SYMEXPORT" << endl + << "#elif defined(" << mp << "_STATIC_BUILD) // Building static." << endl + << "# define " << mp << "_SYMEXPORT" << endl + << "#elif defined(" << mp << "_SHARED) // Using shared." << endl + << "# ifdef _WIN32" << endl + << "# define " << mp << "_SYMEXPORT __declspec(dllimport)" << endl + << "# else" << endl + << "# define " << mp << "_SYMEXPORT" << endl + << "# endif" << endl + << "#elif defined(" << mp << "_SHARED_BUILD) // Building shared." << endl + << "# ifdef _WIN32" << endl + << "# define " << mp << "_SYMEXPORT __declspec(dllexport)" << endl + << "# else" << endl + << "# define " << mp << "_SYMEXPORT" << endl + << "# endif" << endl + << "#else" << endl + << "// If none of the above macros are defined, then we assume we are being used" << endl + << "// by some third-party build system that cannot/doesn't signal the library" << endl + << "// type. Note that this fallback works for both static and shared but in case" << endl + << "// of shared will be sub-optimal compared to having dllimport." << endl + << "//" << endl + << "# define " << mp << "_SYMEXPORT // Using static or shared." << endl + << "#endif" << endl; + os.close (); } - os << "#if defined(" << m << "_STATIC) // Using static." << endl - << "# define " << m << "_SYMEXPORT" << endl - << "#elif defined(" << m << "_STATIC_BUILD) // Building static." << endl - << "# define " << m << "_SYMEXPORT" << endl - << "#elif defined(" << m << "_SHARED) // Using shared." << endl - << "# ifdef _WIN32" << endl - << "# define " << m << "_SYMEXPORT __declspec(dllimport)" << endl - << "# else" << endl - << "# define " << m << "_SYMEXPORT" << endl - << "# endif" << endl - << "#elif defined(" << m << "_SHARED_BUILD) // Building shared." << endl - << "# ifdef _WIN32" << endl - << "# define " << m << "_SYMEXPORT __declspec(dllexport)" << endl - << "# else" << endl - << "# define " << m << "_SYMEXPORT" << endl - << "# endif" << endl - << "#else" << endl - << "// If none of the above macros are defined, then we assume we are being used" << endl - << "// by some third-party build system that cannot/doesn't signal the library" << endl - << "// type. Note that this fallback works for both static and shared but in case" << endl - << "// of shared will be sub-optimal compared to having dllimport." << endl - << "//" << endl - << "# define " << m << "_SYMEXPORT // Using static or shared." << endl - << "#endif" << endl; - os.close (); // version.h[??].in // @@ -846,20 +889,22 @@ namespace bdep << "// 3.0.0-b.2 0029999995020" << endl << "// 2.2.0-a.1.z 0020019990011" << endl << "//" << endl - << "#define " << m << "_VERSION $" << v << ".version.project_number$ULL" << endl - << "#define " << m << "_VERSION_STR \"$" << v << ".version.project$\"" << endl - << "#define " << m << "_VERSION_ID \"$" << v << ".version.project_id$\"" << endl + << "#define " << mp << "_VERSION $" << v << ".version.project_number$ULL" << endl + << "#define " << mp << "_VERSION_STR \"$" << v << ".version.project$\"" << endl + << "#define " << mp << "_VERSION_ID \"$" << v << ".version.project_id$\"" << endl << endl - << "#define " << m << "_VERSION_MAJOR $" << v << ".version.major$" << endl - << "#define " << m << "_VERSION_MINOR $" << v << ".version.minor$" << endl - << "#define " << m << "_VERSION_PATCH $" << v << ".version.patch$" << endl + << "#define " << mp << "_VERSION_MAJOR $" << v << ".version.major$" << endl + << "#define " << mp << "_VERSION_MINOR $" << v << ".version.minor$" << endl + << "#define " << mp << "_VERSION_PATCH $" << v << ".version.patch$" << endl << endl - << "#define " << m << "_PRE_RELEASE $" << v << ".version.pre_release$" << endl + << "#define " << mp << "_PRE_RELEASE $" << v << ".version.pre_release$" << endl << endl - << "#define " << m << "_SNAPSHOT_SN $" << v << ".version.snapshot_sn$ULL" << endl - << "#define " << m << "_SNAPSHOT_ID \"$" << v << ".version.snapshot_id$\"" << endl; + << "#define " << mp << "_SNAPSHOT_SN $" << v << ".version.snapshot_sn$ULL" << endl + << "#define " << mp << "_SNAPSHOT_ID \"$" << v << ".version.snapshot_id$\"" << endl; os.close (); + bool binless (l == lang::cxx && l.cxx_opt.binless ()); + // buildfile // os.open (f = sd / "buildfile"); @@ -869,17 +914,33 @@ namespace bdep << endl; if (!utest) - os << "lib{" << s << "}: " << - "{" << hs << ' ' << x << "}{** -version} " << - h << "{version} $imp_libs $int_libs" << endl; + { + os << "lib{" << s << "}: " << + "{" << hs << (binless ? "" : ' ' + x) << "}" << + "{** -version} " << + h << "{version} $imp_libs $int_libs" << endl; + } else - os << "./: lib{" << s << "}" << endl - << "lib{" << s << "}: libul{" << s << "}" << endl - << "libul{" << s << "}: " << - "{" << hs << ' ' << x << "}{** -version -**.test...} " << - h << "{version} \\" << endl - << " $imp_libs $int_libs" << endl - << endl + { + os << "./: lib{" << s << "}" << endl; + + if (binless) + { + os << "lib{" << s << "}: " << + "{" << hs << "}{** -version -**.test...} " << + h << "{version} \\" << endl + << " $imp_libs $int_libs" << endl; + } + else + { + os << "lib{" << s << "}: libul{" << s << "}" << endl + << "libul{" << s << "}: " << + "{" << hs << ' ' << x << "}{** -version -**.test...} " << + h << "{version} \\" << endl + << " $imp_libs $int_libs" << endl; + } + + os << endl << "# Unit tests." << endl << "#" << endl << "exe{*.test}: test = true" << endl @@ -891,10 +952,16 @@ namespace bdep << " n = $name($t)..." << endl << endl << " ./: $d/exe{$n}" << endl - << " $d/exe{$n}: $t $d/{" << hs << - "}{+$n} $d/testscript{+$n}" << endl - << " $d/exe{$n}: libul{" << s << "}: bin.whole = false"<< endl - << "}" << endl; + << " $d/exe{$n}: $t $d/{" << hs << "}{+$n} $d/testscript{+$n}"; + + if (binless) + os << ' ' << "lib{" << s << "}" << endl; + else + os << '\n' + << " $d/exe{$n}: libul{" << s << "}: bin.whole = false" << endl; + + os << "}" << endl; + } os << endl << "# Include the generated version header into the distribution (so that we don't" << endl @@ -903,34 +970,48 @@ namespace bdep << "#" << endl << h << "{version}: in{version} $src_root/manifest" << endl << h << "{version}: dist = true" << endl - << h << "{version}: clean = ($src_root != $out_root)" << endl - << endl - << x << ".poptions =+ \"-I$out_root\" \"-I$src_root\"" << endl - << endl - << "obja{*}: " << x << ".poptions += -D" << m << "_STATIC_BUILD" << endl - << "objs{*}: " << x << ".poptions += -D" << m << "_SHARED_BUILD" << endl - << endl - << "lib{" << s << "}: " << x << ".export.poptions = \"-I$out_root\" \"-I$src_root\"" << endl - << endl - << "liba{" << s << "}: " << x << ".export.poptions += -D" << m << "_STATIC" << endl - << "libs{" << s << "}: " << x << ".export.poptions += -D" << m << "_SHARED" << endl - << endl - << "lib{" << s << "}: " << x << ".export.libs = $int_libs" << endl - << endl - << "# For pre-releases use the complete version to make sure they cannot be used" << endl - << "# in place of another pre-release or the final version. See the version module" << endl - << "# for details on the version.* variable values." << endl - << "#" << endl - << "if $version.pre_release" << endl - << " lib{" << s << "}: bin.lib.version = @\"-$version.project_id\"" << endl - << "else" << endl - << " lib{" << s << "}: bin.lib.version = @\"-$version.major.$version.minor\"" << endl - << endl + << h << "{version}: clean = ($src_root != $out_root)" << endl; + + // Build. + // + os << endl + << m << ".poptions =+ \"-I$out_root\" \"-I$src_root\"" << endl; + + if (!binless) + os << endl + << "obja{*}: " << m << ".poptions += -D" << mp << "_STATIC_BUILD" << endl + << "objs{*}: " << m << ".poptions += -D" << mp << "_SHARED_BUILD" << endl; + + // Export. + // + os << endl + << "lib{" << s << "}: " << m << ".export.poptions = \"-I$out_root\" \"-I$src_root\"" << endl; + + if (!binless) + os << endl + << "liba{" << s << "}: " << m << ".export.poptions += -D" << mp << "_STATIC" << endl + << "libs{" << s << "}: " << m << ".export.poptions += -D" << mp << "_SHARED" << endl; + + os << endl + << "lib{" << s << "}: " << m << ".export.libs = $int_libs" << endl; + + if (!binless) + os << endl + << "# For pre-releases use the complete version to make sure they cannot be used" << endl + << "# in place of another pre-release or the final version. See the version module" << endl + << "# for details on the version.* variable values." << endl + << "#" << endl + << "if $version.pre_release" << endl + << " lib{" << s << "}: bin.lib.version = @\"-$version.project_id\"" << endl + << "else" << endl + << " lib{" << s << "}: bin.lib.version = @\"-$version.major.$version.minor\"" << endl; + + os << endl << "# Install into the " << b << "/ subdirectory of, say, /usr/include/" << endl << "# recreating subdirectories." << endl << "#" << endl << "{" << hs << "}{*}: install = include/" << b << "/" << endl - << "{" << hs << "}{*}: install.subdirs = true" << endl; + << "{" << hs << "}{*}: install.subdirs = true" << endl; os.close (); // /.gitignore @@ -1067,7 +1148,7 @@ namespace bdep << endl << "# The test target for cross-testing (running tests under Wine, etc)." << endl << "#" << endl - << "test.target = $" << x << ".target" << endl; + << "test.target = $" << m << ".target" << endl; os.close (); // tests/build/.gitignore @@ -1202,7 +1283,7 @@ namespace bdep << "exe{driver}: {" << hs << ' ' << x << "}{**} $libs testscript{**}" << endl; // << endl - // << x << ".poptions =+ \"-I$out_root\" \"-I$src_root\"" << endl; + // << m << ".poptions =+ \"-I$out_root\" \"-I$src_root\"" << endl; os.close (); break; diff --git a/tests/new.testscript b/tests/new.testscript index abda94b..f114ce8 100644 --- a/tests/new.testscript +++ b/tests/new.testscript @@ -68,6 +68,30 @@ status += -d prj EOE } + : lib-binless + : + { + $* -t lib -l c++,binless libfoo 2>>/"EOE" &libfoo/***; + created new library project libfoo in $~/libfoo/ + EOE + + $build libfoo/ $cxx 2>>~%EOE% + %(version\.in|c\+\+|ld) .+%{3} + EOE + } + + : lib-binless-unit-tests + : + { + $* -t lib,unit-tests -l c++,binless libfoo 2>>/"EOE" &libfoo/***; + created new library project libfoo in $~/libfoo/ + EOE + + $build libfoo/ $cxx 2>>~%EOE% + %(version\.in|c\+\+|ld) .+%{5} + EOE + } + # C versions of the above. # : exe-c -- cgit v1.1