vccexe.nim 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238
  1. import strutils, strtabs, os, osproc, vcvarsall, vccenv, vccvswhere
  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. # Attempt discovery using vswhere utility (VS 2017 and later) if no version specified.
  19. if version == vccUndefined:
  20. result = vccVswhereVcVarsAllPath()
  21. if result.len > 0:
  22. return
  23. # Attempt discovery through VccEnv
  24. # (Trying Visual Studio Common Tools Environment Variables)
  25. result = vccEnvVcVarsAllPath(cast[VccEnvVersion](version))
  26. if result.len > 0:
  27. return
  28. # All attempts to discover vcc failed
  29. const
  30. vccversionPrefix = "--vccversion"
  31. printPathPrefix = "--printPath"
  32. vcvarsallPrefix = "--vcvarsall"
  33. commandPrefix = "--command"
  34. noCommandPrefix = "--noCommand"
  35. platformPrefix = "--platform"
  36. sdktypePrefix = "--sdktype"
  37. sdkversionPrefix = "--sdkversion"
  38. vctoolsetPrefix = "--vctoolset"
  39. verbosePrefix = "--verbose"
  40. vccversionSepIdx = vccversionPrefix.len
  41. vcvarsallSepIdx = vcvarsallPrefix.len
  42. commandSepIdx = commandPrefix.len
  43. platformSepIdx = platformPrefix.len
  44. sdktypeSepIdx = sdktypePrefix.len
  45. sdkversionSepIdx = sdkversionPrefix.len
  46. vctoolsetSepIdx = vctoolsetPrefix.len
  47. vcvarsallDefaultPath = "vcvarsall.bat"
  48. helpText = """
  49. +-----------------------------------------------------------------+
  50. | Microsoft C/C++ compiler wrapper for Nim |
  51. | & |
  52. | Microsoft C/C++ Compiler Discovery Utility |
  53. | (c) 2017 Fredrik Hoeisaether Rasch |
  54. +-----------------------------------------------------------------+
  55. Usage:
  56. vccexe [options] [compileroptions]
  57. Options:
  58. --vccversion:<v> Optionally specify the VCC version to discover
  59. <v>: 0, 90, 100, 110, 120, 140
  60. If <v> is omitted, attempts to discover the latest
  61. installed version. <v>: 0, 90, 100, 110, 120, 140
  62. A value of 0 will discover the latest installed SDK
  63. Multiple values can be specified, separated by ,
  64. --printPath Print the discovered path of the vcvarsall utility
  65. of the VCC version specified with the --vccversion argument.
  66. For each specified version the utility prints a line with the
  67. following format: <version>: <path>
  68. --noCommand Flag to suppress VCC secondary command execution
  69. Useful in conjunction with --vccversion and --printPath to
  70. only perform VCC discovery, but without executing VCC tools
  71. --vcvarsall:<path> Path to the Developer Command Prompt utility vcvarsall.bat that selects
  72. the appropriate development settings.
  73. Usual path for Visual Studio 2015 and below:
  74. %VSInstallDir%\VC\vcvarsall
  75. Usual path for Visual Studio 2017 and above:
  76. %VSInstallDir%\VC\Auxiliary\Build\vcvarsall
  77. --command:<exec> Specify the command to run once the development environment is loaded.
  78. <exec> can be any command-line argument. Any arguments not recognized by vccexe
  79. are passed on as arguments to this command.
  80. cl.exe is invoked by default if this argument is omitted.
  81. --platform:<arch> Specify the Compiler Platform Tools architecture
  82. <arch>: x86 | amd64 | arm | x86_amd64 | x86_arm | amd64_x86 | amd64_arm
  83. Values with two architectures (like x86_amd64) specify the architecture
  84. of the cross-platform compiler (e.g. x86) and the target it compiles to (e.g. amd64).
  85. --sdktype:<type> Specify the SDK flavor to use. Defaults to the Desktop SDK.
  86. <type>: {empty} | store | uwp | onecore
  87. --sdkversion:<v> Use a specific Windows SDK version:
  88. <v> is either the full Windows 10 SDK version number or
  89. "8.1" to use the windows 8.1 SDK
  90. --verbose Echoes the command line for loading the Developer Command Prompt
  91. and the command line passed on to the secondary command.
  92. --vctoolset Optionally specifies the Visual Studio compiler toolset to use.
  93. By default, the environment is set to use the current Visual Studio compiler toolset.
  94. Other command line arguments are passed on to the
  95. secondary command specified by --command or to the
  96. Microsoft (R) C/C++ Optimizing Compiler if no secondary
  97. command was specified
  98. """
  99. proc parseVccexeCmdLine(argseq: seq[string],
  100. vccversionArg: var seq[string], printPathArg: var bool,
  101. vcvarsallArg: var string, commandArg: var string, noCommandArg: var bool,
  102. platformArg: var VccArch, sdkTypeArg: var VccPlatformType,
  103. sdkVersionArg: var string, vctoolsetArg: var string, verboseArg: var bool,
  104. clArgs: var seq[string]) =
  105. ## Cannot use usual command-line argument parser here
  106. ## Since vccexe command-line arguments are intermingled
  107. ## with the secondary command-line arguments which have
  108. ## a syntax that is not supported by the default nim
  109. ## argument parser.
  110. for wargv in argseq:
  111. # Check whether the current argument contains -- prefix
  112. if wargv.startsWith("@"): # Check for response file prefix
  113. let
  114. responsefilename = wargv.substr(1)
  115. responsefilehandle = open(responsefilename)
  116. responsecontent = responsefilehandle.readAll()
  117. responseargs = parseCmdLine(responsecontent)
  118. parseVccexeCmdLine(responseargs, vccversionArg, printPathArg,
  119. vcvarsallArg, commandArg, noCommandArg, platformArg, sdkTypeArg,
  120. sdkVersionArg, vctoolsetArg, verboseArg, clArgs)
  121. elif wargv.startsWith(vccversionPrefix): # Check for vccversion
  122. vccversionArg.add(wargv.substr(vccversionSepIdx + 1))
  123. elif wargv.cmpIgnoreCase(printPathPrefix) == 0: # Check for printPath
  124. printPathArg = true
  125. elif wargv.startsWith(vcvarsallPrefix): # Check for vcvarsall
  126. vcvarsallArg = wargv.substr(vcvarsallSepIdx + 1)
  127. elif wargv.startsWith(commandPrefix): # Check for command
  128. commandArg = wargv.substr(commandSepIdx + 1)
  129. elif wargv.cmpIgnoreCase(noCommandPrefix) == 0: # Check for noCommand
  130. noCommandArg = true
  131. elif wargv.startsWith(platformPrefix): # Check for platform
  132. platformArg = parseEnum[VccArch](wargv.substr(platformSepIdx + 1))
  133. elif wargv.startsWith(sdktypePrefix): # Check for sdktype
  134. sdkTypeArg = parseEnum[VccPlatformType](wargv.substr(sdktypeSepIdx + 1))
  135. elif wargv.startsWith(sdkversionPrefix): # Check for sdkversion
  136. sdkVersionArg = wargv.substr(sdkversionSepIdx + 1)
  137. elif wargv.startsWith(vctoolsetPrefix): # Check for vctoolset
  138. vctoolsetArg = wargv.substr(vctoolsetSepIdx + 1)
  139. elif wargv.startsWith(verbosePrefix):
  140. verboseArg = true
  141. else: # Regular cl.exe argument -> store for final cl.exe invocation
  142. if (wargv.len == 2) and (wargv[1] == '?'):
  143. echo helpText
  144. clArgs.add(wargv)
  145. when isMainModule:
  146. var vccversionArg: seq[string] = @[]
  147. var printPathArg: bool = false
  148. var vcvarsallArg: string
  149. var commandArg: string
  150. var noCommandArg: bool = false
  151. var platformArg: VccArch
  152. var sdkTypeArg: VccPlatformType
  153. var sdkVersionArg: string
  154. var vctoolsetArg: string
  155. var verboseArg: bool = false
  156. var clArgs: seq[string] = @[]
  157. let wrapperArgs = commandLineParams()
  158. parseVccexeCmdLine(wrapperArgs, vccversionArg, printPathArg, vcvarsallArg,
  159. commandArg, noCommandArg, platformArg, sdkTypeArg, sdkVersionArg, vctoolsetArg,
  160. verboseArg,
  161. clArgs)
  162. # Support for multiple specified versions. Attempt VCC discovery for each version
  163. # specified, first successful discovery wins
  164. var vccversionValue: VccVersion = vccUndefined
  165. for vccversionItem in vccversionArg:
  166. try:
  167. vccversionValue = cast[VccVersion](parseInt(vccversionItem))
  168. except ValueError:
  169. continue
  170. vcvarsallArg = discoverVccVcVarsAllPath(vccversionValue)
  171. if vcvarsallArg.len > 0:
  172. break
  173. # VCC version not specified, discover latest (call discover without args)
  174. if vcvarsallArg.len < 1 and vccversionArg.len < 1:
  175. vccversionValue = vccUndefined
  176. vcvarsallArg = discoverVccVcVarsAllPath()
  177. if vcvarsallArg == "":
  178. # Assume that default executable is in current directory or in PATH
  179. vcvarsallArg = findExe(vcvarsallDefaultPath)
  180. if printPathArg:
  181. var head = $vccversionValue
  182. if head.len < 1:
  183. head = "latest"
  184. echo "$1: $2" % [head, vcvarsallArg]
  185. # Call vcvarsall to get the appropriate VCC process environment
  186. var vcvars = vccVarsAll(vcvarsallArg, platformArg, sdkTypeArg, sdkVersionArg, vctoolsetArg, verboseArg)
  187. if vcvars != nil:
  188. for vccEnvKey, vccEnvVal in vcvars:
  189. putEnv(vccEnvKey, vccEnvVal)
  190. var vccOptions = {poParentStreams}
  191. if verboseArg:
  192. vccOptions.incl poEchoCmd
  193. let currentDir = getCurrentDir()
  194. for arg in clArgs.mitems:
  195. if fileExists(arg):
  196. arg = relativePath(arg, currentDir)
  197. # Default to the cl.exe command if no secondary command was specified
  198. if commandArg.len < 1:
  199. commandArg = "cl.exe"
  200. if not noCommandArg:
  201. # Run VCC command with the VCC process environment
  202. try:
  203. let vccProcess = startProcess(
  204. commandArg,
  205. args = clArgs,
  206. options = vccOptions
  207. )
  208. quit vccProcess.waitForExit()
  209. except:
  210. if vcvarsallArg.len != 0:
  211. echo "Hint: using " & vcvarsallArg
  212. else:
  213. echo "Hint: vcvarsall.bat was not found"
  214. raise