vccexe.nim 9.6 KB

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