// ProcessEnvironment //------------------------------------------------------------------------------ void FunctionSettings::ProcessEnvironment( const Array< AString > & envStrings ) const { // the environment string is used in windows as a double-null terminated string // so convert our array to a single buffer // work out space required uint32_t size = 0; for ( uint32_t i=0; i<envStrings.GetSize(); ++i ) { size += envStrings[ i ].GetLength() + 1; // string len inc null } // allocate space AutoPtr< char > envString( (char *)ALLOC( size + 1 ) ); // +1 for extra double-null // while iterating, extract the LIB environment variable (if there is one) AStackString<> libEnvVar; // copy strings end to end char * dst = envString.Get(); for ( uint32_t i=0; i<envStrings.GetSize(); ++i ) { if ( envStrings[ i ].BeginsWith( "LIB=" ) ) { libEnvVar.Assign( envStrings[ i ].Get() + 4, envStrings[ i ].GetEnd() ); } const uint32_t thisStringLen = envStrings[ i ].GetLength(); AString::Copy( envStrings[ i ].Get(), dst, thisStringLen ); dst += ( thisStringLen + 1 ); } // final double-null *dst = '\000'; FBuild::Get().SetEnvironmentString( envString.Get(), size, libEnvVar ); }
// Commit //------------------------------------------------------------------------------ /*virtual*/ bool FunctionVCXProject::Commit( const BFFIterator & funcStartIter ) const { // required AStackString<> projectOutput; AStackString<> rootNamespace; AStackString<> projectGuid; AStackString<> defaultLanguage; AStackString<> applicationEnvironment; if ( !GetString( funcStartIter, projectOutput, ".ProjectOutput", true ) || !GetString( funcStartIter, rootNamespace, ".RootNamespace", false ) || !GetString( funcStartIter, projectGuid, ".ProjectGuid", false ) || !GetString( funcStartIter, defaultLanguage, ".DefaultLanguage", false ) || !GetString( funcStartIter, applicationEnvironment, ".ApplicationEnvironment", false ) ) { return false; } // optional inputs Array< AString > inputPaths; Array< AString > inputPathsExclude; if ( !GetStrings( funcStartIter, inputPaths, ".ProjectInputPaths", false ) || !GetStrings( funcStartIter, inputPathsExclude, ".ProjectInputPathsExclude", false ) ) { return false; } // project base Array< AString > basePaths; if ( !GetStrings( funcStartIter, basePaths, ".ProjectBasePath", false ) ) { return false; } CleanFolderPaths( basePaths ); // references Array< AString > references; Array< AString > projectReferences; if ( !GetStrings( funcStartIter, references, ".ProjectReferences", false ) || !GetStrings( funcStartIter, projectReferences, ".ProjectProjectReferences", false ) ) { return false; } // permitted file extensions Array< AString > allowedFileExtensions( 8, true ); if ( !GetStrings( funcStartIter, allowedFileExtensions, ".ProjectAllowedFileExtensions", false ) ) { return true; } if ( allowedFileExtensions.IsEmpty() ) { const char * extensions[] = { ".cpp", ".hpp", ".cxx",".hxx",".c",".h",".cc",".hh", ".cp",".hp",".cs",".inl",".bff",".rc",".resx",".m",".mm", ".cu", nullptr }; AStackString<> tmp; const char ** item = extensions; while ( *item ) { tmp.Assign( *item ); allowedFileExtensions.Append( tmp ); ++item; } } // files and filesToExclude Array< AString > files( 8, true ); Array< AString > filesToExclude( 8, true ); if ( !GetStrings( funcStartIter, files, ".ProjectFiles", false ) || !GetStrings( funcStartIter, filesToExclude, ".ProjectFilesToExclude", false ) ) { return false; } // filetypes Array< VSProjectFileType > fileTypes; const BFFVariable * projectFileTypes = BFFStackFrame::GetVar( ".ProjectFileTypes" ); if ( projectFileTypes ) { if ( projectFileTypes->IsArrayOfStructs() == false ) { Error::Error_1050_PropertyMustBeOfType( funcStartIter, this, ".ProjectFileTypes", projectFileTypes->GetType(), BFFVariable::VAR_ARRAY_OF_STRUCTS ); return false; } const Array< const BFFVariable * > & structs = projectFileTypes->GetArrayOfStructs(); const BFFVariable * const * end = structs.End(); for ( const BFFVariable ** it = structs.Begin(); it != end; ++it ) { const BFFVariable * s = *it; VSProjectFileType ft; // .FileType must be provided if ( !GetStringFromStruct( s, ".FileType", ft.m_FileType ) ) { // TODO:B custom error Error::Error_1101_MissingProperty( funcStartIter, this, AStackString<>( ".FileType" ) ); return false; } // .Pattern must be provided if ( !GetStringFromStruct( s, ".Pattern", ft.m_Pattern ) ) { // TODO:B custom error Error::Error_1101_MissingProperty( funcStartIter, this, AStackString<>( ".Pattern" ) ); return false; } fileTypes.Append( ft ); } } // path cleaning CleanFolderPaths( inputPaths ); // input paths CleanFolderPaths( inputPathsExclude ); // exclude paths CleanFilePaths( files ); // explicit files // per-config options VSProjectConfig baseConfig; // various options if ( !GetString( funcStartIter, baseConfig.m_BuildCommand, ".ProjectBuildCommand", false ) || !GetString( funcStartIter, baseConfig.m_RebuildCommand,".ProjectRebuildCommand", false ) || !GetString( funcStartIter, baseConfig.m_CleanCommand, ".ProjectCleanCommand", false ) || !GetString( funcStartIter, baseConfig.m_Output, ".Output", false ) || !GetString( funcStartIter, baseConfig.m_PreprocessorDefinitions, ".PreprocessorDefinitions", false ) || !GetString( funcStartIter, baseConfig.m_IncludeSearchPath, ".IncludeSearchPath", false ) || !GetString( funcStartIter, baseConfig.m_ForcedIncludes, ".ForcedIncludes", false ) || !GetString( funcStartIter, baseConfig.m_AssemblySearchPath, ".AssemblySearchPath", false ) || !GetString( funcStartIter, baseConfig.m_ForcedUsingAssemblies, ".ForcedUsingAssemblies", false ) || !GetString( funcStartIter, baseConfig.m_AdditionalOptions, ".AdditionalOptions", false ) || !GetString( funcStartIter, baseConfig.m_OutputDirectory, ".OutputDirectory", false ) || !GetString( funcStartIter, baseConfig.m_IntermediateDirectory, ".IntermediateDirectory", false ) || !GetString( funcStartIter, baseConfig.m_Xbox360DebuggerCommand,".Xbox360DebuggerCommand", false ) || !GetString( funcStartIter, baseConfig.m_LayoutDir, ".LayoutDir", false ) || !GetString( funcStartIter, baseConfig.m_LayoutExtensionFilter, ".LayoutExtensionFilter", false ) || !GetString( funcStartIter, baseConfig.m_DebuggerFlavor, ".DebuggerFlavor", false ) || !GetString( funcStartIter, baseConfig.m_AumidOverride, ".AumidOverride", false ) || !GetString( funcStartIter, baseConfig.m_PlatformToolset, ".PlatformToolset", false ) || !GetString( funcStartIter, baseConfig.m_DeploymentType, ".DeploymentType", false ) || !GetString( funcStartIter, baseConfig.m_DeploymentFiles, ".DeploymentFiles", false ) || !GetString( funcStartIter, baseConfig.m_LocalDebuggerCommandArguments, ".LocalDebuggerCommandArguments", false ) || !GetString( funcStartIter, baseConfig.m_LocalDebuggerWorkingDirectory, ".LocalDebuggerWorkingDirectory", false ) || !GetString( funcStartIter, baseConfig.m_LocalDebuggerCommand, ".LocalDebuggerCommand", false ) || !GetString( funcStartIter, baseConfig.m_LocalDebuggerEnvironment, ".LocalDebuggerEnvironment", false ) ) { return false; } // create configs Array< VSProjectConfig > configs( 16, true ); const BFFVariable * projectConfigs = BFFStackFrame::GetVar( ".ProjectConfigs" ); if ( projectConfigs ) { if ( projectConfigs->IsArrayOfStructs() == false ) { Error::Error_1050_PropertyMustBeOfType( funcStartIter, this, ".ProjectConfigs", projectConfigs->GetType(), BFFVariable::VAR_ARRAY_OF_STRUCTS ); return false; } const Array< const BFFVariable * > & structs = projectConfigs->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; } GetStringFromStruct( s, ".ProjectBuildCommand", newConfig.m_BuildCommand ); GetStringFromStruct( s, ".ProjectRebuildCommand", newConfig.m_RebuildCommand ); GetStringFromStruct( s, ".ProjectCleanCommand", newConfig.m_CleanCommand ); GetStringFromStruct( s, ".Output", newConfig.m_Output ); GetStringFromStruct( s, ".PreprocessorDefinitions", newConfig.m_PreprocessorDefinitions ); GetStringFromStruct( s, ".IncludeSearchPath", newConfig.m_IncludeSearchPath ); GetStringFromStruct( s, ".ForcedIncludes", newConfig.m_ForcedIncludes ); GetStringFromStruct( s, ".AssemblySearchPath", newConfig.m_AssemblySearchPath ); GetStringFromStruct( s, ".ForcedUsingAssemblies", newConfig.m_ForcedUsingAssemblies ); GetStringFromStruct( s, ".AdditionalOptions", newConfig.m_AdditionalOptions ); GetStringFromStruct( s, ".OutputDirectory", newConfig.m_OutputDirectory ); GetStringFromStruct( s, ".IntermediateDirectory", newConfig.m_IntermediateDirectory ); GetStringFromStruct( s, ".LayoutDir", newConfig.m_LayoutDir ); GetStringFromStruct( s, ".LayoutExtensionFilter", newConfig.m_LayoutExtensionFilter ); GetStringFromStruct( s, ".Xbox360DebuggerCommand", newConfig.m_Xbox360DebuggerCommand ); GetStringFromStruct( s, ".DebuggerFlavor", newConfig.m_DebuggerFlavor ); GetStringFromStruct( s, ".AumidOverride", newConfig.m_AumidOverride ); GetStringFromStruct( s, ".PlatformToolset", newConfig.m_PlatformToolset ); GetStringFromStruct( s, ".DeploymentType", newConfig.m_DeploymentType ); GetStringFromStruct( s, ".DeploymentFiles", newConfig.m_DeploymentFiles ); GetStringFromStruct( s, ".LocalDebuggerCommandArguments", newConfig.m_LocalDebuggerCommandArguments ); GetStringFromStruct( s, ".LocalDebuggerWorkingDirectory", newConfig.m_LocalDebuggerWorkingDirectory ); GetStringFromStruct( s, ".LocalDebuggerCommand", newConfig.m_LocalDebuggerCommand ); GetStringFromStruct( s, ".LocalDebuggerEnvironment", newConfig.m_LocalDebuggerEnvironment ); 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 ); } NodeGraph & ng = FBuild::Get().GetDependencyGraph(); // create all of the DirectoryListNodes we need Dependencies dirNodes( inputPaths.GetSize() ); if ( !GetDirectoryListNodeList( funcStartIter, inputPaths, Array< AString >(), Array< AString >(), true, nullptr, "ProjectInputPaths", dirNodes ) ) { return false; // GetDirectoryListNodeList will have emitted an error } // Check for existing node if ( ng.FindNode( projectOutput ) ) { Error::Error_1100_AlreadyDefined( funcStartIter, this, projectOutput ); return false; } VCXProjectNode * pn = ng.CreateVCXProjectNode( projectOutput, basePaths, dirNodes, inputPathsExclude, // TODO:B Remove this (handled by DirectoryListNode now) allowedFileExtensions, // TODO:B Remove this (handled by DirectoryListNode now) files, filesToExclude, rootNamespace, projectGuid, defaultLanguage, applicationEnvironment, configs, fileTypes, references, projectReferences ); ASSERT( pn ); return ProcessAlias( funcStartIter, pn ); }
// 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; }
// ParseIncludeDirective //------------------------------------------------------------------------------ bool BFFParser::ParseIncludeDirective( BFFIterator & iter ) { // Sanity check include depth to detect cyclic includes if ( s_Depth >= 128 ) { Error::Error_1035_ExcessiveDepthComplexity( iter ); return false; } // we expect a " quoted string if ( *iter != '"' ) { Error::Error_1031_UnexpectedCharFollowingDirectiveName( iter, AStackString<>( "include" ), '"' ); return false; } BFFIterator stringStart( iter ); stringStart++; // first actual character // find end of string if ( iter.ParseToNext( '"' ) == false ) { Error::Error_1012_UnexpectedEndOfFile( iter ); return false; } // unescape and substitute variables AStackString<> include; if ( PerformVariableSubstitutions( stringStart, iter, include ) == false ) { return false; } iter++; // skip closing quote before returning FLOG_INFO( "Including: %s\n", include.Get() ); // open include // 1) Try current directory AStackString<> includeToUse; if (PathUtils::IsFullPath(include) == false) { const char * lastSlash = iter.GetFileName().FindLast( NATIVE_SLASH ); lastSlash = lastSlash ? lastSlash : iter.GetFileName().FindLast( OTHER_SLASH ); lastSlash = lastSlash ? ( lastSlash + 1 ): iter.GetFileName().Get(); // file only, truncate to empty includeToUse.Assign( iter.GetFileName().Get(), lastSlash ); } includeToUse += include; AStackString<> includeToUseClean; NodeGraph::CleanPath( includeToUse, includeToUseClean ); FileStream f; if ( f.Open( includeToUseClean.Get(), FileStream::READ_ONLY ) == false ) { Error::Error_1032_UnableToOpenInclude( stringStart, includeToUseClean ); return false; } // check if include uses "once" pragma if ( FBuild::Get().GetDependencyGraph().IsOneUseFile( includeToUseClean ) ) { // already seen, and uses #once : don't include again return true; } uint64_t includeTimeStamp = FileIO::GetFileLastWriteTime( includeToUseClean ); // read content of include const uint32_t fileSize = (uint32_t)f.GetFileSize(); AutoPtr< char > mem( (char *)ALLOC( fileSize + 1 ) ); if ( f.Read( mem.Get(), fileSize ) != fileSize ) { Error::Error_1033_ErrorReadingInclude( stringStart, include, Env::GetLastErr() ); return false; } mem.Get()[ fileSize ] = '\000'; // sentinel BFFParser parser; const bool pushStackFrame = false; // include is treated as if injected at this point return parser.Parse( mem.Get(), fileSize, includeToUseClean.Get(), includeTimeStamp, pushStackFrame ); }
// BuildArgs //------------------------------------------------------------------------------ bool LibraryNode::BuildArgs( Args & fullArgs ) const { Array< AString > tokens( 1024, true ); m_LibrarianArgs.Tokenize( tokens ); const AString * const end = tokens.End(); for ( const AString * it = tokens.Begin(); it!=end; ++it ) { const AString & token = *it; if ( token.EndsWith( "%1" ) ) { // handle /Option:%1 -> /Option:A /Option:B /Option:C AStackString<> pre; if ( token.GetLength() > 2 ) { pre.Assign( token.Get(), token.GetEnd() - 2 ); } // concatenate files, unquoted GetInputFiles( fullArgs, pre, AString::GetEmpty() ); } else if ( token.EndsWith( "\"%1\"" ) ) { // handle /Option:"%1" -> /Option:"A" /Option:"B" /Option:"C" AStackString<> pre( token.Get(), token.GetEnd() - 3 ); // 3 instead of 4 to include quote AStackString<> post( "\"" ); // concatenate files, quoted GetInputFiles( fullArgs, pre, post ); } else if ( token.EndsWith( "%2" ) ) { // handle /Option:%2 -> /Option:A if ( token.GetLength() > 2 ) { fullArgs += AStackString<>( token.Get(), token.GetEnd() - 2 ); } fullArgs += m_Name; } else if ( token.EndsWith( "\"%2\"" ) ) { // handle /Option:"%2" -> /Option:"A" AStackString<> pre( token.Get(), token.GetEnd() - 3 ); // 3 instead of 4 to include quote fullArgs += pre; fullArgs += m_Name; fullArgs += '"'; // post } else { fullArgs += token; } fullArgs.AddDelimiter(); } // orbis-ar.exe requires escaped slashes inside response file if ( GetFlag( LIB_FLAG_ORBIS_AR ) ) { fullArgs.SetEscapeSlashesInResponseFile(); } // Handle all the special needs of args if ( fullArgs.Finalize( m_LibrarianPath, GetName(), CanUseResponseFile() ) == false ) { return false; // Finalize will have emitted an error } return true; }
// ExtractIntellisenseOptions //------------------------------------------------------------------------------ /*static*/ void ProjectGeneratorBase::ExtractIntellisenseOptions( const AString & compilerArgs, const char * option, const char * alternateOption, Array< AString > & outOptions, bool escapeQuotes ) { ASSERT( option ); Array< AString > tokens; compilerArgs.Tokenize( tokens ); const size_t optionLen = AString::StrLen( option ); const size_t alternateOptionLen = alternateOption ? AString::StrLen( alternateOption ) : 0; for ( size_t i=0; i<tokens.GetSize(); ++i ) { AString & token = tokens[ i ]; // strip quotes around token, e.g: "-IFolder/Folder" if ( token.BeginsWith( '"' ) && token.EndsWith( '"' ) ) { token.Assign( token.Get() + 1, token.GetEnd() - 1 ); } AStackString<> optionBody; // Handle space between option and payload if ( ( token == option ) || ( token == alternateOption ) ) { // Handle an incomplete token at the end of list if ( i == ( tokens.GetSize() - 1 ) ) { break; } // Use next token optionBody = tokens[ i + 1 ]; } else if ( token.BeginsWith( option ) ) { // use everything after token optionBody.Assign( token.Get() + optionLen ); } else if ( alternateOption && token.BeginsWith( alternateOption ) ) { // use everything after token optionBody.Assign( token.Get() + alternateOptionLen ); } // Strip quotes around body (e.g. -I"Folder/Folder") if ( optionBody.BeginsWith( '"' ) && optionBody.EndsWith( '"' ) ) { optionBody.Trim( 1, 1 ); } // Did we find something? if ( optionBody.IsEmpty() == false ) { if ( escapeQuotes ) { optionBody.Replace( "\"", "\\\"" ); } outOptions.Append( optionBody ); } } }
// BuildArgs //------------------------------------------------------------------------------ bool CSNode::BuildArgs( Args & fullArgs ) const { // split into tokens Array< AString > tokens( 1024, true ); m_CompilerArgs.Tokenize( tokens ); AStackString<> quote( "\"" ); const AString * const end = tokens.End(); for ( const AString * it = tokens.Begin(); it!=end; ++it ) { const AString & token = *it; if ( token.EndsWith( "%1" ) ) { // handle /Option:%1 -> /Option:A /Option:B /Option:C AStackString<> pre; if ( token.GetLength() > 2 ) { pre.Assign( token.Get(), token.GetEnd() - 2 ); } // concatenate files, unquoted GetInputFiles( fullArgs, pre, AString::GetEmpty() ); } else if ( token.EndsWith( "\"%1\"" ) ) { // handle /Option:"%1" -> /Option:"A" /Option:"B" /Option:"C" AStackString<> pre( token.Get(), token.GetEnd() - 3 ); // 3 instead of 4 to include quote // concatenate files, quoted GetInputFiles( fullArgs, pre, quote ); } else if ( token.EndsWith( "%2" ) ) { // handle /Option:%2 -> /Option:A if ( token.GetLength() > 2 ) { fullArgs += AStackString<>( token.Get(), token.GetEnd() - 2 ); } fullArgs += m_Name; } else if ( token.EndsWith( "\"%2\"" ) ) { // handle /Option:"%2" -> /Option:"A" AStackString<> pre( token.Get(), token.GetEnd() - 3 ); // 3 instead of 4 to include quote fullArgs += pre; fullArgs += m_Name; fullArgs += '"'; // post } else if ( token.EndsWith( "%3" ) ) { // handle /Option:%3 -> /Option:A,B,C AStackString<> pre( token.Get(), token.GetEnd() - 2 ); fullArgs += pre; // concatenate files, unquoted GetExtraRefs( fullArgs, AString::GetEmpty(), AString::GetEmpty() ); } else if ( token.EndsWith( "\"%3\"" ) ) { // handle /Option:"%3" -> /Option:"A","B","C" AStackString<> pre( token.Get(), token.GetEnd() - 4 ); fullArgs += pre; // concatenate files, quoted GetExtraRefs( fullArgs, quote, quote ); } else { fullArgs += token; } fullArgs.AddDelimiter(); } // Handle all the special needs of args const bool canUseResponseFile( true ); if ( fullArgs.Finalize( m_CompilerPath, GetName(), canUseResponseFile ) == false ) { return false; // Finalize will have emitted an error } return true; }