diff --git a/.github/workflows/acceptance-test.yaml b/.github/workflows/acceptance-test.yaml
new file mode 100644
index 00000000..e17e3958
--- /dev/null
+++ b/.github/workflows/acceptance-test.yaml
@@ -0,0 +1,38 @@
+name: Acceptance Test
+
+on:
+ workflow_dispatch:
+ pull_request:
+
+
+jobs:
+ acceptance-test-msbuild:
+ runs-on: windows-2022
+ strategy:
+ matrix:
+ test_suite:
+ - name: OutProc
+ path: outproc
+ - name: 32 Bit
+ path: 32bit
+
+ steps:
+ - uses: actions/checkout@v2
+ with:
+ fetch-depth: 0
+
+ - name: Setup .NET
+ uses: actions/setup-dotnet@v1
+ with:
+ dotnet-version: 6.0.300
+
+ - name: Restore dependencies
+ run: dotnet restore
+
+ - name: Build
+ run: dotnet build -c Release
+
+ - name: Run ${{ matrix.test_suite.Name }} Acceptance test
+ run: .\msbuild-acceptance-test.cmd
+ working-directory: .\examples\${{ matrix.test_suite.path }}\scripts
+ continue-on-error: false
diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml
index 4f8ef17c..f87ca9cf 100644
--- a/.github/workflows/build.yaml
+++ b/.github/workflows/build.yaml
@@ -24,14 +24,11 @@ jobs:
- name: Build
run: dotnet build -c Release
- - name: Check code format (editorconfig)
- run: dotnet format --verify-no-changes
-
- - name: Test (Release)
- run: dotnet test -c Release --no-build --verbosity normal
-
- name: Nuget pack library
run: dotnet pack -c Release src/dscom/dscom.csproj
- name: Nuget pack tool
run: dotnet pack -c Release src/dscom.client/dscom.client.csproj
+
+ - name: Nuget pack build tools
+ run: dotnet pack -c Release src/dscom.build/dscom.build.csproj
diff --git a/.github/workflows/code-style.yaml b/.github/workflows/code-style.yaml
new file mode 100644
index 00000000..d0bec3aa
--- /dev/null
+++ b/.github/workflows/code-style.yaml
@@ -0,0 +1,26 @@
+name: Check code style
+
+on:
+ workflow_dispatch:
+ pull_request:
+
+jobs:
+ check-code-style:
+ runs-on: windows-2022
+
+ steps:
+ - uses: actions/checkout@v2
+ with:
+ fetch-depth: 0
+
+ - name: Setup .NET
+ uses: actions/setup-dotnet@v1
+ with:
+ dotnet-version: 6.0.300
+
+ - name: Restore dependencies
+ run: dotnet restore
+
+ - name: Check code format (editorconfig)
+ run: dotnet format --verify-no-changes
+ continue-on-error: false
diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml
index ff3d932c..8c1ba310 100644
--- a/.github/workflows/release.yaml
+++ b/.github/workflows/release.yaml
@@ -48,6 +48,13 @@ jobs:
run: dotnet nuget push *.nupkg --api-key ${{ secrets.NUGET }} --source https://api.nuget.org/v3/index.json
working-directory: src/dscom.client/bin/Release
+ - name: Nuget pack build tools
+ run: dotnet pack -c Release src/dscom.build/dscom.build.csproj
+
+ - name: Nuget push build tools
+ run: dotnet nuget push *.nupkg --api-key ${{ secrets.NUGET }} --source https://api.nuget.org/v3/index.json
+ working-directory: src/dscom.build/bin/Release
+
- name: Publish 32Bit binary
run: dotnet publish .\src\dscom.client\dscom.client.csproj --no-self-contained -c Release -r win-x86 -f net6.0 /p:PublishSingleFile=true; copy src\dscom.client\bin\Release\net6.0\win-x86\publish\dscom.exe src\dscom.client\bin\Release\net6.0\win-x86\publish\dscom32.exe
diff --git a/.github/workflows/unit-test.yaml b/.github/workflows/unit-test.yaml
new file mode 100644
index 00000000..7af0419e
--- /dev/null
+++ b/.github/workflows/unit-test.yaml
@@ -0,0 +1,29 @@
+name: Run Unit Tests
+
+on:
+ workflow_dispatch:
+ pull_request:
+
+jobs:
+ unit-test:
+ runs-on: windows-2022
+
+ steps:
+ - uses: actions/checkout@v2
+ with:
+ fetch-depth: 0
+
+ - name: Setup .NET
+ uses: actions/setup-dotnet@v1
+ with:
+ dotnet-version: 6.0.300
+
+ - name: Restore dependencies
+ run: dotnet restore
+
+ - name: Build
+ run: dotnet build -c Release
+
+ - name: Unit test
+ run: dotnet test --no-build -c Release
+ continue-on-error: false
diff --git a/.gitignore b/.gitignore
index 44dfe9c5..27619f3f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -11,4 +11,6 @@ Release/
test.results/
.tmp/
_temporary.cs
-.publish/
\ No newline at end of file
+.publish/
+_packages
+*.binlog
diff --git a/README.md b/README.md
index 9bd17a6d..8dd58d25 100644
--- a/README.md
+++ b/README.md
@@ -22,6 +22,7 @@ From the Microsoft documentation:
One main goal is to make `dscom` behave like `tlbexp.exe`.
_Happy IUnknowing and IDispatching ;-)_
+
- [dSPACE COM tools](#dspace-com-tools)
- [Command Line Client](#command-line-client)
- [Installation](#installation)
@@ -33,6 +34,15 @@ _Happy IUnknowing and IDispatching ;-)_
- [Migration notes (mscorelib vs System.Private.CoreLib)](#migration-notes-mscorelib-vs-systemprivatecorelib)
- [Why can I load a .NET Framework library into a .NET application?](#why-can-i-load-a-net-framework-library-into-a-net-application)
- [Limitations](#limitations)
+ - [Build Tasks](#build-tasks)
+ - [Preface](#preface)
+ - [Build task usage](#build-task-usage)
+ - [Using the native build task](#using-the-native-build-task)
+ - [Using the CLI based task](#using-the-cli-based-task)
+ - [Enforcing the usage of the CLI](#enforcing-the-usage-of-the-cli)
+ - [Enforcing to stop the build, if an error occurs](#enforcing-to-stop-the-build-if-an-error-occurs)
+ - [Parameters](#parameters)
+
## Command Line Client
The command-line interface (CLI) tool `dscom` is a replacement for `tlbexp.exe` and `OleView` (View TypeLib).
@@ -255,3 +265,87 @@ classextern forwarder System.Exception
- Color is converted to `OLE_COLOR`(stdole)
- No support for `UnmanagedType.CustomMarshaler`
- No support for .NET Framework assemblies with `AssemblyMetadataAttribute` value ".NETFrameworkAssembly"
+
+## Build Tasks
+
+### Preface
+
+The `dSPACE.Runtime.InteropServices.BuildTasks` assembly and NuGet package provide the ability to create type libraries for a certain assembly at runtime.
+
+For details on the implementation refer to the [documentation](/src/dscom.build/docs/ReadMe.md) section of the repository.
+
+### Build task usage
+
+To create a type library at compile time, simply add a reference to the nuget package, e.g. by using the command line.
+
+```shell
+$ dotnet add package dSPACE.Runtime.InteropServices.Build
+...
+$
+```
+
+The result should be a line as follows in your `.csproj` file:
+
+```xml
+
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+ all
+
+```
+
+**Note**: The extra attribute `NoWarn="NU1701"` is only required, if neither `.NET 4.8` nor `.NET 6.0` are targeted, since dotnet pack will currently not create a .NETStandard 2.0 compliant NuGet Package.
+
+#### Using the native build task
+
+The native build task is automatically selected, if a .NET 4.8 or .NET 6.0 assembly for Windows is being build using an x64 platform.
+
+#### Using the CLI based task
+
+The CLI task is automatically selected, if a .NET Standard 2.0 assembly is build. It is also chosen, if the target platform is set to x86.
+
+#### Enforcing the usage of the CLI
+
+It might be necessary to select the CLI based task. To do so, add the following property to your `.csproj` file:
+
+```XML
+<_DsComForceToolUsage>true
+```
+
+This will enforce the usage of the DsCom as a command-line tool. Please note, that verbose logging will no longer be working.
+
+#### Enforcing to stop the build, if an error occurs
+
+The build tasks puts a warning to the build log, if the desired type library has not been created, even if the backend has reported a success.
+
+This warning is issued with the warning code `DSCOM001`, which can be collected in the `WarningsAsErrors` array:
+
+```XML
+$(WarningsAsErrors);DSCOM001
+```
+
+This way the build stops, if the type library is not exported.
+
+### Parameters
+
+The build task can be parameterized with the following properties:
+
+| **Name** | **Description** | **Default** |
+| ------------- | -------------------- | ----------- |
+| _DsComTlbExt | Extension of the resulting type library. | .tlb |
+| _DsComForceToolUsage | Use DsCom Exe files to create the TLB | false |
+| DsComTypeLibraryUniqueId | Overwrite the library UUID | Empty Guid |
+| DsComRegisterTypeLibrariesAfterBuild | Use regasm call after the build to register type library after the build | false |
+| DsComTlbExportAutoAddReferences | Add referenced assemblies automatically to type libraries | true |
+| DsComTlbExportIncludeReferencesWithoutHintPath | If a `Reference` assembly does not provide a `HintPath` Metadata, the item spec shall be task. | false |
+| _DsComExportTypeLibraryTargetFile | Path to the resulting file. | `$(TargetDir)\$(TargetName)$(_DsComTlbExt)` * |
+| _DsComExportTypeLibraryAssemblyFile | Path to the source assembly file. | `$(TargetPath)` * |
+
+*) This value cannot be overridden.
+
+The build task consumes the following items:
+
+| **Name** | **Description** |
+| ---------------------------- | -------------------------------- |
+| DsComTlbExportTlbReferences | Referenced type library files. |
+| DsComTlbExportReferencePaths | Directories containing type libraries to use for export. |
+| DsComTlbExportAssemblyPaths | Assemblies to add for the export. |
diff --git a/dscom.sln b/dscom.sln
index a7a24b89..c343db29 100644
--- a/dscom.sln
+++ b/dscom.sln
@@ -19,6 +19,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "assembly3", "src\dscom.demo
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "dscom.test", "src\dscom.test\dscom.test.csproj", "{5B402A1B-18B1-4D88-804A-BC0E58EF3730}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "dscom.build", "src\dscom.build\dscom.build.csproj", "{F8F68E57-CFFE-4EA5-9C1A-2CD9223B5D85}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -52,6 +54,10 @@ Global
{1BAB2994-1423-433B-BFF0-F4698B6D4A04}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1BAB2994-1423-433B-BFF0-F4698B6D4A04}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1BAB2994-1423-433B-BFF0-F4698B6D4A04}.Release|Any CPU.Build.0 = Release|Any CPU
+ {F8F68E57-CFFE-4EA5-9C1A-2CD9223B5D85}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {F8F68E57-CFFE-4EA5-9C1A-2CD9223B5D85}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {F8F68E57-CFFE-4EA5-9C1A-2CD9223B5D85}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {F8F68E57-CFFE-4EA5-9C1A-2CD9223B5D85}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{54297095-6CB9-4A75-B9C0-41ADA916C27C} = {0A2E33B4-9DF7-4199-BC39-B0FC9C99FA97}
@@ -61,5 +67,6 @@ Global
{B55E3947-1338-4EDF-A802-B24D91636B31} = {CAAB6257-7EC0-484E-9593-B60CEE8F47D1}
{5B402A1B-18B1-4D88-804A-BC0E58EF3730} = {0A2E33B4-9DF7-4199-BC39-B0FC9C99FA97}
{1BAB2994-1423-433B-BFF0-F4698B6D4A04} = {CAAB6257-7EC0-484E-9593-B60CEE8F47D1}
+ {F8F68E57-CFFE-4EA5-9C1A-2CD9223B5D85} = {0A2E33B4-9DF7-4199-BC39-B0FC9C99FA97}
EndGlobalSection
EndGlobal
diff --git a/examples/32bit/comtestdotnet/comtestdotnet.csproj b/examples/32bit/comtestdotnet/comtestdotnet.csproj
index 273af8c9..4cb3f2bf 100644
--- a/examples/32bit/comtestdotnet/comtestdotnet.csproj
+++ b/examples/32bit/comtestdotnet/comtestdotnet.csproj
@@ -1,5 +1,10 @@
+
+ false
+ true
+
+
net6.0;net48
10.0
@@ -8,15 +13,16 @@
disabled
enable
true
+ $(WarningsAsErrors);DSCOM001
+
-
+
-
\ No newline at end of file
diff --git a/examples/32bit/scripts/msbuild-acceptance-test.cmd b/examples/32bit/scripts/msbuild-acceptance-test.cmd
new file mode 100644
index 00000000..b040ed86
--- /dev/null
+++ b/examples/32bit/scripts/msbuild-acceptance-test.cmd
@@ -0,0 +1,72 @@
+@ECHO off
+
+SET root=%~dp0\..\..\..\
+
+PUSHD %root%
+
+dotnet pack -p:Configuration=Release
+
+IF NOT EXIST %root%\_packages MKDIR _packages
+
+XCOPY /Y /I /C /F .\src\dscom\bin\Release\*.nupkg _packages\
+XCOPY /Y /I /C /F .\src\dscom.build\bin\Release\*.nupkg _packages\
+
+POPD
+
+PUSHD %~dp0\..\comtestdotnet
+
+dotnet msbuild -nodeReuse:False -t:Clean -p:Configuration=Release -p:PerformAcceptanceTest=Runtime
+
+dotnet msbuild -nodeReuse:False -t:Clean -p:Configuration=Release -p:PerformAcceptanceTest=Runtime
+
+dotnet add comtestdotnet.csproj package --prerelease -s %root%\_packages dSPACE.Runtime.InteropServices.BuildTasks
+
+dotnet msbuild -nodeReuse:False -t:Restore -p:Configuration=Release -p:Platform=x64 -p:TargetPlatform=net48 -p:PerformAcceptanceTest=Runtime
+dotnet msbuild -nodeReuse:False -t:Build -p:Configuration=Release -p:Platform=x64 -p:TargetPlatform=net48 -p:PerformAcceptanceTest=Runtime -bl
+SET ERRUNTIMEX64_NET48=%ERRORLEVEL%
+
+dotnet msbuild -nodeReuse:False -t:Restore -p:Configuration=Release -p:Platform=x64 -p:TargetPlatform=net6.0-windows -p:PerformAcceptanceTest=Runtime
+dotnet msbuild -nodeReuse:False -t:Build -p:Configuration=Release -p:Platform=x64 -p:TargetPlatform=net6.0-windows -p:PerformAcceptanceTest=Runtime -bl
+SET ERRUNTIMEX64_NET60=%ERRORLEVEL%
+
+dotnet msbuild -nodeReuse:False -t:Restore -p:Configuration=Release -p:Platform=x86 -p:TargetPlatform=net48 -p:PerformAcceptanceTest=Runtime
+dotnet msbuild -nodeReuse:False -t:Build -p:Configuration=Release -p:Platform=x86 -p:TargetPlatform=net48 -p:PerformAcceptanceTest=Runtime -bl
+SET ERRUNTIMEX86_NET48=%ERRORLEVEL%
+
+dotnet msbuild -nodeReuse:False -t:Restore -p:Configuration=Release -p:Platform=x86 -p:TargetPlatform=net6.0-windows -p:PerformAcceptanceTest=Runtime
+dotnet msbuild -nodeReuse:False -t:Build -p:Configuration=Release -p:Platform=x86 -p:TargetPlatform=net6.0-windows -p:PerformAcceptanceTest=Runtime -bl
+SET ERRUNTIMEX86_NET60=%ERRORLEVEL%
+
+dotnet remove comtestdotnet.csproj package dSPACE.Runtime.InteropServices.BuildTasks
+
+POPD
+
+SetLocal EnableDelayedExpansion
+
+SET EXITCODE=0
+
+IF NOT "%ERRUNTIMEX64_NET48%" == "0" (
+ SET EXITCODE=1
+ ECHO "Runtime specific acceptance test for platform x64 using .NET FullFramework 4.8 failed."
+)
+
+IF NOT "%ERRUNTIMEX64_NET60%" == "0" (
+ SET EXITCODE=1
+ ECHO "Runtime specific acceptance test for platform x64 using .NET 6.0 failed."
+)
+
+IF NOT "%ERRUNTIMEX86_NET48%" == "0" (
+ SET EXITCODE=1
+ ECHO "Runtime specific acceptance test for platform x86 using .NET FullFramework 4.8 failed."
+)
+
+IF NOT "%ERRUNTIMEX86_NET60%" == "0" (
+ SET EXITCODE=1
+ ECHO "Runtime specific acceptance test for platform x64 using .NET 6.0 failed."
+)
+
+IF "%EXITCODE%" == "0" (
+ ECHO "Acceptance test completed successfully."
+)
+
+EXIT /B %EXITCODE%
diff --git a/examples/outproc/scripts/msbuild-acceptance-test.cmd b/examples/outproc/scripts/msbuild-acceptance-test.cmd
new file mode 100644
index 00000000..c97fb3be
--- /dev/null
+++ b/examples/outproc/scripts/msbuild-acceptance-test.cmd
@@ -0,0 +1,56 @@
+@ECHO off
+
+SET root=%~dp0\..\..\..\
+
+PUSHD %root%
+
+dotnet pack -p:Configuration=Release
+
+IF NOT EXIST %root%\_packages MKDIR _packages
+
+XCOPY /Y /I /C /F .\src\dscom\bin\Release\*.nupkg _packages\
+XCOPY /Y /I /C /F .\src\dscom.build\bin\Release\*.nupkg _packages\
+
+POPD
+
+PUSHD %~dp0\..
+
+dotnet msbuild -nodeReuse:False -t:Clean -p:Configuration=Release -p:PerformAcceptanceTest=Runtime
+
+dotnet msbuild -nodeReuse:False -t:Restore -p:Configuration=Release -p:PerformAcceptanceTest=Runtime
+
+dotnet add server\common\contract.csproj package --prerelease -s %root%\_packages dSPACE.Runtime.InteropServices.BuildTasks
+
+dotnet msbuild -nodeReuse:False -t:Restore -p:Configuration=Release -p:PerformAcceptanceTest=Runtime
+dotnet msbuild -nodeReuse:False -t:Build -p:Configuration=Release -p:PerformAcceptanceTest=Runtime -bl
+SET ERRUNTIME=%ERRORLEVEL%
+
+dotnet msbuild -nodeReuse:False -t:Clean -p:Configuration=Release -p:PerformAcceptanceTest=Runtime
+
+dotnet msbuild -nodeReuse:False -t:Restore -p:Configuration=Release -p:PerformAcceptanceTest=NetStandard
+dotnet msbuild -nodeReuse:False -t:Build -p:Configuration=Release -p:PerformAcceptanceTest=NetStandard -bl
+SET ERSTANDARD=%ERRORLEVEL%
+
+dotnet remove server\common\contract.csproj package dSPACE.Runtime.InteropServices.BuildTasks
+
+POPD
+
+SetLocal EnableDelayedExpansion
+
+SET EXITCODE=0
+
+IF NOT "%ERRUNTIME%" == "0" (
+ SET EXITCODE=1
+ ECHO "Runtime specific acceptance test failed."
+)
+
+IF NOT "%ERSTANDARD%" == "0" (
+ SET EXITCODE=1
+ ECHO ".NET Standard 2.0 specific acceptance test failed."
+)
+
+IF "%EXITCODE%" == "0" (
+ ECHO "Acceptance test completed successfully."
+)
+
+EXIT /B %EXITCODE%
diff --git a/examples/outproc/server/common/RegistryHelper.cs b/examples/outproc/server/common/RegistryHelper.cs
index aaecb201..13683fb9 100644
--- a/examples/outproc/server/common/RegistryHelper.cs
+++ b/examples/outproc/server/common/RegistryHelper.cs
@@ -25,7 +25,7 @@ public static void RegisterOutProcServer(string versionIndependentProgId, str
exePath +=".exe";
}
string progId = $"{versionIndependentProgId}.{version}";
- GuidAttribute guid = (GuidAttribute)typeof(T).GetCustomAttributes().FirstOrDefault();
+ GuidAttribute? guid = (GuidAttribute?)typeof(T).GetCustomAttributes().FirstOrDefault();
if (guid == null)
{
throw new ArgumentException("Coclass guid not set!");
@@ -93,7 +93,7 @@ public static void RegisterOutProcServer(string versionIndependentProgId, str
public static void UnregisterOutProcServer(string versionIndependentProgId, string version)
{
- GuidAttribute guid = (GuidAttribute)typeof(T).GetCustomAttributes().FirstOrDefault();
+ GuidAttribute? guid = (GuidAttribute?)typeof(T).GetCustomAttributes().FirstOrDefault();
if (guid == null)
{
throw new ArgumentException("Coclass guid not set!");
diff --git a/examples/outproc/server/common/contract.csproj b/examples/outproc/server/common/contract.csproj
index 329741c9..a5939d8d 100644
--- a/examples/outproc/server/common/contract.csproj
+++ b/examples/outproc/server/common/contract.csproj
@@ -1,18 +1,30 @@
- netstandard2.0
+ false
+ true
+
+
+
+ netstandard2.0
+ net6.0-windows;net48
enable
enable
10.0
+ $(WarningsAsErrors);DSCOM001
-
+
+
+
+
+ $(NoWarn);NU1701
+
diff --git a/src/dscom.build/DefaultBuildContext.cs b/src/dscom.build/DefaultBuildContext.cs
new file mode 100644
index 00000000..0b3d492d
--- /dev/null
+++ b/src/dscom.build/DefaultBuildContext.cs
@@ -0,0 +1,359 @@
+// Copyright 2022 dSPACE GmbH, Carsten Igel and Contributors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+using System.Reflection;
+#if NET5_0_OR_GREATER
+using System.Runtime.Loader;
+#endif
+using System.Security;
+using Microsoft.Build.Framework;
+using Microsoft.Build.Utilities;
+
+using COMException = System.Runtime.InteropServices.COMException;
+using SystemInteropServices = System.Runtime.InteropServices;
+
+namespace dSPACE.Runtime.InteropServices.BuildTasks;
+
+///
+/// Default implementation of the interface
+/// using as implementation for conversion
+/// and as implementation for
+/// event handling.
+///
+internal sealed class DefaultBuildContext : IBuildContext
+{
+ ///
+ public bool IsRunningOnWindows => SystemInteropServices.RuntimeInformation.IsOSPlatform(SystemInteropServices.OSPlatform.Windows);
+
+#if NET5_0_OR_GREATER
+ ///
+ public string RuntimeDescription => $"{SystemInteropServices.RuntimeInformation.OSDescription} {SystemInteropServices.RuntimeInformation.OSArchitecture} ({SystemInteropServices.RuntimeInformation.ProcessArchitecture} [{SystemInteropServices.RuntimeInformation.RuntimeIdentifier} - {SystemInteropServices.RuntimeInformation.FrameworkDescription}])";
+#else
+ ///
+ public string RuntimeDescription => $"{SystemInteropServices.RuntimeInformation.OSDescription} {SystemInteropServices.RuntimeInformation.OSArchitecture} ({SystemInteropServices.RuntimeInformation.ProcessArchitecture} [{SystemInteropServices.RuntimeInformation.FrameworkDescription}])";
+#endif
+
+ ///
+ public bool EnsureFileExists(string? fileNameAndPath)
+ {
+ return File.Exists(fileNameAndPath);
+ }
+
+ ///
+ public bool EnsureDirectoryExists(string? directoryPath)
+ {
+ return Directory.Exists(directoryPath);
+ }
+
+ ///
+ public bool ConvertAssemblyToTypeLib(TypeLibConverterSettings settings, TaskLoggingHelper log)
+ {
+ // Load assembly from file.
+#if NET5_0_OR_GREATER
+ var loadContext = CreateLoadContext(settings, log);
+ var assembly = LoadAssembly(settings, loadContext, log);
+#else
+ log.LogWarning("Unloading assemblies is only supported with .NET 5 or later. Please remove all remaining MsBuild processes before rebuild.");
+ var assembly = LoadAssembly(settings, log);
+#endif
+
+ if (assembly is null)
+ {
+ log.LogWarning("Failed to load assembly {0}. Task failed.", settings.Assembly);
+ return false;
+ }
+
+ try
+ {
+ // Create type library converter.
+ var converter = new TypeLibConverter();
+
+ // Create event handler.
+ var sink = new LoggingTypeLibExporterSink(log);
+ // create conversion.
+ var tlb = converter.ConvertAssemblyToTypeLib(assembly, settings, sink);
+ if (tlb == null)
+ {
+ log.LogError("The following type library could not be created successfully: {0}. Reason: Operation was not successful.", settings.Out);
+ }
+ else
+ {
+ log.LogMessage(MessageImportance.High, "Finished generation of the following type library: {0}", settings.Out);
+ }
+
+ if (!File.Exists(settings.Out))
+ {
+ log.LogWarning(
+ null,
+ "DSCOM001",
+ null,
+ null,
+ 0,
+ 0,
+ 0,
+ 0,
+ "Could not find the type library at the following location: {0}", settings.Out);
+ }
+
+ return tlb != null;
+ }
+ catch (COMException e)
+ {
+ log.LogErrorFromException(e, false, true, settings.Assembly);
+ return false;
+ }
+#if NET5_0_OR_GREATER
+ finally
+ {
+ try
+ {
+ loadContext.Unload();
+ }
+ catch (InvalidOperationException)
+ {
+ log.LogWarning("Failed to unload the assembly load context.");
+ }
+ }
+#endif
+ }
+
+#if NET5_0_OR_GREATER
+ ///
+ /// Creates an instance of that will
+ /// take care of the loading and unloading the target assemblies and
+ /// can be unloaded afterwards.
+ ///
+ /// The type library settings.
+ /// An that can be unloaded.
+ private static AssemblyLoadContext CreateLoadContext(TypeLibConverterSettings settings, TaskLoggingHelper log)
+ {
+ var loadContext = new AssemblyLoadContext($"msbuild-load-ctx-{Guid.NewGuid()}", true);
+ loadContext.Resolving += (ctx, name) =>
+ {
+ if (TryResolveAssemblyFromSettings(name.Name ?? string.Empty, settings, log, out var assemblyPath))
+ {
+ return ctx.LoadFromAssemblyPath(assemblyPath);
+ }
+
+ log.LogWarning("Failed to resolve {0} from the following files: {1}", name.Name, string.Join(", ", settings.ASMPath));
+
+ return default;
+ };
+
+ return loadContext;
+ }
+
+ ///
+ /// Tries to load the assembly specified in the using the specified
+ /// . If the assembly cannot be loaded, the result will be null.
+ ///
+ /// The settings.
+ /// The assembly load context.
+ /// The log to write messages to.
+ /// The assembly loaded.
+ private static Assembly? LoadAssembly(TypeLibConverterSettings settings, AssemblyLoadContext loadContext, TaskLoggingHelper log)
+ {
+ Assembly assembly;
+ try
+ {
+ assembly = loadContext.LoadFromAssemblyPath(settings.Assembly);
+ }
+ catch (Exception e) when
+ (e is ArgumentNullException
+ or FileNotFoundException
+ or FileLoadException
+ or BadImageFormatException
+ or SecurityException
+ or ArgumentException
+ or PathTooLongException)
+ {
+ log.LogErrorFromException(e, true, true, settings.Assembly);
+ try
+ {
+ loadContext.Unload();
+ }
+ catch (InvalidOperationException)
+ {
+ log.LogWarning("Failed to unload the following assembly: {0}.", settings.Assembly);
+ }
+
+ return default;
+ }
+
+ return assembly;
+ }
+#else
+ ///
+ /// Tries to load the assembly specified in the using the current
+ /// . If the assembly cannot be loaded, the result will be null.
+ ///
+ /// The settings.
+ /// The log to write messages to.
+ /// The assembly loaded.
+ private static Assembly? LoadAssembly(TypeLibConverterSettings settings, TaskLoggingHelper log)
+ {
+ try
+ {
+ var content = File.ReadAllBytes(settings.Assembly);
+ var appDomain = AppDomain.CurrentDomain;
+ var resolveHandler = CreateResolveHandler(settings, log);
+ try
+ {
+ appDomain.AssemblyResolve += resolveHandler;
+ return appDomain.Load(content);
+ }
+ finally
+ {
+ appDomain.AssemblyResolve -= resolveHandler;
+ }
+ }
+ catch (Exception e) when
+ (e is ArgumentNullException
+ or PathTooLongException
+ or DirectoryNotFoundException
+ or IOException
+ or UnauthorizedAccessException
+ or FileNotFoundException
+ or NotSupportedException
+ or SecurityException)
+ {
+ log.LogErrorFromException(e, true, true, settings.Assembly);
+ return default;
+ }
+ }
+
+ ///
+ /// Creates an that tries to look up a dependent assembly.
+ ///
+ /// The conversion settings.
+ /// The task logging helper.
+ /// A new resolve event handler instance.
+ private static ResolveEventHandler CreateResolveHandler(TypeLibConverterSettings settings, TaskLoggingHelper log)
+ {
+ Assembly? AssemblyResolveClosure(object? sender, ResolveEventArgs args)
+ {
+ if (TryResolveAssemblyFromSettings(args.Name ?? string.Empty, settings, log, out var assemblyPath))
+ {
+ return AppDomain.CurrentDomain.Load(assemblyPath);
+ }
+
+ log.LogWarning("Failed to resolve assembly: {0}", args.Name);
+ return default;
+ }
+
+ return AssemblyResolveClosure;
+ }
+#endif
+
+ ///
+ /// Tries to resolve the managed assembly with the specified from the .
+ /// If this method returns true, the resolved file path will be written to .
+ ///
+ /// The name of the assembly file to load (without extension).
+ /// The settings for type conversions.
+ /// The logging helper.
+ /// Path to resolved file.
+ /// true, if the assembly could be resolved; false otherwise.
+ private static bool TryResolveAssemblyFromSettings(string assemblyFileName, TypeLibConverterSettings settings, TaskLoggingHelper log, out string assemblyPath)
+ {
+ var validAssemblyExtensions = new string[] { ".dll", ".exe" };
+ if (TryResolveAssemblyFromReferencedFiles(assemblyFileName, settings, log, validAssemblyExtensions, out assemblyPath)
+ || TryResolveAssemblyFromAdjacentFiles(assemblyFileName, settings, log, validAssemblyExtensions, out assemblyPath))
+ {
+ return true;
+ }
+
+ log.LogWarning("Failed to resolve {0} from the following files: {1}", assemblyFileName, string.Join(", ", settings.ASMPath));
+ assemblyPath = string.Empty;
+ return false;
+ }
+
+ ///
+ /// Tries to resolve the managed assembly with the specified from the
+ /// using the property to identify the assembly.
+ /// If this method returns true, the resolved file path will be written to .
+ ///
+ /// The name of the assembly file to load (without extension).
+ /// The settings for type conversions.
+ /// The logging helper.
+ /// Any extension that might be considered as valid assembly extension.
+ /// Path to resolved file.
+ /// true, if the assembly could be resolved; false otherwise.
+ private static bool TryResolveAssemblyFromReferencedFiles(string assemblyFileName, TypeLibConverterSettings settings, TaskLoggingHelper log, string[] validAssemblyExtensions, out string assemblyPath)
+ {
+ log.LogMessage(MessageImportance.Low, "Trying to resolve assembly {0} from referenced files.", assemblyFileName);
+ foreach (var path in settings.ASMPath)
+ {
+ var currentAssemblyFileName = Path.GetFileName(path) ?? string.Empty;
+ log.LogMessage(MessageImportance.Low, "Current file is {0}. Maybe it matches.", path);
+ foreach (var extension in validAssemblyExtensions)
+ {
+ var possibleFileName = assemblyFileName + extension;
+ log.LogMessage(MessageImportance.Low, "Trying to resolve assembly {0} as {1}.", assemblyFileName, possibleFileName);
+ if (StringComparer.InvariantCultureIgnoreCase.Equals(possibleFileName, currentAssemblyFileName)
+ && File.Exists(path))
+ {
+ log.LogMessage(MessageImportance.Low, "Assembly resolved as {0}.", path);
+ assemblyPath = path;
+ return true;
+ }
+ }
+ }
+
+ assemblyPath = string.Empty;
+ return false;
+ }
+
+ ///
+ /// Tries to resolve the managed assembly with the specified from the
+ /// using the property to look up directories that might contain the file.
+ /// If this method returns true, the resolved file path will be written to .
+ ///
+ /// The name of the assembly file to load (without extension).
+ /// The settings for type conversions.
+ /// The logging helper.
+ /// Any extension that might be considered as valid assembly extension.
+ /// Path to resolved file.
+ /// true, if the assembly could be resolved; false otherwise.
+ private static bool TryResolveAssemblyFromAdjacentFiles(string assemblyFileName, TypeLibConverterSettings settings, TaskLoggingHelper log, string[] validAssemblyExtensions, out string assemblyPath)
+ {
+ log.LogMessage(MessageImportance.Low, "Trying to resolve assembly {0} from adjacent files.", assemblyFileName);
+ foreach (var path in settings.ASMPath)
+ {
+ var currentAssemblyFileName = Path.GetFileName(path) ?? string.Empty;
+ var assemblyDirectoryName = Path.GetDirectoryName(path) ?? string.Empty;
+ if (string.IsNullOrWhiteSpace(assemblyDirectoryName))
+ {
+ continue;
+ }
+
+ log.LogMessage(MessageImportance.Low, "Current directory to look at is {0}. Maybe it matches.", assemblyDirectoryName);
+ foreach (var extension in validAssemblyExtensions)
+ {
+ var possibleFileName = assemblyFileName + extension;
+ var possibleAssemblyFilePath = Path.Combine(assemblyDirectoryName, possibleFileName);
+ log.LogMessage(MessageImportance.Low, "Trying to resolve assembly {0} as {1}.", assemblyFileName, possibleAssemblyFilePath);
+ if (File.Exists(possibleAssemblyFilePath))
+ {
+ log.LogMessage(MessageImportance.Low, "Assembly resolved as {0}.", possibleAssemblyFilePath);
+ assemblyPath = possibleAssemblyFilePath;
+ return true;
+ }
+ }
+ }
+
+ assemblyPath = string.Empty;
+ return false;
+ }
+}
diff --git a/src/dscom.build/DsComPackaging.targets b/src/dscom.build/DsComPackaging.targets
new file mode 100644
index 00000000..24e4047b
--- /dev/null
+++ b/src/dscom.build/DsComPackaging.targets
@@ -0,0 +1,148 @@
+
+
+
+
+ $(MsBuildThisFileDirectory)..\dscom.client\
+ $(MsBuildThisFileDirectory)obj\tools\
+ PackForBuildTaskTools=true;PublishSingleFile=true;SelfContained=false;Configuration=Release;TargetFramework=$(DsComBuildTargetFrameWork)
+
+
+
+
+ $(GenerateNuspecDependsOn);DsComBuildPackTaskDependencies;DsComBuildPackTool
+
+
+
+
+
+
+ <_PackageFiles Include="$(MsBuildThisFileDirectory)\bin\$(Configuration)\*\dSPACE.Runtime.InteropServices.dll">
+ build\_dscom%(RecursiveDir)
+ false
+ Content
+
+
+
+
+
+
+
+ <_PackageFiles Include="$(DsComClientToolsTargetDir)\x64\dscom.exe">
+ tools\x64\
+ false
+ Content
+
+ <_PackageFiles Include="$(DsComClientToolsTargetDir)\x86\dscom.exe">
+ tools\x86\
+ false
+ Content
+
+
+
+
+
+
+
+ <_TargetFrameworks Include="netstandard2.0" />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/dscom.build/FileSystemChecks.cs b/src/dscom.build/FileSystemChecks.cs
new file mode 100644
index 00000000..4fb16e57
--- /dev/null
+++ b/src/dscom.build/FileSystemChecks.cs
@@ -0,0 +1,161 @@
+// Copyright 2022 dSPACE GmbH, Carsten Igel and Contributors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+using Microsoft.Build.Utilities;
+
+namespace dSPACE.Runtime.InteropServices.BuildTasks;
+
+///
+/// Implementation of file system contracts and the corresponding checks using the logging mechanisms of MsBuild.
+///
+internal sealed class FileSystemChecks
+{
+ ///
+ /// The logging mechanism from the task.
+ ///
+ private readonly TaskLoggingHelper _log;
+
+ ///
+ /// The build context.
+ ///
+ private readonly IBuildContext _context;
+
+ ///
+ /// Creates a new instance of the class
+ /// using the supplied to write messages to.
+ ///
+ /// The log to apply.
+ /// The context to apply.
+ internal FileSystemChecks(TaskLoggingHelper log, IBuildContext context)
+ {
+ _log = log;
+ _context = context;
+ }
+
+ ///
+ /// Verifies, that the specified is a valid file.
+ /// If a non-existing file shall be treated as an error, the corresponding parameter
+ /// must be set to true and the check will fail. The result of the check will be stored in the
+ /// parameter.
+ ///
+ /// The files to check.
+ /// If set to true, the check will fail, if the file does not exist.
+ /// The result of any previous check. If true is supplied,
+ /// the result can remain true. Otherwise the result will always be false, even, if the check succeeds.
+ internal void VerifyFilePresent(string fileSystemReference, bool treatAsError, ref bool checkResult)
+ {
+ checkResult = checkResult && LogCheckIfFileSystemEntryIsMissing(
+ _context.EnsureFileExists,
+ fileSystemReference,
+ treatAsError,
+ "The following file is required, but does not exist: {0}",
+ fileSystemReference);
+ }
+
+ ///
+ /// Verifies, that the specified are valid files.
+ /// If a non-existing file shall be treated as an error, the corresponding parameter
+ /// must be set to true and the check will fail. The result of the check will be stored in the
+ /// parameter.
+ ///
+ /// The files to check.
+ /// If set to true, the check will fail, if at least one file does not exist.
+ /// The result of any previous check. If true is supplied,
+ /// the result can remain true. Otherwise the result will always be false, even, if the check succeeds.
+ internal void VerifyFilesPresent(IReadOnlyCollection fileSystemReferences, bool treatAsError, ref bool checkResult)
+ {
+ foreach (var possibleFileSystemEntry in fileSystemReferences)
+ {
+ VerifyFilePresent(possibleFileSystemEntry, treatAsError, ref checkResult);
+ }
+ }
+
+ ///
+ /// Verifies, that the specified is a valid directory.
+ /// If a non-existing file shall be treated as an error, the corresponding parameter
+ /// must be set to true and the check will fail. The result of the check will be stored in the
+ /// parameter.
+ ///
+ /// The directory to check.
+ /// If set to true, the check will fail, if the directory does not exist.
+ /// The result of any previous check. If true is supplied,
+ /// the result can remain true. Otherwise the result will always be false, even, if the check succeeds.
+ internal void VerifyDirectoryPresent(string fileSystemReference, bool treatAsError, ref bool checkResult)
+ {
+ checkResult = checkResult && LogCheckIfFileSystemEntryIsMissing(
+ _context.EnsureDirectoryExists,
+ fileSystemReference,
+ treatAsError,
+ "The following file is required, but does not exist: {0}",
+ fileSystemReference);
+ }
+
+ ///
+ /// Verifies, that the specified are valid directories.
+ /// If a non-existing file shall be treated as an error, the corresponding parameter
+ /// must be set to true and the check will fail. The result of the check will be stored in the
+ /// parameter.
+ ///
+ /// The directories to check.
+ /// If set to true, the check will fail, if at least one directory does not exist.
+ /// The result of any previous check. If true is supplied,
+ /// the result can remain true. Otherwise the result will always be false, even, if the check succeeds.
+ internal void VerifyDirectoriesPresent(IReadOnlyCollection fileSystemReferences, bool treatAsError, ref bool checkResult)
+ {
+ foreach (var possibleFileSystemEntry in fileSystemReferences)
+ {
+ VerifyDirectoryPresent(possibleFileSystemEntry, treatAsError, ref checkResult);
+ }
+ }
+
+
+ ///
+ /// Performs the specified method using the .
+ /// If the check fails, the specified will be issued to the log.
+ /// If no error is issued, the method will return true.
+ ///
+ /// The check to apply.
+ /// The file system entry to check.
+ /// If set to true, the will be issued as error; else a warning shall be submitted.
+ /// The message to log.
+ /// Formatting arguments for the .
+ /// true, if the check is issued no error; false otherwise.
+ private bool LogCheckIfFileSystemEntryIsMissing(Func performCheck, string fileSystemEntry, bool treatAsError, string message, params object[] args)
+ {
+ var flag = performCheck(fileSystemEntry);
+ if (!flag)
+ {
+ WriteMessageToLog(treatAsError, message, args);
+ }
+
+ return !treatAsError || flag;
+ }
+
+ ///
+ /// Writes a message to the log referenced by this instance.
+ ///
+ /// If set to true, the message will be treated as an error. A warning will be issued otherwise.
+ /// The message to supply.
+ /// The arguments to format the message.
+ private void WriteMessageToLog(bool treatAsError, string message, params object[] args)
+ {
+ Action logger = _log.LogWarning;
+ if (treatAsError)
+ {
+ logger = _log.LogError;
+ }
+
+ logger(message, args);
+ }
+}
diff --git a/src/dscom.build/IBuildContext.cs b/src/dscom.build/IBuildContext.cs
new file mode 100644
index 00000000..62b2b172
--- /dev/null
+++ b/src/dscom.build/IBuildContext.cs
@@ -0,0 +1,64 @@
+// Copyright 2022 dSPACE GmbH, Carsten Igel and Contributors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+using Microsoft.Build.Utilities;
+
+namespace dSPACE.Runtime.InteropServices.BuildTasks;
+
+///
+/// Interface representing the execution and build context.
+/// This interface is used to separate task execution from
+/// tlb conversion.
+/// Hence implementations will perform any TLB conversion.
+///
+public interface IBuildContext
+{
+ ///
+ /// Gets a value indicating whether the current run-time is windows-based or not.
+ ///
+ bool IsRunningOnWindows { get; }
+
+ ///
+ /// Gets a verbatim description of the current run-time.
+ ///
+ string RuntimeDescription { get; }
+
+ ///
+ /// When implemented it will return, whether the specified
+ /// points to a valid file or not.
+ ///
+ /// The name and path of the file.
+ /// true, if the file exists; false otherwise.
+ bool EnsureFileExists(string? fileNameAndPath);
+
+ ///
+ /// When implemented it will return, whether the specified
+ /// points to a valid directory or not.
+ ///
+ /// The name and path of the directory.
+ /// true, if the directory exists; false otherwise.
+ bool EnsureDirectoryExists(string? directoryPath);
+
+ ///
+ /// When implemented in a derived class, the conversion will take place
+ /// trying to load the assembly specified in the
+ /// of the specified object and convert it to the type library specified in
+ /// the of the same parameter.
+ /// Errors, warnings and conversion messages will be written to the build .
+ ///
+ /// The conversion settings to apply to the built-in converter.
+ /// The log to write error messages to.
+ /// true, if the conversion has taken place successfully; false otherwise.
+ bool ConvertAssemblyToTypeLib(TypeLibConverterSettings settings, TaskLoggingHelper log);
+}
diff --git a/src/dscom.build/LoggingTypeLibExporterSink.cs b/src/dscom.build/LoggingTypeLibExporterSink.cs
new file mode 100644
index 00000000..d544853b
--- /dev/null
+++ b/src/dscom.build/LoggingTypeLibExporterSink.cs
@@ -0,0 +1,61 @@
+// Copyright 2022 dSPACE GmbH, Carsten Igel and Contributors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+using System.Reflection;
+using Microsoft.Build.Framework;
+using Microsoft.Build.Utilities;
+
+namespace dSPACE.Runtime.InteropServices.BuildTasks;
+
+///
+/// Implementation of the a
+/// forwarding all messages to the MsBuild log.
+///
+internal sealed class LoggingTypeLibExporterSink : ITypeLibExporterNotifySink
+{
+ ///
+ /// The logging sink.
+ ///
+ private readonly TaskLoggingHelper _log;
+
+ ///
+ /// Creates a new instance of the
+ /// using the specified as logging target.
+ ///
+ /// The log to write to.
+ internal LoggingTypeLibExporterSink(TaskLoggingHelper log)
+ {
+ _log = log;
+ }
+
+ ///
+ void ITypeLibExporterNotifySink.ReportEvent(ExporterEventKind eventKind, int eventCode, string eventMsg)
+ {
+ var importance = eventKind switch
+ {
+ ExporterEventKind.NOTIF_TYPECONVERTED => MessageImportance.Low,
+ ExporterEventKind.NOTIF_CONVERTWARNING => MessageImportance.Normal,
+ ExporterEventKind.ERROR_REFTOINVALIDASSEMBLY => MessageImportance.High,
+ _ => MessageImportance.High,
+ };
+
+ _log.LogMessage(importance, "Received {0} event. Event Code is {1}: {2}", Enum.GetName(typeof(ExporterEventKind), eventKind), eventCode, eventMsg);
+ }
+
+ ///
+ object? ITypeLibExporterNotifySink.ResolveRef(Assembly assembly)
+ {
+ return default;
+ }
+}
diff --git a/src/dscom.build/TlbExport.cs b/src/dscom.build/TlbExport.cs
new file mode 100644
index 00000000..33b145cd
--- /dev/null
+++ b/src/dscom.build/TlbExport.cs
@@ -0,0 +1,237 @@
+// Copyright 2022 dSPACE GmbH, Carsten Igel and Contributors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#if NET5_0_OR_GREATER
+using System.Runtime.Loader;
+#else
+using System.Reflection;
+#endif
+
+using Microsoft.Build.Framework;
+
+namespace dSPACE.Runtime.InteropServices.BuildTasks;
+
+///
+/// Represents the task to export a type library file from an assembly.
+///
+public sealed class TlbExport : Microsoft.Build.Utilities.Task
+{
+ ///
+ /// The build context applied to this instance.
+ ///
+ private readonly IBuildContext _context;
+
+ ///
+ /// Creates a new instance of the
+ /// export class using the specified build context.
+ ///
+ /// The build context to apply.
+ public TlbExport(IBuildContext context)
+ {
+ _context = context;
+ }
+
+ ///
+ /// Creates a new instance of the
+ /// class using the default build context.
+ ///
+ public TlbExport() : this(new DefaultBuildContext())
+ {
+ }
+
+ ///
+ /// Gets or sets the COM Type Library unique id. If this value
+ /// will be equal to , the id of the
+ /// resulting COM type library will be not be changed.
+ ///
+ public string TlbOverriddenId { get; set; } = Guid.Empty.ToString();
+
+ ///
+ /// Gets or sets the name of the resulting COM type library file (tlb).
+ /// This value must be set for this task to work and cannot be left empty.
+ /// Existing files will be overridden.
+ ///
+ [Required]
+ public string TargetFile { get; set; } = string.Empty;
+
+ ///
+ /// Gets or sets the name of the incoming managed assembly file (dll or exe).
+ /// This value must be set for this task to work and cannot be left empty.
+ /// The file must exist and be readable.
+ ///
+ [Required]
+ public string SourceAssemblyFile { get; set; } = string.Empty;
+
+ ///
+ /// Gets or sets the additional type library files required for conversion.
+ ///
+ public ITaskItem[] TypeLibraryReferences { get; set; } = Array.Empty();
+
+ ///
+ /// Gets or sets the additional type library search paths required for conversion.
+ ///
+ public ITaskItem[] TypeLibraryReferencePaths { get; set; } = Array.Empty();
+
+ ///
+ /// Gets or sets the additional assemblies that might be required for conversion.
+ ///
+ public ITaskItem[] AssemblyPaths { get; set; } = Array.Empty();
+
+ ///
+ public override bool Execute()
+ {
+ var targetAssemblyFile = GetTargetRuntimeAssembly();
+
+ // if the assembly is found next to this assembly
+ if (File.Exists(targetAssemblyFile))
+ {
+#if NET5_0_OR_GREATER
+ // Load the assembly from a context
+ var loadContext = new AssemblyLoadContext($"msbuild-preload-ctx-{Guid.NewGuid()}", true);
+
+ try
+ {
+ _ = loadContext.LoadFromAssemblyPath(targetAssemblyFile);
+
+ // Execute the task with the resolved assembly.
+ return ExecuteTask();
+ }
+ finally
+ {
+ // unload the assembly
+ loadContext.Unload();
+ }
+#else
+ _ = Assembly.LoadFrom(targetAssemblyFile);
+
+ return ExecuteTask();
+#endif
+ }
+ else
+ {
+ // Make .NET Runtime resolve the file itself
+ return ExecuteTask();
+ }
+ }
+
+ ///
+ /// Performs the real task execution with the maybe temporarily loaded
+ /// Interop Assembly.
+ ///
+ /// The result of the task.
+ private bool ExecuteTask()
+ {
+ if (!_context.IsRunningOnWindows)
+ {
+ var verbatimDescription = _context.RuntimeDescription;
+ Log.LogError("This task can only be executed on Microsoft Windows (TM) based operating systems. This platform does not support the creation of this task: {0}", verbatimDescription);
+ return false;
+ }
+
+ if (!Guid.TryParse(TlbOverriddenId, out var tlbOverriddenId))
+ {
+ Log.LogError("Cannot convert {0} to a valid Guid", TlbOverriddenId);
+ return false;
+ }
+
+ // Create type library converter settings from task parameters
+ var settings = new TypeLibConverterSettings()
+ {
+ Out = TargetFile,
+ Assembly = SourceAssemblyFile,
+ OverrideTlbId = tlbOverriddenId,
+ TLBReference = ConvertTaskItemToFsPath(TypeLibraryReferences, false),
+ TLBRefpath = ConvertTaskItemToFsPath(TypeLibraryReferencePaths, false),
+ ASMPath = ConvertTaskItemToFsPath(AssemblyPaths, true)
+ };
+
+ // Issue a warning, if the type library is about to be overridden.
+ if (settings.OverrideTlbId != Guid.Empty)
+ {
+ Log.LogMessage(MessageImportance.High, "The default unique id of the resulting type library will be overridden with the following value: {0}", settings.OverrideTlbId);
+ }
+
+ // Perform file system checks.
+ var checks = new FileSystemChecks(Log, _context);
+
+ var result = true;
+ checks.VerifyFilePresent(settings.Assembly, true, ref result);
+ checks.VerifyFilesPresent(settings.TLBReference, false, ref result);
+ checks.VerifyDirectoriesPresent(settings.TLBRefpath, false, ref result);
+ checks.VerifyFilesPresent(settings.ASMPath, false, ref result);
+
+ // run conversion, if result has been successful.
+ result = result && _context.ConvertAssemblyToTypeLib(settings, Log);
+
+ // report success or failure.
+ return result && !Log.HasLoggedErrors;
+ }
+
+ ///
+ /// Takes the of the specified
+ /// and interprets them as file system entry and hence a file or directory path.
+ ///
+ /// The items to interpret a file system entries.
+ /// If set to true, the can
+ /// contain the Metadata value 'HintPath'. Hence the data will be scanned for this.
+ /// If not, it is assumed, that the property
+ /// contains the file system path.
+ /// The converted item-spec values.
+ private static string[] ConvertTaskItemToFsPath(IReadOnlyCollection items, bool canContainHintPaths)
+ {
+ const string HintPath = nameof(HintPath);
+
+ var itemsWithHintPath = Enumerable.Empty();
+ if (canContainHintPaths)
+ {
+ itemsWithHintPath = items
+ .Where(item => item != null)
+ .Where(item => !string.IsNullOrWhiteSpace(item.GetMetadata(HintPath)));
+ }
+
+ var remainingItems = items.Except(itemsWithHintPath);
+
+ return itemsWithHintPath
+ .Select(item => item.GetMetadata(HintPath))
+ .Union(remainingItems.Where(item => item != null)
+ .Select(item => item.ItemSpec)).ToArray();
+ }
+
+
+ ///
+ /// Gets the path to the path to the dSPACE.Runtime.InteropServices.dll
+ /// next to this assembly.
+ ///
+ /// The assembly file path.
+ private static string GetTargetRuntimeAssembly()
+ {
+ var assemblyPath = typeof(TlbExport).Assembly.Location;
+ var extension = Path.GetExtension(assemblyPath);
+ var fileBaseName = typeof(TlbExport).Namespace;
+ fileBaseName = Path.GetFileNameWithoutExtension(fileBaseName);
+ var fileName = fileBaseName + extension;
+ var assemblyDir = Path.GetDirectoryName(assemblyPath);
+
+ var targetAssemblyFile = fileName;
+
+ if (!string.IsNullOrWhiteSpace(assemblyDir))
+ {
+ targetAssemblyFile = Path.Combine(assemblyDir, fileName);
+ }
+
+ return targetAssemblyFile;
+ }
+
+
+}
diff --git a/src/dscom.build/dSPACE.Runtime.InteropServices.BuildTasks.Task.targets b/src/dscom.build/dSPACE.Runtime.InteropServices.BuildTasks.Task.targets
new file mode 100644
index 00000000..8537178c
--- /dev/null
+++ b/src/dscom.build/dSPACE.Runtime.InteropServices.BuildTasks.Task.targets
@@ -0,0 +1,50 @@
+
+
+
+
+ $(MSBuildAllProjects);$(MSBuildThisFileFullPath)
+
+
+
+ <_DsComTargetFramework>net6.0
+ <_DsComTargetFramework Condition="'$(TargetFramework)' == 'net48'">$(TargetFramework)
+
+ <_DsComTaskAssemblyFile>$(MsBuildThisFileDirectory)_dscom\$(_DsComTargetFramework)\dSPACE.Runtime.InteropServices.BuildTasks.dll
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/dscom.build/dSPACE.Runtime.InteropServices.BuildTasks.Tools.targets b/src/dscom.build/dSPACE.Runtime.InteropServices.BuildTasks.Tools.targets
new file mode 100644
index 00000000..d82b5ba9
--- /dev/null
+++ b/src/dscom.build/dSPACE.Runtime.InteropServices.BuildTasks.Tools.targets
@@ -0,0 +1,69 @@
+
+
+
+
+ $(MSBuildAllProjects);$(MSBuildThisFileFullPath)
+
+
+
+ <_DsComToolsFileDir>$(MsBuildThisFileDirectory)..\tools\x64\
+ <_DsComToolsFileDir Condition="'$(Platform)' == 'x86' OR ('$(Platform)' == 'AnyCPU' AND '$(SolutionPlatform)' == 'x86')">$(MsBuildThisFileDirectory)..\tools\x86\
+
+
+
+
+
+ <_DsComTlbExportAssemblyPathsWithHintPath Include="@(DsComTlbExportAssemblyPaths->HasMetadata('HintPath'))" />
+ <_DsComTlbExportAssemblyPathsWithoutHintPath Include="@(DsComTlbExportAssemblyPaths)" />
+ <_DsComTlbExportAssemblyPathsWithoutHintPath Remove="@(_DsComTlbExportAssemblyPathsWithHintPath)" />
+
+
+
+ <_DsComTypeLibraryReferences Condition="@(DsComTlbExportTlbReferences->Count()) > 0">--tlbreference "@(DsComTlbExportTlbReferences, '" --tlbreference "')"
+ <_DsComTypeLibraryReferencePaths Condition="@(DsComTlbExportReferencePaths->Count()) > 0">--tlbrefpath "@(DsComTlbExportReferencePaths, '"--tlbrefpath "')"
+ <_DsComAssemblyReferences Condition="@(_DsComTlbExportAssemblyPathsWithoutHintPath->Count()) > 0">--asmpath "@(_DsComTlbExportAssemblyPathsWithoutHintPath, '"--asmpath "')"
+ <_DsComAssemblyReferences Condition="@(_DsComTlbExportAssemblyPathsWithHintPath->Count()) > 0">--asmpath "@(_DsComTlbExportAssemblyPathsWithHintPath->GetMetadata('HintPath'), '"--asmpath "')"
+ <_DsComArguments>tlbexport
+ <_DsComArguments Condition="'$(DsComTypeLibraryUniqueId)' != '' AND '$(DsComTypeLibraryUniqueId)' != '$([System.Guid]::Empty.ToString())'">$(_DsComArguments) --overridetlbid "$(DsComTypeLibraryUniqueId)"
+ <_DsComArguments Condition="'$(DsComToolVerbose)' == 'true'"> $(_DsComArguments) --verbose
+ <_DsComArguments> $(_DsComArguments) $(_DsComAssemblyReferences)
+ <_DsComArguments> $(_DsComArguments) $(_DsComTypeLibraryReferences)
+ <_DsComArguments> $(_DsComArguments) $(_DsComTypeLibraryReferencePaths)
+ <_DsComArguments> $(_DsComArguments) $(_DsComExportTypeLibraryAssemblyFile)
+ <_DsComArguments> $(_DsComArguments) --out $(_DsComExportTypeLibraryTargetFile)
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/dscom.build/dSPACE.Runtime.InteropServices.BuildTasks.props b/src/dscom.build/dSPACE.Runtime.InteropServices.BuildTasks.props
new file mode 100644
index 00000000..5c55c979
--- /dev/null
+++ b/src/dscom.build/dSPACE.Runtime.InteropServices.BuildTasks.props
@@ -0,0 +1,46 @@
+
+
+
+
+ $(MSBuildAllProjects);$(MSBuildThisFileFullPath)
+
+
+
+
+ <_DsComTlbExt>.tlb
+
+ <_DsComForceToolUsage>true
+
+
+
+
+ 00000000-0000-0000-0000-000000000000
+
+ false
+
+ true
+
+ false
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/dscom.build/dSPACE.Runtime.InteropServices.BuildTasks.targets b/src/dscom.build/dSPACE.Runtime.InteropServices.BuildTasks.targets
new file mode 100644
index 00000000..1fbac616
--- /dev/null
+++ b/src/dscom.build/dSPACE.Runtime.InteropServices.BuildTasks.targets
@@ -0,0 +1,82 @@
+
+
+
+
+ $(MSBuildAllProjects);$(MSBuildThisFileFullPath)
+
+
+
+ <_DsComExporterTargetsFile>$(MsBuildThisFileDirectory)dSPACE.Runtime.InteropServices.BuildTasks.Task.targets
+ <_DsComExporterTargetsFile Condition="'$(TargetFramework)' == 'netstandard2.0' OR ('$(TargetFrameworks)' != '' AND $(TargetFrameworks.Contains('netstandard2.0'))) OR 'Platform' == 'x86' OR ('Platform' == 'AnyCPU' AND '$(SolutionPlatform)' == 'x86')">$(MsBuildThisFileDirectory)dSPACE.Runtime.InteropServices.BuildTasks.Tools.targets
+ <_DsComExporterTargetsFile Condition="'$(_DsComForceToolUsage)' == 'true'">$(MsBuildThisFileDirectory)dSPACE.Runtime.InteropServices.BuildTasks.Tools.targets
+
+
+
+
+
+ <_DsComExportTypeLibraryTargetFile>$(TargetDir)\$(TargetName)$(_DsComTlbExt)
+
+ <_DsComExportTypeLibraryAssemblyFile>$(TargetPath)
+
+
+
+
+ <_DsComFromReferencesWithoutHintPath Include="@(References)" Exclude="@(References->HasMetadata('HintPath'))"/>
+
+ <_DsComFromReferencesWithEmptyHintPath Include="@(References->HasMetadata('HintPath')->WithMetadata('HintPath', ''))"/>
+
+
+
+
+ <_DsComReferences Include="@(_DsComFromReferencesWithEmptyHintPath)" Condition="'$(DsComTlbExportIncludeReferencesWithoutHintPath)' == 'true'" />
+
+ <_DsComReferences Include="@(_DsComFromReferencesWithoutHintPath)" />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/dscom.build/doc/ReadMe.md b/src/dscom.build/doc/ReadMe.md
new file mode 100644
index 00000000..115c8f29
--- /dev/null
+++ b/src/dscom.build/doc/ReadMe.md
@@ -0,0 +1,253 @@
+# dscom.build
+
+## Project
+
+This project consumes the core tasks package of MsBuild and the dSPACE.Runtime.InteropServices assembly.
+The resulting assembly will be called `dSPACE.Runtime.InteropServices.BuildTasks`. This will also be the name of the NuGet package.
+
+The project consists of two notable classes.
+
+### Class `TlbExport`
+
+The class [`dSPACE.Runtime.InteropServices.Build.TlbExport`](/src/dscom.build/TlbExport.cs) contains the implementation of the build task.
+
+It consumes the following properties:
+
+* `TlbOverriddenId`: The id of the type library. If it is set to the empty Guid, the Guid of the TypeLibrary will be used.
+* `TargetFile` (Required): The full path to the type library to export.
+* `SourceAssemblyFile` (Required): The full path of the managed assembly to export.
+* `TypeLibraryReferences`: The input type libraries to reference during export.
+* `TypeLibraryReferencePaths`: A list of paths containing type library files to reference automatically during export.
+* `AssemblyPaths`: The references of the source assembly which must be loaded for export.
+
+It contains the method `bool Execute()` as an implementation of the `ITask` interface.
+
+The execute method will load the `dSPACE.Runtime.InteropServices.dll`.
+It tries to locate the file next to the build task assembly and - if cannot be present at that time - let the .NET runtime try to resolve it.
+
+During the execution phase, the following steps will be made:
+
+* Parse the overridden type library Guid.
+* Fail execution, if the build is not executed on windows.
+* Convert the `ITaskItems` (cf. `TypeLibraryReferences`, `TypeLibraryReferencePaths`, `AssemblyPaths`) to file system identifiers.
+If a file or directory is not present, a warning will be issued using the MsBuild `TaskLoggingHelper`.
+The export will continue.
+* Resolve the `SourceAssemblyFile` and issue an error, if it was not found.
+* Convert the assembly using the `DefaultBuildContext`.
+
+The `DefaultBuildContext` represents a level of abstraction.
+It contains the part of the implementation that cannot be stubbed or mocked at run-time.
+
+The execution will be successful, if the build context returns success and the task logging helper did not log any errors.
+
+### Class `DefaultBuildContext`
+
+The `DefaultBuildContext` is an implementation of the [`IBuildContext`](/src/dscom.build/IBuildContext.cs) interface.
+It provides the following operations:
+
+* Check, whether the build is running on windows.
+* Check, whether given files and directories exist.
+* Perform the export using the `TypeLibConverterSettings`.
+
+The export is done using the `TypeLibConverter` instance and an implementation of the [`ITypeLibExporterNotifySink`](/src/dscom.build/LoggingTypeLibExporterSink.cs), which logs the events to the build log.
+
+If the type library converter did not report a successful export, an error will be issued. If the expected file did not exists after the export, a warning shall be issued.
+
+It is an issue for discussion, if this check can be added to the `TlbExport` implementation. This way, the warning can be unit tested.
+
+### Build sequence (TlbExport)
+
+```ditaa
+ ┌───────────┐ ┌─────────┐ ┌─────────────┐ ┌─────────────────┐ ┌────────┐ ┌───┐
+ │BuildEngine│ │TlbExport│ │IBuildContext│ │AppDomain.Current│ │Assembly│ │Log│
+ └─────┬─────┘ └────┬────┘ └──────┬──────┘ └────────┬────────┘ └───┬────┘ └─┬─┘
+ │ Execute ┌┴┐ │ │ │ │
+ │────────────────────────────────>│ │ │ │ │ │
+ │ │ │ │ │ │ │
+ │ │ │ IsRunningOnWindows() │ │ │ │
+ │ │ │ ──────────────────────────────────────────────────────────────────────────────>│ │ │ │
+ │ │ │ │ │ │ │
+ │ │ │ │ │ │ │
+ ╔══════╤════════╪═════════════════════════════════╪═╪════════════════════════════════════════════════════════════════════════════════╪═════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════╪═══════════════════════╪═══════════════════════════════════════════════════════════════════════════════════╪═════════════════════╗
+ ║ ALT │ result true │ │ │ │ │ │ ║
+ ╟──────┘ │ │ │ │ │ │ │ ║
+ ║ │ │ │────┐ │ │ │ │ ║
+ ║ │ │ │ │ ConvertTaskItemToFsPath(TypeLibraryReferences, SupportHintPath=False) │ │ │ │ ║
+ ║ │ │ │<───┘ │ │ │ │ ║
+ ║ │ │ │ │ │ │ │ ║
+ ║ │ │ │────┐ │ │ │ │ ║
+ ║ │ │ │ │ ConvertTaskItemToFsPath(TypeLibraryReferencePaths, SupportHintPath=False) │ │ │ │ ║
+ ║ │ │ │<───┘ │ │ │ │ ║
+ ║ │ │ │ │ │ │ │ ║
+ ║ │ │ │────┐ │ │ │ │ ║
+ ║ │ │ │ │ ConvertTaskItemToFsPath(AssemblyPaths, SupportHintPath=True) │ │ │ │ ║
+ ║ │ │ │<───┘ │ │ │ │ │ ║
+ ║ │ │ │ │ │ │ │ │ ║
+ ║ │ │ │ set_TLBReference(conversionResult[TypeLibraryReferences]) │ ┌────────────────────────┐ │ │ │ ║
+ ║ │ │ │ ────────────────────────────────────────────────────────────────────────────────────────────────>│TypeLibConverterSettings│ │ │ │ ║
+ ║ │ │ │ │ └───────────┬────────────┘ │ │ │ ║
+ ║ │ │ │ set_TLBRefpath(conversionResult[TypeLibraryReferencePaths]) │ │ │ │ ║
+ ║ │ │ │ ─────────────────────────────────────────────────────────────────────────────────────────────────────────────> │ │ │ ║
+ ║ │ │ │ │ │ │ │ │ ║
+ ║ │ │ │ set_ASMPath(conversionResult[AssemblyPaths]) │ │ │ │ │ ║
+ ║ │ │ │ ─────────────────────────────────────────────────────────────────────────────────────────────────────────────> │ │ │ │ ║
+ ║ │ │ │ │ │ │ │ │ │ ║
+ ║ │ │ │ VerifyFilesPresent(Assembly, ReportAsError=True) │ ┌────────────────┐ │ │ │ ║
+ ║ │ │ │ ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────>│FileSystemChecks│ │ │ │ ║
+ ║ │ │ │ │ │ └───────┬────────┘ │ │ │ ║
+ ║ │ │ │ │ VerifyFilesPresent(files) │ │ │ │ ║
+ ║ │ │ │ │ <──────────────────────────────────────────────────────────── │ │ │ ║
+ ║ │ │ │ │ │ │ │ │ │ ║
+ ║ │ │ │ VerifyFilesPresent(TypeLibraryReferences, AssemblyPaths, ReportAsError=False)│ │ │ │ │ ║
+ ║ │ │ │ ─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────> │ │ │ ║
+ ║ │ │ │ │ │ │ │ │ │ ║
+ ║ │ │ │ │ VerifyFilesPresent(files) │ │ │ │ ║
+ ║ │ │ │ │ <──────────────────────────────────────────────────────────── │ │ │ ║
+ ║ │ │ │ │ │ │ │ │ │ ║
+ ║ │ │ │ VerifyDirectoriesPresent(TypeLibraryReferencePaths, ReportAsError=False) │ │ │ │ │ ║
+ ║ │ │ │ ─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────> │ │ │ ║
+ ║ │ │ │ │ │ │ │ │ │ ║
+ ║ │ │ │ │ VerifyDirectoriesPresent(files) │ │ │ │ ║
+ ║ │ │ │ │ <──────────────────────────────────────────────────────────── │ │ │ ║
+ ║ │ │ │ │ │ │ │ │ │ ║
+ ║ │ │ │ ConvertAssemblyToTypeLib ┌┴┐ │ │ │ │ │ ║
+ ║ │ │ │ ─────────────────────────────────────────────────────────────────────────────>│ │ │ │ │ │ │ ║
+ ║ │ │ │ │ │ │ │ │ │ │ ║
+ ║ │ │ │ │ │ │ │ │ │ │ ║
+ ║ │ │ │ ╔══════╤════════╪═╪════════════════════════════╪═══════════════════════════════╪═══════════════════════════════════════════════════════════╪══════════════════╗ │ │ ║
+ ║ │ │ │ ║ ALT │ IsNet50OrLater │ │ │ │ ║ │ │ ║
+ ║ │ │ │ ╟──────┘ │ │ │ │ │ │ ║ │ │ ║
+ ║ │ │ │ ║ │ │ add_Resolving(ResolveAssemblyFromSettings) ┌───────────────────┐ │ ║ │ │ ║
+ ║ │ │ │ ║ │ │ ──────────────────────────────────────────────────────────────────────────────>│AssemblyLoadContext│ │ ║ │ │ ║
+ ║ │ │ │ ╠═══════════════╪═╪════════════════════════════╪═══════════════════════════════╪═══════════════════════════════════════════════════════════╪══════════════════╣ │ │ ║
+ ║ │ │ │ ║ [IsNetFx] │ │ │ │ │ │ ║ │ │ ║
+ ║ │ │ │ ║ │ │ │ add_AssemblyResolve(ResolveAssemblyFromSettings) │ │ ║ │ │ ║
+ ║ │ │ │ ║ │ │ ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────>│ ║ │ │ ║
+ ║ │ │ │ ╚═══════════════╪═╪════════════════════════════╪═══════════════════════════════╪═════════════════════════════╪═════════════════════════════╪══════════════════╝ │ │ ║
+ ║ │ │ │ │ │ │ │ │ │ │ │ ║
+ ║ │ │ │ │ │ │ Load(TypeLibExportSettings.SourceAssembly) │ │ ╔═══════════════════════════╗ │ ║
+ ║ │ │ │ │ │ ───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────> ║Load Assemblies recursive ░║ │ ║
+ ║ │ │ │ │ │ │ │ │ │ │ ╚═════════════════════════╤═╝ │ ║
+ ║ │ │ │ │ │ │ │ set_Logger(log) │ │ │ ┌──────────────────────────┐ │ ║
+ ║ │ │ │ │ │ ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────>│LoggingTypeLibExporterSink│ │ │ ║
+ ║ │ │ │ │ │ │ │ │ │ │ └────────────┬─────────────┘ │ │ ║
+ ║ │ │ │ │ │ │ ConvertAssemblyToTypeLib(Assembly, TypeLibConverterSettings, LoggingTypeLibExporterSink) │ │ ┌────────────────┐ │ ║
+ ║ │ │ │ │ │ ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────>│TypeLibConverter│ │ ║
+ ║ │ │ │ │ │ │ │ │ │ │ │ └───────┬────────┘ │ ║
+ ║ │ │ │ │ │ │ │ │ │ │ │ │ │ ║
+ ║ │ │ │ ╔══════╤════════╪═╪════════════════════════════╪═══════════════════════════════╪═════════════════════════════╪═════════════════════════════╪═══════════════════════╪════════════════════════════╪════════════════════════════════╪═════════════════════╪═══════════╗ ║
+ ║ │ │ │ ║ ALT │ result is null │ │ │ │ │ │ │ │ ║ ║
+ ║ │ │ │ ╟──────┘ │ │ │ │ │ │ │ │ │ │ ║ ║
+ ║ │ │ │ ║ │ │ │ │ │ Error │ │ │ │ │ ║ ║
+ ║ │ │ │ ║ │ │ ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────>│ ║ ║
+ ║ │ │ │ ╚═══════════════╪═╪════════════════════════════╪═══════════════════════════════╪═════════════════════════════╪═════════════════════════════╪═══════════════════════╪════════════════════════════╪════════════════════════════════╪═════════════════════╪═══════════╝ ║
+ ║ │ │ │ │ │ │ │ │ │ │ │ │ │ ║
+ ║ │ │ │ │ │ │ │ │ │ │ │ │ │ ║
+ ║ │ │ │ ╔══════╤════════╪═╪════════════════════════════╪═══════════════════════════════╪═════════════════════════════╪═════════════════════════════╪═══════════════════════╪════════════════════════════╪════════════════════════════════╪═════════════════════╪═══════════╗ ║
+ ║ │ │ │ ║ ALT │ TLB Not Existing │ │ │ │ │ │ │ │ ║ ║
+ ║ │ │ │ ╟──────┘ │ │ │ │ │ │ │ │ │ │ ║ ║
+ ║ │ │ │ ║ │ │ │ │ │ Warning(DSCOM001) │ │ │ │ ║ ║
+ ║ │ │ │ ║ │ │ ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────>│ ║ ║
+ ║ │ │ │ ╚═══════════════╪═╪════════════════════════════╪═══════════════════════════════╪═════════════════════════════╪═════════════════════════════╪═══════════════════════╪════════════════════════════╪════════════════════════════════╪═════════════════════╪═══════════╝ ║
+ ║ │ │ │ │ │ │ │ │ │ │ │ │ │ ║
+ ║ │ │ │ │ │ │ │ │ │ │ │ │ │ ║
+ ║ │ ╔══════╤══════╪═╪═══════════════════════════════════════════════════════════════════════════════╪═╪════════════════════════════╪═══════════════════════════════╪════════════════════════════\╪/══════════════════╗ │ │ │ │ │ ║
+ ║ │ ║ ALT │ IsNet50OrLater │ │ │ │ X ║ │ │ │ │ │ ║
+ ║ │ ╟──────┘ │ │ │ │ │ │ /│\ ║ │ │ │ │ │ ║
+ ║ │ ║ │ │ │ │ Unload │ │ │ ║ │ │ │ │ │ ║
+ ║ │ ║ │ │ ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────>│ ║ │ │ │ │ │ ║
+ ║ │ ╚═════════════╪═╪═══════════════════════════════════════════════════════════════════════════════╪═╪════════════════════════════╪═══════════════════════════════╪═════════════════════════════╪═══════════════════╝ │ │ │ │ │ ║
+ ║ │ │ │ └┬┘ │ │ │ │ │ │ │ │ ║
+ ║ │return result && Log.HasNoErrors │ │ │ │ │ │ │ │ │ │ │ ║
+ ║ │<────────────────────────────────│ │ │ │ │ │ │ │ │ │ │ ║
+ ╠═══════════════╪═════════════════════════════════╪═╪════════════════════════════════════════════════════════════════════════════════╪═════════════════════════════╪═══════════════════════════════╪═════════════════════════════╪═════════════════════════════╪═══════════════════════╪════════════════════════════╪════════════════════════════════╪═════════════════════╪═════════════════════╣
+ ║ [result false]│ │ │ │ │ │ │ │ │ │ │ │ ║
+ ║ │ │ │ │ │ │ Error │ │ │ │ │ │ ║
+ ║ │ │ │ ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────>│ ║
+ ║ │ └┬┘ │ │ │ │ │ │ │ │ │ ║
+ ║ │ false │ │ │ │ │ │ │ │ │ │ ║
+ ║ │<─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─│ │ │ │ │ │ │ │ │ │ ║
+ ╚═══════════════╪══════════════════════════════════╪═════════════════════════════════════════════════════════════════════════════════╪═════════════════════════════╪═══════════════════════════════╪═════════════════════════════╪═════════════════════════════╪═══════════════════════╪════════════════════════════╪════════════════════════════════╪═════════════════════╪═════════════════════╝
+ ┌─────┴─────┐ ┌────┴────┐ ┌──────┴──────┐ ┌───────────┴────────────┐ ┌───────┴────────┐ ┌─────────┴─────────┐ ┌────────┴────────┐ ┌───┴────┐ ┌────────────┴─────────────┐ ┌───────┴────────┐ ┌─┴─┐
+ │BuildEngine│ │TlbExport│ │IBuildContext│ │TypeLibConverterSettings│ │FileSystemChecks│ │AssemblyLoadContext│ │AppDomain.Current│ │Assembly│ │LoggingTypeLibExporterSink│ │TypeLibConverter│ │Log│
+ └───────────┘ └─────────┘ └─────────────┘ └────────────────────────┘ └────────────────┘ └───────────────────┘ └─────────────────┘ └────────┘ └──────────────────────────┘ └────────────────┘ └───┘
+```
+
+## Test
+
+### Unit tests
+
+The class `TlbExport` is written with a level of abstraction and hence is tested with a [unit test](/src/dscom.test/tests/BuildTaskTest.cs).
+
+### Acceptance tests
+
+The examples [32-bit](/examples/32bit/) and [outproc](/examples/outproc/) each provide an `msbuild-acceptance-test.cmd` script.
+This file builds and packages the build task and adds it to the `.csproj` file of the resulting assembly.
+Then the build is conducted. It must be warning free.
+
+## Packaging
+
+The packaging will contain the executable file for [dscom CLI](/src/dscom.client/dscom.client.csproj) for both platforms, x64 and x86. The configuration is Release.
+The executable will be published in Release as a framework dependent deployment using a [single file](https://learn.microsoft.com/en-us/dotnet/core/deploying/single-file/overview?tabs=cli), which means that [dSPACE.Runtime.InteropServices](/src/dscom/dscom.csproj) and [System.CommandLine](https://packages.nuget.org/packages/System.CommandLine/) are bundled, but no AOT takes place and no framework files will be part of the bundle.
+
+The packaging takes place in [DsComPackaging.targets](/src/dscom.build/DsComPackaging.targets) which is imported by [dscom.build.csproj](/src/dscom.build/dscom.build.csproj).
+The targets file hooks into the packaging procedure by enforcing to be run before the `GenerateNuspec` target of the NuGet Client which is part of the packaging process by the MsBuild dotnet sdk in the [NuGet.Build.Tasks.Pack.targets](https://github.com/NuGet/NuGet.Client/blob/dev/src/NuGet.Core/NuGet.Build.Tasks.Pack/NuGet.Build.Tasks.Pack.targets) and exposes the `GenerateNuspecDependsOn` array variable.
+Two targets must be executed before generating the NuSpec file:
+
+**DsComBuildPackTaskDependencies**:
+ This task packs the [dSPACE.Runtime.InteropServices](/src/dscom/dscom.csproj) and [System.CommandLine](https://packages.nuget.org/packages/System.CommandLine/) assembly into the package. This will enable the [TlbExport](/src/dscom.build/TlbExport.cs) task to load the assembly.
+ Due to a [bug in MsBuild](https://github.com/dotnet/msbuild/issues/1755) and another [issue with NuGet](https://github.com/NuGet/Home/issues/4704), MsBuild does not add assemblies from dependent package to the resolve handler.
+ Hence the dependent assembly must be bundled with the NuGet package. Refer to a [blog article](https://natemcmaster.com/blog/2017/11/11/msbuild-task-with-dependencies/) for details.
+
+**DsComBuildPackTool**:
+ The MsBuild process is hosted using the target framework, i.e. .NET FullFramework 4.8 or .NET 6.0 using an x64 based process environment.
+ In order to enable the build using the 32bit builder and other target frameworks, the executables will be added to the NuGet package.
+ This approach has been inspired by [Grpc.Tools](https://github.com/grpc/grpc/blob/master/src/csharp/Grpc.Tools/build/native/Grpc.Tools.props) NuGet package which bundles the `protoc.exe` and `grpc_cpp_plugin.exe` executables.
+ In order to get the matching binaries without interfering with the regular MsBuild and NuGet packaging, the task calls the [MsBuild](https://learn.microsoft.com/en-us/visualstudio/msbuild/msbuild-task) task for the [dscom.client.csproj](/src/dscom.client/dscom.client.csproj) using the targets `Restore`, `Build` and `Pack` targets for both platforms, x86 and x64 using a different output directory.
+
+## Targets and Properties
+
+The package containing the build task comes with [props and targets for MsBuild](https://learn.microsoft.com/en-us/nuget/concepts/msbuild-props-and-targets).
+These files are automatically included to the build using the naming convention.
+There is also a folder for .NET Standard 2.0 which will only include the general files.
+
+**[dSPACE.Runtime.InteropServices.BuildTasks.props](/src/dscom.build/dSPACE.Runtime.InteropServices.BuildTasks.props)**:
+ This property file contains the general properties for the integration of the build task.
+
+**[dSPACE.Runtime.InteropServices.BuildTasks.targets](/src/dscom.build/dSPACE.Runtime.InteropServices.BuildTasks.targets)**:
+ This target file includes the general tasks and targets.
+ It will also create the properties and item groups based on the data provided by the project being build.
+ Depending on the enforced usage of the tools or task, it will include the tool or task specific targets fill.
+
+**[dSPACE.Runtime.InteropServices.BuildTasks.Task.targets](/src/dscom.build/dSPACE.Runtime.InteropServices.BuildTasks.Task.targets)**:
+ This target file includes the targets and tasks which invoke the TLB export as MsBuild task.
+
+**[dSPACE.Runtime.InteropServices.BuildTasks.Tools.targets](/src/dscom.build/dSPACE.Runtime.InteropServices.BuildTasks.Tools.targets)**:
+ This target file includes the targets and tasks which invoke the TLB export a call to the bundled executables.
+
+The general approach is designed by the MsBuild SDK based projects.
+The props file will be included in the beginning of the project file _automatically_, i.e. before the project file is read.
+The targets file will be in included at the end of the project file _automatically_, i.e. after the project file has been read.
+Hence project specific settings are only available in the targets file.
+
+## Limitations
+
+### InProc Export
+
+The [LibraryWriter](/src/dscom/writer/LibraryWriter.cs) currently refuses to write the type library file (TLB), if the file is hosted within the MsBuild Process.
+The reason is currently unknown.
+Therefore it is not recommended to set `_DsComForceToolUsage` to `false`.
+
+### .NET Standard 2.0 compliant NuGet package
+
+The `Pack` target currently does not create a .NET Standard 2.0 compliant NuGet package.
+Therefore, when adding the `dSPACE.Runtime.InteropServices.Build` package to a .NET Standard 2.0 targeting project, the warning [NU1701](https://learn.microsoft.com/en-us/nuget/reference/errors-and-warnings/nu1701) will be issued.
+This warning can be explicitly disabled for the package using the package meta data:
+
+```xml
+
+ ...
+
+ ...
+
+```
diff --git a/src/dscom.build/doc/TlbExport.sequence-ditaa.txt b/src/dscom.build/doc/TlbExport.sequence-ditaa.txt
new file mode 100644
index 00000000..89741710
--- /dev/null
+++ b/src/dscom.build/doc/TlbExport.sequence-ditaa.txt
@@ -0,0 +1,113 @@
+ ┌───────────┐ ┌─────────┐ ┌─────────────┐ ┌─────────────────┐ ┌────────┐ ┌───┐
+ │BuildEngine│ │TlbExport│ │IBuildContext│ │AppDomain.Current│ │Assembly│ │Log│
+ └─────┬─────┘ └────┬────┘ └──────┬──────┘ └────────┬────────┘ └───┬────┘ └─┬─┘
+ │ Execute ┌┴┐ │ │ │ │
+ │────────────────────────────────>│ │ │ │ │ │
+ │ │ │ │ │ │ │
+ │ │ │ IsRunningOnWindows() │ │ │ │
+ │ │ │ ──────────────────────────────────────────────────────────────────────────────>│ │ │ │
+ │ │ │ │ │ │ │
+ │ │ │ │ │ │ │
+ ╔══════╤════════╪═════════════════════════════════╪═╪════════════════════════════════════════════════════════════════════════════════╪═════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════╪═══════════════════════╪═══════════════════════════════════════════════════════════════════════════════════╪═════════════════════╗
+ ║ ALT │ result true │ │ │ │ │ │ ║
+ ╟──────┘ │ │ │ │ │ │ │ ║
+ ║ │ │ │────┐ │ │ │ │ ║
+ ║ │ │ │ │ ConvertTaskItemToFsPath(TypeLibraryReferences, SupportHintPath=False) │ │ │ │ ║
+ ║ │ │ │<───┘ │ │ │ │ ║
+ ║ │ │ │ │ │ │ │ ║
+ ║ │ │ │────┐ │ │ │ │ ║
+ ║ │ │ │ │ ConvertTaskItemToFsPath(TypeLibraryReferencePaths, SupportHintPath=False) │ │ │ │ ║
+ ║ │ │ │<───┘ │ │ │ │ ║
+ ║ │ │ │ │ │ │ │ ║
+ ║ │ │ │────┐ │ │ │ │ ║
+ ║ │ │ │ │ ConvertTaskItemToFsPath(AssemblyPaths, SupportHintPath=True) │ │ │ │ ║
+ ║ │ │ │<───┘ │ │ │ │ │ ║
+ ║ │ │ │ │ │ │ │ │ ║
+ ║ │ │ │ set_TLBReference(conversionResult[TypeLibraryReferences]) │ ┌────────────────────────┐ │ │ │ ║
+ ║ │ │ │ ────────────────────────────────────────────────────────────────────────────────────────────────>│TypeLibConverterSettings│ │ │ │ ║
+ ║ │ │ │ │ └───────────┬────────────┘ │ │ │ ║
+ ║ │ │ │ set_TLBRefpath(conversionResult[TypeLibraryReferencePaths]) │ │ │ │ ║
+ ║ │ │ │ ─────────────────────────────────────────────────────────────────────────────────────────────────────────────> │ │ │ ║
+ ║ │ │ │ │ │ │ │ │ ║
+ ║ │ │ │ set_ASMPath(conversionResult[AssemblyPaths]) │ │ │ │ │ ║
+ ║ │ │ │ ─────────────────────────────────────────────────────────────────────────────────────────────────────────────> │ │ │ │ ║
+ ║ │ │ │ │ │ │ │ │ │ ║
+ ║ │ │ │ VerifyFilesPresent(Assembly, ReportAsError=True) │ ┌────────────────┐ │ │ │ ║
+ ║ │ │ │ ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────>│FileSystemChecks│ │ │ │ ║
+ ║ │ │ │ │ │ └───────┬────────┘ │ │ │ ║
+ ║ │ │ │ │ VerifyFilesPresent(files) │ │ │ │ ║
+ ║ │ │ │ │ <──────────────────────────────────────────────────────────── │ │ │ ║
+ ║ │ │ │ │ │ │ │ │ │ ║
+ ║ │ │ │ VerifyFilesPresent(TypeLibraryReferences, AssemblyPaths, ReportAsError=False)│ │ │ │ │ ║
+ ║ │ │ │ ─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────> │ │ │ ║
+ ║ │ │ │ │ │ │ │ │ │ ║
+ ║ │ │ │ │ VerifyFilesPresent(files) │ │ │ │ ║
+ ║ │ │ │ │ <──────────────────────────────────────────────────────────── │ │ │ ║
+ ║ │ │ │ │ │ │ │ │ │ ║
+ ║ │ │ │ VerifyDirectoriesPresent(TypeLibraryReferencePaths, ReportAsError=False) │ │ │ │ │ ║
+ ║ │ │ │ ─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────> │ │ │ ║
+ ║ │ │ │ │ │ │ │ │ │ ║
+ ║ │ │ │ │ VerifyDirectoriesPresent(files) │ │ │ │ ║
+ ║ │ │ │ │ <──────────────────────────────────────────────────────────── │ │ │ ║
+ ║ │ │ │ │ │ │ │ │ │ ║
+ ║ │ │ │ ConvertAssemblyToTypeLib ┌┴┐ │ │ │ │ │ ║
+ ║ │ │ │ ─────────────────────────────────────────────────────────────────────────────>│ │ │ │ │ │ │ ║
+ ║ │ │ │ │ │ │ │ │ │ │ ║
+ ║ │ │ │ │ │ │ │ │ │ │ ║
+ ║ │ │ │ ╔══════╤════════╪═╪════════════════════════════╪═══════════════════════════════╪═══════════════════════════════════════════════════════════╪══════════════════╗ │ │ ║
+ ║ │ │ │ ║ ALT │ IsNet50OrLater │ │ │ │ ║ │ │ ║
+ ║ │ │ │ ╟──────┘ │ │ │ │ │ │ ║ │ │ ║
+ ║ │ │ │ ║ │ │ add_Resolving(ResolveAssemblyFromSettings) ┌───────────────────┐ │ ║ │ │ ║
+ ║ │ │ │ ║ │ │ ──────────────────────────────────────────────────────────────────────────────>│AssemblyLoadContext│ │ ║ │ │ ║
+ ║ │ │ │ ╠═══════════════╪═╪════════════════════════════╪═══════════════════════════════╪═══════════════════════════════════════════════════════════╪══════════════════╣ │ │ ║
+ ║ │ │ │ ║ [IsNetFx] │ │ │ │ │ │ ║ │ │ ║
+ ║ │ │ │ ║ │ │ │ add_AssemblyResolve(ResolveAssemblyFromSettings) │ │ ║ │ │ ║
+ ║ │ │ │ ║ │ │ ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────>│ ║ │ │ ║
+ ║ │ │ │ ╚═══════════════╪═╪════════════════════════════╪═══════════════════════════════╪═════════════════════════════╪═════════════════════════════╪══════════════════╝ │ │ ║
+ ║ │ │ │ │ │ │ │ │ │ │ │ ║
+ ║ │ │ │ │ │ │ Load(TypeLibExportSettings.SourceAssembly) │ │ ╔═══════════════════════════╗ │ ║
+ ║ │ │ │ │ │ ───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────> ║Load Assemblies recursive ░║ │ ║
+ ║ │ │ │ │ │ │ │ │ │ │ ╚═════════════════════════╤═╝ │ ║
+ ║ │ │ │ │ │ │ │ set_Logger(log) │ │ │ ┌──────────────────────────┐ │ ║
+ ║ │ │ │ │ │ ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────>│LoggingTypeLibExporterSink│ │ │ ║
+ ║ │ │ │ │ │ │ │ │ │ │ └────────────┬─────────────┘ │ │ ║
+ ║ │ │ │ │ │ │ ConvertAssemblyToTypeLib(Assembly, TypeLibConverterSettings, LoggingTypeLibExporterSink) │ │ ┌────────────────┐ │ ║
+ ║ │ │ │ │ │ ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────>│TypeLibConverter│ │ ║
+ ║ │ │ │ │ │ │ │ │ │ │ │ └───────┬────────┘ │ ║
+ ║ │ │ │ │ │ │ │ │ │ │ │ │ │ ║
+ ║ │ │ │ ╔══════╤════════╪═╪════════════════════════════╪═══════════════════════════════╪═════════════════════════════╪═════════════════════════════╪═══════════════════════╪════════════════════════════╪════════════════════════════════╪═════════════════════╪═══════════╗ ║
+ ║ │ │ │ ║ ALT │ result is null │ │ │ │ │ │ │ │ ║ ║
+ ║ │ │ │ ╟──────┘ │ │ │ │ │ │ │ │ │ │ ║ ║
+ ║ │ │ │ ║ │ │ │ │ │ Error │ │ │ │ │ ║ ║
+ ║ │ │ │ ║ │ │ ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────>│ ║ ║
+ ║ │ │ │ ╚═══════════════╪═╪════════════════════════════╪═══════════════════════════════╪═════════════════════════════╪═════════════════════════════╪═══════════════════════╪════════════════════════════╪════════════════════════════════╪═════════════════════╪═══════════╝ ║
+ ║ │ │ │ │ │ │ │ │ │ │ │ │ │ ║
+ ║ │ │ │ │ │ │ │ │ │ │ │ │ │ ║
+ ║ │ │ │ ╔══════╤════════╪═╪════════════════════════════╪═══════════════════════════════╪═════════════════════════════╪═════════════════════════════╪═══════════════════════╪════════════════════════════╪════════════════════════════════╪═════════════════════╪═══════════╗ ║
+ ║ │ │ │ ║ ALT │ TLB Not Existing │ │ │ │ │ │ │ │ ║ ║
+ ║ │ │ │ ╟──────┘ │ │ │ │ │ │ │ │ │ │ ║ ║
+ ║ │ │ │ ║ │ │ │ │ │ Warning(DSCOM001) │ │ │ │ ║ ║
+ ║ │ │ │ ║ │ │ ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────>│ ║ ║
+ ║ │ │ │ ╚═══════════════╪═╪════════════════════════════╪═══════════════════════════════╪═════════════════════════════╪═════════════════════════════╪═══════════════════════╪════════════════════════════╪════════════════════════════════╪═════════════════════╪═══════════╝ ║
+ ║ │ │ │ │ │ │ │ │ │ │ │ │ │ ║
+ ║ │ │ │ │ │ │ │ │ │ │ │ │ │ ║
+ ║ │ ╔══════╤══════╪═╪═══════════════════════════════════════════════════════════════════════════════╪═╪════════════════════════════╪═══════════════════════════════╪════════════════════════════\╪/══════════════════╗ │ │ │ │ │ ║
+ ║ │ ║ ALT │ IsNet50OrLater │ │ │ │ X ║ │ │ │ │ │ ║
+ ║ │ ╟──────┘ │ │ │ │ │ │ /│\ ║ │ │ │ │ │ ║
+ ║ │ ║ │ │ │ │ Unload │ │ │ ║ │ │ │ │ │ ║
+ ║ │ ║ │ │ ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────>│ ║ │ │ │ │ │ ║
+ ║ │ ╚═════════════╪═╪═══════════════════════════════════════════════════════════════════════════════╪═╪════════════════════════════╪═══════════════════════════════╪═════════════════════════════╪═══════════════════╝ │ │ │ │ │ ║
+ ║ │ │ │ └┬┘ │ │ │ │ │ │ │ │ ║
+ ║ │return result && Log.HasNoErrors │ │ │ │ │ │ │ │ │ │ │ ║
+ ║ │<────────────────────────────────│ │ │ │ │ │ │ │ │ │ │ ║
+ ╠═══════════════╪═════════════════════════════════╪═╪════════════════════════════════════════════════════════════════════════════════╪═════════════════════════════╪═══════════════════════════════╪═════════════════════════════╪═════════════════════════════╪═══════════════════════╪════════════════════════════╪════════════════════════════════╪═════════════════════╪═════════════════════╣
+ ║ [result false]│ │ │ │ │ │ │ │ │ │ │ │ ║
+ ║ │ │ │ │ │ │ Error │ │ │ │ │ │ ║
+ ║ │ │ │ ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────>│ ║
+ ║ │ └┬┘ │ │ │ │ │ │ │ │ │ ║
+ ║ │ false │ │ │ │ │ │ │ │ │ │ ║
+ ║ │<─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─│ │ │ │ │ │ │ │ │ │ ║
+ ╚═══════════════╪══════════════════════════════════╪═════════════════════════════════════════════════════════════════════════════════╪═════════════════════════════╪═══════════════════════════════╪═════════════════════════════╪═════════════════════════════╪═══════════════════════╪════════════════════════════╪════════════════════════════════╪═════════════════════╪═════════════════════╝
+ ┌─────┴─────┐ ┌────┴────┐ ┌──────┴──────┐ ┌───────────┴────────────┐ ┌───────┴────────┐ ┌─────────┴─────────┐ ┌────────┴────────┐ ┌───┴────┐ ┌────────────┴─────────────┐ ┌───────┴────────┐ ┌─┴─┐
+ │BuildEngine│ │TlbExport│ │IBuildContext│ │TypeLibConverterSettings│ │FileSystemChecks│ │AssemblyLoadContext│ │AppDomain.Current│ │Assembly│ │LoggingTypeLibExporterSink│ │TypeLibConverter│ │Log│
+ └───────────┘ └─────────┘ └─────────────┘ └────────────────────────┘ └────────────────┘ └───────────────────┘ └─────────────────┘ └────────┘ └──────────────────────────┘ └────────────────┘ └───┘
\ No newline at end of file
diff --git a/src/dscom.build/doc/TlbExport.sequence.puml b/src/dscom.build/doc/TlbExport.sequence.puml
new file mode 100644
index 00000000..b0dd80dc
--- /dev/null
+++ b/src/dscom.build/doc/TlbExport.sequence.puml
@@ -0,0 +1,64 @@
+@startuml "TlbExport"
+Participant BuildEngine as MsBuild
+
+MsBuild -> TlbExport: Execute
+activate TlbExport
+
+TlbExport -> IBuildContext: IsRunningOnWindows()
+alt result true
+ TlbExport -> TlbExport: ConvertTaskItemToFsPath(TypeLibraryReferences, SupportHintPath=False)
+ TlbExport -> TlbExport: ConvertTaskItemToFsPath(TypeLibraryReferencePaths, SupportHintPath=False)
+ TlbExport -> TlbExport: ConvertTaskItemToFsPath(AssemblyPaths, SupportHintPath=True)
+
+ create TypeLibConverterSettings
+
+ TlbExport -> TypeLibConverterSettings: set_TLBReference(conversionResult[TypeLibraryReferences])
+ TlbExport -> TypeLibConverterSettings: set_TLBRefpath(conversionResult[TypeLibraryReferencePaths])
+ TlbExport -> TypeLibConverterSettings: set_ASMPath(conversionResult[AssemblyPaths])
+
+ create FileSystemChecks
+ TlbExport -> FileSystemChecks: VerifyFilesPresent(Assembly, ReportAsError=True)
+ FileSystemChecks -> IBuildContext: VerifyFilesPresent(files)
+ TlbExport -> FileSystemChecks: VerifyFilesPresent(TypeLibraryReferences, AssemblyPaths, ReportAsError=False)
+ FileSystemChecks -> IBuildContext: VerifyFilesPresent(files)
+ TlbExport -> FileSystemChecks: VerifyDirectoriesPresent(TypeLibraryReferencePaths, ReportAsError=False)
+ FileSystemChecks -> IBuildContext: VerifyDirectoriesPresent(files)
+
+ TlbExport -> IBuildContext: ConvertAssemblyToTypeLib
+ activate IBuildContext
+ alt IsNet50OrLater
+ create AssemblyLoadContext
+ IBuildContext -> AssemblyLoadContext: add_Resolving(ResolveAssemblyFromSettings)
+ else IsNetFx
+ IBuildContext -> AppDomain.Current: add_AssemblyResolve(ResolveAssemblyFromSettings)
+ end
+
+ IBuildContext -> Assembly: Load(TypeLibExportSettings.SourceAssembly)
+ note right: Load Assemblies recursive
+
+ create LoggingTypeLibExporterSink
+ IBuildContext -> LoggingTypeLibExporterSink: set_Logger(log)
+
+ create TypeLibConverter
+ IBuildContext -> TypeLibConverter: ConvertAssemblyToTypeLib(Assembly, TypeLibConverterSettings, LoggingTypeLibExporterSink)
+ alt result is null
+ IBuildContext -> Log: Error
+ end
+
+ alt TLB Not Existing
+ IBuildContext -> Log: Warning(DSCOM001)
+ end
+
+ alt IsNet50OrLater
+ TlbExport -> AssemblyLoadContext: Unload
+ destroy AssemblyLoadContext
+ end
+ deactivate IBuildContext
+
+ TlbExport -> MsBuild: return result && Log.HasNoErrors
+
+else result false
+ TlbExport -> Log: Error
+ return false
+end
+@enduml
\ No newline at end of file
diff --git a/src/dscom.build/dscom.build.csproj b/src/dscom.build/dscom.build.csproj
new file mode 100644
index 00000000..72f7233c
--- /dev/null
+++ b/src/dscom.build/dscom.build.csproj
@@ -0,0 +1,61 @@
+
+
+
+ net6.0
+ dSPACE.Runtime.InteropServices.BuildTasks
+ $(DsComBuildTargetFrameWork);net48
+ x64
+ dSPACE.Runtime.InteropServices.BuildTasks normal
+ v
+ true
+ build\_dscom\
+ true
+ true
+ true
+ false
+ true
+ snupkg
+
+
+
+ dSPACE GmbH
+ dSPACE.Runtime.InteropServices.BuildTasks
+ dSPACE COM tools - MsBuild Tasks
+ dSPACE COM tools - A replacement for tlbexp.exe and TypeLibConverter.ConvertAssemblyToTypeLib
+ true
+ README.md
+ packageIcon.png
+ dSPACE.Runtime.InteropServices.BuildTasks
+ Apache-2.0
+ https://github.com/dspace-group/dscom
+ true
+ com;tlb;ole;idl;tlbexp;interop;typelib;ConvertAssemblyToTypeLib;TypeLibConverter;MsBuild
+
+
+
+
+
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+ all
+
+
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+ all
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/dscom.build/netstandard2.0/dSPACE.Runtime.InteropServices.BuildTasks.props b/src/dscom.build/netstandard2.0/dSPACE.Runtime.InteropServices.BuildTasks.props
new file mode 100644
index 00000000..8d39e6a8
--- /dev/null
+++ b/src/dscom.build/netstandard2.0/dSPACE.Runtime.InteropServices.BuildTasks.props
@@ -0,0 +1,23 @@
+
+
+
+
+ $(MSBuildAllProjects);$(MSBuildThisFileFullPath)
+
+
+
+
\ No newline at end of file
diff --git a/src/dscom.build/netstandard2.0/dSPACE.Runtime.InteropServices.BuildTasks.targets b/src/dscom.build/netstandard2.0/dSPACE.Runtime.InteropServices.BuildTasks.targets
new file mode 100644
index 00000000..c4ab9b09
--- /dev/null
+++ b/src/dscom.build/netstandard2.0/dSPACE.Runtime.InteropServices.BuildTasks.targets
@@ -0,0 +1,23 @@
+
+
+
+
+ $(MSBuildAllProjects);$(MSBuildThisFileFullPath)
+
+
+
+
\ No newline at end of file
diff --git a/src/dscom.client/AssemblyResolver.cs b/src/dscom.client/AssemblyResolver.cs
index b73e5f62..6c53ab7d 100644
--- a/src/dscom.client/AssemblyResolver.cs
+++ b/src/dscom.client/AssemblyResolver.cs
@@ -19,7 +19,7 @@ namespace dSPACE.Runtime.InteropServices;
///
/// Uses the "ASMPath" option to handle the AppDomain.CurrentDomain.AssemblyResolve event and try to load the specified assemblies.
///
-internal class AssemblyResolver : IDisposable
+internal sealed class AssemblyResolver : IDisposable
{
private bool _disposedValue;
@@ -54,7 +54,7 @@ internal AssemblyResolver(TypeLibConverterOptions options)
return null;
}
- protected virtual void Dispose(bool disposing)
+ private void Dispose(bool disposing)
{
if (!_disposedValue)
{
diff --git a/src/dscom.client/dscom.client.csproj b/src/dscom.client/dscom.client.csproj
index 762b241f..4953f509 100644
--- a/src/dscom.client/dscom.client.csproj
+++ b/src/dscom.client/dscom.client.csproj
@@ -6,6 +6,7 @@
net6.0
AnyCPU
AnyCPU;x86
+ win-x64;win-x86
dSPACE.Runtime.InteropServices
normal
v
diff --git a/src/dscom.test/dscom.test.csproj b/src/dscom.test/dscom.test.csproj
index 17540452..98b70239 100644
--- a/src/dscom.test/dscom.test.csproj
+++ b/src/dscom.test/dscom.test.csproj
@@ -16,6 +16,7 @@
+
@@ -26,10 +27,13 @@
runtime; build; native; contentfiles; analyzers; buildtransitive
all
+
+
+
diff --git a/src/dscom.test/tests/BuildTaskTest.cs b/src/dscom.test/tests/BuildTaskTest.cs
new file mode 100644
index 00000000..547e1d64
--- /dev/null
+++ b/src/dscom.test/tests/BuildTaskTest.cs
@@ -0,0 +1,783 @@
+// Copyright 2022 dSPACE GmbH, Mark Lechtermann, Matthias Nissen and Contributors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+using System.Collections;
+using dSPACE.Runtime.InteropServices.BuildTasks;
+using Microsoft.Build.Framework;
+using Microsoft.Build.Utilities;
+
+using Moq;
+
+namespace dSPACE.Runtime.InteropServices.Tests;
+
+public class BuildTaskTest : BaseTest
+{
+ private sealed class BuildContextStub : IBuildContext
+ {
+ public ISet ExistingFiles = new HashSet(StringComparer.InvariantCultureIgnoreCase);
+
+ public ISet ExistingDirectories = new HashSet(StringComparer.InvariantCultureIgnoreCase);
+
+ public bool ShouldSucceed { get; set; } = true;
+
+ public bool IsRunningOnWindows { get; set; } = true;
+
+ public string RuntimeDescription { get; set; } = "x-unit runner";
+
+ public bool ConvertAssemblyToTypeLib(TypeLibConverterSettings settings, TaskLoggingHelper log)
+ {
+ return ShouldSucceed;
+ }
+
+ public bool EnsureFileExists(string? fileNameAndPath)
+ {
+ return !ExistingFiles.Any() || ExistingFiles.Contains(fileNameAndPath ?? string.Empty);
+ }
+
+ public bool EnsureDirectoryExists(string? directoryNameAndPath)
+ {
+ return !ExistingDirectories.Any() || ExistingDirectories.Contains(directoryNameAndPath ?? string.Empty);
+ }
+ }
+
+ private sealed class BuildEngineStub : IBuildEngine
+ {
+ public bool ContinueOnError => true;
+
+ public int LineNumberOfTaskNode => 1;
+
+ public int ColumnNumberOfTaskNode => 1;
+
+ public string ProjectFileOfTaskNode => "demo.test.csproj";
+
+ public int NumberOfCustomLogEvents { get; private set; }
+
+ public int NumberOfMessageLogEvents { get; private set; }
+
+ public int NumberOfWarningLogEvents { get; private set; }
+
+ public int NumberOfErrorLogEvents { get; private set; }
+
+ public bool BuildProjectFile(string projectFileName, string[] targetNames, IDictionary globalProperties, IDictionary targetOutputs)
+ {
+ return true;
+ }
+
+ public void LogCustomEvent(CustomBuildEventArgs e)
+ {
+ NumberOfCustomLogEvents++;
+ }
+
+ public void LogErrorEvent(BuildErrorEventArgs e)
+ {
+ NumberOfErrorLogEvents++;
+ }
+
+ public void LogMessageEvent(BuildMessageEventArgs e)
+ {
+ NumberOfMessageLogEvents++;
+ }
+
+ public void LogWarningEvent(BuildWarningEventArgs e)
+ {
+ NumberOfWarningLogEvents++;
+ }
+ }
+
+ private static ITaskItem GetTaskItem(string itemSpec, string? path = null, bool useHintPath = false)
+ {
+ var taskItemMock = new Mock(MockBehavior.Loose);
+
+ taskItemMock.SetupGet(inst => inst.ItemSpec).Returns(itemSpec);
+
+ if (useHintPath)
+ {
+ const string HintPath = nameof(HintPath);
+
+ var hintPath = path ?? itemSpec;
+
+ taskItemMock.Setup(inst => inst.GetMetadata(It.IsNotIn(new string[] { HintPath }, StringComparer.InvariantCulture))).Returns(string.Empty);
+ taskItemMock.Setup(inst => inst.GetMetadata(It.Is(HintPath, StringComparer.InvariantCulture))).Returns(hintPath);
+
+ taskItemMock.SetupGet(inst => inst.MetadataCount).Returns(1);
+ taskItemMock.SetupGet(inst => inst.MetadataNames).Returns(new string[] { HintPath });
+ }
+ else
+ {
+ taskItemMock.Setup(inst => inst.GetMetadata(It.IsAny())).Returns(string.Empty);
+
+ taskItemMock.SetupGet(inst => inst.MetadataCount).Returns(0);
+ taskItemMock.SetupGet(inst => inst.MetadataNames).Returns(Array.Empty());
+ }
+
+ return taskItemMock.Object;
+ }
+
+ private static BuildContextStub GetBuildContext()
+ {
+ return new BuildContextStub();
+ }
+
+ private static TlbExport GetBuildTask(out BuildContextStub context, bool shouldSucceed = true)
+ {
+ context = GetBuildContext();
+ context.ShouldSucceed = shouldSucceed;
+ var classUnderTest = new TlbExport(context)
+ {
+ BuildEngine = new BuildEngineStub()
+ };
+ return classUnderTest;
+ }
+
+ [Fact]
+ public void TestDefaultSettingsSuccessful()
+ {
+ var task = GetBuildTask(out _);
+ task.Execute().Should().BeTrue();
+ task.Log.HasLoggedErrors.Should().BeFalse();
+ }
+
+ [Fact]
+ public void TestDefaultSettingsFailingOnNonWindows()
+ {
+ var task = GetBuildTask(out var context);
+ context.IsRunningOnWindows = false;
+ task.Execute().Should().BeFalse();
+ task.Log.HasLoggedErrors.Should().BeTrue();
+ }
+
+ [Fact]
+ public void TestDefaultSettingsFailsOnTransformationError()
+ {
+ var task = GetBuildTask(out _, false);
+ task.Execute().Should().BeFalse();
+ task.Log.HasLoggedErrors.Should().BeFalse();
+ }
+
+ [Fact]
+ public void TestGuidIsEmpty()
+ {
+ var task = GetBuildTask(out _);
+ task.TlbOverriddenId = string.Empty;
+ task.Execute().Should().BeFalse();
+ task.Log.HasLoggedErrors.Should().BeTrue();
+ }
+
+ [Theory]
+ [InlineData("03bc128f-0e54-44f0-9c3e-3fa1ed1.ceeb1")]
+ [InlineData("03bc128g0e5444f09c3e3fa1ed1ceeb1")]
+ [InlineData("03bc128f-0e54-44f0-9c3e-3fa1ed1ceeb1-abcd")]
+ [InlineData("dcba-03bc128f-0e54-44f0-9c3e-3fa1ed1ceeb1")]
+ public void TestGuidIsMalformed(string malformedGuid)
+ {
+ var task = GetBuildTask(out _);
+ task.TlbOverriddenId = malformedGuid;
+ task.Execute().Should().BeFalse();
+ task.Log.HasLoggedErrors.Should().BeTrue();
+ }
+
+ [Theory]
+ [InlineData("03bc128f-0e54-44f0-9c3e-3fa1ed1ceeb1")]
+ [InlineData("03bc128f0e5444f09c3e3fa1ed1ceeb1")]
+ [InlineData("00000000-0000-0000-0000-000000000000")]
+ public void TestGuidIsCorrect(string guid)
+ {
+ var task = GetBuildTask(out _);
+ task.TlbOverriddenId = guid;
+ task.Execute().Should().BeTrue();
+ task.Log.HasLoggedErrors.Should().BeFalse();
+ }
+
+ [Fact]
+ public void TestAssemblyFileCheckSuccess()
+ {
+ var task = GetBuildTask(out var context);
+ var assemblyFileName = "MyAssemblyFile.dll";
+ var assemblyFilePath = Path.Combine(Path.GetTempPath(), assemblyFileName);
+ task.SourceAssemblyFile = assemblyFilePath;
+ context.ExistingFiles.Add(assemblyFilePath);
+
+ task.Execute().Should().BeTrue();
+ task.Log.HasLoggedErrors.Should().BeFalse();
+ }
+
+ [Fact]
+ public void TestAssemblyFileCheckFail()
+ {
+ var task = GetBuildTask(out var context);
+ var assemblyFileName = "MyAssemblyFile.dll";
+ var assemblyFilePath = Path.Combine(Path.GetTempPath(), assemblyFileName);
+ task.SourceAssemblyFile = assemblyFilePath;
+ context.ExistingFiles.Add(assemblyFilePath + ".notExisting");
+
+ task.Execute().Should().BeFalse();
+ task.Log.HasLoggedErrors.Should().BeTrue();
+ }
+
+ [Fact]
+ public void TestAssemblyFileReferencesItemSpecCheckSuccess()
+ {
+ var task = GetBuildTask(out var context);
+ var assemblyFileName = "MyAssemblyFile.dll";
+ var assemblyFilePath = Path.Combine(Path.GetTempPath(), assemblyFileName);
+ task.SourceAssemblyFile = assemblyFilePath;
+ context.ExistingFiles.Add(assemblyFilePath);
+
+ var taskItems = new List();
+ for (var i = 1; i <= 5; i++)
+ {
+ var fileName = $"ReferencedAssembly{i}.dll";
+ var filePath = Path.Combine(Path.GetTempPath(), fileName);
+ var taskItem = GetTaskItem(filePath);
+ taskItems.Add(taskItem);
+ context.ExistingFiles.Add(filePath);
+ }
+
+ task.AssemblyPaths = taskItems.ToArray();
+
+ task.Execute().Should().BeTrue();
+ task.Log.HasLoggedErrors.Should().BeFalse();
+ task.BuildEngine.As().NumberOfWarningLogEvents.Should().Be(0, "No warning should be present");
+ }
+
+ [Fact]
+ public void TestAssemblyFileReferencesItemSpecCheckFail()
+ {
+ var task = GetBuildTask(out var context);
+ var assemblyFileName = "MyAssemblyFile.dll";
+ var assemblyFilePath = Path.Combine(Path.GetTempPath(), assemblyFileName);
+ task.SourceAssemblyFile = assemblyFilePath;
+ context.ExistingFiles.Add(assemblyFilePath);
+
+ var notFoundByRandom = new Random().Next(5) + 1;
+
+ var taskItems = new List();
+ for (var i = 1; i <= 5; i++)
+ {
+ var fileName = $"ReferencedAssembly{i}.dll";
+ var filePath = Path.Combine(Path.GetTempPath(), fileName);
+ var taskItem = GetTaskItem(filePath);
+ taskItems.Add(taskItem);
+ if (i != notFoundByRandom)
+ {
+ context.ExistingFiles.Add(filePath);
+ }
+ }
+
+ task.AssemblyPaths = taskItems.ToArray();
+
+ task.Execute().Should().BeTrue();
+ task.Log.HasLoggedErrors.Should().BeFalse();
+ task.BuildEngine.As().Should().NotBe(0, "At least one warning should be present");
+ }
+
+ [Fact]
+ public void TestAssemblyFileReferencesHintPathCheckSuccess()
+ {
+ var task = GetBuildTask(out var context);
+ var assemblyFileName = "MyAssemblyFile.dll";
+ var assemblyFilePath = Path.Combine(Path.GetTempPath(), assemblyFileName);
+ task.SourceAssemblyFile = assemblyFilePath;
+ context.ExistingFiles.Add(assemblyFilePath);
+
+ var taskItems = new List();
+ for (var i = 1; i <= 5; i++)
+ {
+ var fileName = $"ReferencedAssembly{i}.dll";
+ var filePath = Path.Combine(Path.GetTempPath(), fileName);
+ var taskItem = GetTaskItem(fileName, filePath, true);
+ taskItems.Add(taskItem);
+ context.ExistingFiles.Add(filePath);
+ }
+
+ task.AssemblyPaths = taskItems.ToArray();
+
+ task.Execute().Should().BeTrue();
+ task.Log.HasLoggedErrors.Should().BeFalse();
+ task.BuildEngine.As().NumberOfWarningLogEvents.Should().Be(0, "No warning should be present");
+ }
+
+ [Fact]
+ public void TestAssemblyFileReferencesHintPathCheckFail()
+ {
+ var task = GetBuildTask(out var context);
+ var assemblyFileName = "MyAssemblyFile.dll";
+ var assemblyFilePath = Path.Combine(Path.GetTempPath(), assemblyFileName);
+ task.SourceAssemblyFile = assemblyFilePath;
+ context.ExistingFiles.Add(assemblyFilePath);
+
+ var notFoundByRandom = new Random().Next(5) + 1;
+
+ var taskItems = new List();
+ for (var i = 1; i <= 5; i++)
+ {
+ var fileName = $"ReferencedAssembly{i}.dll";
+ var filePath = Path.Combine(Path.GetTempPath(), fileName);
+ var taskItem = GetTaskItem(fileName, filePath, true);
+ taskItems.Add(taskItem);
+ if (i != notFoundByRandom)
+ {
+ context.ExistingFiles.Add(filePath);
+ }
+ }
+
+ task.AssemblyPaths = taskItems.ToArray();
+
+ task.Execute().Should().BeTrue();
+ task.Log.HasLoggedErrors.Should().BeFalse();
+ task.BuildEngine.As().Should().NotBe(0, "At least one warning should be present");
+ }
+
+ [Fact]
+ public void TestAssemblyFileReferencesHybridCheckSuccess()
+ {
+ var task = GetBuildTask(out var context);
+ var assemblyFileName = "MyAssemblyFile.dll";
+ var assemblyFilePath = Path.Combine(Path.GetTempPath(), assemblyFileName);
+ task.SourceAssemblyFile = assemblyFilePath;
+ context.ExistingFiles.Add(assemblyFilePath);
+
+ var taskItems = new List();
+ for (var i = 1; i <= 5; i++)
+ {
+ var fileName = $"ReferencedAssembly{i}.dll";
+ var filePath = Path.Combine(Path.GetTempPath(), fileName);
+ var arg = (i % 2 == 0) ? ((ValueTuple)(fileName, filePath)) : ((ValueTuple)(filePath, null));
+ var (fn, fp) = arg;
+ var taskItem = GetTaskItem(fn, fp!, i % 2 == 0);
+ taskItems.Add(taskItem);
+ context.ExistingFiles.Add(filePath);
+ }
+
+ task.AssemblyPaths = taskItems.ToArray();
+
+ task.Execute().Should().BeTrue();
+ task.Log.HasLoggedErrors.Should().BeFalse();
+ task.BuildEngine.As().NumberOfWarningLogEvents.Should().Be(0, "No warning should be present");
+ }
+
+ [Fact]
+ public void TestAssemblyFileReferencesHybridCheckFail()
+ {
+ var task = GetBuildTask(out var context);
+ var assemblyFileName = "MyAssemblyFile.dll";
+ var assemblyFilePath = Path.Combine(Path.GetTempPath(), assemblyFileName);
+ task.SourceAssemblyFile = assemblyFilePath;
+ context.ExistingFiles.Add(assemblyFilePath);
+
+ var notFoundByRandom = new Random().Next(5) + 1;
+
+ var taskItems = new List();
+ for (var i = 1; i <= 5; i++)
+ {
+ var fileName = $"ReferencedAssembly{i}.dll";
+ var filePath = Path.Combine(Path.GetTempPath(), fileName);
+ var arg = (i % 2 == 0) ? ((ValueTuple)(fileName, filePath)) : ((ValueTuple)(filePath, null));
+ var (fn, fp) = arg;
+ var taskItem = GetTaskItem(fn, fp!, i % 2 == 0);
+ taskItems.Add(taskItem);
+ if (i != notFoundByRandom)
+ {
+ context.ExistingFiles.Add(filePath);
+ }
+ }
+
+ task.AssemblyPaths = taskItems.ToArray();
+
+ task.Execute().Should().BeTrue();
+ task.Log.HasLoggedErrors.Should().BeFalse();
+ task.BuildEngine.As().Should().NotBe(0, "At least one warning should be present");
+ }
+
+ [Fact]
+ public void TestTypeLibraryReferencesItemSpecCheckSuccess()
+ {
+ var task = GetBuildTask(out var context);
+ var assemblyFileName = "MyAssemblyFile.dll";
+ var assemblyFilePath = Path.Combine(Path.GetTempPath(), assemblyFileName);
+ task.SourceAssemblyFile = assemblyFilePath;
+ context.ExistingFiles.Add(assemblyFilePath);
+
+ var taskItems = new List();
+ for (var i = 1; i <= 5; i++)
+ {
+ var fileName = $"TypeLibrary{i}.tlb";
+ var filePath = Path.Combine(Path.GetTempPath(), fileName);
+ var taskItem = GetTaskItem(filePath);
+ taskItems.Add(taskItem);
+ context.ExistingFiles.Add(filePath);
+ }
+
+ task.TypeLibraryReferences = taskItems.ToArray();
+
+ task.Execute().Should().BeTrue();
+ task.Log.HasLoggedErrors.Should().BeFalse();
+ task.BuildEngine.As().NumberOfWarningLogEvents.Should().Be(0, "No warning should be present");
+ }
+
+ [Fact]
+ public void TestTypeLibraryReferencesItemSpecCheckFail()
+ {
+ var task = GetBuildTask(out var context);
+ var assemblyFileName = "MyAssemblyFile.dll";
+ var assemblyFilePath = Path.Combine(Path.GetTempPath(), assemblyFileName);
+ task.SourceAssemblyFile = assemblyFilePath;
+ context.ExistingFiles.Add(assemblyFilePath);
+
+ var notFoundByRandom = new Random().Next(5) + 1;
+
+ var taskItems = new List();
+ for (var i = 1; i <= 5; i++)
+ {
+ var fileName = $"TypeLibrary{i}.tlb";
+ var filePath = Path.Combine(Path.GetTempPath(), fileName);
+ var taskItem = GetTaskItem(filePath);
+ taskItems.Add(taskItem);
+ if (i != notFoundByRandom)
+ {
+ context.ExistingFiles.Add(filePath);
+ }
+ }
+
+ task.TypeLibraryReferences = taskItems.ToArray();
+
+ task.Execute().Should().BeTrue();
+ task.Log.HasLoggedErrors.Should().BeFalse();
+ task.BuildEngine.As().Should().NotBe(0, "At least one warning should be present");
+ }
+
+ [Fact]
+ public void TestTypeLibraryReferencesHintPathCheckSuccess()
+ {
+ var task = GetBuildTask(out var context);
+ var assemblyFileName = "MyAssemblyFile.dll";
+ var assemblyFilePath = Path.Combine(Path.GetTempPath(), assemblyFileName);
+ task.SourceAssemblyFile = assemblyFilePath;
+ context.ExistingFiles.Add(assemblyFilePath);
+
+ var taskItems = new List();
+ for (var i = 1; i <= 5; i++)
+ {
+ var fileName = $"TypeLibrary{i}.tlb";
+ var filePath = Path.Combine(Path.GetTempPath(), fileName);
+ var taskItem = GetTaskItem(fileName, filePath, true);
+ taskItems.Add(taskItem);
+ context.ExistingFiles.Add(filePath);
+ }
+
+ task.TypeLibraryReferences = taskItems.ToArray();
+
+ task.Execute().Should().BeTrue();
+ task.Log.HasLoggedErrors.Should().BeFalse();
+ task.BuildEngine.As().NumberOfWarningLogEvents.Should().Be(5, "Five warnings should be present, since TLBs cannot be referenced using HintPath");
+ }
+
+ [Fact]
+ public void TestypeLibraryReferencesHintPathCheckFail()
+ {
+ var task = GetBuildTask(out var context);
+ var assemblyFileName = "MyAssemblyFile.dll";
+ var assemblyFilePath = Path.Combine(Path.GetTempPath(), assemblyFileName);
+ task.SourceAssemblyFile = assemblyFilePath;
+ context.ExistingFiles.Add(assemblyFilePath);
+
+ var notFoundByRandom = new Random().Next(5) + 1;
+
+ var taskItems = new List();
+ for (var i = 1; i <= 5; i++)
+ {
+ var fileName = $"TypeLibrary{i}.tlb";
+ var filePath = Path.Combine(Path.GetTempPath(), fileName);
+ var taskItem = GetTaskItem(fileName, filePath, true);
+ taskItems.Add(taskItem);
+ if (i != notFoundByRandom)
+ {
+ context.ExistingFiles.Add(filePath);
+ }
+ }
+
+ task.TypeLibraryReferences = taskItems.ToArray();
+
+ task.Execute().Should().BeTrue();
+ task.Log.HasLoggedErrors.Should().BeFalse();
+ task.BuildEngine.As().Should().NotBe(0, "At least one warning should be present");
+ }
+
+ [Fact]
+ public void TestypeLibraryReferencesHybridCheckSuccess()
+ {
+ var task = GetBuildTask(out var context);
+ var assemblyFileName = "MyAssemblyFile.dll";
+ var assemblyFilePath = Path.Combine(Path.GetTempPath(), assemblyFileName);
+ task.SourceAssemblyFile = assemblyFilePath;
+ context.ExistingFiles.Add(assemblyFilePath);
+
+ var taskItems = new List();
+ for (var i = 1; i <= 5; i++)
+ {
+ var fileName = $"TypeLibrary{i}.tlb";
+ var filePath = Path.Combine(Path.GetTempPath(), fileName);
+ var arg = (i % 2 == 0) ? ((ValueTuple)(fileName, filePath)) : ((ValueTuple)(filePath, null));
+ var (fn, fp) = arg;
+ var taskItem = GetTaskItem(fn, fp!, i % 2 == 0);
+ taskItems.Add(taskItem);
+ context.ExistingFiles.Add(filePath);
+ }
+
+ task.TypeLibraryReferences = taskItems.ToArray();
+
+ task.Execute().Should().BeTrue();
+ task.Log.HasLoggedErrors.Should().BeFalse();
+ task.BuildEngine.As().NumberOfWarningLogEvents.Should().Be(2, "Five warnings should be present, since TLBs cannot be referenced using HintPath");
+ }
+
+ [Fact]
+ public void TestTypeLibraryReferencesHybridCheckFail()
+ {
+ var task = GetBuildTask(out var context);
+ var assemblyFileName = "MyAssemblyFile.dll";
+ var assemblyFilePath = Path.Combine(Path.GetTempPath(), assemblyFileName);
+ task.SourceAssemblyFile = assemblyFilePath;
+ context.ExistingFiles.Add(assemblyFilePath);
+
+ var notFoundByRandom = new Random().Next(5) + 1;
+
+ var taskItems = new List();
+ for (var i = 1; i <= 5; i++)
+ {
+ var fileName = $"TypeLibrary{i}.tlb";
+ var filePath = Path.Combine(Path.GetTempPath(), fileName);
+ var arg = (i % 2 == 0) ? ((ValueTuple)(fileName, filePath)) : ((ValueTuple)(filePath, null));
+ var (fn, fp) = arg;
+ var taskItem = GetTaskItem(fn, fp!, i % 2 == 0);
+ taskItems.Add(taskItem);
+ if (i != notFoundByRandom)
+ {
+ context.ExistingFiles.Add(filePath);
+ }
+ }
+
+ task.TypeLibraryReferences = taskItems.ToArray();
+
+ task.Execute().Should().BeTrue();
+ task.Log.HasLoggedErrors.Should().BeFalse();
+ task.BuildEngine.As().Should().NotBe(0, "At least one warning should be present");
+ }
+
+ [Fact]
+ public void TestTypeLibraryReferencePathsItemSpecCheckSuccess()
+ {
+ var task = GetBuildTask(out var context);
+ var assemblyFileName = "MyAssemblyFile.dll";
+ var assemblyFilePath = Path.Combine(Path.GetTempPath(), assemblyFileName);
+ task.SourceAssemblyFile = assemblyFilePath;
+ context.ExistingFiles.Add(assemblyFilePath);
+
+ var taskItems = new List();
+ for (var i = 1; i <= 5; i++)
+ {
+ var dirName = $"TypeLibrarySearchFolder{i}";
+ var dirPath = Path.Combine(Path.GetTempPath(), dirName);
+ var taskItem = GetTaskItem(dirPath);
+ taskItems.Add(taskItem);
+ context.ExistingDirectories.Add(dirPath);
+ }
+
+ task.TypeLibraryReferencePaths = taskItems.ToArray();
+
+ task.Execute().Should().BeTrue();
+ task.Log.HasLoggedErrors.Should().BeFalse();
+ task.BuildEngine.As().NumberOfWarningLogEvents.Should().Be(0, "No warning should be present");
+ }
+
+ [Fact]
+ public void TestTypeLibraryReferencePathsItemSpecCheckFail()
+ {
+ var task = GetBuildTask(out var context);
+ var assemblyFileName = "MyAssemblyFile.dll";
+ var assemblyFilePath = Path.Combine(Path.GetTempPath(), assemblyFileName);
+ task.SourceAssemblyFile = assemblyFilePath;
+ context.ExistingFiles.Add(assemblyFilePath);
+
+ var notFoundByRandom = new Random().Next(5) + 1;
+
+ var taskItems = new List();
+ for (var i = 1; i <= 5; i++)
+ {
+ var dirName = $"TypeLibrarySearchFolder{i}";
+ var dirPath = Path.Combine(Path.GetTempPath(), dirName);
+ var taskItem = GetTaskItem(dirPath);
+ taskItems.Add(taskItem);
+ if (i != notFoundByRandom)
+ {
+ context.ExistingDirectories.Add(dirPath);
+ }
+ }
+
+ task.TypeLibraryReferencePaths = taskItems.ToArray();
+
+ task.Execute().Should().BeTrue();
+ task.Log.HasLoggedErrors.Should().BeFalse();
+ task.BuildEngine.As().Should().NotBe(0, "At least one warning should be present");
+ }
+
+ [Fact]
+ public void TestTypeLibraryReferencePathsHintPathCheckSuccess()
+ {
+ var task = GetBuildTask(out var context);
+ var assemblyFileName = "MyAssemblyFile.dll";
+ var assemblyFilePath = Path.Combine(Path.GetTempPath(), assemblyFileName);
+ task.SourceAssemblyFile = assemblyFilePath;
+ context.ExistingFiles.Add(assemblyFilePath);
+
+ var taskItems = new List();
+ for (var i = 1; i <= 5; i++)
+ {
+ var dirName = $"TypeLibrarySearchFolder{i}";
+ var dirPath = Path.Combine(Path.GetTempPath(), dirName);
+ var taskItem = GetTaskItem(dirName, dirPath, true);
+ taskItems.Add(taskItem);
+ context.ExistingDirectories.Add(dirPath);
+ }
+
+ task.TypeLibraryReferencePaths = taskItems.ToArray();
+
+ task.Execute().Should().BeTrue();
+ task.Log.HasLoggedErrors.Should().BeFalse();
+ task.BuildEngine.As().NumberOfWarningLogEvents.Should().Be(5, "Five warnings should be present, since TLBs cannot be referenced using HintPath");
+ }
+
+ [Fact]
+ public void TestypeLibraryReferencePathsHintPathCheckFail()
+ {
+ var task = GetBuildTask(out var context);
+ var assemblyFileName = "MyAssemblyFile.dll";
+ var assemblyFilePath = Path.Combine(Path.GetTempPath(), assemblyFileName);
+ task.SourceAssemblyFile = assemblyFilePath;
+ context.ExistingFiles.Add(assemblyFilePath);
+
+ var notFoundByRandom = new Random().Next(5) + 1;
+
+ var taskItems = new List();
+ for (var i = 1; i <= 5; i++)
+ {
+ var dirName = $"TypeLibrarySearchFolder{i}";
+ var dirPath = Path.Combine(Path.GetTempPath(), dirName);
+ var taskItem = GetTaskItem(dirName, dirPath, true);
+ taskItems.Add(taskItem);
+ if (i != notFoundByRandom)
+ {
+ context.ExistingDirectories.Add(dirPath);
+ }
+ }
+
+ task.TypeLibraryReferencePaths = taskItems.ToArray();
+
+ task.Execute().Should().BeTrue();
+ task.Log.HasLoggedErrors.Should().BeFalse();
+ task.BuildEngine.As().Should().NotBe(0, "At least one warning should be present");
+ }
+
+ [Fact]
+ public void TestypeLibraryReferencePathsHybridCheckSuccess()
+ {
+ var task = GetBuildTask(out var context);
+ var assemblyFileName = "MyAssemblyFile.dll";
+ var assemblyFilePath = Path.Combine(Path.GetTempPath(), assemblyFileName);
+ task.SourceAssemblyFile = assemblyFilePath;
+ context.ExistingFiles.Add(assemblyFilePath);
+
+ var taskItems = new List();
+ for (var i = 1; i <= 5; i++)
+ {
+ var dirName = $"TypeLibrarySearchFolder{i}";
+ var dirPath = Path.Combine(Path.GetTempPath(), dirName);
+ var arg = (i % 2 == 0) ? ((ValueTuple)(dirName, dirPath)) : ((ValueTuple)(dirPath, null));
+ var (dn, dp) = arg;
+ var taskItem = GetTaskItem(dn, dp!, i % 2 == 0);
+ taskItems.Add(taskItem);
+ context.ExistingDirectories.Add(dirPath);
+ }
+
+ task.TypeLibraryReferencePaths = taskItems.ToArray();
+
+ task.Execute().Should().BeTrue();
+ task.Log.HasLoggedErrors.Should().BeFalse();
+ task.BuildEngine.As().NumberOfWarningLogEvents.Should().Be(2, "Five warnings should be present, since TLBs cannot be referenced using HintPath");
+ }
+
+ [Fact]
+ public void TestTypeLibraryReferencePathsHybridCheckFail()
+ {
+ var task = GetBuildTask(out var context);
+ var assemblyFileName = "MyAssemblyFile.dll";
+ var assemblyFilePath = Path.Combine(Path.GetTempPath(), assemblyFileName);
+ task.SourceAssemblyFile = assemblyFilePath;
+ context.ExistingFiles.Add(assemblyFilePath);
+
+ var notFoundByRandom = new Random().Next(5) + 1;
+
+ var taskItems = new List();
+ for (var i = 1; i <= 5; i++)
+ {
+ var dirName = $"TypeLibrarySearchFolder{i}";
+ var dirPath = Path.Combine(Path.GetTempPath(), dirName);
+ var arg = (i % 2 == 0) ? ((ValueTuple)(dirName, dirPath)) : ((ValueTuple)(dirPath, null));
+ var (dn, dp) = arg;
+ var taskItem = GetTaskItem(dn, dp!, i % 2 == 0);
+ taskItems.Add(taskItem);
+ if (i != notFoundByRandom)
+ {
+ context.ExistingDirectories.Add(dirPath);
+ }
+ }
+
+ task.TypeLibraryReferencePaths = taskItems.ToArray();
+
+ task.Execute().Should().BeTrue();
+ task.Log.HasLoggedErrors.Should().BeFalse();
+ task.BuildEngine.As().Should().NotBe(0, "At least one warning should be present");
+ }
+
+ [Fact]
+ public void TestBuildIsSuccessful()
+ {
+ var task = GetBuildTask(out var context);
+ var assemblyFileName = "MyAssemblyFile.dll";
+ var assemblyFilePath = Path.Combine(Path.GetTempPath(), assemblyFileName);
+ task.SourceAssemblyFile = assemblyFilePath;
+ context.ExistingFiles.Add(assemblyFilePath);
+
+ context.ShouldSucceed = true;
+
+ task.Execute().Should().BeTrue();
+ task.Log.HasLoggedErrors.Should().BeFalse();
+ }
+
+ [Fact]
+ public void TestBuildIsSuccessFail()
+ {
+ var task = GetBuildTask(out var context);
+ var assemblyFileName = "MyAssemblyFile.dll";
+ var assemblyFilePath = Path.Combine(Path.GetTempPath(), assemblyFileName);
+ task.SourceAssemblyFile = assemblyFilePath;
+ context.ExistingFiles.Add(assemblyFilePath);
+
+ context.ShouldSucceed = false;
+
+ task.Execute().Should().BeFalse();
+ task.Log.HasLoggedErrors.Should().BeFalse();
+ }
+}
+