diff options
54 files changed, 2932 insertions, 1058 deletions
| diff --git a/Jenkinsfile b/Jenkinsfile index c95b0eb..290f412 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -1,56 +1,48 @@  pipeline { -	agent { label 'c++20' } - +	agent any  	stages { -		stage('Clean') { +		//////////////////////////////////////////////////// +		stage('Linux gcc') { +			agent { label 'linux && gcc && c++20' }  			steps {  				echo 'Cleaning workspace ...' -				sh 'rm -Rf build*' -			} -		} -		stage('Build-gcc') { -			steps { +				sh 'git clean -d -x -f'  				echo 'Building (gcc) ...' -				sh 'BUILDDIR=build-gcc CXX=g++ ./bootstrap.sh' -			} -		} -		stage('Test-gcc') { -			steps { +				sh 'CXX=g++ ./bootstrap.sh'  				echo 'Testing (gcc) ...'  				sh './ctor check' -			} -		} -		stage('Test-suite-gcc') { -			steps {  				echo 'Testing suite (gcc) ...' -				sh '(cd test/suite; CTORDIR=../../build-gcc CXX=g++ ./test.sh)' +				sh '(cd test/suite; CTORDIR=../../build CXX=g++ ./test.sh)'  			} -		} -		stage('Build-clang') { -			steps { -				echo 'Building (clang) ...' -				sh 'BUILDDIR=build-clang CXX=clang++ ./bootstrap.sh' +			post { +				always { +					xunit(thresholds: [ skipped(failureThreshold: '0'), +					                    failed(failureThreshold: '0') ], +					      tools: [ CppUnit(pattern: 'build/test/*.xml') ]) +				}  			}  		} -		stage('Test-clang') { +		//////////////////////////////////////////////////// +		stage('Linux clang') { +			agent { label 'linux && clang && c++20' }  			steps { +				echo 'Cleaning workspace ...' +				sh 'git clean -d -x -f' +				echo 'Building (clang) ...' +				sh 'CXX=clang++ ./bootstrap.sh'  				echo 'Testing (clang) ...'  				sh './ctor check' -			} -		} -		stage('Test-suite-clang') { -			steps {  				echo 'Testing suite (clang) ...' -				sh '(cd test/suite; CTORDIR=../../build-clang CXX=clang++ ./test.sh)' +				sh '(cd test/suite; CTORDIR=../../build CXX=clang++ ./test.sh)' +			} +			post { +				always { +					xunit(thresholds: [ skipped(failureThreshold: '0'), +					                    failed(failureThreshold: '0') ], +					      tools: [ CppUnit(pattern: 'build/test/*.xml') ]) +				}  			}  		} -	} - -	post { -		always { -			xunit(thresholds: [ skipped(failureThreshold: '0'), -			                    failed(failureThreshold: '0') ], -			      tools: [ CppUnit(pattern: 'build-*/test/*.xml') ]) -		} +		////////////////////////////////////////////////////  	}  } diff --git a/bootstrap.sh b/bootstrap.sh index 76503bb..d10f54b 100755 --- a/bootstrap.sh +++ b/bootstrap.sh @@ -1,11 +1,12 @@  #!/bin/sh +set -e +  : ${CXX:=g++}  : ${BUILDDIR:=build} -CXX=$(which $CXX)  echo "Bootstrapping..." -$CXX -std=c++20 -Wall -O3 -Isrc -pthread src/bootstrap.cc ctor.cc test/ctor.cc -o ctor && \ -./ctor && \ -$CXX -std=c++20 -Wall -O3 -Isrc -pthread ctor.cc test/ctor.cc -L$BUILDDIR -lctor -o ctor && \ -./ctor configure --ctor-includedir=src --ctor-libdir=$BUILDDIR --build-dir=$BUILDDIR&& \ +$CXX -std=c++20 -Wall -O3 -Isrc -pthread src/bootstrap.cc ctor.cc -o ctor +./ctor +$CXX -std=c++20 -Wall -O3 -Isrc -pthread ctor.cc test/ctor.cc -L$BUILDDIR -lctor -o ctor +./ctor configure --ctor-includedir=src --ctor-libdir=$BUILDDIR --build-dir=$BUILDDIR  echo "Done. Now run ./ctor to (re)build." @@ -10,14 +10,16 @@ ctor::build_configurations ctorConfigs(const ctor::settings& settings)  	return  	{  		{ -			.type = ctor::target_type::static_library, +			.system = ctor::output_system::build,  			.target = "libctor.a",  			.sources = {  				"src/build.cc",  				"src/configure.cc", +				"src/deps.cc",  				"src/execute.cc",  				"src/externals_manual.cc",  				"src/libctor.cc", +				"src/pointerlist.cc",  				"src/rebuild.cc",  				"src/task.cc",  				"src/task_ar.cc", @@ -37,6 +39,10 @@ ctor::build_configurations ctorConfigs(const ctor::settings& settings)  					"-g",  					"-Wall",  					"-Werror", +					"-Wextra", +					"-Wshadow", +					"-Wconversion", +//					"-Wnrvo",  					"-Isrc",  				},  			}, diff --git a/src/bootstrap.cc b/src/bootstrap.cc index 0604527..be1b5ed 100644 --- a/src/bootstrap.cc +++ b/src/bootstrap.cc @@ -4,6 +4,7 @@  #include <iostream>  #include <array>  #include <cstdlib> +#include <span>  #define BOOTSTRAP @@ -18,9 +19,10 @@  #include "tasks.cc"  #include "build.cc"  #include "tools.cc" +#include "pointerlist.cc" -std::filesystem::path configurationFile("configuration.cc"); -std::filesystem::path configHeaderFile("config.h"); +const std::filesystem::path configurationFile("configuration.cc"); +const std::filesystem::path configHeaderFile("config.h");  const ctor::configuration& ctor::get_configuration()  { @@ -28,7 +30,7 @@ const ctor::configuration& ctor::get_configuration()  	static bool initialised{false};  	if(!initialised)  	{ -		cfg.host_toolchain = getToolChain(cfg.get(ctor::cfg::host_cxx, "/usr/bin/g++")); +		cfg.build_toolchain = getToolChain(cfg.get(ctor::cfg::build_cxx, "/usr/bin/g++"));  		initialised = true;  	} @@ -40,28 +42,58 @@ bool ctor::configuration::has(const std::string& key) const  	return false;  } -const std::string& ctor::configuration::get(const std::string& key, const std::string& default_value) const +std::string ctor::configuration::get(const std::string& key, const std::string& default_value) const  { -	if(key == ctor::cfg::host_cxx && std::getenv("CXX")) +	static auto paths = get_paths(); +	auto cxx_env = std::getenv("CXX"); +	if(key == ctor::cfg::build_cxx && cxx_env)  	{ -		static std::string s = std::getenv("CXX"); -		return s; +		static auto cxx_prog = locate(cxx_env, paths); +		return cxx_prog;  	} -	if(key == ctor::cfg::builddir && std::getenv("BUILDDIR")) +	auto cc_env = std::getenv("CC"); +	if(key == ctor::cfg::build_cc && cc_env)  	{ -		static std::string s = std::getenv("BUILDDIR"); -		return s; +		static auto cc_prog = locate(cc_env, paths); +		return cc_prog; +	} + +	auto ld_env = std::getenv("LD"); +	if(key == ctor::cfg::build_ld && ld_env) +	{ +		static auto ld_prog = locate(ld_env, paths); +		return ld_prog; +	} + +	auto ar_env = std::getenv("AR"); +	if(key == ctor::cfg::build_ar && ar_env) +	{ +		static auto ar_prog = locate(ar_env, paths); +		return ar_prog; +	} + +	auto builddir_env = std::getenv("BUILDDIR"); +	if(key == ctor::cfg::builddir && builddir_env) +	{ +		return builddir_env;  	}  	return default_value;  } +std::vector<std::string> readDeps(const std::string& depFile, +                                  ctor::toolchain toolchain) +{ +	return {}; +} +  int main(int argc, char* argv[])  { -	if(argc > 1) +	auto args = std::span(argv, static_cast<std::size_t>(argc)); +	if(args.size() > 1)  	{ -		std::cerr << "This is a minimal bootstrap version of " << argv[0] << +		std::cerr << "This is a minimal bootstrap version of " << args[0] <<  			" which doesn't support any arguments\n";  		return 1;  	} diff --git a/src/build.cc b/src/build.cc index ad30719..a31f6a5 100644 --- a/src/build.cc +++ b/src/build.cc @@ -4,12 +4,11 @@  #include "build.h"  #include <future> -#include <vector>  #include <iostream>  #include <chrono> -#include <set>  #include <thread>  #include <list> +#include <algorithm>  #include "ctor.h" @@ -17,8 +16,8 @@ using namespace std::chrono_literals;  int build(const ctor::settings& settings,            const std::string& name, -          const std::set<std::shared_ptr<Task>>& tasks, -          const std::set<std::shared_ptr<Task>>& all_tasks, +          const std::vector<std::shared_ptr<Task>>& tasks, +          const std::vector<std::shared_ptr<Task>>& all_tasks,            bool dryrun)  {  	if(settings.verbose > 1) @@ -26,19 +25,20 @@ int build(const ctor::settings& settings,  		std::cout << "Building '" << name << "'\n";  	} -	std::set<std::shared_ptr<Task>> dirtyTasks; +	std::vector<std::shared_ptr<Task>> dirtyTasks;  	for(auto task : tasks)  	{ -		if(task->dirty()) +		if(task->dirty() && +		   std::find(dirtyTasks.begin(), dirtyTasks.end(), task) == dirtyTasks.end())  		{ -			dirtyTasks.insert(task); +			dirtyTasks.push_back(task);  		}  	}  	// Dry-run returns number of dirty tasks but otherwise does nothing.  	if(dryrun)  	{ -		return dirtyTasks.size(); +		return static_cast<int>(dirtyTasks.size());  	}  	if(dirtyTasks.empty()) @@ -114,17 +114,15 @@ int build(const ctor::settings& settings,  		}  	} -	for(auto process = processes.begin(); -	    process != processes.end(); -	    ++process) +	for(auto& process : processes)  	{ -		if(process->valid() == false) +		if(process.valid() == false)  		{  			continue;  		} -		process->wait(); -		auto ret = process->get(); -		if(ret != 0) +		process.wait(); +		auto ret = process.get(); +		if (ret != 0)  		{  			return ret;  		} @@ -133,30 +131,50 @@ int build(const ctor::settings& settings,  	return 0;  } -namespace +std::vector<std::shared_ptr<Task>> getDepTasks(std::shared_ptr<Task> task, +                                               std::vector<std::shared_ptr<Task>> trace)  { -std::set<std::shared_ptr<Task>> getDepTasks(std::shared_ptr<Task> task) -{ -	std::set<std::shared_ptr<Task>> tasks; -	tasks.insert(task); +	std::vector<std::shared_ptr<Task>> tasks; +	tasks.push_back(task); +	trace.push_back(task);  	auto deps = task->getDependsTasks();  	for(const auto& dep : deps)  	{ -		auto depSet = getDepTasks(dep); -		for(const auto& dep : depSet) +		if(std::find(trace.begin(), trace.end(), dep) != trace.end()) +		{ +			trace.push_back(dep); +			std::cerr << "Error: Cyclic dependency detected: "; +			bool first{true}; +			for(auto t : trace) +			{ +				if(!first) +				{ +					std::cerr << " -> "; +				} + +				first = false; +				std::cerr << t->target(); +			} +			std::cerr << '\n'; +			throw 1; +		} +		auto depSet = getDepTasks(dep, trace); +		for(const auto& dep_inner : depSet)  		{ -			tasks.insert(dep); +			if(std::find(tasks.begin(), tasks.end(), dep_inner) == tasks.end()) +			{ +				tasks.push_back(dep_inner); +			}  		}  	}  	return tasks;  } -}  int build(const ctor::settings& settings,            const std::string& name, -          const std::set<std::shared_ptr<Task>>& all_tasks, +          const std::vector<std::shared_ptr<Task>>& all_tasks,            bool dryrun)  {  	bool task_found{false}; @@ -166,17 +184,27 @@ int build(const ctor::settings& settings,  		{  			task_found = true; -			auto depSet = getDepTasks(task); -			std::set<std::shared_ptr<Task>> ts; -			for(const auto& task : depSet) +			try  			{ -				ts.insert(task); -			} +				auto depSet = getDepTasks(task); +				std::vector<std::shared_ptr<Task>> ts; +				for(const auto& task_inner : depSet) +				{ +					if(std::find(ts.begin(), ts.end(), task_inner) == ts.end()) +					{ +						ts.push_back(task_inner); +					} +				} -			auto ret = build(settings, name, ts, all_tasks, dryrun); -			if(ret != 0) +				auto ret = build(settings, name, ts, all_tasks, dryrun); +				if(ret != 0) +				{ +					return ret; +				} +			} +			catch(...)  			{ -				return ret; +				return 1; // cycle detected  			}  			break; @@ -195,11 +223,11 @@ int build(const ctor::settings& settings,  int build(const ctor::settings& settings,            const std::string& name,            const std::vector<Target>& targets, -          const std::set<std::shared_ptr<Task>>& all_tasks, +          const std::vector<std::shared_ptr<Task>>& all_tasks,            bool dryrun)  {  	bool task_found{false}; -	std::set<std::shared_ptr<Task>> ts; +	std::vector<std::shared_ptr<Task>> ts;  	for(const auto& target : targets)  	{ @@ -210,10 +238,20 @@ int build(const ctor::settings& settings,  			{  				task_found = true; -				auto depSet = getDepTasks(task); -				for(const auto& task : depSet) +				try +				{ +					auto depSet = getDepTasks(task); +					for(const auto& task_inner : depSet) +					{ +						if(std::find(ts.begin(), ts.end(), task_inner) == ts.end()) +						{ +							ts.push_back(task_inner); +						} +					} +				} +				catch(...)  				{ -					ts.insert(task); +					return 1; // cycle detected  				}  			}  		} diff --git a/src/build.h b/src/build.h index d74642c..7296f76 100644 --- a/src/build.h +++ b/src/build.h @@ -4,7 +4,7 @@  #pragma once  #include <string> -#include <set> +#include <vector>  #include <memory>  #include "task.h" @@ -17,19 +17,24 @@ struct settings;  //! Dry-run returns number of dirty tasks but otherwise does nothing.  int build(const ctor::settings& settings,            const std::string& name, -          const std::set<std::shared_ptr<Task>>& tasks, -          const std::set<std::shared_ptr<Task>>& all_tasks, +          const std::vector<std::shared_ptr<Task>>& tasks, +          const std::vector<std::shared_ptr<Task>>& all_tasks,            bool dryrun = false);  //! Dry-run returns number of dirty tasks but otherwise does nothing.  int build(const ctor::settings& settings,            const std::string& name, -          const std::set<std::shared_ptr<Task>>& all_tasks, +          const std::vector<std::shared_ptr<Task>>& all_tasks,            bool dryrun = false);  //! Dry-run returns number of dirty tasks but otherwise does nothing.  int build(const ctor::settings& settings,            const std::string& name,            const std::vector<Target>& targets, -          const std::set<std::shared_ptr<Task>>& all_tasks, +          const std::vector<std::shared_ptr<Task>>& all_tasks,            bool dryrun = false); + +// Recursively build vector of dependency tasks from source task. +// Throws if a cycle is detected. +std::vector<std::shared_ptr<Task>> getDepTasks(std::shared_ptr<Task> task, +                                               std::vector<std::shared_ptr<Task>> trace = {}); diff --git a/src/configure.cc b/src/configure.cc index 8e88092..7a68f03 100644 --- a/src/configure.cc +++ b/src/configure.cc @@ -7,6 +7,7 @@  #include <filesystem>  #include <fstream>  #include <optional> +#include <span>  #include <getoptpp/getoptpp.hpp> @@ -16,9 +17,10 @@  #include "rebuild.h"  #include "externals.h"  #include "tools.h" +#include "util.h" -std::filesystem::path configurationFile("configuration.cc"); -std::filesystem::path configHeaderFile("config.h"); +const std::filesystem::path configurationFile("configuration.cc"); +const std::filesystem::path configHeaderFile("config.h");  std::map<std::string, std::string> external_includedir;  std::map<std::string, std::string> external_libdir; @@ -29,7 +31,7 @@ const ctor::configuration& __attribute__((weak)) ctor::get_configuration()  	static bool initialised{false};  	if(!initialised)  	{ -		cfg.host_toolchain = getToolChain(cfg.get(ctor::cfg::host_cxx, "g++")); +		cfg.build_toolchain = getToolChain(cfg.get(ctor::cfg::build_cxx, "/usr/bin/g++"));  		initialised = true;  	}  	return cfg; @@ -67,7 +69,7 @@ bool ctor::configuration::has(const std::string& key) const  	return tools.find(key) != tools.end();  } -const std::string& ctor::configuration::get(const std::string& key, const std::string& default_value) const +std::string ctor::configuration::get(const std::string& key, const std::string& default_value) const  {  	if(key == ctor::cfg::ctor_includedir && ctor::includedir)  	{ @@ -97,57 +99,6 @@ const std::string& ctor::configuration::get(const std::string& key, const std::s  	return default_value;  } -std::string locate(const std::string& arch, const std::string& app) -{ -	std::string path_env = std::getenv("PATH"); -	//std::cout << path_env << "\n"; - -	std::string program = app; -	if(!arch.empty()) -	{ -		program = arch + "-" + app; -	} -	std::cout << "Looking for: " << program << "\n"; -	std::vector<std::string> paths; - -	{ -		std::stringstream ss(path_env); -		std::string path; -		while (std::getline(ss, path, ':')) -		{ -			paths.push_back(path); -		} -	} -	for(const auto& path_str : paths) -	{ -		std::filesystem::path path(path_str); -		auto prog_path = path / program; -		if(std::filesystem::exists(prog_path)) -		{ -			std::cout << "Found file " << app << " in path: " << path << "\n"; -			auto perms = std::filesystem::status(prog_path).permissions(); -			if((perms & std::filesystem::perms::owner_exec) != std::filesystem::perms::none) -			{ -				//std::cout << " - executable by owner\n"; -			} -			if((perms & std::filesystem::perms::group_exec) != std::filesystem::perms::none) -			{ -				//std::cout << " - executable by group\n"; -			} -			if((perms & std::filesystem::perms::others_exec) != std::filesystem::perms::none) -			{ -				//std::cout << " - executable by others\n"; -			} - -			return prog_path.string(); -		} -	} - -	std::cerr << "Could not locate " << app << " for the " << arch << " architecture\n"; -	exit(1); -	return {}; -} -  class Args  	: public std::vector<char*>  { @@ -192,6 +143,26 @@ std::ostream& operator<<(std::ostream& stream, const ctor::toolchain& toolchain)  	return stream;  } +std::ostream& operator<<(std::ostream& stream, const ctor::arch& arch) +{ +	switch(arch) +	{ +	case ctor::arch::unix: +		stream << "ctor::arch::unix"; +		break; +	case ctor::arch::apple: +		stream << "ctor::arch::apple"; +		break; +	case ctor::arch::windows: +		stream << "ctor::arch::windows"; +		break; +	case ctor::arch::unknown: +		stream << "ctor::arch::unknown"; +		break; +	} +	return stream; +} +  std::ostream& operator<<(std::ostream& ostr, const ctor::c_flag& flag)  {  	for(const auto& s : to_strings(ctor::toolchain::any, flag)) @@ -233,6 +204,7 @@ std::ostream& operator<<(std::ostream& ostr, const ctor::asm_flag& flag)  template<class> inline constexpr bool always_false_v = false;  int regenerateCache(ctor::settings& settings, +                    const std::string& name,                      const std::vector<std::string>& args,                      const std::map<std::string, std::string>& env)  { @@ -241,9 +213,9 @@ int regenerateCache(ctor::settings& settings,  	dg::Options opt;  	int key{128}; -	std::string build_arch; +	std::string build_arch_prefix;  	std::string build_path; -	std::string host_arch; +	std::string host_arch_prefix;  	std::string host_path;  	std::string cc_prog = "gcc";  	std::string cxx_prog = "g++"; @@ -300,7 +272,7 @@ int regenerateCache(ctor::settings& settings,  	opt.add("build", required_argument, key++,  	        "Configure for building on specified architecture.",  	        [&]() { -		        build_arch = optarg; +		        build_arch_prefix = optarg;  		        return 0;  	        }); @@ -314,7 +286,7 @@ int regenerateCache(ctor::settings& settings,  	opt.add("host", required_argument, key++,  	        "Cross-compile to build programs to run on specified architecture.",  	        [&]() { -		        host_arch = optarg; +		        host_arch_prefix = optarg;  		        return 0;  	        }); @@ -350,19 +322,19 @@ int regenerateCache(ctor::settings& settings,  	}  	auto add_path_args = -		[&](const std::string& name) +		[&](const std::string& arg_name)  		{ -			opt.add(name + "-includedir", required_argument, key++, -			        "Set path to " + name + " header file.", +			opt.add(arg_name + "-includedir", required_argument, key++, +			        "Set path to " + arg_name + " header file.",  			        [&]() { -				        external_includedir[name] = optarg; +				        external_includedir[arg_name] = optarg;  				        return 0;  			        }); -			opt.add(name + "-libdir", required_argument, key++, -			        "Set path to " + name + " libraries.", +			opt.add(arg_name + "-libdir", required_argument, key++, +			        "Set path to " + arg_name + " libraries.",  			        [&]() { -				        external_libdir[name] = optarg; +				        external_libdir[arg_name] = optarg;  				        return 0;  			        });  		}; @@ -386,46 +358,117 @@ int regenerateCache(ctor::settings& settings,  	opt.add("help", no_argument, 'h',  	        "Print this help text.", -	        [&]() { -		        std::cout << "configure usage stuff\n"; +	        [&]() -> int { +		        std::cout << "Configure how to build with " << name << "\n"; +		        std::cout << "Usage: " << name << " configure [options]\n\n"; +		        std::cout << "Options:\n";  		        opt.help();  		        exit(0); -		        return 0;  	        }); -	opt.process(vargs.size(), vargs.data()); +	opt.process(static_cast<int>(vargs.size()), vargs.data()); -	if(host_arch.empty()) +	if(host_arch_prefix.empty())  	{ -		host_arch = build_arch; +		host_arch_prefix = build_arch_prefix;  	}  	auto tasks = getTasks(settings, {}, false); -/* -	bool needs_cpp{false}; -	bool needs_c{false}; -	bool needs_ar{false}; -	bool needs_asm{false}; + +	bool needs_build{true}; // we always need to compile ctor itself +	bool needs_build_c{false}; +	bool needs_build_cxx{true}; // we always need to compile ctor itself +	bool needs_build_ld{true}; // we always need to compile ctor itself +	bool needs_build_ar{false}; +	bool needs_build_asm{false}; + +	bool needs_host_c{false}; +	bool needs_host{false}; +	bool needs_host_cxx{false}; +	bool needs_host_ld{false}; +	bool needs_host_ar{false}; +	bool needs_host_asm{false}; +  	for(const auto& task :tasks)  	{ -		switch(task->sourceLanguage()) +		switch(task->outputSystem())  		{ -		case Language::Auto: -			std::cerr << "TargetLanguage not deduced!\n"; -			exit(1); -			break; -		case Language::C: -			needs_cpp = false; -			break; -		case Language::Cpp: -			needs_c = true; +		case ctor::output_system::build: +			needs_build = true; +			switch(task->targetType()) +			{ +			case ctor::target_type::executable: +			case ctor::target_type::unit_test: +			case ctor::target_type::dynamic_library: +				needs_build_ld = true; +				break; +			case ctor::target_type::static_library: +			case ctor::target_type::unit_test_library: +				needs_build_ar = true; +				break; +			case ctor::target_type::object: +				switch(task->sourceLanguage()) +				{ +				case ctor::language::automatic: +					std::cerr << "TargetLanguage not deduced!\n"; +					exit(1); +					break; +				case ctor::language::c: +					needs_build_c = true; +					break; +				case ctor::language::cpp: +					needs_build_cxx = true; +					break; +				case ctor::language::assembler: +					needs_build_asm = true; +					break; +				} +				break; +			case ctor::target_type::function: +			case ctor::target_type::automatic: +			case ctor::target_type::unknown: +				break; +			}  			break; -		case Language::Asm: -			needs_asm = true; +		case ctor::output_system::host: +			needs_host = true; +			switch(task->targetType()) +			{ +			case ctor::target_type::executable: +			case ctor::target_type::unit_test: +			case ctor::target_type::dynamic_library: +				needs_host_ld = true; +				break; +			case ctor::target_type::static_library: +			case ctor::target_type::unit_test_library: +				needs_host_ar = true; +				break; +			case ctor::target_type::object: +				switch(task->sourceLanguage()) +				{ +				case ctor::language::automatic: +					std::cerr << "TargetLanguage not deduced!\n"; +					exit(1); +					break; +				case ctor::language::c: +					needs_host_c = true; +					break; +				case ctor::language::cpp: +					needs_host_cxx = true; +					break; +				case ctor::language::assembler: +					needs_host_asm = true; +					break; +				} +				break; +			case ctor::target_type::function: +			case ctor::target_type::automatic: +			case ctor::target_type::unknown: +				break; +			}  			break;  		}  	} -*/  	auto cc_env = env.find("CC");  	if(cc_env != env.end()) @@ -451,21 +494,149 @@ int regenerateCache(ctor::settings& settings,  		ld_prog = ld_env->second;  	} -	std::string host_cc = locate(host_arch, cc_prog); -	std::string host_cxx = locate(host_arch, cxx_prog); -	std::string host_ar = locate(host_arch, ar_prog); -	std::string host_ld = locate(host_arch, ld_prog); -	std::string build_cc = locate(build_arch, cc_prog); -	std::string build_cxx = locate(build_arch, cxx_prog); -	std::string build_ar = locate(build_arch, ar_prog); -	std::string build_ld = locate(build_arch, ld_prog); +	auto paths = get_paths(); + +	auto path_env = env.find("PATH"); +	if(path_env != env.end()) +	{ +		paths = get_paths(path_env->second); +	} + +	std::string host_cc; +	std::string host_cxx; +	std::string host_ld; +	std::string host_ar; +	ctor::toolchain host_toolchain{ctor::toolchain::none}; +	ctor::arch host_arch{ctor::arch::unknown}; +	if(needs_host) +	{ +		// Host detection +		if(needs_host_c) +		{ +			host_cc = locate(cc_prog, paths, host_arch_prefix); +			if(host_cc.empty()) +			{ +				std::cerr << "Could not locate host_cc prog" << std::endl; +				return 1; +			} +		} + +		if(needs_host_cxx) +		{ +			host_cxx = locate(cxx_prog, paths, host_arch_prefix); +			if(host_cxx.empty()) +			{ +				std::cerr << "Could not locate host_cxx prog" << std::endl; +				return 1; +			} +		} + +		if(needs_host_ar) +		{ +			host_ar = locate(ar_prog, paths, host_arch_prefix); +			if(host_ar.empty()) +			{ +				std::cerr << "Could not locate host_ar prog" << std::endl; +				return 1; +			} +		} + +		if(needs_host_ld) +		{ +			host_ld = locate(ld_prog, paths, host_arch_prefix); +			if(host_ld.empty()) +			{ +				std::cerr << "Could not locate host_ld prog" << std::endl; +				return 1; +			} +		} + +		if(needs_host_asm) +		{ +			// TODO +		} + +		host_toolchain = getToolChain(host_cxx); +		auto host_arch_str = get_arch(ctor::output_system::host); +		host_arch = get_arch(ctor::output_system::host, host_arch_str); + +		std::cout << "** Host architecture '" << host_arch_str << "': " << host_arch << std::endl; -	if(!host_cxx.empty()) +		if(host_arch == ctor::arch::unknown) +		{ +			std::cerr << "Could not detect host architecture" << std::endl; +			return 1; +		} +	} + +	std::string build_cc; +	std::string build_cxx; +	std::string build_ld; +	std::string build_ar; +	ctor::toolchain build_toolchain{ctor::toolchain::none}; +	ctor::arch build_arch{ctor::arch::unknown}; +	if(needs_build)  	{ -		// This is needed for bootstrapping (when running configure for the first time) -		ctor::conf_values[ctor::cfg::host_cxx] = host_cxx; +		// Build detection +		if(needs_build_c) +		{ +			build_cc = locate(cc_prog, paths, build_arch_prefix); +			if(build_cc.empty()) +			{ +				std::cerr << "Could not locate build_cc prog" << std::endl; +				return 1; +			} +		} + +		if(needs_build_cxx) +		{ +			build_cxx = locate(cxx_prog, paths, build_arch_prefix); +			if(build_cxx.empty()) +			{ +				std::cerr << "Could not locate build_cxx prog" << std::endl; +				return 1; +			} +		} + +		if(needs_build_ar) +		{ +			build_ar = locate(ar_prog, paths, build_arch_prefix); +			if(build_ar.empty()) +			{ +				std::cerr << "Could not locate build_ar prog" << std::endl; +				return 1; +			} +		} + +		if(needs_build_ld) +		{ +			build_ld = locate(ld_prog, paths, build_arch_prefix); +			if(build_ld.empty()) +			{ +				std::cerr << "Could not locate build_ld prog" << std::endl; +				return 1; +			} +		} + +		if(needs_build_asm) +		{ +			// TODO +		} + +		build_toolchain = getToolChain(build_cxx); +		auto build_arch_str = get_arch(ctor::output_system::build); +		build_arch = get_arch(ctor::output_system::build, build_arch_str); + +		std::cout << "** Build architecture '" << build_arch_str << "': " << build_arch << std::endl; + +		if(build_arch == ctor::arch::unknown) +		{ +			std::cerr << "Could not detect build architecture" << std::endl; +			return 1; +		}  	} +  	// Store current values for execution in this execution context.  	if(!ctor_includedir.empty())  	{ @@ -490,43 +661,81 @@ int regenerateCache(ctor::settings& settings,  		istr << "{\n";  		istr << "	static ctor::configuration cfg =\n";  		istr << "	{\n"; -		istr << "		.host_toolchain = " << getToolChain(host_cxx) << ",\n"; -		istr << "		.build_toolchain = " << getToolChain(build_cxx) << ",\n"; +		if(needs_host) +		{ +			istr << "		.host_toolchain = " << host_toolchain << ",\n"; +			istr << "		.host_arch = " << host_arch << ",\n"; +		} +		if(needs_build) +		{ +			istr << "		.build_toolchain = " << build_toolchain << ",\n"; +			istr << "		.build_arch = " << build_arch << ",\n"; +		}  		istr << "		.args = {";  		for(const auto& arg : args)  		{ -			istr << "\"" << arg << "\","; +			istr << "\"" << esc(arg) << "\",";  		}  		istr << "},\n"; -		istr << "		.env = {"; +		istr << "		.env = {\n";  		for(const auto& e : env)  		{ -			istr << "{\"" << e.first << "\", \"" << e.second << "\"}, "; +			istr << "			{\"" << esc(e.first) << "\", \"" << esc(e.second) << "\"},\n";  		} -		istr << "},\n"; +		istr << "		},\n";  		istr << "		.tools = {\n";  		if(!builddir.empty())  		{ -			istr << "			{ \"" << ctor::cfg::builddir << "\", \"" << builddir << "\" },\n"; +			istr << "			{ \"" << ctor::cfg::builddir << "\", \"" << esc(builddir) << "\" },\n";  			ctor::builddir = builddir;  		} -		istr << "			{ \"" << ctor::cfg::host_cc << "\", \"" << host_cc << "\" },\n"; -		istr << "			{ \"" << ctor::cfg::host_cxx << "\", \"" << host_cxx << "\" },\n"; -		istr << "			{ \"" << ctor::cfg::host_ar << "\", \"" << host_ar << "\" },\n"; -		istr << "			{ \"" << ctor::cfg::host_ld << "\", \"" << host_ld << "\" },\n"; -		istr << "			{ \"" << ctor::cfg::build_cc << "\", \"" << build_cc << "\" },\n"; -		istr << "			{ \"" << ctor::cfg::build_cxx << "\", \"" << build_cxx << "\" },\n"; -		istr << "			{ \"" << ctor::cfg::build_ar << "\", \"" << build_ar << "\" },\n"; -		istr << "			{ \"" << ctor::cfg::build_ld << "\", \"" << build_ld << "\" },\n"; +		if(needs_host) +		{ +			if(needs_host_c) +			{ +				istr << "			{ \"" << ctor::cfg::host_cc << "\", \"" << esc(host_cc) << "\" },\n"; +			} +			if(needs_host_cxx) +			{ +				istr << "			{ \"" << ctor::cfg::host_cxx << "\", \"" << esc(host_cxx) << "\" },\n"; +			} +			if(needs_host_ar) +			{ +				istr << "			{ \"" << ctor::cfg::host_ar << "\", \"" << esc(host_ar) << "\" },\n"; +			} +			if(needs_host_ld) +			{ +				istr << "			{ \"" << ctor::cfg::host_ld << "\", \"" << esc(host_ld) << "\" },\n"; +			} +		} +		if(needs_build) +		{ +			if(needs_build_c) +			{ +				istr << "			{ \"" << ctor::cfg::build_cc << "\", \"" << esc(build_cc) << "\" },\n"; +			} +			if(needs_build_cxx) +			{ +				istr << "			{ \"" << ctor::cfg::build_cxx << "\", \"" << esc(build_cxx) << "\" },\n"; +			} +			if(needs_build_ar) +			{ +				istr << "			{ \"" << ctor::cfg::build_ar << "\", \"" << esc(build_ar) << "\" },\n"; +			} +			if(needs_build_ld) +			{ +				istr << "			{ \"" << ctor::cfg::build_ld << "\", \"" << esc(build_ld) << "\" },\n"; +			} +		}  		if(!ctor_includedir.empty())  		{ -			istr << "			{ \"" << ctor::cfg::ctor_includedir << "\", \"" << ctor_includedir << "\" },\n"; +			istr << "			{ \"" << ctor::cfg::ctor_includedir << "\", \"" << esc(ctor_includedir) << "\" },\n";  			ctor::includedir = ctor_includedir;  		}  		if(!ctor_libdir.empty())  		{ -			istr << "			{ \"" << ctor::cfg::ctor_libdir << "\", \"" << ctor_libdir << "\" },\n"; +			istr << "			{ \"" << ctor::cfg::ctor_libdir << "\", \"" << esc(ctor_libdir) << "\" },\n";  			ctor::libdir = ctor_libdir;  		} @@ -535,7 +744,7 @@ int regenerateCache(ctor::settings& settings,  		for(const auto& ext : externalConfigs)  		{ -			istr << "			{ \"" << ext.name << "\", {\n"; +			istr << "			{ \"" << esc(ext.name) << "\", {\n";  			ctor::flags resolved_flags;  			if(std::holds_alternative<ctor::external_manual>(ext.external))  			{ @@ -612,12 +821,14 @@ int regenerateCache(ctor::settings& settings,  int configure(const ctor::settings& global_settings, int argc, char* argv[])  { +	auto args_span = std::span(argv, static_cast<std::size_t>(argc)); +  	ctor::settings settings{global_settings};  	std::vector<std::string> args; -	for(int i = 2; i < argc; ++i) // skip command and the first 'configure' arg +	for(std::size_t i = 2; i < args_span.size(); ++i) // skip command and the first 'configure' arg  	{ -		args.push_back(argv[i]); +		args.emplace_back(args_span[i]);  	}  	std::map<std::string, std::string> env; @@ -645,7 +856,13 @@ int configure(const ctor::settings& global_settings, int argc, char* argv[])  		env["LD"] = ld_env;  	} -	auto ret = regenerateCache(settings, args, env); +	auto path_env = getenv("PATH"); +	if(path_env) +	{ +		env["PATH"] = path_env; +	} + +	auto ret = regenerateCache(settings, args_span[0], args, env);  	if(ret != 0)  	{  		return ret; @@ -658,19 +875,21 @@ int configure(const ctor::settings& global_settings, int argc, char* argv[])  int reconfigure(const ctor::settings& global_settings, int argc, char* argv[])  { +	auto args_span = std::span(argv, static_cast<std::size_t>(argc)); +  	ctor::settings settings{global_settings};  	bool no_rerun{false};  	std::vector<std::string> args; -	for(int i = 2; i < argc; ++i) // skip executable name and 'reconfigure' arg +	for(std::size_t i = 2; i < args_span.size(); ++i) // skip executable name and 'reconfigure' arg  	{ -		if(i == 2 && std::string(argv[i]) == "--no-rerun") +		if(i == 2 && std::string(args_span[i]) == "--no-rerun")  		{  			no_rerun = true;  			continue;  		} -		args.push_back(argv[i]); +		args.emplace_back(args_span[i]);  	}  	const auto& cfg = ctor::get_configuration(); @@ -680,14 +899,14 @@ int reconfigure(const ctor::settings& global_settings, int argc, char* argv[])  	{  		std::cout << e.first << "=\"" << e.second << "\" ";  	} -	std::cout << argv[0] << " configure "; +	std::cout << args_span[0] << " configure ";  	for(const auto& arg : cfg.args)  	{  		std::cout << arg << " ";  	}  	std::cout << "\n"; -	auto ret = regenerateCache(settings, cfg.args, cfg.env); +	auto ret = regenerateCache(settings, args_span[0], cfg.args, cfg.env);  	if(ret != 0)  	{  		return ret; @@ -700,5 +919,5 @@ int reconfigure(const ctor::settings& global_settings, int argc, char* argv[])  		return 0; // this was originally invoked by configure, don't loop  	} -	return execute(argv[0], args); +	return execute(settings, args_span[0], args);  } diff --git a/src/configure.cc.bak b/src/configure.cc.bak deleted file mode 100644 index bcbeea9..0000000 --- a/src/configure.cc.bak +++ /dev/null @@ -1,387 +0,0 @@ -// -*- c++ -*- -// Distributed under the BSD 2-Clause License. -// See accompanying file LICENSE for details. -#include "configure.h" - -#include <iostream> -#include <filesystem> -#include <fstream> - -#include <getoptpp/getoptpp.hpp> - -#include "settings.h" -#include "execute.h" -#include "libcppbuild.h" -#include "tasks.h" - -std::filesystem::path configurationFile("configuration.cc"); -std::filesystem::path configHeaderFile("config.h"); - -const std::map<std::string, std::string> default_configuration{}; -const std::map<std::string, std::string>& __attribute__((weak)) configuration() -{ -	return default_configuration; -} - -bool hasConfiguration(const std::string& key) -{ -	const auto& c = configuration(); -	return c.find(key) != c.end(); -} - -const std::string& getConfiguration(const std::string& key, -                                    const std::string& defaultValue) -{ -	const auto& c = configuration(); -	if(hasConfiguration(key)) -	{ -		return c.at(key); -	} - -	return defaultValue; -} - - -/* -Configuration: -  -h, --help              display this help and exit -      --help=short        display options specific to this package -      --help=recursive    display the short help of all the included packages -  -V, --version           display version information and exit -  -q, --quiet, --silent   do not print `checking ...' messages -      --cache-file=FILE   cache test results in FILE [disabled] -  -C, --config-cache      alias for `--cache-file=config.cache' -  -n, --no-create         do not create output files -      --srcdir=DIR        find the sources in DIR [configure dir or `..'] - -Installation directories: -  --prefix=PREFIX         install architecture-independent files in PREFIX -                          [/usr/local] -  --exec-prefix=EPREFIX   install architecture-dependent files in EPREFIX -                          [PREFIX] - -By default, `make install' will install all the files in -`/usr/local/bin', `/usr/local/lib' etc.  You can specify -an installation prefix other than `/usr/local' using `--prefix', -for instance `--prefix=$HOME'. - -For better control, use the options below. - -Fine tuning of the installation directories: -  --bindir=DIR            user executables [EPREFIX/bin] -  --sbindir=DIR           system admin executables [EPREFIX/sbin] -  --libexecdir=DIR        program executables [EPREFIX/libexec] -  --sysconfdir=DIR        read-only single-machine data [PREFIX/etc] -  --sharedstatedir=DIR    modifiable architecture-independent data [PREFIX/com] -  --localstatedir=DIR     modifiable single-machine data [PREFIX/var] -  --libdir=DIR            object code libraries [EPREFIX/lib] -  --includedir=DIR        C header files [PREFIX/include] -  --oldincludedir=DIR     C header files for non-gcc [/usr/include] -  --datarootdir=DIR       read-only arch.-independent data root [PREFIX/share] -  --datadir=DIR           read-only architecture-independent data [DATAROOTDIR] -  --infodir=DIR           info documentation [DATAROOTDIR/info] -  --localedir=DIR         locale-dependent data [DATAROOTDIR/locale] -  --mandir=DIR            man documentation [DATAROOTDIR/man] -  --docdir=DIR            documentation root [DATAROOTDIR/doc/drumgizmo] -  --htmldir=DIR           html documentation [DOCDIR] -  --dvidir=DIR            dvi documentation [DOCDIR] -  --pdfdir=DIR            pdf documentation [DOCDIR] -  --psdir=DIR             ps documentation [DOCDIR] - -Program names: -  --program-prefix=PREFIX            prepend PREFIX to installed program names -  --program-suffix=SUFFIX            append SUFFIX to installed program names -  --program-transform-name=PROGRAM   run sed PROGRAM on installed program names - -System types: -  --build=BUILD     configure for building on BUILD [guessed] -  --host=HOST       cross-compile to build programs to run on HOST [BUILD] - -Optional Features: -  --disable-option-checking  ignore unrecognized --enable/--with options -  --disable-FEATURE       do not include FEATURE (same as --enable-FEATURE=no) -  --enable-FEATURE[=ARG]  include FEATURE [ARG=yes] -  --enable-silent-rules   less verbose build output (undo: "make V=1") -  --disable-silent-rules  verbose build output (undo: "make V=0") -  --enable-dependency-tracking -                          do not reject slow dependency extractors -  --disable-dependency-tracking -                          speeds up one-time build -  --enable-shared[=PKGS]  build shared libraries [default=yes] -  --enable-static[=PKGS]  build static libraries [default=yes] -  --enable-fast-install[=PKGS] -                          optimize for fast installation [default=yes] -  --disable-libtool-lock  avoid locking (might break parallel builds) -  --disable-largefile     omit support for large files -  --enable-gui=backend    Use specified gui backend. Can be x11, win32, cocoa, -                          pugl-x11, pugl-win32, pugl-cocoa or auto -                          [default=auto] -  --enable-custom-channel-count=count -                          Compile with specified number of output channels -                          [default=16] -  --enable-lv2            Compile the LV2 plugin [default=no] -  --enable-vst            Compile the VST plugin [default=no] -  --enable-cli            Compile the command line interface [default=yes] -  --disable-input-dummy   Disable input dummy plugin [default=enabled] -  --disable-input-test    Disable input test plugin [default=enabled] -  --disable-input-jackmidi -                          Disable input jackmidi plugin [default=enabled] -  --disable-input-alsamidi -                          Disable input alsamidi plugin [default=enabled] -  --disable-input-midifile -                          Disable input midifile plugin [default=enabled] -  --disable-input-oss     Disable input oss plugin [enabled by default on -                          FreeBSD, disabled otherwise] -  --disable-output-dummy  Disable output dummy plugin [default=enabled] -  --disable-output-jackaudio -                          Disable output jack plugin [default=enabled] -  --disable-output-alsa   Disable output alsa plugin [default=enabled] -  --disable-output-wavfile -                          Disable output wavfile plugin [default=enabled] -  --disable-output-oss    Disable output oss plugin [enabled by default on -                          FreeBSD, disabled otherwise] -  --enable-sse=level      Enable SSE Level 1, 2, 3 or auto [default=auto] - -Optional Packages: -  --with-PACKAGE[=ARG]    use PACKAGE [ARG=yes] -  --without-PACKAGE       do not use PACKAGE (same as --with-PACKAGE=no) -  --with-pic[=PKGS]       try to use only PIC/non-PIC objects [default=use -                          both] -  --with-aix-soname=aix|svr4|both -                          shared library versioning (aka "SONAME") variant to -                          provide on AIX, [default=aix]. -  --with-gnu-ld           assume the C compiler uses GNU ld [default=no] -  --with-sysroot[=DIR]    Search for dependent libraries within DIR (or the -                          compiler's sysroot if not specified). -  --with-debug            Build with debug support -  --with-nls              Build with nls support (default nls enabled) -  --with-test             Build unit tests -  --with-lv2dir=DIR       Use DIR as the lv2 plugin directory -                          [default=LIBDIR/lv2] -  --with-vst-sources      Point this to the vstsdk24 directory - -Some influential environment variables: -  CXX         C++ compiler command -  CXXFLAGS    C++ compiler flags -  LDFLAGS     linker flags, e.g. -L<lib dir> if you have libraries in a -              nonstandard directory <lib dir> -  LIBS        libraries to pass to the linker, e.g. -l<library> -  CPPFLAGS    (Objective) C/C++ preprocessor flags, e.g. -I<include dir> if -              you have headers in a nonstandard directory <include dir> -  OBJC        Objective C compiler command -  OBJCFLAGS   Objective C compiler flags -  OBJCXX      Objective C++ compiler command -  OBJCXXFLAGS Objective C++ compiler flags -  CC          C compiler command -  CFLAGS      C compiler flags -  LT_SYS_LIBRARY_PATH -              User-defined run-time library search path. -  CPP         C preprocessor -  CXXCPP      C++ preprocessor -  PKG_CONFIG  path to pkg-config utility -  PKG_CONFIG_PATH -              directories to add to pkg-config's search path -  PKG_CONFIG_LIBDIR -              path overriding pkg-config's built-in search path -  X11_CFLAGS  C compiler flags for X11, overriding pkg-config -  X11_LIBS    linker flags for X11, overriding pkg-config -  XEXT_CFLAGS C compiler flags for XEXT, overriding pkg-config -  XEXT_LIBS   linker flags for XEXT, overriding pkg-config -  LV2_CFLAGS  C compiler flags for LV2, overriding pkg-config -  LV2_LIBS    linker flags for LV2, overriding pkg-config -  SMF_CFLAGS  C compiler flags for SMF, overriding pkg-config -  SMF_LIBS    linker flags for SMF, overriding pkg-config -  SNDFILE_CFLAGS -              C compiler flags for SNDFILE, overriding pkg-config -  SNDFILE_LIBS -              linker flags for SNDFILE, overriding pkg-config -  JACK_CFLAGS C compiler flags for JACK, overriding pkg-config -  JACK_LIBS   linker flags for JACK, overriding pkg-config -  ALSA_CFLAGS C compiler flags for ALSA, overriding pkg-config -  ALSA_LIBS   linker flags for ALSA, overriding pkg-config - -Use these variables to override the choices made by `configure' or to help -it to find libraries and programs with nonstandard names/locations. - -Report bugs to the package provider. -*/ -int configure(int argc, char* argv[]) -{ -	Settings settings; - -	settings.builddir = "build"; - -	std::string cmd_str; -	for(int i = 0; i < argc; ++i) -	{ -		if(i > 0) -		{ -			cmd_str += " "; -		} -		cmd_str += argv[i]; -	} - -	dg::Options opt; -	int key{256}; - -	std::string build_arch; -	std::string build_path; -	std::string host_arch; -	std::string host_path; - -	opt.add("build-dir", required_argument, 'b', -	        "Set output directory for build files (default: '" + -	        settings.builddir + "').", -	        [&]() { -		        settings.builddir = optarg; -		        return 0; -	        }); - -	opt.add("verbose", no_argument, 'v', -	        "Be verbose. Add multiple times for more verbosity.", -	        [&]() { -		        settings.verbose++; -		        return 0; -	        }); - -	opt.add("build", required_argument, key++, -	        "Configure for building on specified architecture.", -	        [&]() { -		        build_arch = optarg; -		        return 0; -	        }); - -	opt.add("build-path", required_argument, key++, -	        "Set path to build tool-chain.", -	        [&]() { -		        build_path = optarg; -		        return 0; -	        }); - -	opt.add("host", required_argument, key++, -	        "Cross-compile to build programs to run on specified architecture.", -	        [&]() { -		        host_arch = optarg; -		        return 0; -	        }); - -	opt.add("host-path", required_argument, key++, -	        "Set path to cross-compile tool-chain.", -	        [&]() { -		        host_path = optarg; -		        return 0; -	        }); - -	opt.add("help", no_argument, 'h', -	        "Print this help text.", -	        [&]() { -		        std::cout << "configure usage stuff\n"; -		        opt.help(); -		        exit(0); -		        return 0; -	        }); - -	opt.process(argc, argv); - -	if(host_arch.empty()) -	{ -		host_arch = build_arch; -	} - -	auto tasks = getTasks(settings); - -	bool needs_cpp{false}; -	bool needs_c{false}; -	bool needs_ar{false}; -	bool needs_asm{false}; -	for(const auto& task :tasks) -	{ -		switch(task->sourceLanguage()) -		{ -		case Language::Auto: -			std::cerr << "TargetLanguage not deduced!\n"; -			exit(1); -			break; -		case Language::C: -			needs_cpp = false; -			break; -		case Language::Cpp: -			needs_c = true; -			break; -		case Language::Asm: -			needs_asm = true; -			break; -		} -	} - -	// CC=clang -	// CXX=clang++ - -	std::string path_env = std::getenv("PATH"); -	std::cout << path_env << "\n"; - -	std::vector<std::string> paths; - -	{ -		std::stringstream ss(path_env); -		std::string path; -		while (std::getline(ss, path, ':')) -		{ -			paths.push_back(path); -		} -	} -	for(const auto& path_str : paths) -	{ -		std::filesystem::path path(path_str); -		auto gcc = path / "gcc"; -		if(std::filesystem::exists(gcc)) -		{ -			std::cout << "Found file gcc in path: " << path << "\n"; -			auto perms = std::filesystem::status(gcc).permissions(); -			if((perms & std::filesystem::perms::owner_exec) != std::filesystem::perms::none) -			{ -				std::cout << " - executable by owner\n"; -			} -			if((perms & std::filesystem::perms::group_exec) != std::filesystem::perms::none) -			{ -				std::cout << " - executable by group\n"; -			} -			if((perms & std::filesystem::perms::others_exec) != std::filesystem::perms::none) -			{ -				std::cout << " - executable by others\n"; -			} -		} -	} -	exit(0); - -	{ -		std::ofstream istr(configurationFile); -		istr << "#include \"libcppbuild.h\"\n\n"; -		istr << "const std::map<std::string, std::string>& configuration()\n"; -		istr << "{\n"; -		istr << "	static std::map<std::string, std::string> c =\n"; -		istr << "	{\n"; -		istr << "		{ \"cmd\", \"" << cmd_str << "\" },\n"; -		istr << "		{ \"" << cfg::builddir << "\", \"" << settings.builddir << "\" },\n"; -		istr << "		{ \"" << cfg::target_cc << "\", \"/usr/bin/gcc\" },\n"; -		istr << "		{ \"" << cfg::target_cpp << "\", \"/usr/bin/g++\" },\n"; -		istr << "		{ \"" << cfg::target_ar << "\", \"/usr/bin/ar\" },\n"; -		istr << "		{ \"" << cfg::target_ld << "\", \"/usr/bin/ld\" },\n"; -		istr << "		{ \"" << cfg::host_cc << "\", \"/usr/bin/gcc\" },\n"; -		istr << "		{ \"" << cfg::host_cpp << "\", \"/usr/bin/g++\" },\n"; -		istr << "		{ \"" << cfg::host_ar << "\", \"/usr/bin/ar\" },\n"; -		istr << "		{ \"" << cfg::host_ld << "\", \"/usr/bin/ld\" },\n"; -		istr << "	};\n"; -		istr << "	return c;\n"; -		istr << "}\n"; -	} - -	{ -		std::ofstream istr(configHeaderFile); -		istr << "#pragma once\n\n"; -		istr << "#define HAS_FOO 1\n"; -		istr << "//#define HAS_BAR 1\n"; -	} - -	return 0; -} diff --git a/src/configure.h b/src/configure.h index ac8d721..5344646 100644 --- a/src/configure.h +++ b/src/configure.h @@ -12,8 +12,8 @@ namespace ctor {  struct settings;  } // namespace ctor:: -extern std::filesystem::path configurationFile;; -extern std::filesystem::path configHeaderFile; +extern const std::filesystem::path configurationFile; +extern const std::filesystem::path configHeaderFile;  int configure(const ctor::settings& settings, int argc, char* argv[]);  int reconfigure(const ctor::settings& settings, int argc, char* argv[]); @@ -10,6 +10,7 @@  #include <variant>  #include <cstddef>  #include <functional> +#include <string_view>  namespace ctor { @@ -24,6 +25,8 @@ enum class target_type  	unit_test,  	unit_test_library,  	function, + +	unknown,  };  enum class language @@ -41,21 +44,13 @@ enum class output_system  	build, // Internal tool during cross-compilation  }; -struct source +enum class arch  { -	source(const char* file) : file(file) {} -	source(const std::string& file) : file(file) {} -	source(const char* file, ctor::language lang) : file(file), language(lang) {} -	source(const std::string& file, ctor::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, ctor::language lang, const char* output) : file(file), language(lang), output(output) {} -	source(const std::string& file, ctor::language lang, const std::string& output) : file(file), language(lang), output(output) {} +	unix,    //!< Target platform architecture is unix-based (ie. linux, bsd, etc) +	apple,   //!< Target platform architecture is macos +	windows, //!< Target platform architecture is windows -	std::string file; -	ctor::language language{ctor::language::automatic}; -	std::string output{}; +	unknown, //!< Target platform architecture has not yet detected or was not possible to detect  };  enum class toolchain @@ -66,12 +61,38 @@ enum class toolchain  	clang,  }; +struct source +{ +	source(const char* file_) : file(file_) {} // convenience ctor + +	source(std::string_view file_) : source(file_, ctor::language::automatic) {} +	source(std::string_view file_, ctor::language lang_) : file(file_), language(lang_) {} + +	source(std::string_view file_, std::string_view output_) : file(file_), output(output_) {} +	source(std::string_view file_, ctor::language lang_, std::string_view output_) : file(file_), language(lang_), output(output_) {} + +	source(ctor::toolchain toolchain_, std::string_view file_) : file(file_), toolchain(toolchain_) {} +	source(ctor::toolchain toolchain_, std::string_view file_, ctor::language lang_) : file(file_), toolchain(toolchain_), language(lang_) {} + +	source(ctor::toolchain toolchain_, std::string_view file_, std::string_view output_) : file(file_), toolchain(toolchain_), output(output_) {} + +	source(ctor::toolchain toolchain_, std::string_view file_, ctor::language lang_, std::string_view output_) : file(file_), toolchain(toolchain_), language(lang_), output(output_) {} + +	std::string file; +	ctor::toolchain toolchain{ctor::toolchain::any}; +	ctor::language language{ctor::language::automatic}; +	std::string output{}; +}; +  enum class cxx_opt  {  	//                                  gcc/clang  	output,                          //  -o  	debug,                           //  -g  	warn_all,                        //  -Wall +	warn_conversion,                 //  -Wconversion +	warn_shadow,                     //  -Wshadow +	warn_extra,                      //  -Wextra  	warnings_as_errors,              //  -Werror  	generate_dep_tree,               //  -MMD  	no_link,                         //  -c @@ -80,6 +101,7 @@ enum class cxx_opt  	optimization,                    //  -O<arg>  	position_independent_code,       //  -fPIC  	position_independent_executable, //  -fPIE +	define,                          //  -D<arg>[=<arg2>]  	custom,                          // entire option taken verbatim from <arg>  }; @@ -89,6 +111,9 @@ enum class c_opt  	output,                          //  -o  	debug,                           //  -g  	warn_all,                        //  -Wall +	warn_conversion,                 //  -Wconversion +	warn_shadow,                     //  -Wshadow +	warn_extra,                      //  -Wextra  	warnings_as_errors,              //  -Werror  	generate_dep_tree,               //  -MMD  	no_link,                         //  -c @@ -97,6 +122,7 @@ enum class c_opt  	optimization,                    //  -O<arg>  	position_independent_code,       //  -fPIC  	position_independent_executable, //  -fPIE +	define,                          //  -D<arg>[=<arg2>]  	custom,                          // entire option taken verbatim from <arg>  }; @@ -120,6 +146,11 @@ enum class ld_opt  enum class ar_opt  {  	//                                  gcc/clang +	replace,                         // -r +	add_index,                       // -s +	create,                          // -c +	output,                          // <arg> +  	custom,                          // entire option taken verbatim from <arg>  }; @@ -133,18 +164,24 @@ template<typename T>  class flag  {  public: -	flag(const std::string& str); +	flag(std::string_view str);  	flag(const char* str); -	flag(T opt) : opt(opt) {} -	flag(T opt, const std::string& arg) : opt(opt), arg(arg) {} -	flag(T opt, const char* arg) : opt(opt), arg(arg) {} -	flag(ctor::toolchain toolchain, T opt) : toolchain(toolchain), opt(opt) {} -	flag(ctor::toolchain toolchain, T opt, const char* arg) : toolchain(toolchain), opt(opt), arg(arg) {} -	flag(ctor::toolchain toolchain, T opt, const std::string& arg) : toolchain(toolchain), opt(opt), arg(arg) {} +	flag(T opt_) : opt(opt_) {} +	flag(T opt_, std::string_view arg_, std::string_view arg2_ = "") +		: opt(opt_), arg(arg_), arg2(arg2_) {} +	flag(T opt_, const char* arg_, const char* arg2_ = "") +		: opt(opt_), arg(arg_), arg2(arg2_) {} +	flag(ctor::toolchain toolchain_, T opt_) +		: toolchain(toolchain_), opt(opt_) {} +	flag(ctor::toolchain toolchain_, T opt_, const char* arg_, const char* arg2_ = "") +		: toolchain(toolchain_), opt(opt_), arg(arg_), arg2(arg2_) {} +	flag(ctor::toolchain toolchain_, T opt_, std::string_view arg_, std::string_view arg2_ = "") +		: toolchain(toolchain_), opt(opt_), arg(arg_), arg2(arg2_) {}  	ctor::toolchain toolchain{ctor::toolchain::any}; -	T opt; +	T opt{};  	std::string arg; +	std::string arg2;  };  using c_flag = ctor::flag<ctor::c_opt>; @@ -185,7 +222,7 @@ struct build_configuration  {  	std::string name; // Name - used for referring in other configurations.  	ctor::target_type type{ctor::target_type::automatic}; -	ctor::output_system system{ctor::output_system::host}; +	ctor::output_system system{ctor::output_system::build};  	std::string target; // Output target file for this configuration  	std::vector<ctor::source> sources; // source list  	std::vector<std::string> depends; // internal target dependencies @@ -209,7 +246,7 @@ struct external_manual  struct external_configuration  {  	std::string name; // Name for configuration -	ctor::output_system system{ctor::output_system::host}; +	ctor::output_system system{ctor::output_system::build};  	std::variant<ctor::external_manual> external;  }; @@ -219,10 +256,10 @@ int reg(ctor::external_configurations (*cb)(const ctor::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(cb); } +#define CTOR_CONCAT(a, b) CTOR_CONCAT_INNER(a, b) +#define CTOR_CONCAT_INNER(a, b) a ## b +#define CTOR_UNIQUE_NAME(base) CTOR_CONCAT(base, __LINE__) +#define REG(cb) namespace { int CTOR_UNIQUE_NAME(unique) = reg(cb); }  // Predefined configuration keys  namespace cfg @@ -230,12 +267,12 @@ namespace cfg  constexpr auto builddir = "builddir";  constexpr auto host_cc = "host-cc"; -constexpr auto host_cxx = "host-cpp"; +constexpr auto host_cxx = "host-cxx";  constexpr auto host_ar = "host-ar";  constexpr auto host_ld = "host-ld";  constexpr auto build_cc = "build-cc"; -constexpr auto build_cxx = "build-cpp"; +constexpr auto build_cxx = "build-cxx";  constexpr auto build_ar = "build-ar";  constexpr auto build_ld = "build-ld"; @@ -246,10 +283,13 @@ constexpr auto ctor_libdir = "ctor-libdir";  struct configuration  {  	bool has(const std::string& key) const; -	const std::string& get(const std::string& key, const std::string& default_value = {}) const; +	std::string get(const std::string& key, const std::string& default_value = {}) const;  	ctor::toolchain host_toolchain{ctor::toolchain::none}; +	ctor::arch host_arch{ctor::arch::unknown}; +  	ctor::toolchain build_toolchain{ctor::toolchain::none}; +	ctor::arch build_arch{ctor::arch::unknown};  	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 diff --git a/src/deps.cc b/src/deps.cc new file mode 100644 index 0000000..9400b35 --- /dev/null +++ b/src/deps.cc @@ -0,0 +1,120 @@ +// -*- c++ -*- +// Distributed under the BSD 2-Clause License. +// See accompanying file LICENSE for details. +#include "deps.h" + +#include "util.h" + +#include <fstream> + +namespace { +/* Format example: +build/src/libctor_a-libctor_cc.o: src/libctor.cc \ + src/getoptpp/getoptpp.hpp src/ctor.h src/configure.h src/rebuild.h \ + src/A\ B\ C.h src/task.h src/build.h src/unittest.h + */ +std::vector<std::string> readDepsMake(const std::string& dep_file) +{ +	auto str = readFile(dep_file); + +	std::vector<std::string> output; +	std::string tmp; + +	enum class State +	{ +		pre_colon, +		post_colon, +		in_escape, +	} state{State::pre_colon}; + +	auto old_state{state}; +	for(const auto& c : str) +	{ +		if(c == '\r') +		{ +			// just always ignore windows extra newline char +			continue; +		} + +		switch(state) +		{ +		case State::pre_colon: +			if(c == ':') // ignore everything until the colon +			{ +				state = State::post_colon; +				continue; +			} +			continue; + +		case State::post_colon: +			if(c == '\n') +			{ +				// done +				if(!tmp.empty()) +				{ +					output.emplace_back(tmp); +				} +				return output; +			} +			if(c == '\\') +			{ +				old_state = state; +				state = State::in_escape; +				continue; +			} +			if(c == '\t' || c == ' ') // new token +			{ +				if(!tmp.empty()) +				{ +					output.emplace_back(tmp); +				} +				tmp.clear(); +				continue; +			} +			break; + +		case State::in_escape: +			if(c == '\n') +			{ +				// linewrap +				state = old_state; +				continue; +			} +			state = old_state; +			break; +		} + +		tmp += c; +	} + +	if(!tmp.empty()) +	{ +		output.emplace_back(tmp); +	} + +	return output; +} +} + +std::vector<std::string> readDeps(const std::string& dep_file, +                                  ctor::toolchain toolchain) +{ +	if(!std::filesystem::exists(dep_file)) +	{ +		return {}; +	} + +	switch(toolchain) +	{ +	case ctor::toolchain::any: +	case ctor::toolchain::none: +		return {}; + +	// makefile .d file based: +	case ctor::toolchain::gcc: +	case ctor::toolchain::clang: +		return readDepsMake(dep_file); +	} + +	return {}; +} diff --git a/src/deps.h b/src/deps.h new file mode 100644 index 0000000..be0dfb2 --- /dev/null +++ b/src/deps.h @@ -0,0 +1,12 @@ +// -*- c++ -*- +// Distributed under the BSD 2-Clause License. +// See accompanying file LICENSE for details. +#pragma once + +#include "ctor.h" + +#include <vector> +#include <string> + +std::vector<std::string> readDeps(const std::string& dep_file, +                                  ctor::toolchain toolchain); diff --git a/src/execute.cc b/src/execute.cc index 20a4a02..ad6c2a2 100644 --- a/src/execute.cc +++ b/src/execute.cc @@ -3,6 +3,9 @@  // See accompanying file LICENSE for details.  #include "execute.h" +#include "ctor.h" +#include "pointerlist.h" +  #include <unistd.h>  #include <cstring>  #include <sys/types.h> @@ -20,22 +23,53 @@ https://stackoverflow.com/questions/4259629/what-is-the-difference-between-fork-  namespace  { +  int parent_waitpid(pid_t pid)  { -	int status; +	int status{}; + +	auto rc_pid = waitpid(pid, &status, 0); -	if(waitpid(pid, &status, 0) != pid) +	if(rc_pid > 0)  	{ -		return 1; +		if(WIFEXITED(status)) +		{ +			// Child exited with normally +			return WEXITSTATUS(status); +		} +		if(WIFSIGNALED(status)) +		{ +			// Child exited via signal (segfault, abort, ...) +			std::cerr << strsignal(status) << '\n'; +			return WTERMSIG(status); +		} +	} +	else +	{ // No PID returned, this is an error +		if(errno == ECHILD) +		{ +			// No children exist. +			return 1; +		} +		else +		{ +			// Unexpected error. +			abort(); +		}  	} -	return WEXITSTATUS(status); +	// Should never happen... +	return 1;  }  } // namespace :: -int execute(const std::string& command, +extern char **environ; // see 'man environ' + +int execute(const ctor::settings& settings, +            const std::string& command,              const std::vector<std::string>& args, -            bool verbose) +            const std::map<std::string, std::string>& env, +            [[maybe_unused]] bool terminate)  {  	std::vector<const char*> argv;  	argv.push_back(command.data()); @@ -45,22 +79,22 @@ int execute(const std::string& command,  	}  	argv.push_back(nullptr); -	if(verbose) +	std::string cmd; +	for(const auto& arg : argv)  	{ -		std::string cmd; -		for(const auto& arg : argv) +		if(arg == nullptr) +		{ +			break; +		} +		if(!cmd.empty())  		{ -			if(arg == nullptr) -			{ -				break; -			} -			if(!cmd.empty()) -			{ -				cmd += " "; -			} -			cmd += arg; +			cmd += " ";  		} +		cmd += arg; +	} +	if(settings.verbose > 0) +	{  		std::cout << cmd << std::endl;  	} @@ -68,23 +102,38 @@ int execute(const std::string& command,  	auto pid = vfork();  	if(pid == 0)  	{ -		execv(command.data(), (char**)argv.data()); +		EnvMap envmap((const char**)environ); +		for(const auto& [key, value] : env) +		{ +			envmap.insert(key + "=" + value); +		} + +		auto [_, envv] = envmap.get(); +		execve(command.data(), const_cast<char* const *>(argv.data()), +		       const_cast<char* const *>(envv));  		std::cout << "Could not execute " << command << ": " <<  			strerror(errno) << "\n"; -		_exit(1); // execv only returns if an error occurred +		_exit(1); // execve only returns if an error occurred  	} -	auto ret = parent_waitpid(pid); +	return parent_waitpid(pid);  #elif 0  	pid_t pid; +	std::vector<std::string> venv; +	for(const auto& [key, value] : env) +	{ +		venv.push_back(key + "=" + value); +	} +	Env penv(venv);  	if(posix_spawn(&pid, command.data(), nullptr, nullptr, -	               (char**)argv.data(), nullptr)) +	               (char**)argv.data(), penv.data()))  	{  		return 1;  	} -	auto ret = parent_waitpid(pid); +	return parent_waitpid(pid);  #else -	auto ret = system(cmd.data()); +	(void)parent_waitpid; +	return system(cmd.data());  #endif -	return ret; +	return 1;  } diff --git a/src/execute.h b/src/execute.h index c750a83..4288bb7 100644 --- a/src/execute.h +++ b/src/execute.h @@ -5,7 +5,14 @@  #include <string>  #include <vector> +#include <map> -int execute(const std::string& command, -            const std::vector<std::string>& args, -            bool verbose = true); +namespace ctor { +struct settings; +}//ctor:: + +int execute(const ctor::settings& settings, +            const std::string& command, +            const std::vector<std::string>& args = {}, +            const std::map<std::string, std::string>& env = {}, +            bool terminate = false); diff --git a/src/externals_manual.cc b/src/externals_manual.cc index 3b96263..0563a5e 100644 --- a/src/externals_manual.cc +++ b/src/externals_manual.cc @@ -13,7 +13,7 @@  extern std::map<std::string, std::string> external_includedir;  extern std::map<std::string, std::string> external_libdir; -int resolv(const ctor::settings& settings, const ctor::external_configuration& config, +int resolv([[maybe_unused]]const ctor::settings& settings, const ctor::external_configuration& config,             const ctor::external_manual& ext, ctor::flags& flags)  {  	flags = ext.flags; @@ -21,14 +21,14 @@ int resolv(const ctor::settings& settings, const ctor::external_configuration& c  	auto inc = external_includedir.find(config.name);  	if(inc != external_includedir.end())  	{ -		flags.cflags.push_back({ctor::c_opt::include_path, inc->second}); -		flags.cxxflags.push_back({ctor::cxx_opt::include_path, inc->second}); +		flags.cflags.emplace_back(ctor::c_opt::include_path, inc->second); +		flags.cxxflags.emplace_back(ctor::cxx_opt::include_path, inc->second);  	}  	auto lib = external_libdir.find(config.name);  	if(lib != external_libdir.end())  	{ -		flags.ldflags.push_back({ctor::ld_opt::library_path, lib->second}); +		flags.ldflags.emplace_back(ctor::ld_opt::library_path, lib->second);  	}  	return 0; diff --git a/src/getoptpp b/src/getoptpp -Subproject 9ff20ef857429619267e3f156a4f81ad9e1eb8c +Subproject 5aba94355ec638c6f8612f86be309ed684979ae diff --git a/src/libctor.cc b/src/libctor.cc index 21792e1..aaf17c9 100644 --- a/src/libctor.cc +++ b/src/libctor.cc @@ -15,7 +15,7 @@  #include <deque>  #include <fstream>  #include <cstdlib> -#include <set> +#include <span>  #include <getoptpp/getoptpp.hpp> @@ -28,6 +28,8 @@  int main(int argc, char* argv[])  { +	auto args = std::span(argv, static_cast<std::size_t>(argc)); +  	ctor::settings settings{};  	const auto& c = ctor::get_configuration(); @@ -35,12 +37,12 @@ int main(int argc, char* argv[])  	settings.parallel_processes =  		std::max(1u, std::thread::hardware_concurrency()) * 2 - 1; -	if(argc > 1 && std::string(argv[1]) == "configure") +	if(args.size() > 1 && std::string(args[1]) == "configure")  	{  		return configure(settings, argc, argv);  	} -	if(argc > 1 && std::string(argv[1]) == "reconfigure") +	if(args.size() > 1 && std::string(args[1]) == "reconfigure")  	{  		return reconfigure(settings, argc, argv);  	} @@ -65,7 +67,8 @@ int main(int argc, char* argv[])  	        [&]() {  		        try  		        { -			        settings.parallel_processes = std::stoi(optarg); +			        settings.parallel_processes = +				        static_cast<std::size_t>(std::stoi(optarg));  		        }  		        catch(...)  		        { @@ -101,7 +104,7 @@ int main(int argc, char* argv[])  	        "Add specified file to the build configurations.",  	        [&]() {  		        no_relaunch = true; -		        add_files.push_back(optarg); +		        add_files.emplace_back(optarg);  		        return 0;  	        }); @@ -109,7 +112,7 @@ int main(int argc, char* argv[])  	        "Remove specified file from the build configurations.",  	        [&]() {  		        no_relaunch = true; -		        remove_files.push_back(optarg); +		        remove_files.emplace_back(optarg);  		        return 0;  	        }); @@ -156,8 +159,8 @@ int main(int argc, char* argv[])  	opt.add("help", no_argument, 'h',  	        "Print this help text.", -	        [&]() { -		        std::cout << "Usage: " << argv[0] << " [options] [target] ...\n"; +	        [&]() -> int { +		        std::cout << "Usage: " << args[0] << " [options] [target] ...\n";  		        std::cout <<  R"_( where target can be either:     configure   - run configuration step (cannot be used with other targets). @@ -171,7 +174,6 @@ Options:  )_";  		        opt.help();  		        exit(0); -		        return 0;  	        });  	opt.process(argc, argv); @@ -185,17 +187,18 @@ Options:  	if(list_files)  	{  		no_default_build = true; -		std::set<std::string> files; +		std::vector<std::string> files;  		for(std::size_t i = 0; i < numConfigFiles; ++i)  		{ -			files.insert(configFiles[i].file); +			files.emplace_back(configFiles[i].file);  		}  		for(std::size_t i = 0; i < numExternalConfigFiles; ++i)  		{ -			files.insert(externalConfigFiles[i].file); +			files.emplace_back(externalConfigFiles[i].file);  		} +		std::sort(files.begin(), files.end());  		for(const auto& file : files)  		{  			std::cout << file << "\n"; @@ -270,7 +273,6 @@ Options:  	if(print_configure_db)  	{  		no_default_build = true; -		const auto& c = ctor::get_configuration();  		for(const auto& config : c.tools)  		{  			std::cout << config.first << ": " << config.second << "\n"; diff --git a/src/pointerlist.cc b/src/pointerlist.cc new file mode 100644 index 0000000..c0242f7 --- /dev/null +++ b/src/pointerlist.cc @@ -0,0 +1,123 @@ +// -*- c++ -*- +// Distributed under the BSD 2-Clause License. +// See accompanying file LICENSE for details. +#include "pointerlist.h" + +#include <cstring> + +PointerList::PointerList(int argc, const char* const argv[]) +{ +	for(int i = 0; i < argc; ++i) +	{ +		push_back(argv[i]); +	} +} + +std::pair<int, const char* const*> PointerList::get() +{ +	argptrs.clear(); +	for(const auto& arg : *this) +	{ +		argptrs.push_back(arg.data()); +	} +	argptrs.push_back(nullptr); + +	return {argptrs.size() - 1, // size not counting the nullptr at the end +	        argptrs.data()}; +} + +EnvMap::EnvMap(const char* const env[]) +{ +	if(env == nullptr) +	{ +		return; +	} + +	auto ptr = env; +	while(*ptr) +	{ +		insert(std::string_view(*ptr)); +		++ptr; +	} +} + +EnvMap::EnvMap(const char* env) +{ +	if(env == nullptr) +	{ +		return; +	} + +	auto ptr = env; +	while(*ptr) +	{ +		insert(ptr); +		ptr += strlen(ptr) + 1; +	} +} + +std::string EnvMap::operator[](const std::string& key) const +{ +	try +	{ +		return data.at(key); +	} +	catch(...) +	{ +		return {}; +	} +} + +void EnvMap::clear() +{ +	data.clear(); +} + +void EnvMap::insert(std::string_view key_value) +{ +	auto equals_sign = key_value.find('='); +	if(equals_sign == std::string::npos) +	{ +		insert({key_value, ""}); +		return; +	} +	std::string key{key_value.substr(0, equals_sign)}; +	std::string value{key_value.substr(equals_sign + 1)}; // skip '=' +	insert({key, value}); +} + +void EnvMap::insert(const std::pair<std::string_view, std::string_view>& item) +{ +	data[std::string(item.first)] = item.second; +} + +bool EnvMap::contains(std::string_view key) const +{ +	return data.contains(std::string{key}); +} + +std::size_t EnvMap::size() const +{ +	return data.size(); +} + +std::string EnvMap::stringify() const +{ +	std::string str; +	for(const auto& [key, value] : data) +	{ +		str += key + "=" + value + '\0'; +	} +	str += '\0'; +	return str; +} + +std::pair<int, const char* const*> EnvMap::get() +{ +	pointerlist.clear(); +	for(const auto& [key, value] : data) +	{ +		pointerlist.push_back(key + "=" + value + '\0'); +	} +	return pointerlist.get(); +} diff --git a/src/pointerlist.h b/src/pointerlist.h new file mode 100644 index 0000000..988bb26 --- /dev/null +++ b/src/pointerlist.h @@ -0,0 +1,74 @@ +// -*- c++ -*- +// Distributed under the BSD 2-Clause License. +// See accompanying file LICENSE for details. +#pragma once + +#include <string> +#include <vector> +#include <deque> +#include <utility> +#include <map> + +//! Maintains an (owning) list of string args and converts them to argc/argv +//! compatible arguments on request. +//! The returned pointers are guaranteed to be valid as long as the PointerList +//! object lifetime is not exceeded. +class PointerList +	: public std::deque<std::string> +{ +public: +	PointerList() = default; +	PointerList(int argc, const char* const argv[]); + +	//! Returns argc/argv pair from the current list of args +	//! The argv entry after the last is a nullptr (not included in the argc) +	std::pair<int, const char* const*> get(); + +private: +	std::vector<const char*> argptrs; +}; + + +//! Maintains an owning map of strings representing the env. +class EnvMap +{ +public: +	EnvMap() = default; + +	//! Initialize from an array of pointers to key=value\0 strings terminated +	//! by \0 +	EnvMap(const char* const env[]); + +	//! Initialize from a string of the format +	//!  key1=val\0key2=val\0...keyN=val\0\0 +	EnvMap(const char* env); + +	std::string operator[](const std::string& key) const; + +	//! Clear all items in the map +	void clear(); + +	//! Insert string from format: key=value +	void insert(std::string_view key_value); + +	//! Regular map insert +	void insert(const std::pair<std::string_view, std::string_view>& item); + +	//! Checks if the container contains element with specific key +	bool contains(std::string_view key) const; + +	std::size_t size() const; + +	//! Return string with the following format: +	//!  key1=val\0key2=val\0...keyN=val\0\0 +	std::string stringify() const; + +	//! Returns the map as argc/argv pair where each pointer points to a string +	//! of the format key=value\0 and is terminated with a nullptr +	std::pair<int, const char* const*> get(); + +private: +	std::map<std::string, std::string> data{}; + +	PointerList pointerlist; +}; diff --git a/src/rebuild.cc b/src/rebuild.cc index c97452d..52245fe 100644 --- a/src/rebuild.cc +++ b/src/rebuild.cc @@ -8,6 +8,7 @@  #include <algorithm>  #include <source_location>  #include <cstring> +#include <span>  #include "configure.h"  #include "ctor.h" @@ -70,7 +71,7 @@ int reg(const char* location)  int unreg(const char* location)  { -	std::size_t found{0}; +	int found{0};  	for(std::size_t i = 0; i < numConfigFiles;)  	{  		if(std::string(location) == configFiles[i].file) @@ -150,6 +151,8 @@ bool contains(const std::vector<ctor::source>& sources, const std::string& file)  bool recompileCheck(const ctor::settings& global_settings, int argc, char* argv[],                      bool relaunch_allowed)  { +	auto args_span = std::span(argv, static_cast<std::size_t>(argc)); +  	using namespace std::string_literals;  	if(global_settings.verbose > 1) @@ -159,38 +162,44 @@ bool recompileCheck(const ctor::settings& global_settings, int argc, char* argv[  	ctor::build_configuration config; +	config.type = ctor::target_type::executable;  	config.name = "ctor";  	config.system = ctor::output_system::build; -	config.flags.cxxflags.push_back({ctor::cxx_opt::optimization, "3"}); -	config.flags.cxxflags.push_back({ctor::cxx_opt::cpp_std, "c++20"}); +	config.flags.cxxflags.emplace_back(ctor::cxx_opt::optimization, "3"); +	config.flags.cxxflags.emplace_back(ctor::cxx_opt::cpp_std, "c++20");  	const auto& c = ctor::get_configuration();  	if(c.has(ctor::cfg::ctor_includedir))  	{ -		config.flags.cxxflags.push_back({ctor::cxx_opt::include_path, -		                                 c.get(ctor::cfg::ctor_includedir)}); +		config.flags.cxxflags.emplace_back(ctor::cxx_opt::include_path, +		                                   c.get(ctor::cfg::ctor_includedir));  	}  	if(c.has(ctor::cfg::ctor_libdir))  	{ -		config.flags.ldflags.push_back({ctor::ld_opt::library_path, c.get(ctor::cfg::ctor_libdir)}); +		config.flags.ldflags.emplace_back(ctor::ld_opt::library_path, +		                                  c.get(ctor::cfg::ctor_libdir));  	} -	config.flags.ldflags.push_back({ctor::ld_opt::link, "ctor"}); -	config.flags.ldflags.push_back({ctor::ld_opt::threads}); +	config.flags.ldflags.emplace_back(ctor::ld_opt::link, "ctor"); +	config.flags.ldflags.emplace_back(ctor::ld_opt::strip); +	config.flags.ldflags.emplace_back(ctor::ld_opt::threads);  	ctor::settings settings{global_settings};  	settings.verbose = -1; // Make check completely silent. -	settings.builddir += "/ctor"; // override builddir to use ctor subdir + +	// override builddir to use ctor subdir +	auto ctor_builddir = std::filesystem::path(settings.builddir) / "ctor"; +	settings.builddir = ctor_builddir.string();  	{  		std::filesystem::path buildfile  = settings.builddir; -		std::filesystem::path currentfile = argv[0]; +		std::filesystem::path currentfile = args_span[0];  		config.target = std::filesystem::relative(currentfile, buildfile).string();  	}  	if(std::filesystem::exists(configurationFile))  	{ -		config.sources.push_back(configurationFile.string()); +		config.sources.emplace_back(configurationFile.string());  	}  	for(std::size_t i = 0; i < numConfigFiles; ++i) @@ -204,7 +213,7 @@ bool recompileCheck(const ctor::settings& global_settings, int argc, char* argv[  		// Ensure that files containing multiple configurations are only added once.  		if(!contains(config.sources, location))  		{ -			config.sources.push_back(location); +			config.sources.emplace_back(location);  		}  	} @@ -219,11 +228,11 @@ bool recompileCheck(const ctor::settings& global_settings, int argc, char* argv[  		// Ensure that files containing multiple configurations are only added once.  		if(!contains(config.sources, location))  		{ -			config.sources.push_back(location); +			config.sources.emplace_back(location);  		}  	} -	auto tasks = taskFactory({config}, settings, {}); +	auto tasks = taskFactory({config}, settings, {}, true);  	for(auto task : tasks)  	{ @@ -259,16 +268,17 @@ bool recompileCheck(const ctor::settings& global_settings, int argc, char* argv[  	if(reconfigure)  	{  		std::vector<std::string> args; -		args.push_back("reconfigure"); +		args.emplace_back("reconfigure");  		if(!relaunch_allowed)  		{ -			args.push_back("--no-rerun"); +			args.emplace_back("--no-rerun");  		} -		for(int i = 1; i < argc; ++i) +		for(std::size_t i = 1; i < args_span.size(); ++i)  		{ -			args.push_back(argv[i]); +			args.emplace_back(args_span[i]);  		} -		auto ret = execute(argv[0], args); + +		auto ret = execute(settings, args_span[0], args);  		//if(ret != 0)  		{  			exit(ret); diff --git a/src/task.cc b/src/task.cc index 817ee3a..ef7731b 100644 --- a/src/task.cc +++ b/src/task.cc @@ -3,19 +3,20 @@  // See accompanying file LICENSE for details.  #include "task.h" -#include <unistd.h>  #include <iostream> +#include <algorithm> +#include <utility> -Task::Task(const ctor::build_configuration& config, const ctor::settings& settings, -           const std::string& sourceDir) -	: config(config) +Task::Task(const ctor::build_configuration& config_, const ctor::settings& settings_, +           std::string sourceDir_) +	: config(config_)  	, output_system(config.system) -	, settings(settings) -	, sourceDir(sourceDir) +	, settings(settings_) +	, sourceDir(std::move(sourceDir_))  {  } -int Task::registerDepTasks(const std::set<std::shared_ptr<Task>>& tasks) +int Task::registerDepTasks(const std::vector<std::shared_ptr<Task>>& tasks)  {  	for(const auto& depStr : depends())  	{ @@ -24,7 +25,10 @@ int Task::registerDepTasks(const std::set<std::shared_ptr<Task>>& tasks)  		{  			if(*task == depStr)  			{ -				dependsTasks.insert(task); +				if(std::find(dependsTasks.begin(), dependsTasks.end(), task) == dependsTasks.end()) +				{ +					dependsTasks.push_back(task); +				}  				found = true;  			}  		} @@ -41,11 +45,14 @@ int Task::registerDepTasks(const std::set<std::shared_ptr<Task>>& tasks)  bool Task::operator==(const std::string& depStr)  { +	std::filesystem::path generated_output = sourceDir; +	generated_output /= target();  	return -		name() == depStr || -		target() == depStr || -		sourceDir + "/" + target() == depStr || -		targetFile().string() == depStr +		(!derived() && name() == depStr) || // compare to name +		(!derived() && config.target == depStr) || // compare to stated target (ex. foo.a) +		target() == depStr || // compare to derived (derived to foo.lib on msvc) +		generated_output  == depStr || // not sure what this is for?! +		targetFile().string() == depStr // compare to target output file  		;  } @@ -140,6 +147,7 @@ std::string Task::compiler() const  		case ctor::output_system::build:  			return c.get(ctor::cfg::build_cc, "/usr/bin/gcc");  		} +		break;  	case ctor::language::cpp:  		switch(outputSystem())  		{ @@ -148,14 +156,18 @@ std::string Task::compiler() const  		case ctor::output_system::build:  			return c.get(ctor::cfg::build_cxx, "/usr/bin/g++");  		} +		break;  	default:  		std::cerr << "Unknown CC target type\n";  		exit(1);  		break;  	} + +	std::cerr << "Unhandled compiler!\n"; +	exit(1);  } -std::set<std::shared_ptr<Task>> Task::getDependsTasks() +std::vector<std::shared_ptr<Task>> Task::getDependsTasks()  {  	return dependsTasks;  } @@ -6,7 +6,6 @@  #include <vector>  #include <string>  #include <atomic> -#include <set>  #include <memory>  #include <filesystem> @@ -25,15 +24,15 @@ class Task  {  public:  	Task(const ctor::build_configuration& config, const ctor::settings& settings, -	     const std::string& sourceDir); +	     std::string sourceDir);  	virtual ~Task() = default; -	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::vector<std::shared_ptr<Task>>& tasks); +	virtual int registerDepTasksInner([[maybe_unused]]const std::vector<std::shared_ptr<Task>>& tasks) { return 0; }  	bool operator==(const std::string& dep); -	virtual std::string name() const; +	std::string name() const;  	bool dirty();  	bool ready();  	int run(); @@ -64,7 +63,7 @@ public:  	ctor::output_system outputSystem() const;  	std::string compiler() const; -	std::set<std::shared_ptr<Task>> getDependsTasks(); +	std::vector<std::shared_ptr<Task>> getDependsTasks();  	virtual std::string source() const { return {}; } @@ -74,7 +73,7 @@ protected:  	virtual bool dirtyInner() { return false; }  	std::vector<std::string> dependsStr; -	std::set<std::shared_ptr<Task>> dependsTasks; +	std::vector<std::shared_ptr<Task>> dependsTasks;  	const ctor::build_configuration& config;  	ctor::target_type target_type{ctor::target_type::automatic};  	ctor::language source_language{ctor::language::automatic}; diff --git a/src/task_ar.cc b/src/task_ar.cc index 81ced0f..3b45cc2 100644 --- a/src/task_ar.cc +++ b/src/task_ar.cc @@ -11,19 +11,22 @@  #include "util.h"  #include "tools.h" -TaskAR::TaskAR(const ctor::build_configuration& config, -               const ctor::settings& settings, +TaskAR::TaskAR(const ctor::build_configuration& config_, +               const ctor::settings& settings_,                 const std::string& target,                 const std::vector<std::string>& objects, -               const std::string& sourceDir) -	: Task(config, settings, sourceDir) -	, config(config) -	, settings(settings) -	, sourceDir(sourceDir) +               const std::string& sourceDir_) +	: Task(config_, settings_, sourceDir_) +	, _targetFile(target) +	, config(config_) +	, settings(settings_) +	, sourceDir(sourceDir_)  { -	std::filesystem::create_directories(std::filesystem::path(settings.builddir) / sourceDir); +	target_type = ctor::target_type::static_library; +	output_system = config.system; -	_targetFile = target; +	auto toolchain = getToolChain(config.system); +	_targetFile = extension(toolchain, target_type, config.system, _targetFile);  	for(const auto& object : objects)  	{  		std::filesystem::path objectFile = object; @@ -33,13 +36,12 @@ TaskAR::TaskAR(const ctor::build_configuration& config,  	for(const auto& dep : config.depends)  	{ -		depFiles.push_back(dep); +		depFiles.emplace_back(dep);  	}  	flagsFile = std::filesystem::path(settings.builddir) / cleanUp(sourceDir) / targetFile().stem();  	flagsFile += ".flags"; -	target_type = ctor::target_type::static_library;  	source_language = ctor::language::c;  	for(const auto& source : config.sources)  	{ @@ -50,6 +52,8 @@ TaskAR::TaskAR(const ctor::build_configuration& config,  			source_language = ctor::language::cpp;  		}  	} + +	std::filesystem::create_directories(targetFile().parent_path());  }  bool TaskAR::dirtyInner() @@ -78,9 +82,17 @@ bool TaskAR::dirtyInner()  int TaskAR::runInner()  { +	auto toolchain = getToolChain(config.system); +  	std::vector<std::string> args; -	args.push_back("rcs"); -	args.push_back(targetFile().string()); +	for(const auto& flag : config.flags.arflags) +	{ +		append(args, to_strings(toolchain, flag)); +	} +	append(args, ar_option(toolchain, ctor::ar_opt::replace)); +	append(args, ar_option(toolchain, ctor::ar_opt::add_index)); +	append(args, ar_option(toolchain, ctor::ar_opt::create)); +	append(args, ar_option(toolchain, ctor::ar_opt::output, targetFile().string()));  	for(const auto& task : getDependsTasks())  	{  		args.push_back(task->targetFile().string()); @@ -93,7 +105,8 @@ int TaskAR::runInner()  	if(settings.verbose == 0)  	{ -		std::cout << "AR => " << targetFile().string() << std::endl; +		std::string output = "AR => " + targetFile().string() + '\n'; +		std::cout << output << std::flush;  	}  	const auto& c = ctor::get_configuration(); @@ -108,7 +121,13 @@ int TaskAR::runInner()  		break;  	} -	return execute(tool, args, settings.verbose > 0); +	auto res = execute(settings, tool, args, c.env); +	if(res != 0) +	{ +		std::filesystem::remove(targetFile()); +	} + +	return res;  }  int TaskAR::clean() @@ -176,7 +195,6 @@ std::string TaskAR::flagsString() const  			flagsStr += str;  		}  	} -	flagsStr += "\n";  	for(const auto& dep : config.depends)  	{ diff --git a/src/task_cc.cc b/src/task_cc.cc index 56915f5..b9edcf7 100644 --- a/src/task_cc.cc +++ b/src/task_cc.cc @@ -6,31 +6,33 @@  #include <iostream>  #include <fstream>  #include <cassert> +#include <algorithm>  #include "ctor.h"  #include "execute.h"  #include "util.h"  #include "tools.h" - -TaskCC::TaskCC(const ctor::build_configuration& config, const ctor::settings& settings, -               const std::string& sourceDir, const ctor::source& source) -	: Task(config, settings, sourceDir) -	, config(config) -	, settings(settings) -	, sourceDir(sourceDir) +#include "deps.h" + +TaskCC::TaskCC(const ctor::build_configuration& config_, const ctor::settings& settings_, +               const std::string& sourceDir_, const ctor::source& source) +	: Task(config_, settings_, sourceDir_) +	, sourceFile(sourceDir_) +	, config(config_) +	, settings(settings_) +	, sourceDir(sourceDir_)  	, _source(source)  { -	sourceFile = sourceDir;  	sourceFile /= source.file;  	std::filesystem::path base = sourceFile.parent_path(); -	std::filesystem::create_directories(std::filesystem::path(settings.builddir) / base);  	base /= cleanUp(config.target);  	base += "-";  	base += sourceFile.stem();  	target_type = ctor::target_type::object; +	output_system = config.system;  	source_language = source.language;  	if(source_language == ctor::language::automatic)  	{ @@ -56,36 +58,38 @@ TaskCC::TaskCC(const ctor::build_configuration& config, const ctor::settings& se  	if(source.output.empty())  	{  		_targetFile = base; -		_targetFile += ".o";  	}  	else  	{  		_targetFile = source.output;  	} +	auto toolchain = getToolChain(config.system); +	_targetFile = extension(toolchain, target_type, config.system, _targetFile); +  	depsFile = targetFile().parent_path() / targetFile().stem();  	depsFile += ".d";  	flagsFile = targetFile().parent_path() / targetFile().stem();  	flagsFile += ".flags"; + +	std::filesystem::create_directories(targetFile().parent_path());  } -int TaskCC::registerDepTasksInner(const std::set<std::shared_ptr<Task>>& tasks) +int TaskCC::registerDepTasksInner(const std::vector<std::shared_ptr<Task>>& tasks)  {  	for(const auto& task : tasks)  	{  		if(*task == _source.file)  		{ -			dependsTasks.insert(task); +			if(std::find(dependsTasks.begin(), dependsTasks.end(), task) == dependsTasks.end()) +			{ +				dependsTasks.push_back(task); +			}  		}  	}  	return 0;  } -std::string TaskCC::name() const -{ -	return {}; -} -  bool TaskCC::dirtyInner()  {  	if(!std::filesystem::exists(sourceFile)) @@ -128,7 +132,8 @@ bool TaskCC::dirtyInner()  		}  	} -	auto depList = readDeps(depsFile.string()); +	auto toolchain = getToolChain(config.system); +	auto depList = readDeps(depsFile.string(), toolchain);  	for(const auto& dep : depList)  	{  		if(!std::filesystem::exists(dep) || @@ -167,25 +172,33 @@ int TaskCC::runInner()  	if(settings.verbose == 0)  	{ +		std::string output;  		switch(sourceLanguage())  		{  		case ctor::language::c: -			std::cout << "CC "; +			output = "CC ";  			break;  		case ctor::language::cpp: -			std::cout << "CXX "; +			output = "CXX ";  			break;  		case ctor::language::automatic:  		case ctor::language::assembler:  			// Only c/c++ handled by this task type.  			break;  		} -		std::cout << -			sourceFile.lexically_normal().string() << " => " << -			targetFile().lexically_normal().string() << std::endl; +		output += sourceFile.lexically_normal().string() + " => " + +			targetFile().lexically_normal().string() + '\n'; +		std::cout << output << std::flush; +	} + +	const auto& cfg = ctor::get_configuration(); +	auto res = execute(settings, compiler(), args, cfg.env); +	if(res != 0) +	{ +		std::filesystem::remove(targetFile());  	} -	return execute(compiler(), args, settings.verbose > 0); +	return res;  }  int TaskCC::clean() @@ -278,6 +291,7 @@ std::vector<std::string> TaskCC::flags() const  		exit(1);  		break;  	} +  }  std::string TaskCC::flagsString() const @@ -301,7 +315,7 @@ std::vector<std::string> TaskCC::getCompilerArgs() const  	{  	case ctor::language::c:  		{ -			append(args, c_option(toolchain, ctor::c_opt::generate_dep_tree)); +			append(args, c_option(toolchain, ctor::c_opt::generate_dep_tree, depsFile.string()));  			if(std::filesystem::path(config.target).extension() == ".so")  			{ @@ -343,7 +357,7 @@ std::vector<std::string> TaskCC::getCompilerArgs() const  	case ctor::language::cpp:  		{ -			append(args, cxx_option(toolchain, ctor::cxx_opt::generate_dep_tree)); +			append(args, cxx_option(toolchain, ctor::cxx_opt::generate_dep_tree, depsFile.string()));  			if(std::filesystem::path(config.target).extension() == ".so")  			{ diff --git a/src/task_cc.h b/src/task_cc.h index 70bb13c..2299fcd 100644 --- a/src/task_cc.h +++ b/src/task_cc.h @@ -19,9 +19,8 @@ public:  	       const std::string& sourceDir, const ctor::source& source);  	virtual ~TaskCC() = default; -	int registerDepTasksInner(const std::set<std::shared_ptr<Task>>& tasks) override; +	int registerDepTasksInner(const std::vector<std::shared_ptr<Task>>& tasks) override; -	std::string name() const override;  	bool dirtyInner() override;  	int runInner() override; diff --git a/src/task_fn.cc b/src/task_fn.cc index b11ff15..b6b50ea 100644 --- a/src/task_fn.cc +++ b/src/task_fn.cc @@ -11,13 +11,13 @@  #include "execute.h"  #include "util.h" -TaskFn::TaskFn(const ctor::build_configuration& config, const ctor::settings& settings, -               const std::string& sourceDir, const ctor::source& source) -	: Task(config, settings, sourceDir) -	, config(config) -	, settings(settings) +TaskFn::TaskFn(const ctor::build_configuration& config_, const ctor::settings& settings_, +               const std::string& sourceDir_, const ctor::source& source) +	: Task(config_, settings_, sourceDir_) +	, sourceFile(sourceDir_) +	, config(config_) +	, settings(settings_)  { -	sourceFile = sourceDir;  	sourceFile /= source.file;  	std::filesystem::create_directories(std::filesystem::path(settings.builddir) / sourceFile.parent_path()); @@ -68,15 +68,22 @@ int TaskFn::runInner()  	if(settings.verbose >= 0)  	{ -		std::cout << "Fn" << " " << -			sourceFile.lexically_normal().string() << " => " << -			targetFile().lexically_normal().string() << std::endl; +		std::string output = "Fn " + +			sourceFile.lexically_normal().string() + " => " + +			targetFile().lexically_normal().string() + '\n'; +		std::cout << output << std::flush;  	} -	return config.function(sourceFile.string(), -	                       targetFile().string(), -	                       config, -	                       settings); +	auto res = config.function(sourceFile.string(), +	                           targetFile().string(), +	                           config, +	                           settings); +	if(res != 0) +	{ +		std::filesystem::remove(targetFile()); +	} + +	return res;  }  int TaskFn::clean() @@ -97,7 +104,7 @@ std::vector<std::string> TaskFn::depends() const  std::string TaskFn::target() const  { -	return _targetFile; +	return _targetFile.string();  }  std::filesystem::path TaskFn::targetFile() const diff --git a/src/task_ld.cc b/src/task_ld.cc index 3d917e4..69c3a8a 100644 --- a/src/task_ld.cc +++ b/src/task_ld.cc @@ -11,25 +11,29 @@  #include "util.h"  #include "tools.h" -TaskLD::TaskLD(const ctor::build_configuration& config, -               const ctor::settings& settings, +TaskLD::TaskLD(const ctor::build_configuration& config_, +               const ctor::settings& settings_,                 const std::string& target,                 const std::vector<std::string>& objects, -               const std::string& sourceDir) -	: Task(config, settings, sourceDir) -	, config(config) -	, settings(settings) -	, sourceDir(sourceDir) +               const std::string& sourceDir_, +               bool is_self_) +	: Task(config_, settings_, sourceDir_) +	, config(config_) +	, settings(settings_) +	, sourceDir(sourceDir_) +	, is_self(is_self_)  {  	target_type = config.type; +	output_system = config.system; +  	if(target_type == ctor::target_type::automatic)  	{  		target_type = ctor::target_type::executable;  	} -	std::filesystem::create_directories(std::filesystem::path(settings.builddir) / sourceDir); -  	_targetFile = target; +	auto toolchain = getToolChain(config.system); +	_targetFile = extension(toolchain, target_type, config.system, _targetFile);  	for(const auto& object : objects)  	{  		std::filesystem::path objectFile = object; @@ -39,7 +43,7 @@ TaskLD::TaskLD(const ctor::build_configuration& config,  	for(const auto& dep : config.depends)  	{ -		depFiles.push_back(dep); +		depFiles.emplace_back(dep);  	}  	flagsFile = std::filesystem::path(settings.builddir) / cleanUp(sourceDir) / targetFile().stem(); @@ -54,6 +58,8 @@ TaskLD::TaskLD(const ctor::build_configuration& config,  			source_language = ctor::language::cpp;  		}  	} + +	std::filesystem::create_directories(targetFile().parent_path());  }  bool TaskLD::dirtyInner() @@ -88,14 +94,16 @@ int TaskLD::runInner()  	for(const auto& dep : getDependsTasks())  	{  		auto depFile = dep->targetFile(); -		if(depFile.extension() == ".so") +		auto dep_type = target_type_from_extension(toolchain, depFile); +		if(dep_type == ctor::target_type::dynamic_library)  		{  			append(args, ld_option(toolchain, ctor::ld_opt::library_path,  			                       targetFile().parent_path().string()));  			auto lib = depFile.stem().string().substr(3); // strip 'lib' prefix  			append(args, ld_option(toolchain, ctor::ld_opt::link, lib));  		} -		else if(depFile.extension() == ".a" || depFile.extension() == ".o") +		else if(dep_type == ctor::target_type::static_library || +		        dep_type == ctor::target_type::object)  		{  			args.push_back(depFile.string());  		} @@ -115,11 +123,19 @@ int TaskLD::runInner()  	if(settings.verbose == 0)  	{ -		std::cout << "LD => " << targetFile().string() << std::endl; +		std::string output = "LD => " + targetFile().string() + '\n'; +		std::cout << output << std::flush;  	}  	auto tool = compiler(); -	return execute(tool, args, settings.verbose > 0); +	const auto& c = ctor::get_configuration(); +	auto res = execute(settings, tool, args, c.env, is_self); +	if(res != 0) +	{ +		std::filesystem::remove(targetFile()); +	} + +	return res;  }  int TaskLD::clean() @@ -187,7 +203,6 @@ std::string TaskLD::flagsString() const  			flagsStr += str;  		}  	} -	flagsStr += "\n";  	for(const auto& dep : config.depends)  	{ diff --git a/src/task_ld.h b/src/task_ld.h index dbe7db1..c0e3ebb 100644 --- a/src/task_ld.h +++ b/src/task_ld.h @@ -18,7 +18,8 @@ public:  	       const ctor::settings& settings,  	       const std::string& target,  	       const std::vector<std::string>& objects, -	       const std::string& _sourceDir); +	       const std::string& _sourceDir, +	       bool is_self);  	virtual ~TaskLD() = default;  	bool dirtyInner() override; @@ -44,4 +45,5 @@ private:  	const ctor::build_configuration& config;  	const ctor::settings& settings;  	std::string sourceDir; +	bool is_self;  }; diff --git a/src/task_so.cc b/src/task_so.cc index 9061591..c98e4a7 100644 --- a/src/task_so.cc +++ b/src/task_so.cc @@ -11,20 +11,24 @@  #include "util.h"  #include "tools.h" -TaskSO::TaskSO(const ctor::build_configuration& config, -               const ctor::settings& settings, +TaskSO::TaskSO(const ctor::build_configuration& config_, +               const ctor::settings& settings_,                 const std::string& target,                 const std::vector<std::string>& objects, -               const std::string& sourceDir) -	: Task(config, settings, sourceDir) -	, config(config) -	, settings(settings) -	, sourceDir(sourceDir) +               const std::string& sourceDir_) +	: Task(config_, settings_, sourceDir_) +	, config(config_) +	, settings(settings_) +	, sourceDir(sourceDir_)  {  	std::filesystem::path base = sourceDir; -	std::filesystem::create_directories(std::filesystem::path(settings.builddir) / base); + +	target_type = ctor::target_type::dynamic_library; +	output_system = config.system;  	_targetFile = base / target; +	auto toolchain = getToolChain(config.system); +	_targetFile = extension(toolchain, target_type, config.system, _targetFile);  	for(const auto& object : objects)  	{  		std::filesystem::path objectFile = object; @@ -34,13 +38,12 @@ TaskSO::TaskSO(const ctor::build_configuration& config,  	for(const auto& dep : config.depends)  	{ -		depFiles.push_back(dep); +		depFiles.emplace_back(dep);  	}  	flagsFile = std::filesystem::path(settings.builddir) / cleanUp(sourceDir) / targetFile().stem();  	flagsFile += ".flags"; -	target_type = ctor::target_type::dynamic_library;  	source_language = ctor::language::c;  	for(const auto& source : config.sources)  	{ @@ -51,6 +54,8 @@ TaskSO::TaskSO(const ctor::build_configuration& config,  			source_language = ctor::language::cpp;  		}  	} + +	std::filesystem::create_directories(targetFile().parent_path());  }  bool TaskSO::dirtyInner() @@ -105,11 +110,19 @@ int TaskSO::runInner()  	if(settings.verbose == 0)  	{ -		std::cout << "LD => " << targetFile().string() << std::endl; +		std::string output = "LD => " + targetFile().string() + '\n'; +		std::cout << output << std::flush;  	}  	auto tool = compiler(); -	return execute(tool, args, settings.verbose > 0); +	const auto& cfg = ctor::get_configuration(); +	auto res = execute(settings, tool, args, cfg.env); +	if(res != 0) +	{ +		std::filesystem::remove(targetFile()); +	} + +	return res;  }  int TaskSO::clean() @@ -177,7 +190,6 @@ std::string TaskSO::flagsString() const  			flagsStr += str;  		}  	} -	flagsStr += "\n";  	for(const auto& dep : config.depends)  	{ diff --git a/src/tasks.cc b/src/tasks.cc index 67bed2b..2f9e47a 100644 --- a/src/tasks.cc +++ b/src/tasks.cc @@ -8,6 +8,7 @@  #include <list>  #include <iostream>  #include <algorithm> +#include <span>  #include "ctor.h"  #include "task.h" @@ -18,24 +19,28 @@  #include "task_fn.h"  #include "rebuild.h"  #include "configure.h" +#include "util.h" +#include "tools.h"  const std::deque<Target>& getTargets(const ctor::settings& settings,                                       bool resolve_externals)  { +	auto config_files = std::span(configFiles).subspan(0, numConfigFiles); +  	static bool initialised{false};  	static std::deque<Target> targets;  	if(!initialised)  	{  		const auto& externals = ctor::get_configuration().externals; -		for(std::size_t i = 0; i < numConfigFiles; ++i) +		for(const auto& config_file : config_files)  		{  			std::string path = -				std::filesystem::path(configFiles[i].file).parent_path().string(); +				std::filesystem::path(config_file.file).parent_path().string();  			if(settings.verbose > 1)  			{ -				std::cout << configFiles[i].file << " in path " << path << "\n"; +				std::cout << config_file.file << " in path " << path << "\n";  			} -			auto configs = configFiles[i].cb(settings); +			auto configs = config_file.cb(settings);  			for(auto& config : configs)  			{  				if(resolve_externals) @@ -50,21 +55,12 @@ const std::deque<Target>& getTargets(const ctor::settings& settings,  							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()); +						append(config.flags.cflags, flags.cflags); +						append(config.flags.cxxflags, flags.cxxflags); +						append(config.flags.ldflags, flags.ldflags); +						append(config.flags.arflags, flags.arflags); +						append(config.flags.asmflags, flags.asmflags); +						//append(config.libs.insert(config.libs libs);  					}  				} @@ -77,11 +73,12 @@ const std::deque<Target>& getTargets(const ctor::settings& settings,  	return targets;  } -std::set<std::shared_ptr<Task>> taskFactory(const ctor::build_configuration& config, -                                            const ctor::settings& settings, -                                            const std::string& sourceDir) +std::vector<std::shared_ptr<Task>> taskFactory(const ctor::build_configuration& config, +                                               const ctor::settings& settings, +                                               const std::string& sourceDir, +                                               bool is_self)  { -	std::set<std::shared_ptr<Task>> tasks; +	std::vector<std::shared_ptr<Task>> tasks;  	std::filesystem::path targetFile(config.target); @@ -92,34 +89,26 @@ std::set<std::shared_ptr<Task>> taskFactory(const ctor::build_configuration& con  		{  			target_type = ctor::target_type::function;  		} -		else if(targetFile.extension() == ".a") -		{ -			target_type = ctor::target_type::static_library; -		} -		else if(targetFile.extension() == ".so") -		{ -			target_type = ctor::target_type::dynamic_library; -		} -		else if(targetFile.extension() == "") -		{ -			target_type = ctor::target_type::executable; -		}  		else  		{ -			std::cerr << "Could not deduce target type from target " << -				targetFile.string() << " please specify.\n"; -			exit(1); +			target_type = target_type_from_extension(ctor::toolchain::any, targetFile);  		}  	} +	const auto& c = ctor::get_configuration();  	std::vector<std::string> objects;  	if(target_type != ctor::target_type::function)  	{ -		for(const auto& file : config.sources) +		for(const auto& source : config.sources)  		{ -			auto task = std::make_shared<TaskCC>(config, settings, sourceDir, file); -			tasks.insert(task); -			objects.push_back(task->targetFile().string()); +			if(source.toolchain == ctor::toolchain::any || +			   (config.system == ctor::output_system::build && source.toolchain == c.build_toolchain) || +			   (config.system == ctor::output_system::host && source.toolchain == c.host_toolchain)) +			{ +				auto task = std::make_shared<TaskCC>(config, settings, sourceDir, source); +				tasks.push_back(task); +				objects.push_back(task->targetFile().string()); +			}  		}  	}  #ifndef BOOTSTRAP @@ -128,7 +117,7 @@ std::set<std::shared_ptr<Task>> taskFactory(const ctor::build_configuration& con  		for(const auto& file : config.sources)  		{  			auto task = std::make_shared<TaskFn>(config, settings, sourceDir, file); -			tasks.insert(task); +			tasks.push_back(task);  			objects.push_back(task->target());  		}  	} @@ -140,10 +129,16 @@ std::set<std::shared_ptr<Task>> taskFactory(const ctor::build_configuration& con  		// The target_type cannot be Auto  		break; +	case ctor::target_type::unknown: +		std::cerr << "Could not deduce target type from target " << +			targetFile.string() << " please specify.\n"; +		exit(1); +		break; +  	case ctor::target_type::static_library:  	case ctor::target_type::unit_test_library: -		tasks.insert(std::make_shared<TaskAR>(config, settings, config.target, -		                                      objects, sourceDir)); +		tasks.push_back(std::make_shared<TaskAR>(config, settings, config.target, +		                                         objects, sourceDir));  		break;  #ifndef BOOTSTRAP  	case ctor::target_type::dynamic_library: @@ -153,14 +148,14 @@ std::set<std::shared_ptr<Task>> taskFactory(const ctor::build_configuration& con  			std::cerr << "Dynamic library target must have 'lib' prefix\n";  			exit(1);  		} -		tasks.insert(std::make_shared<TaskSO>(config, settings, config.target, -		                                      objects, sourceDir)); +		tasks.push_back(std::make_shared<TaskSO>(config, settings, config.target, +		                                         objects, sourceDir));  		break;  	case ctor::target_type::executable:  	case ctor::target_type::unit_test: -		tasks.insert(std::make_shared<TaskLD>(config, settings, config.target, -		                                      objects, sourceDir)); +		tasks.push_back(std::make_shared<TaskLD>(config, settings, config.target, +		                                         objects, sourceDir, is_self));  		break;  	case ctor::target_type::object: @@ -175,18 +170,19 @@ std::set<std::shared_ptr<Task>> taskFactory(const ctor::build_configuration& con  	return tasks;  } -std::shared_ptr<Task> getNextTask(const std::set<std::shared_ptr<Task>>& allTasks, -                                  std::set<std::shared_ptr<Task>>& dirtyTasks) +std::shared_ptr<Task> getNextTask([[maybe_unused]]const std::vector<std::shared_ptr<Task>>& allTasks, +                                  std::vector<std::shared_ptr<Task>>& dirtyTasks)  {  	for(auto dirtyTask = dirtyTasks.begin();  	    dirtyTask != dirtyTasks.end();  	    ++dirtyTask)  	{ +		auto task = *dirtyTask;  		//std::cout << "Examining target " << (*dirtyTask)->target() << "\n"; -		if((*dirtyTask)->ready()) +		if(task->ready())  		{  			dirtyTasks.erase(dirtyTask); -			return *dirtyTask; +			return task;  		}  	} @@ -194,20 +190,20 @@ std::shared_ptr<Task> getNextTask(const std::set<std::shared_ptr<Task>>& allTask  	return nullptr;  } -std::set<std::shared_ptr<Task>> getTasks(const ctor::settings& settings, -                                         const std::vector<std::string> names, -                                         bool resolve_externals) +std::vector<std::shared_ptr<Task>> getTasks(const ctor::settings& settings, +                                            const std::vector<std::string> names, +                                            bool resolve_externals)  {  	auto& targets = getTargets(settings, resolve_externals); -	std::set<std::shared_ptr<Task>> tasks; +	std::vector<std::shared_ptr<Task>> tasks;  	for(const auto& target : targets)  	{  		if(names.empty() ||  		   std::find(std::begin(names), std::end(names), target.config.target) != std::end(names))  		{  			std::vector<std::string> objects; -			auto t = taskFactory(target.config, settings, target.path); -			tasks.insert(t.begin(), t.end()); +			auto t = taskFactory(target.config, settings, target.path, false); +			tasks.insert(tasks.end(), t.begin(), t.end());  		}  	} diff --git a/src/tasks.h b/src/tasks.h index ed1bf60..6573784 100644 --- a/src/tasks.h +++ b/src/tasks.h @@ -4,7 +4,6 @@  #pragma once  #include <string> -#include <set>  #include <memory>  #include <deque> @@ -24,17 +23,18 @@ const std::deque<Target>& getTargets(const ctor::settings& settings,  //! 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::set<std::shared_ptr<Task>>& allTasks, -                                  std::set<std::shared_ptr<Task>>& dirtyTasks); +std::shared_ptr<Task> getNextTask(const std::vector<std::shared_ptr<Task>>& allTasks, +                                  std::vector<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::set<std::shared_ptr<Task>> getTasks(const ctor::settings& settings, -                                         const std::vector<std::string> names = {}, -                                         bool resolve_externals = true); +std::vector<std::shared_ptr<Task>> getTasks(const ctor::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 ctor::build_configuration& config, -                                            const ctor::settings& settings, -                                            const std::string& sourceDir); +std::vector<std::shared_ptr<Task>> taskFactory(const ctor::build_configuration& config, +                                               const ctor::settings& settings, +                                               const std::string& sourceDir, +                                               bool is_self); diff --git a/src/tools.cc b/src/tools.cc index 476386b..8c9b7e2 100644 --- a/src/tools.cc +++ b/src/tools.cc @@ -6,8 +6,12 @@  #include <filesystem>  #include <iostream>  #include <sstream> +#include <array>  #include <cassert> +#include <cstdio> + +#include "util.h"  std::ostream& operator<<(std::ostream& stream, const ctor::c_opt& opt)  { @@ -17,6 +21,9 @@ std::ostream& operator<<(std::ostream& stream, const ctor::c_opt& opt)  	case ctor::c_opt::output: stream << "ctor::c_opt::output"; break;  	case ctor::c_opt::debug: stream << "ctor::c_opt::debug"; break;  	case ctor::c_opt::warn_all: stream << "ctor::c_opt::warn_all"; break; +	case ctor::c_opt::warn_conversion: stream << "ctor::c_opt::warn_conversion"; break; +	case ctor::c_opt::warn_shadow: stream << "ctor::c_opt::warn_shadow"; break; +	case ctor::c_opt::warn_extra: stream << "ctor::c_opt::warn_extra"; break;  	case ctor::c_opt::warnings_as_errors: stream << "ctor::c_opt::warnings_as_errors"; break;  	case ctor::c_opt::generate_dep_tree: stream << "ctor::c_opt::generate_dep_tree"; break;  	case ctor::c_opt::no_link: stream << "ctor::c_opt::no_link"; break; @@ -25,6 +32,7 @@ std::ostream& operator<<(std::ostream& stream, const ctor::c_opt& opt)  	case ctor::c_opt::optimization: stream << "ctor::c_opt::optimization"; break;  	case ctor::c_opt::position_independent_code: stream << "ctor::c_opt::position_independent_code"; break;  	case ctor::c_opt::position_independent_executable: stream << "ctor::c_opt::position_independent_executable"; break; +	case ctor::c_opt::define: stream << "ctor::c_opt::define"; break;  	case ctor::c_opt::custom: stream << "ctor::c_opt::custom"; break;  	} @@ -39,6 +47,9 @@ std::ostream& operator<<(std::ostream& stream, const ctor::cxx_opt& opt)  	case ctor::cxx_opt::output: stream << "ctor::cxx_opt::output"; break;  	case ctor::cxx_opt::debug: stream << "ctor::cxx_opt::debug"; break;  	case ctor::cxx_opt::warn_all: stream << "ctor::cxx_opt::warn_all"; break; +	case ctor::cxx_opt::warn_conversion: stream << "ctor::cxx_opt::warn_conversion"; break; +	case ctor::cxx_opt::warn_shadow: stream << "ctor::cxx_opt::warn_shadow"; break; +	case ctor::cxx_opt::warn_extra: stream << "ctor::cxx_opt::warn_extra"; break;  	case ctor::cxx_opt::warnings_as_errors: stream << "ctor::cxx_opt::warnings_as_errors"; break;  	case ctor::cxx_opt::generate_dep_tree: stream << "ctor::cxx_opt::generate_dep_tree"; break;  	case ctor::cxx_opt::no_link: stream << "ctor::cxx_opt::no_link"; break; @@ -47,6 +58,7 @@ std::ostream& operator<<(std::ostream& stream, const ctor::cxx_opt& opt)  	case ctor::cxx_opt::optimization: stream << "ctor::cxx_opt::optimization"; break;  	case ctor::cxx_opt::position_independent_code: stream << "ctor::cxx_opt::position_independent_code"; break;  	case ctor::cxx_opt::position_independent_executable: stream << "ctor::cxx_opt::position_independent_executable"; break; +	case ctor::cxx_opt::define: stream << "ctor::cxx_opt::define"; break;  	case ctor::cxx_opt::custom: stream << "ctor::cxx_opt::custom"; break;  	} @@ -80,6 +92,10 @@ std::ostream& operator<<(std::ostream& stream, const ctor::ar_opt& opt)  	// Adding to this enum should also imply adding to the unit-tests below  	switch(opt)  	{ +	case ctor::ar_opt::replace: stream << "ctor::ar_opt::replace"; break; +	case ctor::ar_opt::add_index: stream << "ctor::ar_opt::add_index"; break; +	case ctor::ar_opt::create: stream << "ctor::ar_opt::create"; break; +	case ctor::ar_opt::output: stream << "ctor::ar_opt::output"; break;  	case ctor::ar_opt::custom: stream << "ctor::ar_opt::custom"; break;  	} @@ -103,11 +119,14 @@ ctor::toolchain getToolChain(const std::string& 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) +	if(cc_cmd.find("clang++") != std::string::npos || +	   cc_cmd.find("clang") != std::string::npos)  	{  		return ctor::toolchain::clang;  	} -	else if(cc_cmd.find("g++") != std::string::npos) +	else if(cc_cmd.find("g++") != std::string::npos || +	        cc_cmd.find("gcc") != std::string::npos || +	        cc_cmd.find("mingw") != std::string::npos)  	{  		return ctor::toolchain::gcc;  	} @@ -138,43 +157,253 @@ ctor::toolchain getToolChain(ctor::output_system system)  }  namespace gcc { +std::string get_arch(ctor::output_system system) +{ +	std::string cmd; + +	const auto& c = ctor::get_configuration(); +	switch(system) +	{ +	case ctor::output_system::host: +		cmd = c.get(ctor::cfg::host_cxx, "/usr/bin/g++"); +		break; +	case ctor::output_system::build: +		cmd = c.get(ctor::cfg::build_cxx, "/usr/bin/g++"); +		break; +	} + +	cmd += " -v"; +	cmd += " 2>&1"; +	auto pipe = popen(cmd.data(), "r"); +	if(!pipe) +	{ +		std::cerr << "Could not run compiler " << cmd << "\n"; +		return {};//ctor::arch::unknown; +	} + +	std::string arch; +	while(!feof(pipe)) +	{ +		constexpr auto buffer_size{1024}; +		std::array<char, buffer_size> buf{}; +		if(fgets(buf.data(), buf.size(), pipe) != nullptr) +		{ +			arch = buf.data(); +			if(arch.starts_with("Target:")) +			{ +				break; +			} +		} +	} + +	pclose(pipe); + +	// Remove trailing newline +	if(arch.ends_with("\n")) +	{ +		arch = arch.substr(0, arch.length() - 1); +	} + +	// Remove 'Target: ' prefix +	constexpr auto prefix_length{8}; +	arch = arch.substr(prefix_length); +	return arch; +} + +ctor::arch get_arch(const std::string& str) +{ +	// gcc -v 2>&1 | grep Target +	// Target: x86_64-apple-darwin19.6.0 +	// Target: x86_64-pc-linux-gnu +	// Target: arm-linux-gnueabihf +	// Target: x86_64-linux-gnu +	// Target: x86_64-unknown-freebsd13.1 +	// # x86_64-w64-mingw32-c++-win32 -v 2>&1 | grep Target +	// Target: x86_64-w64-mingw32 +	// # i686-w64-mingw32-c++-win32 -v 2>&1 | grep Target +	// Target: i686-w64-mingw32 + +	if(str.find("apple") != std::string::npos || +	   str.find("darwin") != std::string::npos) +	{ +		return ctor::arch::apple; +	} + +	if(str.find("linux") != std::string::npos || +	   str.find("bsd") != std::string::npos) +	{ +		return ctor::arch::unix; +	} + +	if(str.find("mingw") != std::string::npos) +	{ +		return ctor::arch::windows; +	} + +	std::cerr << "Could not deduce gcc arch from '" << str << "'" << std::endl; + +	return ctor::arch::unknown; +} +  ctor::c_flag c_option(const std::string& flag)  { -	if(flag.substr(0, 2) == "-I") +	if(flag.starts_with("-I"))  	{  		std::string path = flag.substr(2);  		path.erase(0, path.find_first_not_of(' '));  		return { ctor::c_opt::include_path, path };  	} +	if(flag.starts_with("-std=")) +	{ +		std::string std = flag.substr(5); +		return { ctor::c_opt::c_std, std }; +	} + +	if(flag.starts_with("-O")) +	{ +		std::string opt = flag.substr(2, 1); +		return { ctor::c_opt::optimization, opt }; +	} + +	if(flag.starts_with("-Wall")) +	{ +		return { ctor::c_opt::warn_all }; +	} + +	if(flag.starts_with("-Wconversion")) +	{ +		return { ctor::c_opt::warn_conversion}; +	} + +	if(flag.starts_with("-Wshadow")) +	{ +		return { ctor::c_opt::warn_shadow}; +	} + +	if(flag.starts_with("-Wextra")) +	{ +		return { ctor::c_opt::warn_extra}; +	} + +	if(flag.starts_with("-Werror")) +	{ +		return { ctor::c_opt::warnings_as_errors }; +	} + +	if(flag.starts_with("-g")) +	{ +		return { ctor::c_opt::debug }; +	} + +	if(flag.starts_with("-D")) +	{ +		std::string def = flag.substr(2); +		auto pos = def.find('='); +		if(pos != def.npos) +		{ +			return { ctor::c_opt::define, def.substr(0, pos), def.substr(pos + 1) }; +		} +		else +		{ +			return { ctor::c_opt::define, def }; +		} +	}  	return { ctor::c_opt::custom, flag };  }  ctor::cxx_flag cxx_option(const std::string& flag)  { -	if(flag.substr(0, 2) == "-I") +	if(flag.starts_with("-I"))  	{  		std::string path = flag.substr(2);  		path.erase(0, path.find_first_not_of(' '));  		return { ctor::cxx_opt::include_path, path };  	} +	if(flag.starts_with("-std=")) +	{ +		std::string std = flag.substr(5); +		return { ctor::cxx_opt::cpp_std, std }; +	} + +	if(flag.starts_with("-O")) +	{ +		std::string opt = flag.substr(2, 1); +		return { ctor::cxx_opt::optimization, opt }; +	} + +	if(flag.starts_with("-Wall")) +	{ +		return { ctor::cxx_opt::warn_all }; +	} + +	if(flag.starts_with("-Werror")) +	{ +		return { ctor::cxx_opt::warnings_as_errors }; +	} + +	if(flag.starts_with("-Wconversion")) +	{ +		return { ctor::cxx_opt::warn_conversion}; +	} + +	if(flag.starts_with("-Wshadow")) +	{ +		return { ctor::cxx_opt::warn_shadow}; +	} + +	if(flag.starts_with("-Wextra")) +	{ +		return { ctor::cxx_opt::warn_extra}; +	} + +	if(flag.starts_with("-g")) +	{ +		return { ctor::cxx_opt::debug }; +	} + +	if(flag.starts_with("-D")) +	{ +		std::string def = flag.substr(2); +		auto pos = def.find('='); +		if(pos != def.npos) +		{ +			return { ctor::cxx_opt::define, def.substr(0, pos), def.substr(pos + 1) }; +		} +		else +		{ +			return { ctor::cxx_opt::define, def }; +		} +	} +  	return { ctor::cxx_opt::custom, flag };  }  ctor::ld_flag ld_option(const std::string& flag)  { -	if(flag.substr(0, 2) == "-L") +	if(flag.starts_with("-L"))  	{  		std::string path = flag.substr(2);  		path.erase(0, path.find_first_not_of(' '));  		return { ctor::ld_opt::library_path, path };  	} +	if(flag.starts_with("-pthread")) +	{ +		return { ctor::ld_opt::threads }; +	} +  	return { ctor::ld_opt::custom, flag };  } -std::vector<std::string> cxx_option(ctor::cxx_opt opt, const std::string& arg) +ctor::ar_flag ar_option(const std::string& flag) +{ +	return { ctor::ar_opt::custom, flag }; +} + +std::vector<std::string> cxx_option(ctor::cxx_opt opt, const std::string& arg, +                                    const std::string& arg2)  {  	switch(opt)  	{ @@ -184,6 +413,12 @@ std::vector<std::string> cxx_option(ctor::cxx_opt opt, const std::string& arg)  		return {"-g"};  	case ctor::cxx_opt::warn_all:  		return {"-Wall"}; +	case ctor::cxx_opt::warn_conversion: +		return {"-Wconversion"}; +	case ctor::cxx_opt::warn_shadow: +		return {"-Wshadow"}; +	case ctor::cxx_opt::warn_extra: +		return {"-Wextra"};  	case ctor::cxx_opt::warnings_as_errors:  		return {"-Werror"};  	case ctor::cxx_opt::generate_dep_tree: @@ -200,6 +435,12 @@ std::vector<std::string> cxx_option(ctor::cxx_opt opt, const std::string& arg)  		return {"-fPIC"};  	case ctor::cxx_opt::position_independent_executable:  		return {"-fPIE"}; +	case ctor::cxx_opt::define: +		if(!arg2.empty()) +		{ +			return {"-D" + arg + "=" + arg2}; +		} +		return {"-D" + arg};  	case ctor::cxx_opt::custom:  		return {arg};  	} @@ -208,7 +449,8 @@ std::vector<std::string> cxx_option(ctor::cxx_opt opt, const std::string& arg)  	return {};  } -std::vector<std::string> c_option(ctor::c_opt opt, const std::string& arg) +std::vector<std::string> c_option(ctor::c_opt opt, const std::string& arg, +                                  const std::string& arg2)  {  	switch(opt)  	{ @@ -218,6 +460,12 @@ std::vector<std::string> c_option(ctor::c_opt opt, const std::string& arg)  		return {"-g"};  	case ctor::c_opt::warn_all:  		return {"-Wall"}; +	case ctor::c_opt::warn_conversion: +		return {"-Wconversion"}; +	case ctor::c_opt::warn_shadow: +		return {"-Wshadow"}; +	case ctor::c_opt::warn_extra: +		return {"-Wextra"};  	case ctor::c_opt::warnings_as_errors:  		return {"-Werror"};  	case ctor::c_opt::generate_dep_tree: @@ -234,6 +482,12 @@ std::vector<std::string> c_option(ctor::c_opt opt, const std::string& arg)  		return {"-fPIC"};  	case ctor::c_opt::position_independent_executable:  		return {"-fPIE"}; +	case ctor::c_opt::define: +		if(!arg2.empty()) +		{ +			return {"-D" + arg + "=" + arg2}; +		} +		return {"-D" + arg};  	case ctor::c_opt::custom:  		return {arg};  	} @@ -242,7 +496,8 @@ std::vector<std::string> c_option(ctor::c_opt opt, const std::string& arg)  	return {};  } -std::vector<std::string> ld_option(ctor::ld_opt opt, const std::string& arg) +std::vector<std::string> ld_option(ctor::ld_opt opt, const std::string& arg, +                                   [[maybe_unused]]const std::string& arg2)  {  	switch(opt)  	{ @@ -276,10 +531,19 @@ std::vector<std::string> ld_option(ctor::ld_opt opt, const std::string& arg)  	return {};  } -std::vector<std::string> ar_option(ctor::ar_opt opt, const std::string& arg) +std::vector<std::string> ar_option(ctor::ar_opt opt, const std::string& arg, +                                   [[maybe_unused]]const std::string& arg2)  {  	switch(opt)  	{ +	case ctor::ar_opt::replace: +		return {"-r"}; +	case ctor::ar_opt::add_index: +		return {"-s"}; +	case ctor::ar_opt::create: +		return {"-c"}; +	case ctor::ar_opt::output: +		return {arg};  	case ctor::ar_opt::custom:  		return {arg};  	} @@ -288,7 +552,8 @@ std::vector<std::string> ar_option(ctor::ar_opt opt, const std::string& arg)  	return {};  } -std::vector<std::string> asm_option(ctor::asm_opt opt, const std::string& arg) +std::vector<std::string> asm_option(ctor::asm_opt opt, const std::string& arg, +                                    [[maybe_unused]]const std::string& arg2)  {  	switch(opt)  	{ @@ -301,16 +566,46 @@ std::vector<std::string> asm_option(ctor::asm_opt opt, const std::string& arg)  }  } // gcc:: +std::string get_arch(ctor::output_system system) +{ +	auto toolchain = getToolChain(system); +	switch(toolchain) +	{ +	case ctor::toolchain::clang: +	case ctor::toolchain::gcc: +		return gcc::get_arch(system); +	case ctor::toolchain::any: +	case ctor::toolchain::none: +		break; +	} +	return {}; +} + +ctor::arch get_arch(ctor::output_system system, const std::string& str) +{ +	auto toolchain = getToolChain(system); +	switch(toolchain) +	{ +	case ctor::toolchain::clang: +	case ctor::toolchain::gcc: +		return gcc::get_arch(str); +	case ctor::toolchain::any: +	case ctor::toolchain::none: +		break; +	} +	return ctor::arch::unknown; +}  std::vector<std::string> c_option(ctor::toolchain toolchain,                                    ctor::c_opt opt, -                                  const std::string& arg) +                                  const std::string& arg, +                                  const std::string& arg2)  {  	switch(toolchain)  	{  	case ctor::toolchain::gcc:  	case ctor::toolchain::clang: -		return gcc::c_option(opt, arg); +		return gcc::c_option(opt, arg, arg2);  	case ctor::toolchain::any:  		{  			std::ostringstream ss; @@ -319,6 +614,10 @@ std::vector<std::string> c_option(ctor::toolchain toolchain,  			{  				ss << ", \"" << arg << "\"";  			} +			if(!arg2.empty()) +			{ +				ss << ", \"" << arg2 << "\""; +			}  			ss << "}";  			return { ss.str() };  		} @@ -332,13 +631,14 @@ std::vector<std::string> c_option(ctor::toolchain toolchain,  std::vector<std::string> cxx_option(ctor::toolchain toolchain,                                      ctor::cxx_opt opt, -                                    const std::string& arg) +                                    const std::string& arg, +                                    const std::string& arg2)  {  	switch(toolchain)  	{  	case ctor::toolchain::gcc:  	case ctor::toolchain::clang: -		return gcc::cxx_option(opt, arg); +		return gcc::cxx_option(opt, arg, arg2);  	case ctor::toolchain::any:  		{  			std::ostringstream ss; @@ -347,6 +647,10 @@ std::vector<std::string> cxx_option(ctor::toolchain toolchain,  			{  				ss << ", \"" << arg << "\"";  			} +			if(!arg2.empty()) +			{ +				ss << ", \"" << arg2 << "\""; +			}  			ss << "}";  			return { ss.str() };  		} @@ -360,13 +664,14 @@ std::vector<std::string> cxx_option(ctor::toolchain toolchain,  std::vector<std::string> ld_option(ctor::toolchain toolchain,                                     ctor::ld_opt opt, -                                   const std::string& arg) +                                   const std::string& arg, +                                   const std::string& arg2)  {  	switch(toolchain)  	{  	case ctor::toolchain::gcc:  	case ctor::toolchain::clang: -		return gcc::ld_option(opt, arg); +		return gcc::ld_option(opt, arg, arg2);  	case ctor::toolchain::any:  		{  			std::ostringstream ss; @@ -375,6 +680,10 @@ std::vector<std::string> ld_option(ctor::toolchain toolchain,  			{  				ss << ", \"" << arg << "\"";  			} +			if(!arg2.empty()) +			{ +				ss << ", \"" << arg2 << "\""; +			}  			ss << "}";  			return { ss.str() };  		} @@ -388,13 +697,14 @@ std::vector<std::string> ld_option(ctor::toolchain toolchain,  std::vector<std::string> ar_option(ctor::toolchain toolchain,                                     ctor::ar_opt opt, -                                   const std::string& arg) +                                   const std::string& arg, +                                   const std::string& arg2)  {  	switch(toolchain)  	{  	case ctor::toolchain::gcc:  	case ctor::toolchain::clang: -		return gcc::ar_option(opt, arg); +		return gcc::ar_option(opt, arg, arg2);  	case ctor::toolchain::any:  		{  			std::ostringstream ss; @@ -403,6 +713,10 @@ std::vector<std::string> ar_option(ctor::toolchain toolchain,  			{  				ss << ", \"" << arg << "\"";  			} +			if(!arg2.empty()) +			{ +				ss << ", \"" << arg2 << "\""; +			}  			ss << "}";  			return { ss.str() };  		} @@ -416,13 +730,14 @@ std::vector<std::string> ar_option(ctor::toolchain toolchain,  std::vector<std::string> asm_option(ctor::toolchain toolchain,                                      ctor::asm_opt opt, -                                    const std::string& arg) +                                    const std::string& arg, +                                    const std::string& arg2)  {  	switch(toolchain)  	{  	case ctor::toolchain::gcc:  	case ctor::toolchain::clang: -		return gcc::asm_option(opt, arg); +		return gcc::asm_option(opt, arg, arg2);  	case ctor::toolchain::any:  		{  			std::ostringstream ss; @@ -431,6 +746,10 @@ std::vector<std::string> asm_option(ctor::toolchain toolchain,  			{  				ss << ", \"" << arg << "\"";  			} +			if(!arg2.empty()) +			{ +				ss << ", \"" << arg2 << "\""; +			}  			ss << "}";  			return { ss.str() };  		} @@ -494,6 +813,7 @@ ctor::ar_flag ar_option(const std::string& flag, ctor::toolchain toolchain)  	{  	case ctor::toolchain::gcc:  	case ctor::toolchain::clang: +		return gcc::ar_option(flag);  	case ctor::toolchain::any:  	case ctor::toolchain::none:  		break; @@ -524,7 +844,7 @@ std::vector<std::string> to_strings(ctor::toolchain toolchain,  	if(flag.toolchain == ctor::toolchain::any ||  	   flag.toolchain == toolchain)  	{ -		return c_option(toolchain, flag.opt, flag.arg); +		return c_option(toolchain, flag.opt, flag.arg, flag.arg2);  	}  	return {}; @@ -536,7 +856,7 @@ std::vector<std::string> to_strings(ctor::toolchain toolchain,  	if(flag.toolchain == ctor::toolchain::any ||  	   flag.toolchain == toolchain)  	{ -		return cxx_option(toolchain, flag.opt, flag.arg); +		return cxx_option(toolchain, flag.opt, flag.arg, flag.arg2);  	}  	return {}; @@ -548,7 +868,7 @@ std::vector<std::string> to_strings(ctor::toolchain toolchain,  	if(flag.toolchain == ctor::toolchain::any ||  	   flag.toolchain == toolchain)  	{ -		return ld_option(toolchain, flag.opt, flag.arg); +		return ld_option(toolchain, flag.opt, flag.arg, flag.arg2);  	}  	return {}; @@ -560,7 +880,7 @@ std::vector<std::string> to_strings(ctor::toolchain toolchain,  	if(flag.toolchain == ctor::toolchain::any ||  	   flag.toolchain == toolchain)  	{ -		return ar_option(toolchain, flag.opt, flag.arg); +		return ar_option(toolchain, flag.opt, flag.arg, flag.arg2);  	}  	return {}; @@ -572,7 +892,7 @@ std::vector<std::string> to_strings(ctor::toolchain toolchain,  	if(flag.toolchain == ctor::toolchain::any ||  	   flag.toolchain == toolchain)  	{ -		return asm_option(toolchain, flag.opt, flag.arg); +		return asm_option(toolchain, flag.opt, flag.arg, flag.arg2);  	}  	return {}; @@ -628,3 +948,176 @@ ctor::flag<ctor::asm_opt>::flag(const char* str)  {  	*this = asm_option(str, guess_toolchain(str));  } + + +ctor::target_type target_type_from_extension(ctor::toolchain toolchain, +                                             const std::filesystem::path& file) +{ +	auto ext = to_lower(file.extension().string()); +	// Loosely based on: +	// https://en.wikipedia.org/wiki/List_of_file_formats#Object_code,_executable_files,_shared_and_dynamically_linked_libraries +	if(toolchain == ctor::toolchain::any || +	   toolchain == ctor::toolchain::gcc || +	   toolchain == ctor::toolchain::clang) +	{ +		if(ext == ".a") +		{ +			return ctor::target_type::static_library; +		} + +		if(ext == ".so" || +		   ext == ".dylib") +		{ +			return ctor::target_type::dynamic_library; +		} + +		if(ext == ".o") +		{ +			return ctor::target_type::object; +		} + +		if(ext == "" || +		   ext == ".bin" || +		   ext == ".run" || +		   ext == ".out") +		{ +			return ctor::target_type::executable; +		} +	} + +	if(toolchain == ctor::toolchain::any// || +	   //toolchain == ctor::toolchain::msvc || +	   //toolchain == ctor::toolchain::mingw || +		) +	{ +		if(ext == ".lib") +		{ +			return ctor::target_type::static_library; +		} + +		if(ext == ".dll") +		{ +			return ctor::target_type::dynamic_library; +		} + +		if(ext == ".obj") +		{ +			return ctor::target_type::object; +		} + +		if(ext == ".exe" || +		   ext == ".com") +		{ +			return ctor::target_type::executable; +		} +	} + +	return ctor::target_type::unknown; +} + + +std::filesystem::path extension(ctor::toolchain toolchain, +                                ctor::target_type target_type, +                                ctor::output_system system, +                                const std::filesystem::path& file) +{ +	auto type = target_type_from_extension(toolchain, file); +	if(type == target_type) +	{ +		// File already has the correct extension +		return file; +	} + +	const auto& c = ctor::get_configuration(); +	ctor::arch arch{}; +	switch(system) +	{ +	case ctor::output_system::host: +		arch = c.host_arch; +		break; +	case ctor::output_system::build: +		arch = c.build_arch; +		break; +	} + +	// This might be before configure - so detection is needed for boostrap +	if(arch == ctor::arch::unknown) +	{ +		arch = get_arch(system, get_arch(system)); +	} + +	std::string ext{file.extension().string()}; +	switch(target_type) +	{ +	case ctor::target_type::automatic: +		break; +	case ctor::target_type::executable: +	case ctor::target_type::unit_test: +		switch(arch) +		{ +		case ctor::arch::unix: +		case ctor::arch::apple: +			ext = ""; +			break; +		case ctor::arch::windows: +			ext = ".exe"; +			break; +		case ctor::arch::unknown: +			break; +		} +		break; +	case ctor::target_type::static_library: +	case ctor::target_type::unit_test_library: +		switch(arch) +		{ +		case ctor::arch::unix: +		case ctor::arch::apple: +			ext = ".a"; +			break; +		case ctor::arch::windows: +			ext = ".lib"; +			break; +		case ctor::arch::unknown: +			break; +		} +		break; +	case ctor::target_type::dynamic_library: +		switch(arch) +		{ +		case ctor::arch::unix: +			ext = ".so"; +			break; +		case ctor::arch::apple: +			ext = ".dylib"; +			break; +		case ctor::arch::windows: +			ext = ".dll"; +			break; +		case ctor::arch::unknown: +			break; +		} +		break; +	case ctor::target_type::object: +		switch(arch) +		{ +		case ctor::arch::unix: +		case ctor::arch::apple: +			ext = ".o"; +			break; +		case ctor::arch::windows: +			ext = ".obj"; +			break; +		case ctor::arch::unknown: +			break; +		} +		break; +	case ctor::target_type::function: +		break; +	case ctor::target_type::unknown: +		break; +	} + +	auto output{file}; +	output.replace_extension(ext); +	return output; +} diff --git a/src/tools.h b/src/tools.h index b1285ec..0e7fc15 100644 --- a/src/tools.h +++ b/src/tools.h @@ -6,6 +6,7 @@  #include <vector>  #include <string>  #include <ostream> +#include <filesystem>  #include "ctor.h" @@ -16,6 +17,8 @@ std::ostream& operator<<(std::ostream& stream, const ctor::ld_opt& opt);  std::ostream& operator<<(std::ostream& stream, const ctor::ar_opt& opt);  std::ostream& operator<<(std::ostream& stream, const ctor::asm_opt& opt); +std::string get_arch(ctor::output_system system); +ctor::arch get_arch(ctor::output_system system, const std::string& str);  //! Get tool-chain type from compiler path string  ctor::toolchain getToolChain(const std::string& compiler); @@ -29,31 +32,36 @@ ctor::toolchain getToolChain(ctor::output_system system);  //! tool-chain  std::vector<std::string> c_option(ctor::toolchain toolchain,                                    ctor::c_opt option, -                                  const std::string& arg = {}); +                                  const std::string& arg = {}, +                                  const std::string& arg2 = {});  //! Get tool argument(s) for specific option type matching the supplied  //! tool-chain  std::vector<std::string> cxx_option(ctor::toolchain toolchain,                                      ctor::cxx_opt option, -                                    const std::string& arg = {}); +                                    const std::string& arg = {}, +                                    const std::string& arg2 = {});  //! Get tool argument(s) for specific option type matching the supplied  //! tool-chain  std::vector<std::string> ld_option(ctor::toolchain toolchain,                                     ctor::ld_opt option, -                                   const std::string& arg = {}); +                                   const std::string& arg = {}, +                                   const std::string& arg2 = {});  //! Get tool argument(s) for specific option type matching the supplied  //! tool-chain  std::vector<std::string> ar_option(ctor::toolchain toolchain,                                     ctor::ar_opt option, -                                   const std::string& arg = {}); +                                   const std::string& arg = {}, +                                   const std::string& arg2 = {});  //! Get tool argument(s) for specific option type matching the supplied  //! tool-chain  std::vector<std::string> asm_option(ctor::toolchain toolchain,                                      ctor::asm_opt option, -                                    const std::string& arg = {}); +                                    const std::string& arg = {}, +                                    const std::string& arg2 = {}); @@ -94,3 +102,28 @@ std::vector<std::string> to_strings(ctor::toolchain toolchain,                                      const ctor::ar_flag& flag);  std::vector<std::string> to_strings(ctor::toolchain toolchain,                                      const ctor::asm_flag& flag); + + +// Get target type from file extension +// If toolchain is not ::any only extensions for that toolchain will be accepted +// If no match is found ::unknown will be returned. +ctor::target_type target_type_from_extension(ctor::toolchain toolchain, +                                             const std::filesystem::path& file); + + +// Get appropriate extension from original extension and target type using +// the toolchain. +// ie. { gcc, static_lib, ".lib" } will return ".a" +//     { msvc, dynamic_lib, ".dylib" } will return ".dll" +//       ... +// If the supplied extension is normal for the supplied type and toolchain, then this is used, otherwise a conversion to the default extension to the given toolchain and type is given. +// The defaults for the toolchains are as follows: +// toolchain   executable static-lib dynamic-lib +//   gcc         (none)       .a        .so(.dylib on macos) +//   clang       (none)       .a        .so(.dylib on macos) +//   msvc        .exe         .lib      .dll +//   mingw       .exe         .lib      .dll +std::filesystem::path extension(ctor::toolchain toolchain, +                                ctor::target_type target_type, +                                ctor::output_system system, +                                const std::filesystem::path& file); diff --git a/src/unittest.cc b/src/unittest.cc index 237b2e3..b95a931 100644 --- a/src/unittest.cc +++ b/src/unittest.cc @@ -8,7 +8,7 @@  #include "execute.h"  #include "task.h" -int runUnitTests(std::set<std::shared_ptr<Task>>& tasks, +int runUnitTests(std::vector<std::shared_ptr<Task>>& tasks,                   const ctor::settings& settings)  {  	bool ok{true}; @@ -24,7 +24,7 @@ int runUnitTests(std::set<std::shared_ptr<Task>>& tasks,  				name = task->target();  			}  			std::cout << name << ": " << std::flush; -			auto ret = execute(task->targetFile(), {}, settings.verbose > 0); +			auto ret = execute(settings, task->targetFile().string(), {}, {});  			ok &= ret == 0;  			if(ret == 0)  			{ diff --git a/src/unittest.h b/src/unittest.h index 2880319..6d1385e 100644 --- a/src/unittest.h +++ b/src/unittest.h @@ -3,7 +3,7 @@  // See accompanying file LICENSE for details.  #pragma once -#include <set> +#include <vector>  #include <memory>  class Task; @@ -12,5 +12,5 @@ namespace ctor {  struct settings;  } // namespace ctor:: -int runUnitTests(std::set<std::shared_ptr<Task>>& tasks, +int runUnitTests(std::vector<std::shared_ptr<Task>>& tasks,                   const ctor::settings& settings); diff --git a/src/util.cc b/src/util.cc index ee56ede..dbd4c3c 100644 --- a/src/util.cc +++ b/src/util.cc @@ -5,6 +5,14 @@  #include <iostream>  #include <fstream> +#include <algorithm> + +std::string to_lower(const std::string& str) +{ +	std::string out{str}; +	std::transform(out.begin(), out.end(), out.begin(), ::tolower); +	return out; +}  std::string readFile(const std::string& fileName)  { @@ -14,84 +22,32 @@ std::string readFile(const std::string& fileName)  	std::ifstream::pos_type fileSize = ifs.tellg();  	ifs.seekg(0, std::ios::beg); -	std::vector<char> bytes(fileSize); +	std::vector<char> bytes(static_cast<std::size_t>(fileSize));  	ifs.read(bytes.data(), fileSize); -	return std::string(bytes.data(), fileSize); +	return {bytes.data(), static_cast<std::size_t>(fileSize)};  } -std::vector<std::string> readDeps(const std::string& depFile) +ctor::language languageFromExtension(const std::filesystem::path& file)  { -	if(!std::filesystem::exists(depFile)) -	{ -		return {}; -	} - -	auto str = readFile(depFile); +	auto ext = file.extension().string(); -	std::vector<std::string> output; -	std::string tmp; -	bool start{false}; -	bool in_whitespace{false}; -	for(const auto& c : str) +	// First a few case sensitive comparisons +	if(ext == ".c")  	{ -		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; -		} +		return ctor::language::c;  	} -	if(!tmp.empty()) +	if(ext == ".C")  	{ -		output.push_back(tmp); +		return ctor::language::cpp;  	} -	return output; -} +	// The rest are compared in lowercase +	ext = to_lower(ext); -ctor::language languageFromExtension(const std::filesystem::path& file) -{ -	auto ext = file.extension().string(); -	if(ext == ".c") -	{ -		return ctor::language::c; -	} - -	if(ext == ".C" || -	   ext == ".cc" || +	if(ext == ".cc" ||  	   ext == ".cpp" || -	   ext == ".CPP" ||  	   ext == ".c++" ||  	   ext == ".cp" ||  	   ext == ".cxx") @@ -100,7 +56,6 @@ ctor::language languageFromExtension(const std::filesystem::path& file)  	}  	if(ext == ".s" || -	   ext == ".S" ||  	   ext == ".asm")  	{  		return ctor::language::assembler; @@ -135,3 +90,97 @@ std::string cleanUp(const std::string& path)  	}  	return cleaned;  } + +std::string esc(const std::string& in) +{ +	std::string out; +	for(auto c : in) +	{ +		switch(c) +		{ +		case '\\': out += "\\\\"; break; +		case '"': out += "\\\""; break; +		default: +			out += c; +			break; +		} +	} +	return out; +} + +std::vector<std::string> get_paths(const std::string& path_env) +{ +	std::vector<std::string> paths; + +#ifdef _WIN32 +	const char sep{';'}; +#else +	const char sep{':'}; +#endif + +	std::stringstream ss(path_env); +	std::string path; +	while (std::getline(ss, path, sep)) +	{ +		paths.push_back(path); +	} + +	return paths; +} + +bool check_executable(const std::filesystem::path& prog) +{ +	auto perms = std::filesystem::status(prog).permissions(); + +	if((perms & std::filesystem::perms::owner_exec) != std::filesystem::perms::none) +	{ +		return true; +	} + +	if((perms & std::filesystem::perms::group_exec) != std::filesystem::perms::none) +	{ +		return true; +	} + +	if((perms & std::filesystem::perms::others_exec) != std::filesystem::perms::none) +	{ +		return true; +	} + +	return false; +} + +std::string locate(const std::string& prog, +                   const std::vector<std::string>& paths, +                   const std::string& arch) +{ +	std::string program = prog; +	if(!arch.empty()) +	{ +		program = arch + "-" + prog; +	} + +	// first check if arch contains an absolute path to prog +	if(std::filesystem::exists(program)) +	{ +		if(check_executable(program)) +		{ +			return program; +		} +	} + +	for(const auto& path_str : paths) +	{ +		std::filesystem::path path(path_str); +		auto prog_path = path / program; +		if(std::filesystem::exists(prog_path)) +		{ +			if(check_executable(prog_path)) +			{ +				return prog_path.string(); +			} +		} +	} + +	return {}; +} @@ -7,9 +7,11 @@  #include <string>  #include <filesystem> +#include <cstdlib> + +std::string to_lower(const std::string& str);  std::string readFile(const std::string& fileName); -std::vector<std::string> readDeps(const std::string& depFile);  ctor::language languageFromExtension(const std::filesystem::path& file);  std::string cleanUp(const std::string& path); @@ -19,7 +21,10 @@ void append(T& a, const T& b)  	a.insert(a.end(), b.begin(), b.end());  } -//using cxx_flags = std::vector<ctor::cxx_flag>; -//using c_flags = std::vector<std::string>; -//using ld_flags= std::vector<std::string>; -//using asm_flags = std::vector<std::string>; +std::string esc(const std::string& in); + +std::vector<std::string> get_paths(const std::string& path_env = std::getenv("PATH")); + +std::string locate(const std::string& app, +                   const std::vector<std::string>& paths, +                   const std::string& arch = {}); diff --git a/test/ctor.cc b/test/ctor.cc index a2ad64d..31c63ab 100644 --- a/test/ctor.cc +++ b/test/ctor.cc @@ -11,12 +11,65 @@ ctor::build_configurations ctorTestConfigs(const ctor::settings& settings)  	{  		{  			.type = ctor::target_type::unit_test, +			.system = ctor::output_system::build, +			.target = "pointerlist_test", +			.sources = { +				"pointerlist_test.cc", +				"testmain.cc", +				"../src/pointerlist.cc", +			}, +			.flags = { +				.cxxflags = { +					"-std=c++20", "-O3", "-Wall", "-Werror", +					"-I../src", "-Iuunit", +					"-DOUTPUT=\"pointerlist\"", +				}, +			}, +		}, +		{ +			.type = ctor::target_type::unit_test, +			.system = ctor::output_system::build, +			.target = "deps_test", +			.sources = { +				"deps_test.cc", +				"testmain.cc", +				"../src/deps.cc", +				"../src/util.cc", +			}, +			.depends = { "testprog", }, +			.flags = { +				.cxxflags = { +					"-std=c++20", "-O3", "-Wall", "-Werror", +					"-I../src", "-Iuunit", +					"-DOUTPUT=\"deps\"", +				}, +			}, +		}, +		{ +			.type = ctor::target_type::unit_test, +			.system = ctor::output_system::build, +			.target = "testprog", +			.sources = { +				"testprog.cc", +			}, +			.flags = { +				.cxxflags = { +					"-std=c++20", "-O3", "-Wall", "-Werror", +				}, +			}, +		}, +		{ +			.type = ctor::target_type::unit_test, +			.system = ctor::output_system::build,  			.target = "execute_test",  			.sources = {  				"execute_test.cc",  				"testmain.cc",  				"../src/execute.cc", +				"../src/pointerlist.cc", +				"../src/util.cc",  			}, +			.depends = { "testprog", },  			.flags = {  				.cxxflags = {  					"-std=c++20", "-O3", "-Wall", "-Werror", @@ -28,6 +81,7 @@ ctor::build_configurations ctorTestConfigs(const ctor::settings& settings)  		},  		{  			.type = ctor::target_type::unit_test, +			.system = ctor::output_system::build,  			.target = "tasks_test",  			.sources = {  				"tasks_test.cc", @@ -45,6 +99,25 @@ ctor::build_configurations ctorTestConfigs(const ctor::settings& settings)  		},  		{  			.type = ctor::target_type::unit_test, +			.system = ctor::output_system::build, +			.target = "cycle_test", +			.sources = { +				"cycle_test.cc", +				"testmain.cc", +			}, +			.depends = { "libctor_nomain.a" }, +			.flags = { +				.cxxflags = { +					"-std=c++20", "-O3", "-Wall", "-Werror", +					"-I../src", "-Iuunit", +					"-DOUTPUT=\"cycle\"", +				}, +				.ldflags = { "-pthread" }, +			}, +		}, +		{ +			.type = ctor::target_type::unit_test, +			.system = ctor::output_system::build,  			.target = "source_type_test",  			.sources = {  				"source_type_test.cc", @@ -62,10 +135,12 @@ ctor::build_configurations ctorTestConfigs(const ctor::settings& settings)  		},  		{  			.type = ctor::target_type::unit_test, +			.system = ctor::output_system::build,  			.target = "tools_test",  			.sources = {  				"tools_test.cc",  				"testmain.cc", +				"../src/util.cc",  				"../src/tools.cc",  			},  			//.depends = { "libctor_nomain.a" }, @@ -79,11 +154,14 @@ ctor::build_configurations ctorTestConfigs(const ctor::settings& settings)  		},  		{  			.type = ctor::target_type::unit_test_library, +			.system = ctor::output_system::build,  			.target = "libctor_nomain.a",  			.sources = {  				"../src/build.cc",  				"../src/configure.cc", +				"../src/deps.cc",  				"../src/execute.cc", +				"../src/pointerlist.cc",  				"../src/rebuild.cc",  				"../src/tasks.cc",  				"../src/task.cc", diff --git a/test/cycle_test.cc b/test/cycle_test.cc new file mode 100644 index 0000000..3b45632 --- /dev/null +++ b/test/cycle_test.cc @@ -0,0 +1,87 @@ +#include <uunit.h> + +#include <ctor.h> +#include <build.h> + +namespace +{ +ctor::build_configurations ctorTestConfigsCyclic(const ctor::settings&) +{ +	return +	{ +		// No dependency +		{ +			.target = "target0", +		}, + +		// Direct (self-depends) +		{ +			.target = "target1", +			.depends = { "target1" }, +		}, + +		// Indirect cyclic depends +		{ +			.target = "target2", +			.depends = { "target3" }, +		}, +		{ +			.target = "target3", +			.depends = { "target2" }, +		}, +	}; +} +} + +REG(ctorTestConfigsCyclic); + +class CycleTest +	: public uUnit +{ +public: +	CycleTest() +	{ +		uTEST(CycleTest::getTargets_test); +	} + +	void getTargets_test() +	{ +		using namespace std::string_literals; +		ctor::settings settings{}; +		const auto& tasks = getTasks(settings); +		uASSERT_EQUAL(4u, tasks.size()); + +		uASSERT_EQUAL("target0"s, tasks[0]->target()); +		uASSERT_EQUAL("target1"s, tasks[1]->target()); +		uASSERT_EQUAL("target2"s, tasks[2]->target()); +		uASSERT_EQUAL("target3"s, tasks[3]->target()); + +		for(auto task : tasks) +		{ +			if(task->registerDepTasks(tasks)) +			{ +				uASSERT(false); +			} +		} + +		bool exc; +		exc = false; +		try { getDepTasks(tasks[0]); } catch(...) { exc = true; } +		uASSERT(!exc); + +		exc = false; +		try { getDepTasks(tasks[1]); } catch(...) { exc = true; } +		uASSERT(exc); + +		exc = false; +		try { getDepTasks(tasks[2]); } catch(...) { exc = true; } +		uASSERT(exc); + +		exc = false; +		try { getDepTasks(tasks[3]); } catch(...) { exc = true; } +		uASSERT(exc); +	} +}; + +// Registers the fixture into the 'registry' +static CycleTest test; diff --git a/test/deps_test.cc b/test/deps_test.cc new file mode 100644 index 0000000..762b0e5 --- /dev/null +++ b/test/deps_test.cc @@ -0,0 +1,97 @@ +#include <uunit.h> + +#include <fstream> +#include <map> +#include <vector> + +#include <deps.h> +#include <algorithm> + +#include "paths.h" +#include "tmpfile.h" + +class DepsTest +	: public uUnit +{ +public: +	DepsTest() +	{ +		uTEST(DepsTest::parser_test); +	} + +	void parser_test() +	{ +		using namespace std::string_literals; + +		auto test_data = paths::top_srcdir / "test" / "deps_test_data"; + +		auto empty =  test_data / "empty.d"; +		auto trivial = test_data / "trivial.d"; +		auto no_newline = test_data / "no_newline.d"; +		auto no_deps = test_data / "no_deps.d"; +		auto trailing_whitespace = test_data / "trailing_whitespace.d"; +		auto spaces = test_data / "spaces.d"; +		auto multiline = test_data / "multiline.d"; +		auto no_such_file = test_data / "no_such_file.d"; // doesn't exist +		auto missing_colon = test_data / "missing_colon.d"; + +		{ +			auto res = readDeps(empty.string(), ctor::toolchain::gcc); +			uASSERT(res.empty()); +		} + +		{ +			auto res = readDeps(trivial.string(), ctor::toolchain::gcc); +			uASSERT_EQUAL(1u, res.size()); +			uASSERT_EQUAL("x.cc"s, res[0]); +		} + +		{ +			auto res = readDeps(no_newline.string(), ctor::toolchain::gcc); +			uASSERT_EQUAL(1u, res.size()); +			uASSERT_EQUAL("x.cc"s, res[0]); +		} + +		{ +			auto res = readDeps(no_deps.string(), ctor::toolchain::gcc); +			uASSERT_EQUAL(0u, res.size()); +		} + +		{ +			auto res = readDeps(spaces.string(), ctor::toolchain::gcc); +			uASSERT_EQUAL(2u, res.size()); +			uASSERT_EQUAL("x y.cc"s, res[0]); +			uASSERT_EQUAL("x y.h"s, res[1]); +		} + +		{ +			auto res = readDeps(multiline.string(), ctor::toolchain::gcc); +			uASSERT_EQUAL(12u, res.size()); +			uASSERT_EQUAL("src/configure.cc"s, res[0]); +			uASSERT_EQUAL("src/configure.h"s, res[1]); +			uASSERT_EQUAL("src/getoptpp/getoptpp.hpp"s, res[2]); +			uASSERT_EQUAL("src/execute.h"s, res[3]); +			uASSERT_EQUAL("src/ctor.h"s, res[4]); +			uASSERT_EQUAL("src/tasks.h"s, res[5]); +			uASSERT_EQUAL("src/task.h"s, res[6]); +			uASSERT_EQUAL("src/rebuild.h"s, res[7]); +			uASSERT_EQUAL("src/externals.h"s, res[8]); +			uASSERT_EQUAL("src/externals_manual.h"s, res[9]); +			uASSERT_EQUAL("src/tools.h"s, res[10]); +			uASSERT_EQUAL("src/util.h"s, res[11]); +		} + +		{ +			auto res = readDeps(no_such_file.string(), ctor::toolchain::gcc); +			uASSERT(res.empty()); +		} + +		{ +			auto res = readDeps(missing_colon.string(), ctor::toolchain::gcc); +			uASSERT(res.empty()); +		} +	} +}; + +// Registers the fixture into the 'registry' +static DepsTest test; diff --git a/test/deps_test_data/empty.d b/test/deps_test_data/empty.d new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/test/deps_test_data/empty.d diff --git a/test/deps_test_data/missing_colon.d b/test/deps_test_data/missing_colon.d new file mode 100644 index 0000000..e46996c --- /dev/null +++ b/test/deps_test_data/missing_colon.d @@ -0,0 +1 @@ +x.cc x.h diff --git a/test/deps_test_data/multiline.d b/test/deps_test_data/multiline.d new file mode 100644 index 0000000..8862ab0 --- /dev/null +++ b/test/deps_test_data/multiline.d @@ -0,0 +1,4 @@ +build/src/libctor_a-configure_cc.o: src/configure.cc src/configure.h \ + src/getoptpp/getoptpp.hpp src/execute.h src/ctor.h src/tasks.h \ + src/task.h src/rebuild.h src/externals.h src/externals_manual.h \ + src/tools.h src/util.h diff --git a/test/deps_test_data/no_deps.d b/test/deps_test_data/no_deps.d new file mode 100644 index 0000000..e7cccf6 --- /dev/null +++ b/test/deps_test_data/no_deps.d @@ -0,0 +1 @@ +x.o:  diff --git a/test/deps_test_data/no_newline.d b/test/deps_test_data/no_newline.d new file mode 100644 index 0000000..88829ea --- /dev/null +++ b/test/deps_test_data/no_newline.d @@ -0,0 +1 @@ +x.o: x.cc
\ No newline at end of file diff --git a/test/deps_test_data/spaces.d b/test/deps_test_data/spaces.d new file mode 100644 index 0000000..c53fd64 --- /dev/null +++ b/test/deps_test_data/spaces.d @@ -0,0 +1 @@ +x\ y.o: x\ y.cc x\ y.h diff --git a/test/deps_test_data/trivial.d b/test/deps_test_data/trivial.d new file mode 100644 index 0000000..15a0c29 --- /dev/null +++ b/test/deps_test_data/trivial.d @@ -0,0 +1 @@ +x.o: x.cc diff --git a/test/execute_test.cc b/test/execute_test.cc index 9da18dc..722b6ea 100644 --- a/test/execute_test.cc +++ b/test/execute_test.cc @@ -1,6 +1,15 @@  #include <uunit.h> +#include <fstream> +#include <map> +#include <vector> +  #include <execute.h> +#include <util.h> +#include <algorithm> + +#include "paths.h" +#include "tmpfile.h"  class ExecuteTest  	: public uUnit @@ -8,14 +17,78 @@ class ExecuteTest  public:  	ExecuteTest()  	{ -		uTEST(ExecuteTest::runit); +		uTEST(ExecuteTest::return_value); +		uTEST(ExecuteTest::env); +	} + +	void return_value() +	{ +		ctor::settings s; +		auto cur_path = std::filesystem::path(paths::argv_0).parent_path(); +		std::vector<std::string> paths{{cur_path.string()}}; +		auto cmd = locate("testprog", paths); +		uASSERT(!cmd.empty()); + +		auto value = execute(s, cmd, {"retval", "0"}, {}, false); +		uASSERT_EQUAL(0, value); +		value = execute(s, cmd, {"retval", "1"}, {}, false); +		uASSERT_EQUAL(1, value); +		value = execute(s, "no-such-binary", {}, {}, false); +		uASSERT_EQUAL(1, value); +		value = execute(s, cmd, {"segfault"}, {}, false); +		uASSERT_EQUAL(11, value); +		value = execute(s, cmd, {"throw"}, {}, false); +		uASSERT_EQUAL(6, value); +		value = execute(s, cmd, {"abort"}, {}, false); +		uASSERT_EQUAL(6, value);  	} -	void runit() +	void env()  	{ -		uASSERT_EQUAL(0, execute("/bin/true", {}, false)); -		uASSERT_EQUAL(1, execute("/bin/false", {}, false)); -		uASSERT_EQUAL(1, execute("no-such-binary", {}, false)); +		using namespace std::string_literals; + +		ctor::settings s; +		auto cur_path = std::filesystem::path(paths::argv_0).parent_path(); +		std::vector<std::string> paths{{cur_path.string()}}; +		auto cmd = locate("testprog", paths); +		uASSERT(!cmd.empty()); + +		tmp_file tmp; + +		std::map<std::string, std::string> env; + +		// New env vars +		env["foo"] = "bar"; +		env["bar"] = "42"; + +		// Overwrite the exiting LANG var +		env["LANG"] = "foo"; + +		auto value = execute(s, cmd, {"envdump", tmp.get()}, env, false); +		uASSERT_EQUAL(0, value); + +		std::vector<std::string> vars; +		{ +			std::ifstream infile(tmp.get()); +			std::string line; +			while (std::getline(infile, line)) +			{ +				vars.push_back(line); +			} +		} + +		// Check the two explicitly set vars +		auto chk = std::find(vars.begin(), vars.end(), "foo=bar"s); +		uASSERT(chk != vars.end()); +		chk = std::find(vars.begin(), vars.end(), "bar=42"s); +		uASSERT(chk != vars.end()); + +		// Check the one that should have overwritten the existing one (probably LANG=en_US.UTF-8 or something) +		chk = std::find(vars.begin(), vars.end(), "LANG=foo"s); +		uASSERT(chk != vars.end()); + +		// Check that other vars are also there (ie. the env wasn't cleared on entry) +		uASSERT(vars.size() > 3);  	}  }; diff --git a/test/pointerlist_test.cc b/test/pointerlist_test.cc new file mode 100644 index 0000000..4274473 --- /dev/null +++ b/test/pointerlist_test.cc @@ -0,0 +1,320 @@ +// -*- c++ -*- +// Distributed under the BSD 2-Clause License. +// See accompanying file LICENSE for details. +#include <uunit.h> + +#include <set> +#include <algorithm> + +#include "pointerlist.h" + +class PointerListTest +	: public uUnit +{ +public: +	PointerListTest() +	{ +		uTEST(PointerListTest::test_zom_pointerlist_push); +		uTEST(PointerListTest::test_zom_pointerlist_from_args); +		uTEST(PointerListTest::test_zom_envmap_insert); +		uTEST(PointerListTest::test_zom_envmap_from_env); +		uTEST(PointerListTest::test_exceptional_env); +	} + +	void test_zom_pointerlist_push() +	{ +		using namespace std::string_literals; + +		{ // Zero +			PointerList args; +			uASSERT_EQUAL(0u, args.size()); +			auto [argc, argv] = args.get(); +			uASSERT_EQUAL(0, argc); +			uASSERT(nullptr != argv); +			uASSERT_EQUAL(nullptr, argv[0]); +		} + +		{ // One +			PointerList args; +			args.push_back("hello"); +			uASSERT_EQUAL(1u, args.size()); +			auto [argc, argv] = args.get(); +			uASSERT_EQUAL(1, argc); +			uASSERT_EQUAL("hello"s, std::string(argv[0])); +		} + +		{ // Many +			PointerList args; +			args.push_back("hello"); +			args.push_back("dear"); +			args.push_back("world"); +			uASSERT_EQUAL(3u, args.size()); +			auto [argc, argv] = args.get(); +			uASSERT_EQUAL(3, argc); +			uASSERT_EQUAL("hello"s, std::string(argv[0])); +			uASSERT_EQUAL("dear"s, std::string(argv[1])); +			uASSERT_EQUAL("world"s, std::string(argv[2])); +		} +	} + +	void test_zom_pointerlist_from_args() +	{ +		using namespace std::string_literals; + +		{ // Zero +			PointerList args(0, nullptr); +			uASSERT_EQUAL(0u, args.size()); +			auto [argc, argv] = args.get(); +			uASSERT_EQUAL(0, argc); +			uASSERT(nullptr != argv); +			uASSERT_EQUAL(nullptr, argv[0]); +		} + +		{ // One +			int _argc{1}; +			const char* _argv[] = { "hello" }; +			PointerList args(_argc, _argv); +			uASSERT_EQUAL(1u, args.size()); +			auto [argc, argv] = args.get(); +			uASSERT_EQUAL(1, argc); +			uASSERT_EQUAL("hello"s, std::string(argv[0])); +		} + +		{ // Many +			int _argc{3}; +			const char* _argv[] = { "hello", "dear", "world" }; +			PointerList args(_argc, _argv); +			uASSERT_EQUAL(3u, args.size()); +			auto [argc, argv] = args.get(); +			uASSERT_EQUAL(3, argc); +			// order must be preserved +			uASSERT_EQUAL("hello"s, std::string(argv[0])); +			uASSERT_EQUAL("dear"s, std::string(argv[1])); +			uASSERT_EQUAL("world"s, std::string(argv[2])); +		} +	} + +	void test_zom_envmap_insert() +	{ +		using namespace std::string_literals; + +		{ // Zero +			EnvMap env; +			uASSERT_EQUAL(0u, env.size()); + +			auto [argc, argv] = env.get(); +			uASSERT_EQUAL(0, argc); +			uASSERT(nullptr != argv); +			uASSERT_EQUAL(nullptr, argv[0]); + +			auto str = env.stringify(); +			uASSERT_EQUAL(1u, str.size()); +			uASSERT_EQUAL('\0', str[0]); +		} + +		{ // One (key only) +			EnvMap env; +			env.insert("hello"); +			uASSERT_EQUAL(1u, env.size()); +			auto [argc, argv] = env.get(); +			uASSERT_EQUAL(1, argc); +			uASSERT_EQUAL("hello="s, std::string(argv[0])); +			uASSERT_EQUAL(""s, env["hello"]); +		} +		{ // One (with value) +			EnvMap env; +			env.insert("hello=world"); +			uASSERT_EQUAL(1u, env.size()); +			auto [argc, argv] = env.get(); +			uASSERT_EQUAL(1, argc); +			uASSERT_EQUAL("hello=world"s, std::string(argv[0])); +			uASSERT_EQUAL("world"s, env["hello"]); + +			uASSERT_EQUAL("hello=world\0\0"s, env.stringify()); +		} + +		{ // Overwrite one +			EnvMap env; +			env.insert("hello=world"); +			uASSERT_EQUAL(1u, env.size()); +			uASSERT_EQUAL("world"s, env["hello"s]); + +			env.insert("hello=foo"); +			uASSERT_EQUAL(1u, env.size()); +			uASSERT_EQUAL("foo"s, env["hello"s]); +		} + +		{ // Many +			EnvMap env; +			env.insert("hello=world"); +			env.insert("world=leader"); +			env.insert("dear=boar"); +			uASSERT_EQUAL(3u, env.size()); + +			uASSERT_EQUAL("boar"s, env["dear"s]); +			uASSERT_EQUAL("world"s, env["hello"s]); +			uASSERT_EQUAL("leader"s, env["world"s]); + +			auto [argc, argv] = env.get(); +			uASSERT_EQUAL(3, argc); +			// store and sort to verify unordered +			std::vector<std::string> vals{argv[0], argv[1], argv[2]}; +			std::ranges::sort(vals); +			uASSERT_EQUAL("dear=boar"s, vals[0]); +			uASSERT_EQUAL("hello=world"s, vals[1]); +			uASSERT_EQUAL("world=leader"s, vals[2]); + +			// test all combinations since ordering is not a requirement +			// exactly one of the must be true (boolean XOR) +			auto str = env.stringify(); +			uASSERT(((((("dear=boar\0hello=world\0world=leader\0\0"s == str) != +			            ("dear=boar\0world=leader\0hello=world\0\0"s == str)) != +			           ("hello=world\0dear=boar\0world=leader\0\0"s == str)) != +			          ("hello=world\0world=leader\0dear=boar\0\0"s == str)) != +			         ("world=leader\0dear=boar\0hello=world\0\0"s == str)) != +			        ("world=leader\0hello=world\0dear=boar\0\0"s == str)); +		} +	} + +	void test_zom_envmap_from_env() +	{ +		using namespace std::string_literals; + +		{ // Zero +			const char* penv = nullptr; +			EnvMap env(penv); +			uASSERT_EQUAL(0u, env.size()); +			auto [argc, argv] = env.get(); +			uASSERT_EQUAL(0, argc); +			uASSERT(nullptr != argv); +			uASSERT_EQUAL(nullptr, argv[0]); +		} + +		{ // Zero +			const char** ppenv = nullptr; +			EnvMap env(ppenv); +			uASSERT_EQUAL(0u, env.size()); +			auto [argc, argv] = env.get(); +			uASSERT_EQUAL(0, argc); +			uASSERT(nullptr != argv); +			uASSERT_EQUAL(nullptr, argv[0]); +		} + +		{ // One (key only) +			const char* ptr = "hello\0\0"; +			EnvMap env(ptr); +			uASSERT_EQUAL(1u, env.size()); +			auto [argc, argv] = env.get(); +			uASSERT_EQUAL(1, argc); +			uASSERT_EQUAL("hello="s, std::string(argv[0])); +			uASSERT_EQUAL(""s, env["hello"]); +		} + +		{ // One (key only) +			const char* ptr[] = {"hello", nullptr}; +			EnvMap env(ptr); +			uASSERT_EQUAL(1u, env.size()); +			auto [argc, argv] = env.get(); +			uASSERT_EQUAL(1, argc); +			uASSERT_EQUAL("hello="s, std::string(argv[0])); +			uASSERT_EQUAL(""s, env["hello"]); +		} + +		{ // One (with value) +			const char* ptr = "hello=world\0\0"; +			EnvMap env(ptr); +			uASSERT_EQUAL(1u, env.size()); +			auto [argc, argv] = env.get(); +			uASSERT_EQUAL(1, argc); +			uASSERT_EQUAL("hello=world"s, std::string(argv[0])); +			uASSERT_EQUAL("world"s, env["hello"]); + +			uASSERT_EQUAL("hello=world\0\0"s, env.stringify()); +		} + +		{ // One (with value) +			const char* ptr[] = {"hello=world\0", nullptr}; +			EnvMap env(ptr); +			uASSERT_EQUAL(1u, env.size()); +			auto [argc, argv] = env.get(); +			uASSERT_EQUAL(1, argc); +			uASSERT_EQUAL("hello=world"s, std::string(argv[0])); +			uASSERT_EQUAL("world"s, env["hello"]); + +			uASSERT_EQUAL("hello=world\0\0"s, env.stringify()); +		} + +		{ // Many +			const char* ptr = "hello=world\0world=leader\0dear=boar\0\0"; +			EnvMap env(ptr); +			uASSERT_EQUAL(3u, env.size()); + +			uASSERT_EQUAL("boar"s, env["dear"s]); +			uASSERT_EQUAL("world"s, env["hello"s]); +			uASSERT_EQUAL("leader"s, env["world"s]); + +			auto [argc, argv] = env.get(); +			uASSERT_EQUAL(3, argc); +			// store and sort to verify unordered +			std::vector<std::string> vals{argv[0], argv[1], argv[2]}; +			std::ranges::sort(vals); +			uASSERT_EQUAL("dear=boar"s, vals[0]); +			uASSERT_EQUAL("hello=world"s, vals[1]); +			uASSERT_EQUAL("world=leader"s, vals[2]); + +			// test all combinations since ordering is not a requirement +			// exactly one of the must be true (boolean XOR) +			auto str = env.stringify(); +			uASSERT(((((("dear=boar\0hello=world\0world=leader\0\0"s == str) != +			            ("dear=boar\0world=leader\0hello=world\0\0"s == str)) != +			           ("hello=world\0dear=boar\0world=leader\0\0"s == str)) != +			          ("hello=world\0world=leader\0dear=boar\0\0"s == str)) != +			         ("world=leader\0dear=boar\0hello=world\0\0"s == str)) != +			        ("world=leader\0hello=world\0dear=boar\0\0"s == str)); +		} + +		{ // Many +			const char* ptr[] = +				{"hello=world\0", "world=leader\0", "dear=boar\0", nullptr}; +			EnvMap env(ptr); +			uASSERT_EQUAL(3u, env.size()); + +			uASSERT_EQUAL("boar"s, env["dear"s]); +			uASSERT_EQUAL("world"s, env["hello"s]); +			uASSERT_EQUAL("leader"s, env["world"s]); + +			auto [argc, argv] = env.get(); +			uASSERT_EQUAL(3, argc); +			// store and sort to verify unordered +			std::vector<std::string> vals{argv[0], argv[1], argv[2]}; +			std::ranges::sort(vals); +			uASSERT_EQUAL("dear=boar"s, vals[0]); +			uASSERT_EQUAL("hello=world"s, vals[1]); +			uASSERT_EQUAL("world=leader"s, vals[2]); + +			// test all combinations since ordering is not a requirement +			// exactly one of the must be true (boolean XOR) +			auto str = env.stringify(); +			uASSERT(((((("dear=boar\0hello=world\0world=leader\0\0"s == str) != +			            ("dear=boar\0world=leader\0hello=world\0\0"s == str)) != +			           ("hello=world\0dear=boar\0world=leader\0\0"s == str)) != +			          ("hello=world\0world=leader\0dear=boar\0\0"s == str)) != +			         ("world=leader\0dear=boar\0hello=world\0\0"s == str)) != +			        ("world=leader\0hello=world\0dear=boar\0\0"s == str)); +		} +	} + +	void test_exceptional_env() +	{ +		using namespace std::string_literals; + +		{ // Zero +			EnvMap env; +			uASSERT_EQUAL(0u, env.size()); +			uASSERT_EQUAL(""s, env["foo"]); // lookup of non-existing key +		} +	} +}; + +// Registers the fixture into the 'registry' +static PointerListTest test; diff --git a/test/tasks_test.cc b/test/tasks_test.cc index 5f1db26..b444bd5 100644 --- a/test/tasks_test.cc +++ b/test/tasks_test.cc @@ -10,6 +10,7 @@ ctor::build_configurations ctorTestConfigs1(const ctor::settings&)  	return  	{  		{ +			.name = "Target1",  			.target = "target1",  			.sources = {"foo.cc", "bar.c"},  		}, @@ -33,16 +34,29 @@ ctor::build_configurations ctorTestConfigs2(const ctor::settings&)  }  } +namespace test_global { +ctor::toolchain toolchain{}; +ctor::arch arch{}; +} +const ctor::configuration& ctor::get_configuration() +{ +	static ctor::configuration cfg{}; +	cfg.build_toolchain = test_global::toolchain; +	cfg.build_arch = test_global::arch; +	return cfg; +} + +  REG(ctorTestConfigs1);  REG(ctorTestConfigs2); -std::size_t count(const std::set<std::shared_ptr<Task>>& tasks, -                  const std::string& name) +std::size_t count(const std::vector<std::shared_ptr<Task>>& tasks, +                  const std::filesystem::path& name)  {  	auto cnt{0u};  	for(const auto& task : tasks)  	{ -		if(task->target() == name) +		if(task->target() == name.string())  		{  			cnt++;  		} @@ -54,16 +68,17 @@ class TestTask  	: public Task  {  public: -	TestTask(const std::string& name, bool dirty, +	TestTask(const ctor::build_configuration& config, +	         const ctor::settings& settings, +	         const std::string& name, bool dirty,  	         const std::vector<std::string>& deps = {}) -		: Task({}, {}, {}) +		: Task(config, settings, {})  		, task_name(name)  		, task_dirty(dirty)  		, task_deps(deps)  	{  	} -	std::string name() const override { return task_name; }  	int clean() override { return 0; }  	std::vector<std::string> depends() const override { return task_deps; }  	std::string target() const override { return task_name; } @@ -81,11 +96,14 @@ class TasksTest  	: public uUnit  {  public: +	using fs = std::filesystem::path; +  	TasksTest()  	{  		uTEST(TasksTest::getTargets_test);  		uTEST(TasksTest::getTasks_test);  		uTEST(TasksTest::getNextTask_test); +		uTEST(TasksTest::comparison_test);  	}  	void getTargets_test() @@ -108,27 +126,29 @@ public:  	void getTasks_test()  	{ +		test_global::toolchain = ctor::toolchain::gcc; +		test_global::arch = ctor::arch::unix; +  		using namespace std::string_literals;  		ctor::settings settings{ .builddir = "foo" };  		{  			auto tasks = getTasks(settings);  			uASSERT_EQUAL(6u, tasks.size()); -			// 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)); +			// Note: count() is used here because the order doesn't matter +			uASSERT_EQUAL(1u, count(tasks, fs("target1"))); +			uASSERT_EQUAL(1u, count(tasks, fs("target2"))); +			uASSERT_EQUAL(1u, count(tasks, fs("target3"))); +			uASSERT_EQUAL(1u, count(tasks, fs("target4"))); +			uASSERT_EQUAL(1u, count(tasks, fs("test")/"target1-foo_cc.o")); +			uASSERT_EQUAL(1u, count(tasks, fs("test")/"target1-bar_c.o"));  		}  		{  			auto tasks = getTasks(settings, {"target1", "target3"});  			uASSERT_EQUAL(4u, tasks.size()); -			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)); +			uASSERT_EQUAL(1u, count(tasks, fs("target1"))); +			uASSERT_EQUAL(1u, count(tasks, fs("target3"))); +			uASSERT_EQUAL(1u, count(tasks, fs("test")/"target1-foo_cc.o")); +			uASSERT_EQUAL(1u, count(tasks, fs("test")/"target1-bar_c.o"));  		}  		{  			auto tasks = getTasks(settings, {"no-such-target"}); @@ -142,8 +162,8 @@ public:  		ctor::settings settings{};  		{ // Zero (Empty) -			std::set<std::shared_ptr<Task>> allTasks; -			std::set<std::shared_ptr<Task>> dirtyTasks; +			std::vector<std::shared_ptr<Task>> allTasks; +			std::vector<std::shared_ptr<Task>> dirtyTasks;  			for(auto& task : dirtyTasks)  			{ @@ -154,12 +174,13 @@ public:  		}  		{ // Zero (One task, no dirty) -			auto task1 = std::make_shared<TestTask>("task1", false); +			ctor::build_configuration config; +			auto task1 = std::make_shared<TestTask>(config, settings, "task1", false); -			std::set<std::shared_ptr<Task>> allTasks; -			allTasks.insert(task1); +			std::vector<std::shared_ptr<Task>> allTasks; +			allTasks.push_back(task1); -			std::set<std::shared_ptr<Task>> dirtyTasks; +			std::vector<std::shared_ptr<Task>> dirtyTasks;  			for(auto& task : dirtyTasks)  			{ @@ -170,13 +191,14 @@ public:  		}  		{ // One (One task, one dirty) -			auto task1 = std::make_shared<TestTask>("task1", true); +			ctor::build_configuration config; +			auto task1 = std::make_shared<TestTask>(config, settings, "task1", true); -			std::set<std::shared_ptr<Task>> allTasks; -			allTasks.insert(task1); +			std::vector<std::shared_ptr<Task>> allTasks; +			allTasks.push_back(task1); -			std::set<std::shared_ptr<Task>> dirtyTasks; -			dirtyTasks.insert(task1); +			std::vector<std::shared_ptr<Task>> dirtyTasks; +			dirtyTasks.push_back(task1);  			for(auto& task : dirtyTasks)  			{ @@ -188,15 +210,16 @@ public:  		}  		{ // One (Two tasks, one dirty) -			auto task1 = std::make_shared<TestTask>("task1", false); -			auto task2 = std::make_shared<TestTask>("task2", true); +			ctor::build_configuration config; +			auto task1 = std::make_shared<TestTask>(config, settings, "task1", false); +			auto task2 = std::make_shared<TestTask>(config, settings, "task2", true); -			std::set<std::shared_ptr<Task>> allTasks; -			allTasks.insert(task1); -			allTasks.insert(task2); +			std::vector<std::shared_ptr<Task>> allTasks; +			allTasks.push_back(task1); +			allTasks.push_back(task2); -			std::set<std::shared_ptr<Task>> dirtyTasks; -			dirtyTasks.insert(task2); +			std::vector<std::shared_ptr<Task>> dirtyTasks; +			dirtyTasks.push_back(task2);  			for(auto& task : dirtyTasks)  			{ @@ -208,17 +231,19 @@ public:  		}  		{ // One (Two tasks, one dirty which depends on the other) -			auto task1 = std::make_shared<TestTask>("task1", false); +			ctor::build_configuration config; +			auto task1 = std::make_shared<TestTask>(config, settings, "task1", false);  			std::vector<std::string> deps = {"task1"}; -			auto task2 = std::make_shared<TestTask>("task2", true, deps); +			auto task2 = +				std::make_shared<TestTask>(config, settings, "task2", true, deps); -			std::set<std::shared_ptr<Task>> allTasks; -			allTasks.insert(task1); -			allTasks.insert(task2); +			std::vector<std::shared_ptr<Task>> allTasks; +			allTasks.push_back(task1); +			allTasks.push_back(task2); -			std::set<std::shared_ptr<Task>> dirtyTasks; -			dirtyTasks.insert(task2); +			std::vector<std::shared_ptr<Task>> dirtyTasks; +			dirtyTasks.push_back(task2);  			for(auto& task : dirtyTasks)  			{ @@ -230,18 +255,20 @@ public:  		}  		{ // One (Two tasks, Both dirty, one depends on the other) -			auto task1 = std::make_shared<TestTask>("task1", true); +			ctor::build_configuration config{}; +			auto task1 = std::make_shared<TestTask>(config, settings, "task1", true);  			std::vector<std::string> deps = {"task1"}; -			auto task2 = std::make_shared<TestTask>("task2", true, deps); +			auto task2 = +				std::make_shared<TestTask>(config, settings, "task2", true, deps); -			std::set<std::shared_ptr<Task>> allTasks; -			allTasks.insert(task2); -			allTasks.insert(task1); +			std::vector<std::shared_ptr<Task>> allTasks; +			allTasks.push_back(task2); +			allTasks.push_back(task1); -			std::set<std::shared_ptr<Task>> dirtyTasks; -			dirtyTasks.insert(task2); -			dirtyTasks.insert(task1); +			std::vector<std::shared_ptr<Task>> dirtyTasks; +			dirtyTasks.push_back(task2); +			dirtyTasks.push_back(task1);  			for(auto& task : dirtyTasks)  			{ @@ -251,7 +278,51 @@ public:  			uASSERT_EQUAL(task1, getNextTask(allTasks, dirtyTasks));  			uASSERT_EQUAL(1u, dirtyTasks.size());  		} +	} +	void comparison_test() +	{ +		test_global::toolchain = ctor::toolchain::gcc; +		test_global::arch = ctor::arch::unix; + +		using namespace std::string_literals; +		ctor::settings settings{ .builddir = "foo" }; +		{ // Test Task::operator== +			auto tasks = getTasks(settings, {"target1"}); +			uASSERT_EQUAL(3u, tasks.size()); +			uASSERT_EQUAL(1u, count(tasks, fs("target1"))); +			uASSERT_EQUAL(1u, count(tasks, fs("test")/"target1-foo_cc.o")); +			uASSERT_EQUAL(1u, count(tasks, fs("test")/"target1-bar_c.o")); + +			int cnt1{}; +			int cnt2{}; +			int cnt3{}; +			for(const auto& task : tasks) +			{ +				if(task->target() == fs("target1")) +				{ +					++cnt1; +					uASSERT(*task == "target1"); +					uASSERT(*task == "Target1"); +				} +				if(task->target() == fs("test")/"target1-foo_cc.o") +				{ +					++cnt2; +					uASSERT(*task != "target1"); +					uASSERT(*task != "Target1"); +				} +				if(task->target() == fs("test")/"target1-bar_c.o") +				{ +					++cnt3; +					uASSERT(*task != "target1"); +					uASSERT(*task != "Target1"); +				} +			} +			// Assert that we did actually perform all three tests exactly once +			uASSERT_EQUAL(1, cnt1); +			uASSERT_EQUAL(1, cnt2); +			uASSERT_EQUAL(1, cnt3); +		}  	}  }; diff --git a/test/testprog.cc b/test/testprog.cc new file mode 100644 index 0000000..faf1498 --- /dev/null +++ b/test/testprog.cc @@ -0,0 +1,53 @@ +#include <iostream> +#include <fstream> +#include <string> +#include <signal.h> + +int main(int argc, const char* argv[]) +{ +	if(argc < 2) +	{ +		return 0; +	} + +	std::string cmd = argv[1]; + +	if(cmd == "envdump") +	{ +		if(argc < 3) +		{ +			return 0; +		} +		std::ofstream ostrm(argv[2], std::ios::binary); +		for(auto current = environ; *current; ++current) +		{ +			ostrm << (*current) << "\n"; +		} +	} + +	if(cmd == "retval") +	{ +		if(argc < 3) +		{ +			return 0; +		} +		return std::stoi(argv[2]); +	} + +	if(cmd == "abort") +	{ +		abort(); +	} + +	if(cmd == "segfault") +	{ +		raise(SIGSEGV); +	} + +	if(cmd == "throw") +	{ +		throw "ouch"; +	} + +	return 0; +} diff --git a/test/tmpfile.h b/test/tmpfile.h new file mode 100644 index 0000000..5d114d0 --- /dev/null +++ b/test/tmpfile.h @@ -0,0 +1,48 @@ +// -*- c++ -*- +// Distributed under the BSD 2-Clause License. +// See accompanying file LICENSE for details. +#pragma once + +#include <cstdlib> +#include <unistd.h> + +#ifdef _WIN32 +#define WIN32_LEAN_AND_MEAN +#include <windows.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#endif + +struct tmp_file +{ +	tmp_file(const std::string& data = {}) +	{ +		int fd; +#ifdef _WIN32 +		char templ[] = "ctor_tmp_file-XXXXXX"; // buffer for filename +		_mktemp_s(templ, sizeof(templ)); +		fd = open(templ, O_CREAT | O_RDWR); +#else +		char templ[] = "/tmp/ctor_tmp_file-XXXXXX"; // buffer for filename +		fd = mkstemp(templ); +#endif +		filename = templ; +		auto sz = write(fd, data.data(), data.size()); +		(void)sz; +		close(fd); +	} + +	~tmp_file() +	{ +		unlink(filename.data()); +	} + +	const std::string& get() const +	{ +		return filename; +	} + +private: +	std::string filename; +}; diff --git a/test/tools_test.cc b/test/tools_test.cc index 1d377b7..46ab084 100644 --- a/test/tools_test.cc +++ b/test/tools_test.cc @@ -114,33 +114,27 @@ bool operator!=(const ctor::asm_flag& a, const ctor::asm_flag& b)  #include <uunit.h> -namespace { -std::string conf_host_cxx{}; -std::string conf_build_cxx{}; -} -  const ctor::configuration& ctor::get_configuration()  {  	static ctor::configuration cfg;  	return cfg;  } -const std::string& ctor::configuration::get(const std::string& key, const std::string& defval) const +std::string ctor::configuration::get(const std::string& key, [[maybe_unused]]const std::string& default_value) const  {  	if(key == ctor::cfg::host_cxx)  	{ -		return conf_host_cxx; +		return {};  	}  	if(key == ctor::cfg::build_cxx)  	{ -		return conf_build_cxx; +		return {};  	}  	assert(false); // bad key -	static std::string res{}; -	return res; +	return {};  }  class ToolsTest @@ -172,9 +166,24 @@ public:  	void getToolChain_test()  	{ +		// +		// gcc +		// +		uASSERT_EQUAL(ctor::toolchain::gcc, getToolChain("/usr/bin/gcc")); +		uASSERT_EQUAL(ctor::toolchain::gcc, getToolChain("/usr/bin/gcc-10")); +		uASSERT_EQUAL(ctor::toolchain::gcc, getToolChain("/usr/bin/x86_64-pc-linux-gnu-g++-9.3.0")); +  		uASSERT_EQUAL(ctor::toolchain::gcc, getToolChain("/usr/bin/g++"));  		uASSERT_EQUAL(ctor::toolchain::gcc, getToolChain("/usr/bin/g++-10"));  		uASSERT_EQUAL(ctor::toolchain::gcc, getToolChain("/usr/bin/x86_64-pc-linux-gnu-g++-9.3.0")); + +		// +		// clang +		// +		uASSERT_EQUAL(ctor::toolchain::clang, getToolChain("/usr/bin/clang")); +		uASSERT_EQUAL(ctor::toolchain::clang, getToolChain("/usr/bin/clang-16")); +		uASSERT_EQUAL(ctor::toolchain::clang, getToolChain("/usr/lib/llvm/16/bin/i686-pc-linux-gnu-clang-16")); +  		uASSERT_EQUAL(ctor::toolchain::clang, getToolChain("/usr/bin/clang++"));  		uASSERT_EQUAL(ctor::toolchain::clang, getToolChain("/usr/bin/clang++-16"));  		uASSERT_EQUAL(ctor::toolchain::clang, getToolChain("/usr/lib/llvm/16/bin/i686-pc-linux-gnu-clang++-16")); @@ -666,22 +675,54 @@ public:  		//  		// gcc  		// +		exp = { "-r" }; +		act = ar_option(ctor::toolchain::gcc, ctor::ar_opt::replace); +		uASSERT_EQUAL(exp, act); + +		exp = { "-s" }; +		act = ar_option(ctor::toolchain::gcc, ctor::ar_opt::add_index); +		uASSERT_EQUAL(exp, act); + +		exp = { "-c" }; +		act = ar_option(ctor::toolchain::gcc, ctor::ar_opt::create); +		uASSERT_EQUAL(exp, act); + +		exp = { "foo" }; +		act = ar_option(ctor::toolchain::gcc, ctor::ar_opt::output, "foo"); +		uASSERT_EQUAL(exp, act); +  		exp = { "-foo" }; -		act = asm_option(ctor::toolchain::gcc, ctor::asm_opt::custom, "-foo"); +		act = ar_option(ctor::toolchain::gcc, ctor::ar_opt::custom, "-foo");  		uASSERT_EQUAL(exp, act);  		//  		// clang  		// +		exp = { "-r" }; +		act = ar_option(ctor::toolchain::clang, ctor::ar_opt::replace); +		uASSERT_EQUAL(exp, act); + +		exp = { "-s" }; +		act = ar_option(ctor::toolchain::clang, ctor::ar_opt::add_index); +		uASSERT_EQUAL(exp, act); + +		exp = { "-c" }; +		act = ar_option(ctor::toolchain::clang, ctor::ar_opt::create); +		uASSERT_EQUAL(exp, act); + +		exp = { "foo" }; +		act = ar_option(ctor::toolchain::clang, ctor::ar_opt::output, "foo"); +		uASSERT_EQUAL(exp, act); +  		exp = { "-foo" }; -		act = asm_option(ctor::toolchain::clang, ctor::asm_opt::custom, "-foo"); +		act = ar_option(ctor::toolchain::clang, ctor::ar_opt::custom, "-foo");  		uASSERT_EQUAL(exp, act);  		//  		// any  		// -		exp = { "{ctor::asm_opt::custom, \"-foo\"}" }; -		act = asm_option(ctor::toolchain::any, ctor::asm_opt::custom, "-foo"); +		exp = { "{ctor::ar_opt::custom, \"-foo\"}" }; +		act = ar_option(ctor::toolchain::any, ctor::ar_opt::custom, "-foo");  		uASSERT_EQUAL(exp, act);  } diff --git a/test/uunit b/test/uunit -Subproject bc078da645412c6b36ef59e635d6c35d11088c9 +Subproject 0f371777e02dd068f9675a05a29230221d5d6a7 | 
