Exemplo n.º 1
0
                    /// <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);
        }