diff options
| author | Bent Bisballe Nyeng <deva@aasimon.org> | 2021-08-28 18:59:29 +0200 | 
|---|---|---|
| committer | Bent Bisballe Nyeng <deva@aasimon.org> | 2021-08-28 18:59:29 +0200 | 
| commit | 5da56616cccf4e595ec6a556cf1aef40b37746e3 (patch) | |
| tree | 8142e294a251ca2ab1697f7541fe67dfd31622e0 /src | |
| parent | 0597cb9854d66d918762ff167148516b69c02e9a (diff) | |
Move sources to ... well, src ;)
Diffstat (limited to 'src')
| -rw-r--r-- | src/build.cc | 173 | ||||
| -rw-r--r-- | src/build.h | 18 | ||||
| -rw-r--r-- | src/configure.cc | 301 | ||||
| -rw-r--r-- | src/configure.h | 19 | ||||
| -rw-r--r-- | src/execute.cc | 87 | ||||
| -rw-r--r-- | src/execute.h | 9 | ||||
| -rw-r--r-- | src/libcppbuild.cc | 316 | ||||
| -rw-r--r-- | src/libcppbuild.h | 76 | ||||
| -rw-r--r-- | src/rebuild.cc | 141 | ||||
| -rw-r--r-- | src/rebuild.h | 24 | ||||
| -rw-r--r-- | src/settings.h | 11 | ||||
| -rw-r--r-- | src/task.cc | 146 | ||||
| -rw-r--r-- | src/task.h | 60 | ||||
| -rw-r--r-- | src/task_ar.cc | 221 | ||||
| -rw-r--r-- | src/task_ar.h | 42 | ||||
| -rw-r--r-- | src/task_cc.cc | 339 | ||||
| -rw-r--r-- | src/task_cc.h | 47 | ||||
| -rw-r--r-- | src/task_ld.cc | 227 | ||||
| -rw-r--r-- | src/task_ld.h | 42 | ||||
| -rw-r--r-- | src/task_so.cc | 217 | ||||
| -rw-r--r-- | src/task_so.h | 42 | ||||
| -rw-r--r-- | src/tasks.cc | 130 | ||||
| -rw-r--r-- | src/tasks.h | 18 | 
23 files changed, 2706 insertions, 0 deletions
diff --git a/src/build.cc b/src/build.cc new file mode 100644 index 0000000..445979e --- /dev/null +++ b/src/build.cc @@ -0,0 +1,173 @@ +#include "build.h" + +#include <future> +#include <vector> +#include <iostream> +#include <chrono> +#include <set> +#include <thread> + +#include "tasks.h" + +using namespace std::chrono_literals; + +int build(const Settings& settings, +          const std::string& name, +          const std::list<std::shared_ptr<Task>>& tasks, +          const std::list<std::shared_ptr<Task>>& all_tasks) +{ +	if(settings.verbose > 1) +	{ +		std::cout << "Building '" << name << "'\n"; +	} + +	std::list<std::shared_ptr<Task>> dirtyTasks; +	for(auto task : tasks) +	{ +		if(task->dirty()) +		{ +			dirtyTasks.push_back(task); +		} +	} + +	if(dirtyTasks.empty()) +	{ +		std::cout << "Nothing to be done for '"<< name << "'\n"; +		return 0; +	} + +	std::list<std::future<int>> processes; + +	// Start all tasks +	bool done{false}; +	while(!done) +	{ +		bool started_one{false}; +		while(processes.size() < settings.parallel_processes) +		{ +			if(dirtyTasks.empty()) +			{ +				done = true; +				break; +			} + +			auto task = getNextTask(all_tasks, dirtyTasks); +			if(task == nullptr) +			{ +				if(processes.empty() && !dirtyTasks.empty()) +				{ +					// No running processes, yet no process to run. This is a dead-lock... +					std::cout << "Dead-lock detected.\n"; +					return 1; +				} +				break; +			} + +			processes.emplace_back( +				std::async(std::launch::async, +				           [task]() +				           { +					           return task->run(); +				           })); +			started_one = true; +			std::this_thread::sleep_for(2ms); +		} + +		for(auto process = processes.begin(); +		    process != processes.end(); +		    ++process) +		{ +			if(process->valid()) +			{ +				if(process->get() != 0) +				{ +					// TODO: Wait for other processes to finish before returning +					return 1; +				} +				processes.erase(process); +				break; +			} +		} + +		if(started_one) +		{ +			std::this_thread::sleep_for(2ms); +		} +		else +		{ +			std::this_thread::sleep_for(200ms); +		} +	} + +	for(auto process = processes.begin(); +	    process != processes.end(); +	    ++process) +	{ +		process->wait(); +		auto ret = process->get(); +		if(ret != 0) +		{ +			return 1; +		} +	} + +	return 0; +} + +namespace +{ +std::set<std::shared_ptr<Task>> getDepTasks(std::shared_ptr<Task> task) +{ +	std::set<std::shared_ptr<Task>> tasks; +	tasks.insert(task); + +	auto deps = task->getDependsTasks(); +	for(const auto& dep : deps) +	{ +		auto depSet = getDepTasks(dep); +		for(const auto& dep : depSet) +		{ +			tasks.insert(dep); +		} +	} + +	return tasks; +} +} + +int build(const Settings& settings, +          const std::string& name, +          const std::list<std::shared_ptr<Task>>& all_tasks) +{ +	bool task_found{false}; +	for(auto task : all_tasks) +	{ +		if(task->name() == name || task->target() == name) +		{ +			std::cout << name << "\n"; +			task_found = true; + +			auto depSet = getDepTasks(task); +			std::list<std::shared_ptr<Task>> ts; +			for(const auto& task : depSet) +			{ +				ts.push_back(task); +			} +			auto ret = build(settings, name, ts, all_tasks); +			if(ret != 0) +			{ +				return ret; +			} + +			break; +		} +	} + +	if(!task_found) +	{ +		std::cerr << "*** No rule to make target '" << name << "'.  Stop.\n"; +		return 1; +	} + +	return 0; +} diff --git a/src/build.h b/src/build.h new file mode 100644 index 0000000..36e48ad --- /dev/null +++ b/src/build.h @@ -0,0 +1,18 @@ +// -*- c++ -*- +#pragma once + +#include <string> +#include <list> +#include <memory> + +#include "task.h" +#include "settings.h" + +int build(const Settings& settings, +          const std::string& name, +          const std::list<std::shared_ptr<Task>>& tasks, +          const std::list<std::shared_ptr<Task>>& all_tasks); + +int build(const Settings& settings, +          const std::string& name, +          const std::list<std::shared_ptr<Task>>& all_tasks); diff --git a/src/configure.cc b/src/configure.cc new file mode 100644 index 0000000..ab2f837 --- /dev/null +++ b/src/configure.cc @@ -0,0 +1,301 @@ +#include "configure.h" + +#include <iostream> +#include <filesystem> +#include <fstream> + +#include <getoptpp/getoptpp.hpp> + +#include "settings.h" +#include "execute.h" +#include "libcppbuild.h" +#include "tasks.h" + +std::filesystem::path configurationFile("configuration.cc"); +std::filesystem::path configHeaderFile("config.h"); + +const std::map<std::string, std::string> default_configuration{}; +const std::map<std::string, std::string>& __attribute__((weak)) configuration() +{ +	return default_configuration; +} + +bool hasConfiguration(const std::string& key) +{ +	const auto& c = configuration(); +	return c.find(key) != c.end(); +} + +const std::string& getConfiguration(const std::string& key, +                                    const std::string& defaultValue) +{ +	const auto& c = configuration(); +	if(hasConfiguration(key)) +	{ +		return c.at(key); +	} + +	return defaultValue; +} + +std::string locate(const std::string& arch, const std::string& app) +{ +	std::string path_env = std::getenv("PATH"); +	std::cout << path_env << "\n"; + +	std::string program = app; +	if(!arch.empty()) +	{ +		program = arch + "-" + app; +	} +	std::cout << "Looking for: " << program << "\n"; +	std::vector<std::string> paths; + +	{ +		std::stringstream ss(path_env); +		std::string path; +		while (std::getline(ss, path, ':')) +		{ +			paths.push_back(path); +		} +	} +	for(const auto& path_str : paths) +	{ +		std::filesystem::path path(path_str); +		auto prog_path = path / program; +		if(std::filesystem::exists(prog_path)) +		{ +			std::cout << "Found file " << app << " in path: " << path << "\n"; +			auto perms = std::filesystem::status(prog_path).permissions(); +			if((perms & std::filesystem::perms::owner_exec) != std::filesystem::perms::none) +			{ +				std::cout << " - executable by owner\n"; +			} +			if((perms & std::filesystem::perms::group_exec) != std::filesystem::perms::none) +			{ +				std::cout << " - executable by group\n"; +			} +			if((perms & std::filesystem::perms::others_exec) != std::filesystem::perms::none) +			{ +				std::cout << " - executable by others\n"; +			} + +			return prog_path.string(); +		} +	} + +	std::cerr << "Could not locate " << app << " for the " << arch << " architecture\n"; +	exit(1); +	return {}; +} + +int configure(int argc, char* argv[]) +{ +	Settings settings; + +	settings.builddir = "build"; + +	std::string cmd_str; +	for(int i = 0; i < argc; ++i) +	{ +		if(i > 0) +		{ +			cmd_str += " "; +		} +		cmd_str += argv[i]; +	} + +	dg::Options opt; +	int key{128}; + +	std::string build_arch; +	std::string build_path; +	std::string host_arch; +	std::string host_path; +	std::string cc_prog = "gcc"; +	std::string cxx_prog = "g++"; +	std::string ar_prog = "ar"; +	std::string ld_prog = "ld"; + +	opt.add("build-dir", required_argument, 'b', +	        "Set output directory for build files (default: '" + +	        settings.builddir + "').", +	        [&]() { +		        settings.builddir = optarg; +		        return 0; +	        }); + +	opt.add("verbose", no_argument, 'v', +	        "Be verbose. Add multiple times for more verbosity.", +	        [&]() { +		        settings.verbose++; +		        return 0; +	        }); + +	opt.add("cc", required_argument, key++, +	        "Use specified c-compiler instead of gcc.", +	        [&]() { +		        cc_prog = optarg; +		        return 0; +	        }); + +	opt.add("cxx", required_argument, key++, +	        "Use specified c++-compiler instead of g++.", +	        [&]() { +		        cxx_prog = optarg; +		        return 0; +	        }); + +	opt.add("ar", required_argument, key++, +	        "Use specified archiver instead of ar.", +	        [&]() { +		        ar_prog = optarg; +		        return 0; +	        }); + +	opt.add("ld", required_argument, key++, +	        "Use specified linker instead of ld.", +	        [&]() { +		        ld_prog = optarg; +		        return 0; +	        }); + +	opt.add("build", required_argument, key++, +	        "Configure for building on specified architecture.", +	        [&]() { +		        build_arch = optarg; +		        return 0; +	        }); + +	opt.add("build-path", required_argument, key++, +	        "Set path to build tool-chain.", +	        [&]() { +		        build_path = optarg; +		        return 0; +	        }); + +	opt.add("host", required_argument, key++, +	        "Cross-compile to build programs to run on specified architecture.", +	        [&]() { +		        host_arch = optarg; +		        return 0; +	        }); + +	opt.add("host-path", required_argument, key++, +	        "Set path to cross-compile tool-chain.", +	        [&]() { +		        host_path = optarg; +		        return 0; +	        }); + +	opt.add("help", no_argument, 'h', +	        "Print this help text.", +	        [&]() { +		        std::cout << "configure usage stuff\n"; +		        opt.help(); +		        exit(0); +		        return 0; +	        }); + +	opt.process(argc, argv); + +	if(host_arch.empty()) +	{ +		host_arch = build_arch; +	} + +	auto tasks = getTasks(settings); +/* +	bool needs_cpp{false}; +	bool needs_c{false}; +	bool needs_ar{false}; +	bool needs_asm{false}; +	for(const auto& task :tasks) +	{ +		switch(task->sourceLanguage()) +		{ +		case Language::Auto: +			std::cerr << "TargetLanguage not deduced!\n"; +			exit(1); +			break; +		case Language::C: +			needs_cpp = false; +			break; +		case Language::Cpp: +			needs_c = true; +			break; +		case Language::Asm: +			needs_asm = true; +			break; +		} +	} +*/ +	auto cc_env = getenv("CC"); +	if(cc_env) +	{ +		cmd_str = std::string("CC=") + cc_env + " " + cmd_str; +		cc_prog = cc_env; +	} + +	auto cxx_env = getenv("CXX"); +	if(cxx_env) +	{ +		cmd_str = std::string("CXX=") + cxx_env + " " + cmd_str; +		cxx_prog = cxx_env; +	} + +	auto ar_env = getenv("AR"); +	if(ar_env) +	{ +		cmd_str = std::string("AR=") + ar_env + " " + cmd_str; +		ar_prog = ar_env; +	} + +	auto ld_env = getenv("LD"); +	if(ld_env) +	{ +		cmd_str = std::string("LD=") + ld_env + " " + cmd_str; +		ld_prog = ld_env; +	} + +	std::string host_cc = locate(host_arch, cc_prog); +	std::string host_cxx = locate(host_arch, cxx_prog); +	std::string host_ar = locate(host_arch, ar_prog); +	std::string host_ld = locate(host_arch, ld_prog); +	std::string build_cc = locate(build_arch, cc_prog); +	std::string build_cxx = locate(build_arch, cxx_prog); +	std::string build_ar = locate(build_arch, ar_prog); +	std::string build_ld = locate(build_arch, ld_prog); + +	std::cout << "Writing results to: " << configurationFile.string() << "\n"; +	{ +		std::ofstream istr(configurationFile); +		istr << "#include \"libcppbuild.h\"\n\n"; +		istr << "const std::map<std::string, std::string>& configuration()\n"; +		istr << "{\n"; +		istr << "	static std::map<std::string, std::string> c =\n"; +		istr << "	{\n"; +		istr << "		{ \"cmd\", \"" << cmd_str << "\" },\n"; +		istr << "		{ \"" << cfg::builddir << "\", \"" << settings.builddir << "\" },\n"; +		istr << "		{ \"" << cfg::host_cc << "\", \"" << host_cc << "\" },\n"; +		istr << "		{ \"" << cfg::host_cxx << "\", \"" << host_cxx << "\" },\n"; +		istr << "		{ \"" << cfg::host_ar << "\", \"" << host_ar << "\" },\n"; +		istr << "		{ \"" << cfg::host_ld << "\", \"" << host_ld << "\" },\n"; +		istr << "		{ \"" << cfg::build_cc << "\", \"" << build_cc << "\" },\n"; +		istr << "		{ \"" << cfg::build_cxx << "\", \"" << build_cxx << "\" },\n"; +		istr << "		{ \"" << cfg::build_ar << "\", \"" << build_ar << "\" },\n"; +		istr << "		{ \"" << cfg::build_ld << "\", \"" << build_ld << "\" },\n"; +		istr << "	};\n"; +		istr << "	return c;\n"; +		istr << "}\n"; +	} + +	{ +		std::ofstream istr(configHeaderFile); +		istr << "#pragma once\n\n"; +		istr << "#define HAS_FOO 1\n"; +		istr << "//#define HAS_BAR 1\n"; +	} + +	return 0; +} diff --git a/src/configure.h b/src/configure.h new file mode 100644 index 0000000..95b6765 --- /dev/null +++ b/src/configure.h @@ -0,0 +1,19 @@ +// -*- c++ -*- +#pragma once + +#include <filesystem> +#include <string> +#include <map> + +extern std::filesystem::path configurationFile;; +extern std::filesystem::path configHeaderFile; + +int configure(int argc, char* argv[]); + +bool hasConfiguration(const std::string& key); +const std::string& getConfiguration(const std::string& key, +                                    const std::string& defaultValue); + +const std::map<std::string, std::string>& configuration(); + +extern const std::map<std::string, std::string> default_configuration; diff --git a/src/execute.cc b/src/execute.cc new file mode 100644 index 0000000..bc4cd5f --- /dev/null +++ b/src/execute.cc @@ -0,0 +1,87 @@ +#include "execute.h" + +#include <unistd.h> +#include <cstring> +#include <sys/types.h> +#include <sys/wait.h> +#include <spawn.h> +#include <iostream> + +/* +https://blog.famzah.net/2009/11/20/a-much-faster-popen-and-system-implementation-for-linux/ +https://github.com/famzah/popen-noshell/commit/1f9eaf4eeef348d1efe0f3c7fe8ab670653cfbb1 +https://blog.famzah.net/2018/12/19/posix_spawn-performance-benchmarks-and-usage-examples/ +https://stackoverflow.com/questions/4259629/what-is-the-difference-between-fork-and-vfork/5207945#5207945 + */ + + +namespace +{ +int parent_waitpid(pid_t pid) +{ +	int status; + +	if(waitpid(pid, &status, 0) != pid) +	{ +		return 1; +	} + +	return status; +} +} // namespace :: + +int execute(const std::string& command, +            const std::vector<std::string>& args, +            bool verbose) +{ +	std::vector<const char*> argv; +	argv.push_back(command.data()); +	for(const auto& arg : args) +	{ +		argv.push_back(arg.data()); +	} +	argv.push_back(nullptr); + +	if(verbose) +	{ +		std::string cmd; +		for(const auto& arg : argv) +		{ +			if(arg == nullptr) +			{ +				break; +			} +			if(!cmd.empty()) +			{ +				cmd += " "; +			} +			cmd += arg; +		} + +		std::cout << cmd << "\n"; +	} + +#if 1 +	auto pid = vfork(); +	if(pid == 0) +	{ +		execv(command.data(), (char**)argv.data()); +		std::cout << "Could not execute " << command << ": " << +			strerror(errno) << "\n"; +		_exit(1); // execv only returns if an error occurred +	} +	auto ret = parent_waitpid(pid); +#elif 0 +	pid_t pid; +	if(posix_spawn(&pid, command.data(), nullptr, nullptr, +	               (char**)argv.data(), nullptr)) +	{ +		return 1; +	} +	auto ret = parent_waitpid(pid); +#else +	auto ret = system(cmd.data()); +#endif + +	return ret; +} diff --git a/src/execute.h b/src/execute.h new file mode 100644 index 0000000..f284230 --- /dev/null +++ b/src/execute.h @@ -0,0 +1,9 @@ +// -*- c++ -*- +#pragma once + +#include <string> +#include <vector> + +int execute(const std::string& command, +            const std::vector<std::string>& args, +            bool verbose = true); diff --git a/src/libcppbuild.cc b/src/libcppbuild.cc new file mode 100644 index 0000000..d3d8a51 --- /dev/null +++ b/src/libcppbuild.cc @@ -0,0 +1,316 @@ +#include <vector> +#include <string> +#include <filesystem> +#include <iostream> +#include <utility> +#include <list> +#include <thread> +#include <memory> +#include <algorithm> +#include <list> +#include <array> +#include <deque> +#include <fstream> +#include <cstdlib> +#include <set> + +#include <getoptpp/getoptpp.hpp> + +#include "libcppbuild.h" +#include "settings.h" +#include "configure.h" +#include "rebuild.h" +#include "tasks.h" +#include "build.h" +int main(int argc, char* argv[]) +{ +	if(argc > 1 && std::string(argv[1]) == "configure") +	{ +		return configure(argc, argv); +	} + +	Settings settings{}; + +	settings.builddir = getConfiguration(cfg::builddir, "build"); +	settings.parallel_processes = +		std::max(1u, std::thread::hardware_concurrency() * 2 - 1); +	settings.verbose = 0; + +	bool write_compilation_database{false}; +	std::string compilation_database; +	bool print_configure_cmd{false}; +	bool print_configure_db{false}; +	std::vector<std::string> add_files; +	std::vector<std::string> remove_files; +	bool list_files{false}; +	bool list_targets{false}; +	bool no_relaunch{false}; // true means no re-launch after rebuild. + +	dg::Options opt; +	int key{128}; + +	opt.add("jobs", required_argument, 'j', +	        "Number of parallel jobs. (default: cpucount * 2 - 1)", +	        [&]() { +		        try +		        { +			        settings.parallel_processes = std::stoi(optarg); +		        } +		        catch(...) +		        { +			        std::cerr << "Not a number\n"; +			        return 1; +		        } +		        return 0; +	        }); + +	opt.add("build-dir", required_argument, 'b', +	        "Overload output directory for build files (default: '" + +	        settings.builddir + "').", +	        [&]() { +		        settings.builddir = optarg; +		        return 0; +	        }); + +	opt.add("verbose", no_argument, 'v', +	        "Be verbose. Add multiple times for more verbosity.", +	        [&]() { +		        settings.verbose++; +		        return 0; +	        }); + +	opt.add("add", required_argument, 'a', +	        "Add specified file to the build configurations.", +	        [&]() { +		        no_relaunch = true; +		        add_files.push_back(optarg); +		        return 0; +	        }); + +	opt.add("remove", required_argument, 'r', +	        "Remove specified file from the build configurations.", +	        [&]() { +		        no_relaunch = true; +		        remove_files.push_back(optarg); +		        return 0; +	        }); + +	opt.add("list-files", no_argument, 'L', +	        "List files in the build configurations.", +	        [&]() { +		        no_relaunch = true; +		        list_files = true; +		        return 0; +	        }); + +	opt.add("list-targets", no_argument, 'l', +	        "List targets.", +	        [&]() { +		        no_relaunch = true; +		        list_targets = true; +		        return 0; +	        }); + +	opt.add("configure-cmd", no_argument, key++, +	        "Print commandline for last configure.", +	        [&]() { +		        no_relaunch = true; +		        print_configure_cmd = true; +		        return 0; +	        }); + +	opt.add("configure-db", no_argument, key++, +	        "Print entire configure parameter database.", +	        [&]() { +		        no_relaunch = true; +		        print_configure_db = true; +		        return 0; +	        }); + +	opt.add("database", required_argument, 'd', +	        "Write compilation database json file.", +	        [&]() { +		        no_relaunch = true; +		        write_compilation_database = true; +		        compilation_database = optarg; +		        return 0; +	        }); + +	opt.add("help", no_argument, 'h', +	        "Print this help text.", +	        [&]() { +		        std::cout << "Usage: " << argv[0] << " [options] [target] ...\n"; +		        std::cout << +R"_( where target can be either: +   configure - run configuration step (cannot be used with other targets). +   clean     - clean all generated files. +   all       - build all targets (default) + or the name of a target which will be built along with its dependencies. + Use '-l' to see a list of possible target names. + +Options: +)_"; +		        opt.help(); +		        exit(0); +		        return 0; +	        }); + +	opt.process(argc, argv); + +	auto verbose_env = std::getenv("V"); +	if(verbose_env) +	{ +		settings.verbose = std::atoi(verbose_env); +	} + +	if(list_files) +	{ +		std::set<std::string> files; +		for(std::size_t i = 0; i < numConfigFiles; ++i) +		{ +			files.insert(configFiles[i].file); +		} + +		for(const auto& file : files) +		{ +			std::cout << file << "\n"; +		} +	} + +	if(!add_files.empty() || !remove_files.empty()) +	{ +		for(const auto& add_file : add_files) +		{ +			reg(add_file.data(), [](){ return std::vector<BuildConfiguration>{};}); +		} + +		for(const auto& remove_file : remove_files) +		{ +			unreg(remove_file.data()); +		} + +		// Force rebuild if files were added +		recompileCheck(settings, 1, argv, true, no_relaunch == false); +	} + +	recompileCheck(settings, argc, argv); + +	std::filesystem::path builddir(settings.builddir); +	std::filesystem::create_directories(builddir); + +	auto all_tasks = getTasks(settings); + +	if(list_targets) +	{ +		for(const auto& task : all_tasks) +		{ +			if(task->targetType() != TargetType::Object) +			{ +				std::cout << task->name() << "\n"; +			} +		} +	} + +	if(write_compilation_database) +	{ +		std::ofstream istr(compilation_database); +		istr << "["; +		bool first{true}; +		for(auto task : all_tasks) +		{ +			auto s = task->toJSON(); +			if(!s.empty()) +			{ +				if(!first) +				{ +					istr << ",\n"; +				} +				else +				{ +					istr << "\n"; +				} +				first = false; +				istr << s; +			} +		} +		istr << "\n]\n"; +	} + +	if(print_configure_cmd) +	{ +		std::cout << getConfiguration("cmd") << "\n"; +	} + +	if(print_configure_db) +	{ +		const auto& c = configuration(); +		for(const auto& config : c) +		{ +			std::cout << config.first << ": " << config.second << "\n"; +		} +	} + +	for(auto task : all_tasks) +	{ +		if(task->registerDepTasks(all_tasks)) +		{ +			return 1; +		} +	} + +	bool build_all{true}; +	for(const auto& arg : opt.arguments()) +	{ +		if(arg == "configure") +		{ +			std::cerr << "The 'configure' target must be the first argument.\n"; +			return 1; +		} + +		if(arg == "clean") +		{ +			build_all = false; + +			std::cout << "Cleaning\n"; +			for(auto& task : all_tasks) +			{ +				if(task->clean() != 0) +				{ +					return 1; +				} +			} +		} +		else +		{ +			build_all = false; + +			if(arg == "all") +			{ +				auto ret = build(settings, "all", all_tasks, all_tasks); +				if(ret != 0) +				{ +					return ret; +				} +			} +			else +			{ +				auto ret = build(settings, arg, all_tasks); +				if(ret != 0) +				{ +					return ret; +				} +			} +		} +	} + +	if(build_all) +	{ +		auto ret = build(settings, "all", all_tasks, all_tasks); +		if(ret != 0) +		{ +			return ret; +		} +	} + +	return 0; +} diff --git a/src/libcppbuild.h b/src/libcppbuild.h new file mode 100644 index 0000000..d0a0080 --- /dev/null +++ b/src/libcppbuild.h @@ -0,0 +1,76 @@ +// -*- c++ -*- +#pragma once + +#include <string> +#include <vector> +#include <map> + +enum class TargetType +{ +	Auto, // Default - deduce from target name and sources extensions + +	Executable, +	StaticLibrary, +	DynamicLibrary, +	Object, +}; + +enum class Language +{ +	Auto, // Default - deduce language from source extensions + +	C, +	Cpp, +	Asm, +}; + +enum class OutputSystem +{ +	Host, // Output for the target system +	Build, // Internal tool during cross-compilation +}; + +struct BuildConfiguration +{ +	TargetType type{TargetType::Auto}; +	Language language{Language::Auto}; +	OutputSystem system{OutputSystem::Host}; +	std::string target; +	std::vector<std::string> sources; // source list +	std::vector<std::string> depends; // internal dependencies +	std::vector<std::string> cxxflags; // flags for c++ compiler +	std::vector<std::string> cflags; // flags for c compiler +	std::vector<std::string> ldflags; // flags for linker +	std::vector<std::string> asmflags; // flags for asm translator +}; + +using BuildConfigurations = std::vector<BuildConfiguration>; + +int reg(const char* location, BuildConfigurations (*cb)()); + +// Convenience macro - ugly but keeps things simple(r) +#define CONCAT(a, b) CONCAT_INNER(a, b) +#define CONCAT_INNER(a, b) a ## b +#define UNIQUE_NAME(base) CONCAT(base, __LINE__) +#define REG(cb) namespace { int UNIQUE_NAME(unique) = reg(__FILE__, cb); } + +// Predefined configuration keys +namespace cfg +{ +constexpr auto builddir = "builddir"; + +constexpr auto host_cc = "host-cc"; +constexpr auto host_cxx = "host-cpp"; +constexpr auto host_ar = "host-ar"; +constexpr auto host_ld = "host-ld"; + +constexpr auto build_cc = "build-cc"; +constexpr auto build_cxx = "build-cpp"; +constexpr auto build_ar = "build-ar"; +constexpr auto build_ld = "build-ld"; +} + +const std::map<std::string, std::string>& configuration(); +bool hasConfiguration(const std::string& key); +const std::string& getConfiguration(const std::string& key, +                                    const std::string& defaultValue = {}); diff --git a/src/rebuild.cc b/src/rebuild.cc new file mode 100644 index 0000000..43c4c98 --- /dev/null +++ b/src/rebuild.cc @@ -0,0 +1,141 @@ +#include "rebuild.h" + +#include <iostream> +#include <filesystem> +#include <algorithm> + +#include "execute.h" +#include "configure.h" +#include "settings.h" +#include "libcppbuild.h" + +std::array<BuildConfigurationEntry, 1024> configFiles; +std::size_t numConfigFiles{0}; + +// TODO: Use c++20 when ready, somehing like this: +//int reg(const std::source_location location = std::source_location::current()) +int reg(const char* location, std::vector<BuildConfiguration> (*cb)()) +{ +	// NOTE: std::cout cannot be used here +	if(numConfigFiles >= configFiles.size()) +	{ +		fprintf(stderr, "Max %d build configurations currently supported.\n", +		        (int)configFiles.size()); +		exit(1); +	} + +	configFiles[numConfigFiles].file = location; +	configFiles[numConfigFiles].cb = cb; +	++numConfigFiles; + +	return 0; +} + +int unreg(const char* location) +{ +	std::size_t found{0}; +	for(std::size_t i = 0; i < numConfigFiles;) +	{ +		if(std::string(location) == configFiles[i].file) +		{ +			++found; +			for(std::size_t j = i; j < numConfigFiles; ++j) +			{ +				configFiles[j] = configFiles[j + 1]; +			} +			--numConfigFiles; +		} +		else +		{ +			++i; +		} +	} + +	return found; +} + +void recompileCheck(const Settings& settings, int argc, char* argv[], +                    bool force, bool relaunch_allowed) +{ +	bool dirty{force}; + +	std::vector<std::string> args; +	args.push_back("-s"); +	args.push_back("-O3"); +	args.push_back("-std=c++17"); +	args.push_back("-pthread"); + +	std::filesystem::path binFile(argv[0]); + +	if(std::filesystem::exists(configurationFile)) +	{ +		args.push_back(configurationFile.string()); + +		if(std::filesystem::last_write_time(binFile) <= +		   std::filesystem::last_write_time(configurationFile)) +		{ +			dirty = true; +		} + +		const auto& c = configuration(); +		if(&c == &default_configuration) +		{ +			// configuration.cc exists, but currently compiled with the default one. +			dirty = true; +		} +	} + +	if(settings.verbose > 1) +	{ +		std::cout << "Recompile check (" << numConfigFiles << "):\n"; +	} + +	for(std::size_t i = 0; i < numConfigFiles; ++i) +	{ +		std::string location = configFiles[i].file; +		if(settings.verbose > 1) +		{ +			std::cout << " - " << location << "\n"; +		} +		std::filesystem::path configFile(location); +		if(std::filesystem::last_write_time(binFile) <= +		   std::filesystem::last_write_time(configFile)) +		{ +			dirty = true; +		} + +		// Support adding multiple config functions from the same file +		if(std::find(args.begin(), args.end(), location) == std::end(args)) +		{ +			args.push_back(location); +		} +	} +	args.push_back("libcppbuild.a"); +	args.push_back("-o"); +	args.push_back(binFile.string()); + +	if(dirty) +	{ +		std::cout << "Rebuilding config\n"; +		auto tool = getConfiguration(cfg::build_cxx, "/usr/bin/g++"); +		auto ret = execute(tool, args, settings.verbose > 0); +		if(ret != 0) +		{ +			std::cerr << "Failed: ." << ret << "\n"; +			exit(1); +		} +		else +		{ +			if(relaunch_allowed) +			{ +				std::cout << "Re-launch\n"; +				std::vector<std::string> args; +				for(int i = 1; i < argc; ++i) +				{ +					args.push_back(argv[i]); +				} +				exit(execute(argv[0], args, settings.verbose > 0)); +			} +		} +	} +} diff --git a/src/rebuild.h b/src/rebuild.h new file mode 100644 index 0000000..bc5d889 --- /dev/null +++ b/src/rebuild.h @@ -0,0 +1,24 @@ +// -*- c++ -*- +#pragma once + +#include <vector> +#include <array> + +#include "libcppbuild.h" + +class Settings; + +struct BuildConfigurationEntry +{ +	const char* file; +	std::vector<BuildConfiguration> (*cb)(); +}; + +extern std::array<BuildConfigurationEntry, 1024> configFiles; +extern std::size_t numConfigFiles; + +//int reg(const char* location, std::vector<BuildConfiguration> (*cb)()); +int unreg(const char* location); + +void recompileCheck(const Settings& settings, int argc, char* argv[], +                    bool force = false, bool relaunch_allowed = true); diff --git a/src/settings.h b/src/settings.h new file mode 100644 index 0000000..6b8729f --- /dev/null +++ b/src/settings.h @@ -0,0 +1,11 @@ +// -*- c++ -*- +#pragma once + +#include <cstddef> + +struct Settings +{ +	std::string builddir; +	std::size_t parallel_processes; +	int verbose{1}; +}; diff --git a/src/task.cc b/src/task.cc new file mode 100644 index 0000000..962a02b --- /dev/null +++ b/src/task.cc @@ -0,0 +1,146 @@ +#include "task.h" + +#include <unistd.h> +#include <iostream> + +Task::Task(const BuildConfiguration& config, +           const std::vector<std::string>& depends) +	: dependsStr(depends) +	, config(config) +	, output_system(config.system) +{ +} + +int Task::registerDepTasks(const std::list<std::shared_ptr<Task>>& tasks) +{ +	for(auto const& depStr : dependsStr) +	{ +		bool found{false}; +		for(const auto& task : tasks) +		{ +			if(task->target() == depStr) +			{ +				dependsTasks.push_back(task); +				found = true; +			} +		} +		if(!found) +		{ +			std::cerr << "Could not find dependency " << depStr << " needed by " << +				target() << " target\n"; +			return 1; +		} +	} + +	return 0; +} + +std::string Task::name() const +{ +	return config.target; +} + +bool Task::dirty() +{ +	for(const auto& task : dependsTasks) +	{ +		if(task->dirty()) +		{ +			return true; +		} +	} + +	return dirtyInner(); +} + +bool Task::ready() +{ +	for(const auto& task : dependsTasks) +	{ +		if(task->dirty() || task->state() == State::Running) +		{ +			return false; +		} +	} + +	task_state.store(State::Ready); +	return true; +} + +int Task::run() +{ +	if(task_state.load() == State::Done) +	{ +		return 0; +	} + +	task_state.store(State::Running); +	auto ret = runInner(); +	if(ret == 0) +	{ +		task_state.store(State::Done); +	} +	else +	{ +		task_state.store(State::Error); +	} + +	return ret; +} + +State Task::state() const +{ +	return task_state.load(); +} + +const BuildConfiguration& Task::buildConfig() const +{ +	return config; +} + +TargetType Task::targetType() const +{ +	return target_type; +} + +Language Task::sourceLanguage() const +{ +	return source_language; +} + +OutputSystem Task::outputSystem() const +{ +	return output_system; +} + +std::string Task::compiler() const +{ +	switch(sourceLanguage()) +	{ +	case Language::C: +		switch(outputSystem()) +		{ +		case OutputSystem::Host: +			return getConfiguration(cfg::host_cc, "/usr/bin/gcc"); +		case OutputSystem::Build: +			return getConfiguration(cfg::build_cc, "/usr/bin/gcc"); +		} +	case Language::Cpp: +		switch(outputSystem()) +		{ +		case OutputSystem::Host: +			return getConfiguration(cfg::host_cxx, "/usr/bin/g++"); +		case OutputSystem::Build: +			return getConfiguration(cfg::build_cxx, "/usr/bin/g++"); +		} +	default: +		std::cerr << "Unknown CC target type\n"; +		exit(1); +		break; +	} +} + +std::list<std::shared_ptr<Task>> Task::getDependsTasks() +{ +	return dependsTasks; +} diff --git a/src/task.h b/src/task.h new file mode 100644 index 0000000..98363a1 --- /dev/null +++ b/src/task.h @@ -0,0 +1,60 @@ +// -*- c++ -*- +#pragma once + +#include <vector> +#include <string> +#include <atomic> +#include <list> +#include <memory> + +#include "libcppbuild.h" + +enum class State +{ +	Unknown, +	Ready, +	Running, +	Done, +	Error, +}; + +class Task +{ +public: +	Task(const BuildConfiguration& config, +	     const std::vector<std::string>& depends = {}); + +	int registerDepTasks(const std::list<std::shared_ptr<Task>>& tasks); + +	virtual std::string name() const; +	bool dirty(); +	bool ready(); +	int run(); +	State state() const; +	virtual int clean() = 0 ; +	virtual std::vector<std::string> depends() const = 0; +	virtual std::string target() const = 0; + +	virtual std::string toJSON() const { return {}; }; + +	const BuildConfiguration& buildConfig() const; + +	TargetType targetType() const; +	Language sourceLanguage() const; +	OutputSystem outputSystem() const; +	std::string compiler() const; + +	std::list<std::shared_ptr<Task>> getDependsTasks(); + +protected: +	std::atomic<State> task_state{State::Unknown}; +	virtual int runInner() { return 0; }; +	virtual bool dirtyInner() { return false; } + +	std::vector<std::string> dependsStr; +	std::list<std::shared_ptr<Task>> dependsTasks; +	const BuildConfiguration& config; +	TargetType target_type{TargetType::Auto}; +	Language source_language{Language::Auto}; +	OutputSystem output_system{OutputSystem::Host}; +}; diff --git a/src/task_ar.cc b/src/task_ar.cc new file mode 100644 index 0000000..5568629 --- /dev/null +++ b/src/task_ar.cc @@ -0,0 +1,221 @@ +#include "task_ar.h" + +#include <iostream> +#include <fstream> + +#include "libcppbuild.h" +#include "settings.h" +#include "execute.h" + +namespace +{ +std::string readFile(const std::string &fileName) +{ +	std::ifstream ifs(fileName.c_str(), +	                  std::ios::in | std::ios::binary | std::ios::ate); + +	std::ifstream::pos_type fileSize = ifs.tellg(); +	ifs.seekg(0, std::ios::beg); + +	std::vector<char> bytes(fileSize); +	ifs.read(bytes.data(), fileSize); + +	return std::string(bytes.data(), fileSize); +} + +std::vector<std::string> addPrefix(const std::vector<std::string>& lst, +                                   const Settings& settings) +{ +	std::vector<std::string> out; +	for(const auto& item : lst) +	{ +		std::filesystem::path file = settings.builddir; +		file /= item; +		out.push_back(file.string()); +	} +	return out; +} +} // namespace :: + +TaskAR::TaskAR(const BuildConfiguration& config, +               const Settings& settings, +               const std::string& target, +               const std::vector<std::string>& objects) +	: Task(config, addPrefix(config.depends, settings)) +	, config(config) +	, settings(settings) +{ +	targetFile = settings.builddir; +	targetFile /= target; +	for(const auto& object : objects) +	{ +		std::filesystem::path objectFile = object; +		objectFiles.push_back(objectFile); +		dependsStr.push_back(objectFile.string()); +	} + +	for(const auto& dep : config.depends) +	{ +		std::filesystem::path depFile = settings.builddir; +		depFile /= dep; +		depFiles.push_back(depFile); +	} + +	flagsFile = settings.builddir / targetFile.stem(); +	flagsFile += ".flags"; + +	target_type = TargetType::StaticLibrary; +	source_language = Language::C; +	for(const auto& source : config.sources) +	{ +		std::filesystem::path sourceFile(source); +		if(sourceFile.extension().string() != ".c") +		{ +			source_language = Language::Cpp; +		} +	} +} + +bool TaskAR::dirtyInner() +{ +	if(!std::filesystem::exists(targetFile)) +	{ +		return true; +	} + +	if(!std::filesystem::exists(flagsFile)) +	{ +		return true; +	} + +	for(const auto& objectFile : objectFiles) +	{ +		if(std::filesystem::last_write_time(targetFile) <= +		   std::filesystem::last_write_time(objectFile)) +		{ +			return true; +		} +	} + +	{ +		auto lastFlags = readFile(flagsFile.string()); +		if(flagsString() != lastFlags) +		{ +			//std::cout << "The compiler flags changed\n"; +			return true; +		} +	} + +	return false; +} + +int TaskAR::runInner() +{ +	std::string objectlist; +	for(const auto& objectFile : objectFiles) +	{ +		if(!objectlist.empty()) +		{ +			objectlist += " "; +		} +		objectlist += objectFile.string(); +	} + +	std::vector<std::string> args; +	args.push_back("rcs"); +	args.push_back(targetFile.string()); +	for(const auto& objectFile : objectFiles) +	{ +		args.push_back(objectFile.string()); +	} +	for(const auto& flag : config.ldflags) +	{ +		args.push_back(flag); +	} + +	{ // Write flags to file. +		std::ofstream flagsStream(flagsFile); +		flagsStream << flagsString(); +	} + +	if(settings.verbose == 0) +	{ +		std::cout << "AR => " << targetFile.string() << "\n"; +	} + +	std::string tool; +	switch(outputSystem()) +	{ +	case OutputSystem::Host: +		tool = getConfiguration(cfg::host_ar, "/usr/bin/ar"); +		break; +	case OutputSystem::Build: +		tool = getConfiguration(cfg::build_ar, "/usr/bin/ar"); +		break; +	} + +	return execute(tool, args, settings.verbose > 0); +} + +int TaskAR::clean() +{ +	if(std::filesystem::exists(targetFile)) +	{ +		std::cout << "Removing " << targetFile.string() << "\n"; +		std::filesystem::remove(targetFile); +	} + +	if(std::filesystem::exists(flagsFile)) +	{ +		std::cout << "Removing " << flagsFile.string() << "\n"; +		std::filesystem::remove(flagsFile); +	} + +	return 0; +} + +std::vector<std::string> TaskAR::depends() const +{ +	std::vector<std::string> deps; +	for(const auto& objectFile : objectFiles) +	{ +		deps.push_back(objectFile.string()); +	} + +	for(const auto& depFile : depFiles) +	{ +		deps.push_back(depFile.string()); +	} + +	return deps; +} + +std::string TaskAR::target() const +{ +	return targetFile.string(); +} + +std::string TaskAR::flagsString() const +{ +	std::string flagsStr; +	for(const auto& flag : config.ldflags) +	{ +		if(flag != config.ldflags[0]) +		{ +			flagsStr += " "; +		} +		flagsStr += flag; +	} +	flagsStr += "\n"; + +	for(const auto& dep : config.depends) +	{ +		if(dep != config.depends[0]) +		{ +			flagsStr += " "; +		} +		flagsStr += dep; +	} + +	return flagsStr; +} diff --git a/src/task_ar.h b/src/task_ar.h new file mode 100644 index 0000000..bfa21a2 --- /dev/null +++ b/src/task_ar.h @@ -0,0 +1,42 @@ +// -*- c++ -*- +#pragma once + +#include "task.h" + +#include <vector> +#include <string> +#include <future> +#include <filesystem> + +struct BuildConfiguration; +struct Settings; + +class TaskAR +	: public Task +{ +public: +	TaskAR(const BuildConfiguration& config, +	       const Settings& settings, +	       const std::string& target, +	       const std::vector<std::string>& objects); + +	bool dirtyInner() override; + +	int runInner() override; +	int clean() override; + +	std::vector<std::string> depends() const override; + +	std::string target() const override; + +private: +	std::string flagsString() const; + +	std::vector<std::filesystem::path> objectFiles; +	std::vector<std::filesystem::path> depFiles; +	std::filesystem::path targetFile; +	std::filesystem::path flagsFile; + +	const BuildConfiguration& config; +	const Settings& settings; +}; diff --git a/src/task_cc.cc b/src/task_cc.cc new file mode 100644 index 0000000..af9cf7a --- /dev/null +++ b/src/task_cc.cc @@ -0,0 +1,339 @@ +#include "task_cc.h" + +#include <iostream> +#include <fstream> + +#include "libcppbuild.h" +#include "settings.h" +#include "execute.h" + +namespace +{ +std::string readFile(const std::string &fileName) +{ +	std::ifstream ifs(fileName.c_str(), std::ios::in | std::ios::binary | std::ios::ate); + +	std::ifstream::pos_type fileSize = ifs.tellg(); +	ifs.seekg(0, std::ios::beg); + +	std::vector<char> bytes(fileSize); +	ifs.read(bytes.data(), fileSize); + +	return std::string(bytes.data(), fileSize); +} + +std::vector<std::string> readDeps(const std::string& depFile) +{ +	if(!std::filesystem::exists(depFile)) +	{ +		return {}; +	} + +	auto str = readFile(depFile); + +	std::vector<std::string> output; +	std::string tmp; +	bool start{false}; +	bool in_whitespace{false}; +	for(const auto& c : str) +	{ +		if(c == '\\' || c == '\n') +		{ +			continue; +		} + +		if(c == ':') +		{ +			start = true; +			continue; +		} + +		if(!start) +		{ +			continue; +		} + +		if(c == ' ' || c == '\t') +		{ +			if(in_whitespace) +			{ +				continue; +			} + +			if(!tmp.empty()) +			{ +				output.push_back(tmp); +			} +			tmp.clear(); +			in_whitespace = true; +		} +		else +		{ +			in_whitespace = false; +			tmp += c; +		} +	} + +	if(!tmp.empty()) +	{ +		output.push_back(tmp); +	} + +	return output; +} +} // namespace :: + +TaskCC::TaskCC(const BuildConfiguration& config, const Settings& settings, +               const std::string& sourceDir, const std::string& source) +	: Task(config) +	, config(config) +	, settings(settings) +	, sourceDir(sourceDir) +{ +	sourceFile = sourceDir; +	sourceFile /= source; + +	std::filesystem::path base = settings.builddir; +	base /= config.target; +	base += "-"; +	base += sourceFile.stem(); + +	if(sourceFile.extension().string() == ".c") +	{ +		base += "_c"; +	} +	else +	{ +		base += "_cc"; +	} + +	targetFile = base; +	targetFile += ".o"; +	depsFile = base; +	depsFile += ".d"; +	flagsFile = base; +	flagsFile += ".flags"; + +	target_type = TargetType::Object; +	if(sourceFile.extension().string() == ".c") +	{ +		source_language = Language::C; +	} +	else +	{ +		source_language = Language::Cpp; +	} +} + +std::string TaskCC::name() const +{ +	return target(); +} + +bool TaskCC::dirtyInner() +{ +	if(!std::filesystem::exists(sourceFile)) +	{ +		//std::cout << "Missing source file: " << std::string(sourceFile) << "\n"; +		return true; +	} + +	if(!std::filesystem::exists(targetFile)) +	{ +		//std::cout << "Missing targetFile\n"; +		return true; +	} + +	if(!std::filesystem::exists(depsFile)) +	{ +		//std::cout << "Missing depsFile\n"; +		return true; +	} + +	if(!std::filesystem::exists(flagsFile)) +	{ +		//std::cout << "Missing flagsFile\n"; +		return true; +	} + +	if(std::filesystem::last_write_time(sourceFile) > +	   std::filesystem::last_write_time(depsFile)) +	{ +		//std::cout << "The sourceFile newer than depsFile\n"; +		return true; +	} + +	{ +		auto lastFlags = readFile(flagsFile.string()); +		if(flagsString() != lastFlags) +		{ +			//std::cout << "The compiler flags changed\n"; +			return true; +		} +	} + +	auto depList = readDeps(depsFile.string()); +	for(const auto& dep : depList) +	{ +		if(!std::filesystem::exists(dep) || +		   std::filesystem::last_write_time(targetFile) < +		   std::filesystem::last_write_time(dep)) +		{ +			//std::cout << "The targetFile older than " << std::string(dep) << "\n"; +			return true; +		} +	} + +	if(std::filesystem::last_write_time(sourceFile) > +	   std::filesystem::last_write_time(targetFile)) +	{ +		//std::cout << "The targetFile older than sourceFile\n"; +		return true; +	} + +	return false; +} + +int TaskCC::runInner() +{ +	if(!std::filesystem::exists(sourceFile)) +	{ +		std::cout << "Missing source file: " << sourceFile.string() << "\n"; +		return 1; +	} + +	auto args = getCompilerArgs(); + +	{ // Write flags to file. +		std::ofstream flagsStream(flagsFile.string()); +		flagsStream << flagsString(); +	} + +	if(settings.verbose == 0) +	{ +		std::cout << compiler() << " " << +			sourceFile.lexically_normal().string() << " => " << +			targetFile.lexically_normal().string() << "\n"; +	} + +	return execute(compiler(), args, settings.verbose > 0); +} + +int TaskCC::clean() +{ +	if(std::filesystem::exists(targetFile)) +	{ +		std::cout << "Removing " << targetFile.string() << "\n"; +		std::filesystem::remove(targetFile); +	} + +	if(std::filesystem::exists(depsFile)) +	{ +		std::cout << "Removing " << depsFile.string() << "\n"; +		std::filesystem::remove(depsFile); +	} + +	if(std::filesystem::exists(flagsFile)) +	{ +		std::cout << "Removing " << flagsFile.string() << "\n"; +		std::filesystem::remove(flagsFile); +	} + +	return 0; +} + +std::vector<std::string> TaskCC::depends() const +{ +	return {}; +} + +std::string TaskCC::target() const +{ +	return targetFile.string(); +} + +std::string TaskCC::toJSON() const +{ +	std::string json; +	json += "\t{\n"; +	json += "\t\t\"directory\": \"" + sourceDir.string() + "\",\n"; +	json += "\t\t\"file\": \"" + sourceFile.lexically_normal().string() + "\",\n"; +	json += "\t\t\"output\": \"" + targetFile.string() + "\",\n"; +	json += "\t\t\"arguments\": [ \"" + compiler() + "\""; +	auto args = getCompilerArgs(); +	for(const auto& arg : args) +	{ +		json += ", \"" + arg + "\""; +	} +	json += " ]\n"; +	json += "\t}"; +	return json; +} + +std::vector<std::string> TaskCC::flags() const +{ +	switch(sourceLanguage()) +	{ +	case Language::C: +		return config.cflags; +	case Language::Cpp: +		return config.cxxflags; +	default: +		std::cerr << "Unknown CC target type\n"; +		exit(1); +		break; +	} +} + +std::string TaskCC::flagsString() const +{ +	std::string flagsStr = compiler(); +	for(const auto& flag : flags()) +	{ +		flagsStr += " " + flag; +	} +	return flagsStr; +} + +std::vector<std::string> TaskCC::getCompilerArgs() const +{ +	auto compiler_flags = flags(); + +	std::vector<std::string> args; +	args.push_back("-MMD"); + +	if(std::filesystem::path(config.target).extension() == ".so") +	{ +		// Add -fPIC arg to all contained object files +		args.push_back("-fPIC"); +	} + +	args.push_back("-c"); +	args.push_back(sourceFile.string()); +	args.push_back("-o"); +	args.push_back(targetFile.string()); + +	for(const auto& flag : compiler_flags) +	{ +		// Is arg an added include path? +		if(flag.substr(0, 2) == "-I") +		{ +			std::string include_path = flag.substr(2); +			include_path.erase(0, include_path.find_first_not_of(' ')); +			std::filesystem::path path(include_path); + +			// Is it relative? +			if(path.is_relative()) +			{ +				path = (sourceDir / path).lexically_normal(); +				std::string new_include_path = "-I" + path.string(); +				args.push_back(new_include_path); +				continue; +			} +		} + +		args.push_back(flag); +	} + +	return args; +} diff --git a/src/task_cc.h b/src/task_cc.h new file mode 100644 index 0000000..0ce4947 --- /dev/null +++ b/src/task_cc.h @@ -0,0 +1,47 @@ +// -*- c++ -*- +#pragma once + +#include "task.h" + +#include <vector> +#include <string> +#include <future> +#include <filesystem> + +struct BuildConfiguration; +struct Settings; + +class TaskCC +	: public Task +{ +public: +	TaskCC(const BuildConfiguration& config, +	       const Settings& settings, +	       const std::string& sourceDir, const std::string& source); + +	std::string name() const override; +	bool dirtyInner() override; + +	int runInner() override; +	int clean() override; + +	std::vector<std::string> depends() const override; + +	std::string target() const override; + +	std::string toJSON() const override; + +private: +	std::vector<std::string> flags() const; +	std::string flagsString() const; +	std::vector<std::string> getCompilerArgs() const; + +	std::filesystem::path sourceFile; +	std::filesystem::path targetFile; +	std::filesystem::path depsFile; +	std::filesystem::path flagsFile; + +	const BuildConfiguration& config; +	const Settings& settings; +	std::filesystem::path sourceDir; +}; diff --git a/src/task_ld.cc b/src/task_ld.cc new file mode 100644 index 0000000..91f3316 --- /dev/null +++ b/src/task_ld.cc @@ -0,0 +1,227 @@ +#include "task_ld.h" + +#include <iostream> +#include <fstream> + +#include "libcppbuild.h" +#include "settings.h" +#include "execute.h" + +namespace +{ +std::string readFile(const std::string &fileName) +{ +	std::ifstream ifs(fileName.c_str(), std::ios::in | std::ios::binary | std::ios::ate); + +	std::ifstream::pos_type fileSize = ifs.tellg(); +	ifs.seekg(0, std::ios::beg); + +	std::vector<char> bytes(fileSize); +	ifs.read(bytes.data(), fileSize); + +	return std::string(bytes.data(), fileSize); +} + +std::vector<std::string> addPrefix(const std::vector<std::string>& lst, +                                   const Settings& settings) +{ +	std::vector<std::string> out; +	for(const auto& item : lst) +	{ +		std::filesystem::path file = settings.builddir; +		file /= item; +		out.push_back(file.string()); +	} +	return out; +} +} // namespace :: + +TaskLD::TaskLD(const BuildConfiguration& config, +               const Settings& settings, +               const std::string& target, +               const std::vector<std::string>& objects) +	: Task(config, addPrefix(config.depends, settings)) +	, config(config) +	, settings(settings) +{ +	target_type = TargetType::Executable; + +	targetFile = settings.builddir; +	targetFile /= target; +	for(const auto& object : objects) +	{ +		std::filesystem::path objectFile = object; +		objectFiles.push_back(objectFile); +		dependsStr.push_back(objectFile.string()); +	} + +	for(const auto& dep : config.depends) +	{ +		std::filesystem::path depFile = settings.builddir; +		depFile /= dep; +		depFiles.push_back(depFile); +	} + +	flagsFile = settings.builddir / targetFile.stem(); +	flagsFile += ".flags"; + +	target_type = TargetType::Executable; +	source_language = Language::C; +	for(const auto& source : config.sources) +	{ +		std::filesystem::path sourceFile(source); +		if(sourceFile.extension().string() != ".c") +		{ +			source_language = Language::Cpp; +		} +	} +} + +bool TaskLD::dirtyInner() +{ +	if(!std::filesystem::exists(targetFile)) +	{ +		return true; +	} + +	if(!std::filesystem::exists(flagsFile)) +	{ +		return true; +	} + +	for(const auto& objectFile : objectFiles) +	{ +		if(std::filesystem::last_write_time(targetFile) <= +		   std::filesystem::last_write_time(objectFile)) +		{ +			return true; +		} +	} + +	{ +		auto lastFlags = readFile(flagsFile.string()); +		if(flagsString() != lastFlags) +		{ +			//std::cout << "The compiler flags changed\n"; +			return true; +		} +	} + +	return false; +} + +int TaskLD::runInner() +{ +	std::string objectlist; +	for(const auto& objectFile : objectFiles) +	{ +		if(!objectlist.empty()) +		{ +			objectlist += " "; +		} +		objectlist += objectFile.string(); +	} + +	std::vector<std::string> args; +	for(const auto& objectFile : objectFiles) +	{ +		args.push_back(objectFile.string()); +	} + +	for(const auto& depFile : depFiles) +	{ +		if(depFile.extension() == ".so") +		{ +			args.push_back(std::string("-L") + settings.builddir); +			auto lib = depFile.stem().string().substr(3); // strip 'lib' prefix +			args.push_back(std::string("-l") + lib); +		} +		else if(depFile.extension() == ".a") +		{ +			args.push_back(depFile.string()); +		} +	} + +	for(const auto& flag : config.ldflags) +	{ +		args.push_back(flag); +	} +	args.push_back("-o"); +	args.push_back(targetFile.string()); + +	{ // Write flags to file. +		std::ofstream flagsStream(flagsFile); +		flagsStream << flagsString(); +	} + +	if(settings.verbose == 0) +	{ +		std::cout << "LD => " << targetFile.string() << "\n"; +	} + +	auto tool = compiler(); +	return execute(tool, args, settings.verbose > 0); +} + +int TaskLD::clean() +{ +	if(std::filesystem::exists(targetFile)) +	{ +		std::cout << "Removing " << targetFile.string() << "\n"; +		std::filesystem::remove(targetFile); +	} + +	if(std::filesystem::exists(flagsFile)) +	{ +		std::cout << "Removing " << flagsFile.string() << "\n"; +		std::filesystem::remove(flagsFile); +	} + +	return 0; +} + +std::vector<std::string> TaskLD::depends() const +{ +	std::vector<std::string> deps; +	for(const auto& objectFile : objectFiles) +	{ +		deps.push_back(objectFile.string()); +	} + +	for(const auto& depFile : depFiles) +	{ +		deps.push_back(depFile.string()); +	} + +	return deps; +} + +std::string TaskLD::target() const +{ +	return targetFile.string(); +} + +std::string TaskLD::flagsString() const +{ +	std::string flagsStr; +	for(const auto& flag : config.ldflags) +	{ +		if(flag != config.ldflags[0]) +		{ +			flagsStr += " "; +		} +		flagsStr += flag; +	} +	flagsStr += "\n"; + +	for(const auto& dep : config.depends) +	{ +		if(dep != config.depends[0]) +		{ +			flagsStr += " "; +		} +		flagsStr += dep; +	} + +	return flagsStr; +} diff --git a/src/task_ld.h b/src/task_ld.h new file mode 100644 index 0000000..f56f00d --- /dev/null +++ b/src/task_ld.h @@ -0,0 +1,42 @@ +// -*- c++ -*- +#pragma once + +#include "task.h" + +#include <vector> +#include <string> +#include <future> +#include <filesystem> + +struct BuildConfiguration; +struct Settings; + +class TaskLD +	: public Task +{ +public: +	TaskLD(const BuildConfiguration& config, +	       const Settings& settings, +	       const std::string& target, +	       const std::vector<std::string>& objects); + +	bool dirtyInner() override; + +	int runInner() override; +	int clean() override; + +	std::vector<std::string> depends() const override; + +	std::string target() const override; + +private: +	std::string flagsString() const; + +	std::vector<std::filesystem::path> objectFiles; +	std::vector<std::filesystem::path> depFiles; +	std::filesystem::path targetFile; +	std::filesystem::path flagsFile; + +	const BuildConfiguration& config; +	const Settings& settings; +}; diff --git a/src/task_so.cc b/src/task_so.cc new file mode 100644 index 0000000..eaf6a85 --- /dev/null +++ b/src/task_so.cc @@ -0,0 +1,217 @@ +#include "task_so.h" + +#include <iostream> +#include <fstream> + +#include "libcppbuild.h" +#include "settings.h" +#include "execute.h" + +namespace +{ +std::string readFile(const std::string &fileName) +{ +	std::ifstream ifs(fileName.c_str(), std::ios::in | std::ios::binary | std::ios::ate); + +	std::ifstream::pos_type fileSize = ifs.tellg(); +	ifs.seekg(0, std::ios::beg); + +	std::vector<char> bytes(fileSize); +	ifs.read(bytes.data(), fileSize); + +	return std::string(bytes.data(), fileSize); +} + +std::vector<std::string> addPrefix(const std::vector<std::string>& lst, +                                   const Settings& settings) +{ +	std::vector<std::string> out; +	for(const auto& item : lst) +	{ +		std::filesystem::path file = settings.builddir; +		file /= item; +		out.push_back(file.string()); +	} +	return out; +} +} // namespace :: + +TaskSO::TaskSO(const BuildConfiguration& config, +               const Settings& settings, +               const std::string& target, +               const std::vector<std::string>& objects) +	: Task(config, addPrefix(config.depends, settings)) +	, config(config) +	, settings(settings) +{ +	targetFile = settings.builddir; +	targetFile /= target; +	for(const auto& object : objects) +	{ +		std::filesystem::path objectFile = object; +		objectFiles.push_back(objectFile); +		dependsStr.push_back(objectFile.string()); +	} + +	for(const auto& dep : config.depends) +	{ +		std::filesystem::path depFile = settings.builddir; +		depFile /= dep; +		depFiles.push_back(depFile); +	} + +	flagsFile = settings.builddir / targetFile.stem(); +	flagsFile += ".flags"; + +	target_type = TargetType::DynamicLibrary; +	source_language = Language::C; +	for(const auto& source : config.sources) +	{ +		std::filesystem::path sourceFile(source); +		if(sourceFile.extension().string() != ".c") +		{ +			source_language = Language::Cpp; +		} +	} +} + +bool TaskSO::dirtyInner() +{ +	if(!std::filesystem::exists(targetFile)) +	{ +		return true; +	} + +	if(!std::filesystem::exists(flagsFile)) +	{ +		return true; +	} + +	for(const auto& objectFile : objectFiles) +	{ +		if(std::filesystem::last_write_time(targetFile) <= +		   std::filesystem::last_write_time(objectFile)) +		{ +			return true; +		} +	} + +	{ +		auto lastFlags = readFile(flagsFile.string()); +		if(flagsString() != lastFlags) +		{ +			//std::cout << "The compiler flags changed\n"; +			return true; +		} +	} + +	return false; +} + +int TaskSO::runInner() +{ +	std::string objectlist; +	for(const auto& objectFile : objectFiles) +	{ +		if(!objectlist.empty()) +		{ +			objectlist += " "; +		} +		objectlist += objectFile.string(); +	} + +	std::vector<std::string> args; + +	args.push_back("-fPIC"); +	args.push_back("-shared"); + +	args.push_back("-o"); +	args.push_back(targetFile.string()); + +	for(const auto& objectFile : objectFiles) +	{ +		args.push_back(objectFile.string()); +	} + +	for(const auto& depFile : depFiles) +	{ +		args.push_back(depFile.string()); +	} + +	for(const auto& flag : config.ldflags) +	{ +		args.push_back(flag); +	} + +	{ // Write flags to file. +		std::ofstream flagsStream(flagsFile); +		flagsStream << flagsString(); +	} + +	if(settings.verbose == 0) +	{ +		std::cout << "LD => " << targetFile.string() << "\n"; +	} + +	auto tool = compiler(); +	return execute(tool, args, settings.verbose > 0); +} + +int TaskSO::clean() +{ +	if(std::filesystem::exists(targetFile)) +	{ +		std::cout << "Removing " << targetFile.string() << "\n"; +		std::filesystem::remove(targetFile); +	} + +	if(std::filesystem::exists(flagsFile)) +	{ +		std::cout << "Removing " << flagsFile.string() << "\n"; +		std::filesystem::remove(flagsFile); +	} + +	return 0; +} + +std::vector<std::string> TaskSO::depends() const +{ +	std::vector<std::string> deps; +	for(const auto& objectFile : objectFiles) +	{ +		deps.push_back(objectFile.string()); +	} + +	for(const auto& depFile : depFiles) +	{ +		deps.push_back(depFile.string()); +	} + +	return deps; +} + +std::string TaskSO::target() const +{ +	return targetFile.string(); +} + +std::string TaskSO::flagsString() const +{ +	std::string flagsStr = compiler(); +	for(const auto& flag : config.ldflags) +	{ +		flagsStr += " " + flag; +	} +	flagsStr += "\n"; + +	for(const auto& dep : config.depends) +	{ +		if(dep != config.depends[0]) +		{ +			flagsStr += " "; +		} +		flagsStr += dep; +	} + +	return flagsStr; +} diff --git a/src/task_so.h b/src/task_so.h new file mode 100644 index 0000000..864d108 --- /dev/null +++ b/src/task_so.h @@ -0,0 +1,42 @@ +// -*- c++ -*- +#pragma once + +#include "task.h" + +#include <vector> +#include <string> +#include <future> +#include <filesystem> + +struct BuildConfiguration; +struct Settings; + +class TaskSO +	: public Task +{ +public: +	TaskSO(const BuildConfiguration& config, +	       const Settings& settings, +	       const std::string& target, +	       const std::vector<std::string>& objects); + +	bool dirtyInner() override; + +	int runInner() override; +	int clean() override; + +	std::vector<std::string> depends() const override; + +	std::string target() const override; + +private: +	std::string flagsString() const; + +	std::vector<std::filesystem::path> objectFiles; +	std::vector<std::filesystem::path> depFiles; +	std::filesystem::path targetFile; +	std::filesystem::path flagsFile; + +	const BuildConfiguration& config; +	const Settings& settings; +}; diff --git a/src/tasks.cc b/src/tasks.cc new file mode 100644 index 0000000..93e5a8b --- /dev/null +++ b/src/tasks.cc @@ -0,0 +1,130 @@ +#include "tasks.h" + +#include <filesystem> +#include <deque> +#include <iostream> + +#include "settings.h" +#include "libcppbuild.h" +#include "task.h" +#include "task_cc.h" +#include "task_ld.h" +#include "task_ar.h" +#include "task_so.h" +#include "rebuild.h" + +std::list<std::shared_ptr<Task>> taskFactory(const BuildConfiguration& config, +                                             const Settings& settings, +                                             const std::string& sourceDir) +{ +	std::filesystem::path targetFile(config.target); + +	TargetType target_type{config.type}; +	if(target_type == TargetType::Auto) +	{ +		if(targetFile.extension() == ".a") +		{ +			target_type = TargetType::StaticLibrary; +		} +		else if(targetFile.extension() == ".so") +		{ +			target_type = TargetType::DynamicLibrary; +		} +		else if(targetFile.extension() == "") +		{ +			target_type = TargetType::Executable; +		} +		else +		{ +			std::cerr << "Could not deduce target type from target " << +				targetFile.string() << " please specify.\n"; +			exit(1); +		} +	} + +	std::vector<std::string> objects; +	std::list<std::shared_ptr<Task>> tasks; +	for(const auto& file : config.sources) +	{ +		tasks.emplace_back(std::make_shared<TaskCC>(config, settings, +		                                            sourceDir, file)); +		objects.push_back(tasks.back()->target()); +	} + +	switch(target_type) +	{ +	case TargetType::Auto: +		// The target_type cannot be Auto +		break; + +	case TargetType::StaticLibrary: +		tasks.emplace_back(std::make_shared<TaskAR>(config, settings, config.target, +		                                            objects)); +		break; + +	case TargetType::DynamicLibrary: +		if(targetFile.stem().string().substr(0, 3) != "lib") +		{ +			std::cerr << "Dynamic library target must have 'lib' prefix\n"; +			exit(1); +		} +		tasks.emplace_back(std::make_shared<TaskSO>(config, settings, config.target, +		                                            objects)); +		break; + +	case TargetType::Executable: +		tasks.emplace_back(std::make_shared<TaskLD>(config, settings, config.target, +		                                            objects)); +		break; + +	case TargetType::Object: +		break; +	} + +	return tasks; +} + +std::shared_ptr<Task> getNextTask(const std::list<std::shared_ptr<Task>>& allTasks, +                                  std::list<std::shared_ptr<Task>>& dirtyTasks) +{ +	for(auto dirtyTask = dirtyTasks.begin(); +	    dirtyTask != dirtyTasks.end(); +	    ++dirtyTask) +	{ +		//std::cout << "Examining target " << (*dirtyTask)->target() << "\n"; +		if((*dirtyTask)->ready()) +		{ +			dirtyTasks.erase(dirtyTask); +			return *dirtyTask; +		} +	} + +	//std::cout << "No task ready ... \n"; +	return nullptr; +} + +std::list<std::shared_ptr<Task>> getTasks(const Settings& settings) +{ +	static std::deque<BuildConfiguration> build_configs; +	std::list<std::shared_ptr<Task>> tasks; +	for(std::size_t i = 0; i < numConfigFiles; ++i) +	{ +		std::string path = +			std::filesystem::path(configFiles[i].file).parent_path().string(); +		if(settings.verbose > 1) +		{ +			std::cout << configFiles[i].file << " in path " << path << "\n"; +		} +		auto configs = configFiles[i].cb(); +		for(const auto& config : configs) +		{ +			build_configs.push_back(config); +			const auto& build_config = build_configs.back(); +			std::vector<std::string> objects; +			auto t = taskFactory(build_config, settings, path); +			tasks.insert(tasks.end(), t.begin(), t.end()); +		} +	} + +	return tasks; +} diff --git a/src/tasks.h b/src/tasks.h new file mode 100644 index 0000000..119c7d6 --- /dev/null +++ b/src/tasks.h @@ -0,0 +1,18 @@ +// -*- c++ -*- +#pragma once + +#include <string> +#include <list> +#include <memory> + +#include "task.h" + +class BuildConfiguration; +class Settings; + +std::list<std::shared_ptr<Task>> taskFactory(const BuildConfiguration& config, +                                             const Settings& settings, +                                             const std::string& sourceDir); +std::shared_ptr<Task> getNextTask(const std::list<std::shared_ptr<Task>>& allTasks, +                                  std::list<std::shared_ptr<Task>>& dirtyTasks); +std::list<std::shared_ptr<Task>> getTasks(const Settings& settings);  | 
