// WriteHeader //------------------------------------------------------------------------------ void SLNGenerator::WriteHeader( const AString & solutionVisualStudioVersion, const AString & solutionMinimumVisualStudioVersion ) { const char * defaultVersion = "14.0.22823.1"; // Visual Studio 2015 RC const char * defaultMinimumVersion = "10.0.40219.1"; // Visual Studio Express 2010 const char * version = ( solutionVisualStudioVersion.GetLength() > 0 ) ? solutionVisualStudioVersion.Get() : defaultVersion ; const char * minimumVersion = ( solutionMinimumVisualStudioVersion.GetLength() > 0 ) ? solutionMinimumVisualStudioVersion.Get() : defaultMinimumVersion ; const char * shortVersionStart = version; const char * shortVersionEnd = version; for ( ; *shortVersionEnd && *shortVersionEnd != '.' ; ++shortVersionEnd ); AStackString<> shortVersion( shortVersionStart, shortVersionEnd ); // header Write( "\r\n" ); // Deliberate blank line Write( "Microsoft Visual Studio Solution File, Format Version 12.00\r\n" ); Write( "# Visual Studio %s\r\n", shortVersion.Get() ); Write( "VisualStudioVersion = %s\r\n", version ); Write( "MinimumVisualStudioVersion = %s\r\n", minimumVersion ); }
// AStringAssignment //------------------------------------------------------------------------------ void TestAString::AStringAssignment() const { AString str; str = "test"; TEST_ASSERT( str.GetLength() == 4 ); TEST_ASSERT( str.GetReserved() >= 4 ); TEST_ASSERT( str.IsEmpty() == false ); TEST_ASSERT( str.MemoryMustBeFreed() == true ); AString str2; str2 = str; TEST_ASSERT( str2.GetLength() == 4 ); TEST_ASSERT( str2.GetReserved() >= 4 ); TEST_ASSERT( str2.IsEmpty() == false ); TEST_ASSERT( str2.MemoryMustBeFreed() == true ); const char * testData = "hellozzzzzzzzz"; AString str3; str3.Assign( testData, testData + 5 ); TEST_ASSERT( str3.GetLength() == 5 ); TEST_ASSERT( str3.GetReserved() >= 5 ); TEST_ASSERT( str3.IsEmpty() == false ); TEST_ASSERT( str3.MemoryMustBeFreed() == true ); // assign empty { AString dst; dst.Assign( AString::GetEmpty() ); } { AString dst; dst.Assign( AString::GetEmpty().Get(), AString::GetEmpty().Get() ); } }
REGISTER_TESTS_END // AStringConstructors //------------------------------------------------------------------------------ void TestAString::AStringConstructors() const { { // AString with no arguments AString empty; TEST_ASSERT( empty.GetLength() == 0 ); TEST_ASSERT( empty.GetReserved() == 0 ); TEST_ASSERT( empty.IsEmpty() == true ); TEST_ASSERT( empty.MemoryMustBeFreed() == false ); } { // AString with reserve capacity argument AString empty( 16 ); TEST_ASSERT( empty.GetLength() == 0 ); TEST_ASSERT( empty.GetReserved() == 16 ); TEST_ASSERT( empty.IsEmpty() == true ); TEST_ASSERT( empty.MemoryMustBeFreed() == true ); } { // AString from char * AString fromCharStar( "hello" ); TEST_ASSERT( fromCharStar.GetLength() == 5 ); TEST_ASSERT( fromCharStar.GetReserved() >= 5 ); TEST_ASSERT( fromCharStar.IsEmpty() == false ); TEST_ASSERT( fromCharStar.MemoryMustBeFreed() == true ); // AString from AString AString fromAString( fromCharStar ); TEST_ASSERT( fromAString.GetLength() == 5 ); TEST_ASSERT( fromAString.GetReserved() >= 5 ); TEST_ASSERT( fromAString.IsEmpty() == false ); TEST_ASSERT( fromAString.MemoryMustBeFreed() == true ); } { const char * hello = "hellohellohello"; AString fromCharStarPair( hello, hello + 5 ); TEST_ASSERT( fromCharStarPair.GetLength() == 5 ); TEST_ASSERT( fromCharStarPair.GetReserved() >= 5 ); TEST_ASSERT( fromCharStarPair.IsEmpty() == false ); TEST_ASSERT( fromCharStarPair.MemoryMustBeFreed() == true ); } }
// CONSTRUCTOR (const AString &) //------------------------------------------------------------------------------ AString::AString( const AString & string ) { uint32_t len = string.GetLength(); m_Length = len; uint32_t reserved = Math::RoundUp( len, (uint32_t)2 ); m_Contents = (char *)ALLOC( reserved + 1 ); SetReserved( reserved, true ); Copy( string.Get(), m_Contents, len ); // copy handles terminator }
// EnsWithI //------------------------------------------------------------------------------ bool AString::EndsWithI( const AString & other ) const { const size_t otherLen = other.GetLength(); if ( otherLen > GetLength() ) { return false; } return ( StrNCmpI( GetEnd() - otherLen, other.Get(), otherLen ) == 0 ); }
// BeginsWithI //------------------------------------------------------------------------------ bool AString::BeginsWithI( const AString & string ) const { uint32_t otherLen = string.GetLength(); if ( otherLen > GetLength() ) { return false; } return ( StrNCmpI( m_Contents, string.Get(), otherLen ) == 0 ); }
// operator == (const AString &) //------------------------------------------------------------------------------ bool AString::operator == ( const AString & other ) const { if ( other.GetLength() != GetLength() ) { return false; } return ( *this == other.Get() ); }
// Assign (const AString &) //------------------------------------------------------------------------------ void AString::Assign( const AString & string ) { uint32_t len = string.GetLength(); if ( len > GetReserved() ) { GrowNoCopy( len ); } else if ( m_Contents == s_EmptyString ) { // if we are the special empty string, and we // didn't resize then the passed in string is empty too return; } Copy( string.Get(), m_Contents, len ); // handles terminator m_Length = len; }
// IsStartOfLinkerArg_MSVC //------------------------------------------------------------------------------ /*static*/ bool LinkerNode::IsStartOfLinkerArg_MSVC( const AString & token, const char * arg ) { ASSERT( token.IsEmpty() == false ); // MSVC Linker args can start with - or / if ( ( token[0] != '/' ) && ( token[0] != '-' ) ) { return false; } // Length check to early out const size_t argLen = AString::StrLen( arg ); if ( ( token.GetLength() - 1 ) < argLen ) { return false; // token is too short } // MSVC Linker args are case-insensitive return ( AString::StrNCmpI( token.Get() + 1, arg, argLen ) == 0 ); }
// CONSTRUCTOR //------------------------------------------------------------------------------ DirectoryListNode::DirectoryListNode( const AString & name, const AString & path, const Array< AString > * patterns, bool recursive, const Array< AString > & excludePaths, const Array< AString > & filesToExclude ) : Node( name, Node::DIRECTORY_LIST_NODE, Node::FLAG_NONE ) , m_Path( path ) , m_Patterns() , m_ExcludePaths( excludePaths ) , m_FilesToExclude( filesToExclude ) , m_Recursive( recursive ) , m_Files( 4096, true ) { if ( patterns ) { m_Patterns = *patterns; } // ensure name is correctly formatted // path|[patterns]|recursive|[excludePath] ASSERT( name.BeginsWith( path ) ); ASSERT( name[ path.GetLength() ] == '|' ); ASSERT( m_Patterns.IsEmpty() || ( name.Find( m_Patterns[ 0 ].Get() ) == name.Get() + path.GetLength() + 1 ) ); ASSERT( ( recursive && name.Find( "|true|" ) ) || ( !recursive && name.Find( "|false|" ) ) ); // paths must have trailing slash ASSERT( path.EndsWith( NATIVE_SLASH ) ); // make sure exclusion path has trailing slash if provided #ifdef DEBUG const AString * const end = excludePaths.End(); for ( const AString * it=excludePaths.Begin(); it != end; ++it ) { ASSERT( ( *it ).EndsWith( NATIVE_SLASH ) ); } #endif }
// Finalize //------------------------------------------------------------------------------ bool Args::Finalize( const AString & exe, const AString & nodeNameForError, bool canUseResponseFile ) { ASSERT( !m_Finalized ); #if defined( __WINDOWS__ ) || defined( __OSX__ ) #if defined( __WINDOWS__ ) // Windows has a 32KiB (inc null terminator) command line length limit with CreateProcess // https://msdn.microsoft.com/en-us/library/windows/desktop/ms682425(v=vs.85).aspx const uint32_t argLimit( 32767 ); #elif defined( __OSX__ ) const uint32_t argLimit( ARG_MAX - 1 ); #endif // Calculate final length of args (including exe name) const uint32_t exeLen = exe.GetLength(); const uint32_t extraLen = 3; // quotes around exe name and space const uint32_t argLen = m_Args.GetLength(); // We need to consider the executable, quotes around the exe and a space // as well as the args: "%exe%" %args% const uint32_t totalLen = ( argLen + exeLen + extraLen ); // Small enough? if ( totalLen <= argLimit ) { #if defined( ASSERTS_ENABLED ) m_Finalized = true; #endif return true; // Ok to proceed } // Args are too long. Can we cope using a Response File? if ( canUseResponseFile ) { // Handle per-line limit within response files (e.g. link.exe) #if defined( __WINDOWS__ ) if ( argLen >= 131071 ) // From LNK1170 { // Change spaces to carriage returns for ( uint32_t i : m_DelimiterIndices ) { ASSERT( m_Args[ i ] == ' ' ); m_Args[ i ] = '\n'; } } #endif #if defined( ASSERTS_ENABLED ) m_Finalized = true; #endif // Write args to response file { PROFILE_SECTION( "CreateResponseFile" ) m_ResponseFile.Create( *this ); } // Create new args referencing response file m_ResponseFileArgs = "@\""; m_ResponseFileArgs += m_ResponseFile.GetResponseFilePath(); m_ResponseFileArgs += "\""; return true; // Ok to proceed } // Need response file but not supported FLOG_ERROR( "FBuild: Error: Command Line Limit Exceeded (len: %u, limit: %u) '%s'\n", argLen, argLimit, nodeNameForError.Get() ); return false; #elif defined( __LINUX__ ) // TODO:LINUX Difficult to reliable determine this due to complex interaction with environment #if defined( ASSERTS_ENABLED ) m_Finalized = true; #endif return true; // Ok to proceed #endif }
// GetFilesRecurse //------------------------------------------------------------------------------ /*static*/ void FileIO::GetFilesRecurseEx( AString & pathCopy, const Array< AString > * patterns, Array< FileInfo > * results ) { const uint32_t baseLength = pathCopy.GetLength(); #if defined( __WINDOWS__ ) pathCopy += '*'; // don't want to use wildcard to filter folders // recurse into directories WIN32_FIND_DATA findData; HANDLE hFind = FindFirstFileEx( pathCopy.Get(), FindExInfoBasic, &findData, FindExSearchLimitToDirectories, nullptr, 0 ); if ( hFind == INVALID_HANDLE_VALUE) { return; } do { if ( findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY ) { // ignore magic '.' and '..' folders // (don't need to check length of name, as all names are at least 1 char // which means index 0 and 1 are valid to access) if ( findData.cFileName[ 0 ] == '.' && ( ( findData.cFileName[ 1 ] == '.' ) || ( findData.cFileName[ 1 ] == '\000' ) ) ) { continue; } pathCopy.SetLength( baseLength ); pathCopy += findData.cFileName; pathCopy += NATIVE_SLASH; GetFilesRecurseEx( pathCopy, patterns, results ); } } while ( FindNextFile( hFind, &findData ) != 0 ); FindClose( hFind ); // do files in this directory pathCopy.SetLength( baseLength ); pathCopy += '*'; hFind = FindFirstFileEx( pathCopy.Get(), FindExInfoBasic, &findData, FindExSearchNameMatch, nullptr, 0 ); if ( hFind == INVALID_HANDLE_VALUE) { return; } do { if ( findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY ) { continue; } if ( IsMatch( patterns, findData.cFileName ) ) { pathCopy.SetLength( baseLength ); pathCopy += findData.cFileName; if ( results->GetSize() == results->GetCapacity() ) { results->SetCapacity( results->GetSize() * 2 ); } results->SetSize( results->GetSize() + 1 ); FileInfo & newInfo = results->Top(); newInfo.m_Name = pathCopy; newInfo.m_Attributes = findData.dwFileAttributes; newInfo.m_LastWriteTime = (uint64_t)findData.ftLastWriteTime.dwLowDateTime | ( (uint64_t)findData.ftLastWriteTime.dwHighDateTime << 32 ); newInfo.m_Size = (uint64_t)findData.nFileSizeLow | ( (uint64_t)findData.nFileSizeHigh << 32 ); } } while ( FindNextFile( hFind, &findData ) != 0 ); FindClose( hFind ); #elif defined( __LINUX__ ) || defined( __APPLE__ ) DIR * dir = opendir( pathCopy.Get() ); if ( dir == nullptr ) { return; } for ( ;; ) { dirent * entry = readdir( dir ); if ( entry == nullptr ) { break; // no more entries } // dir? if ( ( entry->d_type & DT_DIR ) == DT_DIR ) { // ignore . and .. if ( entry->d_name[ 0 ] == '.' ) { if ( ( entry->d_name[ 1 ] == 0 ) || ( ( entry->d_name[ 1 ] == '.' ) && ( entry->d_name[ 2 ] == 0 ) ) ) { continue; } } // regular dir pathCopy.SetLength( baseLength ); pathCopy += entry->d_name; pathCopy += NATIVE_SLASH; GetFilesRecurseEx( pathCopy, patterns, results ); continue; } // file - does it match wildcard? if ( IsMatch( patterns, entry->d_name ) ) { pathCopy.SetLength( baseLength ); pathCopy += entry->d_name; if ( results->GetSize() == results->GetCapacity() ) { results->SetCapacity( results->GetSize() * 2 ); } results->SetSize( results->GetSize() + 1 ); FileInfo & newInfo = results->Top(); newInfo.m_Name = pathCopy; // get additional info struct stat info; VERIFY( stat( pathCopy.Get(), &info ) == 0 ); newInfo.m_Attributes = info.st_mode; #if defined( __APPLE__ ) newInfo.m_LastWriteTime = ( ( (uint64_t)info.st_mtimespec.tv_sec * 1000000000ULL ) + (uint64_t)info.st_mtimespec.tv_nsec ); #else newInfo.m_LastWriteTime = ( ( (uint64_t)info.st_mtim.tv_sec * 1000000000ULL ) + (uint64_t)info.st_mtim.tv_nsec ); #endif newInfo.m_Size = info.st_size; } } closedir( dir ); #else #error Unknown platform #endif }
// GetFilesRecurse //------------------------------------------------------------------------------ /*static*/ void FileIO::GetFilesRecurse( AString & pathCopy, const AString & wildCard, Array< AString > * results ) { const uint32_t baseLength = pathCopy.GetLength(); #if defined( __WINDOWS__ ) pathCopy += '*'; // don't want to use wildcard to filter folders // recurse into directories WIN32_FIND_DATA findData; HANDLE hFind = FindFirstFileEx( pathCopy.Get(), FindExInfoBasic, &findData, FindExSearchLimitToDirectories, nullptr, 0 ); if ( hFind == INVALID_HANDLE_VALUE) { return; } do { if ( findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY ) { // ignore magic '.' and '..' folders // (don't need to check length of name, as all names are at least 1 char // which means index 0 and 1 are valid to access) if ( findData.cFileName[ 0 ] == '.' && ( ( findData.cFileName[ 1 ] == '.' ) || ( findData.cFileName[ 1 ] == '\000' ) ) ) { continue; } pathCopy.SetLength( baseLength ); pathCopy += findData.cFileName; pathCopy += NATIVE_SLASH; GetFilesRecurse( pathCopy, wildCard, results ); } } while ( FindNextFile( hFind, &findData ) != 0 ); FindClose( hFind ); // do files in this directory pathCopy.SetLength( baseLength ); pathCopy += '*'; hFind = FindFirstFileEx( pathCopy.Get(), FindExInfoBasic, &findData, FindExSearchNameMatch, nullptr, 0 ); if ( hFind == INVALID_HANDLE_VALUE) { return; } do { if ( findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY ) { continue; } if ( PathUtils::IsWildcardMatch( wildCard.Get(), findData.cFileName ) ) { pathCopy.SetLength( baseLength ); pathCopy += findData.cFileName; results->Append( pathCopy ); } } while ( FindNextFile( hFind, &findData ) != 0 ); FindClose( hFind ); #elif defined( __LINUX__ ) || defined( __APPLE__ ) DIR * dir = opendir( pathCopy.Get() ); if ( dir == nullptr ) { return; } for ( ;; ) { dirent * entry = readdir( dir ); if ( entry == nullptr ) { break; // no more entries } // dir? if ( ( entry->d_type & DT_DIR ) == DT_DIR ) { // ignore . and .. if ( entry->d_name[ 0 ] == '.' ) { if ( ( entry->d_name[ 1 ] == 0 ) || ( ( entry->d_name[ 1 ] == '.' ) && ( entry->d_name[ 2 ] == 0 ) ) ) { continue; } } // regular dir pathCopy.SetLength( baseLength ); pathCopy += entry->d_name; pathCopy += NATIVE_SLASH; GetFilesRecurse( pathCopy, wildCard, results ); continue; } // file - does it match wildcard? if ( PathUtils::IsWildcardMatch( wildCard.Get(), entry->d_name ) ) { pathCopy.SetLength( baseLength ); pathCopy += entry->d_name; results->Append( pathCopy ); } } closedir( dir ); #else #error Unknown platform #endif }
// Commit //------------------------------------------------------------------------------ /*virtual*/ bool FunctionSLN::Commit( const BFFIterator & funcStartIter ) const { AStackString<> solutionOutput; Array< AString > solutionProjects( 8, true ); if ( !GetString( funcStartIter, solutionOutput, ".SolutionOutput", true ) || !GetStrings( funcStartIter, solutionProjects, ".SolutionProjects", false ) ) { return false; } // optional inputs AString solutionBuildProject; AString solutionVisualStudioVersion; AString solutionMinimumVisualStudioVersion; if ( !GetString( funcStartIter, solutionBuildProject, ".SolutionBuildProject", false ) || !GetString( funcStartIter, solutionVisualStudioVersion, ".SolutionVisualStudioVersion", false ) || !GetString( funcStartIter, solutionMinimumVisualStudioVersion, ".SolutionMinimumVisualStudioVersion", false ) ) { return false; } // base config VSProjectConfig baseConfig; // create configs Array< VSProjectConfig > configs( 16, true ); const BFFVariable * solutionConfigs = BFFStackFrame::GetVar( ".SolutionConfigs" ); if ( solutionConfigs ) { if ( solutionConfigs->IsArrayOfStructs() == false ) { Error::Error_1050_PropertyMustBeOfType( funcStartIter, this, ".SolutionConfigs", solutionConfigs->GetType(), BFFVariable::VAR_ARRAY_OF_STRUCTS ); return false; } const Array< const BFFVariable * > & structs = solutionConfigs->GetArrayOfStructs(); const BFFVariable * const * end = structs.End(); for ( const BFFVariable ** it = structs.Begin(); it != end; ++it ) { const BFFVariable * s = *it; // start with the base configuration VSProjectConfig newConfig( baseConfig ); // .Platform must be provided if ( !GetStringFromStruct( s, ".Platform", newConfig.m_Platform ) ) { // TODO:B custom error Error::Error_1101_MissingProperty( funcStartIter, this, AStackString<>( ".Platform" ) ); return false; } // .Config must be provided if ( !GetStringFromStruct( s, ".Config", newConfig.m_Config ) ) { // TODO:B custom error Error::Error_1101_MissingProperty( funcStartIter, this, AStackString<>( ".Config" ) ); return false; } configs.Append( newConfig ); } } else { // no user specified configs, make some defaults // start from the default VSProjectConfig config( baseConfig ); // make the configs config.m_Platform = "Win32"; config.m_Config = "Debug"; configs.Append( config ); config.m_Config = "Release"; configs.Append( config ); config.m_Platform = "x64"; configs.Append( config ); config.m_Config = "Debug"; configs.Append( config ); } // sort project configs by config and by platform (like visual) configs.Sort( VSProjectConfigComp() ); // create solution folders Array< SLNSolutionFolder > folders( 16, true ); const BFFVariable * solutionFolders = BFFStackFrame::GetVar( ".SolutionFolders" ); if ( solutionFolders ) { if ( solutionFolders->IsArrayOfStructs() == false ) { Error::Error_1050_PropertyMustBeOfType( funcStartIter, this, ".SolutionFolders", solutionFolders->GetType(), BFFVariable::VAR_ARRAY_OF_STRUCTS ); return false; } const Array< const BFFVariable * > & structs = solutionFolders->GetArrayOfStructs(); const BFFVariable * const * end = structs.End(); for ( const BFFVariable ** it = structs.Begin(); it != end; ++it ) { const BFFVariable * s = *it; // start with the base configuration SLNSolutionFolder newFolder; // .Path must be provided if ( !GetStringFromStruct( s, ".Path", newFolder.m_Path ) ) { // TODO:B custom error Error::Error_1101_MissingProperty( funcStartIter, this, AStackString<>( ".Path" ) ); return false; } newFolder.m_Path.Replace( OTHER_SLASH, NATIVE_SLASH ); // check if this path was already defined { const SLNSolutionFolder * const end2 = folders.End(); for ( const SLNSolutionFolder * it2 = folders.Begin() ; it2 != end2 ; ++it2 ) { if ( it2->m_Path == newFolder.m_Path ) { // TODO:B custom error Error::Error_1100_AlreadyDefined( funcStartIter, this, it2->m_Path ); return false; } } } // .Projects must be provided if ( !GetStringOrArrayOfStringsFromStruct( funcStartIter, s, ".Projects", newFolder.m_ProjectNames ) ) { return false; // GetStringOrArrayOfStringsFromStruct has emitted an error } // check if this project is included in the solution for ( const AString & projectName : newFolder.m_ProjectNames ) { if ( solutionProjects.Find( projectName ) == nullptr ) { solutionProjects.Append( projectName ); } } folders.Append( newFolder ); } } NodeGraph & ng = FBuild::Get().GetDependencyGraph(); // Check for existing node if ( ng.FindNode( solutionOutput ) ) { Error::Error_1100_AlreadyDefined( funcStartIter, this, solutionOutput ); return false; } // resolves VCXProject nodes associated to solutionProjects Array< VCXProjectNode * > projects( solutionProjects.GetSize(), false ); { const AString * const end = solutionProjects.End(); for ( const AString * it = solutionProjects.Begin(); it != end; ++it ) { VCXProjectNode * project = ResolveVCXProject( funcStartIter, *it ); if ( project == nullptr ) { return false; // ResolveVCXProject will have emitted error } // check that this project contains all .SolutionConfigs const Array< VSProjectConfig > & projectConfigs = project->GetConfigs(); const size_t configsSize = configs.GetSize(); for ( size_t i = 0 ; i < configsSize ; ++i ) { bool containsConfig = false; const VSProjectConfig * const config = &configs[i]; const VSProjectConfig * const end2 = projectConfigs.End(); for ( const VSProjectConfig * it2 = projectConfigs.Begin(); it2 != end2; ++it2 ) { if ( it2->m_Platform == config->m_Platform && it2->m_Config == config->m_Config ) { containsConfig = true; break; } } if ( containsConfig == false ) { // TODO: specific error message "ProjectConfigNotFound" AStackString<> configName; configName.Format( "%s|%s", config->m_Platform.Get(), config->m_Config.Get() ); Error::Error_1104_TargetNotDefined( funcStartIter, this, configName.Get(), project->GetName() ); return false; } } // append vcxproject node to solution projects.Append( project ); } } // sort projects by name (like visual) projects.Sort( VCXProjectNodeComp() ); // resolves VCXProject nodes associated to solutionFolders { SLNSolutionFolder * const end = folders.End(); for ( SLNSolutionFolder * it = folders.Begin(); it != end; ++it ) { // retrieves full path of contained vcxprojects AString * const end2 = it->m_ProjectNames.End(); for ( AString * it2 = it->m_ProjectNames.Begin(); it2 != end2; ++it2 ) { // Get associate project file VCXProjectNode * project = ResolveVCXProject( funcStartIter, *it2 ); if ( project == nullptr ) { return false; // ResolveVCXProjectRecurse will have emitted error } ASSERT( projects.Find( project ) ); // Sanity check in global list // fixup name to be to final project *it2 = project->GetName(); } } } // resolves VCXProject node referenced by solutionBuildProject if ( solutionBuildProject.GetLength() > 0 ) { // Get associate project file const VCXProjectNode * project = ResolveVCXProject( funcStartIter, solutionBuildProject ); if ( project == nullptr ) { return false; // ResolveVCXProject will have emitted error } if ( projects.Find( project ) == nullptr ) { // project referenced in .SolutionBuildProject is not referenced in .SolutionProjects Error::Error_1104_TargetNotDefined( funcStartIter, this, ".SolutionBuildProject", project->GetName() ); return false; } solutionBuildProject = project->GetName(); } // Project Dependencies Array< SLNDependency > slnDeps( 0, true ); const BFFVariable * projectDepsVar = BFFStackFrame::GetVar( ".SolutionDependencies" ); if ( projectDepsVar ) { if ( projectDepsVar->IsArrayOfStructs() == false ) { Error::Error_1050_PropertyMustBeOfType( funcStartIter, this, ".SolutionDependencies", projectDepsVar->GetType(), BFFVariable::VAR_ARRAY_OF_STRUCTS ); return false; } slnDeps.SetCapacity( projectDepsVar->GetArrayOfStructs().GetSize() ); for ( const BFFVariable * s : projectDepsVar->GetArrayOfStructs() ) { // .Projects must be provided // .Dependencies must be provided SLNDependency deps; if ( !GetStringOrArrayOfStringsFromStruct( funcStartIter, s, ".Projects", deps.m_Projects ) || !GetStringOrArrayOfStringsFromStruct( funcStartIter, s, ".Dependencies", deps.m_Dependencies ) ) { return false; // GetStringOrArrayOfStringsFromStruct has emitted an error } // fixup for ( AString & projectName : deps.m_Projects ) { // Get associated project file const VCXProjectNode * project = ResolveVCXProject( funcStartIter, projectName ); if ( project == nullptr ) { return false; // ResolveVCXProject will have emitted error } projectName = project->GetName(); } for ( AString & projectName : deps.m_Dependencies ) { // Get associated project file const VCXProjectNode * project = ResolveVCXProject( funcStartIter, projectName ); if ( project == nullptr ) { return false; // ResolveVCXProject will have emitted error } projectName = project->GetName(); } slnDeps.Append( deps ); } } SLNNode * sln = ng.CreateSLNNode( solutionOutput, solutionBuildProject, solutionVisualStudioVersion, solutionMinimumVisualStudioVersion, configs, projects, slnDeps, folders ); ASSERT( sln ); return ProcessAlias( funcStartIter, sln ); }
// WriteIfDifferent //------------------------------------------------------------------------------ /*static*/ bool ProjectGeneratorBase::WriteIfDifferent( const char * generatorId, const AString & content, const AString & fileName ) { bool needToWrite = false; FileStream old; if ( FBuild::Get().GetOptions().m_ForceCleanBuild ) { needToWrite = true; } else if ( old.Open( fileName.Get(), FileStream::READ_ONLY ) == false ) { needToWrite = true; } else { // files differ in size? size_t oldFileSize = (size_t)old.GetFileSize(); if ( oldFileSize != content.GetLength() ) { needToWrite = true; } else { // check content AutoPtr< char > mem( ( char *)ALLOC( oldFileSize ) ); if ( old.Read( mem.Get(), oldFileSize ) != oldFileSize ) { FLOG_ERROR( "%s - Failed to read '%s'", generatorId, fileName.Get() ); return false; } // compare content if ( memcmp( mem.Get(), content.Get(), oldFileSize ) != 0 ) { needToWrite = true; } } // ensure we are closed, so we can open again for write if needed old.Close(); } // only save if missing or different if ( needToWrite == false ) { return true; // nothing to do. } FLOG_BUILD( "%s: %s\n", generatorId, fileName.Get() ); // ensure path exists (normally handled by framework, but Projects // are not necessarily a single file) if ( Node::EnsurePathExistsForFile( fileName ) == false ) { FLOG_ERROR( "%s - Invalid path for '%s' (error: %u)", generatorId, fileName.Get(), Env::GetLastErr() ); return false; } // actually write FileStream f; if ( !f.Open( fileName.Get(), FileStream::WRITE_ONLY ) ) { FLOG_ERROR( "%s - Failed to open '%s' for write (error: %u)", generatorId, fileName.Get(), Env::GetLastErr() ); return false; } if ( f.Write( content.Get(), content.GetLength() ) != content.GetLength() ) { FLOG_ERROR( "%s - Error writing to '%s' (error: %u)", generatorId, fileName.Get(), Env::GetLastErr() ); return false; } f.Close(); return true; }
// EmbeddedNuls //------------------------------------------------------------------------------ void TestAString::EmbeddedNuls() const { // Create a string with an embedded nul and check various behaviours AStackString<> string( "0123456789" ); const uint32_t originalStringLen = string.GetLength(); string[ 5 ] = 0; // insert null terminator // Copy construction { AString copy( string ); TEST_ASSERT( copy.GetLength() == originalStringLen ); TEST_ASSERT( memcmp( "01234" "\0" "6789", copy.Get(), originalStringLen ) == 0 ); } // Assignment (operator =) { AString copy; copy = string; TEST_ASSERT( copy.GetLength() == originalStringLen ); TEST_ASSERT( memcmp( "01234" "\0" "6789", copy.Get(), originalStringLen ) == 0 ); } // Assignment (Assign) { AString copy; copy.Assign( string ); TEST_ASSERT( copy.GetLength() == originalStringLen ); TEST_ASSERT( memcmp( "01234" "\0" "6789", copy.Get(), originalStringLen ) == 0 ); } // Assignment (Assign with iterators) { AString copy; copy.Assign( string.Get(), string.GetEnd() ); TEST_ASSERT( copy.GetLength() == originalStringLen ); TEST_ASSERT( memcmp( "01234" "\0" "6789", copy.Get(), originalStringLen ) == 0 ); } // Append (operator +=) { // Append to empty AString copy; copy += string; TEST_ASSERT( copy.GetLength() == originalStringLen ); TEST_ASSERT( AString::StrNCmp( string.Get(), copy.Get(), originalStringLen ) == 0 ); TEST_ASSERT( memcmp( "01234" "\0" "6789", copy.Get(), originalStringLen ) == 0 ); // Append to existing AString copy2( string ); copy2 += string; TEST_ASSERT( copy2.GetLength() == ( originalStringLen * 2 ) ); TEST_ASSERT( memcmp( "01234" "\0" "678901234" "\0" "6789", copy2.Get(), ( originalStringLen * 2 ) ) == 0 ); } // Append (Append) { // Append to empty AString copy; copy.Append( string ); TEST_ASSERT( copy.GetLength() == originalStringLen ); TEST_ASSERT( memcmp( "01234" "\0" "6789", copy.Get(), originalStringLen ) == 0 ); // Append to existing AString copy2( string ); copy2.Append( string ); TEST_ASSERT( copy2.GetLength() == ( originalStringLen * 2 ) ); TEST_ASSERT( memcmp( "01234" "\0" "678901234" "\0" "6789", copy2.Get(), ( originalStringLen * 2 ) ) == 0 ); } }
// ParseNamedVariableName //------------------------------------------------------------------------------ /*static*/ bool BFFParser::ParseVariableName( BFFIterator & iter, AString & name, bool & parentScope ) { // skip over the declaration symbol ASSERT( *iter == BFF_DECLARE_VAR_INTERNAL || *iter == BFF_DECLARE_VAR_PARENT ); parentScope = ( *iter == BFF_DECLARE_VAR_PARENT ); const BFFIterator varNameStart = iter; // include type token in var name iter++; // make sure we haven't hit the end of the file if ( iter.IsAtEnd() ) { Error::Error_1012_UnexpectedEndOfFile( iter ); return false; } if ( *iter == '\'' || *iter == '"' ) { // parse the string const BFFIterator openToken = iter; iter.SkipString( *openToken ); if ( *iter != *openToken ) { Error::Error_1002_MatchingClosingTokenNotFound( openToken, nullptr, *openToken ); return false; } BFFIterator stringStart = openToken; stringStart++; // unescape and subsitute embedded variables AStackString< 256 > value; if ( PerformVariableSubstitutions( stringStart, iter, value ) == false ) { return false; } iter++; // skip close token BFFIterator varNameIter( value.Get(), value.GetLength(), iter.GetFileName().Get(), iter.GetFileTimeStamp() ); // sanity check it is a sensible length if ( value.GetLength() + 1/* '.' will be added */ > MAX_VARIABLE_NAME_LENGTH ) { Error::Error_1014_VariableNameIsTooLong( varNameIter, (uint32_t)value.GetLength(), (uint32_t)MAX_VARIABLE_NAME_LENGTH ); return false; } // sanity check it is a valid variable name while ( varNameIter.IsAtEnd() == false ) { if ( varNameIter.IsAtValidVariableNameCharacter() == false ) { Error::Error_1013_UnexpectedCharInVariableName( varNameIter, nullptr ); return false; } varNameIter++; } // append '.' to variable name name = "."; name.Append( value ); } else { // make sure immediately after the symbol starts a variable name if ( iter.IsAtValidVariableNameCharacter() == false ) { Error::Error_1013_UnexpectedCharInVariableName( iter, nullptr ); return false; } // find the end of the variable name iter.SkipVariableName(); const BFFIterator varNameEnd = iter; // sanity check it is a sensible length size_t varNameLen = varNameStart.GetDistTo( varNameEnd ); if ( varNameLen > MAX_VARIABLE_NAME_LENGTH ) { Error::Error_1014_VariableNameIsTooLong( iter, (uint32_t)varNameLen, (uint32_t)MAX_VARIABLE_NAME_LENGTH ); return false; } // store variable name name.Assign( varNameStart.GetCurrent(), varNameEnd.GetCurrent() ); } ASSERT( name.GetLength() > 0 ); if ( parentScope ) { // exchange '^' with '.' ASSERT( BFF_DECLARE_VAR_PARENT == name[0] ); name[0] = BFF_DECLARE_VAR_INTERNAL; } return true; }
// CreateDynamicObjectNode //------------------------------------------------------------------------------ bool ObjectListNode::CreateDynamicObjectNode( NodeGraph & nodeGraph, Node * inputFile, const AString & baseDir, bool isUnityNode, bool isIsolatedFromUnityNode ) { const AString & fileName = inputFile->GetName(); // Transform src file to dst object path // get file name only (no path, no ext) const char * lastSlash = fileName.FindLast( NATIVE_SLASH ); lastSlash = lastSlash ? ( lastSlash + 1 ) : fileName.Get(); const char * lastDot = fileName.FindLast( '.' ); lastDot = lastDot && ( lastDot > lastSlash ) ? lastDot : fileName.GetEnd(); // if source comes from a directory listing, use path relative to dirlist base // to replicate the folder hierearchy in the output AStackString<> subPath; if ( baseDir.IsEmpty() == false ) { ASSERT( NodeGraph::IsCleanPath( baseDir ) ); if ( PathUtils::PathBeginsWith( fileName, baseDir ) ) { // ... use everything after that subPath.Assign( fileName.Get() + baseDir.GetLength(), lastSlash ); // includes last slash } } else { if ( !m_BaseDirectory.IsEmpty() && PathUtils::PathBeginsWith( fileName, m_BaseDirectory ) ) { // ... use everything after that subPath.Assign( fileName.Get() + m_BaseDirectory.GetLength(), lastSlash ); // includes last slash } } AStackString<> fileNameOnly( lastSlash, lastDot ); AStackString<> objFile( m_CompilerOutputPath ); objFile += subPath; objFile += m_CompilerOutputPrefix; objFile += fileNameOnly; objFile += GetObjExtension(); // Create an ObjectNode to compile the above file // and depend on that Node * on = nodeGraph.FindNode( objFile ); if ( on == nullptr ) { // determine flags - TODO:B Move DetermineFlags call out of build-time const bool usingPCH = ( m_PrecompiledHeader != nullptr ); uint32_t flags = ObjectNode::DetermineFlags( m_Compiler, m_CompilerArgs, false, usingPCH ); if ( isUnityNode ) { flags |= ObjectNode::FLAG_UNITY; } if ( isIsolatedFromUnityNode ) { flags |= ObjectNode::FLAG_ISOLATED_FROM_UNITY; } uint32_t preprocessorFlags = 0; if ( m_Preprocessor ) { // determine flags - TODO:B Move DetermineFlags call out of build-time preprocessorFlags = ObjectNode::DetermineFlags( m_Preprocessor, m_PreprocessorArgs, false, usingPCH ); } on = nodeGraph.CreateObjectNode( objFile, inputFile, m_Compiler, m_CompilerArgs, m_CompilerArgsDeoptimized, m_PrecompiledHeader, flags, m_CompilerForceUsing, m_DeoptimizeWritableFiles, m_DeoptimizeWritableFilesWithToken, m_AllowDistribution, m_AllowCaching, m_Preprocessor, m_PreprocessorArgs, preprocessorFlags ); } else if ( on->GetType() != Node::OBJECT_NODE ) { FLOG_ERROR( "Node '%s' is not an ObjectNode (type: %s)", on->GetName().Get(), on->GetTypeName() ); return false; } else { ObjectNode * other = on->CastTo< ObjectNode >(); if ( inputFile != other->GetSourceFile() ) { FLOG_ERROR( "Conflicting objects found:\n" " File A: %s\n" " File B: %s\n" " Both compile to: %s\n", inputFile->GetName().Get(), other->GetSourceFile()->GetName().Get(), objFile.Get() ); return false; } } m_DynamicDependencies.Append( Dependency( on ) ); return true; }
// Save //------------------------------------------------------------------------------ bool SLNNode::Save( const AString & content, const AString & fileName ) const { bool needToWrite = false; FileStream old; if ( FBuild::Get().GetOptions().m_ForceCleanBuild ) { needToWrite = true; } else if ( old.Open( fileName.Get(), FileStream::READ_ONLY ) == false ) { needToWrite = true; } else { // files differ in size? size_t oldFileSize = (size_t)old.GetFileSize(); if ( oldFileSize != content.GetLength() ) { needToWrite = true; } else { // check content AutoPtr< char > mem( ( char *)ALLOC( oldFileSize ) ); if ( old.Read( mem.Get(), oldFileSize ) != oldFileSize ) { FLOG_ERROR( "SLN - Failed to read '%s'", fileName.Get() ); return false; } // compare content if ( memcmp( mem.Get(), content.Get(), oldFileSize ) != 0 ) { needToWrite = true; } } // ensure we are closed, so we can open again for write if needed old.Close(); } // only save if missing or ner if ( needToWrite == false ) { return true; // nothing to do. } FLOG_BUILD( "SLN: %s\n", fileName.Get() ); // actually write FileStream f; if ( !f.Open( fileName.Get(), FileStream::WRITE_ONLY ) ) { FLOG_ERROR( "SLN - Failed to open '%s' for write (error: %u)", fileName.Get(), Env::GetLastErr() ); return false; } if ( f.Write( content.Get(), content.GetLength() ) != content.GetLength() ) { FLOG_ERROR( "SLN - Error writing to '%s' (error: %u)", fileName.Get(), Env::GetLastErr() ); return false; } f.Close(); return true; }
// WriteProjectListings //------------------------------------------------------------------------------ void SLNGenerator::WriteProjectListings( const AString& solutionBasePath, const AString& solutionBuildProject, const Array< VCXProjectNode * > & projects, const Array< SLNSolutionFolder > & folders, const Array< SLNDependency > & slnDeps, AString & solutionBuildProjectGuid, Array< AString > & projectGuids, Array< AString > & solutionProjectsToFolder ) { // Project Listings VCXProjectNode ** const projectsEnd = projects.End(); for( VCXProjectNode ** it = projects.Begin() ; it != projectsEnd ; ++it ) { // check if this project is the master project const bool projectIsActive = ( solutionBuildProject.CompareI( (*it)->GetName() ) == 0 ); AStackString<> projectPath( (*it)->GetName() ); // get project base name only const char * lastSlash = projectPath.FindLast( NATIVE_SLASH ); const char * lastPeriod = projectPath.FindLast( '.' ); AStackString<> projectName( lastSlash ? lastSlash + 1 : projectPath.Get(), lastPeriod ? lastPeriod : projectPath.GetEnd() ); // retrieve projectGuid AStackString<> projectGuid; if ( (*it)->GetProjectGuid().GetLength() == 0 ) { // For backward compatibility, keep the preceding slash and .vcxproj extension for GUID generation AStackString<> projectNameForGuid( lastSlash ? lastSlash : projectPath.Get() ); VSProjectGenerator::FormatDeterministicProjectGUID( projectGuid, projectNameForGuid ); } else { projectGuid = (*it)->GetProjectGuid(); } // make project path relative projectPath.Replace( solutionBasePath.Get(), "" ); // projectGuid must be uppercase (visual does that, it changes the .sln otherwise) projectGuid.ToUpper(); if ( projectIsActive ) { ASSERT( solutionBuildProjectGuid.GetLength() == 0 ); solutionBuildProjectGuid = projectGuid; } Write( "Project(\"{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}\") = \"%s\", \"%s\", \"%s\"\r\n", projectName.Get(), projectPath.Get(), projectGuid.Get() ); // Manage dependencies Array< AString > dependencyGUIDs( 64, true ); const AString & fullProjectPath = (*it)->GetName(); for ( const SLNDependency & deps : slnDeps ) { // is the set of deps relevant to this project? if ( deps.m_Projects.Find( fullProjectPath ) ) { // get all the projects this project depends on for ( const AString & dependency : deps.m_Dependencies ) { // For backward compatibility, keep the preceding slash and .vcxproj extension for GUID generation const char * projNameFromSlash = dependency.FindLast( NATIVE_SLASH ); AStackString<> projectNameForGuid( projNameFromSlash ? projNameFromSlash : dependency.Get() ); AStackString<> newGUID; VSProjectGenerator::FormatDeterministicProjectGUID( newGUID, projectNameForGuid ); dependencyGUIDs.Append( newGUID ); } } } if ( !dependencyGUIDs.IsEmpty() ) { Write( "\tProjectSection(ProjectDependencies) = postProject\r\n" ); for ( const AString & guid : dependencyGUIDs ) { Write( "\t\t%s = %s\r\n", guid.Get(), guid.Get() ); } Write( "\tEndProjectSection\r\n" ); } Write( "EndProject\r\n" ); projectGuids.Append( projectGuid ); // check if this project is in a solution folder const SLNSolutionFolder * const foldersEnd = folders.End(); for ( const SLNSolutionFolder * it2 = folders.Begin() ; it2 != foldersEnd ; ++it2 ) { // this has to be done here to have the same order of declaration (like visual) if ( it2->m_ProjectNames.Find( (*it)->GetName() ) ) { // generate a guid for the solution folder AStackString<> solutionFolderGuid; VSProjectGenerator::FormatDeterministicProjectGUID( solutionFolderGuid, it2->m_Path ); solutionFolderGuid.ToUpper(); AStackString<> projectToFolder; projectToFolder.Format( "\t\t%s = %s\r\n", projectGuid.Get(), solutionFolderGuid.Get() ); solutionProjectsToFolder.Append( projectToFolder ); } } } }