public static Task <ExitCode> ExecuteAsync( string executePath, IEnumerable <string> arguments = null, ILogger logger = null, IEnumerable <KeyValuePair <string, string> > environmentVariables = null, CancellationToken cancellationToken = default, bool addProcessNameAsLogCategory = false, bool addProcessRunnerCategory = false, string parentPrefix = null) { ILogger usedLogger = logger ?? new NullLogger(); string executingCategory = $"[{Path.GetFileNameWithoutExtension(Path.GetFileName(executePath))}]"; return(ProcessRunner.ExecuteAsync( executePath, cancellationToken, arguments, (message, category) => usedLogger.Write(message, executingCategory), usedLogger.WriteError, verboseAction: usedLogger.WriteVerbose, toolAction: (message, category) => usedLogger.Write(message, ToolName), environmentVariables: environmentVariables, debugAction: usedLogger.WriteDebug, addProcessNameAsLogCategory: addProcessNameAsLogCategory, addProcessRunnerCategory: addProcessRunnerCategory, parentPrefix: parentPrefix)); }
private async Task <bool> IsCurrentDirectoryClonableAsync() { if (!_directoryCloneEnabled) { _logger.WriteVerbose("Directory clone is disabled"); return(false); } _logger.WriteVerbose("Directory clone is enabled"); string sourceRoot = VcsPathHelper.TryFindVcsRootPath(); if (string.IsNullOrWhiteSpace(sourceRoot)) { _logger.WriteWarning("Could not find source root", _Prefix); return(false); } bool isClonable = false; string gitExePath = GitHelper.GetGitExePath(_logger); if (!string.IsNullOrWhiteSpace(gitExePath)) { string gitDir = Path.Combine(sourceRoot, ".git"); var statusAllArguments = new[] { $"--git-dir={gitDir}", $"--work-tree={sourceRoot}", "status" }; var argumentVariants = new List <string[]> { new[] { "status" }, statusAllArguments }; foreach (string[] argumentVariant in argumentVariants) { ExitCode statusExitCode = await ProcessRunner.ExecuteAsync( gitExePath, arguments : argumentVariant, standardOutLog : _logger.WriteVerbose, standardErrorAction : _logger.WriteVerbose, toolAction : _logger.Write, verboseAction : _logger.WriteVerbose); if (statusExitCode.IsSuccess) { isClonable = true; break; } } } _logger.WriteVerbose($"Is directory clonable: {isClonable}"); return(isClonable); }
private async Task <ExitCode> RunVsTestAsync( ILogger logger, string vsTestReportDirectoryPath, string vsTestExePath, bool runTestsInReleaseConfiguration, string assemblyFilePrefix) { Type testClassAttribute = typeof(TestClassAttribute); Type testMethodAttribute = typeof(TestMethodAttribute); var directory = new DirectoryInfo(_sourceRoot); var typesToFind = new List <Type> { testClassAttribute, testMethodAttribute }; List <string> vsTestConsoleArguments = new UnitTestFinder(typesToFind).GetUnitTestFixtureDlls(directory, runTestsInReleaseConfiguration, assemblyFilePrefix, FrameworkConstants.NetFramework) .ToList(); if (!vsTestConsoleArguments.Any()) { logger.WriteWarning( $"Could not find any VSTest tests in directory '{directory.FullName}' or any sub-directory"); return(ExitCode.Success); } IEnumerable <string> options = GetVsTestConsoleOptions(); vsTestConsoleArguments.AddRange(options); EnsureTestReportDirectoryExists(vsTestReportDirectoryPath); string oldCurrentDirectory = SaveCurrentDirectory(); SetCurrentDirectory(vsTestReportDirectoryPath); LogExecution(logger, vsTestConsoleArguments, vsTestExePath); try { Task <ExitCode> execute = ProcessRunner.ExecuteAsync( vsTestExePath, arguments: vsTestConsoleArguments, standardOutLog: logger.Write, standardErrorAction: logger.WriteError, toolAction: logger.Write); ExitCode result = await execute; return(result); } finally { RestoreCurrentDirectory(oldCurrentDirectory); } }
private async Task <ExitCode> RunBuildToolsAsync(string buildDir, string buildToolDirectoryName) { string buildToolDirectoryPath = Path.Combine(buildDir, buildToolDirectoryName); var buildToolDirectory = new DirectoryInfo(buildToolDirectoryPath); List <FileInfo> exeFiles = buildToolDirectory.GetFiles("*.exe", SearchOption.TopDirectoryOnly) .Where(file => !file.Name.Equals("nuget.exe", StringComparison.InvariantCultureIgnoreCase)) .ToList(); if (exeFiles.Count != 1) { PrintInvalidExeFileCount(exeFiles, buildToolDirectoryPath); return(ExitCode.Failure); } FileInfo buildToolExe = exeFiles.Single(); string timeoutKey = WellKnownVariables.BuildToolTimeoutInSeconds; string timeoutInSecondsFromEnvironment = Environment.GetEnvironmentVariable(timeoutKey); ParseResult <int> parseResult = timeoutInSecondsFromEnvironment.TryParseInt32(MaxBuildTimeInSeconds); if (parseResult.Parsed) { _logger.WriteVerbose($"Using timeout from environment variable {timeoutKey}", _Prefix); } int usedTimeoutInSeconds = parseResult; _logger.Write($"Using build timeout {usedTimeoutInSeconds} seconds", _Prefix); var cancellationTokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(usedTimeoutInSeconds)); const string buildApplicationPrefix = "[Arbor.X] "; IEnumerable <string> arguments = Enumerable.Empty <string>(); ExitCode result = await ProcessRunner.ExecuteAsync( buildToolExe.FullName, cancellationTokenSource.Token, arguments, (message, prefix) => _logger.Write(message, buildApplicationPrefix), (message, prefix) => _logger.WriteError(message, buildApplicationPrefix), _logger.Write, _logger.WriteVerbose); return(result); }
private static async Task <ExitCode> UploadNugetPackageAsync( string nugetExePath, string symbolServerUrl, string apiKey, string nugetPackage, ILogger logger, int timeout) { var args = new List <string> { "push", nugetPackage, "-source", symbolServerUrl, apiKey, "-verbosity", "detailed" }; if (timeout > 0) { args.Add("-timeout"); args.Add(timeout.ToString(CultureInfo.InvariantCulture)); } ExitCode exitCode = await ProcessRunner.ExecuteAsync( nugetExePath, arguments : args, standardOutLog : logger.Write, standardErrorAction : logger.WriteError, toolAction : logger.Write); return(exitCode); }
private async Task <ExitCode> RunNUnitAsync( IVariable externalTools, ILogger logger, IVariable reportPath, bool runTestsInReleaseConfiguration, string assemblyFilePrefix) { Type fixtureAttribute = typeof(TestFixtureAttribute); Type testMethodAttribute = typeof(TestAttribute); var directory = new DirectoryInfo(_sourceRoot); var typesToFind = new List <Type> { fixtureAttribute, testMethodAttribute }; Stopwatch stopwatch = Stopwatch.StartNew(); List <string> testDlls = new UnitTestFinder(typesToFind) .GetUnitTestFixtureDlls(directory, runTestsInReleaseConfiguration, assemblyFilePrefix, FrameworkConstants.NetFramework) .ToList(); stopwatch.Stop(); logger.Write($"NUnit test assembly lookup took {stopwatch.ElapsedMilliseconds:F2} milliseconds"); if (!testDlls.Any()) { logger.WriteWarning( $"Could not find any NUnit tests in directory '{directory.FullName}' or any sub-directory"); return(ExitCode.Success); } string nunitExePath = GetNunitExePath(externalTools); var results = new List <Tuple <string, ExitCode> >(); foreach (string testDll in testDlls) { var nunitConsoleArguments = new List <string> { testDll }; string reportFilePath = GetNUnitXmlReportFilePath(reportPath, testDll); EnsureNUnitReportDirectoryExists(reportFilePath); IEnumerable <string> options = GetNUnitConsoleOptions(externalTools, reportFilePath); nunitConsoleArguments.AddRange(options); LogExecution(logger, nunitConsoleArguments, nunitExePath); Stopwatch executionStopwatch = Stopwatch.StartNew(); ExitCode result = await ProcessRunner.ExecuteAsync( nunitExePath, arguments : nunitConsoleArguments, standardOutLog : logger.Write, standardErrorAction : logger.WriteError, toolAction : logger.Write); executionStopwatch.Stop(); logger.Write($"NUnit execution took {executionStopwatch.ElapsedMilliseconds:F2} milliseconds"); results.Add(Tuple.Create(testDll, result)); } if (results.All(result => result.Item2.IsSuccess)) { return(ExitCode.Success); } var failedTestsBuilder = new StringBuilder(); failedTestsBuilder.AppendLine("The following DLL files were not tested successfully:"); foreach (Tuple <string, ExitCode> result in results.Where(r => !r.Item2.IsSuccess)) { failedTestsBuilder.AppendLine(result.Item1); } logger.WriteError(failedTestsBuilder.ToString()); return(ExitCode.Failure); }
public async Task <ExitCode> ExecuteAsync( ILogger logger, IReadOnlyCollection <IVariable> buildVariables, CancellationToken cancellationToken) { _logger = logger; ParseResult <bool> parseResult = buildVariables .GetVariableValueOrDefault(WellKnownVariables.ExternalTools_LibZ_Enabled, "false") .TryParseBool(false); if (!parseResult.Value) { _logger.Write( $"LibZPacker is disabled, to enable it, set the flag {WellKnownVariables.ExternalTools_LibZ_Enabled} to true"); return(ExitCode.Success); } _exePath = buildVariables.Require(WellKnownVariables.ExternalTools_LibZ_ExePath) .ThrowIfEmptyValue() .Value; string customExePath = buildVariables.GetVariableValueOrDefault( WellKnownVariables.ExternalTools_LibZ_Custom_ExePath, string.Empty); if (!string.IsNullOrWhiteSpace(customExePath) && File.Exists(customExePath)) { logger.Write($"Using custom path for LibZ: '{customExePath}'"); _exePath = customExePath; } _artifactsPath = buildVariables.Require(WellKnownVariables.Artifacts).ThrowIfEmptyValue().Value; string sourceRoot = buildVariables.Require(WellKnownVariables.SourceRoot).ThrowIfEmptyValue().Value; var sourceRootDirectory = new DirectoryInfo(sourceRoot); List <FileInfo> csharpProjectFiles = sourceRootDirectory.GetFilesRecursive( new List <string> { ".csproj" }, DefaultPaths.DefaultPathLookupSpecification, sourceRoot) .ToList(); List <FileInfo> ilMergeProjects = csharpProjectFiles.Where(IsMergeEnabledInProjectFile).ToList(); string merges = string.Join(Environment.NewLine, ilMergeProjects.Select(item => item.FullName)); logger.Write($"Found {ilMergeProjects.Count} projects marked for merging:{Environment.NewLine}{merges}"); ImmutableArray <ILRepackData> filesToMerge = (await Task.WhenAll(ilMergeProjects.Select(GetMergeFilesAsync))) .SelectMany(item => item).ToImmutableArray(); foreach (ILRepackData repackData in filesToMerge) { var fileInfo = new FileInfo(repackData.Exe); string mergedDirectoryPath = Path.Combine( _artifactsPath, "LibZ", repackData.Platform, repackData.Configuration); DirectoryInfo mergedDirectory = new DirectoryInfo(mergedDirectoryPath).EnsureExists(); string mergedPath = Path.Combine(mergedDirectory.FullName, fileInfo.Name); fileInfo.CopyTo(mergedPath); string exeConfiguration = Path.Combine(fileInfo.Directory.FullName, $"{fileInfo.Name}.config"); if (File.Exists(exeConfiguration)) { string targetConfigFilePath = Path.Combine(mergedDirectory.FullName, Path.GetFileName(exeConfiguration)); File.Copy(exeConfiguration, targetConfigFilePath); } var arguments = new List <string> { "inject-dll", "--assembly", $"{mergedPath}" }; foreach (FileInfo dll in repackData.Dlls) { arguments.Add("--include"); arguments.Add(dll.FullName); } arguments.Add("--move"); ExitCode result; using (CurrentDirectoryScope.Create( new DirectoryInfo(Directory.GetCurrentDirectory()), mergedDirectory)) { result = await ProcessRunner.ExecuteAsync( _exePath, arguments : arguments, standardOutLog : logger.Write, toolAction : logger.Write, standardErrorAction : logger.WriteError, cancellationToken : cancellationToken); } if (!result.IsSuccess) { logger.WriteError($"Could not LibZ '{fileInfo.FullName}'"); return(result); } logger.Write($"LibZ result: {mergedPath}"); } return(ExitCode.Success); }
public async Task <ExitCode> ExecuteAsync( ILogger logger, IReadOnlyCollection <IVariable> buildVariables, CancellationToken cancellationToken) { _logger = logger; ParseResult <bool> parseResult = buildVariables .GetVariableValueOrDefault(WellKnownVariables.ExternalTools_ILRepack_Enabled, "false") .TryParseBool(false); if (!parseResult.Value) { _logger.Write( $"ILRepack is disabled, to enable it, set the flag {WellKnownVariables.ExternalTools_ILRepack_Enabled} to true"); return(ExitCode.Success); } _ilRepackExePath = buildVariables.Require(WellKnownVariables.ExternalTools_ILRepack_ExePath).ThrowIfEmptyValue().Value; string customILRepackPath = buildVariables.GetVariableValueOrDefault( WellKnownVariables.ExternalTools_ILRepack_Custom_ExePath, string.Empty); if (!string.IsNullOrWhiteSpace(customILRepackPath) && File.Exists(customILRepackPath)) { logger.Write($"Using custom path for ILRepack: '{customILRepackPath}'"); _ilRepackExePath = customILRepackPath; } _artifactsPath = buildVariables.Require(WellKnownVariables.Artifacts).ThrowIfEmptyValue().Value; string sourceRoot = buildVariables.Require(WellKnownVariables.SourceRoot).ThrowIfEmptyValue().Value; var sourceRootDirectory = new DirectoryInfo(sourceRoot); List <FileInfo> csharpProjectFiles = sourceRootDirectory.GetFilesRecursive( new List <string> { ".csproj" }, DefaultPaths.DefaultPathLookupSpecification, sourceRoot) .ToList(); List <FileInfo> ilMergeProjects = csharpProjectFiles.Where(IsILMergeEnabledInProjectFile).ToList(); string merges = string.Join(Environment.NewLine, ilMergeProjects.Select(item => item.FullName)); logger.Write($"Found {ilMergeProjects.Count} projects marked for ILMerge:{Environment.NewLine}{merges}"); IReadOnlyCollection <ILRepackData> mergeDatas = ilMergeProjects.SelectMany(GetIlMergeFiles) .ToReadOnlyCollection(); foreach (ILRepackData repackData in mergeDatas) { var fileInfo = new FileInfo(repackData.Exe); string ilMergedDirectoryPath = Path.Combine( _artifactsPath, "ILMerged", repackData.Platform, repackData.Configuration); DirectoryInfo ilMergedDirectory = new DirectoryInfo(ilMergedDirectoryPath).EnsureExists(); string ilMergedPath = Path.Combine(ilMergedDirectory.FullName, fileInfo.Name); var arguments = new List <string> { "/target:exe", $"/out:{ilMergedPath}", $"/Lib:{fileInfo.Directory.FullName}", "/verbose", fileInfo.FullName }; arguments.AddRange(repackData.Dlls.Select(dll => dll.FullName)); string programFiles = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86); string dotNetVersion = repackData.TargetFramework; string referenceAssemblyDirectory = $"{$@"{programFiles}\Reference Assemblies\Microsoft\Framework\.NETFramework\v"}{dotNetVersion}"; if (!Directory.Exists(referenceAssemblyDirectory)) { logger.WriteError( $"Could not ILMerge, the reference assembly directory {referenceAssemblyDirectory} does not exist, currently only .NET v{dotNetVersion} is supported"); return(ExitCode.Failure); } arguments.Add( $@"/targetplatform:v4,{referenceAssemblyDirectory}"); ExitCode result = await ProcessRunner.ExecuteAsync( _ilRepackExePath, arguments : arguments, standardOutLog : logger.Write, toolAction : logger.Write, standardErrorAction : logger.WriteError, cancellationToken : cancellationToken); if (!result.IsSuccess) { logger.WriteError($"Could not ILRepack '{fileInfo.FullName}'"); return(result); } logger.Write($"ILMerged result: {ilMergedPath}"); } return(ExitCode.Success); }
private async Task <bool?> CheckPackageExistsAsync( FileInfo nugetPackage, string nugetExePath, ILogger logger, string sourceName) { if (!File.Exists(nugetPackage.FullName)) { logger.WriteError( $"The NuGet package '{nugetPackage}' does not exist"); return(null); } logger.WriteDebug($"Searching for existing NuGet package '{nugetPackage}'"); string packageVersion; string packageId; using (var fs = new FileStream(nugetPackage.FullName, FileMode.Open, FileAccess.Read)) { using (var archive = new ZipArchive(fs)) { ZipArchiveEntry nuspecEntry = archive.Entries.SingleOrDefault( entry => Path.GetExtension(entry.Name) .Equals(".nuspec", StringComparison.InvariantCultureIgnoreCase)); if (nuspecEntry == null) { throw new InvalidOperationException("The nuget package does not contain any nuspec"); } var nuspecReader = new NuspecReader(nuspecEntry.Open()); NuGetVersion nuGetVersion = nuspecReader.GetVersion(); packageVersion = nuGetVersion.ToNormalizedString(); packageId = nuspecReader.GetIdentity().Id; } } SemanticVersion expectedVersion = SemanticVersion.Parse(packageVersion); var packageInfo = new { Id = packageId, Version = expectedVersion }; var args = new List <string> { "list", packageId }; if (!string.IsNullOrWhiteSpace(sourceName)) { logger.WriteVerbose($"Using specific source name '{sourceName}'"); args.Add("-source"); args.Add(sourceName); } args.Add("-verbosity"); args.Add("normal"); if (packageInfo.Version.IsPrerelease) { logger.WriteVerbose($"Package '{nugetPackage.Name}' is pre-release"); args.Add("-prerelease"); } var errorBuilder = new StringBuilder(); var standardBuilder = new List <string>(); string expectedNameAndVersion = $"{packageInfo.Id} {expectedVersion.ToNormalizedString()}"; logger.Write($"Looking for '{expectedNameAndVersion}' package"); ExitCode exitCode = await ProcessRunner.ExecuteAsync( nugetExePath, arguments : args, standardOutLog : (message, prefix) => { standardBuilder.Add(message); logger.Write(message, prefix); }, standardErrorAction : (message, prefix) => { errorBuilder.AppendLine(message); logger.WriteError(message, prefix); }, toolAction : logger.Write, addProcessNameAsLogCategory : true, addProcessRunnerCategory : true); if (!exitCode.IsSuccess) { logger.WriteError($"Could not execute process to check if package '{expectedNameAndVersion}' exists"); return(null); } bool foundSpecificPackage = standardBuilder.Any( line => line.Equals(expectedNameAndVersion, StringComparison.InvariantCultureIgnoreCase)); if (foundSpecificPackage) { logger.Write($"Found existing package id '{expectedNameAndVersion}'"); } else { logger.Write($"Could not find existing package id '{expectedNameAndVersion}'"); } return(foundSpecificPackage); }
private static async Task <ExitCode> UploadNugetPackageAsync( string nugetExePath, string serverUri, string apiKey, string nugetPackage, ILogger logger, int timeoutInseconds, bool checkNuGetPackagesExists, bool timeoutIncreaseEnabled) { if (!File.Exists(nugetPackage)) { logger.WriteError( $"The NuGet package '{nugetPackage}' does not exist, when trying to push to nuget source"); return(ExitCode.Failure); } logger.WriteDebug($"Pushing NuGet package '{nugetPackage}'"); var args = new List <string> { "push", nugetPackage }; if (!string.IsNullOrWhiteSpace(serverUri)) { args.Add("-source"); args.Add(serverUri); } if (!string.IsNullOrWhiteSpace(apiKey)) { args.Add(apiKey); } args.Add("-verbosity"); args.Add("detailed"); const int MaxAttempts = 5; ExitCode exitCode = ExitCode.Failure; int attemptCount = 1; while (!exitCode.IsSuccess && attemptCount <= MaxAttempts) { var errorBuilder = new StringBuilder(); List <string> runSpecificArgs = args.ToList(); if (timeoutInseconds > 0) { runSpecificArgs.Add("-timeout"); int timeout; if (timeoutIncreaseEnabled) { timeout = timeoutInseconds * attemptCount; } else { timeout = timeoutInseconds; } runSpecificArgs.Add(timeout.ToString(CultureInfo.InvariantCulture)); } exitCode = await ProcessRunner.ExecuteAsync( nugetExePath, arguments : runSpecificArgs, standardOutLog : logger.Write, standardErrorAction : (message, prefix) => { errorBuilder.AppendLine(message); logger.WriteError(message, prefix); }, toolAction : logger.Write, addProcessNameAsLogCategory : true, addProcessRunnerCategory : true); if (!exitCode.IsSuccess && errorBuilder.ToString().IndexOf("conflict", StringComparison.InvariantCultureIgnoreCase) >= 0) { if (checkNuGetPackagesExists) { logger.WriteWarning( $"The NuGet package could not be pushed, however, the pre-check if the package exists succeeded, so this error might be temporal"); return(ExitCode.Success); } logger.WriteError( $"Failed to upload NuGet package '{nugetPackage}', skipping retry for NuGet package, conflict detected"); return(exitCode); } if (!exitCode.IsSuccess && attemptCount < MaxAttempts) { logger.WriteWarning( $"Failed to upload nuget package '{nugetPackage}', attempt {attemptCount} of {MaxAttempts}, retrying..."); } attemptCount++; if (!exitCode.IsSuccess && attemptCount == MaxAttempts) { logger.WriteError( $"Failed to upload nuget package '{nugetPackage}' on last attempt {attemptCount} of {MaxAttempts}"); } } return(exitCode); }
public async Task <IEnumerable <IVariable> > GetEnvironmentVariablesAsync( ILogger logger, IReadOnlyCollection <IVariable> buildVariables, CancellationToken cancellationToken) { int currentProcessBits = Environment.Is64BitProcess ? 64 : 32; const int registryLookupBits = 32; logger.WriteVerbose( $"Running current process [id {Process.GetCurrentProcess().Id}] as a {currentProcessBits}-bit process"); List <SemanticVersion> possibleVersions = new List <string> { "15.0.0", "14.0.0", "12.0.0", "4.0.0" } .Select(SemanticVersion.Parse) .ToList(); string max = buildVariables.GetVariableValueOrDefault( WellKnownVariables.ExternalTools_MSBuild_MaxVersion, "15.0.0"); SemanticVersion[] toRemove = possibleVersions.Where(version => version > SemanticVersion.Parse(max)) .ToArray(); foreach (SemanticVersion semVersion in toRemove) { possibleVersions.Remove(semVersion); } string vsWherePath = Path.Combine( Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86), "Microsoft Visual Studio", "Installer", "vswhere.exe"); if (File.Exists(vsWherePath)) { logger.WriteDebug($"vswhere.exe exists at '{vsWherePath}'"); ExitCode versionExitCode = await ProcessHelper.ExecuteAsync( vsWherePath, new List <string> { "-prerelease" }, cancellationToken : cancellationToken); var vsWhereArgs = new List <string> { "-requires", "Microsoft.Component.MSBuild", "-format", "json" }; bool allowPreRelease = buildVariables.GetBooleanByKey(WellKnownVariables.ExternalTools_MSBuild_AllowPrereleaseEnabled); if (allowPreRelease) { // NOTE only newer releases of vswhere.exe supports -prerelease flag if (versionExitCode.IsSuccess) { vsWhereArgs.Add("-prerelease"); } } var resultBuilder = new StringBuilder(); await ProcessRunner.ExecuteAsync( vsWherePath, arguments : vsWhereArgs, standardOutLog : (message, category) => resultBuilder.Append(message), cancellationToken : cancellationToken, toolAction : logger.WriteDebug, standardErrorAction : logger.WriteError); string json = resultBuilder.ToString(); try { var instanceTypePattern = new[] { new { installationName = string.Empty, installationPath = string.Empty, installationVersion = string.Empty, channelId = string.Empty } }; var typedInstallations = JsonConvert.DeserializeAnonymousType( json, instanceTypePattern); var candidates = typedInstallations.ToArray(); if (!allowPreRelease) { candidates = candidates .Where(candidate => candidate.channelId.IndexOf("preview", StringComparison.OrdinalIgnoreCase) < 0) .ToArray(); } var array = candidates .Select(candidate => new { candidate, versoin = Version.Parse(candidate.installationVersion) }) .ToArray(); var firstOrDefault = array.OrderByDescending(candidateItem => candidateItem.versoin) .FirstOrDefault(); if (firstOrDefault != null) { string msbuildPath = Path.Combine( firstOrDefault.candidate.installationPath, "MSBuild", "15.0", "bin", "MSBuild.exe"); if (File.Exists(msbuildPath)) { logger.Write($"Found MSBuild with vswhere.exe at '{msbuildPath}'"); var variables = new[] { new EnvironmentVariable( WellKnownVariables.ExternalTools_MSBuild_ExePath, msbuildPath) }; return(variables); } } logger.Write($"Could not find any version of MSBuild.exe with vswhere.exe"); } catch (Exception ex) { throw new InvalidOperationException( $"Could not deserialize installed Visual Studio versions from json '{json}'", ex); } } var possiblePaths = new[] { Path.Combine( Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86), "Microsoft Visual Studio", "2017", "Enterprise", "MSBuild", "15.0", "bin", "MSBuild.exe"), Path.Combine( Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86), "Microsoft Visual Studio", "2017", "Profesional", "MSBuild", "15.0", "bin", "MSBuild.exe"), Path.Combine( Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86), "Microsoft Visual Studio", "2017", "Community", "MSBuild", "15.0", "bin", "MSBuild.exe"), Path.Combine( Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86), "Microsoft Visual Studio", "2017", "BuildTools", "MSBuild", "15.0", "bin", "MSBuild.exe") }; string fileBasedLookupResultPath = possiblePaths.FirstOrDefault(File.Exists); if (fileBasedLookupResultPath != null) { logger.Write($"Found MSBuild at '{fileBasedLookupResultPath}'"); var variables = new[] { new EnvironmentVariable( WellKnownVariables.ExternalTools_MSBuild_ExePath, fileBasedLookupResultPath) }; return(variables); } string foundPath = null; foreach (SemanticVersion possibleVersion in possibleVersions) { string registryKeyName = @"SOFTWARE\Microsoft\MSBuild\" + possibleVersion.Major + "." + possibleVersion.Minor; object msBuildPathRegistryKeyValue = null; const string valueKey = "MSBuildOverrideTasksPath"; logger.WriteVerbose( $"Looking for MSBuild exe path in {registryLookupBits}-bit registry key '{registryKeyName}\\{valueKey}"); using (RegistryKey view32 = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry32)) { using (RegistryKey key = view32.OpenSubKey(registryKeyName)) { if (key != null) { msBuildPathRegistryKeyValue = key.GetValue(valueKey, null); } } } string msBuildPath = msBuildPathRegistryKeyValue != null ? $"{msBuildPathRegistryKeyValue}MSBuild.exe" : null; if (!string.IsNullOrWhiteSpace(msBuildPath)) { foundPath = msBuildPath; logger.WriteVerbose( $"Using MSBuild exe path '{foundPath}' defined in {registryLookupBits}-bit registry key {registryKeyName}\\{valueKey}"); break; } } if (string.IsNullOrWhiteSpace(foundPath)) { const string msbuildPath = "MSBUILD_PATH"; string fromEnvironmentVariable = Environment.GetEnvironmentVariable(msbuildPath); if (!string.IsNullOrWhiteSpace(fromEnvironmentVariable)) { logger.Write($"Using MSBuild exe path '{foundPath}' from environment variable {msbuildPath}"); foundPath = fromEnvironmentVariable; } else { logger.WriteError( $"The MSBuild path could not be found in the {registryLookupBits}-bit registry keys."); return(null); } } logger.Write($"Using MSBuild exe path '{foundPath}'"); var environmentVariables = new[] { new EnvironmentVariable( WellKnownVariables.ExternalTools_MSBuild_ExePath, foundPath) }; return(environmentVariables); }
private async Task <string> DownloadNuGetPackageAsync(string buildDir, string nugetExePath) { const string buildToolPackageName = "Arbor.X"; string outputDirectoryPath = Path.Combine(buildDir, buildToolPackageName); var outputDirectory = new DirectoryInfo(outputDirectoryPath); bool reinstall = !outputDirectory.Exists || Environment.GetEnvironmentVariable(WellKnownVariables.NuGetReinstallArborPackageEnabled) .TryParseBool(true); if (!reinstall) { return(outputDirectoryPath); } outputDirectory.DeleteIfExists(); outputDirectory.EnsureExists(); string version = Environment.GetEnvironmentVariable(WellKnownVariables.ArborXNuGetPackageVersion); var nugetArguments = new List <string> { "install", buildToolPackageName, "-ExcludeVersion", "-OutputDirectory", buildDir.TrimEnd('\\') }; if (LogLevel.Verbose.Level <= _logger.LogLevel.Level) { nugetArguments.Add("-Verbosity"); nugetArguments.Add("detailed"); } string nuGetSource = Environment.GetEnvironmentVariable(WellKnownVariables.ArborXNuGetPackageSource); if (!string.IsNullOrWhiteSpace(nuGetSource)) { nugetArguments.Add("-Source"); nugetArguments.Add(nuGetSource); } string noCache = Environment.GetEnvironmentVariable(WellKnownVariables.ArborXNuGetPackageNoCacheEnabled); if (noCache.TryParseBool(false)) { nugetArguments.Add("-NoCache"); } if (!string.IsNullOrWhiteSpace(version)) { nugetArguments.Add("-Version"); nugetArguments.Add(version); _logger.WriteVerbose( $"'{WellKnownVariables.ArborXNuGetPackageVersion}' flag is set, using specific version of Arbor.X: {version}", _Prefix); } else { _logger.WriteVerbose( $"'{WellKnownVariables.ArborXNuGetPackageVersion}' flag is not set, using latest version of Arbor.X", _Prefix); bool allowPrerelease; if (_startOptions.PrereleaseEnabled.HasValue) { allowPrerelease = _startOptions.PrereleaseEnabled.Value; if (allowPrerelease) { _logger.WriteVerbose( "Prerelease option is set via start options, using latest version of Arbor.X allowing prerelease versions", _Prefix); } } else { allowPrerelease = Environment.GetEnvironmentVariable(WellKnownVariables.AllowPrerelease) .TryParseBool(false); if (allowPrerelease) { _logger.WriteVerbose( $"'{WellKnownVariables.AllowPrerelease}' flag is set, using latest version of Arbor.X allowing prerelease versions", _Prefix); } else { _logger.WriteVerbose( $"'{WellKnownVariables.AllowPrerelease}' flag is not set, using latest stable version of Arbor.X", _Prefix); } } if (allowPrerelease) { nugetArguments.Add("-Prerelease"); } } var cancellationTokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(MaxBuildTimeInSeconds)); ExitCode exitCode = await ProcessRunner.ExecuteAsync( nugetExePath, arguments : nugetArguments, cancellationToken : cancellationTokenSource.Token, standardOutLog : _logger.Write, standardErrorAction : _logger.WriteError, toolAction : _logger.Write, verboseAction : _logger.WriteVerbose, addProcessRunnerCategory : true, addProcessNameAsLogCategory : true, parentPrefix : _Prefix); if (!exitCode.IsSuccess) { outputDirectoryPath = string.Empty; } return(outputDirectoryPath); }
public async Task <ExitCode> ExecuteAsync([NotNull] ILogger logger, [NotNull] IReadOnlyCollection <IVariable> buildVariables, CancellationToken cancellationToken) { if (logger == null) { throw new ArgumentNullException(nameof(logger)); } if (buildVariables == null) { throw new ArgumentNullException(nameof(buildVariables)); } bool enabled = buildVariables.GetBooleanByKey(WellKnownVariables.XUnitNetCoreAppEnabled, false); if (!enabled) { logger.WriteDebug("Xunit .NET Core App test runner is not enabled"); return(ExitCode.Success); } _sourceRoot = buildVariables.Require(WellKnownVariables.SourceRoot).ThrowIfEmptyValue().Value; IVariable reportPath = buildVariables.Require(WellKnownVariables.ReportPath).ThrowIfEmptyValue(); string xunitDllPath = buildVariables.GetVariableValueOrDefault(WellKnownVariables.XUnitNetCoreAppDllPath, null) ?? Path.Combine(buildVariables.Require(WellKnownVariables.ExternalTools).Value, "xunit", "netcoreapp2.0", "xunit.console.dll"); logger.WriteDebug($"Using XUnit dll path '{xunitDllPath}'"); Type theoryType = typeof(TheoryAttribute); Type factAttribute = typeof(FactAttribute); var directory = new DirectoryInfo(_sourceRoot); var typesToFind = new List <Type> { theoryType, factAttribute }; bool runTestsInReleaseConfiguration = buildVariables.GetBooleanByKey( WellKnownVariables.RunTestsInReleaseConfigurationEnabled, true); string configuration = runTestsInReleaseConfiguration ? "release" : "debug"; string assemblyFilePrefix = buildVariables.GetVariableValueOrDefault(WellKnownVariables.TestsAssemblyStartsWith, string.Empty); logger.Write($"Finding Xunit test DLL files built with {configuration} in directory '{_sourceRoot}'"); logger.Write($"Looking for types {string.Join(", ", typesToFind.Select(t => t.FullName))} in directory '{_sourceRoot}'"); List <string> testDlls = new UnitTestFinder(typesToFind, logger: logger, debugLogEnabled: true) .GetUnitTestFixtureDlls(directory, runTestsInReleaseConfiguration, assemblyFilePrefix: assemblyFilePrefix, targetFrameworkPrefix: FrameworkConstants.NetCoreApp) .ToList(); if (!testDlls.Any()) { logger.Write("Found no .NETCoreApp Assemblies with Xunit tests"); return(ExitCode.Success); } string dotNetExePath = buildVariables.GetVariableValueOrDefault(WellKnownVariables.DotNetExePath, string.Empty); if (string.IsNullOrWhiteSpace(dotNetExePath)) { logger.Write( $"Path to 'dotnet.exe' has not been specified, set variable '{WellKnownVariables.DotNetExePath}' or ensure the dotnet.exe is installed in its standard location"); return(ExitCode.Failure); } logger.WriteDebug($"Using dotnet.exe in path '{dotNetExePath}'"); string xmlReportName = $"{Guid.NewGuid()}.xml"; var arguments = new List <string>(); string reportFile = Path.Combine(reportPath.Value, "xunit", xmlReportName); var reportFileInfo = new FileInfo(reportFile); reportFileInfo.Directory.EnsureExists(); arguments.Add(xunitDllPath); arguments.AddRange(testDlls); arguments.Add("-nunit"); arguments.Add(reportFileInfo.FullName); ExitCode result = await ProcessRunner.ExecuteAsync( dotNetExePath, arguments : arguments, standardOutLog : logger.Write, standardErrorAction : logger.WriteError, toolAction : logger.Write, cancellationToken : cancellationToken); return(result); }
public async Task <ExitCode> ExecuteAsync( ILogger logger, IReadOnlyCollection <IVariable> buildVariables, CancellationToken cancellationToken) { bool enabled = buildVariables.GetBooleanByKey(WellKnownVariables.MSpecEnabled, true); if (!enabled) { logger.WriteWarning($"{MachineSpecificationsConstants.MachineSpecificationsName} not enabled"); return(ExitCode.Success); } string externalToolsPath = buildVariables.Require(WellKnownVariables.ExternalTools).ThrowIfEmptyValue().Value; string sourceRoot = buildVariables.Require(WellKnownVariables.SourceRoot).ThrowIfEmptyValue().Value; string testReportDirectoryPath = buildVariables.Require(WellKnownVariables.ExternalTools_MSpec_ReportPath).ThrowIfEmptyValue().Value; string sourceRootOverride = buildVariables.GetVariableValueOrDefault(WellKnownVariables.SourceRootOverride, string.Empty); string sourceDirectoryPath; if (string.IsNullOrWhiteSpace(sourceRootOverride) || !Directory.Exists(sourceRootOverride)) { if (sourceRoot == null) { throw new InvalidOperationException("Source root cannot be null"); } sourceDirectoryPath = sourceRoot; } else { sourceDirectoryPath = sourceRootOverride; } var directory = new DirectoryInfo(sourceDirectoryPath); string mspecExePath = Path.Combine( externalToolsPath, MachineSpecificationsConstants.MachineSpecificationsName, "mspec-clr4.exe"); bool runTestsInReleaseConfiguration = buildVariables.GetBooleanByKey( WellKnownVariables.RunTestsInReleaseConfigurationEnabled, true); IEnumerable <Type> typesToFind = new List <Type> { typeof(It), typeof(BehaviorsAttribute), typeof(SubjectAttribute), typeof(Behaves_like <>) }; logger.WriteVerbose( $"Scanning directory '{directory.FullName}' for assemblies containing Machine.Specifications tests"); string assemblyFilePrefix = buildVariables.GetVariableValueOrDefault(WellKnownVariables.TestsAssemblyStartsWith, string.Empty); List <string> testDlls = new UnitTestFinder(typesToFind, logger: logger) .GetUnitTestFixtureDlls(directory, runTestsInReleaseConfiguration, assemblyFilePrefix, FrameworkConstants.NetFramework) .ToList(); if (!testDlls.Any()) { logger.WriteWarning( $"No DLL files with {MachineSpecificationsConstants.MachineSpecificationsName} specifications was found"); return(ExitCode.Success); } var arguments = new List <string>(); arguments.AddRange(testDlls); arguments.Add("--xml"); string timestamp = DateTime.UtcNow.ToString("O").Replace(":", "."); string fileName = "MSpec_" + timestamp + ".xml"; string xmlReportPath = Path.Combine(testReportDirectoryPath, "Xml", fileName); new FileInfo(xmlReportPath).Directory.EnsureExists(); arguments.Add(xmlReportPath); string htmlPath = Path.Combine(testReportDirectoryPath, "Html", "MSpec_" + timestamp); new DirectoryInfo(htmlPath).EnsureExists(); IReadOnlyCollection <string> excludedTags = buildVariables .GetVariableValueOrDefault( WellKnownVariables.IgnoredTestCategories, string.Empty) .Split(new[] { "," }, StringSplitOptions.RemoveEmptyEntries) .Select(item => item.Trim()) .Where(item => !string.IsNullOrWhiteSpace(item)) .ToReadOnlyCollection(); arguments.Add("--html"); arguments.Add(htmlPath); bool hasArborTestDll = testDlls.Any(dll => dll.IndexOf("arbor", StringComparison.InvariantCultureIgnoreCase) >= 0); if (hasArborTestDll || excludedTags.Any()) { var allExcludedTags = new List <string>(); arguments.Add("--exclude"); if (hasArborTestDll) { allExcludedTags.Add(MSpecInternalConstants.RecursiveArborXTest); } if (excludedTags.Any()) { allExcludedTags.AddRange(excludedTags); } string excludedTagsParameter = string.Join(",", allExcludedTags); logger.Write($"Running MSpec with excluded tags: {excludedTagsParameter}"); arguments.Add(excludedTagsParameter); } // ReSharper disable once CollectionNeverUpdated.Local var environmentVariables = new Dictionary <string, string>(); ExitCode exitCode = await ProcessRunner.ExecuteAsync( mspecExePath, arguments : arguments, cancellationToken : cancellationToken, standardOutLog : logger.Write, standardErrorAction : logger.WriteError, toolAction : logger.Write, verboseAction : logger.WriteVerbose, environmentVariables : environmentVariables, debugAction : logger.WriteDebug); if (buildVariables.GetBooleanByKey( WellKnownVariables.MSpecJUnitXslTransformationEnabled, false)) { logger.WriteVerbose( $"Transforming {MachineSpecificationsConstants.MachineSpecificationsName} test reports to JUnit format"); const string junitSuffix = "_junit.xml"; DirectoryInfo xmlReportDirectory = new FileInfo(xmlReportPath).Directory; // ReSharper disable once PossibleNullReferenceException IReadOnlyCollection <FileInfo> xmlReports = xmlReportDirectory .GetFiles("*.xml") .Where(report => !report.Name.EndsWith(junitSuffix, StringComparison.Ordinal)) .ToReadOnlyCollection(); if (xmlReports.Any()) { Encoding encoding = Encoding.UTF8; using (Stream stream = new MemoryStream(encoding.GetBytes(MSpecJUnitXsl.Xml))) { using (XmlReader xmlReader = new XmlTextReader(stream)) { var myXslTransform = new XslCompiledTransform(); myXslTransform.Load(xmlReader); foreach (FileInfo xmlReport in xmlReports) { logger.WriteDebug($"Transforming '{xmlReport.FullName}' to JUnit XML format"); try { TransformReport(xmlReport, junitSuffix, encoding, myXslTransform, logger); } catch (Exception ex) { logger.WriteError($"Could not transform '{xmlReport.FullName}', {ex}"); return(ExitCode.Failure); } logger.WriteDebug( $"Successfully transformed '{xmlReport.FullName}' to JUnit XML format"); } } } } } return(exitCode); }
private static async Task <ExitCode> ExecuteNuGetPackAsync( string nuGetExePath, string packagesDirectoryPath, ILogger logger, string nuSpecFileCopyPath, string properties, NuSpec nuSpecCopy, List <string> removedTags, bool keepBinaryAndSourcePackagesTogetherEnabled = false, bool nugetSymbolPackageEnabled = false, bool ignoreWarnings = false, CancellationToken cancellationToken = default) { bool hasRemovedNoSourceTag = removedTags.Any( tag => tag.Equals(WellKnownNuGetTags.NoSource, StringComparison.InvariantCultureIgnoreCase)); ExitCode result; try { var arguments = new List <string> { "pack", nuSpecFileCopyPath, "-Properties", properties, "-OutputDirectory", packagesDirectoryPath, "-Version", nuSpecCopy.Version }; if (!hasRemovedNoSourceTag && nugetSymbolPackageEnabled) { arguments.Add("-Symbols"); } if (LogLevel.Verbose.Level <= logger.LogLevel.Level) { arguments.Add("-Verbosity"); arguments.Add("Detailed"); } if (ignoreWarnings) { arguments.Add("-NoPackageAnalysis"); } ExitCode processResult = await ProcessRunner.ExecuteAsync( nuGetExePath, arguments : arguments, standardOutLog : logger.Write, standardErrorAction : logger.WriteError, toolAction : logger.Write, cancellationToken : cancellationToken, verboseAction : logger.WriteVerbose, debugAction : logger.WriteDebug, addProcessNameAsLogCategory : true, addProcessRunnerCategory : true); var packagesDirectory = new DirectoryInfo(packagesDirectoryPath); if (!keepBinaryAndSourcePackagesTogetherEnabled) { logger.Write( $"The flag {WellKnownVariables.NuGetKeepBinaryAndSymbolPackagesTogetherEnabled} is set to false, separating binary packages from symbol packages"); List <string> nugetPackages = packagesDirectory.GetFiles("*.nupkg", SearchOption.TopDirectoryOnly) .Select(file => file.FullName) .ToList(); List <string> nugetSymbolPackages = packagesDirectory .GetFiles("*.symbols.nupkg", SearchOption.TopDirectoryOnly) .Select(file => file.FullName) .ToList(); List <string> binaryPackages = nugetPackages.Except(nugetSymbolPackages).ToList(); DirectoryInfo binaryPackagesDirectory = new DirectoryInfo(Path.Combine(packagesDirectory.FullName, "binary")).EnsureExists(); DirectoryInfo symbolPackagesDirectory = new DirectoryInfo(Path.Combine(packagesDirectory.FullName, "symbol")).EnsureExists(); foreach (string binaryPackage in binaryPackages) { var sourceFile = new FileInfo(binaryPackage); var targetBinaryFile = new FileInfo(Path.Combine(binaryPackagesDirectory.FullName, sourceFile.Name)); if (targetBinaryFile.Exists) { targetBinaryFile.Delete(); } logger.WriteDebug($"Copying NuGet binary package '{binaryPackage}' to '{targetBinaryFile}'"); sourceFile.MoveTo(targetBinaryFile.FullName); } foreach (string sourcePackage in nugetSymbolPackages) { var sourceFile = new FileInfo(sourcePackage); var targetSymbolFile = new FileInfo(Path.Combine(symbolPackagesDirectory.FullName, sourceFile.Name)); if (targetSymbolFile.Exists) { targetSymbolFile.Delete(); } logger.WriteDebug($"Copying NuGet symbol package '{sourcePackage}' to '{targetSymbolFile}'"); sourceFile.MoveTo(targetSymbolFile.FullName); } } result = processResult; } finally { if (File.Exists(nuSpecFileCopyPath)) { File.Delete(nuSpecFileCopyPath); } } return(result); }
public async Task <ExitCode> ExecuteAsync([NotNull] ILogger logger, [NotNull] IReadOnlyCollection <IVariable> buildVariables, CancellationToken cancellationToken) { if (logger == null) { throw new ArgumentNullException(nameof(logger)); } if (buildVariables == null) { throw new ArgumentNullException(nameof(buildVariables)); } bool enabled = buildVariables.GetBooleanByKey(WellKnownVariables.XUnitNetFrameworkEnabled, false); if (!enabled) { logger.WriteDebug("Xunit .NET Framework test runner is not enabled"); return(ExitCode.Success); } _sourceRoot = buildVariables.Require(WellKnownVariables.SourceRoot).ThrowIfEmptyValue().Value; IVariable reportPath = buildVariables.Require(WellKnownVariables.ReportPath).ThrowIfEmptyValue(); string xunitExePath = buildVariables.GetVariableValueOrDefault(WellKnownVariables.XUnitNetFrameworkExePath, Path.Combine(buildVariables.Require(WellKnownVariables.ExternalTools).Value, "xunit", "net452", "xunit.console.exe")); Type theoryType = typeof(TheoryAttribute); Type factAttribute = typeof(FactAttribute); var directory = new DirectoryInfo(_sourceRoot); var typesToFind = new List <Type> { theoryType, factAttribute }; bool runTestsInReleaseConfiguration = buildVariables.GetBooleanByKey( WellKnownVariables.RunTestsInReleaseConfigurationEnabled, true); string assemblyFilePrefix = buildVariables.GetVariableValueOrDefault(WellKnownVariables.TestsAssemblyStartsWith, string.Empty); List <string> testDlls = new UnitTestFinder(typesToFind) .GetUnitTestFixtureDlls(directory, runTestsInReleaseConfiguration, assemblyFilePrefix, FrameworkConstants.NetFramework) .ToList(); if (!testDlls.Any()) { logger.Write("Could not find any DLL files with Xunit test and target framework .NETFramework, skipping Xunit Net Framework tests"); return(ExitCode.Success); } string xmlReportName = $"{Guid.NewGuid()}.xml"; var arguments = new List <string>(); string reportFile = Path.Combine(reportPath.Value, "xunit", xmlReportName); var fileInfo = new FileInfo(reportFile); fileInfo.Directory.EnsureExists(); arguments.AddRange(testDlls); arguments.Add("-nunit"); arguments.Add(fileInfo.FullName); ExitCode result = await ProcessRunner.ExecuteAsync( xunitExePath, arguments : arguments, standardOutLog : logger.Write, standardErrorAction : logger.WriteError, toolAction : logger.Write, cancellationToken : cancellationToken); return(result); }