diff options
author | Roland Reichwein <mail@reichwein.it> | 2024-05-10 21:15:30 +0200 |
---|---|---|
committer | Roland Reichwein <mail@reichwein.it> | 2024-05-10 21:15:30 +0200 |
commit | a7e016c2c633667b561a0f26ebde88cb26571d1c (patch) | |
tree | 7c66727de7022d5d962448be365dd059bb0ecac1 | |
parent | bfdb4e9d2cfc7890c5f194e670039fa76c391330 (diff) |
Build static and dynamic libs
-rw-r--r-- | Builder.cpp | 2 | ||||
-rw-r--r-- | LanguageSettings.cpp | 95 | ||||
-rw-r--r-- | LanguageSettings.h | 3 | ||||
-rw-r--r-- | SONAME.cpp | 46 | ||||
-rw-r--r-- | SONAME.h | 18 | ||||
-rw-r--r-- | file.cpp | 63 | ||||
-rw-r--r-- | file.h | 5 | ||||
-rw-r--r-- | test-ymake.cpp | 172 |
8 files changed, 358 insertions, 46 deletions
diff --git a/Builder.cpp b/Builder.cpp index e74809b..cc756f2 100644 --- a/Builder.cpp +++ b/Builder.cpp @@ -315,7 +315,7 @@ void Builder::build_file(const fs::path& p) { if (p.extension() == ".o") { // compile fs::path source_file{get_compile_unit_source_from_object(p)}; - command = _lang.getCompileCommand(p, source_file, include_paths_of_object(p)); + command = _lang.getCompileCommand(p, source_file, target_from_object(p), include_paths_of_object(p)); } else { // link std::vector<fs::path> objects{dependencies_of(p)}; diff --git a/LanguageSettings.cpp b/LanguageSettings.cpp index 1d243d5..8da144f 100644 --- a/LanguageSettings.cpp +++ b/LanguageSettings.cpp @@ -12,45 +12,23 @@ #include <libreichwein/stringhelper.h> +#include "env.h" +#include "file.h" + namespace fs = std::filesystem; using namespace std::string_literals; -namespace { - std::string env_value(const std::string& key) { - char* value_ptr{std::getenv(key.c_str())}; - if (value_ptr == nullptr) { - return {}; - } else { - return value_ptr; - } - } - - // returns "" if not found - std::string find_executable(const std::vector<std::string>& list) { - std::vector<std::string> paths {Reichwein::Stringhelper::split(env_value("PATH"), ":")}; - - for (const auto& i: list) { - for (const auto& j: paths) { - if (fs::exists(j + "/" + i)) { - return i; - } - } - } - - return {}; - } - -} - LanguageSettings::LanguageSettings(): // C++ CXX{env_value("CXX")}, CXXFLAGS{env_value("CXXFLAGS")}, LDFLAGS{env_value("LDFLAGS")}, LDLIBS{env_value("LDLIBS")}, - LIBS{env_value("LIBS")} + LIBS{env_value("LIBS")}, // C // CFLAGS + + AR{env_value("AR")} { std::string _default_compiler(find_executable({"g++", "clang++"})); @@ -64,21 +42,29 @@ LanguageSettings::LanguageSettings(): } if (CXXFLAGS.empty()) { - CXXFLAGS = "-std=c++17"; + CXXFLAGS = "-std=c++17 -Wall -O2"; + } + + if (AR.empty()) { + AR = "ar"; } } std::string LanguageSettings::getCompileCommand(const std::filesystem::path& target, - const std::filesystem::path &source, + const std::filesystem::path& source, + const std::filesystem::path& build, const std::vector<std::filesystem::path>& includepaths) const { std::string includes{std::accumulate(includepaths.begin(), includepaths.end(), std::string{}, [](const std::string& sum, const fs::path& p){ return sum + " -I" + p.string();})}; + std::string CXXFLAGS_add{is_dynamic_lib(build) ? " -fvisibility=hidden -fPIC"s : ""s}; + // compile: $(CXX) $(CXXFLAGS) -c $< -o $@ - return fmt::format("{}{}{} -c {} -o {}", + return fmt::format("{}{}{}{} -c {} -o {}", CXX, CXXFLAGS.empty() ? ""s : (" "s + CXXFLAGS), + CXXFLAGS_add, includes, source.string(), target.string()); @@ -88,22 +74,36 @@ std::string LanguageSettings::getLinkCommand(const std::filesystem::path& target const std::vector<std::filesystem::path> &inputs, const std::vector<std::string>& link_libs) const { - // link: $(CXX) $(LDFLAGS) $^ $(LDLIBS) $(LIBS) -o $@ std::string input_string{std::accumulate(inputs.begin(), inputs.end(), std::string{}, [](const std::string& sum, const fs::path& p){ return sum + " " + p.string(); })}; - std::string link_libs_string{std::accumulate(link_libs.begin(), link_libs.end(), std::string(), - [](const std::string& sum, const std::string& i){ return sum + " -l" + i; })}; - - return fmt::format("{}{}{}{}{}{} -o {}", - CXX, - CXXFLAGS.empty() ? ""s : (" "s + CXXFLAGS), - input_string, - LDLIBS.empty() ? ""s : (" "s + LDLIBS), - LIBS.empty() ? ""s : (" "s + LIBS), - link_libs_string, - target.string()); + if (is_static_lib(target)) { + // link: $(AR) rcs libxyz.a x.o y.o z.o + return fmt::format("{} rcs {}{}", + AR, + target.string(), + input_string); + } else { + // dynamic link: -shared -Wl,-soname,libXXX.so.N -o libXXX.so.N.M.O + std::string LDFLAGS_add{is_dynamic_lib(target) ? fmt::format(" -shared -Wl,-soname,{} -o {}", + soname_shorter(target.string()).string(), + target.string()) : ""s}; + + std::string link_libs_string{std::accumulate(link_libs.begin(), link_libs.end(), std::string(), + [](const std::string& sum, const std::string& i){ return sum + " -l" + i; })}; + + // link: $(CXX) $(LDFLAGS) $^ $(LDLIBS) $(LIBS) -o $@ + return fmt::format("{}{}{}{}{}{}{} -o {}", + CXX, + LDFLAGS.empty() ? ""s : (" "s + LDFLAGS), + LDFLAGS_add, + input_string, + LDLIBS.empty() ? ""s : (" "s + LDLIBS), + LIBS.empty() ? ""s : (" "s + LIBS), + link_libs_string, + target.string()); + } } std::string LanguageSettings::getDepCommand(const std::filesystem::path& target, const std::filesystem::path &source) const @@ -112,3 +112,12 @@ std::string LanguageSettings::getDepCommand(const std::filesystem::path& target, return fmt::format("{} -MM -MF {} -c {}", CXX, target.string(), source.string()); } +// TODO: +// variables: +// CC +// CFLAGS +// AR +// ARFLAGS +// AS +// ASFLAGS +// dynamic lib: diff --git a/LanguageSettings.h b/LanguageSettings.h index 0b4701c..0378d81 100644 --- a/LanguageSettings.h +++ b/LanguageSettings.h @@ -11,6 +11,7 @@ public: std::string getCompileCommand(const std::filesystem::path& target, const std::filesystem::path &source, + const std::filesystem::path& build, const std::vector<std::filesystem::path>& includepaths) const; std::string getLinkCommand(const std::filesystem::path& target, const std::vector<std::filesystem::path> &inputs, @@ -24,5 +25,7 @@ private: std::string LDFLAGS; std::string LDLIBS; std::string LIBS; + + std::string AR; }; diff --git a/SONAME.cpp b/SONAME.cpp new file mode 100644 index 0000000..89a44f1 --- /dev/null +++ b/SONAME.cpp @@ -0,0 +1,46 @@ +#include "SONAME.h" + +#include <stdexcept> +#include <string> + +#include <fmt/format.h> + +#include <libreichwein/stringhelper.h> + +using namespace std::string_literals; + +SONAME::SONAME(): _soname{".0"}, _soname_full{".0.0.0"} +{ +} + +SONAME(const std::string& s) +{ + std::vector<std::string> parts{Reichwein::Stringhelper::split(s, ":")}; + + if (parts.size() > 3) { + throw std::runtime_error("Bad SONAME spec: "s + s); + } + + while (parts.size() < 3) { + parts.push_back("0"); + } + + _soname = fmt::format(".{}"); + _soname_full = fmt::format(".{}.{}.{}", parts[0], parts[1], parts[2]); +} + +std::string getShortName() const // "" +{ + return {}; +} + +std::string getName() const // ".X" +{ + return _soname; +} + +std::string getFullName() const // ".X.Y.Z" +{ + return _soname_full; +} + diff --git a/SONAME.h b/SONAME.h new file mode 100644 index 0000000..998f67f --- /dev/null +++ b/SONAME.h @@ -0,0 +1,18 @@ +#pragma once + +#include <string> + +class SONAME +{ +public: + SONAME(); + SONAME(const std::string& s); // "" or "0" or "1.2.3" + + std::string getShortName() const; // "" + std::string getName() const; // ".X" + std::string getFullName() const; // ".X.Y.Z" +private: + std::string _soname; + std::string _soname_full; +}; + @@ -1,5 +1,7 @@ #include "file.h" +#include <algorithm> +#include <string> #include <unordered_set> #include <libreichwein/stringhelper.h> @@ -7,13 +9,14 @@ #include "env.h" namespace fs = std::filesystem; +using namespace std::string_literals; const fs::path YMakefile{"YMakefile"}; // type of file can be built from dependencies bool is_buildable_by_extension(const fs::path& p) { fs::path ext{p.extension()}; - return ext.empty() || ext == ".o"; + return ext.empty() || ext == ".o" || is_dynamic_lib(p) || is_static_lib(p) ; } namespace { @@ -26,6 +29,64 @@ bool is_compile_unit_source_by_extension(const fs::path& p) { return compile_unit_source_types.find(ext) != compile_unit_source_types.end(); } +// e.g. libxyz.so.1.2.3 (the shorter versions are links to this one) +bool is_dynamic_lib(const std::filesystem::path& p) +{ + std::string name{p.filename()}; + return name.find(".so.") != name.npos && std::count(name.begin(), name.end(), '.') == 4; +} + +// in: libxyz.so.1.2.3 +// out: libxyz.so.1 +std::filesystem::path soname_shorter(const std::filesystem::path& p) +{ + std::string name{p.filename()}; + + if (std::count(name.begin(), name.end(), '.') != 4) { + throw std::runtime_error("Unexpected soname: "s + name + ", 4 dots expected."); + } + + size_t pos{0}; + for (int i = 0; i < 3; ++i) { + pos = name.find('.', pos); + ++pos; + } + + name = name.substr(0, pos - 1); + + fs::path result{p}; + result.replace_filename(name); + return result; +} + +// in: libxyz.so.1.2.3 +// out: libxyz.so +std::filesystem::path soname_short(const std::filesystem::path& p) +{ + std::string name{p.filename()}; + + if (std::count(name.begin(), name.end(), '.') != 4) { + throw std::runtime_error("Unexpected soname: "s + name + ", 4 dots expected."); + } + + size_t pos{0}; + for (int i = 0; i < 2; ++i) { + pos = name.find('.', pos); + ++pos; + } + + name = name.substr(0, pos - 1); + + fs::path result{p}; + result.replace_filename(name); + return result; +} + +bool is_static_lib(const std::filesystem::path& p) +{ + return p.extension() == ".a"; +} + std::filesystem::path simplified_path(const std::filesystem::path& p) { if (p.string().substr(0, 2) == "./") { @@ -9,6 +9,11 @@ extern const std::filesystem::path YMakefile; bool is_buildable_by_extension(const std::filesystem::path& p); bool is_compile_unit_source_by_extension(const std::filesystem::path& p); +bool is_dynamic_lib(const std::filesystem::path& p); +bool is_static_lib(const std::filesystem::path& p); +std::filesystem::path soname_shorter(const std::filesystem::path& p); +std::filesystem::path soname_short(const std::filesystem::path& p); + // removes initial "./" std::filesystem::path simplified_path(const std::filesystem::path& p); diff --git a/test-ymake.cpp b/test-ymake.cpp index 3aea372..c2dd78e 100644 --- a/test-ymake.cpp +++ b/test-ymake.cpp @@ -491,7 +491,177 @@ TEST_F(ymakeTest, build_with_clang) } } -// TODO: multiple dirs / YMakefiles +TEST_F(ymakeTest, multiple_dirs) +{ + create_file("hello.cpp", R"(int main(int argc, char* argv[]) +{ + return 0; +})"); + + create_file("YMakefile", R"( +<ymake> + <build> + <name>hello</name> + <source>hello.cpp</source> + </build> +</ymake> +)"); + + fs::create_directory("subdir1"); + + create_file("subdir1/second.cpp", R"(int main(int argc, char* argv[]) +{ + return 0; +})"); + + create_file("subdir1/YMakefile", R"( +<ymake> + <build> + <name>second</name> + <source>second.cpp</source> + </build> +</ymake> +)"); + + std::vector<std::string> output; + int result = run_command("../ymake", output); + EXPECT_EQ(result, 0); + EXPECT_EQ(output.size(), 4); // compile, link, compile, link + + EXPECT_TRUE(fs::exists("hello")); + EXPECT_TRUE(fs::exists("hello.o")); + EXPECT_TRUE(fs::exists("hello.d")); + + EXPECT_TRUE(fs::exists("subdir1/second")); + EXPECT_TRUE(fs::exists("subdir1/second.o")); + EXPECT_TRUE(fs::exists("subdir1/second.d")); + + result = run_command("../ymake", output); + EXPECT_EQ(result, 0); + EXPECT_EQ(output.size(), 1); // everything up to date + + result = run_command("../ymake clean", output); + EXPECT_EQ(result, 0); + EXPECT_EQ(output.size(), 6); // hello hello.o hello.d second second.o second.d + + result = run_command("../ymake clean", output); + EXPECT_EQ(result, 0); + EXPECT_EQ(output.size(), 0); +} + +TEST_F(ymakeTest, build_one_static_lib) +{ + create_file("hello.cpp", R"(int maine(int argc, char* argv[]) +{ + return 0; +})"); + create_file("second.cpp", R"(int maini(int argc, char* argv[]) +{ + return 0; +})"); + + create_file("YMakefile", R"( +<ymake> + <build> + <name>hello.a</name> + <source>hello.cpp</source> + <source>second.cpp</source> + </build> +</ymake> +)"); + + std::vector<std::string> output; + int result = run_command("../ymake", output); + EXPECT_EQ(result, 0); + + ASSERT_EQ(output.size(), 3); // 2x compile, link + EXPECT_EQ(output[2].substr(0, 7), "ar rcs "); + + EXPECT_TRUE(fs::exists("hello.d")); + EXPECT_TRUE(fs::exists("hello.o")); + EXPECT_TRUE(fs::exists("hello.a")); + + EXPECT_TRUE(fs::exists("second.d")); + EXPECT_TRUE(fs::exists("second.o")); + + result = run_command("../ymake", output); + EXPECT_EQ(result, 0); + ASSERT_EQ(output.size(), 1); // everything up to date + + result = run_command("../ymake clean", output); + EXPECT_EQ(result, 0); + ASSERT_EQ(output.size(), 5); // deleted 5 files + + EXPECT_TRUE(!fs::exists("hello.d")); + EXPECT_TRUE(!fs::exists("hello.o")); + EXPECT_TRUE(!fs::exists("hello.a")); + + EXPECT_TRUE(!fs::exists("second.d")); + EXPECT_TRUE(!fs::exists("second.o")); + + result = run_command("../ymake clean", output); + EXPECT_EQ(result, 0); + ASSERT_EQ(output.size(), 0); +} + +TEST_F(ymakeTest, build_one_dynamic_lib) +{ + create_file("hello.cpp", R"(int maine(int argc, char* argv[]) +{ + return 0; +})"); + create_file("second.cpp", R"(int maini(int argc, char* argv[]) +{ + return 0; +})"); + + create_file("YMakefile", R"( +<ymake> + <build> + <name>hello.so.1.2.3</name> + <source>hello.cpp</source> + <source>second.cpp</source> + </build> +</ymake> +)"); + + std::vector<std::string> output; + int result = run_command("../ymake", output); + EXPECT_EQ(result, 0); + + ASSERT_EQ(output.size(), 3); // 2x compile, link + EXPECT_EQ(output[2].substr(0, 4), "g++ "); + + EXPECT_TRUE(fs::exists("hello.d")); + EXPECT_TRUE(fs::exists("hello.o")); + EXPECT_TRUE(fs::exists("hello.so.1.2.3")); + + EXPECT_TRUE(fs::exists("second.d")); + EXPECT_TRUE(fs::exists("second.o")); + + result = run_command("../ymake", output); + EXPECT_EQ(result, 0); + ASSERT_EQ(output.size(), 1); // everything up to date + + result = run_command("../ymake clean", output); + EXPECT_EQ(result, 0); + ASSERT_EQ(output.size(), 5); // deleted 5 files + + EXPECT_TRUE(!fs::exists("hello.d")); + EXPECT_TRUE(!fs::exists("hello.o")); + EXPECT_TRUE(!fs::exists("hello.so.1.2.3")); + + EXPECT_TRUE(!fs::exists("second.d")); + EXPECT_TRUE(!fs::exists("second.o")); + + result = run_command("../ymake clean", output); + EXPECT_EQ(result, 0); + ASSERT_EQ(output.size(), 0); +} + +// TODO: +// use static lib +// use dynamic lib TEST_F(yscanTest, no_cpp_file) { |