@ -54,57 +54,85 @@ static std::string format(const char *format, const tsl::ordered_map<std::string
return s ;
}
static std : : vector < std : : string > expand_cmake_path ( const fs : : path & name , const fs : : path & toml_dir , bool is_root_project ) {
std : : vector < std : : string > temp ;
auto extract_suffix = [ ] ( const fs : : path & base , const fs : : path & full ) {
auto fullpath = full . string ( ) ;
auto base_len = base . string ( ) . length ( ) ;
auto delet = fullpath . substr ( base_len + 1 , fullpath . length ( ) - base_len ) ;
return delet ;
static std : : vector < fs : : path > expand_cmake_path ( const fs : : path & source_path , const fs : : path & toml_dir , bool is_root_project ) {
auto is_subdir = [ ] ( fs : : path p , const fs : : path & root ) {
while ( true ) {
if ( p = = root ) {
return true ;
}
auto parent = p . parent_path ( ) ;
if ( parent = = p ) {
break ;
}
p = parent ;
}
return false ;
} ;
if ( ! is_subdir ( fs : : absolute ( toml_dir / source_path ) , toml_dir ) ) {
throw std : : runtime_error ( " Path traversal is not allowed: " + source_path . string ( ) ) ;
}
auto stem = name . filename ( ) . stem ( ) . string ( ) ;
auto ext = name . extension ( ) ;
// Split the path at the first period (since fs::path::stem() and fs::path::extension() split at the last period)
std : : string stem , extension ;
auto filename = source_path . filename ( ) . string ( ) ;
auto dot_position = filename . find ( ' . ' ) ;
if ( dot_position ! = std : : string : : npos ) {
stem = filename . substr ( 0 , dot_position ) ;
extension = filename . substr ( dot_position ) ;
} else {
stem = filename ;
}
if ( is_root_project & & stem = = " ** " & & name = = name . filename ( ) ) {
throw std : : runtime_error ( " Recursive globbing not allowed in project root: " + name . string ( ) ) ;
if ( is_root_project & & stem = = " ** " & & ! source_path . has_parent_path ( ) ) {
throw std : : runtime_error ( " Recursive globbing not allowed in project root: " + source_path . string ( ) ) ;
}
auto has_extension = [ ] ( const fs : : path & file_path , const std : : string & extension ) {
auto path = file_path . string ( ) ;
return path . rfind ( extension ) = = path . length ( ) - extension . length ( ) ;
} ;
std : : vector < fs : : path > paths ;
if ( stem = = " * " ) {
for ( const auto & f : fs : : directory_iterator ( toml_dir / name . parent_path ( ) , fs : : directory_options : : follow_directory_symlink ) ) {
if ( ! f . is_directory ( ) & & f . path ( ) . extension ( ) = = ext ) {
temp . push_back ( extract_suffix ( toml_dir , f ) ) ;
for ( const auto & f : fs : : directory_iterator ( toml_dir / source_path . parent_path ( ) , fs : : directory_options : : follow_directory_symlink ) ) {
if ( ! f . is_directory ( ) & & has_extension( f . path ( ) , extension ) ) {
paths. push_back ( fs : : relative ( f , toml_dir ) ) ;
}
}
} else if ( stem = = " ** " ) {
for ( const auto & f : fs : : recursive_directory_iterator ( toml_dir / name . parent_path ( ) , fs : : directory_options : : follow_directory_symlink ) ) {
if ( ! f . is_directory ( ) & & f . path ( ) . extension ( ) = = ext ) {
temp . push_back ( extract_suffix ( toml_dir , f . path ( ) ) ) ;
for ( const auto & f :
fs : : recursive_directory_iterator ( toml_dir / source_path . parent_path ( ) , fs : : directory_options : : follow_directory_symlink ) ) {
if ( ! f . is_directory ( ) & & has_extension ( f . path ( ) , extension ) ) {
paths . push_back ( fs : : relative ( f , toml_dir ) ) ;
}
}
} else {
temp . push_back ( name . string ( ) ) ;
}
// Normalize all paths to work with CMake (it needs a / on Windows as well)
for ( auto & path : temp ) {
std : : replace ( path . begin ( ) , path . end ( ) , ' \\ ' , ' / ' ) ;
paths . push_back ( source_path ) ;
}
// Sort paths alphabetically for consistent cross-OS generation
std : : sort ( temp . begin ( ) , temp . end ( ) ) ;
return temp ;
return paths ;
}
static std : : vector < std : : string > expand_cmake_paths ( const std : : vector < std : : string > & sources , const fs : : path & toml_dir , bool is_root_project ) {
// TODO: add duplicate checking
std : : vector < std : : string > result ;
std : : vector < std : : string > paths ;
for ( const auto & src : sources ) {
auto expanded = expand_cmake_path ( src , toml_dir , is_root_project ) ;
for ( const auto & f : expanded ) {
result. push_back ( f ) ;
paths. push_back ( f . string ( ) ) ;
}
}
return result ;
// Normalize all paths to work with CMake (it needs a / on Windows as well)
for ( auto & path : paths ) {
std : : replace ( path . begin ( ) , path . end ( ) , ' \\ ' , ' / ' ) ;
}
// Sort paths alphabetically for consistent cross-OS generation
std : : sort ( paths . begin ( ) , paths . end ( ) ) ;
// TODO: remove duplicates
return paths ;
}
static void create_file ( const fs : : path & path , const std : : string & contents ) {
@ -514,15 +542,13 @@ struct Generator {
if ( ! value . empty ( ) ) {
for ( const auto & itr : value ) {
const auto & condition = itr . first ;
if ( ! condition . empty ( ) ) {
cmd ( " if " , condition ) ( RawArg ( project . conditions . at ( condition ) ) ) ;
}
auto endif = if_condition ( condition ) ;
if ( ! itr . second . empty ( ) ) {
fn ( condition , itr . second ) ;
}
if ( ! condition . empty ( ) ) {
if ( endif ) {
cmd ( " endif " ) ( ) . endl ( ) ;
} else if ( ! itr . second . empty ( ) ) {
endl ( ) ;
@ -538,6 +564,68 @@ struct Generator {
void conditional_cmake ( const parser : : Condition < std : : string > & cmake ) {
handle_condition ( cmake , [ this ] ( const std : : string & , const std : : string & cmake ) { inject_cmake ( cmake ) ; } ) ;
}
bool if_condition ( const std : : string & condition ) {
if ( condition . empty ( ) ) {
return false ;
}
auto found = project . conditions . find ( condition ) ;
if ( found = = project . conditions . end ( ) ) {
if ( cmkr : : parser : : Project : : is_condition_name ( condition ) ) {
// NOTE: this should have been caught by the parser already
throw std : : runtime_error ( " Condition ' " + condition + " ' is not defined " ) ;
}
cmd ( " if " , " NOTE: unnamed condition " ) ( RawArg ( cmake_condition ( condition ) ) ) ;
} else {
cmd ( " if " , condition ) ( RawArg ( cmake_condition ( found - > second ) ) ) ;
}
return true ;
}
private :
std : : string cmake_condition ( const std : : string & condition ) {
// HACK: this replaces '$<name>' with the value of the 'name' condition. We can safely
// reuse the generator expression syntax, because it is not valid in CMake conditions.
// TODO: properly handle quoted arguments (using a simple state machine):
// https://cmake.org/cmake/help/latest/manual/cmake-language.7.html#quoted-argument
std : : string result = " " ;
bool in_replacement = false ;
std : : string temp ;
for ( size_t i = 0 ; i < condition . length ( ) ; i + + ) {
if ( in_replacement ) {
if ( condition [ i ] = = ' > ' ) {
in_replacement = false ;
if ( temp . empty ( ) ) {
throw std : : runtime_error ( " Empty replacement in condition ' " + condition + " ' " ) ;
}
auto found = project . conditions . find ( temp ) ;
if ( found = = project . conditions . end ( ) ) {
throw std : : runtime_error ( " Unknown condition ' " + temp + " ' in replacement " ) ;
}
auto has_space = found - > second . find ( ' ' ) ! = std : : string : : npos ;
if ( has_space ) {
result + = ' ( ' ;
}
result + = found - > second ;
if ( has_space ) {
result + = ' ) ' ;
}
temp . clear ( ) ;
} else {
temp + = condition [ i ] ;
}
} else if ( condition [ i ] = = ' $ ' & & i + 1 < condition . length ( ) & & condition [ i + 1 ] = = ' < ' ) {
i + + ;
in_replacement = true ;
} else {
result + = condition [ i ] ;
}
}
if ( ! temp . empty ( ) ) {
throw std : : runtime_error ( " Unterminated replacement in condition ' " + condition + " ' " ) ;
}
return result ;
}
} ;
struct ConditionScope {
@ -545,10 +633,7 @@ struct ConditionScope {
bool endif = false ;
ConditionScope ( Generator & gen , const std : : string & condition ) : gen ( gen ) {
if ( ! condition . empty ( ) ) {
gen . cmd ( " if " , condition ) ( RawArg ( gen . project . conditions . at ( condition ) ) ) ;
endif = true ;
}
endif = gen . if_condition ( condition ) ;
}
ConditionScope ( const ConditionScope & ) = delete ;
@ -596,7 +681,10 @@ static std::string vcpkg_escape_identifier(const std::string &name) {
ch = ' - ' ;
}
escaped + = std : : tolower ( ch ) ;
if ( ch > = ' A ' & & ch < = ' Z ' ) {
ch + = ( ' a ' - ' A ' ) ;
}
escaped + = ch ;
}
if ( ! vcpkg_valid_identifier ( escaped ) ) {
throw std : : runtime_error ( " The escaped project name ' " + escaped + " ' is not usable with [vcpkg] " ) ;
@ -617,7 +705,7 @@ void generate_cmake(const char *path, const parser::Project *parent_project) {
parser : : Project project ( parent_project , path , false ) ;
for ( auto const & lang : project . project_languages ) {
for ( const auto & 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 ( ) ) ;
@ -643,23 +731,6 @@ void generate_cmake(const char *path, const parser::Project *parent_project) {
if ( is_root_project ) {
cmd ( " cmake_minimum_required " ) ( " VERSION " , project . cmake_version ) . endl ( ) ;
if ( project . project_msvc_runtime ! = parser : : msvc_last ) {
comment ( " Enable support for MSVC_RUNTIME_LIBRARY " ) ;
cmd ( " cmake_policy " ) ( " SET " , " CMP0091 " , " NEW " ) ;
switch ( project . project_msvc_runtime ) {
case parser : : msvc_dynamic :
cmd ( " set " ) ( " CMAKE_MSVC_RUNTIME_LIBRARY " , " MultiThreaded$<$<CONFIG:Debug>:Debug>DLL " ) ;
break ;
case parser : : msvc_static :
cmd ( " set " ) ( " CMAKE_MSVC_RUNTIME_LIBRARY " , " MultiThreaded$<$<CONFIG:Debug>:Debug> " ) ;
break ;
default :
break ;
}
endl ( ) ;
}
// clang-format on
if ( ! project . allow_in_tree ) {
// clang-format off
@ -690,6 +761,26 @@ void generate_cmake(const char *path, const parser::Project *parent_project) {
cmd ( " endif " ) ( ) . endl ( ) ;
// clang-format on
if ( project . project_msvc_runtime ! = parser : : msvc_last ) {
comment ( " Enable support for MSVC_RUNTIME_LIBRARY " ) ;
cmd ( " cmake_policy " ) ( " SET " , " CMP0091 " , " NEW " ) ;
// clang-format off
cmd ( " if " ) ( " NOT " , " DEFINED " , " CMAKE_MSVC_RUNTIME_LIBRARY " ) ;
switch ( project . project_msvc_runtime ) {
case parser : : msvc_dynamic :
cmd ( " set " ) ( " CMAKE_MSVC_RUNTIME_LIBRARY " , " MultiThreaded$<$<CONFIG:Debug>:Debug>DLL " ) ;
break ;
case parser : : msvc_static :
cmd ( " set " ) ( " CMAKE_MSVC_RUNTIME_LIBRARY " , " MultiThreaded$<$<CONFIG:Debug>:Debug> " ) ;
break ;
default :
break ;
}
cmd ( " endif " ) ( ) . endl ( ) ;
// clang-format on
}
fs : : path cmkr_include ( project . cmkr_include ) ;
if ( ! project . cmkr_include . empty ( ) & & ! fs : : exists ( cmkr_include ) & & cmkr_include . is_relative ( ) ) {
create_file ( cmkr_include , resources : : cmkr ) ;
@ -814,19 +905,20 @@ void generate_cmake(const char *path, const parser::Project *parent_project) {
cmd ( " if " ) ( " CMKR_ROOT_PROJECT " , " AND " , " NOT " , " CMKR_DISABLE_VCPKG " ) ;
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 ( " FetchContent_Declare " ) ( " vcpkg " , " URL " , url ) ;
// Not using FetchContent_MakeAvailable here in case vcpkg adds CMakeLists.txt
cmd ( " FetchContent_GetProperties " ) ( " vcpkg " ) ;
cmd ( " if " ) ( " NOT " , " vcpkg_POPULATED " ) ;
cmd ( " FetchContent_Populate " ) ( " vcpkg " ) ;
cmd ( " include " ) ( " ${vcpkg_SOURCE_DIR}/scripts/buildsystems/vcpkg.cmake " ) ;
cmd ( " endif " ) ( ) ;
cmd ( " message " ) ( " STATUS " , " Fetching vcpkg ( " + version_name + " )... " ) ;
cmd ( " FetchContent_Declare " ) ( " vcpkg " , " URL " , url ) ;
// Not using FetchContent_MakeAvailable here in case vcpkg adds CMakeLists.txt
cmd ( " FetchContent_GetProperties " ) ( " vcpkg " ) ;
cmd ( " if " ) ( " NOT " , " vcpkg_POPULATED " ) ;
cmd ( " FetchContent_Populate " ) ( " vcpkg " ) ;
cmd ( " if " ) ( " CMAKE_HOST_SYSTEM_NAME " , " STREQUAL " , " Darwin " , " AND " , " CMAKE_OSX_ARCHITECTURES " , " STREQUAL " , RawArg ( " \" \" " ) ) ;
cmd ( " set " ) ( " CMAKE_OSX_ARCHITECTURES " , " ${CMAKE_HOST_SYSTEM_PROCESSOR} " , " CACHE " , " STRING " , RawArg ( " \" \" " ) , " FORCE " ) ;
cmd ( " endif " ) ( ) ;
cmd ( " include " ) ( " ${vcpkg_SOURCE_DIR}/scripts/buildsystems/vcpkg.cmake " ) ;
cmd ( " endif " ) ( ) ;
cmd ( " endif " ) ( ) ;
endl ( ) ;
// clang-format on
@ -900,6 +992,20 @@ void generate_cmake(const char *path, const parser::Project *parent_project) {
ofs < < " } \n " ;
}
if ( ! project . packages . empty ( ) ) {
comment ( " Packages " ) ;
for ( const auto & dep : project . packages ) {
auto version = dep . version ;
if ( version = = " * " )
version . clear ( ) ;
auto required = dep . required ? " REQUIRED " : " " ;
auto config = dep . config ? " CONFIG " : " " ;
auto components = std : : make_pair ( " COMPONENTS " , dep . components ) ;
ConditionScope cs ( gen , dep . condition ) ;
cmd ( " find_package " ) ( dep . name , version , required , config , components ) . endl ( ) ;
}
}
if ( ! project . contents . empty ( ) ) {
cmd ( " include " ) ( " FetchContent " ) . endl ( ) ;
if ( ! project . root ( ) - > vcpkg . enabled ( ) ) {
@ -935,20 +1041,6 @@ void generate_cmake(const char *path, const parser::Project *parent_project) {
}
}
if ( ! project . packages . empty ( ) ) {
comment ( " Packages " ) ;
for ( const auto & dep : project . packages ) {
auto version = dep . version ;
if ( version = = " * " )
version . clear ( ) ;
auto required = dep . required ? " REQUIRED " : " " ;
auto config = dep . config ? " CONFIG " : " " ;
auto components = std : : make_pair ( " COMPONENTS " , dep . components ) ;
ConditionScope cs ( gen , dep . condition ) ;
cmd ( " find_package " ) ( dep . name , version , required , config , components ) . endl ( ) ;
}
}
auto add_subdir = [ & ] ( const std : : string & dir ) {
// clang-format off
comment ( " Subdirectory: " + dir ) ;