/// <summary> /// Gets a case-insensitive dictionary containing the /// correct environmental variables for the supplied tool /// version and target architecture. /// </summary> /// <param name="version"> /// The toolset version to fetch the environment for. /// </param> /// <param name="arch"> /// The architecture to set when fetching the environment. /// </param> /// <returns> /// A case-insensitive dictionary containing the correct /// environmental variables for the supplied tool version /// and target architecture. In the event that the requested /// version is not found, returns an empty collection. /// </returns> /// <exception cref="ArgumentException"> /// The arch parameter must have one and only one arch flag /// set. In the event that this is not the case, this method /// will throw/ /// </exception> public static Dictionary <string, string> GetEnvironmentForVersion(ToolVersions version, Architecture arch) { var installedVersions = InstalledToolVersions; if (!installedVersions.ContainsKey(version)) { // Requested version not found. return(new Dictionary <string, string>()); } var clBinPath = installedVersions[version]; // Ensure we only have one flag for arch. int setFlags = 0; foreach (Architecture a in Enum.GetValues(typeof(Architecture))) { if (arch.HasFlag(a)) { ++setFlags; } } Debug.Assert(setFlags == 1, "One and only one Architecture flags must be set."); if (setFlags != 1) { throw new ArgumentException("Must have one and only one flag set.", nameof(arch)); } // Grab the current environmental variables. var currentEnv = Environment.GetEnvironmentVariables(); // Move then into an i-case dictionary. var currentEnvDictionary = new Dictionary <string, string>(StringComparer.OrdinalIgnoreCase); foreach (var key in currentEnv.Keys) { currentEnvDictionary.Add((string)key, (string)currentEnv[key]); } StringBuilder stdOutput = new StringBuilder(); StringBuilder stdErrout = new StringBuilder(); // Set it up so we pipe all err and stdout back to us. DataReceivedEventHandler stdOut = ((object sender, DataReceivedEventArgs e) => { stdOutput.AppendLine(e.Data); }); DataReceivedEventHandler stdErr = ((object sender, DataReceivedEventArgs e) => { stdErrout.AppendLine(e.Data); }); // So what we're doing here is we're taking the input tool bin directory, going // up one level. This puts us in the MSVC root directory, where the batch file // used to setup the Visual Studio dev environment always resides. So our command // then invokes this script with the correct target ach params, and calls SET, which // dumps the new/updated environmental variables back out the console standard out, // which we're capturing with our lambda delegates above. var parentDir = Directory.GetParent(clBinPath); var pathToVcars = parentDir.FullName.ConvertToHostOsPath() + Path.DirectorySeparatorChar + "vcvarsall.bat"; var archString = string.Empty; // XXX TODO - Need to support cross compiler targets as well. For example, // x86_amd64 to invoke the 32 bit compiler in a way that it outputs 64 bit // binaries, etc. switch (arch) { case Architecture.x86: { archString = "x86"; } break; case Architecture.x64: { archString = "x64"; } break; } // Example of the call string expanded/populated: // call "C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\vcvarsall.bat" amd64 && SET string callArgs = string.Format("/C call \"{0}\" {1} && SET", pathToVcars, archString); // Once we run cmd.exe with the above args, we'll have captured all environmental // variables required to run the compiler tools manually for the target architecture. var exitCode = RunProcess(string.Empty, "cmd.exe", new List <string> { callArgs }, Timeout.Infinite, null, null, stdErr, stdOut); Debug.Assert(exitCode >= 0, string.Format("When trying to load MSVC dev environment for arch {0}, the process returned an error code.", arch)); if (exitCode < 0) { throw new ArgumentException(string.Format("When trying to load MSVC dev environment for arch {0}, the process returned an error code.", arch), nameof(arch)); } // Now we should have a bunch of VAR=VALUE params, one per line. So we'll // look for those and try to extract them when we find them. string[] delim = { "\n", "\r" }; string[] lines = stdOutput.ToString().Split(delim, StringSplitOptions.RemoveEmptyEntries); foreach (var line in lines) { var split = line.Split('='); // Ensure we have split a VAR=VALUE pair, and ensure that they're non-null/empty. // We don't actually care if the value part is empty. It's reasonable/valid // for there to simply be a key present which might have some meaning somewhere. // // XXX TODO - Ensure that keys with empty values don't get disqualified by // the split.Length == 2 qualifier. This shouldn't be the case because we // did not ask for empty results to be pruned from the split operation results. if (split.Length == 2) { if (!string.IsNullOrEmpty(split[0]) && !string.IsNullOrWhiteSpace(split[0])) { // If this variable already exists, just update/overwrite it. if (currentEnv.Contains(split[0])) { currentEnvDictionary[split[0]] = split[1]; } else { // Variable doesn't exit, so add it. currentEnvDictionary.Add(split[0], split[1]); } } } } return(currentEnvDictionary); }
public override bool Run(BuildConfiguration config, Architecture arch) { // Clear errors before trying. Errors.Clear(); if (!SupportedArchitectures.HasFlag(arch)) { Errors.Add(new Exception("Unsupported architecture specified for build task.")); return(false); } if (!ConfigureDirectories()) { Errors.Add(new Exception("Failed to configure arch specific directories for openSSL build.")); return(false); } // We need to get the environment for a the MSVC compiler and // associated build tools. var installedMsvcVersions = MSVCCompilerTask.InstalledToolVersions; if (installedMsvcVersions.Count == 0) { Errors.Add(new Exception("Could not detect a compatible installation of MSVC.")); return(false); } // Get a reversed list of tool versions and iterate over them, until we find // an installed version. This way we're always working with the latest // version available. var allVersions = Enum.GetValues(typeof(ToolVersions)).Cast <ToolVersions>().Reverse(); ToolVersions versionToUse = ToolVersions.v11; foreach (var msvcVersion in allVersions) { if (installedMsvcVersions.ContainsKey(msvcVersion)) { versionToUse = msvcVersion; WriteLineToConsole(string.Format("Discovered and using MSVC {0} for compilation.", versionToUse.ToString())); break; } } // Build out the base path to the openSSL source directory. StringBuilder opensslBasePath = new StringBuilder(WorkingDirectory); opensslBasePath.Append(Path.DirectorySeparatorChar); opensslBasePath.Append("deps"); opensslBasePath.Append(Path.DirectorySeparatorChar); opensslBasePath.Append("openssl"); int numCompilationAttempts = 0; int numSuccessfulCompilations = 0; // We're only going to iterate over arches. We're not going to build a debug // version of openSSL, just release versions for each arch. foreach (Architecture a in Enum.GetValues(typeof(Architecture))) { if (arch.HasFlag(a)) { ++numCompilationAttempts; var finalBuildEnvironment = MSVCCompilerTask.GetEnvironmentForVersion(versionToUse, a); // Add perl path if it doesn't already exist. if (finalBuildEnvironment["PATH"].IndexOf(m_perlDir) == -1) { finalBuildEnvironment["PATH"] += (Path.PathSeparator + m_perlDir); } var configArgs = new List <string>(); configArgs.Add("no-idea"); configArgs.Add("no-mdc2"); configArgs.Add("no-rc5"); configArgs.Add("no-comp"); // XXX TODO - Remove this option when upgrading to openSSL 1.1.0 configArgs.Add("no-ssl2"); configArgs.Add("no-ssl3"); configArgs.Add("no-weak-ssl-ciphers"); configArgs.Add("threads"); // The working dir. This will either be the x86 or x64 openSSL source dir. string workingDirectory = string.Empty; // We need to include nasm regardless of rater arch because // the openSSL configuration system will whine and quit if // we don't. We should be guaranteed to have a PATH variable // here unless something went horribly wrong. finalBuildEnvironment["PATH"] += (Path.PathSeparator + m_nasmDir); // XXX TODO - This needs to go away when we bump to OpenSSL 1.1.0 string whichAsmCall = string.Empty; string openSslInstallDir = string.Empty; switch (a) { case Architecture.x86: { // Build inside the x86 dir workingDirectory = m_openSslx86Dir; // Set x86 release build. configArgs.Insert(0, "VC-WIN32"); whichAsmCall = "ms" + Path.DirectorySeparatorChar + "do_nasm.bat"; openSslInstallDir = opensslBasePath.ToString().ConvertToHostOsPath() + Path.DirectorySeparatorChar + "msvc" + Path.DirectorySeparatorChar + "Releasex86"; } break; case Architecture.x64: { // Build inside the x64 dir workingDirectory = m_openSslx64Dir; whichAsmCall = "ms" + Path.DirectorySeparatorChar + "do_win64a.bat"; // Set x64 release build. configArgs.Insert(0, "VC-WIN64A"); openSslInstallDir = opensslBasePath.ToString().ConvertToHostOsPath() + Path.DirectorySeparatorChar + "msvc" + Path.DirectorySeparatorChar + "Releasex64"; } break; default: { WriteLineToConsole(string.Format("Dont have arch: {0}", a.ToString())); continue; } } // Setup prefix (output) path to deps/openssl/msvc/ReleaseX64 configArgs.Add( string.Format( "--prefix={0}", openSslInstallDir) ); // Setup config path to deps/openssl/msvc/ReleaseX86 configArgs.Add( string.Format( "--openssldir={0}", openSslInstallDir) ); WriteLineToConsole(string.Format("Configuring for arch: {0}", a.ToString())); WriteLineToConsole(workingDirectory); WriteLineToConsole(string.Format("Config Path: {0}", workingDirectory + Path.DirectorySeparatorChar + "Configure")); // Push configure script to front of args. configArgs.Insert(0, "Configure"); WriteLineToConsole(string.Join(" ", configArgs)); // Run the configuration process. var perlExitCode = RunProcess(workingDirectory, m_perlDir + Path.DirectorySeparatorChar + "perl.exe", configArgs, Timeout.Infinite, finalBuildEnvironment); // Now run the actual build process. // Example of the call string expanded/populated: // call "ms\do_nasm.bat" && nmake -f ms\ntdll.mak && nmake -f ms\ntdll.mak install string callArgs = string.Format("/C \"{0}\" && {1} && {2}", whichAsmCall, "nmake -f ms" + Path.DirectorySeparatorChar + "ntdll.mak", "nmake -f ms" + Path.DirectorySeparatorChar + "ntdll.mak install"); // XXX TODO - This is way to do it when we jump up to OpenSSL 1.1.0 //string callArgs = string.Format("/C {0} && {1}", "nmake", "nmake install"); // Running cmd.exe with these batch commands will build openSSL. var buildExitCode = RunProcess(workingDirectory, "cmd.exe", new List <string> { callArgs }, Timeout.Infinite, finalBuildEnvironment); if (perlExitCode == 0 && buildExitCode == 0) { // Was a success. Move the output folder now. var destBaseDir = opensslBasePath.ToString().ConvertToHostOsPath() + Path.DirectorySeparatorChar + "msvc" + Path.DirectorySeparatorChar; var destReleaseDir = destBaseDir + string.Format("{0} {1}", BuildConfiguration.Release.ToString(), a.ToString()); var destDebugDir = destBaseDir + string.Format("{0} {1}", BuildConfiguration.Debug.ToString(), a.ToString()); // If we don't delete old stuff, Directory.Move will fail. if (Directory.Exists(destReleaseDir)) { Directory.Delete(destReleaseDir, true); } // Move aka rename the directory to have a space. try { Directory.Move(openSslInstallDir, destReleaseDir); } catch { // Sometimes getting access denied. Perhaps parts of the build // process are still hanging. Try and give them a few seconds // to wrap up, then try again. Thread.Sleep(3000); Directory.Move(openSslInstallDir, destReleaseDir); } // Simply copy the release folder for arch to a debug folder. CopyDirectory(destReleaseDir, destDebugDir, true); ++numSuccessfulCompilations; } } } var wasSuccess = numCompilationAttempts > 0 && numCompilationAttempts == numSuccessfulCompilations; return(wasSuccess); }