static IEnumerable <string> GetModifiedFilesLocally(IProcessManager processManager, Harness harness, int pull_request) { var base_commit = $"origin/pr/{pull_request}/merge^"; var head_commit = $"origin/pr/{pull_request}/merge"; harness.Log("Fetching modified files for commit range {0}..{1}", base_commit, head_commit); if (string.IsNullOrEmpty(head_commit) || string.IsNullOrEmpty(base_commit)) { return(null); } using (var git = new Process()) { git.StartInfo.FileName = "git"; git.StartInfo.Arguments = $"diff-tree --no-commit-id --name-only -r {base_commit}..{head_commit}"; var output = new MemoryLog(); var rv = processManager.RunAsync(git, harness.HarnessLog, stdoutLog: output, stderrLog: output).Result; if (rv.Succeeded) { return(output.ToString().Split(new char [] { '\n' }, StringSplitOptions.RemoveEmptyEntries)); } harness.Log("Could not fetch commit range:"); harness.Log(output.ToString()); return(null); } }
static string DetectXcodePath() { using var process = new Process(); process.StartInfo.FileName = "xcode-select"; process.StartInfo.Arguments = "-p"; var log = new MemoryLog(); var stdout = new MemoryLog() { Timestamp = false }; var stderr = new ConsoleLog(); var timeout = TimeSpan.FromSeconds(30); var result = RunAsyncInternal(process, log, stdout, stderr, timeout).GetAwaiter().GetResult(); if (!result.Succeeded) { throw new Exception("Failed to detect Xcode path from xcode-select!"); } // Something like /Applications/Xcode114.app/Contents/Developers var xcodeRoot = stdout.ToString().Trim(); if (string.IsNullOrEmpty(xcodeRoot)) { throw new Exception("Failed to detect Xcode path from xcode-select!"); } // We need /Applications/Xcode114.app only return(Path.GetDirectoryName(Path.GetDirectoryName(xcodeRoot))); }
public async Task <string> GetAppBundlePath(ILog log, string bundleIdentifier, CancellationToken cancellationToken) { log.WriteLine($"Querying '{Name}' for bundle path of '{bundleIdentifier}'.."); var output = new MemoryLog() { Timestamp = false }; var result = await _processManager.ExecuteXcodeCommandAsync( "simctl", new[] { "get_app_container", UDID, bundleIdentifier }, log, output, output, TimeSpan.FromSeconds(30)); if (!result.Succeeded) { throw new Exception($"Failed to get information for '{bundleIdentifier}'. Please check the app is installed"); } var bundlePath = output.ToString().Trim(); log.WriteLine($"Found installed app bundle at '{bundlePath}'"); return(bundlePath); }
protected async Task <(bool Succeeded, string Stdout)> ExecuteCommand( string filename, TimeSpan?timeout = null, params string[] arguments) { var stdoutLog = new MemoryLog() { Timestamp = false }; var stderrLog = new MemoryLog() { Timestamp = false }; var result = await _processManager.ExecuteCommandAsync( filename, arguments, new CallbackLog(m => Logger.LogDebug(m)), stdoutLog, stderrLog, timeout ?? TimeSpan.FromSeconds(30)); string stderr = stderrLog.ToString(); if (stderr.Length > 0) { Logger.LogDebug("Error output:" + Environment.NewLine + stderr); } return(result.Succeeded, stdoutLog.ToString()); }
private async Task <string> GetPlistProperty(string plistPath, string propertyName, ILog log, CancellationToken cancellationToken = default) { var args = new[] { "-c", $"Print {propertyName}", plistPath, }; var commandOutput = new MemoryLog { Timestamp = false }; var result = await _processManager.ExecuteCommandAsync(PlistBuddyPath, args, log, commandOutput, commandOutput, TimeSpan.FromSeconds(15), cancellationToken : cancellationToken); if (!result.Succeeded) { throw new Exception($"Failed to get bundle information: {commandOutput}"); } return(commandOutput.ToString().Trim()); }
private async Task <string> GetPlistProperty( string plistPath, string propertyName, ILog log, CancellationToken cancellationToken = default, int attempt = 1, int maxAttempts = 3) { var args = new[] { "-c", $"Print {propertyName}", plistPath, }; var commandOutput = new MemoryLog { Timestamp = false }; var result = await _processManager.ExecuteCommandAsync( PlistBuddyPath, args, log, commandOutput, commandOutput, TimeSpan.FromSeconds(10), cancellationToken : cancellationToken); if (!result.Succeeded) { if (result.TimedOut && attempt < maxAttempts) { log.WriteLine($"Attempt to get {propertyName} from {plistPath} timed out, retrying {attempt + 1} out of {maxAttempts}..."); return(await GetPlistProperty(plistPath, propertyName, log, cancellationToken, attempt + 1, maxAttempts)); } throw new Exception($"Failed to get bundle information: {commandOutput}"); } return(commandOutput.ToString().Trim()); }
public IEnumerable <string> GetModifiedFiles(int pullRequest) { var path = Path.Combine(harness.LogDirectory, "pr" + pullRequest + "-files.log"); if (!File.Exists(path)) { var rv = GetModifiedFilesLocally(pullRequest); if (rv == null || rv.Count() == 0) { rv = GetModifiedFilesRemotely(pullRequest); if (rv == null) { rv = new string [] { } } ; } File.WriteAllLines(path, rv.ToArray()); return(rv); } return(File.ReadAllLines(path)); } IEnumerable <string> GetModifiedFilesRemotely(int pullRequest) { var path = Path.Combine(harness.LogDirectory, "pr" + pullRequest + "-remote-files.log"); if (!File.Exists(path)) { Directory.CreateDirectory(harness.LogDirectory); using (var client = CreateClient()) { var rv = new List <string> (); var url = $"https://api.github.com/repos/xamarin/xamarin-macios/pulls/{pullRequest}/files?per_page=100"; // 100 items per page is max do { byte [] data; try { data = client.DownloadData(url); } catch (WebException we) { harness.Log("Could not load pull request files: {0}\n{1}", we, new StreamReader(we.Response.GetResponseStream()).ReadToEnd()); File.WriteAllText(path, string.Empty); return(new string [] { }); } var reader = JsonReaderWriterFactory.CreateJsonReader(data, new XmlDictionaryReaderQuotas()); var doc = new XmlDocument(); doc.Load(reader); foreach (XmlNode node in doc.SelectNodes("/root/item/filename")) { rv.Add(node.InnerText); } url = null; var link = client.ResponseHeaders ["Link"]; try { if (link != null) { var ltIdx = link.IndexOf('<'); var gtIdx = link.IndexOf('>', ltIdx + 1); while (ltIdx >= 0 && gtIdx > ltIdx) { var linkUrl = link.Substring(ltIdx + 1, gtIdx - ltIdx - 1); if (link [gtIdx + 1] != ';') { break; } var commaIdx = link.IndexOf(',', gtIdx + 1); string rel; if (commaIdx != -1) { rel = link.Substring(gtIdx + 3, commaIdx - gtIdx - 3); } else { rel = link.Substring(gtIdx + 3); } if (rel == "rel=\"next\"") { url = linkUrl; break; } if (commaIdx == -1) { break; } ltIdx = link.IndexOf('<', commaIdx); gtIdx = link.IndexOf('>', ltIdx + 1); } } } catch (Exception e) { harness.Log("Could not paginate github response: {0}: {1}", link, e.Message); } } while (url != null); File.WriteAllLines(path, rv.ToArray()); return(rv); } } return(File.ReadAllLines(path)); } IEnumerable <string> GetModifiedFilesLocally(int pullRequest) { var base_commit = $"origin/pr/{pullRequest}/merge^"; var head_commit = $"origin/pr/{pullRequest}/merge"; harness.Log("Fetching modified files for commit range {0}..{1}", base_commit, head_commit); if (string.IsNullOrEmpty(head_commit) || string.IsNullOrEmpty(base_commit)) { return(null); } using (var git = new Process()) { git.StartInfo.FileName = "git"; git.StartInfo.Arguments = $"diff-tree --no-commit-id --name-only -r {base_commit}..{head_commit}"; var output = new MemoryLog() { Timestamp = false // ensure we do not add the timestap or the logic for the file check will be hard and having it adds no value }; var rv = processManager.RunAsync(git, harness.HarnessLog, stdoutLog: output, stderrLog: output).Result; if (rv.Succeeded) { return(output.ToString().Split(new char [] { '\n' }, StringSplitOptions.RemoveEmptyEntries)); } harness.Log("Could not fetch commit range:"); harness.Log(output.ToString()); return(null); } } byte[] DownloadPullRequestInfo(int pullRequest) { var path = Path.Combine(harness.LogDirectory, "pr" + pullRequest + ".log"); if (!File.Exists(path)) { Directory.CreateDirectory(harness.LogDirectory); using (var client = CreateClient()) { byte [] data; try { data = client.DownloadData($"https://api.github.com/repos/xamarin/xamarin-macios/pulls/{pullRequest}"); File.WriteAllBytes(path, data); return(data); } catch (WebException we) { harness.Log("Could not load pull request info: {0}\n{1}", we, new StreamReader(we.Response.GetResponseStream()).ReadToEnd()); File.WriteAllText(path, string.Empty); return(new byte [0]); } } } return(File.ReadAllBytes(path)); } }
protected override async Task <ExitCode> InvokeInternal(ILogger logger) { var processManager = new ProcessManager(mlaunchPath: _arguments.MlaunchPath); var deviceLoader = new HardwareDeviceLoader(processManager); var simulatorLoader = new SimulatorLoader(processManager); var log = new MemoryLog(); // do we really want to log this? var mlaunchLog = new MemoryLog { Timestamp = false }; ProcessExecutionResult result; try { result = await processManager.ExecuteCommandAsync(new MlaunchArguments(new MlaunchVersionArgument()), new MemoryLog(), mlaunchLog, new MemoryLog(), TimeSpan.FromSeconds(10)); } catch (Exception e) { logger.LogError($"Failed to get mlaunch version info:{Environment.NewLine}{e}"); return(ExitCode.GENERAL_FAILURE); } if (!result.Succeeded) { logger.LogError($"Failed to get mlaunch version info:{Environment.NewLine}{mlaunchLog}"); return(ExitCode.GENERAL_FAILURE); } // build the required data, then depending on the format print out var info = new SystemInfo( machineName: Environment.MachineName, oSName: "Mac OS X", oSVersion: Darwin.GetVersion() ?? "", oSPlatform: "Darwin", xcodePath: processManager.XcodeRoot, xcodeVersion: processManager.XcodeVersion.ToString(), mlaunchPath: processManager.MlaunchPath, mlaunchVersion: mlaunchLog.ToString().Trim()); try { await simulatorLoader.LoadDevices(log); } catch (Exception e) { logger.LogError($"Failed to load simulators:{Environment.NewLine}{e}"); logger.LogInformation($"Execution log:{Environment.NewLine}{log}"); return(ExitCode.GENERAL_FAILURE); } foreach (var sim in simulatorLoader.AvailableDevices) { info.Simulators.Add(new DeviceInfo( name: sim.Name, uDID: sim.UDID, type: sim.SimDeviceType.Remove(0, SimulatorPrefix.Length).Replace('-', ' '), oSVersion: sim.OSVersion)); } try { await deviceLoader.LoadDevices(log); } catch (Exception e) { logger.LogError($"Failed to load connected devices:{Environment.NewLine}{e}"); logger.LogInformation($"Execution log:{Environment.NewLine}{log}"); return(ExitCode.GENERAL_FAILURE); } foreach (var dev in deviceLoader.ConnectedDevices) { info.Devices.Add(new DeviceInfo( name: dev.Name, uDID: dev.DeviceIdentifier, type: $"{dev.DeviceClass} {dev.DevicePlatform}", oSVersion: dev.OSVersion)); } if (_arguments.UseJson) { await AsJson(info); } else { AsText(info); } return(ExitCode.SUCCESS); }
public async Task CreateCopyAsync(ILog log, IProcessManager processManager, ITestTask test) { var directory = DirectoryUtilities.CreateTemporaryDirectory(test?.TestName ?? System.IO.Path.GetFileNameWithoutExtension(Path)); Directory.CreateDirectory(directory); var original_path = Path; Path = System.IO.Path.Combine(directory, System.IO.Path.GetFileName(Path)); await Task.Yield(); XmlDocument doc; doc = new XmlDocument(); doc.LoadWithoutNetworkAccess(original_path); var original_name = System.IO.Path.GetFileName(original_path); if (original_name.Contains("GuiUnit_NET") || original_name.Contains("GuiUnit_xammac_mobile")) { // The GuiUnit project files writes stuff outside their project directory using relative paths, // but override that so that we don't end up with multiple cloned projects writing stuff to // the same location. doc.SetOutputPath("bin\\$(Configuration)"); doc.SetNode("DocumentationFile", "bin\\$(Configuration)\\nunitlite.xml"); } doc.ResolveAllPaths(original_path); if (doc.IsDotNetProject()) { // Many types of files below the csproj directory are included by default, // which means that we have to include them manually in the cloned csproj, // because the cloned project is stored in a very different directory. var test_dir = System.IO.Path.GetDirectoryName(original_path); // Get all the files in the project directory from git using var process = new Process(); process.StartInfo.FileName = "git"; process.StartInfo.Arguments = "ls-files"; process.StartInfo.WorkingDirectory = test_dir; var stdout = new MemoryLog() { Timestamp = false }; var result = await processManager.RunAsync(process, log, stdout, stdout, timeout : TimeSpan.FromSeconds(15)); if (!result.Succeeded) { throw new Exception($"Failed to list the files in the directory {test_dir} (TimedOut: {result.TimedOut} ExitCode: {result.ExitCode}):\n{stdout}"); } var files = stdout.ToString().Split('\n'); foreach (var file in files) { var ext = System.IO.Path.GetExtension(file); var full_path = System.IO.Path.Combine(test_dir, file); var windows_file = full_path.Replace('/', '\\'); if (file.Contains(".xcasset")) { doc.AddInclude("ImageAsset", file, windows_file, true); continue; } switch (ext.ToLowerInvariant()) { case ".cs": doc.AddInclude("Compile", file, windows_file, true); break; case ".plist": doc.AddInclude("None", file, windows_file, true); break; case ".storyboard": doc.AddInclude("InterfaceDefinition", file, windows_file, true); break; case ".gitignore": case ".csproj": case ".props": // Directory.Build.props case "": // Makefile break; // ignore these files default: Console.WriteLine($"Unknown file: {file} (extension: {ext}). There might be a default inclusion behavior for this file."); break; } } // The global.json and NuGet.config files make sure we use the locally built packages. var dotnet_test_dir = System.IO.Path.Combine(test.RootDirectory, "dotnet"); var global_json = System.IO.Path.Combine(dotnet_test_dir, "global.json"); var nuget_config = System.IO.Path.Combine(dotnet_test_dir, "NuGet.config"); var target_directory = directory; File.Copy(global_json, System.IO.Path.Combine(target_directory, System.IO.Path.GetFileName(global_json)), true); log.WriteLine($"Copied {global_json} to {target_directory}"); File.Copy(nuget_config, System.IO.Path.Combine(target_directory, System.IO.Path.GetFileName(nuget_config)), true); log.WriteLine($"Copied {nuget_config} to {target_directory}"); } var projectReferences = new List <TestProject> (); foreach (var pr in doc.GetProjectReferences()) { var tp = new TestProject(pr.Replace('\\', '/')); await tp.CreateCopyAsync(log, processManager, test); doc.SetProjectReferenceInclude(pr, tp.Path.Replace('/', '\\')); projectReferences.Add(tp); } this.ProjectReferences = projectReferences; doc.Save(Path); }
public async Task TimedOutQueueIsClearedWhenTooBig() { var logger = new MemoryLog(); var timeout = TimeSpan.FromMilliseconds(1); var endpoint = TestConfig.ServerEndpoint(); using (var server = new TcpServer(endpoint.Ip.Port, TestConfig.Log)) using (var conn = new Connection(endpoint, new ConnectionConfiguration(requestTimeout: timeout), logger)) { await Task.WhenAny(server.ClientConnected, Task.Delay(TimeSpan.FromSeconds(3))); await AssertAsync.Throws <TimeoutException>(() => Task.WhenAll(Enumerable.Range(0, 201).Select(i => conn.SendAsync(new MetadataRequest(), CancellationToken.None)))); await AssertAsync.ThatEventually(() => logger.LogEvents.Any(e => e.Item1 == LogLevel.Debug && e.Item2.Message.StartsWith("Clearing timed out requests to avoid overflow")), () => logger.ToString(LogLevel.Debug)); } }
public async Task MessagesStillLogWhenSendTimesOut() { var logger = new MemoryLog(); var received = 0; var timeout = TimeSpan.FromMilliseconds(100); var endpoint = TestConfig.ServerEndpoint(); using (var server = new TcpServer(endpoint.Ip.Port, TestConfig.Log)) using (var conn = new Connection(endpoint, new ConnectionConfiguration(requestTimeout: timeout, onRead: (e, read, elapsed) => Interlocked.Increment(ref received)), logger)) { await Task.WhenAny(server.ClientConnected, Task.Delay(TimeSpan.FromSeconds(3))); server.OnReceivedAsync = async data => { var context = KafkaDecoder.DecodeHeader(data.Skip(Request.IntegerByteSize)); await Task.Delay(timeout); await server.SendDataAsync(KafkaDecoder.EncodeResponseBytes(context, new MetadataResponse())); }; await AssertAsync.Throws <TimeoutException>(() => conn.SendAsync(new MetadataRequest(), CancellationToken.None)); await AssertAsync.ThatEventually(() => received > 0, () => $"received {received}"); await AssertAsync.ThatEventually(() => logger.LogEvents.Any(e => e.Item1 == LogLevel.Debug && e.Item2.Message.StartsWith("Timed out -----> (timed out or otherwise errored in client)")), () => logger.ToString(LogLevel.Debug)); } }
public async Task ShouldNotFinishPartiallyReadMessage() { var log = new MemoryLog(); var bytesRead = 0; var config = new ConnectionConfiguration(onReadBytes: (e, attempted, actual, elapsed) => Interlocked.Add(ref bytesRead, actual)); var endpoint = TestConfig.ServerEndpoint(); using (var server = new TcpServer(endpoint.Ip.Port, TestConfig.Log)) using (new Connection(endpoint, config, log)) { // send size var size = 200; await server.SendDataAsync(new ArraySegment <byte>(size.ToBytes())); var bytes = new byte[99]; new Random(42).NextBytes(bytes); var offset = 0; var correlationId = 200; foreach (var b in correlationId.ToBytes()) { bytes[offset++] = b; } // send half of payload await server.SendDataAsync(new ArraySegment <byte>(bytes, 0, 99)); await AssertAsync.ThatEventually(() => bytesRead >= bytes.Length, () => $"read {bytesRead}, length {bytes.Length}"); Assert.That(log.LogEvents.Count(e => e.Item1 == LogLevel.Warn && e.Item2.Message.StartsWith($"Unexpected response (id {correlationId}, {size}? bytes) from")), Is.EqualTo(1), log.ToString()); Assert.That(log.LogEvents.Count(e => e.Item1 == LogLevel.Debug && e.Item2.Message.StartsWith($"Received {size} bytes (id {correlationId})")), Is.EqualTo(0)); server.DropConnection(); await AssertAsync.ThatEventually(() => log.LogEvents.Count(e => e.Item1 == LogLevel.Warn && e.Item2.Message.StartsWith("Socket has been re-established, so recovery of the remaining of the current message is uncertain at best")) == 1, log.ToString); } }
public async Task ShouldFinishPartiallyReadMessage() { var log = new MemoryLog(); var bytesRead = 0; var firstLength = 99; var config = new ConnectionConfiguration(onReadBytes: (e, attempted, actual, elapsed) => { if (Interlocked.Add(ref bytesRead, actual) == firstLength) { throw new OutOfMemoryException(); } }); var endpoint = TestConfig.ServerEndpoint(); using (var server = new TcpServer(endpoint.Ip.Port, TestConfig.Log)) using (new Connection(endpoint, config, log)) { // send size var size = 200; await server.SendDataAsync(new ArraySegment <byte>(size.ToBytes())); var bytes = new byte[size]; new Random(42).NextBytes(bytes); var offset = 0; var correlationId = 200; foreach (var b in correlationId.ToBytes()) { bytes[offset++] = b; } // send half of payload await server.SendDataAsync(new ArraySegment <byte>(bytes, 0, firstLength)); await AssertAsync.ThatEventually(() => bytesRead >= firstLength, () => $"read {bytesRead}, length {bytes.Length}"); Assert.That(log.LogEvents.Count(e => e.Item1 == LogLevel.Warn && e.Item2.Message.StartsWith($"Unexpected response (id {correlationId}, {size}? bytes) from")), Is.EqualTo(1), log.ToString()); Assert.That(log.LogEvents.Count(e => e.Item1 == LogLevel.Debug && e.Item2.Message.StartsWith($"Received {size} bytes (id {correlationId})")), Is.EqualTo(0)); // send half of payload should be skipped while (!await server.SendDataAsync(new ArraySegment <byte>(bytes, firstLength, size - firstLength))) { // repeat until the connection is all up and working ... } await AssertAsync.ThatEventually(() => bytesRead >= size, () => $"read {bytesRead}, size {size}"); await AssertAsync.ThatEventually(() => log.LogEvents.Count(e => e.Item1 == LogLevel.Info && e.Item2.Message.StartsWith($"Received {size} bytes (id {correlationId})")) == 1, log.ToString); } }
public async Task ShouldLogDisconnectAndRecover() { var log = new MemoryLog(); var clientDisconnected = 0; var clientConnected = 0; var serverConnected = 0; var config = new ConnectionConfiguration( onDisconnected: (e, exception) => { Interlocked.Increment(ref clientDisconnected); }, onConnected: (e, attempt, elapsed) => { Interlocked.Increment(ref clientConnected); }); var endpoint = TestConfig.ServerEndpoint(); using (var server = new TcpServer(endpoint.Ip.Port, TestConfig.Log) { OnConnected = () => Interlocked.Increment(ref serverConnected) }) using (new Connection(endpoint, config, log: log)) { for (var connectionAttempt = 1; connectionAttempt <= 4; connectionAttempt++) { var currentAttempt = connectionAttempt; await AssertAsync.ThatEventually(() => serverConnected == currentAttempt, () => $"server {serverConnected}, attempt {currentAttempt}"); await server.SendDataAsync(new ArraySegment <byte>(CreateCorrelationMessage(connectionAttempt))); TestConfig.Log.Write(LogLevel.Info, () => LogEvent.Create($"Sent CONNECTION attempt {currentAttempt}")); await AssertAsync.ThatEventually(() => clientConnected == currentAttempt, TimeSpan.FromMilliseconds(200), () => $"client {clientConnected}, attempt {currentAttempt}"); await AssertAsync.ThatEventually(() => log.LogEvents.Count(e => e.Item1 == LogLevel.Info && e.Item2.Message.StartsWith("Received 4 bytes (id ")) == currentAttempt, () => $"attempt {currentAttempt}\n" + log.ToString(LogLevel.Info)); TestConfig.Log.Write(LogLevel.Info, () => LogEvent.Create($"Dropping CONNECTION attempt {currentAttempt}")); server.DropConnection(); await AssertAsync.ThatEventually(() => clientDisconnected == currentAttempt, () => $"client {clientDisconnected}, attempt {currentAttempt}"); Assert.That(log.LogEvents.Count(e => e.Item1 == LogLevel.Info && e.Item2.Message.StartsWith("Disposing transport to")), Is.AtLeast(currentAttempt)); } } }
protected async override Task <ExitCode> InvokeInternal(ILogger logger) { var processManager = new MLaunchProcessManager(); // Validate the presence of Xamarin.iOS var missingXamariniOS = false; if (_arguments.TemplateType == TemplateType.Managed) { var dotnetLog = new MemoryLog() { Timestamp = false }; var process = new Process(); process.StartInfo.FileName = "bash"; process.StartInfo.Arguments = "-c \"" + _arguments.DotnetPath + " --info | grep \\\"Base Path\\\" | cut -d':' -f 2 | tr -d '[:space:]'\""; var result = await processManager.RunAsync(process, new MemoryLog(), dotnetLog, new MemoryLog(), TimeSpan.FromSeconds(5)); if (result.Succeeded) { var sdkPath = dotnetLog.ToString().Trim(); if (Directory.Exists(sdkPath)) { var xamarinIosPath = Path.Combine(sdkPath, "Xamarin", "iOS", "Xamarin.iOS.CSharp.targets"); if (!File.Exists(xamarinIosPath)) { missingXamariniOS = true; logger.LogWarning("Failed to find the Xamarin iOS package which is needed for the Managed template: " + xamarinIosPath); } } } } // create the factory, which will be used to find the diff assemblies var assemblyLocator = new AssemblyLocator(Directory.GetCurrentDirectory()); var assemblyDefinitionFactory = new AssemblyDefinitionFactory(_arguments.TestingFramework, assemblyLocator); ITemplatedProject template = _arguments.TemplateType switch { TemplateType.Managed => new XamariniOSTemplate { AssemblyDefinitionFactory = assemblyDefinitionFactory, AssemblyLocator = assemblyDefinitionFactory.AssemblyLocator, OutputDirectoryPath = _arguments.OutputDirectory, IgnoreFilesRootDirectory = _arguments.IgnoreFilesRootDirectory, ProjectFilter = new ProjectFilter(_arguments.IgnoreFilesRootDirectory, _arguments.TraitsRootDirectory), }, _ => throw new Exception("The 'Native' template is not yet supported. Please use the managed one."), }; var logs = new Logs(_arguments.WorkingDirectory); var runLog = logs.Create("package-log.txt", "Package Log"); var consoleLog = new CallbackLog(s => logger.LogInformation(s)) { Timestamp = false }; var aggregatedLog = Log.CreateReadableAggregatedLog(runLog, consoleLog); aggregatedLog.WriteLine("Generating scaffold app with:"); aggregatedLog.WriteLine($"\tAppname: '{_arguments.AppPackageName}'"); aggregatedLog.WriteLine($"\tAssemblies: '{string.Join(" ", _arguments.Assemblies)}'"); aggregatedLog.WriteLine($"\tExtraArgs: '{_arguments.MtouchExtraArgs}'"); // first step, generate the required info to be passed to the factory var projects = new List <(string Name, string[] Assemblies, string?ExtraArgs, double TimeoutMultiplier)> { // TODO: Timeout multiplier handling (Name : _arguments.AppPackageName, Assemblies : _arguments.Assemblies.ToArray(), ExtraArgs : _arguments.MtouchExtraArgs, TimeoutMultiplier : 1.0), }; // TODO: we are not taking into account all the plaforms, just iOS var allProjects = new GeneratedProjects(); foreach (var p in _arguments.Platforms) { // so wish that mono.options allowed use to use async :/ var testProjects = await template.GenerateTestProjectsAsync(projects, XHarness.iOS.Shared.TestImporter.Platform.iOS); allProjects.AddRange(testProjects); } // We do have all the required projects, time to compile them aggregatedLog.WriteLine("Scaffold app generated."); // First step, nuget restore whatever is needed var projectPath = Path.Combine(_arguments.OutputDirectory, _arguments.AppPackageName + ".csproj"); aggregatedLog.WriteLine($"Project path is {projectPath}"); aggregatedLog.WriteLine($"Performing nuget restore."); using (var dotnetRestore = new Process()) { dotnetRestore.StartInfo.FileName = _arguments.DotnetPath; var args = new List <string> { "restore", projectPath, "--verbosity:detailed" }; dotnetRestore.StartInfo.Arguments = StringUtils.FormatArguments(args); var result = await processManager.RunAsync(dotnetRestore, aggregatedLog, _nugetRestoreTimeout); if (result.TimedOut) { aggregatedLog.WriteLine("nuget restore timedout."); return(ExitCode.PACKAGE_BUNDLING_FAILURE_NUGET_RESTORE); } if (!result.Succeeded) { aggregatedLog.WriteLine($"nuget restore exited with {result.ExitCode}"); return(ExitCode.PACKAGE_BUNDLING_FAILURE_NUGET_RESTORE); } } var finalResult = ExitCode.SUCCESS; // perform the build of the application using (var dotnetBuild = new Process()) { dotnetBuild.StartInfo.FileName = _arguments.DotnetPath; // TODO: Only taking into account one platform // https://github.com/dotnet/xharness/issues/105 if (_arguments.Platforms.Count > 1) { logger.LogWarning($"Multi-platform targetting is not supported yet. Targetting {_arguments.Platforms[0]} only."); } dotnetBuild.StartInfo.Arguments = GetBuildArguments(projectPath, _arguments.Platforms[0].ToString()); aggregatedLog.WriteLine($"Building {_arguments.AppPackageName} ({projectPath})"); var result = await processManager.RunAsync(dotnetBuild, aggregatedLog, _msBuildTimeout); if (result.TimedOut) { aggregatedLog.WriteLine("Build timed out after {0} seconds.", _msBuildTimeout.TotalSeconds); } else if (result.Succeeded) { aggregatedLog.WriteLine("Build was successful."); } else if (!result.Succeeded) { logger.LogError($"Build failed with return code: {result.ExitCode}"); finalResult = ExitCode.PACKAGE_BUNDLING_FAILURE_BUILD; if (missingXamariniOS) { logger.LogWarning("Possible cause of the failure may be missing Xamarin iOS package"); } } } return(finalResult); }
// Retrieves a property from an MSBuild project file by executing MSBuild and getting MSBuild to print the property. // We do this by creating a custom MSBuild file which: // * Will import the project file we're inspecting // * Has a target that will print a given property // and then executing MSBuild on this custom MSBuild file. private async Task <string> GetPropertyByMSBuildEvaluationAsync(XmlDocument csproj, string projectPath, string evaluateProperty, string dependsOnTargets = "", Dictionary <string, string> properties = null) { var xml = @"<Project DefaultTargets='WriteProperty' xmlns='http://schemas.microsoft.com/developer/msbuild/2003'> <!-- Import the project we want to inspect --> <Import Project='$(ProjectFile)' Condition=""'$(ProjectFile)' != ''"" /> <!-- Target to write out the property we want --> <Target Name='WriteProperty' DependsOnTargets='%DEPENDSONTARGETS%'> <PropertyGroup> <_Properties>$(%PROPERTY%)</_Properties> </PropertyGroup> <Error Text='The ProjectFile variable must be set.' Condition=""'$(ProjectFile)' == ''"" /> <Error Text='The OutputFile variable must be set.' Condition=""'$(OutputFile)' == ''"" /> <WriteLinesToFile File='$(OutputFile)' Lines='$(_Properties)' Overwrite='true' /> </Target> </Project> "; var dir = Path.GetDirectoryName(projectPath); var inspector = Path.Combine(dir, "PropertyInspector.csproj"); var output = Path.Combine(dir, "PropertyInspector.txt"); try { File.WriteAllText(inspector, xml.Replace("%PROPERTY%", evaluateProperty).Replace("%DEPENDSONTARGETS%", dependsOnTargets)); using (var proc = new Process()) { var isDotNetProject = csproj.IsDotNetProject(); proc.StartInfo.FileName = isDotNetProject ? GetDotNetExecutable(projectPath) : msBuildPath; var args = new List <string> (); if (isDotNetProject) { args.Add("build"); } args.Add("/p:ProjectFile=" + projectPath); args.Add("/p:OutputFile=" + output); foreach (var prop in properties) { args.Add($"/p:{prop.Key}={prop.Value}"); } args.Add(inspector); var env = new Dictionary <string, string> { { "MSBUILD_EXE_PATH", null }, }; proc.StartInfo.Arguments = StringUtils.FormatArguments(args); proc.StartInfo.WorkingDirectory = dir; // Don't evaluate in parallel on multiple threads to avoid overloading the mac. var acquired = await evaluate_semaphore.WaitAsync(TimeSpan.FromMinutes(5)); try { var log = getLog() ?? new ConsoleLog(); var memoryLog = new MemoryLog(); var aggregated = Log.CreateAggregatedLog(memoryLog, log); if (!acquired) { aggregated.WriteLine("Unable to acquire lock to evaluate MSBuild property in 5 minutes; will try to evaluate anyway."); } var rv = await processManager.RunAsync(proc, aggregated, environmentVariables : env, timeout : TimeSpan.FromMinutes(5)); if (!rv.Succeeded) { var msg = $"Unable to evaluate the property {evaluateProperty} in {projectPath}, build failed with exit code {rv.ExitCode}. Timed out: {rv.TimedOut}"; Console.WriteLine(msg + " Output: \n" + memoryLog.ToString()); throw new Exception(msg); } } finally { if (acquired) { evaluate_semaphore.Release(); } } return(File.ReadAllText(output).Trim()); } } finally { File.Delete(inspector); File.Delete(output); } }