@ -1,20 +1,48 @@
# include "cmake_generator.hpp"
# include "cmake_generator.hpp"
# include "error.hpp"
# include "literals.hpp"
# include "literals.hpp"
# include <resources/cmkr.hpp>
# include <resources/cmkr.hpp>
# include "fs.hpp"
# include "fs.hpp"
# include "project_parser.hpp"
# include "project_parser.hpp"
# include <cstdio>
# include <cstdio>
# include <fstream>
# include <memory>
# include <memory>
# include <sstream>
# include <sstream>
# include <stdexcept>
# include <stdexcept>
# include <fstream>
namespace cmkr {
namespace cmkr {
namespace gen {
namespace gen {
static std : : string format ( const char * format , tsl : : ordered_map < std : : string , std : : string > variables ) {
/*
Location : CMake / share / cmake - 3.26 / Modules
rg " set \ (CMAKE_(.+)_SOURCE_FILE_EXTENSIONS "
Links :
- https : //gitlab.kitware.com/cmake/cmake/-/issues/24340
- https : //cmake.org/cmake/help/latest/command/enable_language.html
*/
static tsl : : ordered_map < std : : string , std : : vector < std : : string > > known_languages = {
{ " ASM " , { " .s " , " .S " , " .asm " , " .abs " , " .msa " , " .s90 " , " .s43 " , " .s85 " , " .s51 " } } ,
{ " ASM-ATT " , { " .s " , " .asm " } } ,
{ " ASM_MARMASM " , { " .asm " } } ,
{ " ASM_MASM " , { " .asm " } } ,
{ " ASM_NASM " , { " .nasm " , " .asm " } } ,
{ " C " , { " .c " , " .m " } } ,
{ " CSharp " , { " .cs " } } ,
{ " CUDA " , { " .cu " } } ,
{ " CXX " , { " .C " , " .M " , " .c++ " , " .cc " , " .cpp " , " .cxx " , " .m " , " .mm " , " .mpp " , " .CPP " , " .ixx " , " .cppm " } } ,
{ " Fortran " , { " .f " , " .F " , " .fpp " , " .FPP " , " .f77 " , " .F77 " , " .f90 " , " .F90 " , " .for " , " .For " , " .FOR " , " .f95 " , " .F95 " , " .cuf " , " .CUF " } } ,
{ " HIP " , { " .hip " } } ,
{ " ISPC " , { " .ispc " } } ,
{ " Java " , { " .java " } } ,
{ " OBJC " , { " .m " } } ,
{ " OBJCXX " , { " .M " , " .m " , " .mm " } } ,
{ " RC " , { " .rc " , " .RC " } } ,
{ " Swift " , { " .swift " } } ,
} ;
static std : : string format ( const char * format , const tsl : : ordered_map < std : : string , std : : string > & variables ) {
std : : string s = format ;
std : : string s = format ;
for ( const auto & itr : variables ) {
for ( const auto & itr : variables ) {
size_t start_pos = 0 ;
size_t start_pos = 0 ;
@ -90,6 +118,19 @@ static void create_file(const fs::path &path, const std::string &contents) {
ofs < < contents ;
ofs < < contents ;
}
}
static std : : string read_file ( const fs : : path & path ) {
std : : ifstream ifs ( path , std : : ios : : binary ) ;
if ( ! ifs ) {
throw std : : runtime_error ( " Failed to read " + path . string ( ) ) ;
}
std : : string contents ;
ifs . seekg ( 0 , std : : ios : : end ) ;
contents . resize ( ifs . tellg ( ) ) ;
ifs . seekg ( 0 , std : : ios : : beg ) ;
ifs . read ( & contents [ 0 ] , contents . size ( ) ) ;
return contents ;
}
// CMake target name rules: https://cmake.org/cmake/help/latest/policy/CMP0037.html [A-Za-z0-9_.+\-]
// CMake target name rules: https://cmake.org/cmake/help/latest/policy/CMP0037.html [A-Za-z0-9_.+\-]
// TOML bare keys: non-empty strings composed only of [A-Za-z0-9_-]
// TOML bare keys: non-empty strings composed only of [A-Za-z0-9_-]
// We replace all non-TOML bare key characters with _
// We replace all non-TOML bare key characters with _
@ -106,18 +147,82 @@ static std::string escape_project_name(const std::string &name) {
return escaped ;
return escaped ;
}
}
static void generate_gitfile ( const char * gitfile , const std : : vector < std : : string > & desired_lines ) {
// Reference: https://github.com/github/linguist/blob/master/docs/overrides.md#summary
auto lines = desired_lines ;
auto generate = [ & lines ] ( const char * newline ) {
std : : string generated ;
generated + = " # cmkr " ;
generated + = newline ;
for ( const auto & line : lines ) {
generated + = line ;
generated + = newline ;
}
return generated ;
} ;
if ( ! fs : : exists ( gitfile ) ) {
create_file ( gitfile , generate ( " \n " ) ) ;
} else {
auto contents = read_file ( gitfile ) ;
std : : string line ;
auto cr = 0 , lf = 0 ;
auto flush_line = [ & line , & lines ] ( ) {
auto itr = std : : find ( lines . begin ( ) , lines . end ( ) , line ) ;
if ( itr ! = lines . end ( ) ) {
lines . erase ( itr ) ;
}
line . clear ( ) ;
} ;
for ( size_t i = 0 ; i < contents . length ( ) ; i + + ) {
if ( contents [ i ] = = ' \r ' ) {
cr + + ;
continue ;
}
if ( contents [ i ] = = ' \n ' ) {
lf + + ;
flush_line ( ) ;
} else {
line + = contents [ i ] ;
}
}
if ( ! line . empty ( ) ) {
flush_line ( ) ;
}
if ( ! lines . empty ( ) ) {
// Append the cmkr .gitattributes using the detected newline
auto newline = cr = = lf ? " \r \n " : " \n " ;
if ( ! contents . empty ( ) & & contents . back ( ) ! = ' \n ' ) {
contents + = newline ;
}
contents + = newline ;
contents + = generate ( newline ) ;
create_file ( gitfile , contents ) ;
}
}
}
void generate_project ( const std : : string & type ) {
void generate_project ( const std : : string & type ) {
const auto name = escape_project_name ( fs : : current_path ( ) . stem ( ) . string ( ) ) ;
const auto name = escape_project_name ( fs : : current_path ( ) . stem ( ) . string ( ) ) ;
if ( fs : : exists ( fs : : current_path ( ) / " cmake.toml " ) ) {
if ( fs : : exists ( fs : : current_path ( ) / " cmake.toml " ) ) {
throw std : : runtime_error ( " Cannot initialize a project when cmake.toml already exists! " ) ;
throw std : : runtime_error ( " Cannot initialize a project when cmake.toml already exists! " ) ;
}
}
// Check if the folder is empty before creating any files
auto is_empty = fs : : is_empty ( fs : : current_path ( ) ) ;
// Automatically generate .gitattributes to not skew the statistics of the repo
generate_gitfile ( " .gitattributes " , { " /**/CMakeLists.txt linguist-generated " , " /**/cmkr.cmake linguist-vendored " } ) ;
// Generate .gitignore with reasonable defaults for CMake
generate_gitfile ( " .gitignore " , { " build*/ " , " cmake-build*/ " , " CMakerLists.txt " , " CMakeLists.txt.user " } ) ;
tsl : : ordered_map < std : : string , std : : string > variables = {
tsl : : ordered_map < std : : string , std : : string > variables = {
{ " @name " , name } ,
{ " @name " , name } ,
{ " @type " , type } ,
{ " @type " , type } ,
} ;
} ;
if ( ! fs : : is_empty ( fs : : current_path ( ) ) ) {
if ( ! is_empty) {
// Make a backup of an existing CMakeLists.txt if it exists
// Make a backup of an existing CMakeLists.txt if it exists
std : : error_code ec ;
std : : error_code ec ;
fs : : rename ( " CMakeLists.txt " , " CMakeLists.txt.bak " , ec ) ;
fs : : rename ( " CMakeLists.txt " , " CMakeLists.txt.bak " , ec ) ;
@ -143,7 +248,7 @@ void generate_project(const std::string &type) {
struct CommandEndl {
struct CommandEndl {
std : : stringstream & ss ;
std : : stringstream & ss ;
CommandEndl( std : : stringstream & ss ) : ss ( ss ) {
explicit CommandEndl( std : : stringstream & ss ) : ss ( ss ) {
}
}
void endl ( ) {
void endl ( ) {
ss < < ' \n ' ;
ss < < ' \n ' ;
@ -152,7 +257,7 @@ struct CommandEndl {
struct RawArg {
struct RawArg {
RawArg ( ) = default ;
RawArg ( ) = default ;
RawArg( std : : string arg ) : arg ( std : : move ( arg ) ) {
explicit RawArg( std : : string arg ) : arg ( std : : move ( arg ) ) {
}
}
std : : string arg ;
std : : string arg ;
@ -187,7 +292,7 @@ struct Command {
// https://cmake.org/cmake/help/latest/manual/cmake-language.7.html#unquoted-argument
// https://cmake.org/cmake/help/latest/manual/cmake-language.7.html#unquoted-argument
// NOTE: Normally '/' does not require quoting according to the documentation but this has been the case here
// NOTE: Normally '/' does not require quoting according to the documentation but this has been the case here
// previously, so for backwards compatibility its still here.
// previously, so for backwards compatibility its still here.
if ( str . find_first_of ( " ()# \" \\ '> |/; " ) = = st r. npos )
if ( str . find_first_of ( " ()# \" \\ '> |/; " ) = = st d: : string : : npos )
return str ;
return str ;
std : : string result ;
std : : string result ;
result + = " \" " ;
result + = " \" " ;
@ -336,14 +441,15 @@ static std::string tolf(const std::string &str) {
}
}
}
}
return result ;
return result ;
} ;
}
struct Generator {
struct Generator {
Generator ( const parser : : Project & project ) : project ( project ) {
Generator ( const parser : : Project & project , fs : : path path ) : project ( project ) , path ( std : : move ( path ) ) {
}
}
Generator ( const Generator & ) = delete ;
Generator ( const Generator & ) = delete ;
const parser : : Project & project ;
const parser : : Project & project ;
fs : : path path ;
std : : stringstream ss ;
std : : stringstream ss ;
int indent = 0 ;
int indent = 0 ;
@ -373,8 +479,8 @@ struct Generator {
void inject_includes ( const std : : vector < std : : string > & includes ) {
void inject_includes ( const std : : vector < std : : string > & includes ) {
if ( ! includes . empty ( ) ) {
if ( ! includes . empty ( ) ) {
for ( const auto & file : includes ) {
for ( const auto & file : includes ) {
if ( ! fs : : is_regular_file( file ) ) {
if ( ! fs : : exists( path / file ) ) {
throw std : : runtime_error ( " Include '" + file + " ' does not exist " ) ;
throw std : : runtime_error ( " Include not found: " + file ) ;
}
}
cmd ( " include " ) ( file ) ;
cmd ( " include " ) ( file ) ;
}
}
@ -387,7 +493,7 @@ struct Generator {
throw std : : runtime_error ( " Detected additional \" at the end of cmake block " ) ;
throw std : : runtime_error ( " Detected additional \" at the end of cmake block " ) ;
}
}
auto cmake_lf = tolf ( cmake ) ;
auto cmake_lf = tolf ( cmake ) ;
while ( cmake_lf . back ( ) = = ' \n ' )
while ( ! cmake_lf . empty ( ) & & cmake_lf . back ( ) = = ' \n ' )
cmake_lf . pop_back ( ) ;
cmake_lf . pop_back ( ) ;
bool did_indent = false ;
bool did_indent = false ;
for ( char ch : cmake_lf ) {
for ( char ch : cmake_lf ) {
@ -456,28 +562,26 @@ struct ConditionScope {
} ;
} ;
static bool vcpkg_valid_identifier ( const std : : string & name ) {
static bool vcpkg_valid_identifier ( const std : : string & name ) {
// prn|aux|nul|con|lpt[1-9]|com[1-9]|core|default
auto is_reserved = [ ] ( const std : : string & s ) {
if ( s = = " prn " | | s = = " aux " | | s = = " nul " | | s = = " con " | | s = = " core " | | s = = " default " ) {
return true ;
}
if ( s . length ( ) = = 4 & & ( s . compare ( 0 , 3 , " lpt " ) = = 0 | | s . compare ( 0 , 3 , " com " ) = = 0 ) & & ( s [ 3 ] > = ' 1 ' & & s [ 3 ] < = ' 9 ' ) ) {
return true ;
}
return false ;
} ;
// [a-z0-9]+(-[a-z0-9]+)*
// [a-z0-9]+(-[a-z0-9]+)*
auto is_identifier = [ ] ( const std : : string & s ) {
for ( size_t i = 0 ; i < name . length ( ) ; i + + ) {
for ( size_t i = 0 ; i < s . length ( ) ; i + + ) {
auto c = name [ i ] ;
auto c = s [ i ] ;
if ( ( c > = ' a ' & & c < = ' z ' ) | | ( c > = ' 0 ' & & c < = ' 9 ' ) | | ( i > 0 & & c = = ' - ' ) ) {
if ( ( c > = ' a ' & & c < = ' z ' ) | | ( c > = ' 0 ' & & c < = ' 9 ' ) | | ( i > 0 & & c = = ' - ' ) ) {
continue ;
continue ;
}
}
return false ;
return false ;
}
}
return true ;
return true ;
} ;
}
return is_identifier ( name ) & & ! is_reserved ( name ) ;
static bool vcpkg_identifier_reserved ( const std : : string & name ) {
// prn|aux|nul|con|lpt[1-9]|com[1-9]|core|default
if ( name = = " prn " | | name = = " aux " | | name = = " nul " | | name = = " con " | | name = = " core " | | name = = " default " ) {
return true ;
}
if ( name . length ( ) = = 4 & & ( name . compare ( 0 , 3 , " lpt " ) = = 0 | | name . compare ( 0 , 3 , " com " ) = = 0 ) & & ( name [ 3 ] > = ' 1 ' & & name [ 3 ] < = ' 9 ' ) ) {
return true ;
}
return false ;
}
}
static std : : string vcpkg_escape_identifier ( const std : : string & name ) {
static std : : string vcpkg_escape_identifier ( const std : : string & name ) {
@ -494,10 +598,12 @@ static std::string vcpkg_escape_identifier(const std::string &name) {
escaped + = std : : tolower ( ch ) ;
escaped + = std : : tolower ( ch ) ;
}
}
if ( ! vcpkg_valid_identifier ( escaped ) ) {
if ( ! vcpkg_valid_identifier ( escaped ) ) {
throw std : : runtime_error ( " The escaped project name ' " + escaped + " ' is not usable with [vcpkg] " ) ;
throw std : : runtime_error ( " The escaped project name ' " + escaped + " ' is not usable with [vcpkg] " ) ;
}
}
if ( vcpkg_identifier_reserved ( escaped ) ) {
throw std : : runtime_error ( " The escaped project name ' " + escaped + " ' is a reserved name [vcpkg] " ) ;
}
return escaped ;
return escaped ;
}
}
@ -510,7 +616,18 @@ void generate_cmake(const char *path, const parser::Project *parent_project) {
auto is_root_project = parent_project = = nullptr ;
auto is_root_project = parent_project = = nullptr ;
parser : : Project project ( parent_project , path , false ) ;
parser : : Project project ( parent_project , path , false ) ;
Generator gen ( project ) ;
for ( auto const & lang : project . project_languages ) {
if ( known_languages . find ( lang ) = = known_languages . end ( ) ) {
if ( project . project_allow_unknown_languages ) {
printf ( " [warning] Unknown language '%s' specified \n " , lang . c_str ( ) ) ;
} else {
throw std : : runtime_error ( " Unknown language ' " + lang + " ' specified " ) ;
}
}
}
Generator gen ( project , path ) ;
// Helper lambdas for more convenient CMake generation
// Helper lambdas for more convenient CMake generation
auto & ss = gen . ss ;
auto & ss = gen . ss ;
@ -616,7 +733,13 @@ void generate_cmake(const char *path, const parser::Project *parent_project) {
if ( ! project . options . empty ( ) ) {
if ( ! project . options . empty ( ) ) {
comment ( " Options " ) ;
comment ( " Options " ) ;
for ( const auto & opt : project . options ) {
for ( const auto & opt : project . options ) {
cmd ( " option " ) ( opt . name , RawArg ( Command : : quote ( opt . comment ) ) , opt . val ? " ON " : " OFF " ) ;
std : : string default_val ;
if ( opt . value . index ( ) = = 0 ) {
default_val = mpark : : get < 0 > ( opt . value ) ? " ON " : " OFF " ;
} else {
default_val = mpark : : get < 1 > ( opt . value ) ;
}
cmd ( " option " ) ( opt . name , RawArg ( Command : : quote ( opt . help ) ) , default_val ) ;
}
}
endl ( ) ;
endl ( ) ;
}
}
@ -625,16 +748,16 @@ void generate_cmake(const char *path, const parser::Project *parent_project) {
comment ( " Variables " ) ;
comment ( " Variables " ) ;
for ( const auto & set : project . variables ) {
for ( const auto & set : project . variables ) {
std : : string set_val ;
std : : string set_val ;
if ( set . val . index ( ) = = 1 ) {
if ( set . val ue . index ( ) = = 1 ) {
set_val = mpark : : get < 1 > ( set . val ) ;
set_val = mpark : : get < 1 > ( set . val ue ) ;
} else {
} else {
set_val = mpark : : get < 0 > ( set . val ) ? " ON " : " OFF " ;
set_val = mpark : : get < 0 > ( set . val ue ) ? " ON " : " OFF " ;
}
}
if ( set . cache ) {
if ( set . cache ) {
auto typ = set . val . index ( ) = = 1 ? " STRING " : " BOOL " ;
auto typ = set . val ue . index ( ) = = 1 ? " STRING " : " BOOL " ;
auto force = set . force ? " FORCE " : " " ;
auto force = set . force ? " FORCE " : " " ;
cmd ( " set " ) ( set . name , set_val , typ , set . comment , force ) ;
cmd ( " set " ) ( set . name , set_val , typ , set . help , force ) ;
} else {
} else {
cmd ( " set " ) ( set . name , set_val ) ;
cmd ( " set " ) ( set . name , set_val ) ;
}
}
@ -662,7 +785,11 @@ void generate_cmake(const char *path, const parser::Project *parent_project) {
gen . conditional_includes ( project . include_after ) ;
gen . conditional_includes ( project . include_after ) ;
gen . conditional_cmake ( project . cmake_after ) ;
gen . conditional_cmake ( project . cmake_after ) ;
if ( ! project . vcpkg . packages . empty ( ) ) {
if ( project . vcpkg . enabled ( ) ) {
if ( ! is_root_project ) {
throw std : : runtime_error ( " [vcpkg] is only supported in the root project " ) ;
}
// Allow the user to specify a url or derive it from the version
// Allow the user to specify a url or derive it from the version
auto url = project . vcpkg . url ;
auto url = project . vcpkg . url ;
auto version_name = url ;
auto version_name = url ;
@ -675,7 +802,8 @@ void generate_cmake(const char *path, const parser::Project *parent_project) {
}
}
// Show a nicer error than vcpkg when specifying an invalid package name
// Show a nicer error than vcpkg when specifying an invalid package name
for ( const auto & package : project . vcpkg . packages ) {
const auto & packages = project . vcpkg . packages ;
for ( const auto & package : packages ) {
if ( ! vcpkg_valid_identifier ( package . name ) ) {
if ( ! vcpkg_valid_identifier ( package . name ) ) {
throw std : : runtime_error ( " Invalid [vcpkg].packages name ' " + package . name + " ' (needs to be lowercase alphanumeric) " ) ;
throw std : : runtime_error ( " Invalid [vcpkg].packages name ' " + package . name + " ' (needs to be lowercase alphanumeric) " ) ;
}
}
@ -685,6 +813,12 @@ void generate_cmake(const char *path, const parser::Project *parent_project) {
// clang-format off
// clang-format off
cmd ( " if " ) ( " CMKR_ROOT_PROJECT " , " AND " , " NOT " , " CMKR_DISABLE_VCPKG " ) ;
cmd ( " if " ) ( " CMKR_ROOT_PROJECT " , " AND " , " NOT " , " CMKR_DISABLE_VCPKG " ) ;
cmd ( " include " ) ( " FetchContent " ) ;
cmd ( " include " ) ( " FetchContent " ) ;
comment ( " Fix warnings about DOWNLOAD_EXTRACT_TIMESTAMP " ) ;
// clang-format off
cmd ( " if " ) ( " POLICY " , " CMP0135 " ) ;
cmd ( " cmake_policy " ) ( " SET " , " CMP0135 " , " NEW " ) ;
cmd ( " endif " ) ( ) ;
// clang-format on
cmd ( " message " ) ( " STATUS " , " Fetching vcpkg ( " + version_name + " )... " ) ;
cmd ( " message " ) ( " STATUS " , " Fetching vcpkg ( " + version_name + " )... " ) ;
cmd ( " FetchContent_Declare " ) ( " vcpkg " , " URL " , url ) ;
cmd ( " FetchContent_Declare " ) ( " vcpkg " , " URL " , url ) ;
// Not using FetchContent_MakeAvailable here in case vcpkg adds CMakeLists.txt
// Not using FetchContent_MakeAvailable here in case vcpkg adds CMakeLists.txt
@ -709,16 +843,21 @@ void generate_cmake(const char *path, const parser::Project *parent_project) {
" dependencies " : [
" dependencies " : [
) " ;
) " ;
const auto & packages = project . vcpkg . packages ;
for ( size_t i = 0 ; i < packages . size ( ) ; i + + ) {
for ( size_t i = 0 ; i < packages . size ( ) ; i + + ) {
const auto & package = packages [ i ] ;
const auto & package = packages [ i ] ;
const auto & features = package . features ;
const auto & features = package . features ;
if ( ! vcpkg_valid_identifier ( package . name ) ) {
if ( ! vcpkg_valid_identifier ( package . name ) ) {
throw std : : runtime_error ( " Invalid vcpkg package name ' " + package . name + " ' " ) ;
throw std : : runtime_error ( " Invalid vcpkg package name ' " + package . name + " ', name is not valid " ) ;
}
if ( vcpkg_identifier_reserved ( package . name ) ) {
throw std : : runtime_error ( " Invalid vcpkg package name ' " + package . name + " ', name is reserved " ) ;
}
}
for ( const auto & feature : features ) {
for ( const auto & feature : features ) {
if ( ! vcpkg_valid_identifier ( feature ) ) {
if ( ! vcpkg_valid_identifier ( feature ) ) {
throw std : : runtime_error ( " Invalid vcpkg package feature ' " + feature + " ' " ) ;
throw std : : runtime_error ( " Invalid vcpkg package feature ' " + feature + " ', name is not valid " ) ;
}
if ( vcpkg_identifier_reserved ( feature ) ) {
throw std : : runtime_error ( " Invalid vcpkg package feature ' " + feature + " ', name is reserved " ) ;
}
}
}
}
if ( features . empty ( ) ) {
if ( features . empty ( ) ) {
@ -763,20 +902,32 @@ void generate_cmake(const char *path, const parser::Project *parent_project) {
if ( ! project . contents . empty ( ) ) {
if ( ! project . contents . empty ( ) ) {
cmd ( " include " ) ( " FetchContent " ) . endl ( ) ;
cmd ( " include " ) ( " FetchContent " ) . endl ( ) ;
if ( ! project . root ( ) - > vcpkg . enabled ( ) ) {
comment ( " Fix warnings about DOWNLOAD_EXTRACT_TIMESTAMP " ) ;
// clang-format off
cmd ( " if " ) ( " POLICY " , " CMP0135 " ) ;
cmd ( " cmake_policy " ) ( " SET " , " CMP0135 " , " NEW " ) ;
cmd ( " endif " ) ( ) ;
// clang-format on
}
for ( const auto & content : project . contents ) {
for ( const auto & content : project . contents ) {
ConditionScope cs ( gen , content . condition ) ;
ConditionScope cs ( gen , content . condition ) ;
gen . conditional_includes ( content . include_before ) ;
gen . conditional_includes ( content . include_before ) ;
gen . conditional_cmake ( content . cmake_before ) ;
gen . conditional_cmake ( content . cmake_before ) ;
std : : string version_info = " " ;
std : : string version_info ;
if ( content . arguments . contains ( " GIT_TAG " ) ) {
if ( content . arguments . contains ( " GIT_TAG " ) ) {
version_info = " ( " + content . arguments . at ( " GIT_TAG " ) + " ) " ;
version_info = " ( " + content . arguments . at ( " GIT_TAG " ) + " ) " ;
} else if ( content . arguments . contains ( " SVN_REVISION " ) ) {
} else if ( content . arguments . contains ( " SVN_REVISION " ) ) {
version_info = " ( " + content . arguments . at ( " SVN_REVISION " ) + " ) " ;
version_info = " ( " + content . arguments . at ( " SVN_REVISION " ) + " ) " ;
}
}
cmd ( " message " ) ( " STATUS " , " Fetching " + content . name + version_info + " ... " ) ;
cmd ( " message " ) ( " STATUS " , " Fetching " + content . name + version_info + " ... " ) ;
if ( content . system ) {
cmd ( " FetchContent_Declare " ) ( content . name , " SYSTEM " , content . arguments ) ;
} else {
cmd ( " FetchContent_Declare " ) ( content . name , content . arguments ) ;
cmd ( " FetchContent_Declare " ) ( content . name , content . arguments ) ;
}
cmd ( " FetchContent_MakeAvailable " ) ( content . name ) . endl ( ) ;
cmd ( " FetchContent_MakeAvailable " ) ( content . name ) . endl ( ) ;
gen . conditional_includes ( content . include_after ) ;
gen . conditional_includes ( content . include_after ) ;
@ -837,10 +988,38 @@ void generate_cmake(const char *path, const parser::Project *parent_project) {
gen . conditional_cmake ( subdir . cmake_after ) ;
gen . conditional_cmake ( subdir . cmake_after ) ;
}
}
// The implicit default is ["C", "CXX"], so make sure this list isn't
// empty or projects without languages explicitly defined will error.
auto project_languages = project . project_languages ;
if ( project_languages . empty ( ) )
project_languages = { " C " , " CXX " } ;
// All acceptable extensions based off our given languages.
tsl : : ordered_set < std : : string > project_extensions ;
for ( const auto & language : project_languages ) {
auto itr = known_languages . find ( language ) ;
if ( itr ! = known_languages . end ( ) ) {
project_extensions . insert ( itr - > second . begin ( ) , itr - > second . end ( ) ) ;
}
}
auto contains_language_source = [ & project_extensions ] ( const std : : vector < std : : string > & sources ) {
for ( const auto & source : sources ) {
auto extension = fs : : path ( source ) . extension ( ) . string ( ) ;
if ( project_extensions . count ( extension ) > 0 ) {
return true ;
}
}
return false ;
} ;
if ( ! project . targets . empty ( ) ) {
if ( ! project . targets . empty ( ) ) {
auto project_root = project . root ( ) ;
auto project_root = project . root ( ) ;
for ( size_t i = 0 ; i < project . targets . size ( ) ; i + + ) {
for ( size_t i = 0 ; i < project . targets . size ( ) ; i + + ) {
const auto & target = project . targets [ i ] ;
const auto & target = project . targets [ i ] ;
auto throw_target_error = [ & target ] ( const std : : string & message ) { throw std : : runtime_error ( " [target. " + target . name + " ] " + message ) ; } ;
const parser : : Template * tmplate = nullptr ;
const parser : : Template * tmplate = nullptr ;
std : : unique_ptr < ConditionScope > tmplate_cs { } ;
std : : unique_ptr < ConditionScope > tmplate_cs { } ;
@ -944,12 +1123,46 @@ void generate_cmake(const char *path, const parser::Project *parent_project) {
auto sources = expand_cmake_paths ( condition_sources , path , is_root_project ) ;
auto sources = expand_cmake_paths ( condition_sources , path , is_root_project ) ;
if ( sources . empty ( ) ) {
if ( sources . empty ( ) ) {
auto source_key = condition . empty ( ) ? " sources " : ( condition + " .sources " ) ;
auto source_key = condition . empty ( ) ? " sources " : ( condition + " .sources " ) ;
throw std : : runtime_error ( target . name + " " + source_key + " wildcard found 0 files " ) ;
throw_target_error ( source_key + " wildcard found 0 files " ) ;
}
}
// Make sure there are source files for the languages used by the project
switch ( target . type ) {
case parser : : target_executable :
case parser : : target_library :
case parser : : target_shared :
case parser : : target_static :
case parser : : target_object :
if ( ! contains_language_source ( sources ) ) {
std : : string extensions ;
for ( const auto & language : project_extensions ) {
if ( ! extensions . empty ( ) ) {
extensions + = " " ;
}
extensions + = language ;
}
throw_target_error ( " No sources found with valid extensions ( " + extensions + " ) " ) ;
}
// Make sure relative source files exist
for ( const auto & source : sources ) {
auto var_index = source . find ( " ${ " ) ;
if ( var_index ! = std : : string : : npos )
continue ;
const auto & source_path = fs : : path ( path ) / source ;
if ( ! fs : : exists ( source_path ) ) {
throw_target_error ( " Source file not found: " + source ) ;
}
}
break ;
default :
break ;
}
if ( sources_with_set ) {
if ( sources_with_set ) {
// This is a sanity check to make sure the unconditional sources are first
// This is a sanity check to make sure the unconditional sources are first
if ( ! condition . empty ( ) ) {
if ( ! condition . empty ( ) ) {
throw std : : runtime_error ( " Unreachable code, make sure unconditional sources are first " ) ;
throw_target _error( " Unreachable code, make sure unconditional sources are first " ) ;
}
}
cmd ( " set " ) ( sources_var , sources ) ;
cmd ( " set " ) ( sources_var , sources ) ;
sources_with_set = false ;
sources_with_set = false ;
@ -962,7 +1175,7 @@ void generate_cmake(const char *path, const parser::Project *parent_project) {
if ( tmplate ! = nullptr ) {
if ( tmplate ! = nullptr ) {
if ( target_type ! = parser : : target_template ) {
if ( target_type ! = parser : : target_template ) {
throw std : : runtime _error( " Unreachable code, unexpected target type for template " ) ;
throw_target _error( " Unreachable code, unexpected target type for template " ) ;
}
}
target_type = tmplate - > outline . type ;
target_type = tmplate - > outline . type ;
}
}
@ -1010,7 +1223,7 @@ void generate_cmake(const char *path, const parser::Project *parent_project) {
target_scope = " PUBLIC " ;
target_scope = " PUBLIC " ;
break ;
break ;
default :
default :
throw std : : runtime _error( " Unimplemented enum value " ) ;
throw_target _error( " Unimplemented enum value " ) ;
}
}
// Handle custom add commands from templates.
// Handle custom add commands from templates.
@ -1091,7 +1304,7 @@ void generate_cmake(const char *path, const parser::Project *parent_project) {
for ( const auto & propItr : properties ) {
for ( const auto & propItr : properties ) {
if ( propItr . first = = " MSVC_RUNTIME_LIBRARY " ) {
if ( propItr . first = = " MSVC_RUNTIME_LIBRARY " ) {
if ( project_root - > project_msvc_runtime = = parser : : msvc_last ) {
if ( project_root - > project_msvc_runtime = = parser : : msvc_last ) {
throw std : : runtime _error( " You cannot set [target]. msvc-runtime without setting the root [project].msvc-runtime" ) ;
throw_target _error( " You cannot set msvc-runtime without setting the root [project].msvc-runtime" ) ;
}
}
}
}
}
}
@ -1134,7 +1347,12 @@ void generate_cmake(const char *path, const parser::Project *parent_project) {
}
}
auto working_directory = std : : make_pair ( " WORKING_DIRECTORY " , dir ) ;
auto working_directory = std : : make_pair ( " WORKING_DIRECTORY " , dir ) ;
auto command = std : : make_pair ( " COMMAND " , test . command ) ;
auto command = std : : make_pair ( " COMMAND " , test . command ) ;
auto arguments = std : : make_pair ( " " , test . arguments ) ;
// Transform the provided arguments into raw arguments to prevent them from being quoted when the generator runs
std : : vector < RawArg > raw_arguments { } ;
for ( const auto & argument : test . arguments )
raw_arguments . emplace_back ( argument ) ;
auto arguments = std : : make_pair ( " " , raw_arguments ) ;
ConditionScope cs ( gen , test . condition ) ;
ConditionScope cs ( gen , test . condition ) ;
cmd ( " add_test " ) ( name , configurations , working_directory , command , arguments ) . endl ( ) ;
cmd ( " add_test " ) ( name , configurations , working_directory , command , arguments ) . endl ( ) ;
}
}
@ -1165,10 +1383,19 @@ void generate_cmake(const char *path, const parser::Project *parent_project) {
}
}
}
}
// Fetch the generated CMakeLists.txt output from the stringstream buffer
auto generated_cmake = ss . str ( ) ;
// Make sure the file ends in a single newline
while ( ! generated_cmake . empty ( ) & & std : : isspace ( generated_cmake . back ( ) ) ) {
generated_cmake . pop_back ( ) ;
}
generated_cmake + = ' \n ' ;
// Generate CMakeLists.txt
// Generate CMakeLists.txt
auto list_path = fs : : path ( path ) / " CMakeLists.txt " ;
auto list_path = fs : : path ( path ) / " CMakeLists.txt " ;
auto should_regenerate = [ & list_path , & ss ] ( ) {
auto should_regenerate = [ & list_path , & generated_cmake ] ( ) {
if ( ! fs : : exists ( list_path ) )
if ( ! fs : : exists ( list_path ) )
return true ;
return true ;
@ -1178,11 +1405,11 @@ void generate_cmake(const char *path, const parser::Project *parent_project) {
}
}
std : : string data ( ( std : : istreambuf_iterator < char > ( ifs ) ) , std : : istreambuf_iterator < char > ( ) ) ;
std : : string data ( ( std : : istreambuf_iterator < char > ( ifs ) ) , std : : istreambuf_iterator < char > ( ) ) ;
return data ! = ss. str ( ) ;
return data ! = generated_cmake ;
} ( ) ;
} ( ) ;
if ( should_regenerate ) {
if ( should_regenerate ) {
create_file ( list_path , ss. str ( ) ) ;
create_file ( list_path , generated_cmake ) ;
}
}
auto generate_subdir = [ path , & project ] ( const fs : : path & sub ) {
auto generate_subdir = [ path , & project ] ( const fs : : path & sub ) {