You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

689 lines
24 KiB
C++

4 years ago
/******************************************************************************
* File - main.cpp
* Author - Joey Pollack
* Date - 2021/12/03 (y/m/d)
* Mod Date - 2021/12/03 (y/m/d)
* Description - Generates a new C++ project with CMake
******************************************************************************/
// TODO: Generate a clean.sh script for linux
4 years ago
#include <utils/CmdArgParser.h>
#include <utils/StringManip.h>
#include <iostream>
#include <filesystem>
#include <string>
#include <fstream>
#include <ctime>
4 years ago
int GenerateLibCMakeFile(std::string name, std::filesystem::path location);
int GenerateSubmodCMakeFile(std::string name, std::filesystem::path location, std::vector<std::string>& libraries);
int GenerateMainFile(std::string project_name, std::string mod_name, std::filesystem::path location);
void CleanUp(std::filesystem::path root);
4 years ago
typedef jpUtils::CmdArgParser::ArgDesc ArgDesc;
int main(int argc, char** argv)
{
jpUtils::CmdArgParser argParser(argc, argv, "Generates a new C++ project with CMake.");
4 years ago
// Required args
argParser.AddArg(ArgDesc("ProjectName", false, 1, "The name of the project to generate."));
// Options
argParser.AddArg(ArgDesc("-h", true, 0, "Displays this help messasge."));
argParser.AddArg(ArgDesc("-v", true, 0, "Verbose - Print extra info while running."));
argParser.AddArg(ArgDesc("-p", true, 1, "Platform - Can be one of: win, linux, both. Default is win."));
argParser.AddArg(ArgDesc("-l", true, 1, "License - Generate an MIT license file with the given name."));
argParser.AddArg(ArgDesc("-d", true, 1, "Path to the location to generate the project at. Defaults to the current dirctory."));
argParser.AddArg(ArgDesc("-i", true, 0, "Generate a .gitignore file"));
argParser.AddArg(ArgDesc("-m", true, 1, "Specify the CMake version to require. Default is 3.16.3"));
argParser.AddArg(ArgDesc("-c", true, 1, "Specify the C++ version to require. Default is 17"));
argParser.AddArg(ArgDesc("-s", true, 1, "Specify the visual studio version to use (17, 19, 22). Default is 19"));
argParser.AddArg(ArgDesc("-a", true, 1, "Add library projects, separated by ',' (ex: -a first,second,third)"));
argParser.AddArg(ArgDesc("-x", true, 1, "Add executable submodule projects, separated by ',' (ex: -x first,second,third)"));
argParser.AddArg(ArgDesc("-r", true, 0, "Remove the root project executable"));
bool result = argParser.Parse();
if (!result)
{
if (argParser.ContainsOption("-h"))
{
std::cout << argParser.GetUsageText() << std::endl;
return 0;
}
else
{
std::cout << argParser.GetErrorMessage();
std::cout << "\n" << argParser.GetUsageText() << std::endl;
return 1;
}
}
////////////////////////////////////////////////////////////
// Parse the submodules/libraries
////////////////////////////////////////////////////////////
// Libraries
std::vector<std::string> libraries;
if (argParser.ContainsOption("-a"))
{
std::string list = argParser.GetOptionValue("-a").values[0];
std::string current_name;
for (int i = 0; i < list.size(); i++)
{
if (list[i] == ',')
{
libraries.push_back(current_name);
current_name.clear();
}
else if (i + 1 == list.size())
{
current_name += list[i];
libraries.push_back(current_name);
current_name.clear();
}
else
{
current_name += list[i];
}
}
}
// Submodules
std::vector<std::string> submods;
if (argParser.ContainsOption("-x"))
{
std::string list = argParser.GetOptionValue("-x").values[0];
std::string current_name;
for (int i = 0; i < list.size(); i++)
{
if (list[i] == ',')
{
submods.push_back(current_name);
current_name.clear();
}
else if (i + 1 == list.size())
{
current_name += list[i];
submods.push_back(current_name);
current_name.clear();
}
else
{
current_name += list[i];
}
}
}
4 years ago
////////////////////////////////////////////////////////////
// Generate directory structure
////////////////////////////////////////////////////////////
std::filesystem::path root = std::filesystem::current_path();
if (argParser.ContainsOption("-d"))
{
if (std::filesystem::exists(argParser.GetOptionValue("-d").values[0]))
{
root = argParser.GetOptionValue("-d").values[0];
}
}
std::cout << "\nGenerating project at location: " << root.string().c_str();
std::string project_name = argParser.GetPositionalArg(0).values[0];
root /= std::filesystem::path(project_name);
if (!std::filesystem::create_directory(root))
{
std::cout << "\nCould not create directory: " << root.string().c_str();
return 1;
}
4 years ago
if (!std::filesystem::create_directory(root / "build"))
{
std::cout << "\nCould not create build directory";
return 1;
}
if (!std::filesystem::create_directory(root / "scripts"))
{
std::cout << "\nCould not create scripts directory";
return 1;
}
if (!std::filesystem::create_directory(root / "src"))
{
std::cout << "\nCould not create src directory";
return 1;
}
// Generate folders/files for any submodules/libraries
std::filesystem::path src = root / "src";
for (int i = 0; i < libraries.size(); i++)
{
std::filesystem::path location = src / libraries[i];
if (!std::filesystem::create_directory(location))
{
std::cout << "\nCould not create library directory: " << location.c_str();
return 1;
}
if (GenerateLibCMakeFile(libraries[i], location) > 0)
return 1;
}
src = root / "src";
for (int i = 0; i < submods.size(); i++)
{
std::filesystem::path location = src / submods[i];
if (!std::filesystem::create_directory(location))
{
std::cout << "\nCould not create submodule directory: " << location.c_str();
return 1;
}
if (GenerateSubmodCMakeFile(submods[i], location, libraries) > 0)
return 1;
}
////////////////////////////////////////////////////////////
// Generate CMake files
////////////////////////////////////////////////////////////
std::filesystem::path cmake_file = root / std::filesystem::path("CMakeLists.txt");
std::ofstream ofs(cmake_file.string().c_str());
if (!ofs.is_open())
{
std::cout << "\nCould not generate the CMake file: " << cmake_file.string().c_str();
return 1;
}
std::cout << "\nGenerating CMake file: " << cmake_file.string().c_str();
std::string cmversion = "3.16.3";
if (argParser.ContainsOption("-m"))
{
cmversion = argParser.GetOptionValue("-m").values[0];
}
std::string cversion = "17";
if (argParser.ContainsOption("-c"))
{
cversion = argParser.GetOptionValue("-c").values[0];
}
ofs << "cmake_minimum_required(VERSION " << cmversion << ")";
ofs << "\n\n# set the project name and version";
ofs << "\nproject(" << project_name << " VERSION 0.1.0)";
ofs << "\n\n# specify the C++ standard";
ofs << "\nset(CMAKE_CXX_STANDARD " << cversion << ")";
ofs << "\nset(CMAKE_CXX_STANDARD_REQUIRED True)";
ofs << "\n# setup target output directories";
ofs << "\nset(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)";
ofs << "\nset(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)";
ofs << "\nset(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)";
ofs << "\n\nconfigure_file(" << project_name << "Config.h.in " << project_name << "Config.h)";
// remove root executable if the option is set
if (!argParser.ContainsOption("-r"))
{
ofs << "\n\n# specify the project source files";
ofs << "\nset( " << jpUtils::StringManip::ToUpper(project_name) << "_SRC";
ofs << "\n\tsrc/main.cpp";
ofs << "\n)";
ofs << "\n\n# add the executable";
ofs << "\nadd_executable(${PROJECT_NAME} ${" << jpUtils::StringManip::ToUpper(project_name) << "_SRC})";
ofs << "\n\ntarget_include_directories(${PROJECT_NAME}";
ofs << "\n\tPUBLIC \"${PROJECT_BINARY_DIR}\"";
ofs << "\n\tPUBLIC src";
ofs << "\n)";
// Link to any libs that exist
ofs << "\n\ntarget_link_directories(${PROJECT_NAME} ";
for (int i = 0; i < libraries.size(); i++)
{
ofs << "\n\tPRIVATE ../" << libraries[i].c_str();
}
ofs << ")";
ofs << "\n\ntarget_link_libraries(${PROJECT_NAME}";
for (int i = 0; i < libraries.size(); i++)
{
ofs << " " << libraries[i].c_str();
}
ofs << ")";
}
// include the libraries and submodules
for (int i = 0; i < libraries.size(); i++)
{
ofs << "\nadd_subdirectory(src/" << libraries[i].c_str() << ")";
}
for (int i = 0; i < submods.size(); i++)
{
ofs << "\nadd_subdirectory(src/" << submods[i].c_str() << ")";
}
ofs.close();
ofs.clear();
////////////////////////////////////////////////////////////
// Generate Config file
////////////////////////////////////////////////////////////
std::string config_name = project_name;
config_name += "Config.h.in";
std::filesystem::path config_file = root / config_name;
ofs = std::ofstream(config_file.string().c_str());
if (!ofs.is_open())
{
std::cout << "\nCould not generate the Config file: " << config_file.string().c_str();
return 1;
}
std::cout << "\nGenerating Config file: " << config_file.string().c_str();
ofs << "#define " << project_name << "_VERSION_MAJOR @" << project_name << "_VERSION_MAJOR@";
ofs << "\n#define " << project_name << "_VERSION_MINOR @" << project_name << "_VERSION_MINOR@";
ofs << "\n#define " << project_name << "_VERSION_PATCH @" << project_name << "_VERSION_PATCH@";
ofs.close();
ofs.clear();
////////////////////////////////////////////////////////////
// Generate License file
////////////////////////////////////////////////////////////
if (argParser.ContainsOption("-l"))
{
std::filesystem::path license_file = root / "LICENSE";
ofs = std::ofstream(license_file.string().c_str());
if (!ofs.is_open())
{
std::cout << "\nCould not generate the License file: " << license_file.string().c_str();
return 1;
}
std::cout << "\nGenerating License file: " << license_file.string().c_str();
time_t now = time(0);
tm *gmtm = gmtime(&now);
ofs << "Copyright " << gmtm->tm_year + 1900 <<" " << argParser.GetOptionValue("-l").values[0];
ofs << "\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:";
ofs << "\n\nThe above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.";
ofs << "\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.";
ofs.close();
ofs.clear();
}
////////////////////////////////////////////////////////////
// Generate .gitignore file
////////////////////////////////////////////////////////////
if (argParser.ContainsOption("-i"))
{
std::filesystem::path ignore_file = root / ".gitignore";
ofs = std::ofstream(ignore_file.string().c_str());
if (!ofs.is_open())
{
std::cout << "\nCould not generate the .gitignore file: " << ignore_file.string().c_str();
return 1;
}
std::cout << "\nGenerating .gitignore file: " << ignore_file.string().c_str();
ofs << "######################## VSCODE IGNORES";
ofs << "\n.vscode/";
ofs << "\n";
ofs << "\n";
ofs << "\n######################## C++ IGNORES";
ofs << "\n# Prerequisites";
ofs << "\n*.d";
ofs << "\n";
ofs << "\n# Compiled Object files";
ofs << "\n*.slo";
ofs << "\n*.lo";
ofs << "\n*.o";
ofs << "\n*.obj";
ofs << "\n";
ofs << "\n# Precompiled Headers";
ofs << "\n*.gch";
ofs << "\n*.pch";
ofs << "\n";
ofs << "\n# Compiled Dynamic libraries";
ofs << "\n*.so";
ofs << "\n*.dylib";
ofs << "\n*.dll";
ofs << "\n";
ofs << "\n# Fortran module files";
ofs << "\n*.mod";
ofs << "\n*.smod";
ofs << "\n";
ofs << "\n# Compiled Static libraries";
ofs << "\n*.lai";
ofs << "\n*.la";
ofs << "\n*.a";
ofs << "\n*.lib";
ofs << "\n";
ofs << "\n# Executables";
ofs << "\n*.exe";
ofs << "\n*.out";
ofs << "\n*.app";
ofs << "\n";
ofs << "\n# other";
ofs << "\n*.log";
ofs << "\n*.zip";
ofs << "\n*.ini";
ofs << "\n";
ofs << "\n######################## CMAKE IGNORES";
ofs << "\nCMakeLists.txt.user";
ofs << "\nCMakeCache.txt";
ofs << "\nCMakeFiles";
ofs << "\nCMakeScripts";
ofs << "\nTesting";
ofs << "\nMakefile";
ofs << "\ncmake_install.cmake";
ofs << "\ninstall_manifest.txt";
ofs << "\ncompile_commands.json";
ofs << "\nCTestTestfile.cmake";
ofs << "\n_deps";
ofs << "\n";
ofs << "\n######################## BUILD IGNORES";
ofs << "\nbuild/";
ofs.close();
ofs.clear();
}
////////////////////////////////////////////////////////////
// Generate Script files
////////////////////////////////////////////////////////////
bool have_platform_option = argParser.ContainsOption("-p");
bool gen_windows = (!have_platform_option || (argParser.GetOptionValue("-p").values[0] == "win" || argParser.GetOptionValue("-p").values[0] == "both"));
bool gen_linux = (have_platform_option && (argParser.GetOptionValue("-p").values[0] == "linux" || argParser.GetOptionValue("-p").values[0] == "both"));
std::string vs_version = "\"Visual Studio 16 2019\"";
if (argParser.ContainsOption("-s"))
{
if (argParser.GetOptionValue("-s").values[0] == "17")
vs_version = "\"Visual Studio 15 2017\"";
if (argParser.GetOptionValue("-s").values[0] == "22")
vs_version = "\"Visual Studio 17 2022\"";
}
std::filesystem::path script_dir = root / "scripts";
if (gen_windows)
{
// CONFIG
std::filesystem::path config_file = script_dir / "config.bat";
ofs = std::ofstream(config_file.string().c_str());
if (!ofs.is_open())
{
std::cout << "\nCould not generate the config script file: " << config_file.string().c_str();
return 1;
}
std::cout << "\nGenerating config script file: " << config_file.string().c_str();
ofs << "@echo off";
ofs << "\nREM This script expects to be run from the parent directory";
ofs << "\nREM ex. scripts/cmconfig.bat";
ofs << "\n@echo off";
ofs << "\n\ncmake -Wno-dev -B build/ -S . -G " << vs_version.c_str() << " -A x64";
ofs.close();
ofs.clear();
// BUILD
std::filesystem::path build_file = script_dir / "build.bat";
ofs = std::ofstream(build_file.string().c_str());
if (!ofs.is_open())
{
std::cout << "\nCould not generate the build script file: " << build_file.string().c_str();
return 1;
}
std::cout << "\nGenerating build script file: " << build_file.string().c_str();
ofs << "@echo off";
ofs << "\nREM This script expects to be run from the parent directory";
ofs << "\nREM ex. scripts/build.bat";
ofs << "\n\nIF not exist build/ (";
ofs << "\n\techo This script needs to be run from the directory above build/";
ofs << "\n\tgoto END";
ofs << "\n)";
ofs << "\n\nIF \"%~1\" == \"r\" (";
ofs << "\n\tcmake --build build/ --target ALL_BUILD --config Release";
ofs << "\n) ELSE (";
ofs << "\n\tcmake --build build/ --target ALL_BUILD --config Debug";
ofs << "\n)";
ofs << "\n\n:END";
ofs.close();
ofs.clear();
// CLEAN
std::filesystem::path clean_file = script_dir / "clean.bat";
ofs = std::ofstream(clean_file.string().c_str());
if (!ofs.is_open())
{
std::cout << "\nCould not generate the clean script file: " << clean_file.string().c_str();
return 1;
}
std::cout << "\nGenerating clean script file: " << clean_file.string().c_str();
ofs << "@echo off";
ofs << "\n\nIF not exist build/ (";
ofs << "\n\techo This script needs to be run from the directory above build/";
ofs << "\n\tgoto END";
ofs << "\n)";
ofs << "\n\necho Removing the build directory";
ofs << "\ndel /s /q build";
ofs << "\nrd /s /q build";
ofs << "\nmkdir build";
ofs << "\n\n:END";
ofs.close();
ofs.clear();
}
if (gen_linux)
{
// CONFIG
std::filesystem::path config_file = script_dir / "config.sh";
ofs = std::ofstream(config_file.string().c_str());
if (!ofs.is_open())
{
std::cout << "\nCould not generate the config script file: " << config_file.string().c_str();
return 1;
}
std::cout << "\nGenerating config script file: " << config_file.string().c_str();
ofs << "#! /bin/sh";
ofs << "\n# This script expects to be run from the parent directory";
ofs << "\n# ex. scripts/cmconfig.sh";
ofs << "\n\ncmake -S . -B build/";
ofs.close();
ofs.clear();
// BUILD
std::filesystem::path build_file = script_dir / "build.sh";
ofs = std::ofstream(build_file.string().c_str());
if (!ofs.is_open())
{
std::cout << "\nCould not generate the build script file: " << build_file.string().c_str();
return 1;
}
std::cout << "\nGenerating build script file: " << build_file.string().c_str();
ofs << "#! /bin/sh";
ofs << "\n# This script expects to be run from the parent directory";
ofs << "\n# ex. scripts/build.sh";
ofs << "\n\ncmake -C build/";
ofs.close();
ofs.clear();
}
////////////////////////////////////////////////////////////
// Generate Main.cpp files
////////////////////////////////////////////////////////////
// Check if there is a root project executable
if (!argParser.ContainsOption("-r"))
{
if (GenerateMainFile(project_name, project_name, root / "src/main.cpp") > 0)
return 1;
}
// Generate Main files for all libraries/submodules
for (int i = 0; i < libraries.size(); i++)
{
std::filesystem::path location = root / "src";
location /= libraries[i];
if (GenerateMainFile(project_name, libraries[i], location / "main.cpp") > 0)
return 1;
}
// Generate Main files for all libraries/submodules
for (int i = 0; i < submods.size(); i++)
{
std::filesystem::path location = root / "src";
location /= submods[i];
if (GenerateMainFile(project_name, submods[i], location / "main.cpp") > 0)
return 1;
}
std::cout << "\n\nNew project generated!";
return 0;
}
int GenerateLibCMakeFile(std::string name, std::filesystem::path location)
{
std::filesystem::path cmake_file = location / std::filesystem::path("CMakeLists.txt");
std::ofstream ofs(cmake_file.string().c_str());
if (!ofs.is_open())
{
std::cout << "\nCould not generate the CMake file: " << cmake_file.string().c_str();
return 1;
}
std::cout << "\nGenerating CMake file: " << cmake_file.string().c_str();
ofs << "\n\nset(MODULE_NAME " << name << ")";
ofs << "\n# specify the libraries source files";
ofs << "\nset( " << name << "_SRC";
ofs << "\n\tmain.cpp";
ofs << "\n)";
ofs << "\n\n# add the library";
ofs << "\nadd_library(${MODULE_NAME} ${" << name << "_SRC})";
ofs << "\n\ntarget_include_directories(${MODULE_NAME}";
ofs << "\n\tPUBLIC \"${PROJECT_BINARY_DIR}\"";
ofs << "\n)";
ofs.close();
ofs.clear();
return 0;
}
int GenerateSubmodCMakeFile(std::string name, std::filesystem::path location, std::vector<std::string>& libraries)
{
std::filesystem::path cmake_file = location / std::filesystem::path("CMakeLists.txt");
std::ofstream ofs(cmake_file.string().c_str());
if (!ofs.is_open())
{
std::cout << "\nCould not generate the CMake file: " << cmake_file.string().c_str();
return 1;
}
std::cout << "\nGenerating CMake file: " << cmake_file.string().c_str();
ofs << "\n\nset(MODULE_NAME " << name << ")";
ofs << "\n# specify the libraries source files";
ofs << "\nset( " << name << "_SRC";
ofs << "\n\tmain.cpp";
ofs << "\n)";
ofs << "\n\n# add the executable";
ofs << "\nadd_executable(${MODULE_NAME} ${" << name << "_SRC})";
ofs << "\n\ntarget_include_directories(${MODULE_NAME}";
ofs << "\n\tPUBLIC \"${PROJECT_BINARY_DIR}\"";
for (int i = 0; i < libraries.size(); i++)
{
ofs << "\n\tPUBLIC ../" << libraries[i].c_str();
}
ofs << "\n)";
// link to libs
ofs << "\n\ntarget_link_directories(${MODULE_NAME} ";
for (int i = 0; i < libraries.size(); i++)
{
ofs << "\n\tPRIVATE ../" << libraries[i].c_str();
}
ofs << "\n)";
ofs << "\n\ntarget_link_libraries(${MODULE_NAME}";
for (int i = 0; i < libraries.size(); i++)
{
ofs << " " << libraries[i].c_str();
}
ofs << ")";
ofs.close();
ofs.clear();
return 0;
}
int GenerateMainFile(std::string project_name, std::string mod_name, std::filesystem::path location)
{
std::ofstream ofs = std::ofstream(location.string().c_str());
if (!ofs.is_open())
{
std::cout << "\nCould not generate the config script file: " << location.string().c_str();
return 1;
}
std::cout << "\nGenerating main.cpp file: " << location.string().c_str();
ofs << "\n\n#include <iostream>";
ofs << "\n#include <" << project_name << "Config.h>";
ofs << "\n\nint main(int argc, char** argv)";
ofs << "\n{";
ofs << "\n\tstd::cout << \"Hello, World!\";";
ofs << "\n\tstd::cout << \"\\nThis is " << mod_name << " version: \" << " << project_name << "_VERSION_MAJOR << \".\" << " << project_name << "_VERSION_MINOR << \".\" << " << project_name << "_VERSION_PATCH;";
ofs << "\n\treturn 0;";
ofs << "\n}";
ofs.close();
ofs.clear();
4 years ago
return 0;
}