123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226 |
- 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"
- verbosePrefix = "--verbose"
- vccversionSepIdx = vccversionPrefix.len
- vcvarsallSepIdx = vcvarsallPrefix.len
- commandSepIdx = commandPrefix.len
- platformSepIdx = platformPrefix.len
- sdktypeSepIdx = sdktypePrefix.len
- sdkversionSepIdx = sdkversionPrefix.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.
- 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, 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, 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(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 verboseArg: bool = false
- var clArgs: seq[string] = @[]
- let wrapperArgs = commandLineParams()
- parseVccexeCmdLine(wrapperArgs, vccversionArg, printPathArg, vcvarsallArg,
- commandArg, noCommandArg, platformArg, sdkTypeArg, sdkVersionArg,
- 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, verboseArg)
- if vcvars != nil:
- for vccEnvKey, vccEnvVal in vcvars:
- putEnv(vccEnvKey, vccEnvVal)
- var vccOptions = {poParentStreams}
- if verboseArg:
- vccOptions.incl poEchoCmd
- # 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
|