vccexe.nim 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206
  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 dicover 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 supress VCC secondary command execution
  67. Useful in conjuction with --vccversion and --printPath to
  68. only perfom VCC discovery, but without executing VCC tools
  69. --vcvarsall:<path> Path to the Developer Command Prompt utility vcvarsall.bat that selects
  70. the appropiate devlopment 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. when isMainModule:
  96. var vccversionArg: seq[string] = @[]
  97. var printPathArg: bool = false
  98. var vcvarsallArg: string
  99. var commandArg: string
  100. var noCommandArg: bool = false
  101. var platformArg: VccArch
  102. var sdkTypeArg: VccPlatformType
  103. var sdkVersionArg: string
  104. var verboseArg: bool = false
  105. var clArgs: seq[TaintedString] = @[]
  106. # Cannot use usual command-line argument parser here
  107. # Since vccexe command-line arguments are intermingled
  108. # with the secondary command-line arguments which have
  109. # a syntax that is not supported by the default nim
  110. # argument parser.
  111. var wrapperArgs = commandLineParams()
  112. for wargv in wrapperArgs:
  113. # Check whether the current argument contains -- prefix
  114. if wargv.startsWith(vccversionPrefix): # Check for vccversion
  115. vccversionArg.add(wargv.substr(vccversionSepIdx + 1))
  116. elif wargv.cmpIgnoreCase(printPathPrefix) == 0: # Check for printPath
  117. printPathArg = true
  118. elif wargv.startsWith(vcvarsallPrefix): # Check for vcvarsall
  119. vcvarsallArg = wargv.substr(vcvarsallSepIdx + 1)
  120. elif wargv.startsWith(commandPrefix): # Check for command
  121. commandArg = wargv.substr(commandSepIdx + 1)
  122. elif wargv.cmpIgnoreCase(noCommandPrefix) == 0: # Check for noCommand
  123. noCommandArg = true
  124. elif wargv.startsWith(platformPrefix): # Check for platform
  125. platformArg = parseEnum[VccArch](wargv.substr(platformSepIdx + 1))
  126. elif wargv.startsWith(sdktypePrefix): # Check for sdktype
  127. sdkTypeArg = parseEnum[VccPlatformType](wargv.substr(sdktypeSepIdx + 1))
  128. elif wargv.startsWith(sdkversionPrefix): # Check for sdkversion
  129. sdkVersionArg = wargv.substr(sdkversionSepIdx + 1)
  130. elif wargv.startsWith(verbosePrefix):
  131. verboseArg = true
  132. else: # Regular cl.exe argument -> store for final cl.exe invocation
  133. if (wargv.len == 2) and (wargv[1] == '?'):
  134. echo helpText
  135. clArgs.add(wargv)
  136. # Support for multiple specified versions. Attempt VCC discovery for each version
  137. # specified, first successful discovery wins
  138. var vccversionValue: VccVersion = vccUndefined
  139. for vccversionItem in vccversionArg:
  140. try:
  141. vccversionValue = cast[VccVersion](parseInt(vccversionItem))
  142. except ValueError:
  143. continue
  144. vcvarsallArg = discoverVccVcVarsAllPath(vccversionValue)
  145. if vcvarsallArg.len > 0:
  146. break
  147. # VCC version not specified, discover latest (call discover without args)
  148. if vcvarsallArg.len < 1 and vccversionArg.len < 1:
  149. vccversionValue = vccUndefined
  150. vcvarsallArg = discoverVccVcVarsAllPath()
  151. if vcvarsallArg == "":
  152. # Assume that default executable is in current directory or in PATH
  153. vcvarsallArg = findExe(vcvarsallDefaultPath)
  154. if printPathArg:
  155. var head = $vccversionValue
  156. if head.len < 1:
  157. head = "latest"
  158. echo "$1: $2" % [head, vcvarsallArg]
  159. # Call vcvarsall to get the appropiate VCC process environment
  160. var vcvars = vccVarsAll(vcvarsallArg, platformArg, sdkTypeArg, sdkVersionArg, verboseArg)
  161. if vcvars != nil:
  162. for vccEnvKey, vccEnvVal in vcvars:
  163. putEnv(vccEnvKey, vccEnvVal)
  164. var vccOptions = {poParentStreams}
  165. if verboseArg:
  166. vccOptions.incl poEchoCmd
  167. # Default to the cl.exe command if no secondary command was specified
  168. if commandArg.len < 1:
  169. commandArg = "cl.exe"
  170. if not noCommandArg:
  171. # Run VCC command with the VCC process environment
  172. try:
  173. let vccProcess = startProcess(
  174. commandArg,
  175. args = clArgs,
  176. options = vccOptions
  177. )
  178. quit vccProcess.waitForExit()
  179. except:
  180. if vcvarsallArg.len != 0:
  181. echo "Hint: using " & vcvarsallArg
  182. else:
  183. echo "Hint: vcvarsall.bat was not found"
  184. raise