diff --git a/appveyor.yml b/appveyor.yml index 941a3068..ff466c5a 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,5 +1,15 @@ environment: matrix: + - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019 + LLVM_VERSION: 7.0.0 + LLVM_VERSION_SHORT: 70 + VS_MAJOR_VERSION: 14 # Just use LLVM built with VS 2015 + USE_CMAKE: 1 + - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017 + LLVM_VERSION: 7.0.0 + LLVM_VERSION_SHORT: 70 + VS_MAJOR_VERSION: 14 # Just use LLVM built with VS 2015 + USE_CMAKE: 1 - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015 LLVM_VERSION: 7.0.0 LLVM_VERSION_SHORT: 70 diff --git a/src/MSVCSetupAPI.h b/src/MSVCSetupAPI.h new file mode 100644 index 00000000..0e2863dc --- /dev/null +++ b/src/MSVCSetupAPI.h @@ -0,0 +1,514 @@ +// +// Copyright (C) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. +// +// +// The MIT License (MIT) +// +// Copyright (C) Microsoft Corporation. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// + +#pragma once + +// Constants +// +#ifndef E_NOTFOUND +#define E_NOTFOUND HRESULT_FROM_WIN32(ERROR_NOT_FOUND) +#endif + +#ifndef E_FILENOTFOUND +#define E_FILENOTFOUND HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND) +#endif + +// Enumerations +// +/// +/// The state of an instance. +/// +enum InstanceState : unsigned { + /// + /// The instance state has not been determined. + /// + eNone = 0, + + /// + /// The instance installation path exists. + /// + eLocal = 1, + + /// + /// A product is registered to the instance. + /// + eRegistered = 2, + + /// + /// No reboot is required for the instance. + /// + eNoRebootRequired = 4, + + /// + /// The instance represents a complete install. + /// + eComplete = MAXUINT, +}; + +// Forward interface declarations +// +#ifndef __ISetupInstance_FWD_DEFINED__ +#define __ISetupInstance_FWD_DEFINED__ +typedef struct ISetupInstance ISetupInstance; +#endif + +#ifndef __ISetupInstance2_FWD_DEFINED__ +#define __ISetupInstance2_FWD_DEFINED__ +typedef struct ISetupInstance2 ISetupInstance2; +#endif + +#ifndef __IEnumSetupInstances_FWD_DEFINED__ +#define __IEnumSetupInstances_FWD_DEFINED__ +typedef struct IEnumSetupInstances IEnumSetupInstances; +#endif + +#ifndef __ISetupConfiguration_FWD_DEFINED__ +#define __ISetupConfiguration_FWD_DEFINED__ +typedef struct ISetupConfiguration ISetupConfiguration; +#endif + +#ifndef __ISetupConfiguration2_FWD_DEFINED__ +#define __ISetupConfiguration2_FWD_DEFINED__ +typedef struct ISetupConfiguration2 ISetupConfiguration2; +#endif + +#ifndef __ISetupPackageReference_FWD_DEFINED__ +#define __ISetupPackageReference_FWD_DEFINED__ +typedef struct ISetupPackageReference ISetupPackageReference; +#endif + +#ifndef __ISetupHelper_FWD_DEFINED__ +#define __ISetupHelper_FWD_DEFINED__ +typedef struct ISetupHelper ISetupHelper; +#endif + +// Forward class declarations +// +#ifndef __SetupConfiguration_FWD_DEFINED__ +#define __SetupConfiguration_FWD_DEFINED__ + +#ifdef __cplusplus +typedef class SetupConfiguration SetupConfiguration; +#endif + +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +// Interface definitions +// +EXTERN_C const IID IID_ISetupInstance; + +#if defined(__cplusplus) && !defined(CINTERFACE) +/// +/// Information about an instance of a product. +/// +struct DECLSPEC_UUID("B41463C3-8866-43B5-BC33-2B0676F7F42E") + DECLSPEC_NOVTABLE ISetupInstance : public IUnknown { + /// + /// Gets the instance identifier (should match the name of the parent instance + /// directory). + /// + /// The instance identifier. + /// Standard HRESULT indicating success or failure, including + /// E_FILENOTFOUND if the instance state does not exist. + STDMETHOD(GetInstanceId)(_Out_ BSTR *pbstrInstanceId) = 0; + + /// + /// Gets the local date and time when the installation was originally + /// installed. + /// + /// The local date and time when the installation + /// was originally installed. + /// Standard HRESULT indicating success or failure, including + /// E_FILENOTFOUND if the instance state does not exist and E_NOTFOUND if the + /// property is not defined. + STDMETHOD(GetInstallDate)(_Out_ LPFILETIME pInstallDate) = 0; + + /// + /// Gets the unique name of the installation, often indicating the branch and + /// other information used for telemetry. + /// + /// The unique name of the installation, + /// often indicating the branch and other information used for + /// telemetry. + /// Standard HRESULT indicating success or failure, including + /// E_FILENOTFOUND if the instance state does not exist and E_NOTFOUND if the + /// property is not defined. + STDMETHOD(GetInstallationName)(_Out_ BSTR *pbstrInstallationName) = 0; + + /// + /// Gets the path to the installation root of the product. + /// + /// The path to the installation root of + /// the product. + /// Standard HRESULT indicating success or failure, including + /// E_FILENOTFOUND if the instance state does not exist and E_NOTFOUND if the + /// property is not defined. + STDMETHOD(GetInstallationPath)(_Out_ BSTR *pbstrInstallationPath) = 0; + + /// + /// Gets the version of the product installed in this instance. + /// + /// The version of the product + /// installed in this instance. + /// Standard HRESULT indicating success or failure, including + /// E_FILENOTFOUND if the instance state does not exist and E_NOTFOUND if the + /// property is not defined. + STDMETHOD(GetInstallationVersion)(_Out_ BSTR *pbstrInstallationVersion) = 0; + + /// + /// Gets the display name (title) of the product installed in this instance. + /// + /// The LCID for the display name. + /// The display name (title) of the product + /// installed in this instance. + /// Standard HRESULT indicating success or failure, including + /// E_FILENOTFOUND if the instance state does not exist and E_NOTFOUND if the + /// property is not defined. + STDMETHOD(GetDisplayName)(_In_ LCID lcid, _Out_ BSTR *pbstrDisplayName) = 0; + + /// + /// Gets the description of the product installed in this instance. + /// + /// The LCID for the description. + /// The description of the product installed in + /// this instance. + /// Standard HRESULT indicating success or failure, including + /// E_FILENOTFOUND if the instance state does not exist and E_NOTFOUND if the + /// property is not defined. + STDMETHOD(GetDescription)(_In_ LCID lcid, _Out_ BSTR *pbstrDescription) = 0; + + /// + /// Resolves the optional relative path to the root path of the instance. + /// + /// A relative path within the instance to + /// resolve, or NULL to get the root path. + /// The full path to the optional relative + /// path within the instance. If the relative path is NULL, the root path will + /// always terminate in a backslash. + /// Standard HRESULT indicating success or failure, including + /// E_FILENOTFOUND if the instance state does not exist and E_NOTFOUND if the + /// property is not defined. + STDMETHOD(ResolvePath) + (_In_opt_z_ LPCOLESTR pwszRelativePath, _Out_ BSTR *pbstrAbsolutePath) = 0; +}; +#endif + +EXTERN_C const IID IID_ISetupInstance2; + +#if defined(__cplusplus) && !defined(CINTERFACE) +/// +/// Information about an instance of a product. +/// +struct DECLSPEC_UUID("89143C9A-05AF-49B0-B717-72E218A2185C") + DECLSPEC_NOVTABLE ISetupInstance2 : public ISetupInstance { + /// + /// Gets the state of the instance. + /// + /// The state of the instance. + /// Standard HRESULT indicating success or failure, including + /// E_FILENOTFOUND if the instance state does not exist. + STDMETHOD(GetState)(_Out_ InstanceState *pState) = 0; + + /// + /// Gets an array of package references registered to the instance. + /// + /// Pointer to an array of . + /// Standard HRESULT indicating success or failure, including + /// E_FILENOTFOUND if the instance state does not exist and E_NOTFOUND if the + /// packages property is not defined. + STDMETHOD(GetPackages)(_Out_ LPSAFEARRAY *ppsaPackages) = 0; + + /// + /// Gets a pointer to the that represents + /// the registered product. + /// + /// Pointer to an instance of . This may be NULL if does not return . + /// Standard HRESULT indicating success or failure, including + /// E_FILENOTFOUND if the instance state does not exist and E_NOTFOUND if the + /// packages property is not defined. + STDMETHOD(GetProduct) + (_Outptr_result_maybenull_ ISetupPackageReference **ppPackage) = 0; + + /// + /// Gets the relative path to the product application, if available. + /// + /// The relative path to the product + /// application, if available. + /// Standard HRESULT indicating success or failure, including + /// E_FILENOTFOUND if the instance state does not exist. + STDMETHOD(GetProductPath) + (_Outptr_result_maybenull_ BSTR *pbstrProductPath) = 0; +}; +#endif + +EXTERN_C const IID IID_IEnumSetupInstances; + +#if defined(__cplusplus) && !defined(CINTERFACE) +/// +/// A enumerator of installed objects. +/// +struct DECLSPEC_UUID("6380BCFF-41D3-4B2E-8B2E-BF8A6810C848") + DECLSPEC_NOVTABLE IEnumSetupInstances : public IUnknown { + /// + /// Retrieves the next set of product instances in the enumeration sequence. + /// + /// The number of product instances to retrieve. + /// A pointer to an array of . + /// A pointer to the number of product instances + /// retrieved. If celt is 1 this parameter may be NULL. + /// S_OK if the number of elements were fetched, S_FALSE if nothing + /// was fetched (at end of enumeration), E_INVALIDARG if celt is greater than + /// 1 and pceltFetched is NULL, or E_OUTOFMEMORY if an could not be allocated. + STDMETHOD(Next) + (_In_ ULONG celt, _Out_writes_to_(celt, *pceltFetched) ISetupInstance **rgelt, + _Out_opt_ _Deref_out_range_(0, celt) ULONG *pceltFetched) = 0; + + /// + /// Skips the next set of product instances in the enumeration sequence. + /// + /// The number of product instances to skip. + /// S_OK if the number of elements could be skipped; otherwise, + /// S_FALSE; + STDMETHOD(Skip)(_In_ ULONG celt) = 0; + + /// + /// Resets the enumeration sequence to the beginning. + /// + /// Always returns S_OK; + STDMETHOD(Reset)(void) = 0; + + /// + /// Creates a new enumeration object in the same state as the current + /// enumeration object: the new object points to the same place in the + /// enumeration sequence. + /// + /// A pointer to a pointer to a new interface. If the method fails, this + /// parameter is undefined. + /// S_OK if a clone was returned; otherwise, E_OUTOFMEMORY. + STDMETHOD(Clone)(_Deref_out_opt_ IEnumSetupInstances **ppenum) = 0; +}; +#endif + +EXTERN_C const IID IID_ISetupConfiguration; + +#if defined(__cplusplus) && !defined(CINTERFACE) +/// +/// Gets information about product instances set up on the machine. +/// +struct DECLSPEC_UUID("42843719-DB4C-46C2-8E7C-64F1816EFD5B") + DECLSPEC_NOVTABLE ISetupConfiguration : public IUnknown { + /// + /// Enumerates all completed product instances installed. + /// + /// An enumeration of completed, installed + /// product instances. + /// Standard HRESULT indicating success or failure. + STDMETHOD(EnumInstances)(_Out_ IEnumSetupInstances **ppEnumInstances) = 0; + + /// + /// Gets the instance for the current process path. + /// + /// The instance for the current process + /// path. + /// The instance for the current process path, or E_NOTFOUND if not + /// found. + STDMETHOD(GetInstanceForCurrentProcess) + (_Out_ ISetupInstance **ppInstance) = 0; + + /// + /// Gets the instance for the given path. + /// + /// The instance for the given path. + /// The instance for the given path, or E_NOTFOUND if not + /// found. + STDMETHOD(GetInstanceForPath) + (_In_z_ LPCWSTR wzPath, _Out_ ISetupInstance **ppInstance) = 0; +}; +#endif + +EXTERN_C const IID IID_ISetupConfiguration2; + +#if defined(__cplusplus) && !defined(CINTERFACE) +/// +/// Gets information about product instances. +/// +struct DECLSPEC_UUID("26AAB78C-4A60-49D6-AF3B-3C35BC93365D") + DECLSPEC_NOVTABLE ISetupConfiguration2 : public ISetupConfiguration { + /// + /// Enumerates all product instances. + /// + /// An enumeration of all product + /// instances. + /// Standard HRESULT indicating success or failure. + STDMETHOD(EnumAllInstances)(_Out_ IEnumSetupInstances **ppEnumInstances) = 0; +}; +#endif + +EXTERN_C const IID IID_ISetupPackageReference; + +#if defined(__cplusplus) && !defined(CINTERFACE) +/// +/// A reference to a package. +/// +struct DECLSPEC_UUID("da8d8a16-b2b6-4487-a2f1-594ccccd6bf5") + DECLSPEC_NOVTABLE ISetupPackageReference : public IUnknown { + /// + /// Gets the general package identifier. + /// + /// The general package identifier. + /// Standard HRESULT indicating success or failure. + STDMETHOD(GetId)(_Out_ BSTR *pbstrId) = 0; + + /// + /// Gets the version of the package. + /// + /// The version of the package. + /// Standard HRESULT indicating success or failure. + STDMETHOD(GetVersion)(_Out_ BSTR *pbstrVersion) = 0; + + /// + /// Gets the target process architecture of the package. + /// + /// The target process architecture of the + /// package. + /// Standard HRESULT indicating success or failure. + STDMETHOD(GetChip)(_Out_ BSTR *pbstrChip) = 0; + + /// + /// Gets the language and optional region identifier. + /// + /// The language and optional region + /// identifier. + /// Standard HRESULT indicating success or failure. + STDMETHOD(GetLanguage)(_Out_ BSTR *pbstrLanguage) = 0; + + /// + /// Gets the build branch of the package. + /// + /// The build branch of the package. + /// Standard HRESULT indicating success or failure. + STDMETHOD(GetBranch)(_Out_ BSTR *pbstrBranch) = 0; + + /// + /// Gets the type of the package. + /// + /// The type of the package. + /// Standard HRESULT indicating success or failure. + STDMETHOD(GetType)(_Out_ BSTR *pbstrType) = 0; + + /// + /// Gets the unique identifier consisting of all defined tokens. + /// + /// The unique identifier consisting of all + /// defined tokens. + /// Standard HRESULT indicating success or failure, including + /// E_UNEXPECTED if no Id was defined (required). + STDMETHOD(GetUniqueId)(_Out_ BSTR *pbstrUniqueId) = 0; +}; +#endif + +EXTERN_C const IID IID_ISetupHelper; + +#if defined(__cplusplus) && !defined(CINTERFACE) +/// +/// Helper functions. +/// +/// +/// You can query for this interface from the +/// class. +/// +struct DECLSPEC_UUID("42b21b78-6192-463e-87bf-d577838f1d5c") + DECLSPEC_NOVTABLE ISetupHelper : public IUnknown { + /// + /// Parses a dotted quad version string into a 64-bit unsigned integer. + /// + /// The dotted quad version string to parse, e.g. + /// 1.2.3.4. + /// A 64-bit unsigned integer representing the + /// version. You can compare this to other versions. + /// Standard HRESULT indicating success or failure. + STDMETHOD(ParseVersion) + (_In_ LPCOLESTR pwszVersion, _Out_ PULONGLONG pullVersion) = 0; + + /// + /// Parses a dotted quad version string into a 64-bit unsigned integer. + /// + /// The string containing 1 or 2 dotted quad + /// version strings to parse, e.g. [1.0,) that means 1.0.0.0 or newer. + /// A 64-bit unsigned integer representing the + /// minimum version, which may be 0. You can compare this to other + /// versions. + /// A 64-bit unsigned integer representing the + /// maximum version, which may be MAXULONGLONG. You can compare this to other + /// versions. + /// Standard HRESULT indicating success or failure. + STDMETHOD(ParseVersionRange) + (_In_ LPCOLESTR pwszVersionRange, _Out_ PULONGLONG pullMinVersion, + _Out_ PULONGLONG pullMaxVersion) = 0; +}; +#endif + +// Class declarations +// +EXTERN_C const CLSID CLSID_SetupConfiguration; + +#ifdef __cplusplus +/// +/// This class implements , , and . +/// +class DECLSPEC_UUID("177F0C4A-1CD3-4DE7-A32C-71DBBB9FA36D") SetupConfiguration; +#endif + +// Function declarations +// +/// +/// Gets an that provides information about +/// product instances installed on the machine. +/// +/// The that +/// provides information about product instances installed on the +/// machine. +/// Reserved for future use. +/// Standard HRESULT indicating success or failure. +STDMETHODIMP GetSetupConfiguration(_Out_ ISetupConfiguration **ppConfiguration, + _Reserved_ LPVOID pReserved); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/src/tcompiler.cpp b/src/tcompiler.cpp index e1fd79c4..d7c6762c 100644 --- a/src/tcompiler.cpp +++ b/src/tcompiler.cpp @@ -2918,7 +2918,7 @@ static int terra_disassemble(lua_State * L) { return 0; } -static bool FindLinker(terra_State * T, LLVM_PATH_TYPE * linker) { +static bool FindLinker(terra_State * T, LLVM_PATH_TYPE * linker, const char* target) { #ifndef _WIN32 #if LLVM_VERSION >= 36 *linker = *sys::findProgramByName("gcc"); @@ -2933,7 +2933,8 @@ static bool FindLinker(terra_State * T, LLVM_PATH_TYPE * linker) { #else lua_getfield(T->L, LUA_GLOBALSINDEX, "terra"); lua_getfield(T->L, -1, "getvclinker"); - lua_call(T->L, 0, 3); + lua_pushstring(T->L, target); + lua_call(T->L, 1, 3); *linker = LLVM_PATH_TYPE(lua_tostring(T->L,-3)); const char * vclib = lua_tostring(T->L,-2); const char * vcpath = lua_tostring(T->L,-1); @@ -2962,7 +2963,9 @@ static bool SaveAndLink(TerraCompilationUnit * CU, Module * M, std::vectorT,&linker)) { + std::string arch(CU->TT->Triple); + arch.erase(arch.find_first_of('-')); + if(FindLinker(CU->T,&linker, arch.c_str())) { unlink(tmpnamebuf); terra_pusherror(CU->T,"llvm: failed to find linker"); return true; diff --git a/src/terra.cpp b/src/terra.cpp index ee0c6320..34975e88 100644 --- a/src/terra.cpp +++ b/src/terra.cpp @@ -21,8 +21,18 @@ #include #else #define NOMINMAX +#include #include #include +#include +#include "MSVCSetupAPI.h" + +_COM_SMARTPTR_TYPEDEF(ISetupConfiguration, __uuidof(ISetupConfiguration)); +_COM_SMARTPTR_TYPEDEF(ISetupConfiguration2, __uuidof(ISetupConfiguration2)); +_COM_SMARTPTR_TYPEDEF(ISetupHelper, __uuidof(ISetupHelper)); +_COM_SMARTPTR_TYPEDEF(IEnumSetupInstances, __uuidof(IEnumSetupInstances)); +_COM_SMARTPTR_TYPEDEF(ISetupInstance, __uuidof(ISetupInstance)); +_COM_SMARTPTR_TYPEDEF(ISetupInstance2, __uuidof(ISetupInstance2)); #endif static char * vstringf(const char * fmt, va_list ap) { @@ -207,6 +217,198 @@ static bool pushterrahome(lua_State * L) { return true; } +// On windows, gets the value of a given registry key in HKEY_LOCAL_MACHINE, or returns nil otherwise. +static int query_reg_value(lua_State* L) +{ + if(lua_gettop(L) != 2) + lua_pushnil(L); + else + { + auto key = luaL_checkstring(L, 1); + auto value = luaL_checkstring(L, 2); + + HKEY pkey; + if(!key || !value || RegOpenKeyExA(HKEY_LOCAL_MACHINE, key, 0, KEY_QUERY_VALUE | KEY_WOW64_32KEY | KEY_ENUMERATE_SUB_KEYS, &pkey) != S_OK) + lua_pushnil(L); + else + { + DWORD type = 0; + DWORD sz = 0; + LSTATUS r = RegQueryValueExA(pkey, value, 0, &type, 0, &sz); + if(r != S_OK) + lua_pushnil(L); + else + { + BYTE* data = (BYTE*)malloc(sz + 1); + r = RegQueryValueExA(pkey, value, 0, &type, data, &sz); + data[sz] = 0; + if(!data || r != S_OK) + lua_pushnil(L); + else + { + switch(type) + { + case REG_BINARY: + lua_pushlstring(L, (const char*)data, sz); + break; + case REG_DWORD: + lua_pushinteger(L, *(int32_t*)data); + break; + case REG_QWORD: + lua_pushinteger(L, *(int64_t*)data); // This doesn't really work but we attempt it anyway + break; + case REG_EXPAND_SZ: { + sz = ExpandEnvironmentStringsA((const char*)data, 0, 0); + char* buf = (char*)malloc(sz); + ExpandEnvironmentStringsA((const char*)data, buf, sz); + lua_pushstring(L, (const char*)buf); + free(buf); + } break; + case REG_MULTI_SZ: + case REG_SZ: + lua_pushstring(L, (const char*)data); + break; + } + } + if(data) + free(data); + } + + RegCloseKey(pkey); + } + } + return 1; +} + +struct InitializeCOMRAII +{ + InitializeCOMRAII() { ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); } + ~InitializeCOMRAII() { ::CoUninitialize(); } +}; + +// Based on clang's MSVC.cpp file +static bool findVCToolChainViaSetupConfig(lua_State* L) +{ + InitializeCOMRAII com; + HRESULT HR; + + ISetupConfigurationPtr Query; + HR = Query.CreateInstance(__uuidof(SetupConfiguration)); + if(FAILED(HR)) + return false; + + IEnumSetupInstancesPtr EnumInstances; + HR = ISetupConfiguration2Ptr(Query)->EnumAllInstances(&EnumInstances); + if(FAILED(HR)) + return false; + + ISetupInstancePtr Instance; + HR = EnumInstances->Next(1, &Instance, nullptr); + if(HR != S_OK) + return false; + + ISetupInstancePtr NewestInstance; + uint64_t NewestVersionNum = 0; + do + { + bstr_t VersionString; + uint64_t VersionNum; + HR = Instance->GetInstallationVersion(VersionString.GetAddress()); + if(FAILED(HR)) + continue; + HR = ISetupHelperPtr(Query)->ParseVersion(VersionString, &VersionNum); + if(FAILED(HR)) + continue; + if(!NewestVersionNum || (VersionNum > NewestVersionNum)) + { + NewestInstance = Instance; + NewestVersionNum = VersionNum; + } + } while((HR = EnumInstances->Next(1, &Instance, nullptr)) == S_OK); + + if(!NewestInstance) + return false; + + bstr_t VCPathWide; + HR = NewestInstance->ResolvePath(L"VC", VCPathWide.GetAddress()); + if(FAILED(HR)) + return false; + + std::wstring ToolsVersionFilePath(VCPathWide); + ToolsVersionFilePath += L"\\Auxiliary\\Build\\Microsoft.VCToolsVersion.default.txt"; + + FILE* f = nullptr; + auto open_result = _wfopen_s(&f, ToolsVersionFilePath.c_str(), L"rt"); + if(open_result != 0 || !f) + return false; + fseek(f, 0, SEEK_END); + size_t tools_file_size = ftell(f); + fseek(f, 0, SEEK_SET); + std::string version; + version.resize(tools_file_size); + auto retval = fgets((char*)version.data(), version.size(), f); + fclose(f); + + if(!retval) + return false; + + while(!version.back()) // strip trailing null terminators so whitespace removal works + version.pop_back(); + version.erase(version.find_last_not_of(" \n\r\t\v") + 1); + + std::string ToolchainPath; + ToolchainPath.resize(WideCharToMultiByte(CP_UTF8, 0, VCPathWide, VCPathWide.length(), 0, 0, NULL, NULL)); + ToolchainPath.resize(WideCharToMultiByte(CP_UTF8, 0, VCPathWide, VCPathWide.length(), (char*)ToolchainPath.data(), ToolchainPath.capacity(), NULL, NULL)); + ToolchainPath += "\\Tools\\MSVC\\"; + ToolchainPath += version; + lua_pushstring(L, ToolchainPath.c_str()); + return true; +} + +static int find_visual_studio_toolchain(lua_State* L) +{ + if(!findVCToolChainViaSetupConfig(L)) + lua_pushnil(L); + return 1; +} + +static int list_subdirectories(lua_State* L) +{ + if(lua_gettop(L) != 1) + lua_pushnil(L); + else + { + std::string path = luaL_checkstring(L, 1); + if(path.back() != '\\') + path += '\\'; + path += '*'; + + WIN32_FIND_DATAA find_data; + auto handle = FindFirstFileA(path.c_str(), &find_data); + if(handle == INVALID_HANDLE_VALUE) + lua_pushnil(L); + else + { + lua_newtable(L); + int i = 0; + BOOL success = TRUE; + while(success) + { + if((find_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) && (find_data.cFileName[0] != '.')) + { + lua_pushnumber(L, ++i); // Make sure we start at 1 + lua_pushstring(L, find_data.cFileName); + lua_settable(L, -3); + } + + success = FindNextFileA(handle, &find_data); + } + + FindClose(handle); + } + } + return 1; +} #endif static void setterrahome(lua_State * L) { @@ -245,6 +447,19 @@ int terra_initwithoptions(lua_State * L, terra_Options * options) { if(err) { return err; } + +#ifdef _WIN32 + // These functions are required by terralib.lua + lua_getfield(T->L, LUA_GLOBALSINDEX, "terra"); + lua_pushcfunction(T->L, query_reg_value); + lua_setfield(T->L, -2, "queryregvalue"); + lua_pushcfunction(T->L, find_visual_studio_toolchain); + lua_setfield(T->L, -2, "findvisualstudiotoolchain"); + lua_pushcfunction(T->L, list_subdirectories); + lua_setfield(T->L, -2, "listsubdirectories"); + lua_pop(T->L, 1); //'terra' global +#endif + err = terra_loadandrunbytecodes(T->L,(const unsigned char*)luaJIT_BC_strict,luaJIT_BC_strict_SIZE, "strict.lua") || terra_loadandrunbytecodes(T->L,(const unsigned char*)luaJIT_BC_terralist,luaJIT_BC_terralist_SIZE, "terralist.lua") || terra_loadandrunbytecodes(T->L,(const unsigned char*)luaJIT_BC_asdl,luaJIT_BC_asdl_SIZE, "asdl.lua") diff --git a/src/terralib.lua b/src/terralib.lua index dbbf8130..32e39beb 100644 --- a/src/terralib.lua +++ b/src/terralib.lua @@ -4043,12 +4043,16 @@ end -- configure path variables -terra.cudahome = os.getenv("CUDA_HOME") or (ffi.os == "Windows" and os.getenv("CUDA_PATH")) or "/usr/local/cuda" +if ffi.os == "Windows" then + terra.cudahome = os.getenv("CUDA_PATH") +else + terra.cudahome = os.getenv("CUDA_HOME") or "/usr/local/cuda" +end terra.cudalibpaths = ({ OSX = {driver = "/usr/local/cuda/lib/libcuda.dylib", runtime = "$CUDA_HOME/lib/libcudart.dylib", nvvm = "$CUDA_HOME/nvvm/lib/libnvvm.dylib"}; Linux = {driver = "libcuda.so", runtime = "$CUDA_HOME/lib64/libcudart.so", nvvm = "$CUDA_HOME/nvvm/lib64/libnvvm.so"}; Windows = {driver = "nvcuda.dll", runtime = "$CUDA_HOME\\bin\\cudart64_*.dll", nvvm = "$CUDA_HOME\\nvvm\\bin\\nvvm64_*.dll"}; })[ffi.os] -- OS's that are not supported by CUDA will have an undefined value here -if terra.cudalibpaths then +if terra.cudalibpaths and terra.cudahome then for name,path in pairs(terra.cudalibpaths) do path = path:gsub("%$CUDA_HOME",terra.cudahome) if path:match("%*") and ffi.os == "Windows" then @@ -4074,39 +4078,127 @@ if ffi.os == "Windows" then end terra.includepath = os.getenv("INCLUDE") - function terra.getvclinker() + function terra.getvclinker(target) local vclib = os.getenv("LIB") local vcpath = terra.vcpath or os.getenv("Path") vclib,vcpath = "LIB="..vclib,"Path="..vcpath return terra.vclinker,vclib,vcpath end else - -- this is the reason we can't have nice things - local function registrystring(key,value,default) - local F = io.popen( ([[reg query "%s" /v "%s"]]):format(key,value) ) - local result = F and F:read("*all"):match("REG_SZ%W*([^\n]*)\n") - return result or default + local function compareversion(form, a, b) + if (a == nil) or (b == nil) then return true end + + local alist = {} + for e in string.gmatch(a, form) do table.insert(alist, tonumber(e)) end + local blist = {} + for e in string.gmatch(b, form) do table.insert(blist, tonumber(e)) end + + for i=1,#alist do + if alist[i] ~= blist[i] then + return alist[i] > blist[i] + end + end + return false end - terra.vshome = registrystring([[HKLM\Software\WOW6432Node\Microsoft\VisualStudio\12.0]],"ShellFolder",[[C:\Program Files (x86)\Microsoft Visual Studio 12.0\]]) - local windowsdk = registrystring([[HKLM\SOFTWARE\Wow6432Node\Microsoft\Microsoft SDKs\Windows\v8.1]],"InstallationFolder",[[C:\Program Files (x86)\Windows Kits\8.1\]]) - + + -- First find the latest Windows SDK installed using the registry + local installedroots = [[SOFTWARE\Microsoft\Windows Kits\Installed Roots]] + local windowsdk = terra.queryregvalue(installedroots, "KitsRoot10") + if windowsdk == nil then + windowsdk = terra.queryregvalue(installedroots, "KitsRoot81") + if windowsdk == nil then + error "Can't find windows SDK version 8.1 or 10! Try running Terra in a Native Tools Developer Console instead." + end + + terra.systemincludes:insertall{ "shared", "um", "winrt" } + terra.systemincludes = terra.systemincludes:map(function(e) return windowsdk..[[include\]]..e end) + + local version = nil + for i, v in ipairs(terra.listsubdirectories(windowsdk .. "lib")) do + if compareversion("%d+", v, version) then + version = v + end + end + if version == nil then + error "Can't find valid version subdirectory in the SDK! Is the Windows 8.1 SDK installation corrupt?" + end + + terra.sdklib = windowsdk .. "lib\\" .. version + else + -- Find highest version. For sanity reasons, we assume the same version folders are in both lib/ and include/ + local version = nil + for i, v in ipairs(terra.listsubdirectories(windowsdk .. "include")) do + if compareversion("%d+", v, version) then + version = v + end + end + if version == nil then + error "Can't find valid version subdirectory in the SDK! Is the SDK installation corrupt?" + end + + terra.systemincludes:insertall{ "ucrt", "shared", "um", "winrt" } + terra.systemincludes = terra.systemincludes:map(function(e) return windowsdk..version..[[\include\]]..e end) + terra.sdklib = windowsdk .. "lib\\" .. version + end + + terra.vshome = terra.findvisualstudiotoolchain() + if terra.vshome == nil then + terra.vshome = terra.queryregvalue([[SOFTWARE\WOW6432Node\Microsoft\VisualStudio\14.0]], "ShellFolder") or + terra.queryregvalue([[SOFTWARE\WOW6432Node\Microsoft\VisualStudio\12.0]], "ShellFolder") or + terra.queryregvalue([[SOFTWARE\WOW6432Node\Microsoft\VisualStudio\11.0]], "ShellFolder") or + terra.queryregvalue([[SOFTWARE\WOW6432Node\Microsoft\VisualStudio\10.0]], "ShellFolder") + + if terra.vshome == nil then + error "Can't find Visual Studio either via COM or the registry! Try running Terra in a Native Tools Developer Console instead." + end + terra.vshome = terra.vshome .. "VC\\" + terra.vsarch64 = "amd64" -- Before 2017, Visual Studio had it's own special architecture convention, because who needs standards + terra.vslinkpath = function(host, target) + if string.lower(host) == string.lower(target) then + return ([[BIN\%s\]]):format(host) + else + return ([[BIN\%s_%s\]]):format(host, target) + end + end + else + if terra.vshome[#terra.vshome] ~= '\\' then + terra.vshome = terra.vshome .. "\\" + end + terra.vsarch64 = "x64" + terra.vslinkpath = function(host, target) return ([[bin\Host%s\%s\]]):format(host, target) end + end + terra.systemincludes:insertall { - ("%sVC/INCLUDE"):format(terra.vshome), - ("%sVC/ATLMFC/INCLUDE"):format(terra.vshome), - ("%sinclude/shared"):format(windowsdk), - ("%sinclude/um"):format(windowsdk), - ("%sinclude/winrt"):format(windowsdk), - ("%s/include"):format(terra.cudahome) + ([[%sINCLUDE]]):format(terra.vshome), + ([[%sATLMFC\INCLUDE]]):format(terra.vshome) } - - function terra.getvclinker() --get the linker, and guess the needed environment variables for Windows if they are not set ... - local linker = terra.vshome..[[VC\BIN\x86_amd64\link.exe]] - local vclib = terra.vclib or string.gsub([[%VC\LIB\amd64;%VC\ATLMFC\LIB\amd64;C:\Program Files (x86)\Windows Kits\8.1\lib\winv6.3\um\x64;]],"%%",terra.vshome) - local vcpath = terra.vcpath or (os.getenv("Path") or "")..";"..terra.vshome..[[VC\BIN;]] + + function terra.getvclinker(target) --get the linker, and guess the needed environment variables for Windows if they are not set ... + target = target or "x86_64" + local winarch = target + if target == "x86_64" then + target = terra.vsarch64 + winarch = "x64" -- Unbelievably, Visual Studio didn't follow the Windows SDK convention until 2017+ + elseif target == "aarch64" or target == "aarch64_be" then + target = "arm64" + winarch = "arm64" + end + + local host = ffi.arch + if host == "x64" then + host = terra.vsarch64 + end + + local linker = terra.vshome..terra.vslinkpath(host, target).."link.exe" + local vclib = ([[%s\um\%s;%s\ucrt\%s;]]):format(terra.sdklib, winarch, terra.sdklib, winarch) .. ([[%sLIB\%s;%sATLMFC\LIB\%s;]]):format(terra.vshome, target, terra.vshome, target) + local vcpath = terra.vcpath or (os.getenv("Path") or "")..";"..terra.vshome..[[BIN;]]..terra.vshome..terra.vslinkpath(host, host)..";" -- deals with VS2017 cross-compile nonsense: https://github.com/rust-lang/rust/issues/31063 vclib,vcpath = "LIB="..vclib,"Path="..vcpath return linker,vclib,vcpath end end + if terra.cudahome then + terra.systemincludes:insertall{terra.cudahome.."\\include"} + end end