3.2 C#: Upgrade GodotTools to nuget Microsoft.Build
This upgrade is needed in order to support reading and editing project files that use Sdks as well as other new features. A common example in 3.2 is having to specify a PackageReference version with a child element rather than the attribute. This is no longer the case now. Partial cherry-pick off3bcd5f8dd
Most of the other changes from that commit were already partially cherry-picked in3928fe200f
.
This commit is contained in:
parent
e1f17a0b35
commit
4d7b7d9b73
|
@ -2,7 +2,6 @@ using System;
|
|||
using System.IO;
|
||||
using System.Security;
|
||||
using Microsoft.Build.Framework;
|
||||
using GodotTools.Core;
|
||||
|
||||
namespace GodotTools.BuildLogger
|
||||
{
|
||||
|
@ -18,7 +17,7 @@ namespace GodotTools.BuildLogger
|
|||
if (null == Parameters)
|
||||
throw new LoggerException("Log directory was not set.");
|
||||
|
||||
var parameters = Parameters.Split(new[] { ';' });
|
||||
var parameters = Parameters.Split(new[] {';'});
|
||||
|
||||
string logDir = parameters[0];
|
||||
|
||||
|
@ -183,4 +182,17 @@ namespace GodotTools.BuildLogger
|
|||
private StreamWriter issuesStreamWriter;
|
||||
private int indent;
|
||||
}
|
||||
|
||||
internal static class StringExtensions
|
||||
{
|
||||
public static string CsvEscape(this string value, char delimiter = ',')
|
||||
{
|
||||
bool hasSpecialChar = value.IndexOfAny(new[] {'\"', '\n', '\r', delimiter}) != -1;
|
||||
|
||||
if (hasSpecialChar)
|
||||
return "\"" + value.Replace("\"", "\"\"") + "\"";
|
||||
|
||||
return value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,13 +1,11 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<ProjectGuid>{6CE9A984-37B1-4F8A-8FE9-609F05F071B3}</ProjectGuid>
|
||||
<OutputType>Library</OutputType>
|
||||
<TargetFramework>net472</TargetFramework>
|
||||
<LangVersion>7</LangVersion>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
<LangVersion>7.2</LangVersion>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="Microsoft.Build.Framework" />
|
||||
<PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.0" PrivateAssets="All" />
|
||||
<PackageReference Include="Microsoft.Build.Framework" Version="16.5.0" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\GodotTools.Core\GodotTools.Core.csproj" />
|
||||
|
|
|
@ -1,11 +1,7 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<ProjectGuid>{639E48BD-44E5-4091-8EDD-22D36DC0768D}</ProjectGuid>
|
||||
<OutputType>Library</OutputType>
|
||||
<TargetFramework>net472</TargetFramework>
|
||||
<LangVersion>7</LangVersion>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
<LangVersion>7.2</LangVersion>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.0" PrivateAssets="All" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
|
|
@ -34,23 +34,13 @@ namespace GodotTools.Core
|
|||
return rooted ? Path.DirectorySeparatorChar + path : path;
|
||||
}
|
||||
|
||||
private static readonly string driveRoot = Path.GetPathRoot(Environment.CurrentDirectory);
|
||||
private static readonly string DriveRoot = Path.GetPathRoot(Environment.CurrentDirectory);
|
||||
|
||||
public static bool IsAbsolutePath(this string path)
|
||||
{
|
||||
return path.StartsWith("/", StringComparison.Ordinal) ||
|
||||
path.StartsWith("\\", StringComparison.Ordinal) ||
|
||||
path.StartsWith(driveRoot, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
public static string CsvEscape(this string value, char delimiter = ',')
|
||||
{
|
||||
bool hasSpecialChar = value.IndexOfAny(new char[] { '\"', '\n', '\r', delimiter }) != -1;
|
||||
|
||||
if (hasSpecialChar)
|
||||
return "\"" + value.Replace("\"", "\"\"") + "\"";
|
||||
|
||||
return value;
|
||||
path.StartsWith(DriveRoot, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
public static string ToSafeDirName(this string dirName, bool allowDirSeparator)
|
||||
|
|
|
@ -1,16 +1,24 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<ProjectGuid>{A8CDAD94-C6D4-4B19-A7E7-76C53CC92984}</ProjectGuid>
|
||||
<OutputType>Library</OutputType>
|
||||
<TargetFramework>net472</TargetFramework>
|
||||
<LangVersion>7</LangVersion>
|
||||
<LangVersion>7.2</LangVersion>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="Microsoft.Build" />
|
||||
<PackageReference Include="DotNet.Glob" Version="2.1.1" />
|
||||
<PackageReference Include="Microsoft.Build" Version="16.5.0" />
|
||||
<PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.0" PrivateAssets="All" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\GodotTools.Core\GodotTools.Core.csproj" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<!--
|
||||
The Microsoft.Build.Runtime package is too problematic so we create a MSBuild.exe stub. The workaround described
|
||||
here doesn't work with Microsoft.NETFramework.ReferenceAssemblies: https://github.com/microsoft/msbuild/issues/3486
|
||||
We need a MSBuild.exe file as there's an issue in Microsoft.Build where it executes platform dependent code when
|
||||
searching for MSBuild.exe before the fallback to not using it. A stub is fine as it should never be executed.
|
||||
-->
|
||||
<None Include="MSBuild.exe" CopyToOutputDirectory="Always" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
|
|
@ -2,8 +2,8 @@ using GodotTools.Core;
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using DotNet.Globbing;
|
||||
using Microsoft.Build.Construction;
|
||||
using Microsoft.Build.Globbing;
|
||||
|
||||
namespace GodotTools.ProjectEditor
|
||||
{
|
||||
|
@ -11,8 +11,6 @@ namespace GodotTools.ProjectEditor
|
|||
{
|
||||
public static ProjectItemElement FindItemOrNull(this ProjectRootElement root, string itemType, string include, bool noCondition = false)
|
||||
{
|
||||
GlobOptions globOptions = new GlobOptions {Evaluation = {CaseInsensitive = false}};
|
||||
|
||||
string normalizedInclude = include.NormalizePath();
|
||||
|
||||
foreach (var itemGroup in root.ItemGroups)
|
||||
|
@ -25,7 +23,7 @@ namespace GodotTools.ProjectEditor
|
|||
if (item.ItemType != itemType)
|
||||
continue;
|
||||
|
||||
var glob = Glob.Parse(item.Include.NormalizePath(), globOptions);
|
||||
var glob = MSBuildGlob.Parse(item.Include.NormalizePath());
|
||||
|
||||
if (glob.IsMatch(normalizedInclude))
|
||||
return item;
|
||||
|
@ -36,8 +34,6 @@ namespace GodotTools.ProjectEditor
|
|||
}
|
||||
public static ProjectItemElement FindItemOrNullAbs(this ProjectRootElement root, string itemType, string include, bool noCondition = false)
|
||||
{
|
||||
GlobOptions globOptions = new GlobOptions {Evaluation = {CaseInsensitive = false}};
|
||||
|
||||
string normalizedInclude = Path.GetFullPath(include).NormalizePath();
|
||||
|
||||
foreach (var itemGroup in root.ItemGroups)
|
||||
|
@ -50,7 +46,7 @@ namespace GodotTools.ProjectEditor
|
|||
if (item.ItemType != itemType)
|
||||
continue;
|
||||
|
||||
var glob = Glob.Parse(Path.GetFullPath(item.Include).NormalizePath(), globOptions);
|
||||
var glob = MSBuildGlob.Parse(Path.GetFullPath(item.Include).NormalizePath());
|
||||
|
||||
if (glob.IsMatch(normalizedInclude))
|
||||
return item;
|
||||
|
|
|
@ -4,8 +4,8 @@ using System.Diagnostics;
|
|||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using DotNet.Globbing;
|
||||
using Microsoft.Build.Construction;
|
||||
using Microsoft.Build.Globbing;
|
||||
|
||||
namespace GodotTools.ProjectEditor
|
||||
{
|
||||
|
@ -133,9 +133,6 @@ namespace GodotTools.ProjectEditor
|
|||
var result = new List<string>();
|
||||
var existingFiles = GetAllFilesRecursive(Path.GetDirectoryName(projectPath), "*.cs");
|
||||
|
||||
var globOptions = new GlobOptions();
|
||||
globOptions.Evaluation.CaseInsensitive = false;
|
||||
|
||||
var root = ProjectRootElement.Open(projectPath);
|
||||
Debug.Assert(root != null);
|
||||
|
||||
|
@ -151,7 +148,7 @@ namespace GodotTools.ProjectEditor
|
|||
|
||||
string normalizedInclude = item.Include.NormalizePath();
|
||||
|
||||
var glob = Glob.Parse(normalizedInclude, globOptions);
|
||||
var glob = MSBuildGlob.Parse(normalizedInclude);
|
||||
|
||||
// TODO Check somehow if path has no blob to avoid the following loop...
|
||||
|
||||
|
|
|
@ -36,15 +36,13 @@ namespace GodotTools.Build
|
|||
}
|
||||
case BuildTool.MsBuildVs:
|
||||
{
|
||||
if (_msbuildToolsPath.Empty() || !File.Exists(_msbuildToolsPath))
|
||||
if (string.IsNullOrEmpty(_msbuildToolsPath) || !File.Exists(_msbuildToolsPath))
|
||||
{
|
||||
// Try to search it again if it wasn't found last time or if it was removed from its location
|
||||
_msbuildToolsPath = FindMsBuildToolsPathOnWindows();
|
||||
|
||||
if (_msbuildToolsPath.Empty())
|
||||
{
|
||||
if (string.IsNullOrEmpty(_msbuildToolsPath))
|
||||
throw new FileNotFoundException($"Cannot find executable for '{BuildManager.PropNameMSBuildVs}'.");
|
||||
}
|
||||
}
|
||||
|
||||
if (!_msbuildToolsPath.EndsWith("\\"))
|
||||
|
@ -57,15 +55,14 @@ namespace GodotTools.Build
|
|||
string msbuildPath = Path.Combine(Internal.MonoWindowsInstallRoot, "bin", "msbuild.bat");
|
||||
|
||||
if (!File.Exists(msbuildPath))
|
||||
{
|
||||
throw new FileNotFoundException($"Cannot find executable for '{BuildManager.PropNameMSBuildMono}'. Tried with path: {msbuildPath}");
|
||||
}
|
||||
|
||||
return (msbuildPath, BuildTool.MsBuildMono);
|
||||
}
|
||||
case BuildTool.JetBrainsMsBuild:
|
||||
{
|
||||
var editorPath = (string)editorSettings.GetSetting(RiderPathManager.EditorPathSettingName);
|
||||
|
||||
if (!File.Exists(editorPath))
|
||||
throw new FileNotFoundException($"Cannot find Rider executable. Tried with path: {editorPath}");
|
||||
|
||||
|
@ -83,7 +80,7 @@ namespace GodotTools.Build
|
|||
}
|
||||
}
|
||||
|
||||
if (OS.IsUnixLike())
|
||||
if (OS.IsUnixLike)
|
||||
{
|
||||
switch (buildTool)
|
||||
{
|
||||
|
@ -138,12 +135,12 @@ namespace GodotTools.Build
|
|||
{
|
||||
string ret = OS.PathWhich(name);
|
||||
|
||||
if (!ret.Empty())
|
||||
if (!string.IsNullOrEmpty(ret))
|
||||
return ret;
|
||||
|
||||
string retFallback = OS.PathWhich($"{name}.exe");
|
||||
|
||||
if (!retFallback.Empty())
|
||||
if (!string.IsNullOrEmpty(retFallback))
|
||||
return retFallback;
|
||||
|
||||
foreach (string hintDir in MsBuildHintDirs)
|
||||
|
@ -195,7 +192,7 @@ namespace GodotTools.Build
|
|||
|
||||
string value = line.Substring(sepIdx + 1).StripEdges();
|
||||
|
||||
if (value.Empty())
|
||||
if (string.IsNullOrEmpty(value))
|
||||
throw new FormatException("installationPath value is empty");
|
||||
|
||||
if (!value.EndsWith("\\"))
|
||||
|
|
|
@ -72,7 +72,7 @@ namespace GodotTools
|
|||
{
|
||||
string[] csvColumns = file.GetCsvLine();
|
||||
|
||||
if (csvColumns.Length == 1 && csvColumns[0].Empty())
|
||||
if (csvColumns.Length == 1 && string.IsNullOrEmpty(csvColumns[0]))
|
||||
return;
|
||||
|
||||
if (csvColumns.Length != 7)
|
||||
|
@ -115,12 +115,12 @@ namespace GodotTools
|
|||
// Get correct issue idx from issue list
|
||||
int issueIndex = (int)issuesList.GetItemMetadata(idx);
|
||||
|
||||
if (idx < 0 || idx >= issues.Count)
|
||||
if (issueIndex < 0 || issueIndex >= issues.Count)
|
||||
throw new IndexOutOfRangeException("Issue index out of range");
|
||||
|
||||
BuildIssue issue = issues[issueIndex];
|
||||
|
||||
if (issue.ProjectFile.Empty() && issue.File.Empty())
|
||||
if (string.IsNullOrEmpty(issue.ProjectFile) && string.IsNullOrEmpty(issue.File))
|
||||
return;
|
||||
|
||||
string projectDir = issue.ProjectFile.Length > 0 ? issue.ProjectFile.GetBaseDir() : BuildInfo.Solution.GetBaseDir();
|
||||
|
@ -158,14 +158,14 @@ namespace GodotTools
|
|||
string tooltip = string.Empty;
|
||||
tooltip += $"Message: {issue.Message}";
|
||||
|
||||
if (!issue.Code.Empty())
|
||||
if (!string.IsNullOrEmpty(issue.Code))
|
||||
tooltip += $"\nCode: {issue.Code}";
|
||||
|
||||
tooltip += $"\nType: {(issue.Warning ? "warning" : "error")}";
|
||||
|
||||
string text = string.Empty;
|
||||
|
||||
if (!issue.File.Empty())
|
||||
if (!string.IsNullOrEmpty(issue.File))
|
||||
{
|
||||
text += $"{issue.File}({issue.Line},{issue.Column}): ";
|
||||
|
||||
|
@ -174,7 +174,7 @@ namespace GodotTools
|
|||
tooltip += $"\nColumn: {issue.Column}";
|
||||
}
|
||||
|
||||
if (!issue.ProjectFile.Empty())
|
||||
if (!string.IsNullOrEmpty(issue.ProjectFile))
|
||||
tooltip += $"\nProject: {issue.ProjectFile}";
|
||||
|
||||
text += issue.Message;
|
||||
|
|
|
@ -302,7 +302,7 @@ namespace GodotTools
|
|||
|
||||
case ExternalEditorId.VsCode:
|
||||
{
|
||||
if (_vsCodePath.Empty() || !File.Exists(_vsCodePath))
|
||||
if (string.IsNullOrEmpty(_vsCodePath) || !File.Exists(_vsCodePath))
|
||||
{
|
||||
// Try to search it again if it wasn't found last time or if it was removed from its location
|
||||
_vsCodePath = VsCodeNames.SelectFirstNotNull(OS.PathWhich, orElse: string.Empty);
|
||||
|
@ -354,7 +354,7 @@ namespace GodotTools
|
|||
|
||||
if (OS.IsOSX)
|
||||
{
|
||||
if (!osxAppBundleInstalled && _vsCodePath.Empty())
|
||||
if (!osxAppBundleInstalled && string.IsNullOrEmpty(_vsCodePath))
|
||||
{
|
||||
GD.PushError("Cannot find code editor: VSCode");
|
||||
return Error.FileNotFound;
|
||||
|
@ -364,7 +364,7 @@ namespace GodotTools
|
|||
}
|
||||
else
|
||||
{
|
||||
if (_vsCodePath.Empty())
|
||||
if (string.IsNullOrEmpty(_vsCodePath))
|
||||
{
|
||||
GD.PushError("Cannot find code editor: VSCode");
|
||||
return Error.FileNotFound;
|
||||
|
@ -551,7 +551,7 @@ namespace GodotTools
|
|||
$",Visual Studio Code:{(int)ExternalEditorId.VsCode}" +
|
||||
$",JetBrains Rider:{(int)ExternalEditorId.Rider}";
|
||||
}
|
||||
else if (OS.IsUnixLike())
|
||||
else if (OS.IsUnixLike)
|
||||
{
|
||||
settingsHintStr += $",MonoDevelop:{(int)ExternalEditorId.MonoDevelop}" +
|
||||
$",Visual Studio Code:{(int)ExternalEditorId.VsCode}" +
|
||||
|
|
|
@ -1,12 +1,8 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<ProjectGuid>{27B00618-A6F2-4828-B922-05CAEB08C286}</ProjectGuid>
|
||||
<OutputType>Library</OutputType>
|
||||
<TargetFramework>net472</TargetFramework>
|
||||
<GodotSourceRootPath>$(SolutionDir)/../../../../</GodotSourceRootPath>
|
||||
<DataDirToolsOutputPath>$(GodotSourceRootPath)/bin/GodotSharp/Tools</DataDirToolsOutputPath>
|
||||
<GodotApiConfiguration>Debug</GodotApiConfiguration>
|
||||
<LangVersion>7</LangVersion>
|
||||
<LangVersion>7.2</LangVersion>
|
||||
<GodotApiConfiguration>Debug</GodotApiConfiguration> <!-- The Godot editor uses the Debug Godot API assemblies -->
|
||||
<GodotSourceRootPath>$(SolutionDir)/../../../../</GodotSourceRootPath>
|
||||
<GodotOutputDataDir>$(GodotSourceRootPath)/bin/GodotSharp</GodotOutputDataDir>
|
||||
|
|
|
@ -128,7 +128,7 @@ namespace GodotTools.Ides.MonoDevelop
|
|||
{EditorId.MonoDevelop, "MonoDevelop.exe"}
|
||||
};
|
||||
}
|
||||
else if (OS.IsUnixLike())
|
||||
else if (OS.IsUnixLike)
|
||||
{
|
||||
ExecutableNames = new Dictionary<EditorId, string>
|
||||
{
|
||||
|
|
|
@ -36,7 +36,7 @@ namespace GodotTools.Ides.Rider
|
|||
{
|
||||
return CollectRiderInfosMac();
|
||||
}
|
||||
if (OS.IsUnixLike())
|
||||
if (OS.IsUnixLike)
|
||||
{
|
||||
return CollectAllRiderPathsLinux();
|
||||
}
|
||||
|
@ -147,7 +147,7 @@ namespace GodotTools.Ides.Rider
|
|||
return GetToolboxRiderRootPath(localAppData);
|
||||
}
|
||||
|
||||
if (OS.IsUnixLike())
|
||||
if (OS.IsUnixLike)
|
||||
{
|
||||
var home = Environment.GetEnvironmentVariable("HOME");
|
||||
if (string.IsNullOrEmpty(home))
|
||||
|
@ -209,7 +209,7 @@ namespace GodotTools.Ides.Rider
|
|||
|
||||
private static string GetRelativePathToBuildTxt()
|
||||
{
|
||||
if (OS.IsWindows || OS.IsUnixLike())
|
||||
if (OS.IsWindows || OS.IsUnixLike)
|
||||
return "../../build.txt";
|
||||
if (OS.IsOSX)
|
||||
return "Contents/Resources/build.txt";
|
||||
|
|
|
@ -62,6 +62,11 @@ namespace GodotTools.Utils
|
|||
return name.Equals(GetPlatformName(), StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
private static bool IsAnyOS(IEnumerable<string> names)
|
||||
{
|
||||
return names.Any(p => p.Equals(GetPlatformName(), StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
|
||||
private static readonly Lazy<bool> _isWindows = new Lazy<bool>(() => IsOS(Names.Windows));
|
||||
private static readonly Lazy<bool> _isOSX = new Lazy<bool>(() => IsOS(Names.OSX));
|
||||
private static readonly Lazy<bool> _isX11 = new Lazy<bool>(() => IsOS(Names.X11));
|
||||
|
@ -71,6 +76,7 @@ namespace GodotTools.Utils
|
|||
private static readonly Lazy<bool> _isAndroid = new Lazy<bool>(() => IsOS(Names.Android));
|
||||
private static readonly Lazy<bool> _isiOS = new Lazy<bool>(() => IsOS(Names.iOS));
|
||||
private static readonly Lazy<bool> _isHTML5 = new Lazy<bool>(() => IsOS(Names.HTML5));
|
||||
private static readonly Lazy<bool> _isUnixLike = new Lazy<bool>(() => IsAnyOS(UnixLikePlatforms));
|
||||
|
||||
public static bool IsWindows => _isWindows.Value || IsUWP;
|
||||
public static bool IsOSX => _isOSX.Value;
|
||||
|
@ -82,18 +88,9 @@ namespace GodotTools.Utils
|
|||
public static bool IsiOS => _isiOS.Value;
|
||||
public static bool IsHTML5 => _isHTML5.Value;
|
||||
|
||||
private static bool? _isUnixCache;
|
||||
private static readonly string[] UnixLikePlatforms = { Names.OSX, Names.X11, Names.Server, Names.Haiku, Names.Android, Names.iOS };
|
||||
private static readonly string[] UnixLikePlatforms = {Names.OSX, Names.X11, Names.Server, Names.Haiku, Names.Android, Names.iOS};
|
||||
|
||||
public static bool IsUnixLike()
|
||||
{
|
||||
if (_isUnixCache.HasValue)
|
||||
return _isUnixCache.Value;
|
||||
|
||||
string osName = GetPlatformName();
|
||||
_isUnixCache = UnixLikePlatforms.Any(p => p.Equals(osName, StringComparison.OrdinalIgnoreCase));
|
||||
return _isUnixCache.Value;
|
||||
}
|
||||
public static bool IsUnixLike => _isUnixLike.Value;
|
||||
|
||||
public static char PathSep => IsWindows ? ';' : ':';
|
||||
|
||||
|
@ -121,10 +118,10 @@ namespace GodotTools.Utils
|
|||
return searchDirs.Select(dir => Path.Combine(dir, name)).FirstOrDefault(File.Exists);
|
||||
|
||||
return (from dir in searchDirs
|
||||
select Path.Combine(dir, name)
|
||||
select Path.Combine(dir, name)
|
||||
into path
|
||||
from ext in windowsExts
|
||||
select path + ext).FirstOrDefault(File.Exists);
|
||||
from ext in windowsExts
|
||||
select path + ext).FirstOrDefault(File.Exists);
|
||||
}
|
||||
|
||||
private static string PathWhichUnix([NotNull] string name)
|
||||
|
@ -189,7 +186,7 @@ namespace GodotTools.Utils
|
|||
|
||||
startInfo.UseShellExecute = false;
|
||||
|
||||
using (var process = new Process { StartInfo = startInfo })
|
||||
using (var process = new Process {StartInfo = startInfo})
|
||||
{
|
||||
process.Start();
|
||||
process.WaitForExit();
|
||||
|
|
Loading…
Reference in New Issue