diff options
50 files changed, 2649 insertions, 665 deletions
| diff --git a/Jenkinsfile b/Jenkinsfile new file mode 100644 index 0000000..b483ac3 --- /dev/null +++ b/Jenkinsfile @@ -0,0 +1,27 @@ +pipeline { +	agent { label 'c++20' } + +	stages { +		stage('Build') { +			steps { +				echo 'Building...' +				sh 'rm -Rf build' +				sh './bootstrap.sh' +			} +		} +		stage('Test') { +			steps { +				echo 'Testing...' +				sh './ctor check' +			} +		} +	} + +	post { +		always { +			xunit(thresholds: [ skipped(failureThreshold: '0'), +			                    failed(failureThreshold: '0') ], +			      tools: [ CppUnit(pattern: 'build/test/*.xml') ]) +		} +	} +}
\ No newline at end of file @@ -1,4 +1,4 @@ -# Introduction + # Introduction  Have you ever wondered, if you want to compile a C++ program, why you always  need to learn another language to make it build properly with dependency @@ -1,8 +0,0 @@ -Glob convenience methods -std::string glob = getFilesInDir(...); - -Add support for pre/post build hooks with conditions - -Add shell script targets to be able to generate soucre files that can -be included as sources in normal build targets. -For example imageconverter diff --git a/bootstrap.sh b/bootstrap.sh index 833d3be..3a9c454 100755 --- a/bootstrap.sh +++ b/bootstrap.sh @@ -1,4 +1,8 @@ -#!/bin/bash +#!/bin/sh +set -e  echo "Bootstrapping..." -g++ -std=c++17 -I. -Isrc -pthread src/*.cc ctor.cc test/ctor.cc -o ctor && \ -echo "Done. Now run ./ctor to build." +g++ -std=c++20 -Wall -O3 -Isrc -pthread src/bootstrap.cc ctor.cc test/ctor.cc -o ctor +./ctor +g++ -std=c++20 -Wall -O3 -Isrc -pthread ctor.cc test/ctor.cc -Lbuild -lctor -o ctor +./ctor configure --ctor-includedir=src --ctor-libdir=build +echo "Done. Now run ./ctor to (re)build." @@ -1,11 +1,11 @@  // -*- c++ -*-  // Distributed under the BSD 2-Clause License.  // See accompanying file LICENSE for details. -#include "libctor.h" +#include <libctor.h>  namespace  { -BuildConfigurations ctorConfigs() +BuildConfigurations ctorConfigs(const Settings& settings)  {  	return  	{ @@ -16,23 +16,29 @@ BuildConfigurations ctorConfigs()  				"src/build.cc",  				"src/configure.cc",  				"src/execute.cc", +				"src/externals_manual.cc",  				"src/libctor.cc",  				"src/rebuild.cc",  				"src/task.cc",  				"src/task_ar.cc", +				"src/task_fn.cc",  				"src/task_cc.cc",  				"src/task_ld.cc",  				"src/task_so.cc",  				"src/tasks.cc", +				"src/tools.cc", +				"src/util.cc",  				"src/unittest.cc",  			}, -			.cxxflags = { -				"-std=c++17", -				"-O3", -				"-s", -				"-Wall", -				"-Werror", -				"-Isrc", +			.flags = { +				.cxxflags = { +					"-std=c++20", +					"-O3", +					"-g", +					"-Wall", +					"-Werror", +					"-Isrc", +				},  			},  		}  	}; diff --git a/src/bootstrap.cc b/src/bootstrap.cc new file mode 100644 index 0000000..9a3c321 --- /dev/null +++ b/src/bootstrap.cc @@ -0,0 +1,83 @@ +// -*- c++ -*- +// Distributed under the BSD 2-Clause License. +// See accompanying file LICENSE for details. +#include <iostream> +#include <array> + +#define BOOTSTRAP + +#include "libctor.h" + +#include "util.cc" +#include "rebuild.cc" +#include "task.cc" +#include "task_cc.cc" +#include "task_ar.cc" +#include "execute.cc" +#include "tasks.cc" +#include "build.cc" +#include "tools.cc" + +std::filesystem::path configurationFile("configuration.cc"); +std::filesystem::path configHeaderFile("config.h"); + +const Configuration default_configuration{}; +const Configuration& configuration() +{ +	return default_configuration; +} + +bool hasConfiguration(const std::string& key) +{ +	return false; +} + +const std::string& getConfiguration(const std::string& key, +                                    const std::string& defaultValue) +{ +	return defaultValue; +} + +int main(int argc, char* argv[]) +{ +	if(argc > 1) +	{ +		std::cerr << "This is a minimal bootstrap version of " << argv[0] << +			" which doesn't support any arguments\n"; +		return 1; +	} + +	Settings settings{}; + +	settings.builddir = getConfiguration(cfg::builddir, "build"); +	settings.parallel_processes = +		std::max(1u, std::thread::hardware_concurrency() * 2 - 1); +	settings.verbose = 0; +	auto all_tasks = getTasks(settings, {}, false); +	for(auto task : all_tasks) +	{ +		if(task->registerDepTasks(all_tasks)) +		{ +			return 1; +		} +	} + +	std::vector<Target> non_unittest_targets; +	auto& targets = getTargets(settings); +	for(const auto& target : targets) +	{ +		if(target.config.type != TargetType::UnitTest && +		   target.config.type != TargetType::UnitTestLib) +		{ +			non_unittest_targets.push_back(target); +		} +	} + +	auto ret = build(settings, "all", non_unittest_targets, all_tasks); +	if(ret != 0) +	{ +		return ret; +	} + +	return 0; +} diff --git a/src/build.cc b/src/build.cc index 1b70c5b..98952e0 100644 --- a/src/build.cc +++ b/src/build.cc @@ -9,31 +9,44 @@  #include <chrono>  #include <set>  #include <thread> +#include <list> + +#include "libctor.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) +          const std::set<std::shared_ptr<Task>>& tasks, +          const std::set<std::shared_ptr<Task>>& all_tasks, +          bool dryrun)  {  	if(settings.verbose > 1)  	{  		std::cout << "Building '" << name << "'\n";  	} -	std::list<std::shared_ptr<Task>> dirtyTasks; +	std::set<std::shared_ptr<Task>> dirtyTasks;  	for(auto task : tasks)  	{  		if(task->dirty())  		{ -			dirtyTasks.push_back(task); +			dirtyTasks.insert(task);  		}  	} +	// Dry-run returns number of dirty tasks but otherwise does nothing. +	if(dryrun) +	{ +		return dirtyTasks.size(); +	} +  	if(dirtyTasks.empty())  	{ -		std::cout << "Nothing to be done for '"<< name << "'\n"; +		if(settings.verbose > -1) +		{ +			std::cout << "Nothing to be done for '"<< name << "'\n"; +		}  		return 0;  	} @@ -71,32 +84,33 @@ int build(const Settings& settings,  					           return task->run();  				           }));  			started_one = true; -			std::this_thread::sleep_for(2ms); +			// Make sure we don't start tasks on top of each other to prevent +			// straining the disk. +			std::this_thread::sleep_for(50ms);  		}  		for(auto process = processes.begin();  		    process != processes.end();  		    ++process)  		{ -			if(process->valid()) +			if(process->valid() == false)  			{ -				if(process->get() != 0) -				{ -					// TODO: Wait for other processes to finish before returning -					return 1; -				} -				processes.erase(process); -				break; +				continue;  			} -		} -		if(started_one) -		{ -			std::this_thread::sleep_for(2ms); +			auto ret = process->get(); +			if(ret != 0) +			{ +				// NOTE Wait for other processes to finish before returning +				return ret; +			} +			processes.erase(process); +			break;  		} -		else + +		if(!started_one) // prevent polling too fast if no task is yet ready  		{ -			std::this_thread::sleep_for(200ms); +			std::this_thread::sleep_for(10ms);  		}  	} @@ -104,11 +118,15 @@ int build(const Settings& settings,  	    process != processes.end();  	    ++process)  	{ +		if(process->valid() == false) +		{ +			continue; +		}  		process->wait();  		auto ret = process->get();  		if(ret != 0)  		{ -			return 1; +			return ret;  		}  	} @@ -138,23 +156,24 @@ std::set<std::shared_ptr<Task>> getDepTasks(std::shared_ptr<Task> task)  int build(const Settings& settings,            const std::string& name, -          const std::list<std::shared_ptr<Task>>& all_tasks) +          const std::set<std::shared_ptr<Task>>& all_tasks, +          bool dryrun)  {  	bool task_found{false};  	for(auto task : all_tasks)  	{ -		if(task->name() == name || task->target() == name) +		if(*task == name)  		{ -			std::cout << name << "\n";  			task_found = true;  			auto depSet = getDepTasks(task); -			std::list<std::shared_ptr<Task>> ts; +			std::set<std::shared_ptr<Task>> ts;  			for(const auto& task : depSet)  			{ -				ts.push_back(task); +				ts.insert(task);  			} -			auto ret = build(settings, name, ts, all_tasks); + +			auto ret = build(settings, name, ts, all_tasks, dryrun);  			if(ret != 0)  			{  				return ret; @@ -176,25 +195,25 @@ int build(const Settings& settings,  int build(const Settings& settings,            const std::string& name,            const std::vector<Target>& targets, -          const std::list<std::shared_ptr<Task>>& all_tasks) +          const std::set<std::shared_ptr<Task>>& all_tasks, +          bool dryrun)  {  	bool task_found{false}; -	std::list<std::shared_ptr<Task>> ts; +	std::set<std::shared_ptr<Task>> ts;  	for(const auto& target : targets)  	{  		for(auto task : all_tasks)  		{ -			if(task->name() == target.config.target || -			   task->target() == target.config.target) +			if(!task->derived() && // only consider non-derived tasks +			   task->buildConfig().target == target.config.target)  			{ -				std::cout << target.config.target << "\n";  				task_found = true;  				auto depSet = getDepTasks(task);  				for(const auto& task : depSet)  				{ -					ts.push_back(task); +					ts.insert(task);  				}  			}  		} @@ -206,5 +225,5 @@ int build(const Settings& settings,  		return 1;  	} -	return build(settings, name, ts, all_tasks); +	return build(settings, name, ts, all_tasks, dryrun);  } diff --git a/src/build.h b/src/build.h index 1db3f5c..caf7a68 100644 --- a/src/build.h +++ b/src/build.h @@ -4,23 +4,30 @@  #pragma once  #include <string> -#include <list> +#include <set>  #include <memory>  #include "task.h" -#include "settings.h"  #include "tasks.h" +struct Settings; + +//! Dry-run returns number of dirty tasks but otherwise does nothing.  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); +          const std::set<std::shared_ptr<Task>>& tasks, +          const std::set<std::shared_ptr<Task>>& all_tasks, +          bool dryrun = false); +//! Dry-run returns number of dirty tasks but otherwise does nothing.  int build(const Settings& settings,            const std::string& name, -          const std::list<std::shared_ptr<Task>>& all_tasks); +          const std::set<std::shared_ptr<Task>>& all_tasks, +          bool dryrun = false); +//! Dry-run returns number of dirty tasks but otherwise does nothing.  int build(const Settings& settings,            const std::string& name,            const std::vector<Target>& targets, -          const std::list<std::shared_ptr<Task>>& all_tasks); +          const std::set<std::shared_ptr<Task>>& all_tasks, +          bool dryrun = false); diff --git a/src/configure.cc b/src/configure.cc index b3517dc..a81f8ad 100644 --- a/src/configure.cc +++ b/src/configure.cc @@ -6,36 +6,67 @@  #include <iostream>  #include <filesystem>  #include <fstream> +#include <optional>  #include <getoptpp/getoptpp.hpp> -#include "settings.h"  #include "execute.h"  #include "libctor.h"  #include "tasks.h" +#include "rebuild.h" +#include "externals.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() +std::map<std::string, std::string> external_includedir; +std::map<std::string, std::string> external_libdir; + +const Configuration default_configuration{}; +const Configuration& __attribute__((weak)) configuration()  {  	return default_configuration;  } +namespace ctor +{ +std::optional<std::string> includedir; +std::optional<std::string> libdir; +} +  bool hasConfiguration(const std::string& key)  { +	if(key == cfg::ctor_includedir && ctor::includedir) +	{ +		return true; +	} + +	if(key == cfg::ctor_libdir && ctor::libdir) +	{ +		return true; +	} +  	const auto& c = configuration(); -	return c.find(key) != c.end(); +	return c.tools.find(key) != c.tools.end();  }  const std::string& getConfiguration(const std::string& key,                                      const std::string& defaultValue)  { +	if(key == cfg::ctor_includedir && ctor::includedir) +	{ +		return *ctor::includedir; +	} + +	if(key == cfg::ctor_libdir && ctor::libdir) +	{ +		return *ctor::libdir; +	} +  	const auto& c = configuration();  	if(hasConfiguration(key))  	{ -		return c.at(key); +		return c.tools.at(key);  	}  	return defaultValue; @@ -44,7 +75,7 @@ const std::string& getConfiguration(const std::string& key,  std::string locate(const std::string& arch, const std::string& app)  {  	std::string path_env = std::getenv("PATH"); -	std::cout << path_env << "\n"; +	//std::cout << path_env << "\n";  	std::string program = app;  	if(!arch.empty()) @@ -72,15 +103,15 @@ std::string locate(const std::string& arch, const std::string& app)  			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"; +				//std::cout << " - executable by owner\n";  			}  			if((perms & std::filesystem::perms::group_exec) != std::filesystem::perms::none)  			{ -				std::cout << " - executable by group\n"; +				//std::cout << " - executable by group\n";  			}  			if((perms & std::filesystem::perms::others_exec) != std::filesystem::perms::none)  			{ -				std::cout << " - executable by others\n"; +				//std::cout << " - executable by others\n";  			}  			return prog_path.string(); @@ -92,21 +123,38 @@ std::string locate(const std::string& arch, const std::string& app)  	return {};  } -int configure(int argc, char* argv[]) +class Args +	: public std::vector<char*>  { -	Settings settings; - -	settings.builddir = "build"; +public: +	Args(const std::vector<std::string>& args) +	{ +		resize(args.size() + 1); +		(*this)[0] = strdup("./ctor"); +		for(std::size_t i = 0; i < size() - 1; ++i) +		{ +			(*this)[i + 1] = strdup(args[i].data()); +		} +	} -	std::string cmd_str; -	for(int i = 0; i < argc; ++i) +	~Args()  	{ -		if(i > 0) +		for(std::size_t i = 0; i < size(); ++i)  		{ -			cmd_str += " "; +			free((*this)[i]);  		} -		cmd_str += argv[i];  	} +}; + +// helper constant for the visitor +template<class> inline constexpr bool always_false_v = false; + +int regenerateCache(const Settings& default_settings, +                    const std::vector<std::string>& args, +                    const std::map<std::string, std::string>& env) +{ +	Settings settings{default_settings}; +	Args vargs(args);  	dg::Options opt;  	int key{128}; @@ -119,6 +167,8 @@ int configure(int argc, char* argv[])  	std::string cxx_prog = "g++";  	std::string ar_prog = "ar";  	std::string ld_prog = "ld"; +	std::string ctor_includedir; +	std::string ctor_libdir;  	opt.add("build-dir", required_argument, 'b',  	        "Set output directory for build files (default: '" + @@ -191,6 +241,65 @@ int configure(int argc, char* argv[])  		        return 0;  	        }); +	opt.add("ctor-includedir", required_argument, key++, +	        "Set path to ctor header file, used for re-compiling.", +	        [&]() { +		        ctor_includedir = optarg; +		        return 0; +	        }); + +	opt.add("ctor-libdir", required_argument, key++, +	        "Set path to ctor library file, used for re-compiling.", +	        [&]() { +		        ctor_libdir = optarg; +		        return 0; +	        }); + +	// Resolv externals +	ExternalConfigurations externalConfigs; +	for(std::size_t i = 0; i < numExternalConfigFiles; ++i) +	{ +		auto newExternalConfigs = externalConfigFiles[i].cb(settings); +		externalConfigs.insert(externalConfigs.end(), +		                       newExternalConfigs.begin(), +		                       newExternalConfigs.end()); +	} + +	auto add_path_args = +		[&](const std::string& name) +		{ +			opt.add(name + "-includedir", required_argument, key++, +			        "Set path to " + name + " header file.", +			        [&]() { +				        external_includedir[name] = optarg; +				        return 0; +			        }); + +			opt.add(name + "-libdir", required_argument, key++, +			        "Set path to " + name + " libraries.", +			        [&]() { +				        external_libdir[name] = optarg; +				        return 0; +			        }); +		}; + +	for(const auto& ext : externalConfigs) +	{ +		std::visit([&](auto&& arg) +		           { +			           using T = std::decay_t<decltype(arg)>; +			           if constexpr (std::is_same_v<T, ExternalManual>) +			           { +				           add_path_args(ext.name); +			           } +			           else +			           { +				           static_assert(always_false_v<T>, "non-exhaustive visitor!"); +			           } +		           }, ext.external); + +	} +  	opt.add("help", no_argument, 'h',  	        "Print this help text.",  	        [&]() { @@ -200,14 +309,14 @@ int configure(int argc, char* argv[])  		        return 0;  	        }); -	opt.process(argc, argv); +	opt.process(vargs.size(), vargs.data());  	if(host_arch.empty())  	{  		host_arch = build_arch;  	} -	auto tasks = getTasks(settings); +	auto tasks = getTasks(settings, {}, false);  /*  	bool needs_cpp{false};  	bool needs_c{false}; @@ -233,32 +342,29 @@ int configure(int argc, char* argv[])  		}  	}  */ -	auto cc_env = getenv("CC"); -	if(cc_env) + +	auto cc_env = env.find("CC"); +	if(cc_env != env.end())  	{ -		cmd_str = std::string("CC=") + cc_env + " " + cmd_str; -		cc_prog = cc_env; +		cc_prog = cc_env->second;  	} -	auto cxx_env = getenv("CXX"); -	if(cxx_env) +	auto cxx_env = env.find("CXX"); +	if(cxx_env != env.end())  	{ -		cmd_str = std::string("CXX=") + cxx_env + " " + cmd_str; -		cxx_prog = cxx_env; +		cxx_prog = cxx_env->second;  	} -	auto ar_env = getenv("AR"); -	if(ar_env) +	auto ar_env = env.find("AR"); +	if(ar_env != env.end())  	{ -		cmd_str = std::string("AR=") + ar_env + " " + cmd_str; -		ar_prog = ar_env; +		ar_prog = ar_env->second;  	} -	auto ld_env = getenv("LD"); -	if(ld_env) +	auto ld_env = env.find("LD"); +	if(ld_env != env.end())  	{ -		cmd_str = std::string("LD=") + ld_env + " " + cmd_str; -		ld_prog = ld_env; +		ld_prog = ld_env->second;  	}  	std::string host_cc = locate(host_arch, cc_prog); @@ -273,24 +379,113 @@ int configure(int argc, char* argv[])  	std::cout << "Writing results to: " << configurationFile.string() << "\n";  	{  		std::ofstream istr(configurationFile); -		istr << "#include \"libctor.h\"\n\n"; -		istr << "const std::map<std::string, std::string>& configuration()\n"; +		istr << "#include <libctor.h>\n\n"; +		istr << "const Configuration& configuration()\n";  		istr << "{\n"; -		istr << "	static std::map<std::string, std::string> c =\n"; +		istr << "	static Configuration cfg =\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 << "		.args = {"; +		for(const auto& arg : args) +		{ +			istr << "\"" << arg << "\","; +		} +		istr << "},\n"; +		istr << "		.env = {"; +		for(const auto& e : env) +		{ +			istr << "{\"" << e.first << "\", \"" << e.second << "\"}, "; +		} +		istr << "},\n"; + +		istr << "		.tools = {\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"; +		if(!ctor_includedir.empty()) +		{ +			istr << "			{ \"" << cfg::ctor_includedir << "\", \"" << ctor_includedir << "\" },\n"; +			ctor::includedir = ctor_includedir; +		} +		if(!ctor_libdir.empty()) +		{ +			istr << "			{ \"" << cfg::ctor_libdir << "\", \"" << ctor_libdir << "\" },\n"; +			ctor::libdir = ctor_libdir; +		} + +		istr << "		},\n"; +		istr << "		.externals = {\n"; + +		for(const auto& ext : externalConfigs) +		{ +			istr << "			{ \"" << ext.name << "\", {\n"; +			Flags resolved_flags; +			if(std::holds_alternative<ExternalManual>(ext.external)) +			{ +				if(auto ret = resolv(settings, ext, +				                     std::get<ExternalManual>(ext.external), +				                     resolved_flags)) +				{ +					return ret; +				} +			} +			else +			{ +				std::cout << "Unknown external type\n"; +				return 1; +			} + +			if(!resolved_flags.cxxflags.empty()) +			{ +				istr << "			  .cxxflags = {"; +				for(const auto& flag : resolved_flags.cxxflags) +				{ +					istr << "\"" << flag << "\","; +				} +				istr << "},\n"; +			} + +			if(!resolved_flags.cflags.empty()) +			{ +				istr << "			  .cflags = {"; +				for(const auto& flag : resolved_flags.cflags) +				{ +					istr << "\"" << flag << "\","; +				} +				istr << "},\n"; +			} + +			if(!resolved_flags.ldflags.empty()) +			{ +				istr << "			  .ldflags = {"; +				for(const auto& flag : resolved_flags.ldflags) +				{ +					istr << "\"" << flag << "\","; +				} +				istr << "},\n"; +			} + +			if(!resolved_flags.asmflags.empty()) +			{ +				istr << "			  .asmflags = {"; +				for(const auto& flag : resolved_flags.asmflags) +				{ +					istr << "\"" << flag << "\","; +				} +				istr << "},\n"; +			} +			istr << "			}},\n"; +		} + +		istr << "		},\n";  		istr << "	};\n"; -		istr << "	return c;\n"; -		istr << "}\n"; +		istr << "	return cfg;\n"; +		istr << "}\n\n";  	}  	{ @@ -302,3 +497,94 @@ int configure(int argc, char* argv[])  	return 0;  } + +int configure(const Settings& global_settings, int argc, char* argv[]) +{ +	Settings settings{global_settings}; + +	std::vector<std::string> args; +	for(int i = 2; i < argc; ++i) // skip command and the first 'configure' arg +	{ +		args.push_back(argv[i]); +	} + +	std::map<std::string, std::string> env; +	auto cc_env = getenv("CC"); +	if(cc_env) +	{ +		env["CC"] = cc_env; +	} + +	auto cxx_env = getenv("CXX"); +	if(cxx_env) +	{ +		env["CXX"] = cxx_env; +	} + +	auto ar_env = getenv("AR"); +	if(ar_env) +	{ +		env["AR"] = ar_env; +	} + +	auto ld_env = getenv("LD"); +	if(ld_env) +	{ +		env["LD"] = ld_env; +	} + +	auto ret = regenerateCache(settings, args, env); +	if(ret != 0) +	{ +		return ret; +	} + +	recompileCheck(settings, argc, argv, false); + +	return 0; +} + +int reconfigure(const Settings& settings, int argc, char* argv[]) +{ +	bool no_rerun{false}; + +	std::vector<std::string> args; +	for(int i = 2; i < argc; ++i) // skip executable name and 'reconfigure' arg +	{ +		if(i == 2 && std::string(argv[i]) == "--no-rerun") +		{ +			no_rerun = true; +			continue; +		} +		args.push_back(argv[i]); +	} + +	const auto& cfg = configuration(); + +	std::cout << "Re-running configure:\n"; +	for(const auto& e : cfg.env) +	{ +		std::cout << e.first << "=\"" << e.second << "\" "; +	} +	std::cout << argv[0] << " configure "; +	for(const auto& arg : cfg.args) +	{ +		std::cout << arg << " "; +	} +	std::cout << "\n"; + +	auto ret = regenerateCache(settings, cfg.args, cfg.env); +	if(ret != 0) +	{ +		return ret; +	} + +	recompileCheck(settings, 1, argv, false); + +	if(no_rerun) +	{ +		return 0; // this was originally invoked by configure, don't loop +	} + +	return execute(argv[0], args); +} diff --git a/src/configure.h b/src/configure.h index de1b7e0..16499d6 100644 --- a/src/configure.h +++ b/src/configure.h @@ -6,16 +6,12 @@  #include <filesystem>  #include <string>  #include <map> +#include <vector> + +struct Settings;  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; +int configure(const Settings& settings, int argc, char* argv[]); +int reconfigure(const Settings& settings, int argc, char* argv[]); diff --git a/src/settings.h b/src/externals.h index d71717a..7b4aa23 100644 --- a/src/settings.h +++ b/src/externals.h @@ -3,11 +3,4 @@  // See accompanying file LICENSE for details.  #pragma once -#include <cstddef> - -struct Settings -{ -	std::string builddir; -	std::size_t parallel_processes; -	int verbose{1}; -}; +#include "externals_manual.h" diff --git a/src/externals_manual.cc b/src/externals_manual.cc new file mode 100644 index 0000000..0e3cdfd --- /dev/null +++ b/src/externals_manual.cc @@ -0,0 +1,37 @@ +// -*- c++ -*- +// Distributed under the BSD 2-Clause License. +// See accompanying file LICENSE for details. +#include "externals_manual.h" + +#include <map> + +#include "libctor.h" + +#include "util.h" +#include "tools.h" + +extern std::map<std::string, std::string> external_includedir; +extern std::map<std::string, std::string> external_libdir; + +int resolv(const Settings& settings, const ExternalConfiguration& config, +           const ExternalManual& ext, Flags& flags) +{ +	auto tool_chain = getToolChain(config.system); + +	flags = ext.flags; + +	auto inc = external_includedir.find(config.name); +	if(inc != external_includedir.end()) +	{ +		append(flags.cflags, getOption(tool_chain, opt::include_path, inc->second)); +		append(flags.cxxflags, getOption(tool_chain, opt::include_path, inc->second)); +	} + +	auto lib = external_libdir.find(config.name); +	if(lib != external_libdir.end()) +	{ +		append(flags.ldflags, getOption(tool_chain, opt::library_path, lib->second)); +	} + +	return 0; +} diff --git a/src/externals_manual.h b/src/externals_manual.h new file mode 100644 index 0000000..7bd968d --- /dev/null +++ b/src/externals_manual.h @@ -0,0 +1,12 @@ +// -*- c++ -*- +// Distributed under the BSD 2-Clause License. +// See accompanying file LICENSE for details. +#pragma once + +struct Settings; +struct ExternalConfiguration; +struct ExternalManual; +struct Flags; + +int resolv(const Settings& settings, const ExternalConfiguration& name, +           const ExternalManual& ext, Flags& flags); diff --git a/src/libctor.cc b/src/libctor.cc index 4d0d6d9..d188771 100644 --- a/src/libctor.cc +++ b/src/libctor.cc @@ -20,25 +20,29 @@  #include <getoptpp/getoptpp.hpp>  #include "libctor.h" -#include "settings.h"  #include "configure.h"  #include "rebuild.h"  #include "tasks.h"  #include "build.h"  #include "unittest.h" +  int main(int argc, char* argv[])  { +	Settings settings{}; + +	settings.builddir = getConfiguration(cfg::builddir, settings.builddir); +	settings.parallel_processes = +		std::max(1u, std::thread::hardware_concurrency()) * 2 - 1; +  	if(argc > 1 && std::string(argv[1]) == "configure")  	{ -		return configure(argc, argv); +		return configure(settings, 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; +	if(argc > 1 && std::string(argv[1]) == "reconfigure") +	{ +		return reconfigure(settings, argc, argv); +	}  	bool write_compilation_database{false};  	std::string compilation_database; @@ -85,6 +89,13 @@ int main(int argc, char* argv[])  		        return 0;  	        }); +	opt.add("quiet", no_argument, 'q', +	        "Be completely silent.", +	        [&]() { +		        settings.verbose = -1; +		        return 0; +	        }); +  	opt.add("add", required_argument, 'a',  	        "Add specified file to the build configurations.",  	        [&]() { @@ -148,9 +159,10 @@ int main(int argc, char* argv[])  		        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) +   configure   - run configuration step (cannot be used with other targets). +   reconfigure - rerun configuration step with the same arguments as last (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. @@ -178,6 +190,11 @@ Options:  			files.insert(configFiles[i].file);  		} +		for(std::size_t i = 0; i < numExternalConfigFiles; ++i) +		{ +			files.insert(externalConfigFiles[i].file); +		} +  		for(const auto& file : files)  		{  			std::cout << file << "\n"; @@ -189,7 +206,7 @@ Options:  		no_default_build = true;  		for(const auto& add_file : add_files)  		{ -			reg(add_file.data(), [](){ return std::vector<BuildConfiguration>{};}); +			reg(add_file.data());  		}  		for(const auto& remove_file : remove_files) @@ -198,7 +215,7 @@ Options:  		}  		// Force rebuild if files were added -		recompileCheck(settings, 1, argv, true, no_relaunch == false); +		recompileCheck(settings, 1, argv, no_relaunch == false);  	}  	recompileCheck(settings, argc, argv); @@ -253,7 +270,7 @@ Options:  	{  		no_default_build = true;  		const auto& c = configuration(); -		for(const auto& config : c) +		for(const auto& config : c.tools)  		{  			std::cout << config.first << ": " << config.second << "\n";  		} @@ -298,7 +315,8 @@ Options:  			auto& targets = getTargets(settings);  			for(const auto& target : targets)  			{ -				if(target.config.type == TargetType::UnitTest) +				if(target.config.type == TargetType::UnitTest || +				   target.config.type == TargetType::UnitTestLib)  				{  					unittest_targets.push_back(target);  				} @@ -320,7 +338,8 @@ Options:  				auto& targets = getTargets(settings);  				for(const auto& target : targets)  				{ -					if(target.config.type != TargetType::UnitTest) +					if(target.config.type != TargetType::UnitTest && +					   target.config.type != TargetType::UnitTestLib)  					{  						non_unittest_targets.push_back(target);  					} @@ -349,7 +368,8 @@ Options:  		auto& targets = getTargets(settings);  		for(const auto& target : targets)  		{ -			if(target.config.type != TargetType::UnitTest) +			if(target.config.type != TargetType::UnitTest && +			   target.config.type != TargetType::UnitTestLib)  			{  				non_unittest_targets.push_back(target);  			} diff --git a/src/libctor.h b/src/libctor.h index 70c62a8..96e1115 100644 --- a/src/libctor.h +++ b/src/libctor.h @@ -3,9 +3,13 @@  // See accompanying file LICENSE for details.  #pragma once +#include <source_location>  #include <string>  #include <vector>  #include <map> +#include <variant> +#include <cstddef> +#include <functional>  enum class TargetType  { @@ -16,6 +20,8 @@ enum class TargetType  	DynamicLibrary,  	Object,  	UnitTest, +	UnitTestLib, +	Function,  };  enum class Language @@ -33,29 +39,86 @@ enum class OutputSystem  	Build, // Internal tool during cross-compilation  }; -struct BuildConfiguration +struct Source  { -	TargetType type{TargetType::Auto}; +	Source(const char* file) : file(file) {} +	Source(const std::string& file) : file(file) {} +	Source(const char* file, Language lang) : file(file), language(lang) {} +	Source(const std::string& file, Language lang) : file(file), language(lang) {} + +	Source(const char* file, const char* output) : file(file), output(output) {} +	Source(const std::string& file, const std::string& output) : file(file), output(output) {} +	Source(const char* file, Language lang, const char* output) : file(file), language(lang), output(output) {} +	Source(const std::string& file, Language lang, const std::string& output) : file(file), language(lang), output(output) {} + +	std::string file;  	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::string output{}; +}; + +struct Flags +{  	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  }; +struct Settings +{ +	std::string builddir{"build"}; +	std::size_t parallel_processes{1}; +	int verbose{0}; // -1: completely silent, 0: normal, 1: verbose, ... +}; + +struct BuildConfiguration; +using GeneratorCb = std::function<int(const std::string& input, +                                      const std::string& output, +                                      const BuildConfiguration& config, +                                      const Settings& settings)>; + +struct BuildConfiguration +{ +	std::string name; // Name - used for referring in other configurations. +	TargetType type{TargetType::Auto}; +	OutputSystem system{OutputSystem::Host}; +	std::string target; // Output target file for this configuration +	std::vector<Source> sources; // source list +	std::vector<std::string> depends; // internal target dependencies +	Flags flags; +	std::vector<std::string> externals; // externals used by this configuration +	GeneratorCb function; +}; +  using BuildConfigurations = std::vector<BuildConfiguration>; -int reg(const char* location, BuildConfigurations (*cb)()); +int reg(BuildConfigurations (*cb)(const Settings&), +        const std::source_location location = std::source_location::current()); + +// This type will use flags verbatim +struct ExternalManual +{ +	Flags flags; +}; + + +struct ExternalConfiguration +{ +	std::string name; // Name for configuration +	OutputSystem system{OutputSystem::Host}; +	std::variant<ExternalManual> external; +}; + +using ExternalConfigurations = std::vector<ExternalConfiguration>; + +int reg(ExternalConfigurations (*cb)(const Settings&), +        const std::source_location location = std::source_location::current());  // 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); } +#define REG(cb) namespace { int UNIQUE_NAME(unique) = reg(cb); }  // Predefined configuration keys  namespace cfg @@ -71,9 +134,21 @@ constexpr auto build_cc = "build-cc";  constexpr auto build_cxx = "build-cpp";  constexpr auto build_ar = "build-ar";  constexpr auto build_ld = "build-ld"; + +constexpr auto ctor_includedir = "ctor-includedir"; +constexpr auto ctor_libdir = "ctor-libdir";  } -const std::map<std::string, std::string>& configuration(); +struct Configuration +{ +	std::vector<std::string> args; // vector of arguments used when last calling configure +	std::map<std::string, std::string> env; // env used when last calling configure + +	std::map<std::string, std::string> tools; // tools +	std::map<std::string, Flags> externals; +}; + +const Configuration& 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 index 353beb0..9ddf5ba 100644 --- a/src/rebuild.cc +++ b/src/rebuild.cc @@ -6,18 +6,22 @@  #include <iostream>  #include <filesystem>  #include <algorithm> +#include <source_location> +#include <cstring> -#include "execute.h"  #include "configure.h" -#include "settings.h"  #include "libctor.h" +#include "tasks.h" +#include "build.h" +#include "execute.h" +#include "tools.h" +#include "util.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)()) +int reg(BuildConfigurations (*cb)(const Settings&), +        const std::source_location location)  {  	// NOTE: std::cout cannot be used here  	if(numConfigFiles >= configFiles.size()) @@ -27,13 +31,41 @@ int reg(const char* location, std::vector<BuildConfiguration> (*cb)())  		exit(1);  	} -	configFiles[numConfigFiles].file = location; +	auto loc = std::filesystem::path(location.file_name()); +	if(loc.is_absolute()) +	{ +		auto pwd = std::filesystem::current_path(); +		auto rel = std::filesystem::relative(loc, pwd); +		configFiles[numConfigFiles].file = strdup(rel.string().data()); // NOTE: This intentionally leaks memory +	} +	else +	{ +		configFiles[numConfigFiles].file = location.file_name(); +	}  	configFiles[numConfigFiles].cb = cb;  	++numConfigFiles;  	return 0;  } +int reg(const char* location) +{ +	// 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 = +		[](const Settings&){ return std::vector<BuildConfiguration>{}; }; +	++numConfigFiles; + +	return 0; +} +  int unreg(const char* location)  {  	std::size_t found{0}; @@ -54,91 +86,196 @@ int unreg(const char* location)  		}  	} +	for(std::size_t i = 0; i < numExternalConfigFiles;) +	{ +		if(std::string(location) == externalConfigFiles[i].file) +		{ +			++found; +			for(std::size_t j = i; j < numExternalConfigFiles; ++j) +			{ +				externalConfigFiles[j] = externalConfigFiles[j + 1]; +			} +			--numExternalConfigFiles; +		} +		else +		{ +			++i; +		} +	} +  	return found;  } -void recompileCheck(const Settings& settings, int argc, char* argv[], -                    bool force, bool relaunch_allowed) +std::array<ExternalConfigurationEntry, 1024> externalConfigFiles; +std::size_t numExternalConfigFiles{0}; + +int reg(ExternalConfigurations (*cb)(const Settings&), +        const std::source_location location)  { -	bool dirty{force}; +	// NOTE: std::cout cannot be used here +	if(numExternalConfigFiles >= externalConfigFiles.size()) +	{ +		fprintf(stderr, "Max %d external configurations currently supported.\n", +		        (int)externalConfigFiles.size()); +		exit(1); +	} -	std::vector<std::string> args; -	args.push_back("-s"); -	args.push_back("-O3"); -	args.push_back("-std=c++17"); -	args.push_back("-pthread"); +	externalConfigFiles[numExternalConfigFiles].file = location.file_name(); +	externalConfigFiles[numExternalConfigFiles].cb = cb; +	++numExternalConfigFiles; -	std::filesystem::path binFile(argv[0]); +	return 0; +} -	if(std::filesystem::exists(configurationFile)) +namespace +{ +bool contains(const std::vector<Source>& sources, const std::string& file) +{ +	for(const auto& source : sources)  	{ -		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) +		if(source.file == file)  		{ -			// configuration.cc exists, but currently compiled with the default one. -			dirty = true; +			return true;  		}  	} -	if(settings.verbose > 1) +	return false; +} +} + +bool recompileCheck(const Settings& global_settings, int argc, char* argv[], +                    bool relaunch_allowed) +{ +	using namespace std::string_literals; + +	if(global_settings.verbose > 1)  	{  		std::cout << "Recompile check (" << numConfigFiles << "):\n";  	} +	BuildConfiguration config; + +	config.name = "ctor"; +	config.system = OutputSystem::Build; + +	auto tool_chain = getToolChain(config.system); + +	append(config.flags.cxxflags, +	       getOption(tool_chain, opt::optimization, "3")); +	append(config.flags.cxxflags, +	       getOption(tool_chain, opt::cpp_std, "c++20")); +	if(hasConfiguration(cfg::ctor_includedir)) +	{ +		append(config.flags.cxxflags, +		       getOption(tool_chain, opt::include_path, +		                 getConfiguration(cfg::ctor_includedir))); +	} +	if(hasConfiguration(cfg::ctor_libdir)) +	{ +		append(config.flags.ldflags, +		       getOption(tool_chain, opt::library_path, +		                 getConfiguration(cfg::ctor_libdir))); +	} +	append(config.flags.ldflags, getOption(tool_chain, opt::link, "ctor")); +	append(config.flags.ldflags, getOption(tool_chain, opt::threads)); + +	Settings settings{global_settings}; +	settings.verbose = -1; // Make check completely silent. +	settings.builddir += "/ctor"; // override builddir to use ctor subdir + +	{ +		std::filesystem::path buildfile  = settings.builddir; +		std::filesystem::path currentfile = argv[0]; +		config.target = std::filesystem::relative(currentfile, buildfile).string(); +	} + +	if(std::filesystem::exists(configurationFile)) +	{ +		config.sources.push_back(configurationFile.string()); +	} +  	for(std::size_t i = 0; i < numConfigFiles; ++i)  	{  		std::string location = configFiles[i].file; -		if(settings.verbose > 1) +		if(global_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)) + +		// Ensure that files containing multiple configurations are only added once. +		if(!contains(config.sources, location))  		{ -			dirty = true; +			config.sources.push_back(location);  		} +	} -		// Support adding multiple config functions from the same file -		if(std::find(args.begin(), args.end(), location) == std::end(args)) +	for(std::size_t i = 0; i < numExternalConfigFiles; ++i) +	{ +		std::string location = externalConfigFiles[i].file; +		if(global_settings.verbose > 1)  		{ -			args.push_back(location); +			std::cout << " - " << location << "\n"; +		} + +		// Ensure that files containing multiple configurations are only added once. +		if(!contains(config.sources, location)) +		{ +			config.sources.push_back(location); +		} +	} + +	auto tasks = taskFactory({config}, settings, {}); + +	for(auto task : tasks) +	{ +		if(task->registerDepTasks(tasks)) +		{ +			return false; +		} +	} + +	// Find out if reconfigure is needed +	bool reconfigure{false}; +	for(auto task : tasks) +	{ +		if(task->dirty() && +		   task->source() != "" && // only look at source files +		   task->source() != "configuration.cc") // don't reconfigure if only configuration.cc is changed. +		{ +			reconfigure |= true;  		}  	} -	args.push_back("libctor.a"); -	args.push_back("-o"); -	args.push_back(binFile.string()); -	if(dirty) +	auto dirty_tasks = build(settings, "ctor", tasks, true); // dryrun +	if(dirty_tasks)  	{ -		std::cout << "Rebuilding config\n"; -		auto tool = getConfiguration(cfg::build_cxx, "/usr/bin/g++"); -		auto ret = execute(tool, args, settings.verbose > 0); +		std::cout << "Rebuilding config.\n"; +		auto ret = build(settings, "ctor", tasks); // run for real  		if(ret != 0)  		{ -			std::cerr << "Failed: ." << ret << "\n"; -			exit(1); +			return ret;  		} -		else +	} + +	if(reconfigure) +	{ +		std::vector<std::string> args; +		args.push_back("reconfigure"); +		if(!relaunch_allowed)  		{ -			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)); -			} +			args.push_back("--no-rerun");  		} +		for(int i = 1; i < argc; ++i) +		{ +			args.push_back(argv[i]); +		} +		auto ret = execute(argv[0], args); +		//if(ret != 0) +		{ +			exit(ret); +		} +  	} + +	return dirty_tasks;  } diff --git a/src/rebuild.h b/src/rebuild.h index 906d089..f1255c6 100644 --- a/src/rebuild.h +++ b/src/rebuild.h @@ -13,14 +13,24 @@ class Settings;  struct BuildConfigurationEntry  {  	const char* file; -	std::vector<BuildConfiguration> (*cb)(); +	BuildConfigurations (*cb)(const Settings&); +}; + +struct ExternalConfigurationEntry +{ +	const char* file; +	ExternalConfigurations (*cb)(const Settings&);  };  extern std::array<BuildConfigurationEntry, 1024> configFiles;  extern std::size_t numConfigFiles; -//int reg(const char* location, std::vector<BuildConfiguration> (*cb)()); +extern std::array<ExternalConfigurationEntry, 1024> externalConfigFiles; +extern std::size_t numExternalConfigFiles; + +int reg(const char* location);  int unreg(const char* location); -void recompileCheck(const Settings& settings, int argc, char* argv[], -                    bool force = false, bool relaunch_allowed = true); +//! Returns true of recompilation was needed. +bool recompileCheck(const Settings& settings, int argc, char* argv[], +                    bool relaunch_allowed = true); diff --git a/src/task.cc b/src/task.cc index 1c6c233..fb50765 100644 --- a/src/task.cc +++ b/src/task.cc @@ -6,22 +6,25 @@  #include <unistd.h>  #include <iostream> -Task::Task(const BuildConfiguration& config) +Task::Task(const BuildConfiguration& config, const Settings& settings, +           const std::string& sourceDir)  	: config(config)  	, output_system(config.system) +	, settings(settings) +	, sourceDir(sourceDir)  {  } -int Task::registerDepTasks(const std::list<std::shared_ptr<Task>>& tasks) +int Task::registerDepTasks(const std::set<std::shared_ptr<Task>>& tasks)  {  	for(const auto& depStr : depends())  	{  		bool found{false};  		for(const auto& task : tasks)  		{ -			if(task->target() == depStr) +			if(*task == depStr)  			{ -				dependsTasks.push_back(task); +				dependsTasks.insert(task);  				found = true;  			}  		} @@ -33,12 +36,22 @@ int Task::registerDepTasks(const std::list<std::shared_ptr<Task>>& tasks)  		}  	} -	return 0; +	return registerDepTasksInner(tasks); +} + +bool Task::operator==(const std::string& depStr) +{ +	return +		name() == depStr || +		target() == depStr || +		sourceDir + "/" + target() == depStr || +		targetFile().string() == depStr +		;  }  std::string Task::name() const  { -	return config.target; +	return config.name;  }  bool Task::dirty() @@ -141,7 +154,7 @@ std::string Task::compiler() const  	}  } -std::list<std::shared_ptr<Task>> Task::getDependsTasks() +std::set<std::shared_ptr<Task>> Task::getDependsTasks()  {  	return dependsTasks;  } @@ -6,8 +6,9 @@  #include <vector>  #include <string>  #include <atomic> -#include <list> +#include <set>  #include <memory> +#include <filesystem>  #include "libctor.h" @@ -20,12 +21,18 @@ enum class State  	Error,  }; +struct Settings; +  class Task  {  public: -	Task(const BuildConfiguration& config); +	Task(const BuildConfiguration& config, const Settings& settings, +	     const std::string& sourceDir); + +	int registerDepTasks(const std::set<std::shared_ptr<Task>>& tasks); +	virtual int registerDepTasksInner(const std::set<std::shared_ptr<Task>>& tasks) { return 0; } -	int registerDepTasks(const std::list<std::shared_ptr<Task>>& tasks); +	bool operator==(const std::string& dep);  	virtual std::string name() const;  	bool dirty(); @@ -34,10 +41,23 @@ public:  	State state() const;  	virtual int clean() = 0 ;  	virtual std::vector<std::string> depends() const = 0; + +	//! Raw target name as stated in ctor.cc config file or (in case of a derived +	//! target) the calculated target without builddir prefix.  	virtual std::string target() const = 0; +	//! Target file with full path prefix +	virtual std::filesystem::path targetFile() const = 0; + +	//! Returns true for tasks that are non-target tasks, ie. for example derived +	//! objects files from target sources. +	virtual bool derived() const = 0; +  	virtual std::string toJSON() const { return {}; }; +	//! Returns a reference to the originating build config. +	//! Note: the build config of a derived task will be that of its parent +	//! (target) task.  	const BuildConfiguration& buildConfig() const;  	TargetType targetType() const; @@ -45,7 +65,9 @@ public:  	OutputSystem outputSystem() const;  	std::string compiler() const; -	std::list<std::shared_ptr<Task>> getDependsTasks(); +	std::set<std::shared_ptr<Task>> getDependsTasks(); + +	virtual std::string source() const { return {}; }  protected:  	std::atomic<State> task_state{State::Unknown}; @@ -53,9 +75,11 @@ protected:  	virtual bool dirtyInner() { return false; }  	std::vector<std::string> dependsStr; -	std::list<std::shared_ptr<Task>> dependsTasks; +	std::set<std::shared_ptr<Task>> dependsTasks;  	const BuildConfiguration& config;  	TargetType target_type{TargetType::Auto};  	Language source_language{Language::Auto};  	OutputSystem output_system{OutputSystem::Host}; +	const Settings& settings; +	std::string sourceDir;  }; diff --git a/src/task_ar.cc b/src/task_ar.cc index e920571..3e1746c 100644 --- a/src/task_ar.cc +++ b/src/task_ar.cc @@ -7,36 +7,22 @@  #include <fstream>  #include "libctor.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); -} -} // namespace :: +#include "util.h"  TaskAR::TaskAR(const BuildConfiguration& config,                 const Settings& settings,                 const std::string& target, -               const std::vector<std::string>& objects) -	: Task(config) +               const std::vector<std::string>& objects, +               const std::string& sourceDir) +	: Task(config, settings, sourceDir)  	, config(config)  	, settings(settings) +	, sourceDir(sourceDir)  { -	targetFile = settings.builddir; -	targetFile /= target; +	std::filesystem::create_directories(std::filesystem::path(settings.builddir) / sourceDir); + +	_targetFile = target;  	for(const auto& object : objects)  	{  		std::filesystem::path objectFile = object; @@ -46,19 +32,18 @@ TaskAR::TaskAR(const BuildConfiguration& config,  	for(const auto& dep : config.depends)  	{ -		std::filesystem::path depFile = settings.builddir; -		depFile /= dep; -		depFiles.push_back(depFile); +		depFiles.push_back(dep);  	} -	flagsFile = settings.builddir / targetFile.stem(); +	flagsFile = std::filesystem::path(settings.builddir) / cleanUp(sourceDir) / targetFile().stem();  	flagsFile += ".flags";  	target_type = TargetType::StaticLibrary;  	source_language = Language::C;  	for(const auto& source : config.sources)  	{ -		std::filesystem::path sourceFile(source); +		std::filesystem::path sourceFile(source.file); +		// TODO: Use task languages instead  		if(sourceFile.extension().string() != ".c")  		{  			source_language = Language::Cpp; @@ -68,7 +53,7 @@ TaskAR::TaskAR(const BuildConfiguration& config,  bool TaskAR::dirtyInner()  { -	if(!std::filesystem::exists(targetFile)) +	if(!std::filesystem::exists(targetFile()))  	{  		return true;  	} @@ -78,15 +63,6 @@ bool TaskAR::dirtyInner()  		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) @@ -101,26 +77,12 @@ bool TaskAR::dirtyInner()  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(targetFile().string()); +	for(const auto& task : getDependsTasks())  	{ -		args.push_back(objectFile.string()); -	} -	for(const auto& flag : config.ldflags) -	{ -		args.push_back(flag); +		args.push_back(task->targetFile().string());  	}  	{ // Write flags to file. @@ -130,7 +92,7 @@ int TaskAR::runInner()  	if(settings.verbose == 0)  	{ -		std::cout << "AR => " << targetFile.string() << "\n"; +		std::cout << "AR => " << targetFile().string() << "\n";  	}  	std::string tool; @@ -149,10 +111,10 @@ int TaskAR::runInner()  int TaskAR::clean()  { -	if(std::filesystem::exists(targetFile)) +	if(std::filesystem::exists(targetFile()))  	{ -		std::cout << "Removing " << targetFile.string() << "\n"; -		std::filesystem::remove(targetFile); +		std::cout << "Removing " << targetFile().string() << "\n"; +		std::filesystem::remove(targetFile());  	}  	if(std::filesystem::exists(flagsFile)) @@ -172,9 +134,9 @@ std::vector<std::string> TaskAR::depends() const  		deps.push_back(objectFile.string());  	} -	for(const auto& depFile : depFiles) +	for(const auto& dep : config.depends)  	{ -		deps.push_back(depFile.string()); +		deps.push_back(dep);  	}  	return deps; @@ -182,15 +144,25 @@ std::vector<std::string> TaskAR::depends() const  std::string TaskAR::target() const  { -	return targetFile.string(); +	return _targetFile.string(); +} + +std::filesystem::path TaskAR::targetFile() const +{ +	return std::filesystem::path(settings.builddir) / sourceDir / _targetFile; +} + +bool TaskAR::derived() const +{ +	return false;  }  std::string TaskAR::flagsString() const  {  	std::string flagsStr; -	for(const auto& flag : config.ldflags) +	for(const auto& flag : config.flags.ldflags)  	{ -		if(flag != config.ldflags[0]) +		if(flag != config.flags.ldflags[0])  		{  			flagsStr += " ";  		} @@ -207,5 +179,12 @@ std::string TaskAR::flagsString() const  		flagsStr += dep;  	} +	auto deps = depends(); +	for(const auto& dep : deps) +	{ +		flagsStr += " "; +		flagsStr += dep; +	} +  	return flagsStr;  } diff --git a/src/task_ar.h b/src/task_ar.h index abdc3ae..c76a852 100644 --- a/src/task_ar.h +++ b/src/task_ar.h @@ -20,7 +20,9 @@ public:  	TaskAR(const BuildConfiguration& config,  	       const Settings& settings,  	       const std::string& target, -	       const std::vector<std::string>& objects); +	       const std::vector<std::string>& objects, +	       const std::string& sourceDir); +	virtual ~TaskAR() = default;  	bool dirtyInner() override; @@ -30,15 +32,19 @@ public:  	std::vector<std::string> depends() const override;  	std::string target() const override; +	std::filesystem::path targetFile() const override; + +	bool derived() 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 _targetFile;  	std::filesystem::path flagsFile;  	const BuildConfiguration& config;  	const Settings& settings; +	std::string sourceDir;  }; diff --git a/src/task_cc.cc b/src/task_cc.cc index 8256c70..c4343b6 100644 --- a/src/task_cc.cc +++ b/src/task_cc.cc @@ -5,132 +5,85 @@  #include <iostream>  #include <fstream> +#include <cassert>  #include "libctor.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 :: +#include "util.h" +#include "tools.h"  TaskCC::TaskCC(const BuildConfiguration& config, const Settings& settings, -               const std::string& sourceDir, const std::string& source) -	: Task(config) +               const std::string& sourceDir, const Source& source) +	: Task(config, settings, sourceDir)  	, config(config)  	, settings(settings)  	, sourceDir(sourceDir) +	, _source(source)  {  	sourceFile = sourceDir; -	sourceFile /= source; +	sourceFile /= source.file; + +	std::filesystem::path base = sourceFile.parent_path(); +	std::filesystem::create_directories(std::filesystem::path(settings.builddir) / base); -	std::filesystem::path base = settings.builddir; -	base /= config.target; +	base /= cleanUp(config.target);  	base += "-";  	base += sourceFile.stem(); -	if(sourceFile.extension().string() == ".c") +	target_type = TargetType::Object; +	source_language = source.language; +	if(source_language == Language::Auto)  	{ -		base += "_c"; +		source_language = languageFromExtension(sourceFile);  	} -	else + +	switch(source_language)  	{ +	case Language::C: +		base += "_c"; +		break; +	case Language::Cpp:  		base += "_cc"; +		break; +	case Language::Asm: +		base += "_asm"; +		break; +	case Language::Auto: +		assert(0 && "This should never happen"); +		break;  	} -	targetFile = base; -	targetFile += ".o"; -	depsFile = base; -	depsFile += ".d"; -	flagsFile = base; -	flagsFile += ".flags"; - -	target_type = TargetType::Object; -	if(sourceFile.extension().string() == ".c") +	if(source.output.empty())  	{ -		source_language = Language::C; +		_targetFile = base; +		_targetFile += ".o";  	}  	else  	{ -		source_language = Language::Cpp; +		_targetFile = source.output;  	} +	depsFile = targetFile().parent_path() / targetFile().stem(); +	depsFile += ".d"; +	flagsFile = targetFile().parent_path() / targetFile().stem(); +	flagsFile += ".flags"; +} + +int TaskCC::registerDepTasksInner(const std::set<std::shared_ptr<Task>>& tasks) +{ +	for(const auto& task : tasks) +	{ +		if(*task == _source.file) +		{ +			dependsTasks.insert(task); +		} +	} + +	return 0;  }  std::string TaskCC::name() const  { -	return target(); +	return {};  }  bool TaskCC::dirtyInner() @@ -141,7 +94,7 @@ bool TaskCC::dirtyInner()  		return true;  	} -	if(!std::filesystem::exists(targetFile)) +	if(!std::filesystem::exists(targetFile()))  	{  		//std::cout << "Missing targetFile\n";  		return true; @@ -179,7 +132,7 @@ bool TaskCC::dirtyInner()  	for(const auto& dep : depList)  	{  		if(!std::filesystem::exists(dep) || -		   std::filesystem::last_write_time(targetFile) < +		   std::filesystem::last_write_time(targetFile()) <  		   std::filesystem::last_write_time(dep))  		{  			//std::cout << "The targetFile older than " << std::string(dep) << "\n"; @@ -188,7 +141,7 @@ bool TaskCC::dirtyInner()  	}  	if(std::filesystem::last_write_time(sourceFile) > -	   std::filesystem::last_write_time(targetFile)) +	   std::filesystem::last_write_time(targetFile()))  	{  		//std::cout << "The targetFile older than sourceFile\n";  		return true; @@ -214,9 +167,22 @@ int TaskCC::runInner()  	if(settings.verbose == 0)  	{ -		std::cout << compiler() << " " << +		switch(sourceLanguage()) +		{ +		case Language::C: +			std::cout << "CC "; +			break; +		case Language::Cpp: +			std::cout << "CXX "; +			break; +		case Language::Auto: +		case Language::Asm: +			// Only c/c++ handled by this task type. +			break; +		} +		std::cout <<  			sourceFile.lexically_normal().string() << " => " << -			targetFile.lexically_normal().string() << "\n"; +			targetFile().lexically_normal().string() << "\n";  	}  	return execute(compiler(), args, settings.verbose > 0); @@ -224,10 +190,10 @@ int TaskCC::runInner()  int TaskCC::clean()  { -	if(std::filesystem::exists(targetFile)) +	if(std::filesystem::exists(targetFile()))  	{ -		std::cout << "Removing " << targetFile.string() << "\n"; -		std::filesystem::remove(targetFile); +		std::cout << "Removing " << targetFile().string() << "\n"; +		std::filesystem::remove(targetFile());  	}  	if(std::filesystem::exists(depsFile)) @@ -252,7 +218,17 @@ std::vector<std::string> TaskCC::depends() const  std::string TaskCC::target() const  { -	return targetFile.string(); +	return _targetFile.string(); +} + +std::filesystem::path TaskCC::targetFile() const +{ +	return std::filesystem::path(settings.builddir) / _targetFile; +} + +bool TaskCC::derived() const +{ +	return true;  }  std::string TaskCC::toJSON() const @@ -261,7 +237,7 @@ std::string TaskCC::toJSON() const  	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\"output\": \"" + targetFile().string() + "\",\n";  	json += "\t\t\"arguments\": [ \"" + compiler() + "\"";  	auto args = getCompilerArgs();  	for(const auto& arg : args) @@ -273,14 +249,19 @@ std::string TaskCC::toJSON() const  	return json;  } +std::string TaskCC::source() const +{ +	return sourceFile.string(); +} +  std::vector<std::string> TaskCC::flags() const  {  	switch(sourceLanguage())  	{  	case Language::C: -		return config.cflags; +		return config.flags.cflags;  	case Language::Cpp: -		return config.cxxflags; +		return config.flags.cxxflags;  	default:  		std::cerr << "Unknown CC target type\n";  		exit(1); @@ -300,39 +281,41 @@ std::string TaskCC::flagsString() const  std::vector<std::string> TaskCC::getCompilerArgs() const  { +	auto tool_chain = getToolChain(config.system); +  	auto compiler_flags = flags();  	std::vector<std::string> args; -	args.push_back("-MMD"); +	append(args, getOption(tool_chain, opt::generate_dep_tree));  	if(std::filesystem::path(config.target).extension() == ".so")  	{  		// Add -fPIC arg to all contained object files -		args.push_back("-fPIC"); +		append(args, getOption(tool_chain, opt::position_independent_code));  	} -	args.push_back("-c"); +	append(args, getOption(tool_chain, opt::no_link));  	args.push_back(sourceFile.string()); -	args.push_back("-o"); -	args.push_back(targetFile.string()); +	append(args, getOption(tool_chain, opt::output, targetFile().string()));  	for(const auto& flag : compiler_flags)  	{ -		// Is arg an added include path? -		if(flag.substr(0, 2) == "-I") +		auto option = getOption(flag); +		switch(option.first)  		{ -			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()) +		// Relative include paths has to be altered to be relative to sourceDir +		case opt::include_path:  			{ -				path = (sourceDir / path).lexically_normal(); -				std::string new_include_path = "-I" + path.string(); -				args.push_back(new_include_path); -				continue; +				std::filesystem::path path(option.second); +				if(path.is_relative()) +				{ +					path = (sourceDir / path).lexically_normal(); +					append(args, getOption(tool_chain, opt::include_path, path.string())); +				}  			} +			continue; +		default: +			break;  		}  		args.push_back(flag); diff --git a/src/task_cc.h b/src/task_cc.h index 10813a7..0a0d96a 100644 --- a/src/task_cc.h +++ b/src/task_cc.h @@ -19,7 +19,10 @@ class TaskCC  public:  	TaskCC(const BuildConfiguration& config,  	       const Settings& settings, -	       const std::string& sourceDir, const std::string& source); +	       const std::string& sourceDir, const Source& source); +	virtual ~TaskCC() = default; + +	int registerDepTasksInner(const std::set<std::shared_ptr<Task>>& tasks) override;  	std::string name() const override;  	bool dirtyInner() override; @@ -30,20 +33,26 @@ public:  	std::vector<std::string> depends() const override;  	std::string target() const override; +	std::filesystem::path targetFile() const override; + +	bool derived() const override;  	std::string toJSON() const override; -private: +	std::string source() const override; + +protected:  	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 _targetFile;  	std::filesystem::path depsFile;  	std::filesystem::path flagsFile;  	const BuildConfiguration& config;  	const Settings& settings;  	std::filesystem::path sourceDir; +	const Source& _source;  }; diff --git a/src/task_fn.cc b/src/task_fn.cc new file mode 100644 index 0000000..ab00fae --- /dev/null +++ b/src/task_fn.cc @@ -0,0 +1,121 @@ +// -*- c++ -*- +// Distributed under the BSD 2-Clause License. +// See accompanying file LICENSE for details. +#include "task_fn.h" + +#include <iostream> +#include <fstream> +#include <cassert> + +#include "libctor.h" +#include "execute.h" +#include "util.h" + +TaskFn::TaskFn(const BuildConfiguration& config, const Settings& settings, +               const std::string& sourceDir, const Source& source) +	: Task(config, settings, sourceDir) +	, config(config) +	, settings(settings) +{ +	sourceFile = sourceDir; +	sourceFile /= source.file; + +	std::filesystem::create_directories(std::filesystem::path(settings.builddir) / sourceFile.parent_path()); + +	target_type = config.type; +	source_language = source.language; + +	if(source.output.empty()) +	{ +		std::cerr << "Missing output file for functional target\n"; +		exit(1); +	} + +	_targetFile = source.output; +} + +bool TaskFn::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::last_write_time(sourceFile) > +	   std::filesystem::last_write_time(targetFile())) +	{ +		//std::cout << "The targetFile older than sourceFile\n"; +		return true; +	} + +	return false; +} + +int TaskFn::runInner() +{ +	if(!std::filesystem::exists(sourceFile)) +	{ +		std::cout << "Missing source file: " << sourceFile.string() << "\n"; +		return 1; +	} + +	if(settings.verbose >= 0) +	{ +		std::cout << "Fn" << " " << +			sourceFile.lexically_normal().string() << " => " << +			targetFile().lexically_normal().string() << "\n"; +	} + +	return config.function(sourceFile.string(), +	                       targetFile().string(), +	                       config, +	                       settings); +} + +int TaskFn::clean() +{ +	if(std::filesystem::exists(targetFile())) +	{ +		std::cout << "Removing " << targetFile().string() << "\n"; +		std::filesystem::remove(targetFile()); +	} + +	return 0; +} + +std::vector<std::string> TaskFn::depends() const +{ +	return {}; +} + +std::string TaskFn::target() const +{ +	return _targetFile; +} + +std::filesystem::path TaskFn::targetFile() const +{ +	return std::filesystem::path(settings.builddir) / sourceDir / _targetFile; +} + +bool TaskFn::derived() const +{ +	return false; +} + +std::string TaskFn::toJSON() const +{ +	return {}; // TODO: Not sure how to express this... +} + +std::string TaskFn::source() const +{ +	return sourceFile.string(); +} diff --git a/src/task_fn.h b/src/task_fn.h new file mode 100644 index 0000000..e350395 --- /dev/null +++ b/src/task_fn.h @@ -0,0 +1,47 @@ +// -*- c++ -*- +// Distributed under the BSD 2-Clause License. +// See accompanying file LICENSE for details. +#pragma once + +#include "task.h" + +#include <vector> +#include <string> +#include <future> +#include <filesystem> + +struct BuildConfiguration; +struct Settings; + +class TaskFn +	: public Task +{ +public: +	TaskFn(const BuildConfiguration& config, +	       const Settings& settings, +	       const std::string& sourceDir, const Source& source); +	virtual ~TaskFn() = default; + +	bool dirtyInner() override; + +	int runInner() override; +	int clean() override; + +	std::vector<std::string> depends() const override; + +	std::string target() const override; +	std::filesystem::path targetFile() const override; +	bool derived() const override; + +	std::string toJSON() const override; + +	std::string source() const override; + +protected: +	std::filesystem::path sourceFile; +	std::filesystem::path _targetFile; + +	const BuildConfiguration& config; +	const Settings& settings; +	std::filesystem::path sourceDir; +}; diff --git a/src/task_ld.cc b/src/task_ld.cc index 1b5665e..20e823d 100644 --- a/src/task_ld.cc +++ b/src/task_ld.cc @@ -7,32 +7,19 @@  #include <fstream>  #include "libctor.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); -} -} // namespace :: +#include "util.h" +#include "tools.h"  TaskLD::TaskLD(const BuildConfiguration& config,                 const Settings& settings,                 const std::string& target, -               const std::vector<std::string>& objects) -	: Task(config) +               const std::vector<std::string>& objects, +               const std::string& sourceDir) +	: Task(config, settings, sourceDir)  	, config(config)  	, settings(settings) +	, sourceDir(sourceDir)  {  	target_type = config.type;  	if(target_type == TargetType::Auto) @@ -40,8 +27,9 @@ TaskLD::TaskLD(const BuildConfiguration& config,  		target_type = TargetType::Executable;  	} -	targetFile = settings.builddir; -	targetFile /= target; +	std::filesystem::create_directories(std::filesystem::path(settings.builddir) / sourceDir); + +	_targetFile = target;  	for(const auto& object : objects)  	{  		std::filesystem::path objectFile = object; @@ -51,18 +39,16 @@ TaskLD::TaskLD(const BuildConfiguration& config,  	for(const auto& dep : config.depends)  	{ -		std::filesystem::path depFile = settings.builddir; -		depFile /= dep; -		depFiles.push_back(depFile); +		depFiles.push_back(dep);  	} -	flagsFile = settings.builddir / targetFile.stem(); +	flagsFile = std::filesystem::path(settings.builddir) / cleanUp(sourceDir) / targetFile().stem();  	flagsFile += ".flags";  	source_language = Language::C;  	for(const auto& source : config.sources)  	{ -		std::filesystem::path sourceFile(source); +		std::filesystem::path sourceFile(source.file);  		if(sourceFile.extension().string() != ".c")  		{  			source_language = Language::Cpp; @@ -72,7 +58,7 @@ TaskLD::TaskLD(const BuildConfiguration& config,  bool TaskLD::dirtyInner()  { -	if(!std::filesystem::exists(targetFile)) +	if(!std::filesystem::exists(targetFile()))  	{  		return true;  	} @@ -82,15 +68,6 @@ bool TaskLD::dirtyInner()  		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) @@ -105,42 +82,27 @@ bool TaskLD::dirtyInner()  int TaskLD::runInner()  { -	std::string objectlist; -	for(const auto& objectFile : objectFiles) -	{ -		if(!objectlist.empty()) -		{ -			objectlist += " "; -		} -		objectlist += objectFile.string(); -	} +	auto tool_chain = getToolChain(config.system);  	std::vector<std::string> args; -	for(const auto& objectFile : objectFiles) -	{ -		args.push_back(objectFile.string()); -	} - -	for(const auto& depFile : depFiles) +	for(const auto& dep : getDependsTasks())  	{ +		auto depFile = dep->targetFile();  		if(depFile.extension() == ".so")  		{ -			args.push_back(std::string("-L") + settings.builddir); +			append(args, getOption(tool_chain, opt::library_path, +			                       targetFile().parent_path().string()));  			auto lib = depFile.stem().string().substr(3); // strip 'lib' prefix -			args.push_back(std::string("-l") + lib); +			append(args, getOption(tool_chain, opt::link, lib));  		} -		else if(depFile.extension() == ".a") +		else if(depFile.extension() == ".a" || depFile.extension() == ".o")  		{  			args.push_back(depFile.string());  		}  	} -	for(const auto& flag : config.ldflags) -	{ -		args.push_back(flag); -	} -	args.push_back("-o"); -	args.push_back(targetFile.string()); +	append(args, config.flags.ldflags); +	append(args, getOption(tool_chain, opt::output, targetFile().string()));  	{ // Write flags to file.  		std::ofstream flagsStream(flagsFile); @@ -149,7 +111,7 @@ int TaskLD::runInner()  	if(settings.verbose == 0)  	{ -		std::cout << "LD => " << targetFile.string() << "\n"; +		std::cout << "LD => " << targetFile().string() << "\n";  	}  	auto tool = compiler(); @@ -158,10 +120,10 @@ int TaskLD::runInner()  int TaskLD::clean()  { -	if(std::filesystem::exists(targetFile)) +	if(std::filesystem::exists(targetFile()))  	{ -		std::cout << "Removing " << targetFile.string() << "\n"; -		std::filesystem::remove(targetFile); +		std::cout << "Removing " << targetFile().string() << "\n"; +		std::filesystem::remove(targetFile());  	}  	if(std::filesystem::exists(flagsFile)) @@ -191,15 +153,25 @@ std::vector<std::string> TaskLD::depends() const  std::string TaskLD::target() const  { -	return targetFile.string(); +	return _targetFile.string(); +} + +std::filesystem::path TaskLD::targetFile() const +{ +	return std::filesystem::path(settings.builddir) / sourceDir / _targetFile; +} + +bool TaskLD::derived() const +{ +	return false;  }  std::string TaskLD::flagsString() const  {  	std::string flagsStr; -	for(const auto& flag : config.ldflags) +	for(const auto& flag : config.flags.ldflags)  	{ -		if(flag != config.ldflags[0]) +		if(flag != config.flags.ldflags[0])  		{  			flagsStr += " ";  		} @@ -216,5 +188,12 @@ std::string TaskLD::flagsString() const  		flagsStr += dep;  	} +	auto deps = depends(); +	for(const auto& dep : deps) +	{ +		flagsStr += " "; +		flagsStr += dep; +	} +  	return flagsStr;  } diff --git a/src/task_ld.h b/src/task_ld.h index 730975a..8625075 100644 --- a/src/task_ld.h +++ b/src/task_ld.h @@ -20,7 +20,9 @@ public:  	TaskLD(const BuildConfiguration& config,  	       const Settings& settings,  	       const std::string& target, -	       const std::vector<std::string>& objects); +	       const std::vector<std::string>& objects, +	       const std::string& _sourceDir); +	virtual ~TaskLD() = default;  	bool dirtyInner() override; @@ -30,15 +32,19 @@ public:  	std::vector<std::string> depends() const override;  	std::string target() const override; +	std::filesystem::path targetFile() const override; + +	bool derived() 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 _targetFile;  	std::filesystem::path flagsFile;  	const BuildConfiguration& config;  	const Settings& settings; +	std::string sourceDir;  }; diff --git a/src/task_so.cc b/src/task_so.cc index 5623bcf..8c6dbd4 100644 --- a/src/task_so.cc +++ b/src/task_so.cc @@ -7,35 +7,24 @@  #include <fstream>  #include "libctor.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); -} -} // namespace :: +#include "util.h" +#include "tools.h"  TaskSO::TaskSO(const BuildConfiguration& config,                 const Settings& settings,                 const std::string& target, -               const std::vector<std::string>& objects) -	: Task(config) +               const std::vector<std::string>& objects, +               const std::string& sourceDir) +	: Task(config, settings, sourceDir)  	, config(config)  	, settings(settings) +	, sourceDir(sourceDir)  { -	targetFile = settings.builddir; -	targetFile /= target; +	std::filesystem::path base = sourceDir; +	std::filesystem::create_directories(std::filesystem::path(settings.builddir) / base); + +	_targetFile = base / target;  	for(const auto& object : objects)  	{  		std::filesystem::path objectFile = object; @@ -45,19 +34,18 @@ TaskSO::TaskSO(const BuildConfiguration& config,  	for(const auto& dep : config.depends)  	{ -		std::filesystem::path depFile = settings.builddir; -		depFile /= dep; -		depFiles.push_back(depFile); +		depFiles.push_back(dep);  	} -	flagsFile = settings.builddir / targetFile.stem(); +	flagsFile = std::filesystem::path(settings.builddir) / cleanUp(sourceDir) / targetFile().stem();  	flagsFile += ".flags";  	target_type = TargetType::DynamicLibrary;  	source_language = Language::C;  	for(const auto& source : config.sources)  	{ -		std::filesystem::path sourceFile(source); +		std::filesystem::path sourceFile(source.file); +		// TODO: Use task languages instead  		if(sourceFile.extension().string() != ".c")  		{  			source_language = Language::Cpp; @@ -67,7 +55,7 @@ TaskSO::TaskSO(const BuildConfiguration& config,  bool TaskSO::dirtyInner()  { -	if(!std::filesystem::exists(targetFile)) +	if(!std::filesystem::exists(targetFile()))  	{  		return true;  	} @@ -77,15 +65,6 @@ bool TaskSO::dirtyInner()  		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) @@ -100,38 +79,21 @@ bool TaskSO::dirtyInner()  int TaskSO::runInner()  { -	std::string objectlist; -	for(const auto& objectFile : objectFiles) -	{ -		if(!objectlist.empty()) -		{ -			objectlist += " "; -		} -		objectlist += objectFile.string(); -	} +	auto tool_chain = getToolChain(config.system);  	std::vector<std::string> args; -	args.push_back("-fPIC"); -	args.push_back("-shared"); - -	args.push_back("-o"); -	args.push_back(targetFile.string()); +	append(args, getOption(tool_chain, opt::position_independent_code)); +	append(args, getOption(tool_chain, opt::build_shared)); -	for(const auto& objectFile : objectFiles) -	{ -		args.push_back(objectFile.string()); -	} +	append(args, getOption(tool_chain, opt::output, targetFile().string())); -	for(const auto& depFile : depFiles) +	for(const auto& task : getDependsTasks())  	{ -		args.push_back(depFile.string()); +		args.push_back(task->targetFile().string());  	} -	for(const auto& flag : config.ldflags) -	{ -		args.push_back(flag); -	} +	append(args, config.flags.ldflags);  	{ // Write flags to file.  		std::ofstream flagsStream(flagsFile); @@ -140,7 +102,7 @@ int TaskSO::runInner()  	if(settings.verbose == 0)  	{ -		std::cout << "LD => " << targetFile.string() << "\n"; +		std::cout << "LD => " << targetFile().string() << "\n";  	}  	auto tool = compiler(); @@ -149,10 +111,10 @@ int TaskSO::runInner()  int TaskSO::clean()  { -	if(std::filesystem::exists(targetFile)) +	if(std::filesystem::exists(targetFile()))  	{ -		std::cout << "Removing " << targetFile.string() << "\n"; -		std::filesystem::remove(targetFile); +		std::cout << "Removing " << targetFile().string() << "\n"; +		std::filesystem::remove(targetFile());  	}  	if(std::filesystem::exists(flagsFile)) @@ -182,13 +144,23 @@ std::vector<std::string> TaskSO::depends() const  std::string TaskSO::target() const  { -	return targetFile.string(); +	return _targetFile.string(); +} + +std::filesystem::path TaskSO::targetFile() const +{ +	return std::filesystem::path(settings.builddir) / sourceDir / _targetFile; +} + +bool TaskSO::derived() const +{ +	return false;  }  std::string TaskSO::flagsString() const  {  	std::string flagsStr = compiler(); -	for(const auto& flag : config.ldflags) +	for(const auto& flag : config.flags.ldflags)  	{  		flagsStr += " " + flag;  	} @@ -203,5 +175,12 @@ std::string TaskSO::flagsString() const  		flagsStr += dep;  	} +	auto deps = depends(); +	for(const auto& dep : deps) +	{ +		flagsStr += " "; +		flagsStr += dep; +	} +  	return flagsStr;  } diff --git a/src/task_so.h b/src/task_so.h index 1e65694..fe5d2fd 100644 --- a/src/task_so.h +++ b/src/task_so.h @@ -20,7 +20,9 @@ public:  	TaskSO(const BuildConfiguration& config,  	       const Settings& settings,  	       const std::string& target, -	       const std::vector<std::string>& objects); +	       const std::vector<std::string>& objects, +	       const std::string& sourceDir); +	virtual ~TaskSO() = default;  	bool dirtyInner() override; @@ -30,15 +32,19 @@ public:  	std::vector<std::string> depends() const override;  	std::string target() const override; +	std::filesystem::path targetFile() const override; + +	bool derived() 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 _targetFile;  	std::filesystem::path flagsFile;  	const BuildConfiguration& config;  	const Settings& settings; +	std::string sourceDir;  }; diff --git a/src/tasks.cc b/src/tasks.cc index a52b0be..68b2476 100644 --- a/src/tasks.cc +++ b/src/tasks.cc @@ -5,24 +5,28 @@  #include <filesystem>  #include <deque> +#include <list>  #include <iostream>  #include <algorithm> -#include "settings.h"  #include "libctor.h"  #include "task.h"  #include "task_cc.h"  #include "task_ld.h"  #include "task_ar.h"  #include "task_so.h" +#include "task_fn.h"  #include "rebuild.h" +#include "configure.h" -const std::deque<Target>& getTargets(const Settings& settings) +const std::deque<Target>& getTargets(const Settings& settings, +                                     bool resolve_externals)  {  	static bool initialised{false};  	static std::deque<Target> targets;  	if(!initialised)  	{ +		const auto& externals = configuration().externals;  		for(std::size_t i = 0; i < numConfigFiles; ++i)  		{  			std::string path = @@ -31,9 +35,39 @@ const std::deque<Target>& getTargets(const Settings& settings)  			{  				std::cout << configFiles[i].file << " in path " << path << "\n";  			} -			auto configs = configFiles[i].cb(); -			for(const auto& config : configs) +			auto configs = configFiles[i].cb(settings); +			for(auto& config : configs)  			{ +				if(resolve_externals) +				{ +					// Resolv config externals +					for(const auto& external : config.externals) +					{ +						if(externals.find(external) == externals.end()) +						{ +							std::cout << "External '" << external << +								"' not found in cache - run configure.\n"; +							exit(1); +						} +						const auto& flags = externals.at(external); +						config.flags.cflags.insert(config.flags.cflags.end(), +						                           flags.cflags.begin(), +						                           flags.cflags.end()); +						config.flags.cxxflags.insert(config.flags.cxxflags.end(), +						                             flags.cxxflags.begin(), +						                             flags.cxxflags.end()); +						config.flags.ldflags.insert(config.flags.ldflags.end(), +						                            flags.ldflags.begin(), +						                            flags.ldflags.end()); +						config.flags.asmflags.insert(config.flags.asmflags.end(), +						                             flags.asmflags.begin(), +						                             flags.asmflags.end()); +						//config.libs.insert(config.libs.end(), +						//                   libs.begin(), +						//                   libs.end()); +					} +				} +  				targets.push_back({config, path});  			}  		} @@ -43,18 +77,22 @@ const std::deque<Target>& getTargets(const Settings& settings)  	return targets;  } -namespace -{ -std::list<std::shared_ptr<Task>> taskFactory(const BuildConfiguration& config, -                                             const Settings& settings, -                                             const std::string& sourceDir) +std::set<std::shared_ptr<Task>> taskFactory(const BuildConfiguration& config, +                                            const Settings& settings, +                                            const std::string& sourceDir)  { +	std::set<std::shared_ptr<Task>> tasks; +  	std::filesystem::path targetFile(config.target);  	TargetType target_type{config.type};  	if(target_type == TargetType::Auto)  	{ -		if(targetFile.extension() == ".a") +		if(config.function != nullptr) +		{ +			target_type = TargetType::Function; +		} +		else if(targetFile.extension() == ".a")  		{  			target_type = TargetType::StaticLibrary;  		} @@ -75,13 +113,26 @@ std::list<std::shared_ptr<Task>> taskFactory(const BuildConfiguration& config,  	}  	std::vector<std::string> objects; -	std::list<std::shared_ptr<Task>> tasks; -	for(const auto& file : config.sources) +	if(target_type != TargetType::Function) +	{ +		for(const auto& file : config.sources) +		{ +			auto task = std::make_shared<TaskCC>(config, settings, sourceDir, file); +			tasks.insert(task); +			objects.push_back(task->targetFile().string()); +		} +	} +#ifndef BOOTSTRAP +	else  	{ -		tasks.emplace_back(std::make_shared<TaskCC>(config, settings, -		                                            sourceDir, file)); -		objects.push_back(tasks.back()->target()); +		for(const auto& file : config.sources) +		{ +			auto task = std::make_shared<TaskFn>(config, settings, sourceDir, file); +			tasks.insert(task); +			objects.push_back(task->target()); +		}  	} +#endif  	switch(target_type)  	{ @@ -90,36 +141,42 @@ std::list<std::shared_ptr<Task>> taskFactory(const BuildConfiguration& config,  		break;  	case TargetType::StaticLibrary: -		tasks.emplace_back(std::make_shared<TaskAR>(config, settings, config.target, -		                                            objects)); +	case TargetType::UnitTestLib: +		tasks.insert(std::make_shared<TaskAR>(config, settings, config.target, +		                                      objects, sourceDir));  		break; - +#ifndef BOOTSTRAP  	case TargetType::DynamicLibrary: +		// TODO: Use C++20 starts_with  		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)); +		tasks.insert(std::make_shared<TaskSO>(config, settings, config.target, +		                                      objects, sourceDir));  		break;  	case TargetType::Executable:  	case TargetType::UnitTest: -		tasks.emplace_back(std::make_shared<TaskLD>(config, settings, config.target, -		                                            objects)); +		tasks.insert(std::make_shared<TaskLD>(config, settings, config.target, +		                                      objects, sourceDir));  		break;  	case TargetType::Object: +	case TargetType::Function: +		break; +#else +	default:  		break; +#endif  	}  	return tasks;  } -} -std::shared_ptr<Task> getNextTask(const std::list<std::shared_ptr<Task>>& allTasks, -                                  std::list<std::shared_ptr<Task>>& dirtyTasks) +std::shared_ptr<Task> getNextTask(const std::set<std::shared_ptr<Task>>& allTasks, +                                  std::set<std::shared_ptr<Task>>& dirtyTasks)  {  	for(auto dirtyTask = dirtyTasks.begin();  	    dirtyTask != dirtyTasks.end(); @@ -137,11 +194,12 @@ std::shared_ptr<Task> getNextTask(const std::list<std::shared_ptr<Task>>& allTas  	return nullptr;  } -std::list<std::shared_ptr<Task>> getTasks(const Settings& settings, -                                          const std::vector<std::string> names) +std::set<std::shared_ptr<Task>> getTasks(const Settings& settings, +                                         const std::vector<std::string> names, +                                         bool resolve_externals)  { -	auto& targets = getTargets(settings); -	std::list<std::shared_ptr<Task>> tasks; +	auto& targets = getTargets(settings, resolve_externals); +	std::set<std::shared_ptr<Task>> tasks;  	for(const auto& target : targets)  	{  		if(names.empty() || @@ -149,7 +207,7 @@ std::list<std::shared_ptr<Task>> getTasks(const Settings& settings,  		{  			std::vector<std::string> objects;  			auto t = taskFactory(target.config, settings, target.path); -			tasks.insert(tasks.end(), t.begin(), t.end()); +			tasks.insert(t.begin(), t.end());  		}  	} diff --git a/src/tasks.h b/src/tasks.h index 54591a4..c547432 100644 --- a/src/tasks.h +++ b/src/tasks.h @@ -4,7 +4,7 @@  #pragma once  #include <string> -#include <list> +#include <set>  #include <memory>  #include <deque> @@ -20,16 +20,24 @@ struct Target  };  //! Get list of all registered targets -const std::deque<Target>& getTargets(const Settings& settings); +const std::deque<Target>& getTargets(const Settings& settings, +                                     bool resolve_externals = true);  //! Returns next dirty task from the dirtyTasks list that has all its dependencies  //! fulfilled.  //! The returned task is removed from the dirty list.  //! Return nullptr if no dirty task is ready. -std::shared_ptr<Task> getNextTask(const std::list<std::shared_ptr<Task>>& allTasks, -                                  std::list<std::shared_ptr<Task>>& dirtyTasks); +std::shared_ptr<Task> getNextTask(const std::set<std::shared_ptr<Task>>& allTasks, +                                  std::set<std::shared_ptr<Task>>& dirtyTasks);  //! Get list of tasks filtered by name including each of their direct  //! dependency tasks (ie. objects tasks from their sources). -std::list<std::shared_ptr<Task>> getTasks(const Settings& settings, -                                          const std::vector<std::string> names = {}); +std::set<std::shared_ptr<Task>> getTasks(const Settings& settings, +                                         const std::vector<std::string> names = {}, +                                         bool resolve_externals = true); + +//! Generate list of targets from a single configuration, including the final +//! link target and all its objects files (if any). +std::set<std::shared_ptr<Task>> taskFactory(const BuildConfiguration& config, +                                            const Settings& settings, +                                            const std::string& sourceDir); diff --git a/src/tools.cc b/src/tools.cc new file mode 100644 index 0000000..7e8ac78 --- /dev/null +++ b/src/tools.cc @@ -0,0 +1,136 @@ +// -*- c++ -*- +// Distributed under the BSD 2-Clause License. +// See accompanying file LICENSE for details. +#include "tools.h" + +#include <filesystem> +#include <iostream> + +#include <cassert> + +ToolChain getToolChain(OutputSystem system) +{ +	std::string compiler; +	switch(system) +	{ +	case OutputSystem::Host: +		compiler = getConfiguration(cfg::host_cxx, "g++"); +		break; +	case OutputSystem::Build: +		compiler = getConfiguration(cfg::build_cxx, "g++"); +		break; +	} + +	std::filesystem::path cc(compiler); +	auto cc_cmd = cc.stem().string(); + +	// Note: "g++" is a substring of "clang++" so "clang++" must be tested first. +	if(cc_cmd.find("clang++") != std::string::npos) +	{ +		return ToolChain::clang; +	} +	else if(cc_cmd.find("g++") != std::string::npos) +	{ +		return ToolChain::gcc; +	} + +	std::cerr << "Unsupported output system.\n"; +	return ToolChain::gcc; +} + +namespace +{ +std::vector<std::string> getOptionGcc(opt option, const std::string& arg) +{ +	switch(option) +	{ +	case opt::output: +		return {"-o", arg}; +	case opt::debug: +		return {"-g"}; +	case opt::strip: +		return {"-s"}; +	case opt::warn_all: +		return {"-Wall"}; +	case opt::warnings_as_errors: +		return {"-Werror"}; +	case opt::generate_dep_tree: +		return {"-MMD"}; +	case opt::no_link: +		return {"-c"}; +	case opt::include_path: +		return {"-I" + arg}; +	case opt::library_path: +		return {"-L" + arg}; +	case opt::link: +		return {"-l" + arg}; +	case opt::cpp_std: +		return {"-std=" + arg}; +	case opt::build_shared: +		return {"-shared"}; +	case opt::threads: +		return {"-pthread"}; +	case opt::optimization: +		return {"-O" + arg}; +	case opt::position_independent_code: +		return {"-fPIC"}; +	case opt::position_independent_executable: +		return {"-fPIE"}; +	case opt::custom: +		return {arg}; +	} + +	std::cerr << "Unsupported compiler option.\n"; +	return {}; +} +} + +std::vector<std::string> getOption(ToolChain tool_chain, +                                   opt option, +                                   const std::string& arg) +{ +	switch(tool_chain) +	{ +	case ToolChain::gcc: +	case ToolChain::clang: +		return getOptionGcc(option, arg); +	} + +	std::cerr << "Unsupported tool-chain.\n"; +	return {}; +} + +namespace { +std::pair<opt, std::string> getOptionGcc(const std::string& flag) +{ +	if(flag.substr(0, 2) == "-I") +	{ +		std::string path = flag.substr(2); +		path.erase(0, path.find_first_not_of(' ')); +		return { opt::include_path, path }; +	} + +	if(flag.substr(0, 2) == "-L") +	{ +		std::string path = flag.substr(2); +		path.erase(0, path.find_first_not_of(' ')); +		return { opt::library_path, path }; +	} + +	return { opt::custom, flag }; +} +} + +std::pair<opt, std::string> getOption(const std::string& flag, +                                      ToolChain tool_chain) +{ +	switch(tool_chain) +	{ +	case ToolChain::gcc: +	case ToolChain::clang: +		return getOptionGcc(flag); +	} + +	std::cerr << "Unsupported tool-chain.\n"; +	return { opt::custom, flag }; +} diff --git a/src/tools.h b/src/tools.h new file mode 100644 index 0000000..39118d2 --- /dev/null +++ b/src/tools.h @@ -0,0 +1,52 @@ +// -*- c++ -*- +// Distributed under the BSD 2-Clause License. +// See accompanying file LICENSE for details. +#pragma once + +#include <vector> +#include <string> + +#include "libctor.h" + +enum class ToolChain +{ +	gcc, +	clang, +}; + +enum class opt +{ +	//                                  gcc/clang +	output,                          //  -o +	debug,                           //  -g +	strip,                           //  -s +	warn_all,                        //  -Wall +	warnings_as_errors,              //  -Werror +	generate_dep_tree,               //  -MMD +	no_link,                         //  -c +	include_path,                    //  -I<arg> +	library_path,                    //  -L<arg> +	link,                            //  -l<arg> +	cpp_std,                         //  -std=<arg> +	build_shared,                    //  -shared +	threads,                         //  -pthread +	optimization,                    //  -O<arg> +	position_independent_code,       //  -fPIC +	position_independent_executable, //  -fPIE +	custom,                          // entire option taken verbatim from <arg> +}; + +//! Get tool-chain type from output system (via configuration) +ToolChain getToolChain(OutputSystem system); + +//! Get tool argument(s) for specific option type matching the supplied +//! tool-chain +std::vector<std::string> getOption(ToolChain tool_chain, +                                   opt option, +                                   const std::string& arg = {}); + +//! Get opt enum value and argument from string, +//! ie. { opt::InludePath, "foo/bar" } from "-Ifoo/bar" +//! Returns { opt::Custom, flag } if unknown. +std::pair<opt, std::string> getOption(const std::string& flag, +                                      ToolChain tool_chain = ToolChain::gcc); diff --git a/src/unittest.cc b/src/unittest.cc index ade2d0a..f18de47 100644 --- a/src/unittest.cc +++ b/src/unittest.cc @@ -6,10 +6,9 @@  #include <iostream>  #include "execute.h" -#include "settings.h"  #include "task.h" -int runUnitTests(std::list<std::shared_ptr<Task>>& tasks, +int runUnitTests(std::set<std::shared_ptr<Task>>& tasks,                   const Settings& settings)  {  	bool ok{true}; @@ -19,16 +18,21 @@ int runUnitTests(std::list<std::shared_ptr<Task>>& tasks,  	{  		if(task->targetType() == TargetType::UnitTest)  		{ -			std::cout << task->name() << ": "; -			auto ret = execute(task->target(), {}, false); +			auto name = task->name(); +			if(name.empty()) +			{ +				name = task->target(); +			} +			std::cout << name << ": " << std::flush; +			auto ret = execute(task->targetFile(), {}, settings.verbose > 0);  			ok &= ret == 0;  			if(ret == 0)  			{ -				std::cout << "OK\n"; +				std::cout << " OK\n";  			}  			else  			{ -				std::cout << "FAILED\n"; +				std::cout << " FAILED\n";  			}  		}  	} diff --git a/src/unittest.h b/src/unittest.h index 7eef0e2..8dee33c 100644 --- a/src/unittest.h +++ b/src/unittest.h @@ -3,11 +3,11 @@  // See accompanying file LICENSE for details.  #pragma once -#include <list> +#include <set>  #include <memory>  class Task;  class Settings; -int runUnitTests(std::list<std::shared_ptr<Task>>& tasks, +int runUnitTests(std::set<std::shared_ptr<Task>>& tasks,                   const Settings& settings); diff --git a/src/util.cc b/src/util.cc new file mode 100644 index 0000000..92560b6 --- /dev/null +++ b/src/util.cc @@ -0,0 +1,137 @@ +// -*- c++ -*- +// Distributed under the BSD 2-Clause License. +// See accompanying file LICENSE for details. +#include "util.h" + +#include <iostream> +#include <fstream> + +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; +} + +Language languageFromExtension(const std::filesystem::path& file) +{ +	auto ext = file.extension().string(); +	if(ext == ".c") +	{ +		return Language::C; +	} + +	if(ext == ".C" || +	   ext == ".cc" || +	   ext == ".cpp" || +	   ext == ".CPP" || +	   ext == ".c++" || +	   ext == ".cp" || +	   ext == ".cxx") +	{ +		return Language::Cpp; +	} + +	if(ext == ".s" || +	   ext == ".S" || +	   ext == ".asm") +	{ +		return Language::Asm; +	} + +	std::cerr << "Could not deduce language from " << file.string() << "\n"; +	exit(1); +	return {}; +} + +namespace +{ +bool isClean(char c) +{ +	return c != '.' && c != '/'; +} +} + +std::string cleanUp(const std::string& path) +{ +	std::string cleaned; +	for(const auto& c : path) +	{ +		if(isClean(c)) +		{ +			cleaned += c; +		} +		else +		{ +			cleaned += '_'; +		} +	} +	return cleaned; +} diff --git a/src/util.h b/src/util.h new file mode 100644 index 0000000..c8b591a --- /dev/null +++ b/src/util.h @@ -0,0 +1,20 @@ +// -*- c++ -*- +// Distributed under the BSD 2-Clause License. +// See accompanying file LICENSE for details. +#pragma once + +#include "libctor.h" + +#include <string> +#include <filesystem> + +std::string readFile(const std::string& fileName); +std::vector<std::string> readDeps(const std::string& depFile); +Language languageFromExtension(const std::filesystem::path& file); +std::string cleanUp(const std::string& path); + +template<typename T> +void append(T& a, const T& b) +{ +	a.insert(a.end(), b.begin(), b.end()); +} diff --git a/test/ctor.cc b/test/ctor.cc index 8080d61..9b690a2 100644 --- a/test/ctor.cc +++ b/test/ctor.cc @@ -5,7 +5,7 @@  namespace  { -BuildConfigurations ctorTestConfigs() +BuildConfigurations ctorTestConfigs(const Settings& settings)  {  	return  	{ @@ -14,13 +14,16 @@ BuildConfigurations ctorTestConfigs()  			.target = "execute_test",  			.sources = {  				"execute_test.cc", -				"uunit/uunit.cc", +				"testmain.cc",  				"../src/execute.cc",  			}, -			.cxxflags = { -				"-std=c++17", "-O3", "-s", "-Wall", "-Werror", -				"-I../src", "-Iuunit", -				"-DOUTPUT=\"execute\"", +			.flags = { +				.cxxflags = { +					"-std=c++20", "-O3", "-s", "-Wall", "-Werror", +					"-I../src", "-Iuunit", +					"-DOUTPUT=\"execute\"", +				}, +				.ldflags = { "-pthread" },  			},  		},  		{ @@ -28,13 +31,77 @@ BuildConfigurations ctorTestConfigs()  			.target = "tasks_test",  			.sources = {  				"tasks_test.cc", -				"uunit/uunit.cc", +				"testmain.cc",  			}, -			.depends = {"libctor.a"}, -			.cxxflags = { -				"-std=c++17", "-O3", "-s", "-Wall", "-Werror", -				"-I../src", "-Iuunit", -				"-DOUTPUT=\"tasks\"", +			.depends = { "libctor_nomain.a" }, +			.flags = { +				.cxxflags = { +					"-std=c++20", "-O3", "-s", "-Wall", "-Werror", +					"-I../src", "-Iuunit", +					"-DOUTPUT=\"tasks\"", +				}, +				.ldflags = { "-pthread" }, +			}, +		}, +		{ +			.type = TargetType::UnitTest, +			.target = "source_type_test", +			.sources = { +				"source_type_test.cc", +				"testmain.cc", +			}, +			.depends = { "libctor_nomain.a" }, +			.flags = { +				.cxxflags = { +					"-std=c++20", "-O3", "-s", "-Wall", "-Werror", +					"-I../src", "-Iuunit", +					"-DOUTPUT=\"source_type\"", +				}, +				.ldflags = { "-pthread" }, +			}, +		}, +		{ +			.type = TargetType::UnitTest, +			.target = "tools_test", +			.sources = { +				"tools_test.cc", +				"testmain.cc", +				"../src/tools.cc", +			}, +			//.depends = { "libctor_nomain.a" }, +			.flags = { +				.cxxflags = { +					"-std=c++20", "-O3", "-Wall", "-Werror", +					"-I../src", "-Iuunit", +					"-DOUTPUT=\"tools\"", +				}, +			}, +		}, +		{ +			.type = TargetType::UnitTestLib, +			.target = "libctor_nomain.a", +			.sources = { +				"../src/build.cc", +				"../src/configure.cc", +				"../src/execute.cc", +				"../src/rebuild.cc", +				"../src/tasks.cc", +				"../src/task.cc", +				"../src/task_ar.cc", +				"../src/task_cc.cc", +				"../src/task_fn.cc", +				"../src/task_ld.cc", +				"../src/task_so.cc", +				"../src/tools.cc", +				"../src/util.cc", +				"../src/externals_manual.cc", +			}, +			.flags = { +				.cxxflags = { +					"-std=c++20", "-O3", "-s", "-Wall", "-Werror", +					"-I../src", +				}, +				.ldflags = { "-pthread" },  			},  		},  	}; diff --git a/test/paths.h b/test/paths.h new file mode 100644 index 0000000..f149972 --- /dev/null +++ b/test/paths.h @@ -0,0 +1,15 @@ +// -*- c++ -*- +// Distributed under the BSD 2-Clause License. +// See accompanying file LICENSE for details. +#pragma once + +#include <string> +#include <filesystem> + +namespace paths +{ +extern std::string argv_0; +extern std::filesystem::path top_srcdir; +extern std::filesystem::path top_builddir; +extern std::filesystem::path testdir; +} diff --git a/test/source_type_test.cc b/test/source_type_test.cc new file mode 100644 index 0000000..ed7e783 --- /dev/null +++ b/test/source_type_test.cc @@ -0,0 +1,76 @@ +#include <uunit.h> + +#include <libctor.h> +#include <task_cc.h> + +std::ostream& operator<<(std::ostream& stream, const Language& lang) +{ +	switch(lang) +	{ +	case Language::Auto: +		stream << "Language::Auto"; +		break; +	case Language::C: +		stream << "Language::C"; +		break; +	case Language::Cpp: +		stream << "Language::Cpp"; +		break; +	case Language::Asm: +		stream << "Language::Asm"; +		break; +	} + +	return stream; +} +class TestableTaskCC +	: public TaskCC +{ +public: +	TestableTaskCC(const Source& source) +		: TaskCC({}, {}, "build", source) +	{} + +	Language language() const +	{ +		return source_language; +	} +}; + +class SourceTypeTest +	: public uUnit +{ +public: +	SourceTypeTest() +	{ +		uTEST(SourceTypeTest::test); +	} + +	void test() +	{ +		{ // c++ +			TestableTaskCC task("hello.cc"); +			uASSERT_EQUAL(Language::Cpp, task.language()); +		} + +		{ // c +			TestableTaskCC task("hello.c"); +			uASSERT_EQUAL(Language::C, task.language()); +		} + +		{ // asm +			TestableTaskCC task("hello.s"); +			uASSERT_EQUAL(Language::Asm, task.language()); +		} + +		{ // custom/explicit language +			TestableTaskCC task( {"hello.foo", Language::Asm} ); +			uASSERT_EQUAL(Language::Asm, task.language()); +		} + +		// Note: Failure state will result in exit(1) so cannot be tested +	} +}; + +// Registers the fixture into the 'registry' +static SourceTypeTest test; diff --git a/test/suite/ctor_files/ctor.cc.bar b/test/suite/ctor_files/ctor.cc.bar new file mode 100644 index 0000000..92456cb --- /dev/null +++ b/test/suite/ctor_files/ctor.cc.bar @@ -0,0 +1,62 @@ +// -*- c++ -*- +// Distributed under the BSD 2-Clause License. +// See accompanying file LICENSE for details. +#include <libctor.h> +//#include "config.h" + +namespace +{ +BuildConfigurations ctorConfigs() +{ +	return +	{ +		{ +			.name = "hello", +			.target = "hello", +			.sources = { +				"hello.cc", +			}, +			.flags = { +				.cxxflags = { +					"-std=c++20", +					"-O3", +					"-g", +					"-Wall", +					"-Werror", +				}, +			}, +			.externals = {"bar"}, +		} +	}; +} + +ExternalConfigurations ctorExtConfigs() +{ +	return +	{ +		{ +			.name = "bar", +			.flags = { +				.cxxflags = { "-D_A_", "-DBAR"}, +				.cflags = { "-D_B_" }, +				.ldflags = { "-D_C_" }, +				.asmflags = { "-D_D_" }, +			}, +			// Creates --with-foo-prefix arg to configure which will be used for +			// -L and -I flags. +			// If not specified configure will try to find them in the system paths. +		}, +//		{ +//			.name = "bar", +//			.type = TargetType::ExternalPkgConfig, +//			.min_version = "0.1", +//			.max_version = "0.9", +//			// cflags, cxxflags and ldflags deduced by pkg-config tool (or parsed +//			// directly from .pc if faster) +//		}, +	}; +} +} + +REG(ctorConfigs); +REG(ctorExtConfigs); diff --git a/test/suite/ctor_files/ctor.cc.base b/test/suite/ctor_files/ctor.cc.base new file mode 100644 index 0000000..6c60513 --- /dev/null +++ b/test/suite/ctor_files/ctor.cc.base @@ -0,0 +1,62 @@ +// -*- c++ -*- +// Distributed under the BSD 2-Clause License. +// See accompanying file LICENSE for details. +#include <libctor.h> +//#include "config.h" + +namespace +{ +BuildConfigurations ctorConfigs() +{ +	return +	{ +		{ +			.name = "hello", +			.target = "hello", +			.sources = { +				"hello.cc", +			}, +			.flags = { +				.cxxflags = { +					"-std=c++20", +					"-O3", +					"-g", +					"-Wall", +					"-Werror", +				}, +			}, +			.externals = {"bar"}, +		} +	}; +} + +ExternalConfigurations ctorExtConfigs() +{ +	return +	{ +		{ +			.name = "bar", +			.flags = { +				.cxxflags = { "-D_A_", "-DFOO"}, +				.cflags = { "-D_B_" }, +				.ldflags = { "-D_C_" }, +				.asmflags = { "-D_D_" }, +			}, +			// Creates --with-foo-prefix arg to configure which will be used for +			// -L and -I flags. +			// If not specified configure will try to find them in the system paths. +		}, +//		{ +//			.name = "bar", +//			.type = TargetType::ExternalPkgConfig, +//			.min_version = "0.1", +//			.max_version = "0.9", +//			// cflags, cxxflags and ldflags deduced by pkg-config tool (or parsed +//			// directly from .pc if faster) +//		}, +	}; +} +} + +REG(ctorConfigs); +REG(ctorExtConfigs); diff --git a/test/suite/ctor_files/ctor.cc.multi b/test/suite/ctor_files/ctor.cc.multi new file mode 100644 index 0000000..9db2517 --- /dev/null +++ b/test/suite/ctor_files/ctor.cc.multi @@ -0,0 +1,64 @@ +// -*- c++ -*- +// Distributed under the BSD 2-Clause License. +// See accompanying file LICENSE for details. +#include <libctor.h> +//#include "config.h" + +#include "foobar.h" + +namespace +{ +BuildConfigurations ctorConfigs() +{ +	return +	{ +		{ +			.name = "hello", +			.target = "hello", +			.sources = { +				"hello.cc", +			}, +			.flags = { +				.cxxflags = { +					"-std=c++20", +					"-O3", +					"-g", +					"-Wall", +					"-Werror", +				}, +			}, +			.externals = {"bar"}, +		} +	}; +} + +ExternalConfigurations ctorExtConfigs() +{ +	return +	{ +		{ +			.name = "bar", +			.flags = { +				.cxxflags = { "-D_A_", "-DFOO"}, +				.cflags = { "-D_B_" }, +				.ldflags = { "-D_C_" }, +				.asmflags = { "-D_D_" }, +			}, +			// Creates --with-foo-prefix arg to configure which will be used for +			// -L and -I flags. +			// If not specified configure will try to find them in the system paths. +		}, +//		{ +//			.name = "bar", +//			.type = TargetType::ExternalPkgConfig, +//			.min_version = "0.1", +//			.max_version = "0.9", +//			// cflags, cxxflags and ldflags deduced by pkg-config tool (or parsed +//			// directly from .pc if faster) +//		}, +	}; +} +} + +REG(ctorConfigs); +REG(ctorExtConfigs); diff --git a/test/suite/foobar.h b/test/suite/foobar.h new file mode 100644 index 0000000..e6e274e --- /dev/null +++ b/test/suite/foobar.h @@ -0,0 +1,3 @@ +#pragma once + +// Nothing to see here diff --git a/test/suite/hello.cc b/test/suite/hello.cc new file mode 100644 index 0000000..06e3deb --- /dev/null +++ b/test/suite/hello.cc @@ -0,0 +1,6 @@ +#include <iostream> + +int main() +{ +	std::cout << "Hello\n"; +} diff --git a/test/suite/test.sh b/test/suite/test.sh new file mode 100755 index 0000000..c980154 --- /dev/null +++ b/test/suite/test.sh @@ -0,0 +1,104 @@ +#!/bin/bash + +function fail +{ +		echo "*** Failure at line $1" +		exit 1 +} + +function ctor +{ +		echo "*** Running: ./ctor $*" +		./ctor $* +} + +# Wipe the board +rm -Rf build +rm -f configuration.cc +rm -f ctor + +cp ctor_files/ctor.cc.base ctor.cc + +# Compile bootstrap binary +g++ -pthread -std=c++20 -L../../build -lctor -I../../src ctor.cc -o ctor || fail ${LINENO} + +# No build files should have been created yet +[ -d build ] && fail ${LINENO} + +# capture md5 sum of ctor binary before configure is called +MD5=`md5sum ctor` +ctor configure --ctor-includedir ../../src --ctor-libdir ../../build + +# ctor should be rebuilt at this point, so md5 sum should have changed +(echo $MD5 | md5sum --status -c) && fail ${LINENO} + +# configuration.cc should have been generated now +[ ! -f configuration.cc ] && fail ${LINENO} + +# Shouldn't compile anything yet - only configure +[ -f build/hello-hello_cc.o ] && fail ${LINENO} + +MD5=`md5sum ctor` + +# Run normally to build project +ctor -v + +# Compiled object should now exist +[ ! -f build/hello-hello_cc.o ] && fail ${LINENO} + +# ctor should not have been rebuilt, so md5 sum should be the same +(echo $MD5 | md5sum --status -c) || fail ${LINENO} + +MOD1=`stat -c %Y build/hello-hello_cc.o` +touch hello.cc +sleep 1.1 + +# Run normally to rebuild hello.cc +ctor -v + +# Object file should have been recompiled +MOD2=`stat -c %Y build/hello-hello_cc.o` +[[ $MOD1 == $MOD2 ]] && fail ${LINENO} + +# Replacve -DFOO with -DBAR in foo external.cxxflags +cp ctor_files/ctor.cc.bar ctor.cc + +MD5C=`md5sum configuration.cc` +MD5=`md5sum ctor` +MOD1=`stat -c %Y build/hello-hello_cc.o` +sleep 1.1 + +# Run normally to reconfigure, rebuild ctor and rebuild hello.cc +ctor -v + +MOD2=`stat -c %Y build/hello-hello_cc.o` +[[ $MOD1 == $MOD2 ]] && fail ${LINENO} +(echo $MD5C | md5sum --status -c) && fail ${LINENO} +(echo $MD5 | md5sum --status -c) && fail ${LINENO} + +cp ctor_files/ctor.cc.multi ctor.cc + +MD5C=`md5sum configuration.cc` +MD5=`md5sum ctor` +MOD1=`stat -c %Y build/hello-hello_cc.o` +sleep 1.1 + +# Run normally to reconfigure, rebuild ctor and rebuild hello.cc +ctor -v + +MOD2=`stat -c %Y build/hello-hello_cc.o` +[[ $MOD1 == $MOD2 ]] && fail ${LINENO} +(echo $MD5C | md5sum --status -c) && fail ${LINENO} +(echo $MD5 | md5sum --status -c) && fail ${LINENO} + +# now touching foobar.h, should retrigger re-configuration +touch foobar.h + +MOD1=`stat -c %Y ctor` +sleep 1.1 + +# Run normally to reconfigure, rebuild ctor and rebuild hello.cc +ctor -v + +MOD2=`stat -c %Y ctor` +[[ $MOD1 == $MOD2 ]] && fail ${LINENO} diff --git a/test/tasks_test.cc b/test/tasks_test.cc index d6515b8..2e0ffc7 100644 --- a/test/tasks_test.cc +++ b/test/tasks_test.cc @@ -2,11 +2,10 @@  #include <libctor.h>  #include <tasks.h> -#include <settings.h>  namespace  { -BuildConfigurations ctorTestConfigs1() +BuildConfigurations ctorTestConfigs1(const Settings&)  {  	return  	{ @@ -20,7 +19,7 @@ BuildConfigurations ctorTestConfigs1()  	};  } -BuildConfigurations ctorTestConfigs2() +BuildConfigurations ctorTestConfigs2(const Settings&)  {  	return  	{ @@ -37,6 +36,19 @@ BuildConfigurations ctorTestConfigs2()  REG(ctorTestConfigs1);  REG(ctorTestConfigs2); +std::size_t count(const std::set<std::shared_ptr<Task>>& tasks, +                  const std::string& name) +{ +	auto cnt{0u}; +	for(const auto& task : tasks) +	{ +		if(task->target() == name) +		{ +			cnt++; +		} +	} +	return cnt; +}  class TestTask  	: public Task @@ -44,7 +56,7 @@ class TestTask  public:  	TestTask(const std::string& name, bool dirty,  	         const std::vector<std::string>& deps = {}) -		: Task({}) +		: Task({}, {}, {})  		, task_name(name)  		, task_dirty(dirty)  		, task_deps(deps) @@ -55,6 +67,8 @@ public:  	int clean() override { return 0; }  	std::vector<std::string> depends() const override { return task_deps; }  	std::string target() const override { return task_name; } +	std::filesystem::path targetFile() const override { return {}; } +	bool derived() const override { return false; }  	bool dirtyInner() override { return task_dirty; }  private: @@ -95,34 +109,26 @@ public:  	void getTasks_test()  	{  		using namespace std::string_literals; -		Settings settings{}; +		Settings settings{ .builddir = "foo" };  		{  			auto tasks = getTasks(settings);  			uASSERT_EQUAL(6u, tasks.size()); -			auto task = tasks.begin(); -			uASSERT_EQUAL("target1-foo_cc.o"s, (*task)->target()); -			task++; -			uASSERT_EQUAL("target1-bar_c.o"s, (*task)->target()); -			task++; -			uASSERT_EQUAL("target1"s, (*task)->target()); -			task++; -			uASSERT_EQUAL("target2"s, (*task)->target()); -			task++; -			uASSERT_EQUAL("target3"s, (*task)->target()); -			task++; -			uASSERT_EQUAL("target4"s, (*task)->target()); +			// Note: count() is used here because the order of +			// std::set<std::shared_ptr<T>> is not deterministic. +			uASSERT_EQUAL(1u, count(tasks, "target1"s)); +			uASSERT_EQUAL(1u, count(tasks, "target2"s)); +			uASSERT_EQUAL(1u, count(tasks, "target3"s)); +			uASSERT_EQUAL(1u, count(tasks, "target4"s)); +			uASSERT_EQUAL(1u, count(tasks, "test/target1-foo_cc.o"s)); +			uASSERT_EQUAL(1u, count(tasks, "test/target1-bar_c.o"s));  		}  		{  			auto tasks = getTasks(settings, {"target1", "target3"});  			uASSERT_EQUAL(4u, tasks.size()); -			auto task = tasks.begin(); -			uASSERT_EQUAL("target1-foo_cc.o"s, (*task)->target()); -			task++; -			uASSERT_EQUAL("target1-bar_c.o"s, (*task)->target()); -			task++; -			uASSERT_EQUAL("target1"s, (*task)->target()); -			task++; -			uASSERT_EQUAL("target3"s, (*task)->target()); +			uASSERT_EQUAL(1u, count(tasks, "target1"s)); +			uASSERT_EQUAL(1u, count(tasks, "target3"s)); +			uASSERT_EQUAL(1u, count(tasks, "test/target1-foo_cc.o"s)); +			uASSERT_EQUAL(1u, count(tasks, "test/target1-bar_c.o"s));  		}  		{  			auto tasks = getTasks(settings, {"no-such-target"}); @@ -136,8 +142,8 @@ public:  		Settings settings{};  		{ // Zero (Empty) -			std::list<std::shared_ptr<Task>> allTasks; -			std::list<std::shared_ptr<Task>> dirtyTasks; +			std::set<std::shared_ptr<Task>> allTasks; +			std::set<std::shared_ptr<Task>> dirtyTasks;  			for(auto& task : dirtyTasks)  			{ @@ -150,10 +156,10 @@ public:  		{ // Zero (One task, no dirty)  			auto task1 = std::make_shared<TestTask>("task1", false); -			std::list<std::shared_ptr<Task>> allTasks; -			allTasks.push_back(task1); +			std::set<std::shared_ptr<Task>> allTasks; +			allTasks.insert(task1); -			std::list<std::shared_ptr<Task>> dirtyTasks; +			std::set<std::shared_ptr<Task>> dirtyTasks;  			for(auto& task : dirtyTasks)  			{ @@ -166,11 +172,11 @@ public:  		{ // One (One task, one dirty)  			auto task1 = std::make_shared<TestTask>("task1", true); -			std::list<std::shared_ptr<Task>> allTasks; -			allTasks.push_back(task1); +			std::set<std::shared_ptr<Task>> allTasks; +			allTasks.insert(task1); -			std::list<std::shared_ptr<Task>> dirtyTasks; -			dirtyTasks.push_back(task1); +			std::set<std::shared_ptr<Task>> dirtyTasks; +			dirtyTasks.insert(task1);  			for(auto& task : dirtyTasks)  			{ @@ -185,12 +191,12 @@ public:  			auto task1 = std::make_shared<TestTask>("task1", false);  			auto task2 = std::make_shared<TestTask>("task2", true); -			std::list<std::shared_ptr<Task>> allTasks; -			allTasks.push_back(task1); -			allTasks.push_back(task2); +			std::set<std::shared_ptr<Task>> allTasks; +			allTasks.insert(task1); +			allTasks.insert(task2); -			std::list<std::shared_ptr<Task>> dirtyTasks; -			dirtyTasks.push_back(task2); +			std::set<std::shared_ptr<Task>> dirtyTasks; +			dirtyTasks.insert(task2);  			for(auto& task : dirtyTasks)  			{ @@ -207,12 +213,12 @@ public:  			std::vector<std::string> deps = {"task1"};  			auto task2 = std::make_shared<TestTask>("task2", true, deps); -			std::list<std::shared_ptr<Task>> allTasks; -			allTasks.push_back(task1); -			allTasks.push_back(task2); +			std::set<std::shared_ptr<Task>> allTasks; +			allTasks.insert(task1); +			allTasks.insert(task2); -			std::list<std::shared_ptr<Task>> dirtyTasks; -			dirtyTasks.push_back(task2); +			std::set<std::shared_ptr<Task>> dirtyTasks; +			dirtyTasks.insert(task2);  			for(auto& task : dirtyTasks)  			{ @@ -229,13 +235,13 @@ public:  			std::vector<std::string> deps = {"task1"};  			auto task2 = std::make_shared<TestTask>("task2", true, deps); -			std::list<std::shared_ptr<Task>> allTasks; -			allTasks.push_back(task2); -			allTasks.push_back(task1); +			std::set<std::shared_ptr<Task>> allTasks; +			allTasks.insert(task2); +			allTasks.insert(task1); -			std::list<std::shared_ptr<Task>> dirtyTasks; -			dirtyTasks.push_back(task2); -			dirtyTasks.push_back(task1); +			std::set<std::shared_ptr<Task>> dirtyTasks; +			dirtyTasks.insert(task2); +			dirtyTasks.insert(task1);  			for(auto& task : dirtyTasks)  			{ diff --git a/test/testmain.cc b/test/testmain.cc new file mode 100644 index 0000000..db8e22c --- /dev/null +++ b/test/testmain.cc @@ -0,0 +1,36 @@ +// -*- c++ -*- +// Distributed under the BSD 2-Clause License. +// See accompanying file LICENSE for details. +#define uUNIT_MAIN +#include "uunit.h" + +#include <fstream> +#include <string> +#include <filesystem> + +namespace paths +{ +std::string argv_0; +std::filesystem::path top_srcdir; +std::filesystem::path top_builddir; +std::filesystem::path testdir; +} + +int main(int argc, char* argv[]) +{ +	(void)argc; + +	paths::argv_0 = argv[0]; + +	auto cur = std::filesystem::current_path(); +	paths::testdir = std::filesystem::path(paths::argv_0).parent_path(); +	// assuming <builddir>/test +	paths::top_builddir = paths::testdir.parent_path(); +	paths::top_srcdir = std::filesystem::relative(cur, paths::testdir); + +	std::filesystem::current_path(paths::testdir); + +	std::ofstream xmlfile; +	xmlfile.open("result_" OUTPUT ".xml"); +	return uUnit::runTests(xmlfile); +} diff --git a/test/tools_test.cc b/test/tools_test.cc new file mode 100644 index 0000000..3e9de1b --- /dev/null +++ b/test/tools_test.cc @@ -0,0 +1,212 @@ +#include <uunit.h> + +#include <initializer_list> +#include <cassert> + +#include <tools.h> + +using namespace std::string_literals; + +namespace +{ +std::ostream& operator<<(std::ostream& stream, const ToolChain& tool_chain) +{ +	switch(tool_chain) +	{ +	case ToolChain::gcc: +		stream << "ToolChain::gcc"; +		break; +	case ToolChain::clang: +		stream << "ToolChain::clang"; +		break; +	} +	return stream; +} + +std::ostream& operator<<(std::ostream& stream, const std::vector<std::string>& vs) +{ +	bool first{true}; +	stream << "{ "; +	for(const auto& v : vs) +	{ +		if(!first) +		{ +			stream << ", "; +		} +		stream << "'" << v << "'"; +		first = false; +	} +	stream << " }"; + +	return stream; +} + +std::ostream& operator<<(std::ostream& stream, opt o) +{ +	// Adding to this enum should also imply adding to the unit-tests below +	switch(o) +	{ +	case opt::output: stream << "opt::output"; break; +	case opt::debug: stream << "opt::debug"; break; +	case opt::strip: stream << "opt::strip"; break; +	case opt::warn_all: stream << "opt::warn_all"; break; +	case opt::warnings_as_errors: stream << "opt::warnings_as_errors"; break; +	case opt::generate_dep_tree: stream << "opt::generate_dep_tree"; break; +	case opt::no_link: stream << "opt::no_link"; break; +	case opt::include_path: stream << "opt::include_path"; break; +	case opt::library_path: stream << "opt::library_path"; break; +	case opt::link: stream << "opt::link"; break; +	case opt::cpp_std: stream << "opt::cpp_std"; break; +	case opt::build_shared: stream << "opt::build_shared"; break; +	case opt::threads: stream << "opt::threads"; break; +	case opt::optimization: stream << "opt::optimization"; break; +	case opt::position_independent_code: stream << "opt::position_independent_code"; break; +	case opt::position_independent_executable: stream << "opt::position_independent_executable"; break; +	case opt::custom: stream << "opt::custom"; break; +	} + +	return stream; +} + +std::ostream& operator<<(std::ostream& stream, const std::pair<opt, std::string>& vs) +{ +	stream << "{ " << vs.first << ", " << vs.second << " }"; + +	return stream; +} +} + +// Controllable getConfiguration stub: +namespace +{ +std::string conf_host_cxx{}; +std::string conf_build_cxx{}; +} +const std::string& getConfiguration(const std::string& key, +                                    const std::string& defval) +{ +	if(key == cfg::host_cxx) +	{ +		return conf_host_cxx; +	} + +	if(key == cfg::build_cxx) +	{ +		return conf_build_cxx; +	} + +	assert(false); // bad key + +	static std::string res{}; +	return res; +} + +class ToolsTest +	: public uUnit +{ +public: +	ToolsTest() +	{ +		uTEST(ToolsTest::getToolChain_test); +		uTEST(ToolsTest::getOption_toolchain_test); +		uTEST(ToolsTest::getOption_str_test); +	} + +	void getToolChain_test() +	{ +		// host +		conf_host_cxx = "/usr/bin/g++"; +		uASSERT_EQUAL(ToolChain::gcc, getToolChain(OutputSystem::Host)); + +		conf_host_cxx = "/usr/bin/g++-10"; +		uASSERT_EQUAL(ToolChain::gcc, getToolChain(OutputSystem::Host)); + +		conf_host_cxx = "/usr/bin/x86_64-pc-linux-gnu-g++-9.3.0"; +		uASSERT_EQUAL(ToolChain::gcc, getToolChain(OutputSystem::Host)); + +		conf_host_cxx = "/usr/bin/clang++"; +		uASSERT_EQUAL(ToolChain::clang, getToolChain(OutputSystem::Host)); + +		conf_host_cxx = "/usr/bin/clang++-10"; +		uASSERT_EQUAL(ToolChain::clang, getToolChain(OutputSystem::Host)); + +		conf_host_cxx = "/usr/lib/llvm/12/bin/i686-pc-linux-gnu-clang++-12"; +		uASSERT_EQUAL(ToolChain::clang, getToolChain(OutputSystem::Host)); + +		// build +		conf_build_cxx = "/usr/bin/g++"; +		uASSERT_EQUAL(ToolChain::gcc, getToolChain(OutputSystem::Build)); + +		conf_build_cxx = "/usr/bin/g++-10"; +		uASSERT_EQUAL(ToolChain::gcc, getToolChain(OutputSystem::Build)); + +		conf_build_cxx = "/usr/bin/x86_64-pc-linux-gnu-g++-9.3.0"; +		uASSERT_EQUAL(ToolChain::gcc, getToolChain(OutputSystem::Build)); + +		conf_build_cxx = "/usr/bin/clang++"; +		uASSERT_EQUAL(ToolChain::clang, getToolChain(OutputSystem::Build)); + +		conf_build_cxx = "/usr/bin/clang++-10"; +		uASSERT_EQUAL(ToolChain::clang, getToolChain(OutputSystem::Build)); + +		conf_build_cxx = "/usr/lib/llvm/12/bin/i686-pc-linux-gnu-clang++-12"; +		uASSERT_EQUAL(ToolChain::clang, getToolChain(OutputSystem::Build)); +	} + +	void getOption_toolchain_test() +	{ +		using sv = std::vector<std::string>; + +		uUnit::assert_equal(sv{ "-o", "foo" }, getOption(ToolChain::clang, opt::output, "foo"), __FILE__, __LINE__); +		uUnit::assert_equal(sv{ "-g" },        getOption(ToolChain::clang, opt::debug), __FILE__, __LINE__); +		uUnit::assert_equal(sv{ "-s" },        getOption(ToolChain::clang, opt::strip), __FILE__, __LINE__); +		uUnit::assert_equal(sv{ "-Wall" },     getOption(ToolChain::clang, opt::warn_all), __FILE__, __LINE__); +		uUnit::assert_equal(sv{ "-Werror" },   getOption(ToolChain::clang, opt::warnings_as_errors), __FILE__, __LINE__); +		uUnit::assert_equal(sv{ "-MMD" },      getOption(ToolChain::clang, opt::generate_dep_tree), __FILE__, __LINE__); +		uUnit::assert_equal(sv{ "-c" },        getOption(ToolChain::clang, opt::no_link), __FILE__, __LINE__); +		uUnit::assert_equal(sv{ "-Ifoo" },     getOption(ToolChain::clang, opt::include_path, "foo"), __FILE__, __LINE__); +		uUnit::assert_equal(sv{ "-Lfoo" },     getOption(ToolChain::clang, opt::library_path, "foo"), __FILE__, __LINE__); +		uUnit::assert_equal(sv{ "-lfoo" },     getOption(ToolChain::clang, opt::link, "foo"), __FILE__, __LINE__); +		uUnit::assert_equal(sv{ "-std=foo" },  getOption(ToolChain::clang, opt::cpp_std, "foo"), __FILE__, __LINE__); +		uUnit::assert_equal(sv{ "-shared" },   getOption(ToolChain::clang, opt::build_shared), __FILE__, __LINE__); +		uUnit::assert_equal(sv{ "-pthread" },  getOption(ToolChain::clang, opt::threads), __FILE__, __LINE__); +		uUnit::assert_equal(sv{ "-Ofoo" },     getOption(ToolChain::clang, opt::optimization, "foo"), __FILE__, __LINE__); +		uUnit::assert_equal(sv{ "-fPIC" },     getOption(ToolChain::clang, opt::position_independent_code), __FILE__, __LINE__); +		uUnit::assert_equal(sv{ "-fPIE" },     getOption(ToolChain::clang, opt::position_independent_executable), __FILE__, __LINE__); +		uUnit::assert_equal(sv{ "-foo" },      getOption(ToolChain::clang, opt::custom, "-foo"), __FILE__, __LINE__); + +		uUnit::assert_equal(sv{ "-o", "foo" }, getOption(ToolChain::gcc, opt::output, "foo"), __FILE__, __LINE__); +		uUnit::assert_equal(sv{ "-g" },        getOption(ToolChain::gcc, opt::debug), __FILE__, __LINE__); +		uUnit::assert_equal(sv{ "-s" },        getOption(ToolChain::gcc, opt::strip), __FILE__, __LINE__); +		uUnit::assert_equal(sv{ "-Wall" },     getOption(ToolChain::gcc, opt::warn_all), __FILE__, __LINE__); +		uUnit::assert_equal(sv{ "-Werror" },   getOption(ToolChain::gcc, opt::warnings_as_errors), __FILE__, __LINE__); +		uUnit::assert_equal(sv{ "-MMD" },      getOption(ToolChain::gcc, opt::generate_dep_tree), __FILE__, __LINE__); +		uUnit::assert_equal(sv{ "-c" },        getOption(ToolChain::gcc, opt::no_link), __FILE__, __LINE__); +		uUnit::assert_equal(sv{ "-Ifoo" },     getOption(ToolChain::gcc, opt::include_path, "foo"), __FILE__, __LINE__); +		uUnit::assert_equal(sv{ "-Lfoo" },     getOption(ToolChain::gcc, opt::library_path, "foo"), __FILE__, __LINE__); +		uUnit::assert_equal(sv{ "-lfoo" },     getOption(ToolChain::gcc, opt::link, "foo"), __FILE__, __LINE__); +		uUnit::assert_equal(sv{ "-std=foo" },  getOption(ToolChain::gcc, opt::cpp_std, "foo"), __FILE__, __LINE__); +		uUnit::assert_equal(sv{ "-shared" },   getOption(ToolChain::gcc, opt::build_shared), __FILE__, __LINE__); +		uUnit::assert_equal(sv{ "-pthread" },  getOption(ToolChain::gcc, opt::threads), __FILE__, __LINE__); +		uUnit::assert_equal(sv{ "-Ofoo" },     getOption(ToolChain::gcc, opt::optimization, "foo"), __FILE__, __LINE__); +		uUnit::assert_equal(sv{ "-fPIC" },     getOption(ToolChain::gcc, opt::position_independent_code), __FILE__, __LINE__); +		uUnit::assert_equal(sv{ "-fPIE" },     getOption(ToolChain::gcc, opt::position_independent_executable), __FILE__, __LINE__); +		uUnit::assert_equal(sv{ "-foo" },      getOption(ToolChain::gcc, opt::custom, "-foo"), __FILE__, __LINE__); +	} + +	void getOption_str_test() +	{ +		using p = std::pair<opt, std::string>; +		uUnit::assert_equal(p{ opt::include_path, "foo" }, getOption("-Ifoo", ToolChain::gcc), __FILE__, __LINE__); +		uUnit::assert_equal(p{ opt::library_path, "foo" }, getOption("-Lfoo", ToolChain::gcc), __FILE__, __LINE__); + +		uUnit::assert_equal(p{ opt::include_path, "foo" }, getOption("-Ifoo", ToolChain::clang), __FILE__, __LINE__); +		uUnit::assert_equal(p{ opt::library_path, "foo" }, getOption("-Lfoo", ToolChain::clang), __FILE__, __LINE__); +	} + + + +}; + +// Registers the fixture into the 'registry' +static ToolsTest test; diff --git a/test/uunit b/test/uunit -Subproject fd83de802d05a227cc00489f66ea70fccb4dda0 +Subproject bc078da645412c6b36ef59e635d6c35d11088c9 | 
