yum-mirror/slang
Making it easier to work with shaders
git clone https://git.yummers.dev/yum-mirror/slang
48ac6f25f
master
1#include "slang-win-visual-studio-util.h" 2 3#include "../../core/slang-common.h" 4#include "../../core/slang-process-util.h" 5#include "../../core/slang-string-util.h" 6#include "../slang-json-parser.h" 7#include "../slang-json-value.h" 8#include "../slang-visual-studio-compiler-util.h" 9 10#ifdef _WIN32 11#include <shlobj.h> 12#include <windows.h> 13#pragma comment(lib, "advapi32") 14#pragma comment(lib, "Shell32") 15#endif 16 17// The method used to invoke VS was originally inspired by some ideas in 18// https://github.com/RuntimeCompiledCPlusPlus/RuntimeCompiledCPlusPlus/ 19 20namespace Slang 21{ 22 23// Information on VS versioning can be found here 24// https://en.wikipedia.org/wiki/Microsoft_Visual_C%2B%2B#Internal_version_numbering 25 26 27namespace 28{// anonymous 29 30struct RegistryInfo 31{ 32const char * regName ;///< The name of the entry in the registry 33const char * pathFix ;///< With the value from the registry how to fix the path 34}; 35 36struct VersionInfo 37{ 38SemanticVersion version ;///< The version 39const char * name ;///< The name of the registry key 40}; 41 42}// namespace 43 44static SlangResult _readRegistryKey (const char * path ,const char * keyName ,String & outString ) 45{ 46// https://docs.microsoft.com/en-us/windows/desktop/api/winreg/nf-winreg-regopenkeyexa 47HKEY key ; 48LONG ret = RegOpenKeyExA (HKEY_LOCAL_MACHINE ,path ,0 ,KEY_READ |KEY_WOW64_32KEY ,& key ); 49if (ret != ERROR_SUCCESS ) 50 { 51return SLANG_FAIL ; 52 } 53 54char value [MAX_PATH ]; 55DWORD size = MAX_PATH ; 56 57// https://docs.microsoft.com/en-us/windows/desktop/api/winreg/nf-winreg-regqueryvalueexa 58ret = RegQueryValueExA (key ,keyName ,nullptr ,nullptr , (LPBYTE )value ,& size ); 59RegCloseKey (key ); 60 61if (ret != ERROR_SUCCESS ) 62 { 63return SLANG_FAIL ; 64 } 65 66outString = value ; 67return SLANG_OK ; 68} 69 70// Make easier to set up the array 71 72[[maybe_unused ]]static DownstreamCompilerMatchVersion _makeVersion (int main ) 73{ 74DownstreamCompilerMatchVersion version ; 75version .type = SLANG_PASS_THROUGH_VISUAL_STUDIO ; 76version .matchVersion .set (main ); 77return version ; 78} 79 80[[maybe_unused ]]static DownstreamCompilerMatchVersion _makeVersion (int main ,int dot ) 81{ 82DownstreamCompilerMatchVersion version ; 83version .type = SLANG_PASS_THROUGH_VISUAL_STUDIO ; 84version .matchVersion .set (main ,dot ); 85return version ; 86} 87 88VersionInfo _makeVersionInfo (const char * name ,int high ,int dot = 0 ) 89{ 90VersionInfo info ; 91info .name = name ; 92info .version = SemanticVersion (high ,dot ); 93return info ; 94} 95 96// https://en.wikipedia.org/wiki/Microsoft_Visual_Studio 97static const VersionInfo s_versionInfos []= { 98_makeVersionInfo ("VS 2005" ,8 ), 99_makeVersionInfo ("VS 2008" ,9 ), 100_makeVersionInfo ("VS 2010" ,10 ), 101_makeVersionInfo ("VS 2012" ,11 ), 102_makeVersionInfo ("VS 2013" ,12 ), 103_makeVersionInfo ("VS 2015" ,14 ), 104_makeVersionInfo ("VS 2017" ,15 ), 105_makeVersionInfo ("VS 2019" ,16 ), 106_makeVersionInfo ("VS 2022" ,17 ), 107}; 108 109// When trying to figure out how this stuff works by running regedit - care is needed, 110// because what regedit displays varies on which version of regedit is used. 111// In order to use the registry paths used here it's necessary to use Start/Run with 112// %systemroot%\syswow64\regedit to view 32 bit keys 113 114static const RegistryInfo s_regInfos []= { 115 {"SOFTWARE\\Microsoft\\VisualStudio\\SxS\\VC7" ,"" }, 116 {"SOFTWARE\\Microsoft\\VisualStudio\\SxS\\VS7" ,"VC\\Auxiliary\\Build\\" }, 117}; 118 119static bool _canUseVSWhere (SemanticVersion version ) 120{ 121// If greater than 15.0 we can use vswhere tool 122return version .m_major >=15 ; 123} 124 125static int _getRegistryKeyIndex (const SemanticVersion & version ) 126{ 127if (version .m_major >=15 ) 128 { 129return 1 ; 130 } 131return 0 ; 132} 133 134static SlangResult _parseVersion (UnownedStringSlice versionString ,SemanticVersion & outVersion ) 135{ 136// We only want the first 2 semantic numbers as 3rd looks like a build number, and too large 137List < UnownedStringSlice > slices ; 138StringUtil ::split (versionString ,'.' ,slices ); 139if (slices .getCount () >=2 ) 140 { 141versionString = UnownedStringSlice (versionString .begin (),slices [1 ].end ()); 142 } 143 144// Extract the version 145SemanticVersion semanticVersion ; 146return SemanticVersion ::parse (versionString ,outVersion ); 147} 148 149 150/* static */ DownstreamCompilerMatchVersion WinVisualStudioUtil ::getCompiledVersion () 151{ 152#ifdef _MSC_VER 153// Get the version of visual studio used to compile this source 154// Not const, because otherwise we get an warning/error about constant expression... 155uint32_t version = _MSC_VER ; 156 157switch (version ) 158 { 159case 1400 : 160return _makeVersion (8 ); 161case 1500 : 162return _makeVersion (9 ); 163case 1600 : 164return _makeVersion (10 ); 165case 1700 : 166return _makeVersion (11 ); 167case 1800 : 168return _makeVersion (12 ); 169default : 170break ; 171 } 172 173// Seems like versions go in runs of 10 at this point 174// https://docs.microsoft.com/en-us/cpp/preprocessor/predefined-macros?view=msvc-170 175// https://docs.microsoft.com/en-us/cpp/preprocessor/predefined-macros?redirectedfrom=MSDN&view=msvc-170 176if (version >=1900 && version < 1910 ) 177 { 178return _makeVersion (14 ); 179 } 180else if (version >=1910 && version < 1920 ) 181 { 182switch (version ) 183 { 184case 1910 : 185return _makeVersion (15 ,0 ); 186case 1911 : 187return _makeVersion (15 ,3 ); 188case 1912 : 189return _makeVersion (15 ,5 ); 190case 1913 : 191return _makeVersion (15 ,6 ); 192case 1914 : 193return _makeVersion (15 ,7 ); 194case 1915 : 195return _makeVersion (15 ,8 ); 196case 1916 : 197return _makeVersion (15 ,9 ); 198default : 199return _makeVersion (15 ); 200 } 201 } 202else if (version >=1920 && version < 1930 ) 203 { 204switch (version ) 205 { 206case 1920 : 207return _makeVersion (16 ,0 ); 208case 1921 : 209return _makeVersion (16 ,1 ); 210case 1922 : 211return _makeVersion (16 ,2 ); 212case 1923 : 213return _makeVersion (16 ,3 ); 214case 1924 : 215return _makeVersion (16 ,4 ); 216case 1925 : 217return _makeVersion (16 ,5 ); 218case 1926 : 219return _makeVersion (16 ,6 ); 220case 1927 : 221return _makeVersion (16 ,7 ); 222case 1928 : 223return _makeVersion (16 ,9 ); 224case 1929 : 225return _makeVersion (16 ,11 ); 226default : 227return _makeVersion (16 ); 228 } 229 } 230else if (version >=1930 && version < 1940 ) 231 { 232switch (version ) 233 { 234case 1930 : 235return _makeVersion (17 ,0 ); 236case 1931 : 237return _makeVersion (17 ,1 ); 238case 1932 : 239return _makeVersion (17 ,2 ); 240default : 241return _makeVersion (17 ); 242 } 243 } 244else if (version >=1940 ) 245 { 246// Its an unknown newer version 247return DownstreamCompilerMatchVersion ( 248SLANG_PASS_THROUGH_VISUAL_STUDIO , 249MatchSemanticVersion ::makeFuture ()); 250 } 251#endif 252 253// Unknown version 254return DownstreamCompilerMatchVersion (SLANG_PASS_THROUGH_VISUAL_STUDIO ,MatchSemanticVersion ()); 255} 256 257static SlangResult _parseJson ( 258const String & contents , 259DiagnosticSink * sink , 260JSONContainer * container , 261JSONValue & outRoot ) 262{ 263auto sourceManager = sink -> getSourceManager (); 264 265SourceFile * sourceFile = 266sourceManager -> createSourceFileWithString (PathInfo ::makeUnknown (),contents ); 267SourceView * sourceView = sourceManager -> createSourceView (sourceFile ,nullptr ,SourceLoc ()); 268 269JSONLexer lexer ; 270lexer .init (sourceView ,sink ); 271 272JSONBuilder builder (container ); 273 274JSONParser parser ; 275SLANG_RETURN_ON_FAIL (parser .parse (& lexer ,sourceView ,& builder ,sink )); 276 277outRoot = builder .getRootValue (); 278return SLANG_OK ; 279} 280 281static void _orderVersions (List < WinVisualStudioUtil ::VersionPath >& ioVersions ) 282{ 283typedef WinVisualStudioUtil ::VersionPath VersionPath ; 284// Put into increasing version order, from oldest to newest 285ioVersions .sort ( 286 [& ](const VersionPath & a ,const VersionPath & b )-> bool {return a .version < b .version ; }); 287} 288 289static SlangResult _findVersionsWithVSWhere ( 290const VersionInfo * versionInfo , 291List < WinVisualStudioUtil ::VersionPath >& outVersions ) 292{ 293typedef WinVisualStudioUtil ::VersionPath VersionPath ; 294 295CommandLine cmd ; 296 297// Lookup directly %ProgramFiles(x86)% path 298// https://docs.microsoft.com/en-us/windows/desktop/api/shlobj_core/nf-shlobj_core-shgetfolderpatha 299HWND hwnd = GetConsoleWindow (); 300 301char programFilesPath [_MAX_PATH ]; 302SHGetFolderPathA (hwnd ,CSIDL_PROGRAM_FILESX86 ,NULL ,0 ,programFilesPath ); 303 304String vswherePath = programFilesPath ; 305vswherePath .append ("\\Microsoft Visual Studio\\Installer\\vswhere" ); 306 307cmd .setExecutableLocation (ExecutableLocation (vswherePath )); 308 309// Using -? we can find out vswhere options. 310 311// Previous args - works but returns multiple versions, without listing what version is 312// associated with which path or the order. 313// String args[] = { "-version", versionName, "-requires", 314// "Microsoft.VisualStudio.Component.VC.Tools.x86.x64", ""-property", "installationPath" }; 315 316// Use JSON parsing, we can verify the versions for a path, otherwise multiple versions are 317// returned not just the version specified. The ordering isn't defined (and -sort doesn't appear 318// to work) 319 320SemanticVersion requiredVersion ; 321if (versionInfo ) 322 { 323StringBuilder versionName ; 324versionInfo -> version .append (versionName ); 325 326cmd .addArg ("-version" ); 327cmd .addArg (versionName ); 328 } 329 330// Add other args 331 { 332// TODO(JS): 333// For arm targets will probably need something different for tooling 334String args []= { 335"-format" , 336"json" , 337"-utf8" , 338"-requires" , 339"Microsoft.VisualStudio.Component.VC.Tools.x86.x64" }; 340cmd .addArgs (args ,SLANG_COUNT_OF (args )); 341 } 342 343// We are going to use JSON parser to extract the info 344SourceManager sourceManager ; 345sourceManager .initialize (nullptr ,nullptr ); 346DiagnosticSink sink (& sourceManager ,nullptr ); 347 348RefPtr < JSONContainer > container = new JSONContainer (& sourceManager ); 349 350ExecuteResult exeRes ; 351SLANG_RETURN_ON_FAIL (ProcessUtil ::execute (cmd ,exeRes )); 352 353JSONValue jsonRoot ; 354SLANG_RETURN_ON_FAIL (_parseJson (exeRes .standardOutput ,& sink ,container ,jsonRoot )); 355 356// Search through the array... 357if (jsonRoot .getKind ()!= JSONValue ::Kind ::Array ) 358 { 359return SLANG_FAIL ; 360 } 361 362auto arr = container -> getArray (jsonRoot ); 363 364const auto pathKey = container -> getKey (UnownedStringSlice ::fromLiteral ("installationPath" )); 365const auto versionKey = 366container -> getKey (UnownedStringSlice ::fromLiteral ("installationVersion" )); 367 368// Find all the versions, that match 369for (auto elem :arr ) 370 { 371// Get the path and the name 372if (elem .getKind ()!= JSONValue ::Kind ::Object ) 373 { 374continue ; 375 } 376 377auto pathJsonValue = container -> findObjectValue (elem ,pathKey ); 378auto versionJsonValue = container -> findObjectValue (elem ,versionKey ); 379 380if (!pathJsonValue .isValid ()|| !versionJsonValue .isValid ()) 381 { 382continue ; 383 } 384 385auto pathString = container -> getString (pathJsonValue ); 386auto versionString = container -> getString (versionJsonValue ).trim (); 387 388// Extract the version 389SemanticVersion semanticVersion ; 390if (SLANG_SUCCEEDED (_parseVersion (versionString ,semanticVersion ))) 391 { 392if (!requiredVersion .isSet ()|| requiredVersion .m_major == semanticVersion .m_major ) 393 { 394WinVisualStudioUtil ::VersionPath versionPath ; 395 396versionPath .vcvarsPath = pathString ; 397versionPath .vcvarsPath .append ("\\VC\\Auxiliary\\Build\\" ); 398versionPath .version = semanticVersion ; 399 400outVersions .add (versionPath ); 401 } 402 } 403 } 404 405return SLANG_OK ; 406} 407 408static SlangResult _findVersionsWithRegistery (List < WinVisualStudioUtil ::VersionPath >& outVersions ) 409{ 410typedef WinVisualStudioUtil ::VersionPath VersionPath ; 411 412const int versionCount = SLANG_COUNT_OF (s_versionInfos ); 413 414for (int i = versionCount - 1 ;i >=0 ;-- i ) 415 { 416const auto versionInfo = s_versionInfos [i ]; 417 418auto version = versionInfo .version ; 419 420// Try locating via the registry 421const Int keyIndex = _getRegistryKeyIndex (version ); 422if (keyIndex >=0 ) 423 { 424SLANG_ASSERT (keyIndex < SLANG_COUNT_OF (s_regInfos )); 425 426// Try reading the key 427const auto & keyInfo = s_regInfos [keyIndex ]; 428 429StringBuilder keyName ; 430versionInfo .version .append (keyName ); 431 432String value ; 433if (SLANG_SUCCEEDED (_readRegistryKey (keyInfo .regName ,keyName .getBuffer (),value ))) 434 { 435VersionPath versionPath ; 436versionPath .version = versionInfo .version ; 437versionPath .vcvarsPath = value ; 438 439// Append 440if (keyInfo .pathFix && keyInfo .pathFix [0 ]!= 0 ) 441 { 442versionPath .vcvarsPath .append (keyInfo .pathFix ); 443 } 444 445outVersions .add (versionPath ); 446 } 447 } 448 } 449 450return SLANG_OK ; 451} 452 453/* static */ SlangResult WinVisualStudioUtil ::find (List < VersionPath >& outVersionPaths ) 454{ 455outVersionPaths .clear (); 456 457List < VersionPath > regVersions ; 458 459// Find all versions with vswhere 460_findVersionsWithVSWhere (nullptr ,outVersionPaths ); 461// Find all with the registry 462_findVersionsWithRegistery (regVersions ); 463 464// Merge 465for (const auto & regVersion :regVersions ) 466 { 467Index foundIndex = -1 ; 468if (_canUseVSWhere (regVersion .version )) 469 { 470// If there is a major version already from vswhere, we don't need to merge 471const auto majorVersion = regVersion .version .m_major ; 472foundIndex = outVersionPaths .findFirstIndex ( 473 [& ](const VersionPath & cur )-> bool 474 {return cur .version .m_major == majorVersion ; }); 475 } 476else 477 { 478// See if we can find the exact version 479foundIndex = outVersionPaths .findFirstIndex ( 480 [& ](const VersionPath & cur )-> bool {return cur .version == regVersion .version ; }); 481 } 482 483// If it wasn't found add it. 484if (foundIndex < 0 ) 485 { 486outVersionPaths .add (regVersion ); 487 } 488 } 489// Sort 490_orderVersions (outVersionPaths ); 491return SLANG_OK ; 492} 493 494/* static */ SlangResult WinVisualStudioUtil ::find (DownstreamCompilerSet * set ) 495{ 496List < VersionPath > versionPaths ; 497SLANG_RETURN_ON_FAIL (find (versionPaths )); 498 499for (const auto & versionPath :versionPaths ) 500 { 501// Turn into a desc 502const DownstreamCompilerDesc desc (SLANG_PASS_THROUGH_VISUAL_STUDIO ,versionPath .version ); 503 504// If not in set add it 505if (!set -> getCompiler (desc )) 506 { 507auto compiler = new VisualStudioDownstreamCompiler (desc ); 508ComPtr < IDownstreamCompiler > compilerIntf (compiler ); 509calcExecuteCompilerArgs (versionPath ,compiler -> m_cmdLine ); 510set -> addCompiler (compilerIntf ); 511 } 512 } 513 514return SLANG_OK ; 515} 516 517/* static */ void WinVisualStudioUtil ::calcExecuteCompilerArgs ( 518const VersionPath & versionPath , 519CommandLine & outCmdLine ) 520{ 521// To invoke cl we need to run the suitable vcvars. In order to run this we have to have MS 522// CommandLine. So here we build up a cl command line that is run by first running vcvars, and 523// then executing cl with the parameters as passed to commandLine 524 525// https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-createprocessa 526// To run a batch file, you must start the command interpreter; set lpApplicationName to cmd.exe 527// and set lpCommandLine to the following arguments: /c plus the name of the batch file. 528 529CommandLine cmdLine ; 530cmdLine .setExecutableLocation (ExecutableLocation (ExecutableLocation ::Type ::Name ,"cmd.exe" )); 531 532 { 533String options []= {"/q" ,"/c" ,"@prompt" ,"$" }; 534cmdLine .addArgs (options ,SLANG_COUNT_OF (options )); 535 } 536 537cmdLine .addArg ("&&" ); 538cmdLine .addArg (Path ::combine (versionPath .vcvarsPath ,"vcvarsall.bat" )); 539 540#if SLANG_PTR_IS_32 541cmdLine .addArg ("x86" ); 542#else 543cmdLine .addArg ("x86_amd64" ); 544#endif 545 546cmdLine .addArg ("&&" ); 547cmdLine .addArg ("cl" ); 548 549outCmdLine = cmdLine ; 550} 551 552/* static */ SlangResult WinVisualStudioUtil ::executeCompiler ( 553const VersionPath & versionPath , 554const CommandLine & commandLine , 555ExecuteResult & outResult ) 556{ 557CommandLine cmdLine ; 558calcExecuteCompilerArgs (versionPath ,cmdLine ); 559// Append the command line options 560cmdLine .addArgs (commandLine .m_args .getBuffer (),commandLine .m_args .getCount ()); 561return ProcessUtil ::execute (cmdLine ,outResult ); 562} 563 564}// namespace Slang