summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorRoland Reichwein <mail@reichwein.it>2024-05-10 21:15:30 +0200
committerRoland Reichwein <mail@reichwein.it>2024-05-10 21:15:30 +0200
commita7e016c2c633667b561a0f26ebde88cb26571d1c (patch)
tree7c66727de7022d5d962448be365dd059bb0ecac1
parentbfdb4e9d2cfc7890c5f194e670039fa76c391330 (diff)
Build static and dynamic libs
-rw-r--r--Builder.cpp2
-rw-r--r--LanguageSettings.cpp95
-rw-r--r--LanguageSettings.h3
-rw-r--r--SONAME.cpp46
-rw-r--r--SONAME.h18
-rw-r--r--file.cpp63
-rw-r--r--file.h5
-rw-r--r--test-ymake.cpp172
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;
+};
+
diff --git a/file.cpp b/file.cpp
index 3149f63..6de8b07 100644
--- a/file.cpp
+++ b/file.cpp
@@ -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) == "./") {
diff --git a/file.h b/file.h
index f951a27..ba3dfdd 100644
--- a/file.h
+++ b/file.h
@@ -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)
{