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 <fstream>
#include <iostream>
#include <iomanip>
#include <vector>
#include <string_view>
#include <sstream>
#include <map>
#include <experimental/filesystem>
namespace fs = std::experimental::filesystem;
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();
}
static std::ostream& message = std::clog;
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;
}
};
enum class Compiler { NONE, C, CPP };
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;
}
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 };
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;
is >> token;
tt = mappings.at(token);
return is;
}
static bool needlink(TargetType tt) {
switch(tt) {
case TargetType::STATICLIBTARGET:
case TargetType::OBJS:
return false;
default:
return true;
}
}
#include <boost/program_options.hpp>
namespace po = boost::program_options;
struct Options {
std::vector<QxSrcFile> m_srcfiles;
std::vector<fs::path> m_targets;
fs::path target;
fs::path cc_compiler;
fs::path c_compiler;
fs::path m_directory;
std::vector<std::string> m_compilerflags;
TargetType m_tt = TargetType::UNSPECIFIED;
std::vector<fs::path> m_includedirs;
std::vector<fs::path> m_libraries;
std::vector<fs::path> m_libdirs;
std::vector<std::string> m_defines;
std::vector<fs::path> m_staticlibraries;
bool m_headerdeps = false;
std::string m_makefile;
bool m_make = false;
};
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.m_includedirs)->composing(), "Include directory:\nInclude directories can be several. Prepend each directory with an '-i'")
("l", po::value(&options.m_libraries)->composing(), "Library name, may also be several, prepend each library with '-l'.")
("L", po::value(&options.m_libdirs)->composing(), "Library directory\nLibrary directories may also be several, prepend each directory with an '-L'")
("d", po::value(&options.m_defines)->composing(), "Define\nMay also be several, prepend each define with '-d'.")
("tt", po::value(&options.m_tt), "Target type <exe|dynamic|objs|static>")
("o", po::value(&options.m_makefile)->default_value("makefile.mk"), "Makefile filename to generate")
("hd", po::bool_switch(&options.m_headerdeps), "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.m_staticlibraries)->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.m_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")
;
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;
}
int main(int argc, char **argv) {
auto const saved_pwd = fs::current_path();
try {
Options options = params(argc, argv);
if (::chdir(options.m_directory.c_str()))
throw std::system_error(errno, std::system_category());
std::ofstream out(options.m_makefile);
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.m_srcfiles.push_back(source);
}
message << "Creating makefile:\t" << options.m_makefile << "\n";
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
<< "\nC=" << options.c_compiler
<< "\nCC=" << options.cc_compiler
<< "\nOBJS=";
for (auto& source : options.m_srcfiles) out << source.objectFileName() << " ";
out << "\n";
std::copy(begin(options.m_staticlibraries), end(options.m_staticlibraries),
std::ostream_iterator<fs::path>(out << "STATICLIBS=", " "));
out << "\n";
out << comment_block { { "Target" } };
switch (options.m_tt) {
case TargetType::OBJS: {
out << "all:";
for (auto& source : options.m_srcfiles)
out << source.objectFileName() << " ";
out << "\n\n";
}
break;
case TargetType::STATICLIBTARGET: {
out << "\nall:$(STATICLIBS) \n\tar rvs $(TARGET) ";
for (auto& source : options.m_srcfiles)
out << source.objectFileName() << " ";
out << "\n\n";
}
break;
default: {
out << "$(TARGET):$(OBJS)\n\t$(CC) $(OBJS) ";
if (options.m_staticlibraries.size())
out << "$(STATICLIBS) ";
if (options.m_tt == TargetType::LIBTARGET)
out << "-shared ";
out << "-o $@ ";
}
break;
}
// Libraries to link to
if (needlink(options.m_tt)) {
for (auto& d : options.m_libdirs) out << "-L" << d << " ";
for (auto& l : options.m_libraries) out << "-l" << l << " ";
}
out << "\n";
// Includes and defines
out << comment_block { {"Object file dependencies"} };
for(auto& source : options.m_srcfiles) {
out << source.objectFileName()
<< ":" << source._path.generic_string();
//if (m_headerdeps) {
//source.setIncludeDirectories(m_includedirs);
//source.scanIncludes();
//source.includes(out);
//}
out << "\n";
if (source._compiler == Compiler::C) out << "\t$(C) ";
else if (source._compiler == Compiler::CPP) out << "\t$(CC) -std=c++1z ";
for (auto& i : options.m_includedirs) out << "-I" << i << " ";
for (auto& d : options.m_defines) out << "-D" << std::quoted(d) << " ";
out << "-c " << source._path << " -o " << source.objectFileName() << "\n";
}
out << comment_block { {"make clean"} };
out << "clean:\n\trm -f *.o\n\trm -f $(TARGET)\n"; // TODO FIXME use list of object files
out.close();
if (options.m_make) {
message << "Making...\n";
message << "------------------------------------------------------\n";
int ec = ::system(("make -f " + options.m_makefile).c_str());
message << "------------------------------------------------------\n";
if (ec)
std::cerr << "Failed with exit code " << ec << "\n";
}
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;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment