-
Notifications
You must be signed in to change notification settings - Fork 32
/
Copy pathmsvs-detect
1237 lines (1154 loc) · 44.4 KB
/
msvs-detect
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
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
#!/usr/bin/env bash
# ################################################################################################ #
# MetaStack Solutions Ltd. #
# ################################################################################################ #
# Microsoft C Compiler Environment Detection Script #
# ################################################################################################ #
# Copyright (c) 2016, 2017, 2018, 2019, 2020, 2021 MetaStack Solutions Ltd. #
# Copyright (c) 2022, 2024 David Allsopp Ltd. #
# ################################################################################################ #
# Author: David Allsopp #
# 16-Feb-2016 #
# ################################################################################################ #
# Redistribution and use in source and binary forms, with or without modification, are permitted #
# provided that the following two conditions are met: #
# 1. Redistributions of source code must retain the above copyright notice, this list of #
# conditions and the following disclaimer. #
# 2. Neither the name of MetaStack Solutions Ltd. nor the names of its contributors may be #
# used to endorse or promote products derived from this software without specific prior #
# written permission. #
# #
# This software is provided by the Copyright Holder 'as is' and any express or implied warranties #
# including, but not limited to, the implied warranties of merchantability and fitness for a #
# particular purpose are disclaimed. In no event shall the Copyright Holder be liable for any #
# direct, indirect, incidental, special, exemplary, or consequential damages (including, but not #
# limited to, procurement of substitute goods or services; loss of use, data, or profits; or #
# business interruption) however caused and on any theory of liability, whether in contract, #
# strict liability, or tort (including negligence or otherwise) arising in any way out of the use #
# of this software, even if advised of the possibility of such damage. #
# ################################################################################################ #
VERSION=0.7.0
set -f
# debug [level=2] message
debug ()
{
if [[ -z ${2+x} ]] ; then
DEBUG_LEVEL=2
else
DEBUG_LEVEL=$1
shift
fi
if [[ $DEBUG -ge $DEBUG_LEVEL ]] ; then
echo "$1">&2
fi
}
# warning message
warning ()
{
if [[ $DEBUG -gt 0 ]] ; then
echo "Warning: $1">&2
fi
}
# reg_string key value
# Retrieves a REG_SZ value from the registry (redirected on WOW64)
reg_string ()
{
reg query "$1" /v "$2" 2>/dev/null | tr -d '\r' | sed -ne "s/ *$2 *REG_SZ *//p"
}
# reg64_string key value
# As reg_string, but without WOW64 redirection (i.e. guaranteed access to 64-bit registry)
reg64_string ()
{
$REG64 query "$1" /v "$2" 2>/dev/null | tr -d '\r' | sed -ne "s/ *$2 *REG_SZ *//p"
}
# find_in list file
# Increments $RET if file does not exist in any of the directories in the *-separated list
find_in ()
{
debug 4 "Looking for $2 in $1"
if [[ -z $1 ]] ; then
STATUS=1
else
IFS='*'
STATUS=1
for f in $1; do
if [[ -e "$f/$2" ]] ; then
STATUS=0
break
fi
done
unset IFS
fi
if [[ $STATUS -eq 1 ]] ; then
debug 4 "$2 not found"
fi
((RET+=STATUS))
}
# check_environment PATH INC LIB name arch
# By checking for the presence of various files, verifies that PATH, INC and LIB provide a complete
# compiler and indicates this in its return status. RET is assumed to be zero on entry. $ASSEMBLER
# will contain the name of assembler for this compiler series (ml.exe or ml64.exe).
# The following files are checked:
# cl.exe PATH Microsoft C compiler
# kernel32.lib LIB Implies Windows SDK present
# link.exe PATH Microsoft Linker
# ml[64].exe PATH Microsoft Assembler (ml.exe or ml64.exe)
# msvcrt.lib LIB Implies C Runtime Libraries present
# mt.exe PATH Microsoft Manifest Tool
# oldnames.lib LIB Implies C Runtime Libraries present
# rc.exe PATH Microsoft Resource Compiler (implies tools present)
# stdlib.h INC Implies Microsoft C Runtime Libraries present
# windows.h INC Implies Windows SDK present
# oldnames.lib is included, because certain SDKs and older versions don't correctly install the
# entire runtime if only some options (e.g. Dynamic Runtime and not Static) are selected.
check_environment ()
{
debug 4 "Checking $4 ($5)"
for tool in cl rc link ; do
find_in "$1" $tool.exe
done
if [[ $RET -gt 0 ]] ; then
warning "Microsoft C Compiler tools not all found - $4 ($5) excluded"
return 1
fi
RET=0
find_in "$2" windows.h
find_in "$3" kernel32.lib
if [[ $RET -gt 0 ]] ; then
warning "Windows SDK not all found - $4 ($5) excluded"
return 1
fi
RET=0
find_in "$2" stdlib.h
find_in "$3" msvcrt.lib
find_in "$3" oldnames.lib
if [[ $RET -gt 0 ]] ; then
warning "Microsoft C runtime library not all found - $4 ($5) excluded"
return 1
fi
ASSEMBLER=ml${5#x}
ASSEMBLER=${ASSEMBLER%86}.exe
if [[ $ML_REQUIRED -eq 1 ]] ; then
RET=0
find_in "$1" "$ASSEMBLER"
if [[ $RET -gt 0 ]] ; then
warning "Microsoft Assembler ($ASSEMBLER) not found - $4 ($5)"
return 1
fi
fi
if [[ $MT_REQUIRED -eq 1 ]] ; then
RET=0
find_in "$1" mt.exe
if [[ $RET -gt 0 ]] ; then
warning "Microsoft Manifest Tool not found - $4 ($5)"
return 1
fi
fi
return 0
}
# output VAR value arch
# Outputs a command for setting VAR to value based on $OUTPUT. If $ENV_ARCH is arch, then an empty
# value (i.e. no change) is output.
output ()
{
if [[ $3 = "$ENV_ARCH" ]] ; then
VALUE=
else
VALUE=$2
fi
case "$OUTPUT" in
0)
echo "$1='${VALUE//\'/\'\"\'\"\'}'";;
1)
VALUE=${VALUE//#/\\\#}
echo "$1=${VALUE//\$/\$\$}";;
2)
case $1 in
MSVS_PATH)
prefix='bin';;
MSVS_INC)
prefix='inc';;
MSVS_LIB)
prefix='lib';;
MSVS_ML)
prefix='asm';;
*)
echo Internal fault "$1">&2
exit 3;;
esac
while IFS= read -r entry; do
if [[ -n $entry ]] ; then
echo "$prefix*$entry"
fi
done <<< "${VALUE//;/$'\n'}"
;;
esac
}
# DEBUG Debugging level
# MODE Operation mode
# 0 - Normal
# 1 - --all
# 2 - --help
# 3 - --version
# 4 - --installed
# OUTPUT --output option
# 0 - =shell
# 1 - =make
# 2 - =data
# MT_REQUIRED --with-mt
# ML_REQUIRED --with-assembler
# TARGET_ARCH Normalised --arch (x86, x64 or blank for both)
# LEFT_ARCH \ If $TARGET_ARCH is blank, these will be x86 and x64 respectively, otherwise they
# RIGHT_ARCH / equal $TARGET_ARCH
# SCAN_ENV Controls from parsing whether the environment should be queried for a compiler
DEBUG=0
MODE=0
OUTPUT=0
MT_REQUIRED=0
ML_REQUIRED=0
TARGET_ARCH=
SCAN_ENV=0
# Various PATH messing around means it's sensible to know where tools are now
WHICH=$(which which)
# Parse command-line. At the moment, the short option which usefully combines with anything is -d,
# so for the time being, combining short options is not permitted, as the loop becomes even less
# clear with getopts. GNU getopt isn't installed by default on Cygwin...
if [[ $# -gt 0 ]] ; then
while true ; do
case "$1" in
# Mode settings ($MODE)
-a|--all)
MODE=1
shift 1;;
-i|--installed)
MODE=2
shift;;
-h|--help)
MODE=3
shift;;
-v|--version)
MODE=4
shift;;
# Simple flags ($MT_REQUIRED and $ML_REQUIRED)
--with-mt)
MT_REQUIRED=1
shift;;
--with-assembler)
ML_REQUIRED=1
shift;;
# -o, --output ($OUTPUT)
-o|--output)
case "$2" in
shell)
;;
make)
OUTPUT=1;;
data)
OUTPUT=2;;
*)
echo "$0: unrecognised option for $1: '$2'">&2
exit 2;;
esac
shift 2;;
-oshell|--output=shell)
shift;;
-omake|--output=make)
OUTPUT=1
shift;;
-odata|--output=data)
OUTPUT=2
shift;;
-o*)
echo "$0: unrecognised option for -o: '${1#-o}'">&2
exit 2;;
--output=*)
echo "$0: unrecognised option for --output: '${1#--output=}'">&2
exit 2;;
# -x, --arch ($TARGET_ARCH)
-x|--arch)
case "$2" in
86|x86|x86_32)
TARGET_ARCH=x86;;
64|x64|x86_64)
TARGET_ARCH=x64;;
*)
echo "$0: unrecognised option for $1: '$2'">&2
exit 2
esac
shift 2;;
-x86|-xx86|-xx86_32|--arch=x86|--arch=86|--arch=x86_32)
TARGET_ARCH=x86
shift;;
-x64|-xx64|-xx86_64|--arch=x64|--arch=64|--arch=x86_64)
TARGET_ARCH=x64
shift;;
-x*)
echo "$0: unrecognised option for -x: '${1#-x}'">&2
exit 2;;
--arch=*)
echo "$0: unrecognised option for --arch: '${1#--arch}'">&2
exit 2;;
# -d, --debug ($DEBUG)
-d*)
DEBUG=${1#-d}
if [[ -z $DEBUG ]] ; then
DEBUG=1
fi
shift;;
--debug=*)
DEBUG=${1#*=}
shift;;
--debug)
DEBUG=1
shift;;
# End of option marker
--)
shift
break;;
# Invalid options
--*)
echo "$0: unrecognised option: '${1%%=*}'">&2
exit 2;;
-*)
echo "$0: unrecognised option: '${1:1:1}'">&2
exit 2;;
# MSVS_PREFERENCE (without end-of-option marker)
*)
break;;
esac
done
if [[ -n ${1+x} ]] ; then
if [[ $MODE -eq 1 || $MODE -eq 2 ]] ; then
echo "$0: cannot specify MSVS_PREFERENCE and --all or --installed">&2
exit 2
else
MSVS_PREFERENCE="$*"
fi
fi
fi
# Options sanitising
if [[ $MODE -eq 1 ]] ; then
if [[ -n $TARGET_ARCH ]] ; then
echo "$0: --all/--installed and --arch are mutually exclusive">&2
exit 2
fi
MSVS_PREFERENCE=
SCAN_ENV=1
elif [[ $OUTPUT -eq 2 && -z $TARGET_ARCH ]] ; then
echo "$0: --output=data requires --arch">&2
exit 2
elif [[ -z ${MSVS_PREFERENCE+x} ]] ; then
MSVS_PREFERENCE='@;VS17.*;VS16.*;VS15.*;VS14.0;VS12.0;VS11.0;10.0;9.0;8.0;7.1;7.0'
fi
MSVS_PREFERENCE=${MSVS_PREFERENCE//;/ }
if [[ -z $TARGET_ARCH ]] ; then
LEFT_ARCH=x86
RIGHT_ARCH=x64
else
LEFT_ARCH=$TARGET_ARCH
RIGHT_ARCH=$TARGET_ARCH
fi
# Command line parsing complete (MSVS_PREFERENCE pending)
NAME="Microsoft C Compiler Environment Detection Script"
case $MODE in
3)
echo "$NAME"
echo "Queries the environment and registry to locate Visual Studio / Windows SDK"
echo "installations and uses their initialisation scripts (SetEnv.cmd, vcvarsall.bat,"
echo "etc.) to determine INCLUDE, LIB and PATH alterations."
echo
echo "Usage:"
echo " $0 [OPTIONS] [--] [MSVS_PREFERENCE]"
echo
echo "Options:"
echo " -a, --all Display all available compiler packages"
echo " -x, --arch=ARCH Only consider packages for ARCH (x86 or x64). Default is"
echo " to return packages containing both architectures"
echo " -d, --debug[=LEVEL] Set debug messages level"
echo " -h, --help Display this help screen"
echo " -i, --installed Display all detected compiler packages; similarly to --all,"
echo " except the list includes versions and may include packages"
echo " which don't include the required compiler, tools, or libraries."
echo " -o, --output=OUTPUT Set final output. Default is shell. Valid values:"
echo " shell - shell assignments, for use with eval"
echo " make - make assignments, for inclusion in a Makefile"
echo " data - raw data, for parsing in other systems. The first"
echo " line is the full path to the batch file which was"
echo " analysed. The following lines are the individual"
echo " components of MSVS_PATH, MSVS_INC and MSVS_LIB, each"
echo " prefixed bin*, inc* or lib* respectively."
echo " -v, --version Display the version"
echo " --with-mt Only consider packages including the Manifest Tool"
echo " --with-assembler Only consider packages including an assembler"
echo
echo "If MSVS_PREFERENCE is not given, then the environment variable MSVS_PREFERENCE"
echo "is read. MSVS_PREFERENCE is a semicolon separated list of preferred versions."
echo "Three kinds of version notation are supported:"
echo " 1. @ - which refers to the C compiler found in PATH (if it can be identified)"
echo " (this allows the C compiler corresponding to the opposite architecture to"
echo " be selected, if possible)."
echo " 2. mm.n - which refers to a Visual Studio version (e.g. 14.0, 7.1) but which"
echo " also allows an SDK to provide the compiler (e.g. Windows SDK 7.1 provides"
echo " 10.0). Visual Studio packages are always preferred ahead of SDKs."
echo " 3. SPEC - an actual package specification. Visual Studio packages are VSmm.n"
echo " (e.g. VS14.0, VS7.1) and SDK packages are SDKm.n (e.g. SDK7.1)."
echo " Any Visual Studio 2017 update can be selected with VS15.*"
echo "The default behaviour is to match the environment compiler followed by the most"
echo "recent version of the compiler."
exit 0;;
4)
echo "$NAME"
echo "Version $VERSION"
exit 0;;
esac
# Known compiler packages. Visual Studio .NET 2002 onwards. Detection is in place for Visual Studio
# 2005 Express, but because it doesn't include a Windows SDK, it can only ever be detected if the
# script has been launched from within a Platform SDK command prompt (this provides the Windows
# Headers and Libraries which allows this script to detect the rest).
# Each element is either a Visual Studio or SDK package and the value is the syntax for a bash
# associative array to be eval'd. Each of these contains the following properties:
# NAME - the friendly name of the package
# ENV - (VS only) the version-specific portion of the VSCOMNTOOLS environment variable
# VERSION - (VS only) version number of the package
# ARCH - Lists the architectures available in this version
# ARCH_SWITCHES - The script is assumed to accept x86 and x64 to indicate architecture. This key
# contains another eval'd associative array allowing alternate values to be given
# SETENV_RELEASE - (SDK only) script switch necessary to select release than debugging versions
# EXPRESS - (VS only) the prefix to the registry key to detect the Express edition
# EXPRESS_ARCH - (VS only) overrides ARCH if Express edition is detected
# EXPRESS_ARCH_SWITCHES - (VS only) overrides ARCH_SWITCHES if Express edition is detected
# VC_VER - (SDK only) specifies the version of the C Compilers included in the SDK (SDK
# equivalent of the VERSION key)
# REG_KEY - (SDK only) registry key to open to identify this package installation
# REG_VALUE - (SDK only) registry value to query to identify this package installation
# VSWHERE - (VS 2017+) is 1 if the compiler can only be detected using vswhere
# For a while, Windows SDKs followed a standard pattern which is stored in the SDK element and
# copied to the appropriate version. SDKs after 7.1 do not include compilers, and so are not
# captured (as of Visual Studio 2015, the Windows SDK is official part of Visual Studio).
declare -A COMPILERS
# shellcheck disable=SC2034 # SDK52_KEY is used in COMPILERS which is eval'd...
SDK52_KEY='HKLM\SOFTWARE\Microsoft\MicrosoftSDK\InstalledSDKs\8F9E5EF3-A9A5-491B-A889-C58EFFECE8B3'
# shellcheck disable=SC2016 # ... and is therefore intentionally in a quoted string
COMPILERS=(
["VS7.0"]='(
["NAME"]="Visual Studio .NET 2002"
["ENV"]=""
["VERSION"]="7.0"
["ARCH"]="x86")'
["VS7.1"]='(
["NAME"]="Visual Studio .NET 2003"
["ENV"]="71"
["VERSION"]="7.1"
["ARCH"]="x86")'
["VS8.0"]='(
["NAME"]="Visual Studio 2005"
["ENV"]="80"
["VERSION"]="8.0"
["EXPRESS"]="VC"
["ARCH"]="x86 x64"
["EXPRESS_ARCH"]="x86")'
["VS9.0"]='(
["NAME"]="Visual Studio 2008"
["ENV"]="90"
["VERSION"]="9.0"
["EXPRESS"]="VC"
["ARCH"]="x86 x64"
["EXPRESS_ARCH"]="x86")'
["VS10.0"]='(
["NAME"]="Visual Studio 2010"
["ENV"]="100"
["VERSION"]="10.0"
["EXPRESS"]="VC"
["ARCH"]="x86 x64"
["EXPRESS_ARCH"]="x86")'
["VS11.0"]='(
["NAME"]="Visual Studio 2012"
["ENV"]="110"
["VERSION"]="11.0"
["EXPRESS"]="WD"
["ARCH"]="x86 x64"
["EXPRESS_ARCH_SWITCHES"]="([\"x64\"]=\"x86_amd64\")")'
["VS12.0"]='(
["NAME"]="Visual Studio 2013"
["ENV"]="120"
["VERSION"]="12.0"
["EXPRESS"]="WD"
["ARCH"]="x86 x64"
["EXPRESS_ARCH_SWITCHES"]="([\"x64\"]=\"x86_amd64\")")'
["VS14.0"]='(
["NAME"]="Visual Studio 2015"
["ENV"]="140"
["VERSION"]="14.0"
["ARCH"]="x86 x64")'
["VS15.*"]='(
["NAME"]="Visual Studio 2017"
["VSWHERE"]="1")'
["VS16.*"]='(
["NAME"]="Visual Studio 2019"
["VSWHERE"]="1")'
["VS17.*"]='(
["NAME"]="Visual Studio 2022"
["VSWHERE"]="1")'
["SDK5.2"]='(
["NAME"]="Windows Server 2003 SP1 SDK"
["VC_VER"]="8.0"
["VERSION"]="5.2"
["REG_KEY"]="$SDK52_KEY"
["REG_VALUE"]="Install Dir"
["SETENV_RELEASE"]="/RETAIL"
["ARCH"]="x64"
["ARCH_SWITCHES"]="([\"x64\"]=\"/X64\")")'
["SDK"]='(
["NAME"]="Generalised Windows SDK"
["SETENV_RELEASE"]="/Release"
["ARCH"]="x86 x64"
["ARCH_SWITCHES"]="([\"x86\"]=\"/x86\" [\"x64\"]=\"/x64\")")'
["SDK6.1"]='(
["NAME"]="Windows Server 2008 with .NET 3.5 SDK"
["VC_VER"]="9.0")'
["SDK7.0"]='(
["NAME"]="Windows 7 with .NET 3.5 SP1 SDK"
["VC_VER"]="9.0")'
["SDK7.1"]='(
["NAME"]="Windows 7 with .NET 4 SDK"
["VC_VER"]="10.0")'
)
# FOUND is ultimately an associative array containing installed compiler packages. It's
# hijacked here as part of MSVS_PREFERENCE validation.
# Ultimately, it contains a copy of the value from COMPILERS with the following extra keys:
# IS_EXPRESS - (VS only) indicates whether the Express edition was located
# SETENV - (SDK only) the full location of the SetEnv.cmd script
# ASSEMBLER - the name of the assembler (ml or ml64)
# MSVS_PATH \
# MSVS_INC > prefix values for PATH, INCLUDE and LIB determined by running the scripts.
# MSVS_LIB /
declare -A FOUND
# Check that MSVS_PREFERENCE is valid and contains no repetitions.
for v in $MSVS_PREFERENCE ; do
if [[ -n ${FOUND[$v]+x} ]] ; then
echo "$0: corrupt MSVS_PREFERENCE: repeated '$v'">&2
exit 2
fi
if [[ $v != "@" ]] ; then
if [[ -z ${COMPILERS[$v]+x} && -z ${COMPILERS["VS$v"]+x} && -z ${COMPILERS[${v%.*}.*]+x} ]] ; then
echo "$0: corrupt MSVS_PREFERENCE: unknown compiler '$v'">&2
exit 2
fi
else
SCAN_ENV=1
fi
FOUND["$v"]=""
done
# Reset FOUND for later use.
FOUND=()
if "$WHICH" cl >/dev/null 2>&1 ; then
env_has_cl=1
else
env_has_cl=0
fi
# Scan the environment for a C compiler, and check that it's valid. Throughout the rest of the
# script, it is assumed that if ENV_ARCH is set then there is a valid environment compiler.
if [[ $SCAN_ENV -eq 1 ]] ; then
if [[ $env_has_cl -eq 1 ]]; then
# Determine its architecture from the Microsoft Logo line.
ENV_ARCH=$(cl 2>&1 | head -1 | tr -d '\r')
case "${ENV_ARCH#* for }" in
x64|AMD64)
ENV_ARCH=x64;;
80x86|x86)
ENV_ARCH=x86;;
*)
echo "Unable to identify C compiler architecture from '${ENV_ARCH#* for }'">&2
echo "Environment C compiler discarded">&2
unset ENV_ARCH;;
esac
# Environment variable names are a bit of a nightmare on Windows - they are actually case
# sensitive (at the kernel level) but not at the user level! To compound the misery is that SDKs
# use Include and Lib where vcvars32 tends to use INCLUDE and LIB. Windows versions also contain
# a mix of Path and PATH, but fortunately Cygwin normalises that to PATH for us! For this
# reason, use env to determine the actual case of the LIB and INCLUDE variables.
if [[ -n ${ENV_ARCH+x} ]] ; then
RET=0
ENV_INC=$(env | sed -ne 's/^\(INCLUDE\)=.*/\1/pi')
ENV_LIB=$(env | sed -ne 's/^\(LIB\)=.*/\1/pi')
if [[ -z $ENV_INC || -z $ENV_LIB ]] ; then
warning "Microsoft C Compiler Include and/or Lib not set - Environment C compiler ($ENV_ARCH) excluded"
unset ENV_ARCH
else
if check_environment "${PATH//:/*}" \
"${!ENV_INC//;/*}" \
"${!ENV_LIB//;/*}" \
"Environment C compiler" \
"$ENV_ARCH" ; then
ENV_CL=$("$WHICH" cl)
ENV_cl=${ENV_CL,,}
ENV_cl=${ENV_cl/bin\/*_/bin\/}
debug "Environment appears to include a compiler at $ENV_CL"
# It doesn't matter whether the architecture matches - when the full probe happens in the
# next loop, the corresponding architecture should then be selected (so, for example, if
# the environment compiler is x86 VS2017, but VS2022 is installed, then by default the
# compiler returned for x64 is VS2017's x64 compiler, not VS2022's).
if [[ -n $TARGET_ARCH && $TARGET_ARCH != "$ENV_ARCH" ]] ; then
debug "But architecture doesn't match required value"
fi
else
unset ENV_ARCH
fi
fi
fi
fi
fi
# Even if launched from a 64-bit Command Prompt, Cygwin is usually 32-bit and so the scripts
# executed will inherit that fact. This is a problem when querying the registry, but fortunately
# WOW64 provides a mechanism to break out of the 32-bit environment by mapping $WINDIR/sysnative to
# the real 64-bit programs.
# Thus:
# MS_ROOT is the 32-bit Microsoft Registry key (all Visual Studio keys are located there)
# REG64 is the processor native version of the reg utility (allowing 64-bit keys to be read for
# the SDKs)
if [[ -n ${PROCESSOR_ARCHITEW6432+x} ]] ; then
debug "WOW64 detected"
MS_ROOT='HKLM\SOFTWARE\Microsoft'
REG64=$WINDIR/sysnative/reg
else
MS_ROOT='HKLM\SOFTWARE\Wow6432Node\Microsoft'
REG64=reg
fi
# COMPILER contains each eval'd element from COMPILERS
declare -A COMPILER
# Scan the registry for compiler package (vswhere is later)
for i in "${!COMPILERS[@]}" ; do
eval "COMPILER=${COMPILERS["$i"]}"
if [[ -n ${COMPILER["ENV"]+x} ]] ; then
# Visual Studio package - test for its environment variable
ENV=VS${COMPILER["ENV"]}COMNTOOLS
if [[ -n ${!ENV+x} ]] ; then
debug "$ENV is a candidate"
TEST_PATH=${!ENV%\"}
TEST_PATH=$(cygpath -u "${TEST_PATH#\"}")
if [[ -e $TEST_PATH/vsvars32.bat ]] ; then
debug "Directory pointed to by $ENV contains vsvars32.bat"
EXPRESS=0
# Check for the primary Visual Studio registry value indicating installation
INSTALL_DIR=$(reg_string "$MS_ROOT\\VisualStudio\\${COMPILER["VERSION"]}" InstallDir)
if [[ -z $INSTALL_DIR ]] ; then
if [[ -n ${COMPILER["EXPRESS"]+x} ]] ; then
TEST_KEY="$MS_ROOT\\${COMPILER["EXPRESS"]}Express\\${COMPILER["VERSION"]}"
INSTALL_DIR=$(reg_string "$TEST_KEY" InstallDir)
# Exception for Visual Studio 2005 Express, which doesn't set the registry correctly, so
# set INSTALL_DIR to a fake value to pass the next test.
if [[ ${COMPILER["VERSION"]} = "8.0" ]] ; then
INSTALL_DIR=$(cygpath -w "$TEST_PATH")
EXPRESS=1
else
if [[ -z $INSTALL_DIR ]] ; then
warning "vsvars32.bat found, but registry value not located (Exp or Pro)"
else
EXPRESS=1
fi
fi
else
warning "vsvars32.bat found, but registry value not located"
fi
fi
if [[ -n $INSTALL_DIR ]] ; then
if [[ ${TEST_PATH%/} = $(cygpath -u "$INSTALL_DIR\\..\\Tools") ]] ; then
RESULT=${COMPILERS[$i]%)}
DISPLAY=${COMPILER["NAME"]}
if [[ $EXPRESS -eq 1 ]] ; then
DISPLAY="$DISPLAY Express"
fi
ENV=VS${COMPILER["ENV"]}COMNTOOLS
ENV=${!ENV%\"}
ENV=${ENV#\"}
if [[ ${COMPILER["ENV"]}0 -ge 800 ]] ; then
SCRIPT="$(cygpath -d "$ENV")\\..\\..\\VC\\vcvarsall.bat"
else
SCRIPT="$(cygpath -d "$ENV")\\vsvars32.bat"
fi
FOUND+=(["$i"]="$RESULT [\"DISPLAY\"]=\"$DISPLAY\" [\"IS_EXPRESS\"]=\"$EXPRESS\" [\"SETENV\"]=\"$SCRIPT\")")
debug "${COMPILER["NAME"]} accepted for further detection"
else
warning "$ENV doesn't agree with registry"
fi
else
warning "vsvars32.bat found, but registry settings not found"
fi
else
warning "$ENV set, but vsvars32.bat not found"
fi
fi
elif [[ -n ${COMPILER["REG_KEY"]+x} ]] ; then
# SDK with explicit registry detection value
INSTALL_DIR=$(reg64_string "${COMPILER["REG_KEY"]}" "${COMPILER["REG_VALUE"]}")
if [[ -n $INSTALL_DIR ]] ; then
TEST_PATH=$(cygpath -u "$INSTALL_DIR")
if [[ -e $TEST_PATH/SetEnv.cmd ]] ; then
RESULT=${COMPILERS[$i]%)}
FOUND+=(["$i"]="$RESULT [\"DISPLAY\"]=\"${COMPILER["NAME"]}\" [\"SETENV\"]=\"$INSTALL_DIR\\SetEnv.cmd\")")
debug "${COMPILER["NAME"]} accepted for further detection"
else
warning "Registry set for Windows Server 2003 SDK, but SetEnv.cmd not found"
fi
fi
fi
done
# Now enumerate installed SDKs for v6.0+
SDK_ROOT='HKLM\SOFTWARE\Microsoft\Microsoft SDKs\Windows'
for i in $(reg query "$SDK_ROOT" 2>/dev/null | tr -d '\r' | sed -ne '/Windows\\v/s/.*\\//p') ; do
debug "Analysing SDK key $SDK_ROOT\\$i"
INSTALL_DIR=$(reg_string "$SDK_ROOT\\$i" InstallationFolder)
VERSION=$(reg_string "$SDK_ROOT\\$i" ProductVersion)
if [[ -n $INSTALL_DIR ]] ; then
TEST_PATH=$(cygpath -u "$INSTALL_DIR")
if [[ -e $TEST_PATH/Bin/SetEnv.cmd ]] ; then
if [[ -z ${COMPILERS["SDK${i#v}"]+x} ]] ; then
warning "SDK $i is not known to this script - assuming compatibility"
DISPLAY="Windows SDK $i"
else
eval "COMPILER=${COMPILERS["SDK${i#v}"]}"
DISPLAY=${COMPILER['NAME']}
fi
RESULT=${COMPILERS['SDK']%)}
FOUND+=(["SDK${i/v/}"]="$RESULT [\"DISPLAY\"]=\"$DISPLAY\" [\"VERSION\"]=\"$VERSION\" [\"SETENV\"]=\"$INSTALL_DIR\\Bin\\SetEnv.cmd\")")
else
if [[ -n ${COMPILERS["SDK${i#v}"]+x} ]] ; then
warning "Registry set for Windows SDK $i, but SetEnv.cmd not found"
fi
fi
else
warning "Registry key for Windows SDK $i doesn't contain expected InstallationFolder value"
fi
done
# Now enumerate Visual Studio 2017+ instances
VSWHERE="$(dirname "$(realpath "$0")")/vswhere.exe"
if [[ ! -x $VSWHERE ]] ; then
VSWHERE="$(printenv 'ProgramFiles(x86)')\\Microsoft Visual Studio\\Installer\\vswhere.exe"
VSWHERE=$(cygpath "$VSWHERE")
fi
if [[ -x $VSWHERE ]] ; then
debug "$VSWHERE found"
while IFS= read -r line; do
case ${line%: *} in
instanceId)
INSTANCE=${line#*: };;
installationPath)
INSTANCE_PATH=${line#*: };;
installationVersion)
INSTANCE_FULL_VER=${line#*: }
INSTANCE_VER=${INSTANCE_FULL_VER%.*}
INSTANCE_VER=${INSTANCE_VER%.*};;
displayName)
INSTANCE_NAME=${line#*: }
debug "Looking at $INSTANCE in $INSTANCE_PATH ($INSTANCE_FULL_VER $INSTANCE_NAME)"
if [[ -e "$(cygpath "$INSTANCE_PATH")/VC/Auxiliary/Build/vcvarsall.bat" ]] ; then
debug "vcvarsall.bat found"
if [[ $MODE -eq 2 ]]; then
INSTANCE_VER="$INSTANCE_VER-$INSTANCE"
fi
FOUND+=(["VS$INSTANCE_VER"]="([\"DISPLAY\"]=\"$INSTANCE_NAME\" [\"VERSION\"]=\"$INSTANCE_FULL_VER\" [\"ARCH\"]=\"x86 x64\" [\"SETENV\"]=\"$INSTANCE_PATH\\VC\\Auxiliary\\Build\\vcvarsall.bat\" [\"SETENV_RELEASE\"]=\"\")")
else
warning "vcvarsall.bat not found for $INSTANCE"
fi;;
esac
done < <("$VSWHERE" -all -prerelease -products '*' -nologo | tr -d '\r')
fi
if [[ $DEBUG -gt 1 || $MODE -eq 2 ]] ; then
for i in "${!FOUND[@]}" ; do
if [[ $MODE -eq 2 ]]; then
eval "COMPILER=${FOUND["$i"]}"
echo " $i (${COMPILER["VERSION"]}; ${COMPILER["SETENV"]})"
else
echo "Inspect $i">&2
fi
done | sort
fi
if [[ $MODE -eq 2 ]] ; then
exit 0
fi
# Basic scanning is complete, now interrogate the packages which seem to be installed and ensure
# that they pass the check_environment tests.
# CANDIDATES is a hash table of the keys of FOUND. The result of the next piece of processing is to
# derive two arrays PREFERENCE and TEST. TEST will contain a list of the keys of FOUND in the order
# in which they should be evaluated. PREFERENCE contains a parsed version of MSVS_PREFERENCE but
# filtered on the basis of the compiler packages already identified. The current "hoped for"
# preference is stored in $pref (the index into PREFERENCE) and $PREF (which is
# ${PREFERENCE[$pref]}). These two arrays together allow testing to complete quickly if the desired
# version is found (note that often this won't be possible as the @ environment option requires all
# packages to be tested in order to be sure that the environment compiler is not ambiguous).
declare -A CANDIDATES
for i in "${!FOUND[@]}" ; do
CANDIDATES[$i]="";
done
# For --all/--installed, act as though MSVS_PREFERENCE were "@" because this causes all packages to
# be tested.
if [[ $MODE -gt 0 ]] ; then
PREFER_ENV=1
PREFERENCE=("@")
else
PREFER_ENV=0
PREFERENCE=()
fi
TEST=()
for i in $MSVS_PREFERENCE ; do
if [[ $i = "@" ]] ; then
if [[ -n ${ENV_ARCH+x} ]] ; then
PREFERENCE+=("@")
PREFER_ENV=1
else
debug "Preference @ ignored since no environment compiler selected"
fi
else
if [[ -n ${COMPILERS[$i]+x} || -n ${COMPILERS[${i%.*}.*]+x} ]] ; then
if [[ -n ${CANDIDATES[$i]+x} ]] ; then
unset "CANDIDATES[$i]"
TEST+=("$i")
PREFERENCE+=("$i")
elif [[ ${i#*.} = "*" ]] ; then
INSTANCES=
for j in "${!CANDIDATES[@]}" ; do
if [[ "${j%.*}.*" = "$i" ]] ; then
unset "CANDIDATES[$j]"
INSTANCES="$INSTANCES $j"
fi
done
INSTANCES="$(sort -r <<< "${INSTANCES// /$'\n'}")"
eval "TEST+=($INSTANCES)"
eval "PREFERENCE+=($INSTANCES)"
fi
else
if [[ -n ${CANDIDATES["VS$i"]+x} ]] ; then
unset "CANDIDATES[VS$i]"
TEST+=("VS$i")
PREFERENCE+=("VS$i")
fi
SDKS=
for j in "${!COMPILERS[@]}" ; do
eval "COMPILER=${COMPILERS["$j"]}"
if [[ -n ${COMPILER["VC_VER"]+x} ]] ; then
if [[ $i = "${COMPILER["VC_VER"]}" && -n ${CANDIDATES[$j]+x} ]] ; then
unset "CANDIDATES[$j]"
SDKS="$j $SDKS"
fi
fi
done
SDKS=${SDKS% }
SDKS="$(sort -r <<< "${SDKS// /$'\n'}")"
SDKS=${SDKS//$'\n'/ }
eval "TEST+=($SDKS)"
eval "PREFERENCE+=($SDKS)"
fi
fi
done
# If MSVS_PREFERENCE includes @, add any remaining items from CANDIDATES to TEST, otherwise remove
# them from FOUND so that they don't accidentally get reported on later.
for i in "${!CANDIDATES[@]}" ; do
if [[ $PREFER_ENV -eq 1 ]] ; then
TEST+=("$i")
else
unset "FOUND[$i]"
fi
done
# Initialise pref and PREF to ${PREFERENCE[0]}
pref=0
PREF=${PREFERENCE[0]}
if [[ $DEBUG -gt 1 ]] ; then
for i in "${!TEST[@]}" ; do
echo "Test ${TEST[$i]}">&2
done
fi
# Now run each compiler's environment script and then test whether it is suitable. During this loop,
# attempt to identify the environment C compiler (if one was found). The environment C compiler is
# strongly identified if the full location of cl matches the one in PATH and both LIB and INCLUDE
# contain the strings returned by the script in an otherwise empty environment (if one or both of
# the LIB and INCLUDE variables do not contain the string returned, then the compiler is weakly
# identified). If the environment compiler is strongly identified by more than one package, then it
# is not identified at all; if it is strongly identified by no packages but weakly identified by
# exactly 1, then we grudgingly accept that that's probably the one.
ENV_COMPILER=
WEAK_ENV=
# ARCHINFO contains the appropriate ARCH_SWITCHES associative array for each compiler.
declare -A ARCHINFO
for i in "${TEST[@]}" ; do
CURRENT=${FOUND[$i]}
eval "COMPILER=$CURRENT"
# At the end of this process, the keys of FOUND will be augmented with the architecture found in
# each case (so if "VS14.0" was in FOUND from the scan and both the x86 and x64 compilers are
# valid, then at the end of this loop FOUND will contain "VS14.0-x86" and "VS14.0-x64").
unset "FOUND[$i]"
if [[ ${COMPILER["IS_EXPRESS"]}0 -gt 0 && -n ${COMPILER["EXPRESS_ARCH_SWITCHES"]+x} ]] ; then
eval "ARCHINFO=${COMPILER['EXPRESS_ARCH_SWITCHES']}"
elif [[ -n ${COMPILER["ARCH_SWITCHES"]+x} ]] ; then
eval "ARCHINFO=${COMPILER['ARCH_SWITCHES']}"
else
ARCHINFO=()
fi
# Determine the script to be executed and any non-architecture specific switches needed.
# $ENV will contain the value of the environment variable for the compiler (empty for an SDK)
# which is required for the Visual Studio 7.x shim later.
SCRIPT=${COMPILER["SETENV"]}
if [[ -n ${COMPILER["ENV"]+x} ]] ; then
SCRIPT_SWITCHES=''
else
ENV=
if [[ -n ${COMPILER["SETENV_RELEASE"]} ]]; then
SCRIPT_SWITCHES=" ${COMPILER["SETENV_RELEASE"]}"
else
SCRIPT_SWITCHES=''
fi
fi
# For reasons of escaping, the script is executed using its basename so the directory needs
# prepending to PATH.
DIR=$(cygpath -u "$(dirname "$SCRIPT")")
if [[ ${COMPILER["IS_EXPRESS"]} -gt 0 && -n ${COMPILER["EXPRESS_ARCH"]+x} ]] ; then
ARCHS=${COMPILER["EXPRESS_ARCH"]}
else
ARCHS=${COMPILER["ARCH"]}
fi
for arch in $ARCHS ; do
# Determine the command line switch for this architecture
if [[ -n ${ARCHINFO[$arch]+x} ]] ; then
ARCH_SWITCHES=${ARCHINFO[$arch]}
else
ARCH_SWITCHES=$arch
fi
# Run the script in order to determine changes made to PATH, INCLUDE and LIB. These scripts
# always prepend changes to the environment variables.
MSVS_PATH=
MSVS_LIB=
MSVS_INC=
COMMAND='%EXEC_SCRIPT% && echo XMARKER && echo !PATH! && echo !LIB! && echo !INCLUDE! && echo !VCToolsVersion! && echo !VSCMD_VER! && echo !VisualStudioVersion!'
if [[ $env_has_cl -eq 1 ]]; then
if [[ -n $__VSCMD_PREINIT_PATH ]]; then
TEST_PATH=$(cygpath -p "$__VSCMD_PREINIT_PATH")
elif [[ -n $ORIGINALPATH ]]; then
TEST_PATH="$ORIGINALPATH"
else
TEST_PATH="$PATH"
fi
else
TEST_PATH="$PATH"
fi
# Note that EXEC_SCRIPT must have ARCH_SWITCHES first for older Platform SDKs (newer ones parse
# arguments properly)