// Copyright (c) 2012-2023 Flax Engine. All rights reserved. using System; using System.Collections.Generic; using System.IO; using System.Linq; using Flax.Build; using Flax.Build.Platforms; using Flax.Build.Projects.VisualStudio; using Microsoft.Win32; #pragma warning disable CA1416 namespace Flax.Deploy { /// /// Stores information about a Visual C++ installation and compile environment. /// public static class VCEnvironment { private static string _msBuildPath; private static string _cscPath; /// /// Gets the path to the MSBuild executable. /// public static string MSBuildPath { get { if (_msBuildPath == null) _msBuildPath = GetMSBuildToolPath(); return _msBuildPath; } } /// /// Gets the path to the C# compiler executable. /// public static string CscPath { get { if (_cscPath == null) _cscPath = GetCscPath(); return _cscPath; } } private static string GetMSBuildToolPath() { string toolPath; switch (Platform.BuildPlatform.Target) { case TargetPlatform.Linux: case TargetPlatform.Mac: { // Use msbuild for .NET toolPath = UnixPlatform.Which("dotnet"); if (toolPath != null) { return toolPath + " msbuild"; } // Use msbuild from Mono toolPath = UnixPlatform.Which("msbuild"); if (toolPath != null) { return toolPath; } break; } case TargetPlatform.Windows: { var visualStudioInstances = VisualStudioInstance.GetInstances(); foreach (var visualStudioInstance in visualStudioInstances) { toolPath = Path.Combine(visualStudioInstance.Path, "MSBuild\\Current\\Bin\\MSBuild.exe"); if (File.Exists(toolPath)) { return toolPath; } } if (CheckMsBuildPathFromRegistry("Microsoft\\VisualStudio\\SxS\\VS7", "15.0", "MSBuild\\15.0\\bin\\MSBuild.exe", out toolPath)) { return toolPath; } toolPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86), "MSBuild", "14.0", "bin", "MSBuild.exe"); if (File.Exists(toolPath)) { return toolPath; } if (CheckMsBuildPathFromRegistry("Microsoft\\MSBuild\\ToolsVersions\\14.0", "MSBuildToolsPath", "MSBuild.exe", out toolPath)) { return toolPath; } if (CheckMsBuildPathFromRegistry("Microsoft\\MSBuild\\ToolsVersions\\12.0", "MSBuildToolsPath", "MSBuild.exe", out toolPath)) { return toolPath; } if (CheckMsBuildPathFromRegistry("Microsoft\\MSBuild\\ToolsVersions\\4.0", "MSBuildToolsPath", "MSBuild.exe", out toolPath)) { return toolPath; } break; } } return string.Empty; } private static string GetCscPath() { string toolPath; switch (Platform.BuildPlatform.Target) { case TargetPlatform.Linux: case TargetPlatform.Mac: { // Use csc from Mono toolPath = UnixPlatform.Which("csc"); if (toolPath != null) { return toolPath; } break; } case TargetPlatform.Windows: { if (CheckMsBuildPathFromRegistry("Microsoft\\VisualStudio\\SxS\\VS7", "15.0", "MSBuild\\15.0\\bin\\csc.exe", out toolPath)) { return toolPath; } toolPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86), "MSBuild", "14.0", "bin", "csc.exe"); if (File.Exists(toolPath)) { return toolPath; } if (CheckMsBuildPathFromRegistry("Microsoft\\MSBuild\\ToolsVersions\\14.0", "MSBuildToolsPath", "csc.exe", out toolPath)) { return toolPath; } if (CheckMsBuildPathFromRegistry("Microsoft\\MSBuild\\ToolsVersions\\12.0", "MSBuildToolsPath", "csc.exe", out toolPath)) { return toolPath; } if (CheckMsBuildPathFromRegistry("Microsoft\\MSBuild\\ToolsVersions\\4.0", "MSBuildToolsPath", "csc.exe", out toolPath)) { return toolPath; } break; } } return string.Empty; } private static bool CheckMsBuildPathFromRegistry(string keyRelativePath, string keyName, string msBuildRelativePath, out string outMsBuildPath) { string[] prefixes = { @"HKEY_CURRENT_USER\SOFTWARE\", @"HKEY_LOCAL_MACHINE\SOFTWARE\", @"HKEY_CURRENT_USER\SOFTWARE\Wow6432Node\", @"HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\" }; for (var i = 0; i < prefixes.Length; i++) { if (Registry.GetValue(prefixes[i] + keyRelativePath, keyName, null) is string value) { string msBuildPath = Path.Combine(value, msBuildRelativePath); if (File.Exists(msBuildPath)) { outMsBuildPath = msBuildPath; return true; } } } outMsBuildPath = null; return false; } internal static string Verbosity => Configuration.Verbose ? "" : "/verbosity:minimal"; /// /// Runs msbuild.exe with the specified arguments. /// /// Path to the project to build. /// Configuration to build. /// Platform to build. public static void BuildProject(string project, string buildConfig, string buildPlatform) { var msBuild = MSBuildPath; if (string.IsNullOrEmpty(msBuild)) { throw new Exception(string.Format("Unable to find msbuild.exe at: \"{0}\"", msBuild)); } if (!File.Exists(project)) { throw new Exception(string.Format("Project {0} does not exist!", project)); } string cmdLine = string.Format("\"{0}\" /m /t:Restore,Build /p:Configuration=\"{1}\" /p:Platform=\"{2}\" {3} /nologo", project, buildConfig, buildPlatform, Verbosity); int result = Utilities.Run(msBuild, cmdLine); if (result != 0) { throw new Exception(string.Format("Unable to build project {0}. MSBuild failed. See log to learn more.", project)); } } /// /// Builds a Visual Studio solution with MsBuild. /// /// Path to the solution file /// Configuration to build. /// Platform to build. /// Custom build properties mapping (property=value). /// Custom MSBuild executable path. public static void BuildSolution(string solutionFile, string buildConfig, string buildPlatform, Dictionary props = null, string msBuild = null) { if (msBuild == null) msBuild = MSBuildPath; if (string.IsNullOrEmpty(msBuild)) { throw new Exception(string.Format("Unable to find msbuild.exe at: \"{0}\"", msBuild)); } if (!File.Exists(solutionFile)) { throw new Exception(string.Format("Unable to build solution {0}. Solution file not found.", solutionFile)); } string cmdLine = string.Format("\"{0}\" /m /t:Restore,Build /p:Configuration=\"{1}\" /p:Platform=\"{2}\" {3} /nologo", solutionFile, buildConfig, buildPlatform, Verbosity); if (props != null) { foreach (var e in props) cmdLine += string.Format(" /p:{0}={1}", e.Key, e.Value); } int result = Utilities.Run(msBuild, cmdLine); if (result != 0) { throw new Exception(string.Format("Unable to build solution {0}. MSBuild failed. See log to learn more.", solutionFile)); } } /// /// Clears the build a Visual Studio solution cache with MsBuild. /// /// Path to the solution file public static void CleanSolution(string solutionFile) { var msBuild = MSBuildPath; if (string.IsNullOrEmpty(msBuild)) { throw new Exception(string.Format("Unable to find msbuild.exe at: \"{0}\"", msBuild)); } if (!File.Exists(solutionFile)) { throw new Exception(string.Format("Unable to clean solution {0}. Solution file not found.", solutionFile)); } string cmdLine = string.Format("\"{0}\" /t:Clean {1} /nologo", solutionFile, Verbosity); Utilities.Run(msBuild, cmdLine); } internal static void CodeSign(string file, string certificatePath, string certificatePass) { if (!File.Exists(file)) throw new FileNotFoundException("Missing file to sign.", file); if (!File.Exists(certificatePath)) throw new FileNotFoundException("Missing certificate to sign with.", certificatePath); var sdks = WindowsPlatformBase.GetSDKs(); if (sdks.Count == 0) throw new Exception("No Windows SDK found. Cannot sign file."); var sdkKeys = sdks.Keys.ToList(); sdkKeys.Sort(); var sdk = sdks[sdkKeys.Last()]; var signtool = Path.Combine(sdk, "bin", "x64", "signtool.exe"); var cmdLine = string.Format("sign /debug /f \"{0}\" /p \"{1}\" /tr http://timestamp.comodoca.com /td sha256 /fd sha256 \"{2}\"", certificatePath, certificatePass, file); Utilities.Run(signtool, cmdLine, null, null, Utilities.RunOptions.Default | Utilities.RunOptions.ThrowExceptionOnError); } } } #pragma warning restore CA1416