//========= Copyright Valve Corporation, All rights reserved. ============// // // Purpose: // // $NoKeywords: $ //=============================================================================// #ifdef _LINUX #include // needed by xercesc #endif #include "stdafx.h" #include "tier0/platform.h" #include #include #include #include #ifdef _WIN32 #include #include // _variant_t #include // CComPtr #elif _LINUX #include #include // scandir() #define _stat stat #include #include #include #include #include #include "valve_minmax_off.h" #if defined(XERCES_NEW_IOSTREAMS) #include #else #include #endif #include "valve_minmax_on.h" #define IXMLDOMNode DOMNode #define IXMLDOMNodeList DOMNodeList #define _alloca alloca XERCES_CPP_NAMESPACE_USE class XStr { public : XStr(const char* const toTranscode) { // Call the private transcoding method fUnicodeForm = XMLString::transcode(toTranscode); } ~XStr() { XMLString::release(&fUnicodeForm); } // ----------------------------------------------------------------------- // Getter methods // ----------------------------------------------------------------------- const XMLCh* unicodeForm() const { return fUnicodeForm; } private : XMLCh* fUnicodeForm; }; #define _bstr_t(str) XStr(str).unicodeForm() #else #error "Unsupported platform" #endif #include "vcprojconvert.h" #include "utlvector.h" //----------------------------------------------------------------------------- // Purpose: constructor //----------------------------------------------------------------------------- CVCProjConvert::CVCProjConvert() { #ifdef _WIN32 ::CoInitialize(NULL); #elif _LINUX try { XMLPlatformUtils::Initialize(); } catch (const XMLException& toCatch) { char* message = XMLString::transcode(toCatch.getMessage()); Error( "Error during initialization! : %s\n", message); XMLString::release(&message); } #endif m_bProjectLoaded = false; } //----------------------------------------------------------------------------- // Purpose: destructor //----------------------------------------------------------------------------- CVCProjConvert::~CVCProjConvert() { #ifdef _WIN32 ::CoUninitialize(); #elif _LINUX // nothing to shutdown #endif } //----------------------------------------------------------------------------- // Purpose: load up a project and parse it //----------------------------------------------------------------------------- bool CVCProjConvert::LoadProject( const char *project ) { #ifdef _WIN32 HRESULT hr; IXMLDOMDocument *pXMLDoc=NULL; hr = ::CoCreateInstance(CLSID_DOMDocument, NULL, CLSCTX_INPROC_SERVER, IID_IXMLDOMDocument, (void**)&pXMLDoc); if (FAILED(hr)) { Msg ("Cannot instantiate msxml2.dll\n"); Msg ("Please download the MSXML run-time (url below)\n"); Msg ("http://msdn.microsoft.com/downloads/default.asp?url=/downloads/sample.asp?url=/msdn-files/027/001/766/msdncompositedoc.xml\n"); return false; } VARIANT_BOOL vtbool; _variant_t bstrProject(project); pXMLDoc->put_async( VARIANT_BOOL(FALSE) ); hr = pXMLDoc->load(bstrProject,&vtbool); if (FAILED(hr) || vtbool==VARIANT_FALSE) { Msg ("Could not open %s.\n", bstrProject); pXMLDoc->Release(); return false; } #elif _LINUX XercesDOMParser* parser = new XercesDOMParser(); parser->setValidationScheme(XercesDOMParser::Val_Always); // optional. parser->setDoNamespaces(true); // optional ErrorHandler* errHandler = (ErrorHandler*) new HandlerBase(); parser->setErrorHandler(errHandler); try { parser->parse(project); } catch (const XMLException& toCatch) { char* message = XMLString::transcode(toCatch.getMessage()); Error( "Exception message is: %s\n", message ); XMLString::release(&message); return; } catch (const DOMException& toCatch) { char* message = XMLString::transcode(toCatch.msg); Error( "Exception message is: %s\n", message ); XMLString::release(&message); return; } catch (...) { Error( "Unexpected Exception \n" ); return; } DOMDocument *pXMLDoc = parser->getDocument(); #endif ExtractProjectName( pXMLDoc ); if ( !m_Name.IsValid() ) { Msg( "Failed to extract project name\n" ); return false; } char baseDir[ MAX_PATH ]; Q_ExtractFilePath( project, baseDir, sizeof(baseDir) ); Q_StripTrailingSlash( baseDir ); m_BaseDir = baseDir; ExtractConfigurations( pXMLDoc ); if ( m_Configurations.Count() == 0 ) { Msg( "Failed to find any configurations to load\n" ); return false; } ExtractFiles( pXMLDoc ); #ifdef _WIN32 pXMLDoc->Release(); #elif _LINUX delete pXMLDoc; delete errHandler; #endif m_bProjectLoaded = true; return true; } //----------------------------------------------------------------------------- // Purpose: returns the number of different configurations loaded //----------------------------------------------------------------------------- int CVCProjConvert::GetNumConfigurations() { Assert( m_bProjectLoaded ); return m_Configurations.Count(); } //----------------------------------------------------------------------------- // Purpose: returns the index of a config with this name, -1 on err //----------------------------------------------------------------------------- int CVCProjConvert::FindConfiguration( CUtlSymbol name ) { if ( !name.IsValid() ) { return -1; } for ( int i = 0; i < m_Configurations.Count(); i++ ) { if ( m_Configurations[i].GetName() == name ) { return i; } } return -1; } //----------------------------------------------------------------------------- // Purpose: extracts the value of the xml attrib "attribName" //----------------------------------------------------------------------------- CUtlSymbol CVCProjConvert::GetXMLAttribValue( IXMLDOMElement *p, const char *attribName ) { if (!p) { return CUtlSymbol(); } #ifdef _WIN32 VARIANT vtValue; p->getAttribute( _bstr_t(attribName), &vtValue); if ( vtValue.vt == VT_NULL ) { return CUtlSymbol(); // element not found } Assert( vtValue.vt == VT_BSTR ); CUtlSymbol name( static_cast( _bstr_t( vtValue.bstrVal ) ) ); ::SysFreeString(vtValue.bstrVal); #elif _LINUX const XMLCh *xAttrib = XMLString::transcode( attribName ); const XMLCh *value = p->getAttribute( xAttrib ); if ( value == NULL ) { return CUtlSymbol(); // element not found } char *transValue = XMLString::transcode(value); CUtlSymbol name( transValue ); XMLString::release( &xAttrib ); XMLString::release( &transValue ); #endif return name; } //----------------------------------------------------------------------------- // Purpose: returns the name of this node //----------------------------------------------------------------------------- CUtlSymbol CVCProjConvert::GetXMLNodeName( IXMLDOMElement *p ) { CUtlSymbol name; if (!p) { return name; } #ifdef _WIN32 BSTR bstrName; p->get_nodeName( &bstrName ); _bstr_t bstr(bstrName); name = static_cast(bstr); return name; #elif _LINUX Assert( 0 ); Error( "Function CVCProjConvert::GetXMLNodeName not implemented\n" ); return name; #endif } //----------------------------------------------------------------------------- // Purpose: returns the config object at this index //----------------------------------------------------------------------------- CVCProjConvert::CConfiguration & CVCProjConvert::GetConfiguration( int i ) { Assert( m_bProjectLoaded ); Assert( m_Configurations.IsValidIndex(i) ); return m_Configurations[i]; } //----------------------------------------------------------------------------- // Purpose: extracts the project name from the loaded vcproj //----------------------------------------------------------------------------- bool CVCProjConvert::ExtractProjectName( IXMLDOMDocument *pDoc ) { #ifdef _WIN32 CComPtr pProj; pDoc->getElementsByTagName( _bstr_t("VisualStudioProject"), &pProj); if (pProj) { long len = 0; pProj->get_length(&len); Assert( len == 1 ); if ( len == 1 ) { CComPtr pNode; pProj->get_item( 0, &pNode ); if (pNode) { CComQIPtr pElem( pNode ); m_Name = GetXMLAttribValue( pElem, "Name"); } } } #elif _LINUX DOMNodeList *nodes = pDoc->getElementsByTagName( _bstr_t("VisualStudioProject") ); if ( nodes ) { int len = nodes->getLength(); if ( len == 1 ) { DOMNode *node = nodes->item(0); if ( node ) { m_Name = GetXMLAttribValue( node, "Name" ); } } } #endif return true; } //----------------------------------------------------------------------------- // Purpose: extracts the list of configuration names from the vcproj //----------------------------------------------------------------------------- bool CVCProjConvert::ExtractConfigurations( IXMLDOMDocument *pDoc ) { m_Configurations.RemoveAll(); if (!pDoc) { return false; } #ifdef _WIN32 CComPtr pConfigs; pDoc->getElementsByTagName( _bstr_t("Configuration"), &pConfigs); if (pConfigs) { long len = 0; pConfigs->get_length(&len); for ( int i=0; i pNode; pConfigs->get_item( i, &pNode ); if (pNode) { CComQIPtr pElem( pNode ); CUtlSymbol configName = GetXMLAttribValue( pElem, "Name" ); if ( configName.IsValid() ) { int newIndex = m_Configurations.AddToTail(); CConfiguration & config = m_Configurations[newIndex]; config.SetName( configName ); ExtractIncludes( pElem, config ); } } } } #elif _LINUX DOMNodeList *nodes = pDoc->getElementsByTagName( _bstr_t("Configuration")); if ( nodes ) { int len = nodes->getLength(); for ( int i=0; iitem(i); if (node) { CUtlSymbol configName = GetXMLAttribValue( node, "Name" ); if ( configName.IsValid() ) { int newIndex = m_Configurations.AddToTail(); CConfiguration & config = m_Configurations[newIndex]; config.SetName( configName ); ExtractIncludes( node, config ); } } } } #endif return true; } //----------------------------------------------------------------------------- // Purpose: extracts the list of defines and includes used for this config //----------------------------------------------------------------------------- bool CVCProjConvert::ExtractIncludes( IXMLDOMElement *pDoc, CConfiguration & config ) { config.ResetDefines(); config.ResetIncludes(); if (!pDoc) { return false; } #ifdef _WIN32 CComPtr pTools; pDoc->getElementsByTagName( _bstr_t("Tool"), &pTools); if (pTools) { long len = 0; pTools->get_length(&len); for ( int i=0; i pNode; pTools->get_item( i, &pNode ); if (pNode) { CComQIPtr pElem( pNode ); CUtlSymbol toolName = GetXMLAttribValue( pElem, "Name" ); if ( toolName == "VCCLCompilerTool" ) { CUtlSymbol defines = GetXMLAttribValue( pElem, "PreprocessorDefinitions" ); char *str = (char *)_alloca( Q_strlen( defines.String() ) + 1 ); Assert( str ); Q_strcpy( str, defines.String() ); // now tokenize the string on the ";" char char *delim = strchr( str, ';' ); char *curpos = str; while ( delim ) { *delim = 0; delim++; if ( Q_stricmp( curpos, "WIN32" ) && Q_stricmp( curpos, "_WIN32" ) && Q_stricmp( curpos, "_WINDOWS") && Q_stricmp( curpos, "WINDOWS")) // don't add WIN32 defines { config.AddDefine( curpos ); } curpos = delim; delim = strchr( delim, ';' ); } if ( Q_stricmp( curpos, "WIN32" ) && Q_stricmp( curpos, "_WIN32" ) && Q_stricmp( curpos, "_WINDOWS") && Q_stricmp( curpos, "WINDOWS")) // don't add WIN32 defines { config.AddDefine( curpos ); } CUtlSymbol includes = GetXMLAttribValue( pElem, "AdditionalIncludeDirectories" ); char *str2 = (char *)_alloca( Q_strlen( includes.String() ) + 1 ); Assert( str2 ); Q_strcpy( str2, includes.String() ); // now tokenize the string on the ";" char delim = strchr( str2, ',' ); curpos = str2; while ( delim ) { *delim = 0; delim++; config.AddInclude( curpos ); curpos = delim; delim = strchr( delim, ',' ); } config.AddInclude( curpos ); } } } } #elif _LINUX DOMNodeList *nodes= pDoc->getElementsByTagName( _bstr_t("Tool")); if (nodes) { int len = nodes->getLength(); for ( int i=0; iitem(i); if (node) { CUtlSymbol toolName = GetXMLAttribValue( node, "Name" ); if ( toolName == "VCCLCompilerTool" ) { CUtlSymbol defines = GetXMLAttribValue( node, "PreprocessorDefinitions" ); char *str = (char *)_alloca( Q_strlen( defines.String() ) + 1 ); Assert( str ); Q_strcpy( str, defines.String() ); // now tokenize the string on the ";" char char *delim = strchr( str, ';' ); char *curpos = str; while ( delim ) { *delim = 0; delim++; if ( Q_stricmp( curpos, "WIN32" ) && Q_stricmp( curpos, "_WIN32" ) && Q_stricmp( curpos, "_WINDOWS") && Q_stricmp( curpos, "WINDOWS")) // don't add WIN32 defines { config.AddDefine( curpos ); } curpos = delim; delim = strchr( delim, ';' ); } if ( Q_stricmp( curpos, "WIN32" ) && Q_stricmp( curpos, "_WIN32" ) && Q_stricmp( curpos, "_WINDOWS") && Q_stricmp( curpos, "WINDOWS")) // don't add WIN32 defines { config.AddDefine( curpos ); } CUtlSymbol includes = GetXMLAttribValue( node, "AdditionalIncludeDirectories" ); char *str2 = (char *)_alloca( Q_strlen( includes.String() ) + 1 ); Assert( str2 ); Q_strcpy( str2, includes.String() ); // now tokenize the string on the ";" char char token = ','; delim = strchr( str2, token ); if ( !delim ) { token = ';'; delim = strchr( str2, token ); } curpos = str2; while ( delim ) { *delim = 0; delim++; Q_FixSlashes( curpos ); char fullPath[ MAX_PATH ]; Q_snprintf( fullPath, sizeof(fullPath), "%s/%s", m_BaseDir.String(), curpos ); Q_StripTrailingSlash( fullPath ); config.AddInclude( fullPath ); curpos = delim; delim = strchr( delim, token ); } Q_FixSlashes( curpos ); Q_strlower( curpos ); char fullPath[ MAX_PATH ]; Q_snprintf( fullPath, sizeof(fullPath), "%s/%s", m_BaseDir.String(), curpos ); Q_StripTrailingSlash( fullPath ); config.AddInclude( fullPath ); } } } } #endif return true; } //----------------------------------------------------------------------------- // Purpose: walks a particular files config entry and removes an files not valid for this config //----------------------------------------------------------------------------- bool CVCProjConvert::IterateFileConfigurations( IXMLDOMElement *pFile, CUtlSymbol fileName ) { #ifdef _WIN32 CComPtr pConfigs; pFile->getElementsByTagName( _bstr_t("FileConfiguration"), &pConfigs); if (pConfigs) { long len = 0; pConfigs->get_length(&len); for ( int i=0; i pNode; pConfigs->get_item( i, &pNode); if (pNode) { CComQIPtr pElem( pNode ); CUtlSymbol configName = GetXMLAttribValue( pElem, "Name"); CUtlSymbol excluded = GetXMLAttribValue( pElem ,"ExcludedFromBuild"); if ( configName.IsValid() && excluded.IsValid() ) { int index = FindConfiguration( configName ); if ( index > 0 && excluded == "TRUE" ) { m_Configurations[index].RemoveFile( fileName ); } } } }//for }//if #elif _LINUX DOMNodeList *nodes = pFile->getElementsByTagName( _bstr_t("FileConfiguration")); if (nodes) { int len = nodes->getLength(); for ( int i=0; iitem(i); if (node) { CUtlSymbol configName = GetXMLAttribValue( node, "Name"); CUtlSymbol excluded = GetXMLAttribValue( node ,"ExcludedFromBuild"); if ( configName.IsValid() && excluded.IsValid() ) { int index = FindConfiguration( configName ); if ( index >= 0 && excluded == "TRUE" ) { m_Configurations[index].RemoveFile( fileName ); } } } }//for }//if #endif return true; } //----------------------------------------------------------------------------- // Purpose: walks the file elements in the vcproj and inserts them into configs //----------------------------------------------------------------------------- bool CVCProjConvert::ExtractFiles( IXMLDOMDocument *pDoc ) { if (!pDoc) { return false; } Assert( m_Configurations.Count() ); // some configs must be loaded first #ifdef _WIN32 CComPtr pFiles; pDoc->getElementsByTagName( _bstr_t("File"), &pFiles); if (pFiles) { long len = 0; pFiles->get_length(&len); for ( int i=0; i pNode; pFiles->get_item( i, &pNode); if (pNode) { CComQIPtr pElem( pNode ); CUtlSymbol fileName = GetXMLAttribValue(pElem,"RelativePath"); if ( fileName.IsValid() ) { CConfiguration::FileType_e type = GetFileType( fileName.String() ); CConfiguration::CFileEntry fileEntry( fileName.String(), type ); for ( int i = 0; i < m_Configurations.Count(); i++ ) // add the file to all configs { CConfiguration & config = m_Configurations[i]; config.InsertFile( fileEntry ); } IterateFileConfigurations( pElem, fileName ); // now remove the excluded ones } } }//for } #elif _LINUX DOMNodeList *nodes = pDoc->getElementsByTagName( _bstr_t("File") ); if (nodes) { int len = nodes->getLength(); for ( int i=0; iitem(i); if (node) { CUtlSymbol fileName = GetXMLAttribValue(node,"RelativePath"); if ( fileName.IsValid() ) { char fixedFileName[ MAX_PATH ]; Q_strncpy( fixedFileName, fileName.String(), sizeof(fixedFileName) ); if ( fixedFileName[0] == '.' && fixedFileName[1] == '\\' ) { Q_memmove( fixedFileName, fixedFileName+2, sizeof(fixedFileName)-2 ); } Q_FixSlashes( fixedFileName ); FindFileCaseInsensitive( fixedFileName, sizeof(fixedFileName) ); CConfiguration::FileType_e type = GetFileType( fileName.String() ); CConfiguration::CFileEntry fileEntry( fixedFileName, type ); for ( int i = 0; i < m_Configurations.Count(); i++ ) // add the file to all configs { CConfiguration & config = m_Configurations[i]; config.InsertFile( fileEntry ); } IterateFileConfigurations( node, fixedFileName ); // now remove the excluded ones } } }//for } #endif return true; } #ifdef _LINUX static char fileName[MAX_PATH]; int CheckName(const struct dirent *dir) { return !strcasecmp( dir->d_name, fileName ); } const char *findFileInDirCaseInsensitive(const char *file) { const char *dirSep = strrchr(file,'/'); if( !dirSep ) { dirSep=strrchr(file,'\\'); if( !dirSep ) { return NULL; } } char *dirName = static_cast( alloca( ( dirSep - file ) +1 ) ); if( !dirName ) return NULL; Q_strncpy( dirName, file, dirSep - file ); dirName[ dirSep - file ] = '\0'; struct dirent **namelist; int n; Q_strncpy( fileName, dirSep + 1, MAX_PATH ); n = scandir( dirName , &namelist, CheckName, alphasort ); if( n > 0 ) { while( n > 1 ) { free( namelist[n] ); // free the malloc'd strings n--; } Q_snprintf( fileName, sizeof( fileName ), "%s/%s", dirName, namelist[0]->d_name ); return fileName; } else { // last ditch attempt, just return the lower case version! Q_strncpy( fileName, file, sizeof(fileName) ); Q_strlower( fileName ); return fileName; } } #endif void CVCProjConvert::FindFileCaseInsensitive( char *fileName, int fileNameSize ) { char filePath[ MAX_PATH ]; Q_snprintf( filePath, sizeof(filePath), "%s/%s", m_BaseDir.String(), fileName ); struct _stat buf; if ( _stat( filePath, &buf ) == 0) { return; // found the filename directly } #ifdef _LINUX const char *realName = findFileInDirCaseInsensitive( filePath ); if ( realName ) { Q_strncpy( fileName, realName+strlen(m_BaseDir.String())+1, fileNameSize ); } #endif } //----------------------------------------------------------------------------- // Purpose: extracts the generic type of a file being loaded //----------------------------------------------------------------------------- CVCProjConvert::CConfiguration::FileType_e CVCProjConvert::GetFileType( const char *fileName ) { CConfiguration::FileType_e type = CConfiguration::FILE_TYPE_UNKNOWN_E; char ext[10]; Q_ExtractFileExtension( fileName, ext, sizeof(ext) ); if ( !Q_stricmp( ext, "lib" ) ) { type = CConfiguration::FILE_LIBRARY; } else if ( !Q_stricmp( ext, "h" ) ) { type = CConfiguration::FILE_HEADER; } else if ( !Q_stricmp( ext, "hh" ) ) { type = CConfiguration::FILE_HEADER; } else if ( !Q_stricmp( ext, "hpp" ) ) { type = CConfiguration::FILE_HEADER; } else if ( !Q_stricmp( ext, "cpp" ) ) { type = CConfiguration::FILE_SOURCE; } else if ( !Q_stricmp( ext, "c" ) ) { type = CConfiguration::FILE_SOURCE; } else if ( !Q_stricmp( ext, "cc" ) ) { type = CConfiguration::FILE_SOURCE; } return type; }