summary refs log tree commit diff stats
path: root/tools/vccexe/vccexe.nim
diff options
context:
space:
mode:
Diffstat (limited to 'tools/vccexe/vccexe.nim')
-rw-r--r--tools/vccexe/vccexe.nim237
1 files changed, 237 insertions, 0 deletions
diff --git a/tools/vccexe/vccexe.nim b/tools/vccexe/vccexe.nim
new file mode 100644
index 000000000..2a43f7422
--- /dev/null
+++ b/tools/vccexe/vccexe.nim
@@ -0,0 +1,237 @@
+import strutils, strtabs, os, osproc, vcvarsall, vccenv, vccvswhere
+
+type
+  VccVersion* = enum ## VCC compiler backend versions
+    vccUndefined = 0,   ## VCC version undefined, resolves to the latest recognizable VCC version
+    vcc90  = vs90  ## Visual Studio 2008 (Version 9.0)
+    vcc100 = vs100 ## Visual Studio 2010 (Version 10.0)
+    vcc110 = vs110 ## Visual Studio 2012 (Version 11.0)
+    vcc120 = vs120 ## Visual Studio 2013 (Version 12.0)
+    vcc140 = vs140 ## Visual Studio 2015 (Version 14.0)
+
+proc discoverVccVcVarsAllPath*(version: VccVersion = vccUndefined): string =
+  ## Returns the path to the vcvarsall utility of the specified VCC compiler backend.
+  ##
+  ## version
+  ##   The specific version of the VCC compiler backend to discover.
+  ##   Defaults to the latest recognized VCC compiler backend that is found on the system.
+  ##
+  ## Returns `nil` if the VCC compiler backend discovery failed.
+
+  # Attempt discovery using vswhere utility (VS 2017 and later) if no version specified.
+  if version == vccUndefined:
+    result = vccVswhereVcVarsAllPath()
+    if result.len > 0:
+      return
+
+  # Attempt discovery through VccEnv
+  # (Trying Visual Studio Common Tools Environment Variables)
+  result = vccEnvVcVarsAllPath(cast[VccEnvVersion](version))
+  if result.len > 0:
+    return
+
+  # All attempts to discover vcc failed
+
+const
+  vccversionPrefix = "--vccversion"
+  printPathPrefix = "--printPath"
+  vcvarsallPrefix = "--vcvarsall"
+  commandPrefix = "--command"
+  noCommandPrefix = "--noCommand"
+  platformPrefix = "--platform"
+  sdktypePrefix = "--sdktype"
+  sdkversionPrefix = "--sdkversion"
+  vctoolsetPrefix = "--vctoolset"
+  verbosePrefix = "--verbose"
+
+  vccversionSepIdx = vccversionPrefix.len
+  vcvarsallSepIdx = vcvarsallPrefix.len
+  commandSepIdx = commandPrefix.len
+  platformSepIdx = platformPrefix.len
+  sdktypeSepIdx = sdktypePrefix.len
+  sdkversionSepIdx = sdkversionPrefix.len
+  vctoolsetSepIdx = vctoolsetPrefix.len
+
+  vcvarsallDefaultPath = "vcvarsall.bat"
+
+  helpText = """
++-----------------------------------------------------------------+
+|         Microsoft C/C++ compiler wrapper for Nim                |
+|                                &                                |
+|        Microsoft C/C++ Compiler Discovery Utility               |
+|            (c) 2017 Fredrik Hoeisaether Rasch                   |
++-----------------------------------------------------------------+
+
+Usage:
+  vccexe [options] [compileroptions]
+Options:
+  --vccversion:<v>    Optionally specify the VCC version to discover
+                      <v>: 0, 90, 100, 110, 120, 140
+                      If <v> is omitted, attempts to discover the latest
+                      installed version. <v>: 0, 90, 100, 110, 120, 140
+                      A value of 0 will discover the latest installed SDK
+                      Multiple values can be specified, separated by ,
+  --printPath         Print the discovered path of the vcvarsall utility
+                      of the VCC version specified with the --vccversion argument.
+                      For each specified version the utility prints a line with the
+                      following format: <version>: <path>
+  --noCommand         Flag to suppress VCC secondary command execution
+                      Useful in conjunction with --vccversion and --printPath to
+                      only perform VCC discovery, but without executing VCC tools
+  --vcvarsall:<path>  Path to the Developer Command Prompt utility vcvarsall.bat that selects
+                      the appropriate development settings.
+                      Usual path for Visual Studio 2015 and below:
+                        %VSInstallDir%\VC\vcvarsall
+                      Usual path for Visual Studio 2017 and above:
+                        %VSInstallDir%\VC\Auxiliary\Build\vcvarsall
+  --command:<exec>    Specify the command to run once the development environment is loaded.
+                      <exec> can be any command-line argument. Any arguments not recognized by vccexe
+                      are passed on as arguments to this command.
+                      cl.exe is invoked by default if this argument is omitted.
+  --platform:<arch>   Specify the Compiler Platform Tools architecture
+                      <arch>: x86 | amd64 | arm | x86_amd64 | x86_arm | amd64_x86 | amd64_arm
+                      Values with two architectures (like x86_amd64) specify the architecture
+                      of the cross-platform compiler (e.g. x86) and the target it compiles to (e.g. amd64).
+  --sdktype:<type>    Specify the SDK flavor to use. Defaults to the Desktop SDK.
+                      <type>: {empty} | store | uwp | onecore
+  --sdkversion:<v>    Use a specific Windows SDK version:
+                      <v> is either the full Windows 10 SDK version number or
+                      "8.1" to use the windows 8.1 SDK
+  --verbose           Echoes the command line for loading the Developer Command Prompt
+                      and the command line passed on to the secondary command.
+  --vctoolset         Optionally specifies the Visual Studio compiler toolset to use. 
+                      By default, the environment is set to use the current Visual Studio compiler toolset.
+
+Other command line arguments are passed on to the
+secondary command specified by --command or to the
+Microsoft (R) C/C++ Optimizing Compiler if no secondary
+command was specified
+"""
+
+proc parseVccexeCmdLine(argseq: seq[string],
+    vccversionArg: var seq[string], printPathArg: var bool,
+    vcvarsallArg: var string, commandArg: var string, noCommandArg: var bool,
+    platformArg: var VccArch, sdkTypeArg: var VccPlatformType,
+    sdkVersionArg: var string,  vctoolsetArg: var string, verboseArg: var bool,
+    clArgs: var seq[string]) =
+  ## Cannot use usual command-line argument parser here
+  ## Since vccexe command-line arguments are intermingled
+  ## with the secondary command-line arguments which have
+  ## a syntax that is not supported by the default nim
+  ## argument parser.
+  for wargv in argseq:
+    # Check whether the current argument contains -- prefix
+    if wargv.startsWith("@"): # Check for response file prefix
+      let
+        responsefilename = wargv.substr(1)
+        responsefilehandle = open(responsefilename)
+        responsecontent = responsefilehandle.readAll()
+        responseargs = parseCmdLine(responsecontent)
+      parseVccexeCmdLine(responseargs, vccversionArg, printPathArg,
+        vcvarsallArg, commandArg, noCommandArg, platformArg, sdkTypeArg,
+        sdkVersionArg, vctoolsetArg, verboseArg, clArgs)
+    elif wargv.startsWith(vccversionPrefix): # Check for vccversion
+      vccversionArg.add(wargv.substr(vccversionSepIdx + 1))
+    elif wargv.cmpIgnoreCase(printPathPrefix) == 0: # Check for printPath
+      printPathArg = true
+    elif wargv.startsWith(vcvarsallPrefix): # Check for vcvarsall
+      vcvarsallArg = wargv.substr(vcvarsallSepIdx + 1)
+    elif wargv.startsWith(commandPrefix): # Check for command
+      commandArg = wargv.substr(commandSepIdx + 1)
+    elif wargv.cmpIgnoreCase(noCommandPrefix) == 0: # Check for noCommand
+      noCommandArg = true
+    elif wargv.startsWith(platformPrefix): # Check for platform
+      platformArg = parseEnum[VccArch](wargv.substr(platformSepIdx + 1))
+    elif wargv.startsWith(sdktypePrefix): # Check for sdktype
+      sdkTypeArg = parseEnum[VccPlatformType](wargv.substr(sdktypeSepIdx + 1))
+    elif wargv.startsWith(sdkversionPrefix): # Check for sdkversion
+      sdkVersionArg = wargv.substr(sdkversionSepIdx + 1)
+    elif wargv.startsWith(vctoolsetPrefix): # Check for vctoolset
+      vctoolsetArg = wargv.substr(vctoolsetSepIdx + 1)
+    elif wargv.startsWith(verbosePrefix):
+      verboseArg = true
+    else: # Regular cl.exe argument -> store for final cl.exe invocation
+      if (wargv.len == 2) and (wargv[1] == '?'):
+        echo helpText
+      clArgs.add(wargv)
+
+when isMainModule:
+  var vccversionArg: seq[string] = @[]
+  var printPathArg: bool = false
+  var vcvarsallArg: string
+  var commandArg: string
+  var noCommandArg: bool = false
+  var platformArg: VccArch
+  var sdkTypeArg: VccPlatformType
+  var sdkVersionArg: string
+  var vctoolsetArg: string
+  var verboseArg: bool = false
+
+  var clArgs: seq[string] = @[]
+
+  let wrapperArgs = commandLineParams()
+  parseVccexeCmdLine(wrapperArgs, vccversionArg, printPathArg, vcvarsallArg,
+    commandArg, noCommandArg, platformArg, sdkTypeArg, sdkVersionArg, vctoolsetArg,
+    verboseArg,
+    clArgs)
+
+  # Support for multiple specified versions. Attempt VCC discovery for each version
+  # specified, first successful discovery wins
+  var vccversionValue: VccVersion = vccUndefined
+  for vccversionItem in vccversionArg:
+    try:
+      vccversionValue = cast[VccVersion](parseInt(vccversionItem))
+    except ValueError:
+      continue
+    vcvarsallArg = discoverVccVcVarsAllPath(vccversionValue)
+    if vcvarsallArg.len > 0:
+      break
+  # VCC version not specified, discover latest (call discover without args)
+  if vcvarsallArg.len < 1 and vccversionArg.len < 1:
+    vccversionValue = vccUndefined
+    vcvarsallArg = discoverVccVcVarsAllPath()
+
+  if vcvarsallArg == "":
+    # Assume that default executable is in current directory or in PATH
+    vcvarsallArg = findExe(vcvarsallDefaultPath)
+
+  if printPathArg:
+    var head = $vccversionValue
+    if head.len < 1:
+      head = "latest"
+    echo "$1: $2" % [head, vcvarsallArg]
+
+  # Call vcvarsall to get the appropriate VCC process environment
+  var vcvars = vccVarsAll(vcvarsallArg, platformArg, sdkTypeArg, sdkVersionArg, vctoolsetArg, verboseArg)
+  if vcvars != nil:
+    for vccEnvKey, vccEnvVal in vcvars:
+      putEnv(vccEnvKey, vccEnvVal)
+
+  var vccOptions = {poParentStreams}
+  if verboseArg:
+    vccOptions.incl poEchoCmd
+
+  let currentDir = getCurrentDir()
+  for arg in clArgs.mitems:
+    if fileExists(arg):
+      arg = relativePath(arg, currentDir)
+
+  # Default to the cl.exe command if no secondary command was specified
+  if commandArg.len < 1:
+    commandArg = "cl.exe"
+
+  if not noCommandArg:
+    # Run VCC command with the VCC process environment
+    try:
+      let vccProcess = startProcess(
+          commandArg,
+          args = clArgs,
+          options = vccOptions
+        )
+      quit vccProcess.waitForExit()
+    except:
+      if vcvarsallArg.len != 0:
+        echo "Hint: using " & vcvarsallArg
+      else:
+        echo "Hint: vcvarsall.bat was not found"
+      raise