Skip to content

Instantly share code, notes, and snippets.

@sehe
Last active April 5, 2018 02:10
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save sehe/57693dedda3de679c33848074d61c8e6 to your computer and use it in GitHub Desktop.
Save sehe/57693dedda3de679c33848074d61c8e6 to your computer and use it in GitHub Desktop.
//////////////////////////////////////////////////////////////////////////////
// 'mymakefile' is a tool for creating makefiles to be used with make
//
// Author: Greg Haga 2018
// email: ggh@multi.fi
//
// Quick reimplementation in C++ by Seth Heeren, public domain
///////////////////////////////////////////////////////////////////////////////
#include <unistd.h> // chdir
#include <experimental/filesystem>
#include <iostream>
#include <vector>
namespace fs = std::experimental::filesystem;
namespace MyMakefile {
enum class Compiler { NONE, C, CPP };
Compiler detectedCompiler(fs::path filespec);
struct QxSrcFile {
fs::path _path;
Compiler _compiler = Compiler::NONE;
QxSrcFile(fs::path path, Compiler compiler) : _path(path), _compiler(compiler) {}
QxSrcFile(fs::path path) : QxSrcFile(path, detectedCompiler(path)) {}
size_t fileSize() const { return fs::file_size(_path); }
fs::path objectFileName() const { auto o = _path; return o.replace_extension(".o"); } // FIXME should be compiler specific
};
enum class TargetType { UNSPECIFIED = 0, EXETARGET = 1, LIBTARGET = 2, OBJS = 3, STATICLIBTARGET = 4 };
struct Options {
std::string makefile;
std::vector<QxSrcFile> sources;
fs::path target;
fs::path cc_compiler;
fs::path c_compiler;
fs::path m_directory;
std::vector<fs::path> includes;
std::vector<fs::path> libraries;
std::vector<fs::path> librarypath;
std::vector<std::string> defines;
std::vector<fs::path> staticlibs;
std::string cppflags;
std::string cflags;
std::string cxxflags;
std::string ldflags;
TargetType target_type = TargetType::UNSPECIFIED;
bool auto_deps = false;
bool run_make = false;
};
Options params(int argc, char **argv);
void discover_sourcefiles(Options&);
void generate_makefile(Options const&);
int run_make(fs::path const&);
}
int main(int argc, char **argv) {
auto const saved_pwd = fs::current_path();
try {
using namespace MyMakefile;
Options options = params(argc, argv);
if (::chdir(options.m_directory.c_str()))
throw std::system_error(errno, std::system_category());
discover_sourcefiles(options);
generate_makefile(options);
if (options.run_make) {
run_make(options.makefile);
}
if (::chdir(saved_pwd.c_str()))
throw std::system_error(errno, std::system_category());
return 0;
} catch(std::exception const& e) {
std::cerr << e.what() << "\n";
::chdir(saved_pwd.c_str());
return 255;
}
}
#include <string_view>
#include <fstream>
#include <iomanip>
#include <sstream>
#include <map>
static std::ostream& message = std::clog;
namespace MyMakefile {
static const std::map<std::string_view, Compiler> s_compiler_mappings {
{".cpp", Compiler::CPP},
{".c++", Compiler::CPP},
{".cxx", Compiler::CPP},
{".cc", Compiler::CPP},
{".C", Compiler::CPP},
{".c", Compiler::C}
};
Compiler detectedCompiler(fs::path filespec) {
auto match = s_compiler_mappings.find(filespec.extension().string());
return match == s_compiler_mappings.end() ? Compiler::NONE : match->second;
}
static inline std::ostream& operator<<(std::ostream& os, TargetType tt) {
switch (tt) {
case TargetType::EXETARGET: return os << "exe";
case TargetType::LIBTARGET: return os << "dynamic";
case TargetType::OBJS: return os << "objs";
case TargetType::STATICLIBTARGET: return os << "static";
default: break;
}
return os << "?";
}
static inline std::istream& operator>>(std::istream& is, TargetType& tt) {
static const std::map<std::string_view, TargetType> mappings {
{ "exe", TargetType::EXETARGET },
{ "dynamic", TargetType::LIBTARGET },
{ "objs", TargetType::OBJS },
{ "static", TargetType::STATICLIBTARGET },
};
std::string token;
if (is >> token) try {
tt = mappings.at(token);
} catch(std::out_of_range const&) {
throw std::runtime_error("invalid target type '" + token + "'");
}
return is;
}
}
#include <boost/program_options.hpp>
namespace po = boost::program_options;
namespace MyMakefile {
Options params(int argc, char **argv) {
Options options;
po::options_description desc;
desc.add_options()
("help", "This help information")
("t", po::value(&options.target)->default_value("target"), "Target name")
("i", po::value(&options.includes)->composing(), "Include directory:\nInclude directories can be several. Prepend each directory with an '-i'")
("l", po::value(&options.libraries)->composing(), "Library name, may also be several, prepend each library with '-l'.")
("L", po::value(&options.librarypath)->composing(), "Library directory\nLibrary directories may also be several, prepend each directory with an '-L'")
("d", po::value(&options.defines)->composing(), "Define\nMay also be several, prepend each define with '-d'.")
("tt", po::value(&options.target_type)->required(), "Target type <exe|dynamic|objs|static>")
("o", po::value(&options.makefile)->default_value("makefile.mk"), "Makefile filename to generate")
("hd", po::bool_switch(&options.auto_deps), "Header dependencies\n"
"This flag makes the generator scan the source files for header files included. Creates dependency rules in the generated makefile.\n"
"If a header file is included in a source file and found in any of the include directories given it will be listed as a dependency to the source file.")
("c", po::value(&options.c_compiler)->default_value("gcc"), "C compiler, the compiler needs to be in the PATH environment variable.")
("cc", po::value(&options.cc_compiler)->default_value("g++"), "C++ compiler, the C++ compiler needs to be in the PATH environment variable as well.")
("sl", po::value(&options.staticlibs)->composing(), "Link to static library\nThe number of static libraries can be any. Prepend each library name with an '-sl' tag.")
("wd", po::value(&options.m_directory)->default_value("."), "Target directory.\nThe program changes to the target directory, creates the makefile and reverts back to the directory it was started from. The makefile is written to the target directory. If this argument is not set then the current directory will be used.")
("mk", po::bool_switch(&options.run_make), "Make the program / library. This option executes 'make' with the make file given. If no makefile argument is given it will use 'makefile.mk' by default")
("cppflags", po::value(&options.cppflags), "CPPFLAGS")
("cflags", po::value(&options.cflags), "CFLAGS")
("cxxflags", po::value(&options.cxxflags), "CXXFLAGS")
("ldflags", po::value(&options.ldflags), "LDFLAGS")
;
try {
po::variables_map vm;
store(po::parse_command_line(argc, argv, desc,
po::command_line_style::allow_dash_for_short |
po::command_line_style::allow_short |
po::command_line_style::allow_long_disguise |
po::command_line_style::long_allow_next |
po::command_line_style::short_allow_next), vm);
notify(vm);
if (vm.count("help")) {
desc.print(message << "\n");
exit(0);
}
} catch(po::error const& e) {
desc.print(message << e.what() << "\n");
exit(1);
}
return options;
}
void discover_sourcefiles(Options& options) {
message << "Reading directory:\t" << options.m_directory << "\n";
for (auto de : fs::directory_iterator(".")) {
QxSrcFile source(de.path());
if (source._path.stem().empty())
continue;
if (source._compiler != Compiler::NONE && source.fileSize() > 0)
options.sources.push_back(source);
}
}
namespace GenerateHelpers {
static Compiler deduceLinker(Options const& options) {
auto const is_cpp = std::any_of(
begin(options.sources),
end(options.sources),
[](QxSrcFile const& src) { return Compiler::CPP == src._compiler; });
switch(options.target_type) {
case TargetType::STATICLIBTARGET:
case TargetType::OBJS: return Compiler::NONE;
default: return is_cpp? Compiler::CPP : Compiler::C;
}
}
static std::string timestamp() {
std::time_t t = std::time(nullptr);
std::tm tm = *std::localtime(&t);
std::ostringstream oss;
oss << std::put_time(&tm, "%c %Z");
return oss.str();
}
struct comment_block {
std::vector<std::string> lines;
friend std::ostream& operator<<(std::ostream& os, comment_block const& cb) {
std::string delimit(81, '-'); delimit[0] = '#'; delimit += '\n';
os << delimit << "#\n";
for (auto& line : cb.lines)
os << "# " << line << "\n";
return os << "#\n" << delimit;
}
};
}
void generate_makefile(Options const& options) {
message << "Creating makefile:\t" << options.makefile << "\n";
std::ofstream out(options.makefile);
using namespace GenerateHelpers;
out << comment_block { {
"This makefile was generated by", "",
"\tmymakefile", "",
"Date: " + timestamp(), "",
"Author: Greg Haga, 2018",
"Email: ggh@multi.fi", "",
"Reimplementation in c++ by Seth Heeren, Public Domain"
} }
<< "TARGET=" << options.target
<< "\nCC=" << options.c_compiler
<< "\nCXX=" << options.cc_compiler
<< "\nCPPFLAGS=" << options.cppflags
<< "\nLDFLAGS= " << options.ldflags
<< "\nCFLAGS=$(CPPFLAGS) " << options.cflags
<< "\nCXXFLAGS=$(CFLAGS) " << options.cxxflags
<< "\n";
{
out << "\nOBJS=";
for (auto& source : options.sources) out << source.objectFileName() << " ";
out << "\n";
}
for (auto& i : options.includes) out << "CPPFLAGS += -I" << i << "\n";
for (auto& d : options.defines) out << "CPPFLAGS += -D" << std::quoted(d) << "\n";
if (options.auto_deps) {
out << "CPPFLAGS += -MMD # automatic .d dependency file generation\n";
}
auto compilerVar = [](Compiler c) {
switch(c) {
case Compiler::C: return "$(CC) $(CFLAGS)";
case Compiler::CPP: return "$(CXX) $(CXXFLAGS)";
default: return "$(UNKNOWN_COMPILER)";
}
};
Compiler const linker = deduceLinker(options);
if (options.target_type == TargetType::LIBTARGET) {
out << "CPPFLAGS += -fPIC\n"
<< "LDFLAGS += -shared\n";
}
if (Compiler::NONE != linker) {
for (auto& d : options.librarypath) out << "LDFLAGS += -L" << d << "\n";
for (auto& l : options.libraries) out << "LDFLAGS += -l" << l << "\n";
}
std::copy(begin(options.staticlibs), end(options.staticlibs), std::ostream_iterator<fs::path>(out << "STATICLIBS=", " "));
out << "\n";
out << comment_block { { "Target" } };
switch (options.target_type) {
case TargetType::OBJS:
out << "all: $(OBJS)\n\n";
break;
case TargetType::STATICLIBTARGET:
out << "$(TARGET): $(OBJS) $(STATICLIBS)\n";
out << "\tar rvs $(TARGET) $^\n";
break;
default:
out << "$(TARGET):$(OBJS)\n\t" << compilerVar(linker) << " $^ $(STATICLIBS) $(LDFLAGS) -o $@\n";
break;
}
// Includes and defines
out << comment_block { {"Object file dependencies"} };
for(auto& source : options.sources) {
out << source.objectFileName() << ":" << source._path.generic_string() << "\n";
out << "\t" << compilerVar(source._compiler) << " -c $< -o $@\n";
if (options.auto_deps) {
auto depfile = source._path;
out << "-include " << depfile.replace_extension(".d") << "\n";
}
}
{
out << comment_block { {"make clean"} };
out << "clean:\n";
out << "\trm -f $(OBJS)\n";
out << "\trm -f $(TARGET)\n";
if (options.auto_deps)
out << "\trm -f *.d\n";
}
out.close();
}
int run_make(fs::path const& makefile) {
message << "Making...\n";
message << "------------------------------------------------------\n";
int ec = ::system(("make -f " + makefile.string()).c_str());
message << "------------------------------------------------------\n";
if (ec)
std::cerr << "Failed with exit code " << ec << "\n";
return ec;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment