Add ability to patch corefx binaries from a locally built enlistment for coreclr...
authorDavid Mason <davmason@microsoft.com>
Thu, 14 Nov 2019 00:47:10 +0000 (16:47 -0800)
committerGitHub <noreply@github.com>
Thu, 14 Nov 2019 00:47:10 +0000 (16:47 -0800)
Adds the "localcorefxpath" argument to build-test.cmd

Commit migrated from https://github.com/dotnet/coreclr/commit/216a3b7fb55c10cfde63397f9f2ba411a6a61518

src/coreclr/build-test.cmd
src/coreclr/build-test.sh
src/coreclr/tests/build.proj
src/coreclr/tests/dir.common.props
src/coreclr/tests/runtest.cmd
src/coreclr/tests/scripts/patch-corefx.py [new file with mode: 0644]
src/coreclr/tests/src/Common/test_dependencies/test_dependencies.csproj
src/coreclr/tests/src/runtest.proj

index 3f1ad5e..435a089 100644 (file)
@@ -62,6 +62,8 @@ set __DoCrossgen2=
 set __CopyNativeTestBinaries=0
 set __CopyNativeProjectsAfterCombinedTestBuild=true
 set __SkipGenerateLayout=0
+set __LocalCoreFXPath=
+set __SkipFXRestoreArg=
 set __GenerateLayoutOnly=0
 
 @REM CMD has a nasty habit of eating "=" on the argument list, so passing:
@@ -106,6 +108,7 @@ if /i "%1" == "Exclude"               (set __Exclude=%2&set processedArgs=!proce
 if /i "%1" == "-priority"             (set __Priority=%2&shift&set processedArgs=!processedArgs! %1=%2&shift&goto Arg_Loop)
 if /i "%1" == "copynativeonly"        (set __CopyNativeTestBinaries=1&set __SkipNative=1&set __CopyNativeProjectsAfterCombinedTestBuild=false&set processedArgs=!processedArgs! %1&shift&goto Arg_Loop)
 if /i "%1" == "skipgeneratelayout"    (set __SkipGenerateLayout=1&set processedArgs=!processedArgs! %1&shift&goto Arg_Loop)
+if /i "%1" == "localcorefxpath"       (set __LocalCoreFXPath=%2&set __SkipFXRestoreArg=/p:__SkipFXRestore=true&set processedArgs=!processedArgs! %1 %2&shift&shift&goto Arg_Loop)
 if /i "%1" == "generatelayoutonly"    (set __SkipManaged=1&set __SkipNative=1&set __CopyNativeProjectsAfterCombinedTestBuild=false&set processedArgs=!processedArgs! %1&shift&goto Arg_Loop)
 if /i "%1" == "--"                    (set processedArgs=!processedArgs! %1&shift&goto Arg_Loop)
 
@@ -296,6 +299,7 @@ powershell -NoProfile -ExecutionPolicy ByPass -NoLogo -Command "%__RepoRootDir%\
   %__ProjectDir%\tests\build.proj -warnAsError:0 /t:BatchRestorePackages /nodeReuse:false^
   /p:RestoreDefaultOptimizationDataPackage=false /p:PortableBuild=true^
   /p:UsePartialNGENOptimization=false /maxcpucount^
+  %__SkipFXRestoreArg%^
   !__Logging! %__CommonMSBuildArgs% %__PriorityArg% %__UnprocessedBuildArgs%
 
 if errorlevel 1 (
@@ -361,6 +365,7 @@ for /l %%G in (1, 1, %__NumberOfTestGroups%) do (
         set __MSBuildBuildArgs=!__MSBuildBuildArgs! !__UnprocessedBuildArgs!
         set __MSBuildBuildArgs=!__MSBuildBuildArgs! /p:CopyNativeProjectBinaries=!__CopyNativeProjectsAfterCombinedTestBuild!
         set __MSBuildBuildArgs=!__MSBuildBuildArgs! /p:__SkipPackageRestore=true
+        set __MSBuildBuildArgs=!__MSBuildBuildArgs! !__SkipFXRestoreArg!
         echo Running: msbuild !__MSBuildBuildArgs!
         !__CommonMSBuildCmdPrefix! !__MSBuildBuildArgs!
 
@@ -376,7 +381,7 @@ for /l %%G in (1, 1, %__NumberOfTestGroups%) do (
         )
     ) else (
         REM Disable warnAsError - coreclr issue 19922
-        set __MSBuildBuildArgs=!__ProjectDir!\tests\build.proj -warnAsError:0 /nodeReuse:false !__Logging! !TargetsWindowsMsbuildArg! !__msbuildArgs! !__PriorityArg! !__UnprocessedBuildArgs! "/t:CopyAllNativeProjectReferenceBinaries"
+        set __MSBuildBuildArgs=!__ProjectDir!\tests\build.proj -warnAsError:0 /nodeReuse:false !__Logging! !TargetsWindowsMsbuildArg! !__msbuildArgs!  !__PriorityArg! !__SkipFXRestoreArg! !__UnprocessedBuildArgs! "/t:CopyAllNativeProjectReferenceBinaries"
         echo Running: msbuild !__MSBuildBuildArgs!
         !__CommonMSBuildCmdPrefix! !__MSBuildBuildArgs!
 
@@ -402,7 +407,7 @@ if "%__CopyNativeTestBinaries%" == "1" goto :SkipManagedBuild
 REM Check that we've built about as many tests as we expect. This is primarily intended to prevent accidental changes that cause us to build
 REM drastically fewer Pri-1 tests than expected.
 echo %__MsgPrefix%Check the managed tests build
-echo Running: dotnet msbuild %__ProjectDir%\tests\src\runtest.proj /t:CheckTestBuild /nodeReuse:false /p:CLRTestPriorityToBuild=%__Priority% %__msbuildArgs% %__unprocessedBuildArgs%
+echo Running: dotnet msbuild %__ProjectDir%\tests\src\runtest.proj /t:CheckTestBuild /nodeReuse:false /p:CLRTestPriorityToBuild=%__Priority% %__SkipFXRestoreArg% %__msbuildArgs% %__unprocessedBuildArgs%
 powershell -NoProfile -ExecutionPolicy ByPass -NoLogo -File "%__RepoRootDir%\eng\common\msbuild.ps1" %__ArcadeScriptArgs%^
     %__ProjectDir%\tests\src\runtest.proj /t:CheckTestBuild /nodeReuse:false /p:CLRTestPriorityToBuild=%__Priority% %__msbuildArgs% %__unprocessedBuildArgs%
 if errorlevel 1 (
@@ -460,6 +465,7 @@ powershell -NoProfile -ExecutionPolicy ByPass -NoLogo -File "%__RepoRootDir%\eng
   %__ProjectDir%\tests\src\runtest.proj /t:CreateTestOverlay /nodeReuse:false^
   /p:RestoreDefaultOptimizationDataPackage=false /p:PortableBuild=true^
   /p:UsePartialNGENOptimization=false /maxcpucount^
+  %__SkipFXRestoreArg%^
   !__Logging! %__CommonMSBuildArgs% %RuntimeIdArg% %__PriorityArg% %__UnprocessedBuildArgs%
 if errorlevel 1 (
     echo %__ErrMsgPrefix%%__MsgPrefix%Error: Create Test Overlay failed. Refer to the build log files for details:
@@ -469,7 +475,7 @@ if errorlevel 1 (
     exit /b 1
 )
 
-xcopy /s /y "%CORE_ROOT_STAGE%" "%CORE_ROOT%"
+xcopy /s /y /i "%CORE_ROOT_STAGE%" "%CORE_ROOT%"
 
 REM =========================================================================================
 REM ===
@@ -522,7 +528,7 @@ set __MsbuildErr=/flp2:ErrorsOnly;LogFile="%__BuildErr%"
 set __Logging=!__MsbuildLog! !__MsbuildWrn! !__MsbuildErr!
 
 REM Build wrappers using the local SDK's msbuild. As we move to arcade, the other builds should be moved away from run.exe as well.
-call "%__ProjectDir%\dotnet.cmd" msbuild %__ProjectDir%\tests\src\runtest.proj /nodereuse:false /p:BuildWrappers=true !__Logging! %__msbuildArgs% %TargetsWindowsMsbuildArg% %__UnprocessedBuildArgs%
+call "%__ProjectDir%\dotnet.cmd" msbuild %__ProjectDir%\tests\src\runtest.proj /nodereuse:false /p:BuildWrappers=true !__Logging! %__msbuildArgs% %TargetsWindowsMsbuildArg% %__SkipFXRestoreArg% %__UnprocessedBuildArgs%
 if errorlevel 1 (
     echo %__ErrMsgPrefix%%__MsgPrefix%Error: XUnit wrapper build failed. Refer to the build log files for details:
     echo     %__BuildLog%
@@ -565,6 +571,22 @@ if defined __DoCrossgen2 (
 
 rd /s /q "%CORE_ROOT_STAGE%"
 
+
+REM =========================================================================================
+REM ===
+REM === Copy CoreFX assemblies if needed.
+REM ===
+REM =========================================================================================
+
+if NOT "%__LocalCoreFXPath%"=="" (
+    echo Patch CoreFX from %__LocalCoreFXPath%
+    set NEXTCMD=python "%__ProjectDir%\tests\scripts\patch-corefx.py" -clr_core_root "%CORE_ROOT%"^
+    -fx_root "%__LocalCoreFXPath%" -arch %__BuildArch% -build_type %__BuildType%
+    echo !NEXTCMD!
+    !NEXTCMD!
+)
+
+
 REM =========================================================================================
 REM ===
 REM === All builds complete!
index 7d6db0d..f85be52 100755 (executable)
@@ -132,6 +132,16 @@ generate_layout()
     fi
 }
 
+patch_corefx_libraries()
+{
+    echo "${__MsgPrefix}Patching CORE_ROOT: '${CORE_ROOT}' with CoreFX libaries from enlistment '${__LocalCoreFXPath}"
+
+    patchCoreFXArguments=("-clr_core_root" "${CORE_ROOT}" "-fx_root" "${__LocalCoreFXPath}" "-arch" "${__BuildArch}" "-build_type" "${__BuildType}")
+    scriptPath="$__ProjectDir/tests/scripts"
+    echo "python ${scriptPath}/patch-corefx.py ${patchCoreFXArguments[@]}"
+    $__Python "${scriptPath}/patch-corefx.py" "${patchCoreFXArguments[@]}"
+}
+
 precompile_coreroot_fx()
 {
     local overlayDir=$CORE_ROOT
@@ -362,6 +372,10 @@ build_Tests()
     if [ $__SkipGenerateLayout != 1 ]; then
         generate_layout
     fi
+
+    if [ ! -z "$__LocalCoreFXPath" ]; then
+        patch_corefx_libraries
+    fi
 }
 
 build_MSBuild_projects()
@@ -622,6 +636,16 @@ handle_arguments() {
             __SkipGenerateLayout=1
             ;;
 
+        localcorefxpath)
+            if [ -n "$2" ]; then
+                __LocalCoreFXPath="$2"
+                shift
+            else
+                echo "ERROR: 'localcorefxpath' requires a non-empty option argument"
+                exit 1
+            fi
+            ;;
+
         *)
             __UnprocessedBuildArgs+=("$1")
             ;;
index 50c4682..da347b5 100644 (file)
@@ -55,8 +55,8 @@
   <Target Name="RestorePackage">
     <PropertyGroup>
       <_ConfigurationProperties>/p:__BuildOS=$(__BuildOS) /p:__BuildArch=$(__BuildArch) /p:__BuildType=$(__BuildType)</_ConfigurationProperties>
-      <DotnetRestoreCommand Condition="'$(__DistroRid)' == ''">"$(DotNetTool)" restore $(RestoreProj) $(PackageVersionArg) /p:SetTFMForRestore=true $(_ConfigurationProperties)</DotnetRestoreCommand>
-      <DotnetRestoreCommand Condition="'$(__DistroRid)' != ''">"$(DotNetTool)" restore -r $(__DistroRid) $(RestoreProj) $(PackageVersionArg) /p:SetTFMForRestore=true $(_ConfigurationProperties)</DotnetRestoreCommand>
+      <DotnetRestoreCommand Condition="'$(__DistroRid)' == ''">"$(DotNetTool)" restore $(RestoreProj) $(PackageVersionArg) /p:SetTFMForRestore=true $(_ConfigurationProperties) $(__SkipFXRestoreArg)</DotnetRestoreCommand>
+      <DotnetRestoreCommand Condition="'$(__DistroRid)' != ''">"$(DotNetTool)" restore -r $(__DistroRid) $(RestoreProj) $(PackageVersionArg) /p:SetTFMForRestore=true $(_ConfigurationProperties) $(__SkipFXRestoreArg)</DotnetRestoreCommand>
     </PropertyGroup>
     <Exec Command="$(DotnetRestoreCommand)"/>
   </Target>
index 8a24b67..7255e73 100644 (file)
     <OutputPath>$(BaseOutputPath)</OutputPath>
 
   </PropertyGroup>
+  
+  <PropertyGroup>
+    <DisableImplicitFrameworkReferences Condition="'$(__SkipFXRestore)' == 'true'">true</DisableImplicitFrameworkReferences>
+  </PropertyGroup>
 
+  <ItemGroup Condition="'$(__SkipFXRestore)' == 'true' AND '$(ReferenceSystemPrivateCoreLib)' != 'true'" >
+    <Reference Include="$(__LocalCoreFXPath)\artifacts\bin\ref\netcoreapp\*.dll" Private="false" />
+  </ItemGroup>
+  
 </Project>
index 8594d1c..30713e9 100644 (file)
@@ -70,6 +70,7 @@ if /i "%1" == "jitminopts"                              (set COMPlus_JITMinOpts=
 if /i "%1" == "jitforcerelocs"                          (set COMPlus_ForceRelocs=1&shift&goto Arg_Loop)
 if /i "%1" == "jitdisasm"                               (set __JitDisasm=1&shift&goto Arg_Loop)
 if /i "%1" == "ilasmroundtrip"                          (set __IlasmRoundTrip=1&shift&goto Arg_Loop)
+
 if /i "%1" == "buildxunitwrappers"                      (set __BuildXunitWrappers=1&shift&goto Arg_Loop)
 if /i "%1" == "printlastresultsonly"                    (set __PrintLastResultsOnly=1&shift&goto Arg_Loop)
 if /i "%1" == "runcrossgentests"                        (set RunCrossGen=true&shift&goto Arg_Loop)
diff --git a/src/coreclr/tests/scripts/patch-corefx.py b/src/coreclr/tests/scripts/patch-corefx.py
new file mode 100644 (file)
index 0000000..bbc44c1
--- /dev/null
@@ -0,0 +1,263 @@
+#!/usr/bin/env python3
+#
+# Licensed to the .NET Foundation under one or more agreements.
+# The .NET Foundation licenses this file to you under the MIT license.
+# See the LICENSE file in the project root for more information.
+#
+##########################################################################
+##########################################################################
+#
+# Module: patch-corefx.py
+#
+# Notes:
+#
+# Script to overwrite the nuget downloaded corefx libraries with ones 
+# built from a local enlistment.
+#
+##########################################################################
+##########################################################################
+
+import argparse
+import distutils.dir_util
+import os
+import re
+import shutil
+import subprocess
+import sys
+
+##########################################################################
+# Globals
+##########################################################################
+
+testing = False
+
+# This should be factored out of build.sh
+Unix_name_map = {
+    'Linux': 'Linux',
+    'Darwin': 'OSX',
+    'FreeBSD': 'FreeBSD',
+    'OpenBSD': 'OpenBSD',
+    'NetBSD': 'NetBSD',
+    'SunOS': 'SunOS'
+}
+
+Is_windows = (os.name == 'nt')
+
+##########################################################################
+# Delete protocol
+##########################################################################
+
+def del_rw(action, name, exc):
+    os.chmod(name, 0o651)
+    os.remove(name)
+
+##########################################################################
+# Argument Parser
+##########################################################################
+
+description = 'Tool to patch CoreFx tests on the CoreCLR repo'
+
+parser = argparse.ArgumentParser(description=description)
+
+parser.add_argument('-arch', dest='arch', default='x64')
+parser.add_argument('-build_type', dest='build_type', default='Debug')
+parser.add_argument('-clr_core_root', dest='clr_core_root', default=None)
+parser.add_argument('-fx_root', dest='fx_root', default=None)
+
+
+##########################################################################
+# Helper Functions
+##########################################################################
+
+def validate_args(args):
+    """ Validate all of the arguments parsed.
+    Args:
+        args (argparser.ArgumentParser): Args parsed by the argument parser.
+    Returns:
+        (arch, build_type, clr_core_root, fx_root,)
+            (str, str, str, str)
+    Notes:
+    If the arguments are valid then return them all in a tuple. If not, raise
+    an exception stating x argument is incorrect.
+    """
+
+    arch = args.arch
+    build_type = args.build_type
+    clr_core_root = args.clr_core_root
+    fx_root = args.fx_root
+
+    def validate_arg(arg, check):
+        """ Validate an individual arg
+        Args:
+           arg (str|bool): argument to be validated
+           check (lambda: x-> bool): test that returns either True or False
+                                   : based on whether the check passes.
+
+        Returns:
+           is_valid (bool): Is the argument valid?
+        """
+
+        helper = lambda item: item is not None and check(item)
+
+        if not helper(arg):
+            raise Exception('Argument: %s is not valid.' % (arg))
+
+    valid_archs = ['x86', 'x64', 'arm', 'arm64']
+    valid_build_types = ['Debug', 'Checked', 'Release']
+
+    arch = next((a for a in valid_archs if a.lower() == arch.lower()), arch)
+    build_type = next((b for b in valid_build_types if b.lower() == build_type.lower()), build_type)
+
+    validate_arg(arch, lambda item: item in valid_archs)
+    validate_arg(build_type, lambda item: item in valid_build_types)
+
+    if clr_core_root is None:
+        raise Exception('No clr_core_root argument provided')
+    else:
+        clr_core_root = os.path.normpath(clr_core_root)
+        validate_arg(clr_core_root, lambda item: os.path.isdir(clr_core_root))
+
+    if fx_root is None:
+        raise Exception('No fx_root argument provided')
+    else:
+        fx_root = os.path.normpath(fx_root)
+
+    args = (arch, build_type, clr_core_root, fx_root)
+
+    log('Configuration:')
+    log(' arch: %s' % arch)
+    log(' build_type: %s' % build_type)
+    log(' clr_core_root: %s' % clr_core_root)
+    log(' fx_root: %s' % fx_root)
+
+    return args
+
+def log(message):
+    """ Print logging information
+    Args:
+        message (str): message to be printed
+    """
+
+    print('[%s]: %s' % (sys.argv[0], message))
+
+def test_log(message):
+    """ Print logging information only if testing mode is enabled
+    Args:
+        message (str): message to be printed
+    """
+    if testing:
+        print('[%s]: %s' % (sys.argv[0], message))
+
+def copy_files(source_dir, target_dir):
+    """ Copy any files in the source_dir to the target_dir.
+        The copy is not recursive.
+        The directories must already exist.
+    Args:
+        source_dir (str): source directory path
+        target_dir (str): target directory path
+    Returns:
+        Nothing
+    """
+
+    global testing
+    assert os.path.isdir(source_dir)
+    assert testing or os.path.isdir(target_dir)
+
+    for source_filename in os.listdir(source_dir):
+        source_pathname = os.path.join(source_dir, source_filename)
+        if os.path.isfile(source_pathname):
+            target_pathname = os.path.join(target_dir, source_filename)
+            log('Copy: %s => %s' % (source_pathname, target_pathname))
+            if not testing:
+                shutil.copy2(source_pathname, target_pathname)
+
+def patch_coreclr_root(core_root, fx_bin):
+    """ Walk through the fx bin and patch corefx dlls to the core root.
+    Args:
+        core_root       (str):       the core root path
+        fx_bin          (str):       the runtime folder from a corefx build
+    Returns:
+        nothing
+    """
+    test_log('Patching coreclr core_root')
+
+    forbidden_names = ['coreclr.dll', 
+                       'system.private.corelib.dll',
+                       'r2rdump.dll',
+                       'runincontext.dll',
+                       'mscordaccore.dll',
+                       'linuxnonjit.dll',
+                       'protononjit.dll',
+                       'mscordbi.dll',
+                       'clrjit.dll',
+                       'dbgshim.dll',
+                       'coreshim.dll',
+                       'clrgc.dll',
+                       'superpmi-shim-counter.dll',
+                       'clretwrc.dll',
+                       'superpmi-shim-collector.dll',
+                       'superpmi-shim-simple.dll',
+                       'jitinterface.dll',
+                       'mscorrc.debug.dll',
+                       'mscorrc.dll',
+                       'sos.dll']
+
+    test_log('forbidden_names = %s' % forbidden_names)
+
+    for file in os.listdir(fx_bin):
+        test_log('considering file %s' % file)
+
+        filename = os.path.basename(file)
+        comparename = filename.lower()
+        if (    comparename.endswith('.dll') and  
+                comparename not in forbidden_names and
+                not comparename.startswith('api-ms-core')   ):
+            source_pathname = os.path.join(fx_bin, filename)
+            target_pathname = os.path.join(core_root, filename)
+
+            test_log ('copying file %s to file %s' % (source_pathname, target_pathname))
+
+            shutil.copy2(source_pathname, target_pathname)
+
+##########################################################################
+# Main
+##########################################################################
+
+def main(args):
+    """
+        The way this script decides what to patch is by looking at the core
+        root for a list of dlls, then filtering out any ones built by coreclr.
+        This leaves us with a list of non-coreclr build dlls. Now we can use
+        that list to go through the corefx repo and any ones that also exist
+        in the corefx bin folder are copied over.
+    """
+
+    log('Patching CoreFX binaries from local enlistment.')
+
+    arch, build_type, clr_core_root, fx_root = validate_args(args)
+
+    clr_os = 'Windows_NT' if Is_windows else Unix_name_map[os.uname()[0]]
+
+    if not os.path.exists(clr_core_root):
+        raise Exception('Core root path %s does not exist.' % (core_root))
+
+    fx_bin = os.path.join(fx_root,
+                          'artifacts',
+                          'bin',
+                          'runtime',
+                          'netcoreapp-%s-%s-%s' % (clr_os, build_type, arch))
+
+    if not os.path.exists(fx_bin):
+        raise Exception('CoreFX bin path %s does not exist.' % (core_root))
+
+    patch_coreclr_root(clr_core_root, fx_bin)
+
+
+##########################################################################
+# setup for Main
+##########################################################################
+
+if __name__ == '__main__':
+    Args = parser.parse_args(sys.argv[1:])
+
+    main(Args)
index 795e033..af83abd 100644 (file)
@@ -8,16 +8,17 @@
     <DisablePackageAssetsCache>true</DisablePackageAssetsCache>
     <RuntimeIdentifiers>win-arm;win-arm64;win-x64;win-x86;$(TargetRid)</RuntimeIdentifiers>
   </PropertyGroup>
-  <ItemGroup>
-    <PackageReference Include="Microsoft.Private.CoreFx.NETCoreApp" Version="$(MicrosoftPrivateCoreFxNETCoreAppVersion)" />
+  <ItemGroup Condition="'$(__SkipFXRestore)' != 'true'" >
+    <PackageReference Include="Microsoft.Private.CoreFx.NETCoreApp" Version="$(MicrosoftPrivateCoreFxNETCoreAppVersion)"/>
     <PackageReference Include="System.Runtime.CompilerServices.Unsafe" Version="$(MicrosoftPrivateCoreFxNETCoreAppVersion)" />
     <PackageReference Include="System.Security.Permissions" Version="$(MicrosoftPrivateCoreFxNETCoreAppVersion)" />
     <PackageReference Include="System.Diagnostics.EventLog" Version="$(MicrosoftPrivateCoreFxNETCoreAppVersion)" />
     <PackageReference Include="System.Drawing.Common" Version="$(MicrosoftPrivateCoreFxNETCoreAppVersion)" />
     <PackageReference Include="System.Runtime.Intrinsics.Experimental" Version="$(MicrosoftPrivateCoreFxNETCoreAppVersion)" />
+  </ItemGroup>
+  <ItemGroup>
     <PackageReference Include="Microsoft.Diagnostics.Tools.RuntimeClient" Version="$(MicrosoftDiagnosticsToolsRuntimeClientVersion)" />
   </ItemGroup>
-
   <Target Name="Build" DependsOnTargets="$(TraversalBuildDependsOn)" />
 
   <PropertyGroup>
index c7b3483..0736ac7 100644 (file)
@@ -94,7 +94,7 @@ $(_XunitEpilog)
 
   <PropertyGroup>
     <OutputPath>$(XUnitTestBinBase)\$(CategoryWithSlash)</OutputPath>
-    <RuntimeFrameworkVersion>$(MicrosoftNETCoreAppVersion)</RuntimeFrameworkVersion>
+    <RuntimeFrameworkVersion Condition="'$(__SkipFXRestore)' != 'true'">$(MicrosoftNETCoreAppVersion)</RuntimeFrameworkVersion>
  </PropertyGroup>
 
   <Import Sdk="Microsoft.NET.Sdk" Project="Sdk.props" />