1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
|
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[TaintedString],
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[TaintedString]) =
## 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[TaintedString] = @[]
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
|