build: add meson as the new buildsystem for libostd

This commit is contained in:
q66 2018-10-27 23:47:04 +02:00
parent 4a55d315e4
commit 3906e5f1ee
10 changed files with 193 additions and 597 deletions

View file

@ -17,33 +17,15 @@ files in there directly if you don't need the API documentation.
## Building
Libostd is built using the supplied C++ build tool. You need to compile the
build tool first using the compiler you will use to build the library itself.
On a typical Unix-like setup, this will involve:
Libostd is built using Meson. Therefore, you need to install Meson and then
you can compile it as usual. Typically, this will be something like
~~~
c++ build.cc -o build -std=c++1z -I. -pthread
./build --help
mkdir build && cd build
meson ..
ninja all
~~~
This will typically build using either GCC or Clang with the default standard
library. **Keep in mind that it is you need at least Clang 4.0 or
GCC 7.1 to build.**
You can skip `-pthread` on Windows.
Using the tool should be straightforward. The `./build --help` command lists
the available options.
It also recognizes the environment variables `CXX` (the C++ compiler used
to build, defaults to `c++`), `AS` (the assembler used to build, defaults to
`c++` as well, as Clang and GCC can compile assembly files), `AR` (the tool
to create static lib archives, `ar` by default) and `STRIP` (the tool used
to strip the library in release mode, `strip` by default).
Additionally, the `CXXFLAGS`, `LDFLAGS` and `ASFLAGS` environment variables
are also used. The `CXXFLAGS` are passed when compiling C++ source files as
well as when linking (the compiler is used to link). The `LDFLAGS` are passed
additionally to `CXXFLAGS` only when linking. The `ASFLAGS` are passed to
the assembler (`CXXFLAGS` are not, even when Clang/GCC is used).

572
build.cc
View file

@ -1,572 +0,0 @@
/* A simple build system to build libostd.
*
* This file is a part of the libostd project. Libostd is licensed under the
* University of Illinois/NCSA Open Source License, as is this file. See the
* COPYING.md file further information.
*/
/* just glibc bein awful */
#define _FILE_OFFSET_BITS 64
#include <string>
#include <vector>
#include <stdexcept>
#include <utility>
#include "ostd/io.hh"
#include "ostd/range.hh"
#include "ostd/format.hh"
#include "ostd/string.hh"
#include "ostd/path.hh"
#include "ostd/thread_pool.hh"
#include "ostd/channel.hh"
#include "ostd/argparse.hh"
namespace fs = ostd::fs;
using path = ostd::path;
/* ugly, but do not explicitly compile */
#include "src/io.cc"
#include "src/process.cc"
#include "src/path.cc"
#include "src/thread_pool.cc"
#include "src/channel.cc"
#include "src/string.cc"
#include "src/argparse.cc"
#define OSTD_GEN_UNICODE_INCLUDE
#include "gen_unicode.cc"
using strvec = std::vector<std::string>;
using pathvec = std::vector<path>;
/* THESE VARIABLES CAN BE ALTERED */
/* all examples in the directory are built */
static path EXAMPLES_DIR = "examples";
static path ASM_SOURCE_DIR = path{"src"} / "asm";
static pathvec ASM_SOURCES = {
"jump_all_gas", "make_all_gas", "ontop_all_gas"
};
/* all sources in the directory are built */
static path CXX_SOURCE_DIR = "src";
static path TEST_DIR = "tests";
static pathvec TEST_CASES = {
"algorithm", "range"
};
static path OSTD_UNICODE_DATA = "data/UnicodeData-11.0.txt";
static path OSTD_UNICODE_SRC = CXX_SOURCE_DIR / "string_utf.hh";
static path OSTD_SHARED_LIB = "libostd.so";
static path OSTD_STATIC_LIB = "libostd.a";
static std::string DEFAULT_CXXFLAGS = "-std=c++1z -I. -O2 -Wall -Wextra "
"-Wshadow -Wold-style-cast -fPIC "
"-D_FILE_OFFSET_BITS=64";
static std::string DEFAULT_LDFLAGS = "-pthread";
static std::string DEFAULT_ASFLAGS = "-fPIC";
static std::string DEBUG_CXXFLAGS = "-g";
static std::string SHARED_CXXFLAGS = "";
static std::string SHARED_LDFLAGS = "-shared";
static std::string SHARED_ASFLAGS = "";
/* DO NOT CHANGE PAST THIS POINT */
static std::string from_env_or(char const *evar, char const *defval) {
char const *varv = std::getenv(evar);
if (!varv || !varv[0]) {
return defval;
}
return varv;
}
static void add_cross(std::string &arg) {
char const *cross = std::getenv("CROSS");
if (!cross || !cross[0]) {
return;
}
arg.insert(0, cross);
}
static void add_env(std::string &val, char const *evar) {
char const *varv = std::getenv(evar);
if (!varv || !varv[0]) {
return;
}
val += ' ';
val += varv;
}
static void exec_command(strvec const &args) {
if (int ret = ostd::subprocess{nullptr, ostd::iter(args)}.close(); ret) {
throw std::runtime_error{ostd::format(
ostd::appender<std::string>(), "command failed with code %d", ret
).get()};
}
}
static std::string get_command(strvec const &args) {
std::string ret;
for (auto &s: args) {
if (!ret.empty()) {
ret += ' ';
}
ret += s;
}
return ret;
}
static void add_args(strvec &args, std::string const &cmdl) {
ostd::split_args([&args](ostd::string_range arg) {
args.emplace_back(arg);
}, cmdl);
}
static void try_remove(path const &path, bool all = false) {
try {
if (all) {
fs::remove_all(path);
} else {
fs::remove(path);
}
} catch (fs::fs_error const &) {}
}
static bool verbose = false;
int main(int argc, char **argv) {
bool build_examples = true;
bool build_testsuite = true;
bool build_static = true;
bool build_shared = false;
std::string build_cfg = "debug";
bool clean = false;
std::string cxxflags = DEFAULT_CXXFLAGS;
std::string ldflags = DEFAULT_LDFLAGS;
std::string asflags = DEFAULT_ASFLAGS;
ostd::arg_parser ap;
auto &help = ap.add_optional("-h", "--help", 0)
.help("print this message and exit")
.action(ostd::arg_print_help(ap));
ap.add_optional("--no-examples", 0)
.help("do not build examples")
.action(ostd::arg_store_false(build_examples));
ap.add_optional("--no-test-suite", 0)
.help("do not build test suite")
.action(ostd::arg_store_false(build_testsuite));
ap.add_optional("--no-static", 0)
.help("do not build static libostd")
.action(ostd::arg_store_false(build_static));
ap.add_optional("--shared", 0)
.help("build shared libostd")
.action(ostd::arg_store_true(build_shared));
ap.add_optional("--config", 1)
.help("build configuration (debug/release)")
.action(ostd::arg_store_str(build_cfg));
ap.add_optional("-v", "--verbose", 0)
.help("print entire commands")
.action(ostd::arg_store_true(verbose));
ap.add_positional("target", ostd::arg_value::OPTIONAL)
.help("the action to perform")
.action([&clean](auto vals) {
if (!vals.empty()) {
if (vals.front() == "clean") {
clean = true;
} else if (vals.front() != "build") {
throw ostd::arg_error{"invalid build action"};
}
}
});
try {
ap.parse(argc, argv);
} catch (ostd::arg_error const &e) {
ostd::cerr.writefln("argument parsing failed: %s", e.what());
ap.print_help(ostd::cerr.iter());
return 1;
}
if (help.used()) {
return 0;
}
std::string default_lib = OSTD_SHARED_LIB;
if (build_static) {
default_lib = OSTD_STATIC_LIB;
}
auto strip = from_env_or("STRIP", "strip");
auto cxx = from_env_or("CXX", "c++");
auto as = from_env_or("AS", "c++");
auto ar = from_env_or("AR", "ar");
add_cross(strip);
add_cross(cxx);
add_cross(as);
add_cross(ar);
if (build_cfg == "debug") {
cxxflags += ' ';
cxxflags += DEBUG_CXXFLAGS;
} else if (build_cfg != "release") {
ostd::cerr.writefln("invalid build configuration: %s", build_cfg);
ap.print_help(ostd::cerr.iter());
return 1;
}
add_env(cxxflags, "CXXFLAGS");
add_env(ldflags, "LDFLAGS");
add_env(asflags, "ASFLAGS");
auto examples = ostd::appender<pathvec>();
fs::glob_match(examples, EXAMPLES_DIR / "*.cc");
for (auto &ex: examples.get()) {
ex.replace_suffix();
}
auto cxx_sources = ostd::appender<pathvec>();
fs::glob_match(cxx_sources, CXX_SOURCE_DIR / "*.cc");
for (auto &cc: cxx_sources.get()) {
cc.replace_suffix();
}
if (clean) {
ostd::writeln("Cleaning...");
for (auto &ex: examples.get()) {
auto rp = ex;
try_remove(rp);
rp.replace_suffix(".o");
try_remove(rp);
}
for (auto &aso: ASM_SOURCES) {
auto rp = (ASM_SOURCE_DIR / aso).with_suffix(".o");
try_remove(rp);
rp.replace_name((aso + "_dyn.o").string());
try_remove(rp);
}
for (auto &cso: cxx_sources.get()) {
auto rp = cso.with_suffix(".o");
try_remove(rp);
rp.replace_name((cso + "_dyn.o").string());
try_remove(rp);
}
try_remove(OSTD_UNICODE_SRC);
try_remove(OSTD_STATIC_LIB);
try_remove(OSTD_SHARED_LIB);
try_remove("test_runner.o");
try_remove("test_runner");
try_remove("tests", true);
return 0;
}
/* a queue of stuff to print to stdout */
ostd::channel<std::string> io_msgs;
/* a thread which reads from the queue */
std::thread io_thread{[&io_msgs]() {
try {
for (;;) {
/* wait for a msg; if closed, throw channel_error */
ostd::writeln(io_msgs.get());
}
} catch (ostd::channel_error const &) {
/* the queue is empty and closed, thread is done */
return;
}
}};
auto echo_q = [&io_msgs](ostd::string_range fmt, auto const &...args) {
if (!verbose) {
auto app = ostd::appender<std::string>();
ostd::format(app, fmt, args...);
io_msgs.put(std::move(app.get()));
}
};
auto exec_v = [&io_msgs](strvec const &args) {
if (verbose) {
io_msgs.put(get_command(args));
}
exec_command(args);
};
auto call_cxx = [&](
path const &input, path const &output, bool lib, bool shared
) {
strvec args = { cxx };
add_args(args, cxxflags);
auto ifs = input.string();
auto outp = output;
if (shared) {
outp.replace_suffix();
outp += "_dyn.o";
echo_q("CXX (shared): %s", ifs);
add_args(args, SHARED_CXXFLAGS);
} else {
echo_q("CXX: %s", ifs);
}
if (lib) {
args.push_back("-DOSTD_BUILD");
if (shared) {
args.push_back("-DOSTD_DLL");
}
}
args.push_back("-c");
args.push_back("-o");
args.push_back(outp);
args.push_back(ifs);
exec_v(args);
return outp;
};
/* mostly unnecessary to separately compile shared, but
* the files may check for __PIC__ (at least mips32 does)
*/
auto call_as = [&](
path const &input, path const &output, bool, bool shared
) {
strvec args = { as };
add_args(args, asflags);
auto ifs = input.string();
auto outp = output;
if (shared) {
outp.replace_suffix();
outp += "_dyn.o";
echo_q("AS (shared): %s", ifs);
add_args(args, SHARED_ASFLAGS);
} else {
echo_q("AS: %s", ifs);
}
args.push_back("-c");
args.push_back("-o");
args.push_back(outp);
args.push_back(ifs);
exec_v(args);
return outp;
};
auto call_ld = [&](
path const &output, pathvec const &files, strvec const &flags
) {
echo_q("LD: %s", output);
strvec args = { cxx };
add_args(args, cxxflags);
args.push_back("-o");
args.push_back(output);
for (auto &p: files) {
args.push_back(p);
}
args.insert(args.cend(), flags.begin(), flags.end());
add_args(args, ldflags);
exec_v(args);
if (build_cfg == "release") {
args.clear();
args.push_back(strip);
args.push_back(output);
exec_v(args);
}
};
auto call_ldlib = [&](
path const &output, pathvec const &files, bool shared
) {
if (shared) {
strvec flags;
add_args(flags, SHARED_CXXFLAGS);
add_args(flags, SHARED_LDFLAGS);
call_ld(output, files, flags);
} else {
strvec args = { ar };
echo_q("AR: %s", output);
args.push_back("rcs");
args.push_back(output);
for (auto &p: files) {
args.push_back(p);
}
exec_v(args);
}
};
auto build_example = [&](path const &name) {
auto base = name;
auto ccf = base.with_suffix(".cc");
auto obf = base.with_suffix(".o");
call_cxx(ccf, obf, false, false);
call_ld(base, { obf }, { default_lib });
try_remove(obf);
};
auto build_test = [&](path const &name) {
auto base = TEST_DIR / name;
auto ccf = base.with_suffix(".cc");
auto obf = base.with_suffix(".o");
try_remove(ccf);
ostd::file_stream f{ccf.string(), ostd::stream_mode::WRITE};
if (!f.is_open()) {
auto app = ostd::appender<std::string>();
ostd::format(app, "cannot open '%s' for writing", ccf);
throw std::runtime_error{app.get()};
}
f.writef(
"#define OSTD_BUILD_TESTS libostd_%s\n"
"\n"
"#include <ostd/unit_test.hh>\n"
"#include <ostd/%s.hh>\n"
"#include <ostd/io.hh>\n"
"\n"
"int main() {\n"
" auto [ succ, fail ] = ostd::test::run();\n"
" ostd::writeln(succ, \" \", fail);\n"
" return 0;\n"
"}\n",
name, name
);
f.close();
call_cxx(ccf, obf, false, false);
call_ld(base, { obf }, { default_lib });
try_remove(obf);
};
auto build_test_runner = [&]() {
call_cxx("test_runner.cc", "test_runner.o", false, false);
call_ld("test_runner", { "test_runner.o" }, { default_lib });
try_remove("test_runner.o");
};
ostd::thread_pool tp{};
tp.start();
std::queue<std::future<path>> future_obj, future_dynobj;
/* build object files in static and shared (PIC) variants */
auto build_all = [&](
pathvec const &list, path const &spath,
path const &sext, auto &buildf
) {
auto build_obj = [&](path const &fpath, bool shared) {
auto srcf = fpath.with_suffix(sext.string());
auto srco = srcf.with_suffix(".o");
auto &fq = (shared ? future_dynobj : future_obj);
fq.push(tp.push([&buildf, srcf, srco, shared]() {
return buildf(srcf, srco, true, shared);
}));
};
for (auto &sf: list) {
auto sp = spath.empty() ? sf : (spath / sf);
if (build_static) {
build_obj(sp, false);
}
if (build_shared) {
build_obj(sp, true);
}
}
};
echo_q("Generating Unicode tables...");
ostd::unicode_gen::parse_state{}.build_all_from_file(
OSTD_UNICODE_DATA.string(), OSTD_UNICODE_SRC.string()
);
echo_q("Building the library...");
build_all(ASM_SOURCES, ASM_SOURCE_DIR, ".S", call_as);
build_all(cxx_sources.get(), path{}, ".cc", call_cxx);
if (build_static) {
pathvec objs;
while (!future_obj.empty()) {
objs.push_back(future_obj.front().get());
future_obj.pop();
}
call_ldlib(OSTD_STATIC_LIB, objs, false);
}
if (build_shared) {
pathvec objs;
while (!future_dynobj.empty()) {
objs.push_back(future_dynobj.front().get());
future_dynobj.pop();
}
call_ldlib(OSTD_SHARED_LIB, objs, true);
}
std::queue<std::future<void>> future_bin;
if (build_examples) {
echo_q("Building examples...");
for (auto &ex: examples.get()) {
future_bin.push(tp.push([&build_example, ex]() {
build_example(ex);
}));
}
}
if (build_testsuite) {
echo_q("Building tests...");
build_test_runner();
if (!fs::is_directory(TEST_DIR)) {
if (!fs::create_directory(TEST_DIR)) {
echo_q("Failed creating test directory");
return 1;
}
}
for (auto &test: TEST_CASES) {
future_bin.push(tp.push([&build_test, test]() {
build_test(test);
}));
}
}
while (!future_bin.empty()) {
/* wait and propagate possible exception */
future_bin.front().get();
future_bin.pop();
}
if (build_testsuite) {
exec_v({ "./test_runner", TEST_DIR });
}
io_msgs.close();
io_thread.join();
return 0;
}

25
examples/meson.build Normal file
View file

@ -0,0 +1,25 @@
libostd_examples_src = [
'argparse.cc',
'concurrency.cc',
'coroutine1.cc',
'coroutine2.cc',
'format.cc',
'glob.cc',
'listdir.cc',
'range.cc',
'range_pipe.cc',
'signal.cc',
'stream1.cc',
'stream2.cc'
]
thread_dep = dependency('threads')
foreach example: libostd_examples_src
executable(example.split('.')[0],
[example],
dependencies: [libostd, thread_dep],
include_directories: libostd_includes,
install: false
)
endforeach

View file

@ -19,6 +19,12 @@
#include <ostd/string.hh>
#include <ostd/algorithm.hh>
#ifdef OSTD_GEN_UNICODE_BUILD
#define OSTD_NO_UNICODE_TABLES
#include "src/string.cc"
#include "src/io.cc"
#endif
namespace ostd {
namespace unicode_gen {

35
meson.build Normal file
View file

@ -0,0 +1,35 @@
project('libostd', ['cpp'],
version: '0.1.0',
default_options: ['buildtype=plain', 'cpp_std=c++17'],
meson_version: '>=0.46'
)
dir_prefix = get_option('prefix')
dir_include = join_paths(dir_prefix, get_option('includedir'))
dir_data = join_paths(dir_prefix, get_option('datadir'))
dir_lib = join_paths(dir_prefix, get_option('libdir'))
dir_package_include = join_paths(dir_include, 'ostd')
unicode_data = join_paths('data', 'UnicodeData-11.0.txt')
libostd_includes = [include_directories('.')]
tgt_compiler_id = meson.get_compiler('cpp').get_id()
if tgt_compiler_id == 'gcc' or tgt_compiler_id == 'clang'
add_global_arguments(
'-Wextra', '-Wshadow', '-Wold-style-cast',
language: 'cpp'
)
endif
subdir('src')
if get_option('build-tests')
subdir('tests')
endif
if get_option('build-examples')
subdir('examples')
endif

11
meson_options.txt Normal file
View file

@ -0,0 +1,11 @@
option('build-examples',
type: 'boolean',
value: true,
description: 'Build examples'
)
option('build-tests',
type: 'boolean',
value: true,
description: 'Build tests'
)

81
src/meson.build Normal file
View file

@ -0,0 +1,81 @@
libostd_header_src = [
'../ostd/algorithm.hh',
'../ostd/argparse.hh',
'../ostd/channel.hh',
'../ostd/concurrency.hh',
'../ostd/context_stack.hh',
'../ostd/coroutine.hh',
'../ostd/environ.hh',
'../ostd/event.hh',
'../ostd/format.hh',
'../ostd/generic_condvar.hh',
'../ostd/io.hh',
'../ostd/path.hh',
'../ostd/platform.hh',
'../ostd/process.hh',
'../ostd/range.hh',
'../ostd/stream.hh',
'../ostd/string.hh',
'../ostd/thread_pool.hh',
'../ostd/unit_test.hh',
'../ostd/vecmath.hh',
'../ostd/build/make.hh',
'../ostd/build/make_coroutine.hh',
'../ostd/ext/sdl_rwops.hh'
]
libostd_src = [
'argparse.cc',
'build_make.cc',
'channel.cc',
'concurrency.cc',
'context_stack.cc',
'environ.cc',
'io.cc',
'path.cc',
'process.cc',
'string.cc',
'thread_pool.cc',
'asm/jump_all_gas.S',
'asm/make_all_gas.S',
'asm/ontop_all_gas.S'
]
thread_dep = dependency('threads')
libostd_gen_unicode_exe = executable('gen_unicode',
['../gen_unicode.cc'],
include_directories: libostd_includes,
cpp_args: '-DOSTD_GEN_UNICODE_BUILD',
install: false
)
libostd_extra_src = []
libostd_extra_src += custom_target('libostd_unicode_tables',
input: join_paths('..', unicode_data),
output: ['string_utf.hh'],
install: false,
command: [
libostd_gen_unicode_exe, '@INPUT@',
join_paths(meson.current_build_dir(), 'string_utf.hh')
]
)
libostd_lib = library('ostd',
libostd_src, libostd_extra_src,
dependencies: thread_dep,
include_directories: libostd_includes + [include_directories('.')],
install: true,
version: meson.project_version()
)
libostd = declare_dependency(
include_directories: libostd_includes,
link_with: libostd_lib
)
install_headers(libostd_header_src, install_dir: dir_package_include)

View file

@ -508,7 +508,7 @@ OSTD_EXPORT bool isupper(char32_t c) noexcept;
OSTD_EXPORT char32_t tolower(char32_t c) noexcept;
OSTD_EXPORT char32_t toupper(char32_t c) noexcept;
#if __has_include("string_utf.hh")
#ifndef OSTD_NO_UNICODE_TABLES
#include "string_utf.hh"
#else
@ -558,7 +558,7 @@ OSTD_EXPORT char32_t toupper(char32_t c) noexcept {
return c;
}
#endif /* __has_include("string_utf.hh") */
#endif /* !OSTD_NO_UNICODE_TABLES */
namespace detail {
template<typename C>

28
tests/meson.build Normal file
View file

@ -0,0 +1,28 @@
test_runner_exe = executable('test_runner',
['test_runner.cc'],
dependencies: libostd,
include_directories: libostd_includes,
install: false
)
libostd_tests_src = [
'algorithm.cc',
'range.cc'
]
test_target = []
foreach test_src: libostd_tests_src
test_target += executable(test_src.split('.')[0],
[test_src],
dependencies: libostd,
include_directories: libostd_includes,
install: false
)
endforeach
test('libostd',
test_runner_exe,
args: [meson.current_build_dir()],
workdir: meson.current_build_dir(),
depends: test_target
)