blob: 601575c67c32fb412ce64547cbca9546b59dc1ae [file] [log] [blame]
#!/usr/bin/env bash
# ################################################################################################ #
# MetaStack Solutions Ltd. #
# ################################################################################################ #
# Microsoft C Compiler Environment Detection Script #
# ################################################################################################ #
# Copyright (c) 2016, 2017, 2018, 2019, 2020 MetaStack Solutions 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.4.1
# 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//\$/\$\$}";;
esac
}
# DEBUG Debugging level
# MODE Operation mode
# 0 - Normal
# 1 - --all
# 2 - --help
# 3 - --version
# OUTPUT --output option
# 0 - =shell
# 1 - =make
# 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)
if [[ $(uname --operating-system 2>/dev/null) = "Msys" ]] ; then
# Prevent MSYS from translating command line switches to paths
SWITCH_PREFIX='//'
else
SWITCH_PREFIX='/'
fi
# 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 [[ $@ != "" ]] ; then
while true ; do
case "$1" in
# Mode settings ($MODE)
-a|--all)
MODE=1
shift 1;;
-h|--help)
MODE=2
shift;;
-v|--version)
MODE=3
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;;
*)
echo "$0: unrecognised option for $1: '$2'">&2
exit 2;;
esac
shift 2;;
-oshell|--output=shell)
shift;;
-omake|--output=make)
OUTPUT=1
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)
TARGET_ARCH=x86;;
64|x64)
TARGET_ARCH=x64;;
*)
echo "$0: unrecognised option for $1: '$2'">&2
exit 2
esac
shift 2;;
-x86|-xx86|--arch=x86|--arch=86)
TARGET_ARCH=x86
shift;;
-x64|-xx64|--arch=x64|--arch=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 ]] ; then
echo "$0: cannot specify MSVS_PREFERENCE and --all">&2
exit 2
else
MSVS_PREFERENCE="$@"
fi
fi
fi
# Options sanitising
if [[ $MODE -eq 1 ]] ; then
if [[ -n $TARGET_ARCH ]] ; then
echo "$0: --all and --arch are mutually exclusive">&2
exit 2
fi
MSVS_PREFERENCE=
SCAN_ENV=1
elif [[ -z ${MSVS_PREFERENCE+x} ]] ; then
MSVS_PREFERENCE='@;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
2)
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 " -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 " -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;;
3)
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
SDK52_KEY='HKLM\SOFTWARE\Microsoft\MicrosoftSDK\InstalledSDKs\8F9E5EF3-A9A5-491B-A889-C58EFFECE8B3'
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")'
["SDK5.2"]='(
["NAME"]="Windows Server 2003 SP1 SDK"
["VC_VER"]="8.0"
["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=()
# 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 "$WHICH" cl >/dev/null 2>&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"
if [[ -n $TARGET_ARCH && $TARGET_ARCH != $ENV_ARCH ]] ; then
debug "But architecture doesn't match required value"
unset ENV_ARCH
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 -f - <<< ${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
FOUND+=(["$i"]="$RESULT [\"DISPLAY\"]=\"$DISPLAY\" [\"IS_EXPRESS\"]=\"$EXPRESS\")")
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)
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\" [\"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=$(echo $VSWHERE| cygpath -f -)
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_VER=${line#*: }
INSTANCE_VER=${INSTANCE_VER%.*}
INSTANCE_VER=${INSTANCE_VER%.*};;
displayName)
INSTANCE_NAME=${line#*: }
debug "Looking at $INSTANCE in $INSTANCE_PATH ($INSTANCE_VER $INSTANCE_NAME)"
if [[ -e "$(echo $INSTANCE_PATH| cygpath -f -)/VC/Auxiliary/Build/vcvarsall.bat" ]] ; then
debug "vcvarsall.bat found"
FOUND+=(["VS$INSTANCE_VER"]="([\"DISPLAY\"]=\"$INSTANCE_NAME\" [\"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 -nologo | tr -d '\r')
fi
if [[ $DEBUG -gt 1 ]] ; then
for i in "${!FOUND[@]}" ; do
echo "Inspect $i">&2
done
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, act as though MSVS_PREFERENCE were "@" because this causes all packages to be tested.
if [[ $MODE -eq 1 ]] ; 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 is will contain the value of the environment variable for the compiler (empty for an SDK)
# which is required for Visual Studio 7.x shim later.
if [[ -n ${COMPILER["ENV"]+x} ]] ; then
ENV=VS${COMPILER["ENV"]}COMNTOOLS
ENV=${!ENV%\"}
ENV=${ENV#\"}
if [[ ${COMPILER["ENV"]}0 -ge 800 ]] ; then
SCRIPT="$(cygpath -d -f - <<< $ENV)\\..\\..\\VC\\vcvarsall.bat"
SCRIPT_SWITCHES=
else
SCRIPT="$(cygpath -d -f - <<< $ENV)\\vsvars32.bat"
SCRIPT_SWITCHES=
fi
else
ENV=
SCRIPT=${COMPILER["SETENV"]}
SCRIPT_SWITCHES=${COMPILER["SETENV_RELEASE"]}
fi
# For reasons of escaping, the script is executed using its basename so the directory needs
# prepending to PATH.
DIR=$(dirname "$SCRIPT" | cygpath -u -f -)
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!'
# Note that EXEC_SCRIPT must have ARCH_SWITCHES first for older Platform SDKs (newer ones parse
# arguments properly)
if [[ $DEBUG -gt 3 ]] ; then
printf "Scanning %s... " "$(basename "$SCRIPT") $ARCH_SWITCHES $SCRIPT_SWITCHES">&2
fi
num=0
while IFS= read -r line; do
case $num in
0)
MSVS_PATH=${line%% };;
1)
MSVS_LIB=${line%% };;
2)
MSVS_INC=${line%% };;
esac
((num++))
done < <(INCLUDE='' LIB='' PATH="?msvs-detect?:$DIR:$PATH" ORIGINALPATH='' \
EXEC_SCRIPT="$(basename "$SCRIPT") $ARCH_SWITCHES $SCRIPT_SWITCHES" \
$(cygpath "$COMSPEC") ${SWITCH_PREFIX}v:on ${SWITCH_PREFIX}c $COMMAND 2>/dev/null | grep -F XMARKER -A 3 | tr -d '\015' | tail -3)
if [[ $DEBUG -gt 3 ]] ; then
echo done>&2
fi
if [[ -n $MSVS_PATH ]] ; then
# Translate MSVS_PATH back to Cygwin notation (/cygdrive, etc. and colon-separated)
MSVS_PATH=$(cygpath "$MSVS_PATH" -p)
# Remove any trailing / from elements of MSVS_PATH
MSVS_PATH=$(echo "$MSVS_PATH" | sed -e 's|\([^:]\)/\+\(:\|$\)|\1\2|g;s/?msvs-detect?.*//')
# Guarantee that MSVS_PATH ends with a single :
MSVS_PATH="${MSVS_PATH%%:}:"
fi
# Ensure that both variables end with a semi-colon (it doesn't matter if for some erroneous
# reason they have come back blank, because check_environment will shortly fail)
MSVS_LIB="${MSVS_LIB%%;};"
MSVS_INC="${MSVS_INC%%;};"
# Visual Studio .NET 2002 and 2003 do not include mt in PATH, for not entirely clear reasons.
# This shim detects that scenario and adds the winnt folder to MSVS_PATH.
RET=0
if [[ ${i/.*/} = "VS7" ]] ; then
find_in "${MSVS_PATH//:/*}" mt.exe
if [[ $RET -eq 1 ]] ; then
MSVS_PATH="$MSVS_PATH$(cygpath -u -f - <<< $ENV\\Bin\\winnt):"
RET=0
fi
fi
# Ensure that these derived values give a valid compiler.
if check_environment "${MSVS_PATH//:/*}" "${MSVS_INC//;/*}" "${MSVS_LIB//;/*}" "$i" $arch ; then
# Put the package back into FOUND, but augmented with the architecture name and with the
# derived values.
FOUND["$i-$arch"]="${CURRENT%)} [\"MSVS_PATH\"]=\"$MSVS_PATH\" \
[\"MSVS_INC\"]=\"$MSVS_INC\" \
[\"MSVS_LIB\"]=\"$MSVS_LIB\" \
[\"ASSEMBLER\"]=\"$ASSEMBLER\")" #"# fixes vim syn match error
# Check to see if this is a match for the environment C compiler.
if [[ -n ${ENV_ARCH+x} ]] ; then
TEST_cl=$(PATH="$MSVS_PATH:$PATH" "$WHICH" cl)
TEST_cl=${TEST_cl,,}
TEST_cl=${TEST_cl/bin\/*_/bin\/}
if [[ $TEST_cl = $ENV_cl ]] ; then
if [[ ${!ENV_INC/"$MSVS_INC"/} != "${!ENV_INC}" && \
${!ENV_LIB/"$MSVS_LIB"/} != "${!ENV_LIB}" ]] ; then
debug "$i-$arch is a strong candidate for the Environment C compiler"
if [[ -n ${ENV_COMPILER+x} ]] ; then
if [[ -z ${ENV_COMPILER} ]] ; then
ENV_COMPILER=$i-$arch
unset WEAK_ENV
else
# More than one strong candidate - no fall back available
unset ENV_COMPILER
unset WEAK_ENV
fi
fi
else
debug "$i-$arch is a weak candidate for the Environment C compiler"
if [[ -n ${WEAK_ENV+x} ]] ; then
if [[ -z ${WEAK_ENV} ]] ; then
WEAK_ENV=$i-$arch
else
# More than one weak candidate - no fall back available
unset WEAK_ENV
fi
fi
fi
fi
fi
fi
done
# Does this package match the current preference? Note that PREFERENCE and TEST are constructed in
# a cunning (and hopefully not too "You are not expected to understand this" way) such that $PREF
# will always equal $i, unless $PREF = "@".
if [[ $PREF = $i ]] ; then
# In which case, check that the architecture(s)s were found
if [[ -n ${FOUND["$i-$LEFT_ARCH"]+x} && -n ${FOUND["$i-$RIGHT_ARCH"]+x} ]] ; then
debug "Solved TARGET_ARCH=$TARGET_ARCH with $i"
SOLUTION=$i
break
fi
fi
if [[ $PREF != "@" ]] ; then
((pref++))
PREF=${PREFERENCE[$pref]}
fi
done
# If we got this far, then either we failed to find a compiler at all, or we were looking for the
# environment compiler (or --all was specified).
# Adopt a weak match for the environment compiler, if that's the best we can do.
if [[ -n ${ENV_COMPILER+x} && -z ${ENV_COMPILER} && -n ${WEAK_ENV} ]] ; then
warning "Assuming Environment C compiler is $WEAK_ENV"
ENV_COMPILER=$WEAK_ENV
fi
declare -A FLIP
FLIP=(["x86"]="x64" ["x64"]="x86")
if [[ $MODE -eq 0 ]] ; then
if [[ $PREF = "@" && -n ${ENV_COMPILER} ]] ; then
SOLUTION=${ENV_COMPILER%-$ENV_ARCH}
# If --arch wasn't specified, then ensure that the other architecture was also found. If --arch
# was specified, then validate that the compiler was valid. This should always happen, unless
# something went wrong running the script to get MSVS_PATH, MSVS_LIB and MSVS_INC.
if [[ -n ${FOUND["$SOLUTION-${FLIP[$ENV_ARCH]}"]+x} ||
-n ${FOUND["$SOLUTION-$TARGET_ARCH"]+x} ]] ; then
debug "Solved with $SOLUTION"
else
unset SOLUTION
unset ENV_ARCH
fi
fi
if [[ -z ${SOLUTION+x} ]] ; then
((pref++))
debug "Search remaining: ${PREFERENCE[*]}"
TEST_ARCH=$TARGET_ARCH
for i in "${PREFERENCE[@]:$pref}" ; do
if [[ -n ${FOUND["$i-$LEFT_ARCH"]+x} && -n ${FOUND["$i-$RIGHT_ARCH"]+x} ]] ; then
debug "Solved TARGET_ARCH='$TARGET_ARCH' with $i"
SOLUTION=$i
break
fi
done
fi
fi
debug "Solution: $SOLUTION"
if [[ -n ${ENV_COMPILER} && $MODE -eq 1 ]] ; then
echo "Identified Environment C compiler as $ENV_COMPILER"
fi
if [[ $MODE -eq 1 ]] ; then
echo "Installed and usable packages:"
for i in "${!FOUND[@]}" ; do
echo " $i"
done | sort
exit 0
fi
if [[ -n $SOLUTION ]] ; then
eval COMPILER=${FOUND[$SOLUTION-$LEFT_ARCH]}
output MSVS_NAME "${COMPILER["DISPLAY"]}" $LEFT_ARCH
output MSVS_PATH "${COMPILER["MSVS_PATH"]}" $LEFT_ARCH
output MSVS_INC "${COMPILER["MSVS_INC"]}" $LEFT_ARCH
output MSVS_LIB "${COMPILER["MSVS_LIB"]}" $LEFT_ARCH
if [[ $ML_REQUIRED -eq 1 ]] ; then
output MSVS_ML "${COMPILER["ASSEMBLER"]%.exe}" always
fi
if [[ -z $TARGET_ARCH ]] ; then
eval COMPILER=${FOUND[$SOLUTION-$RIGHT_ARCH]}
output MSVS64_PATH "${COMPILER["MSVS_PATH"]}" $RIGHT_ARCH
output MSVS64_INC "${COMPILER["MSVS_INC"]}" $RIGHT_ARCH
output MSVS64_LIB "${COMPILER["MSVS_LIB"]}" $RIGHT_ARCH
if [[ $ML_REQUIRED -eq 1 ]] ; then
output MSVS64_ML "${COMPILER["ASSEMBLER"]%.exe}" always
fi
fi
exit 0
else
exit 1
fi