From ceadcc1802d10225df9adb43b5657d7ed3dc2043 Mon Sep 17 00:00:00 2001 From: Joey Pollack Date: Fri, 15 Apr 2022 17:40:31 -0400 Subject: [PATCH] Adds support for multiple libraries and submodules Adds support for visual studio 17 and 22 Adds ability to remove the top-level project executable --- scripts/clean.bat | 1 + scripts/cmconfig.bat | 2 +- src/main.cpp | 304 ++++++++++++++++++++++++++++++++++++++++--- 3 files changed, 285 insertions(+), 22 deletions(-) diff --git a/scripts/clean.bat b/scripts/clean.bat index 9a786e4..a6f7e69 100644 --- a/scripts/clean.bat +++ b/scripts/clean.bat @@ -9,5 +9,6 @@ IF not exist build/ ( echo Removing the build directory del /s /q build rd /s /q build +mkdir build :END \ No newline at end of file diff --git a/scripts/cmconfig.bat b/scripts/cmconfig.bat index 0afaaef..02b2938 100644 --- a/scripts/cmconfig.bat +++ b/scripts/cmconfig.bat @@ -4,4 +4,4 @@ REM ex. scripts/cmconfig.bat @echo off -cmake -Wno-dev -B build/ -S . -G "Visual Studio 16 2019" -A x64 +cmake -Wno-dev -B build/ -S . -G "Visual Studio 17 2022" -A x64 diff --git a/src/main.cpp b/src/main.cpp index 4904f58..1e82a8b 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -17,6 +17,11 @@ #include #include +int GenerateLibCMakeFile(std::string name, std::filesystem::path location); +int GenerateSubmodCMakeFile(std::string name, std::filesystem::path location, std::vector& libraries); +int GenerateMainFile(std::string project_name, std::string mod_name, std::filesystem::path location); +void CleanUp(std::filesystem::path root); + typedef jpUtils::CmdArgParser::ArgDesc ArgDesc; int main(int argc, char** argv) @@ -35,6 +40,10 @@ int main(int argc, char** argv) 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(); @@ -52,6 +61,62 @@ int main(int argc, char** argv) return 1; } } + //////////////////////////////////////////////////////////// + // Parse the submodules/libraries + //////////////////////////////////////////////////////////// + + // Libraries + std::vector 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 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]; + } + } + } + //////////////////////////////////////////////////////////// // Generate directory structure @@ -92,6 +157,37 @@ int main(int argc, char** argv) 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 //////////////////////////////////////////////////////////// @@ -127,20 +223,56 @@ int main(int argc, char** argv) 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)"; - ofs << "\n\n# specify the project source files"; - ofs << "\nset( " << jpUtils::StringManip::ToUpper(project_name) << "_SRC"; - ofs << "\n\tsrc/main.cpp"; - ofs << "\n)"; + // 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\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)"; + 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(); @@ -283,6 +415,16 @@ int main(int argc, char** argv) 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"; @@ -303,7 +445,7 @@ int main(int argc, char** argv) 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 \"Visual Studio 16 2019\" -A x64"; + ofs << "\n\ncmake -Wno-dev -B build/ -S . -G " << vs_version.c_str() << " -A x64"; ofs.close(); ofs.clear(); @@ -353,6 +495,7 @@ int main(int argc, char** argv) 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(); @@ -374,7 +517,7 @@ int main(int argc, char** argv) ofs << "#! /bin/sh"; ofs << "\n# This script expects to be run from the parent directory"; ofs << "\n# ex. scripts/cmconfig.sh"; - ofs << "\n\ncmake -Wno-dev -DBOX2D_BUILD_TESTBED=OFF -S . -B build/"; + ofs << "\n\ncmake -S . -B build/"; ofs.close(); ofs.clear(); @@ -392,36 +535,155 @@ int main(int argc, char** argv) ofs << "#! /bin/sh"; ofs << "\n# This script expects to be run from the parent directory"; ofs << "\n# ex. scripts/build.sh"; - ofs << "\n\nmake -C build/"; + ofs << "\n\ncmake -C build/"; ofs.close(); ofs.clear(); } //////////////////////////////////////////////////////////// - // Generate Main.cpp + // Generate Main.cpp files //////////////////////////////////////////////////////////// - std::filesystem::path main_file = root / "src/main.cpp"; - ofs = std::ofstream(main_file.string().c_str()); + + // 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& 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: " << main_file.string().c_str(); + std::cout << "\nCould not generate the config script file: " << location.string().c_str(); return 1; } - std::cout << "\nGenerating config script file: " << main_file.string().c_str(); + std::cout << "\nGenerating main.cpp file: " << location.string().c_str(); ofs << "\n\n#include "; 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 " << project_name << " version: \" << " << project_name << "_VERSION_MAJOR << \".\" << " << project_name << "_VERSION_MINOR << \".\" << " << project_name << "_VERSION_PATCH;"; + 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(); - std::cout << "\n\nNew project generated!"; - return 0; } \ No newline at end of file