vccexe.nim 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189
  1. import strutils, strtabs, os, osproc, vcvarsall, vccenv
  2. type
  3. VccVersion* = enum ## VCC compiler backend versions
  4. vccUndefined = 0, ## VCC version undefined, resolves to the latest recognizable VCC version
  5. vcc90 = vs90 ## Visual Studio 2008 (Version 9.0)
  6. vcc100 = vs100 ## Visual Studio 2010 (Version 10.0)
  7. vcc110 = vs110 ## Visual Studio 2012 (Version 11.0)
  8. vcc120 = vs120 ## Visual Studio 2013 (Version 12.0)
  9. vcc140 = vs140 ## Visual Studio 2015 (Version 14.0)
  10. proc discoverVccVcVarsAllPath*(version: VccVersion = vccUndefined): string =
  11. ## Returns the path to the vcvarsall utility of the specified VCC compiler backend.
  12. ##
  13. ## version
  14. ## The specific version of the VCC compiler backend to discover.
  15. ## Defaults to the latest recognized VCC compiler backend that is found on the system.
  16. ##
  17. ## Returns `nil` if the VCC compiler backend discovery failed.
  18. # TODO: Attempt discovery using vswhere utility.
  19. # Attempt discovery through VccEnv
  20. # (Trying Visual Studio Common Tools Environment Variables)
  21. result = vccEnvVcVarsAllPath(cast[VccEnvVersion](version))
  22. if result.len > 0:
  23. return
  24. # All attempts to dicover vcc failed
  25. const
  26. vccversionPrefix = "--vccversion"
  27. printPathPrefix = "--printPath"
  28. vcvarsallPrefix = "--vcvarsall"
  29. commandPrefix = "--command"
  30. noCommandPrefix = "--noCommand"
  31. platformPrefix = "--platform"
  32. sdktypePrefix = "--sdktype"
  33. sdkversionPrefix = "--sdkversion"
  34. verbosePrefix = "--verbose"
  35. vccversionSepIdx = vccversionPrefix.len
  36. vcvarsallSepIdx = vcvarsallPrefix.len
  37. commandSepIdx = commandPrefix.len
  38. platformSepIdx = platformPrefix.len
  39. sdktypeSepIdx = sdktypePrefix.len
  40. sdkversionSepIdx = sdkversionPrefix.len
  41. helpText = """
  42. +-----------------------------------------------------------------+
  43. | Microsoft C/C++ compiler wrapper for Nim |
  44. | & |
  45. | Microsoft C/C++ Compiler Discovery Utility |
  46. | (c) 2017 Fredrik Hoeisaether Rasch |
  47. +-----------------------------------------------------------------+
  48. Usage:
  49. vccexe [options] [compileroptions]
  50. Options:
  51. --vccversion:<v> Optionally specify the VCC version to discover
  52. <v>: 0, 90, 100, 110, 120, 140
  53. If <v> is omitted, attempts to discover the latest
  54. installed version. <v>: 0, 90, 100, 110, 120, 140
  55. A value of 0 will discover the latest installed SDK
  56. Multiple values can be specified, separated by ,
  57. --printPath Print the discovered path of the vcvarsall utility
  58. of the VCC version specified with the --vccversion argument.
  59. For each specified version the utility prints a line with the
  60. following format: <version>: <path>
  61. --noCommand Flag to supress VCC secondary command execution
  62. Useful in conjuction with --vccversion and --printPath to
  63. only perfom VCC discovery, but without executing VCC tools
  64. --vcvarsall:<path> Path to the Developer Command Prompt utility vcvarsall.bat that selects
  65. the appropiate devlopment settings.
  66. Usual path for Visual Studio 2015 and below:
  67. %VSInstallDir%\VC\vcvarsall
  68. Usual path for Visual Studio 2017 and above:
  69. %VSInstallDir%\VC\Auxiliary\Build\vcvarsall
  70. --command:<exec> Specify the command to run once the development environment is loaded.
  71. <exec> can be any command-line argument. Any arguments not recognized by vccexe
  72. are passed on as arguments to this command.
  73. cl.exe is invoked by default if this argument is omitted.
  74. --platform:<arch> Specify the Compiler Platform Tools architecture
  75. <arch>: x86 | amd64 | arm | x86_amd64 | x86_arm | amd64_x86 | amd64_arm
  76. Values with two architectures (like x86_amd64) specify the architecture
  77. of the cross-platform compiler (e.g. x86) and the target it compiles to (e.g. amd64).
  78. --sdktype:<type> Specify the SDK flavor to use. Defaults to the Desktop SDK.
  79. <type>: {empty} | store | uwp | onecore
  80. --sdkversion:<v> Use a specific Windows SDK version:
  81. <v> is either the full Windows 10 SDK version number or
  82. "8.1" to use the windows 8.1 SDK
  83. --verbose Echoes the command line for loading the Developer Command Prompt
  84. and the command line passed on to the secondary command.
  85. Other command line arguments are passed on to the
  86. secondary command specified by --command or to the
  87. Microsoft (R) C/C++ Optimizing Compiler if no secondary
  88. command was specified
  89. """
  90. when isMainModule:
  91. var vccversionArg: seq[string] = @[]
  92. var printPathArg: bool = false
  93. var vcvarsallArg: string
  94. var commandArg: string
  95. var noCommandArg: bool = false
  96. var platformArg: VccArch
  97. var sdkTypeArg: VccPlatformType
  98. var sdkVersionArg: string
  99. var verboseArg: bool = false
  100. var clArgs: seq[TaintedString] = @[]
  101. # Cannot use usual command-line argument parser here
  102. # Since vccexe command-line arguments are intermingled
  103. # with the secondary command-line arguments which have
  104. # a syntax that is not supported by the default nim
  105. # argument parser.
  106. var wrapperArgs = commandLineParams()
  107. for wargv in wrapperArgs:
  108. # Check whether the current argument contains -- prefix
  109. if wargv.startsWith(vccversionPrefix): # Check for vccversion
  110. vccversionArg.add(wargv.substr(vccversionSepIdx + 1))
  111. elif wargv.cmpIgnoreCase(printPathPrefix) == 0: # Check for printPath
  112. printPathArg = true
  113. elif wargv.startsWith(vcvarsallPrefix): # Check for vcvarsall
  114. vcvarsallArg = wargv.substr(vcvarsallSepIdx + 1)
  115. elif wargv.startsWith(commandPrefix): # Check for command
  116. commandArg = wargv.substr(commandSepIdx + 1)
  117. elif wargv.cmpIgnoreCase(noCommandPrefix) == 0: # Check for noCommand
  118. noCommandArg = true
  119. elif wargv.startsWith(platformPrefix): # Check for platform
  120. platformArg = parseEnum[VccArch](wargv.substr(platformSepIdx + 1))
  121. elif wargv.startsWith(sdktypePrefix): # Check for sdktype
  122. sdkTypeArg = parseEnum[VccPlatformType](wargv.substr(sdktypeSepIdx + 1))
  123. elif wargv.startsWith(sdkversionPrefix): # Check for sdkversion
  124. sdkVersionArg = wargv.substr(sdkversionSepIdx + 1)
  125. elif wargv.startsWith(verbosePrefix):
  126. verboseArg = true
  127. else: # Regular cl.exe argument -> store for final cl.exe invocation
  128. if (wargv.len == 2) and (wargv[1] == '?'):
  129. echo helpText
  130. clArgs.add(wargv)
  131. # Support for multiple specified versions. Attempt VCC discovery for each version
  132. # specified, first successful discovery wins
  133. var vccversionValue: VccVersion = vccUndefined
  134. for vccversionItem in vccversionArg:
  135. try:
  136. vccversionValue = cast[VccVersion](parseInt(vccversionItem))
  137. except ValueError:
  138. continue
  139. vcvarsallArg = discoverVccVcVarsAllPath(vccversionValue)
  140. if vcvarsallArg.len > 0:
  141. break
  142. # VCC version not specified, discover latest (call discover without args)
  143. if vcvarsallArg.len < 1 and vccversionArg.len < 1:
  144. vccversionValue = vccUndefined
  145. vcvarsallArg = discoverVccVcVarsAllPath()
  146. if printPathArg:
  147. var head = $vccversionValue
  148. if head.len < 1:
  149. head = "latest"
  150. echo "$1: $2" % [head, vcvarsallArg]
  151. # Call vcvarsall to get the appropiate VCC process environment
  152. var vcvars = vccVarsAll(vcvarsallArg, platformArg, sdkTypeArg, sdkVersionArg, verboseArg)
  153. if vcvars != nil:
  154. for vccEnvKey, vccEnvVal in vcvars:
  155. putEnv(vccEnvKey, vccEnvVal)
  156. var vccOptions = {poParentStreams}
  157. if verboseArg:
  158. vccOptions.incl poEchoCmd
  159. # Default to the cl.exe command if no secondary command was specified
  160. if commandArg.len < 1:
  161. commandArg = "cl.exe"
  162. if not noCommandArg:
  163. # Run VCC command with the VCC process environment
  164. let vccProcess = startProcess(
  165. commandArg,
  166. args = clArgs,
  167. options = vccOptions
  168. )
  169. quit vccProcess.waitForExit()