diff --git a/.github/workflows/code-style.yaml b/.github/workflows/code-style.yaml index b266cae..9e64740 100644 --- a/.github/workflows/code-style.yaml +++ b/.github/workflows/code-style.yaml @@ -16,15 +16,16 @@ jobs: - name: Setup .NET uses: actions/setup-dotnet@v3 with: - global-json-file: global.json + global-json-file: "./global.json" - name: Restore dependencies run: dotnet restore - name: Check dotnet version run: dotnet --version - continue-on-error: false + + - name: Check code format (editorconfig) + run: dotnet run --project .\test\test.csproj - name: Check code format (editorconfig) run: dotnet format --verify-no-changes - continue-on-error: false diff --git a/README.md b/README.md index 4a26ac0..a75c40b 100644 --- a/README.md +++ b/README.md @@ -4,20 +4,20 @@ [![Nuget:Lib](https://img.shields.io/nuget/v/dSPACE.Runtime.InteropServices?label=dSPACE.Runtime.InteropServices&style=flat)](https://www.nuget.org/packages/dSPACE.Runtime.InteropServices/) [![Nuget:LibBuildTask](https://img.shields.io/nuget/v/dSPACE.Runtime.InteropServices?label=dSPACE.Runtime.InteropServices.BuildTasks&style=flat)](https://www.nuget.org/packages/dSPACE.Runtime.InteropServices.BuildTasks/) -[![Release](https://img.shields.io/github/v/release/dspace-group/dscom?label=release)](https://github.com/dspace-group/dscom/releases) -![License](https://img.shields.io/github/license/dspace-group/dscom) -[![dSPACE](https://img.shields.io/badge/-OpenSource%20powered%20by%20dSPACE-blue)](https://www.dspace.com/) - [![Unit Tests](https://github.com/dspace-group/dscom/actions/workflows/unit-test.yaml/badge.svg)](https://github.com/dspace-group/dscom/actions/workflows/unit-test.yaml) [![Example Tests](https://github.com/dspace-group/dscom/actions/workflows/example-test.yaml/badge.svg)](https://github.com/dspace-group/dscom/actions/workflows/example-test.yaml) [![Code Style Check](https://github.com/dspace-group/dscom/actions/workflows/code-style.yaml/badge.svg)](https://github.com/dspace-group/dscom/actions/workflows/code-style.yaml) The command line client `dscom` is a replacement for `tlbexp.exe` and creates and registers TLBs from .NET assemblies. -The `dSPACE.Runtime.InteropServices` library contains various classes and methods for COM. -It can be used in `net5+` or in `net48` projects. With the library you can register assemblies and classes for COM and programmatically generate TLBs at runtime. +With the library `dSPACE.Runtime.InteropServices` you can, among other things, register assemblies and classes for COM and programmatically generate TLBs at runtime. The `dSPACE.Runtime.InteropServices.BuildTasks` library provides build tasks which can be used to automatically generate TLBs at compile time. -> This is an unstable prerelease. Anything may change at any time! +Example: + +```pwsh +PS C:\> dotnet tool install --global dscom +PS C:\> dscom tlbexport myassembly.dll +``` - [dSPACE COM tools](#dspace-com-tools) - [Introducing](#introducing) @@ -271,17 +271,17 @@ This way the build stops, if the type library is not exported. The build task can be parameterized with the following [properties](https://learn.microsoft.com/en-us/visualstudio/msbuild/msbuild-properties?view=vs-2022): -| **Name** | **Description** | -| ---------------------------------------------- | ---------------------------------------------------------------------------------------------- | -| _DsComTlbExt | Extension of the resulting type library.
Default Value: `.tlb` | -| _DsComForceToolUsage | Use DsCom Exe files to create the TLB
Default value: `false` | false | -| DsComTypeLibraryUniqueId | Overwrite the library UUID
Default Value: Empty Guid | -| DsComOverideLibraryName | Overwrite the IDL name of the library.
Default Value: Empty string | -| DsComRegisterTypeLibrariesAfterBuild | Use regasm call after the build to register type library after the build
Default value: `false` | -| DsComTlbExportAutoAddReferences | Add referenced assemblies automatically to type libraries
Default value: `true` | -| DsComTlbExportIncludeReferencesWithoutHintPath | If a `Reference` assembly does not provide a `HintPath` Metadata, the item spec shall be task.
Default value: `false` | -| _DsComExportTypeLibraryTargetFile | Path to the resulting file.
Default value: `$(TargetDir)\$(TargetName)$(_DsComTlbExt)` * | -| _DsComExportTypeLibraryAssemblyFile | Path to the source assembly file.
Default value: `$(TargetPath)` * | +| **Name** | **Description** | +| ---------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------- | +| _DsComTlbExt | Extension of the resulting type library.
Default Value: `.tlb` | +| _DsComForceToolUsage | Use DsCom Exe files to create the TLB
Default value: `false` | false | +| DsComTypeLibraryUniqueId | Overwrite the library UUID
Default Value: Empty Guid | +| DsComOverideLibraryName | Overwrite the IDL name of the library.
Default Value: Empty string | +| DsComRegisterTypeLibrariesAfterBuild | Use regasm call after the build to register type library after the build
Default value: `false` | +| DsComTlbExportAutoAddReferences | Add referenced assemblies automatically to type libraries
Default value: `true` | +| DsComTlbExportIncludeReferencesWithoutHintPath | If a `Reference` assembly does not provide a `HintPath` Metadata, the item spec shall be task.
Default value: `false` | +| _DsComExportTypeLibraryTargetFile | Path to the resulting file.
Default value: `$(TargetDir)\$(TargetName)$(_DsComTlbExt)` * | +| _DsComExportTypeLibraryAssemblyFile | Path to the source assembly file.
Default value: `$(TargetPath)` * | *) This value cannot be overridden. diff --git a/global.json b/global.json index 5eba956..534c5d8 100644 --- a/global.json +++ b/global.json @@ -1,6 +1,6 @@ { "sdk": { - "version": "6.0.406", + "version": "6.0.408", "rollForward": "latestPatch" } } \ No newline at end of file diff --git a/test/ProcessRunner.cs b/test/ProcessRunner.cs new file mode 100644 index 0000000..73ce174 --- /dev/null +++ b/test/ProcessRunner.cs @@ -0,0 +1,194 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Diagnostics; +using System.IO; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.CodeAnalysis.Tools.Utilities +{ + public readonly struct ProcessResult + { + public Process Process { get; } + public int ExitCode { get; } + public ReadOnlyCollection OutputLines { get; } + public ReadOnlyCollection ErrorLines { get; } + + public ProcessResult(Process process, int exitCode, ReadOnlyCollection outputLines, ReadOnlyCollection errorLines) + { + Process = process; + ExitCode = exitCode; + OutputLines = outputLines; + ErrorLines = errorLines; + } + } + + public readonly struct ProcessInfo + { + public Process Process { get; } + public ProcessStartInfo StartInfo { get; } + public Task Result { get; } + + public int Id => Process.Id; + + public ProcessInfo(Process process, ProcessStartInfo startInfo, Task result) + { + Process = process; + StartInfo = startInfo; + Result = result; + } + } + + public static class ProcessRunner + { + public static void OpenFile(string file) + { + if (File.Exists(file)) + { + Process.Start(file); + } + } + + public static ProcessInfo CreateProcess( + string executable, + string arguments, + bool lowPriority = false, + string? workingDirectory = null, + bool captureOutput = false, + bool displayWindow = true, + Dictionary? environmentVariables = null, + Action? onProcessStartHandler = null, + CancellationToken cancellationToken = default) => + CreateProcess( + CreateProcessStartInfo(executable, arguments, workingDirectory, captureOutput, displayWindow, environmentVariables), + lowPriority: lowPriority, + onProcessStartHandler: onProcessStartHandler, + cancellationToken: cancellationToken); + + public static ProcessInfo CreateProcess( + ProcessStartInfo processStartInfo, + bool lowPriority = false, + Action? onProcessStartHandler = null, + CancellationToken cancellationToken = default) + { + var errorLines = new List(); + var outputLines = new List(); + var process = new Process(); + var tcs = new TaskCompletionSource(); + + process.EnableRaisingEvents = true; + process.StartInfo = processStartInfo; + + process.OutputDataReceived += (s, e) => + { + if (e.Data != null) + { + outputLines.Add(e.Data); + } + }; + + process.ErrorDataReceived += (s, e) => + { + if (e.Data != null) + { + errorLines.Add(e.Data); + } + }; + + process.Exited += (s, e) => + { + // We must call WaitForExit to make sure we've received all OutputDataReceived/ErrorDataReceived calls + // or else we'll be returning a list we're still modifying. For paranoia, we'll start a task here rather + // than enter right back into the Process type and start a wait which isn't guaranteed to be safe. + Task.Run(() => + { + process.WaitForExit(); + var result = new ProcessResult( + process, + process.ExitCode, + new ReadOnlyCollection(outputLines), + new ReadOnlyCollection(errorLines)); + tcs.TrySetResult(result); + }); + }; + + _ = cancellationToken.Register(() => + { + if (tcs.TrySetCanceled()) + { + // If the underlying process is still running, we should kill it + if (!process.HasExited) + { + try + { + process.Kill(); + } + catch (InvalidOperationException) + { + // Ignore, since the process is already dead + } + } + } + }); + + process.Start(); + onProcessStartHandler?.Invoke(process); + + if (lowPriority) + { + process.PriorityClass = ProcessPriorityClass.BelowNormal; + } + + if (processStartInfo.RedirectStandardOutput) + { + process.BeginOutputReadLine(); + } + + if (processStartInfo.RedirectStandardError) + { + process.BeginErrorReadLine(); + } + + return new ProcessInfo(process, processStartInfo, tcs.Task); + } + + public static ProcessStartInfo CreateProcessStartInfo( + string executable, + string arguments, + string? workingDirectory = null, + bool captureOutput = false, + bool displayWindow = true, + Dictionary? environmentVariables = null) + { + var processStartInfo = new ProcessStartInfo(executable, arguments); + + if (!string.IsNullOrEmpty(workingDirectory)) + { + processStartInfo.WorkingDirectory = workingDirectory; + } + + if (environmentVariables != null) + { + foreach (var pair in environmentVariables) + { + processStartInfo.EnvironmentVariables[pair.Key] = pair.Value; + } + } + + if (captureOutput) + { + processStartInfo.UseShellExecute = false; + processStartInfo.RedirectStandardOutput = true; + processStartInfo.RedirectStandardError = true; + } + else + { + processStartInfo.CreateNoWindow = !displayWindow; + processStartInfo.UseShellExecute = displayWindow; + } + + return processStartInfo; + } + } +} diff --git a/test/Program.cs b/test/Program.cs new file mode 100644 index 0000000..7f3a9b3 --- /dev/null +++ b/test/Program.cs @@ -0,0 +1,22 @@ +using Microsoft.CodeAnalysis.Tools.Utilities; + +internal class Program +{ + private static int Main(string[] args) + { + TryGetDotNetCliVersion(out string a); + Console.WriteLine(a); + + return 0; + } + + + internal static bool TryGetDotNetCliVersion(out string dotnetVersion) + { + var processInfo = ProcessRunner.CreateProcess("dotnet", "--version", captureOutput: true, displayWindow: false); + var versionResult = processInfo.Result.GetAwaiter().GetResult(); + + dotnetVersion = versionResult.OutputLines[0].Trim(); + return true; + } +} diff --git a/test/test.csproj b/test/test.csproj new file mode 100644 index 0000000..74abf5c --- /dev/null +++ b/test/test.csproj @@ -0,0 +1,10 @@ + + + + Exe + net6.0 + enable + enable + + +