diff --git a/.gitignore b/.gitignore index cf7bd7d87b22..441cf052871c 100644 --- a/.gitignore +++ b/.gitignore @@ -239,3 +239,6 @@ src/DataFactory/DataFactoryV2.Test/SessionRecords/Microsoft.Azure.Commands.DataF # GitHub codespaces .venv + +# Visual studio live unit testing config +*.lutconfig diff --git a/build.proj b/build.proj index 00959af144a1..189960e55d9a 100644 --- a/build.proj +++ b/build.proj @@ -205,7 +205,7 @@ Microsoft.Powershell.*.dll,System*.dll,Microsoft.VisualBasic.dll,Microsoft.CSharp.dll,Microsoft.CodeAnalysis.dll,Microsoft.CodeAnalysis.CSharp.dll System.Security.Cryptography.ProtectedData.dll,System.Configuration.ConfigurationManager.dll,System.Runtime.CompilerServices.Unsafe.dll,System.IO.FileSystem.AccessControl.dll,System.Buffers.dll,System.Text.Encodings.Web.dll,System.CodeDom.dll,System.Management.dll,System.Text.Json.dll,System.Threading.Tasks.Extensions.dll,System.IO.Hashing.dll - + diff --git a/src/Accounts/Accounts.sln b/src/Accounts/Accounts.sln index 08af4b6e000f..1a4e2f29b2fa 100644 --- a/src/Accounts/Accounts.sln +++ b/src/Accounts/Accounts.sln @@ -1,6 +1,6 @@ Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 16 -VisualStudioVersion = 16.0.30503.244 +# Visual Studio Version 17 +VisualStudioVersion = 17.3.32929.385 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Accounts", "Accounts\Accounts.csproj", "{142D7B0B-388A-4CEB-A228-7F6D423C5C2E}" EndProject @@ -22,6 +22,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Authentication.Test", "Auth EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AuthenticationAssemblyLoadContext", "AuthenticationAssemblyLoadContext\AuthenticationAssemblyLoadContext.csproj", "{D06EF9FC-4C3E-4A51-A4AD-D39AD426EF8C}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AssemblyLoading", "AssemblyLoading\AssemblyLoading.csproj", "{74C0BD7E-DFFD-4B96-818E-D8660FA43159}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AssemblyLoading.Test", "AssemblyLoading.Test\AssemblyLoading.Test.csproj", "{D4540550-9808-4DEB-9D5E-F88E38D58A85}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -64,6 +68,14 @@ Global {D06EF9FC-4C3E-4A51-A4AD-D39AD426EF8C}.Debug|Any CPU.Build.0 = Debug|Any CPU {D06EF9FC-4C3E-4A51-A4AD-D39AD426EF8C}.Release|Any CPU.ActiveCfg = Release|Any CPU {D06EF9FC-4C3E-4A51-A4AD-D39AD426EF8C}.Release|Any CPU.Build.0 = Release|Any CPU + {74C0BD7E-DFFD-4B96-818E-D8660FA43159}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {74C0BD7E-DFFD-4B96-818E-D8660FA43159}.Debug|Any CPU.Build.0 = Debug|Any CPU + {74C0BD7E-DFFD-4B96-818E-D8660FA43159}.Release|Any CPU.ActiveCfg = Release|Any CPU + {74C0BD7E-DFFD-4B96-818E-D8660FA43159}.Release|Any CPU.Build.0 = Release|Any CPU + {D4540550-9808-4DEB-9D5E-F88E38D58A85}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D4540550-9808-4DEB-9D5E-F88E38D58A85}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D4540550-9808-4DEB-9D5E-F88E38D58A85}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D4540550-9808-4DEB-9D5E-F88E38D58A85}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -71,6 +83,7 @@ Global GlobalSection(NestedProjects) = preSolution {152D78F0-A642-4D0E-B3A8-2FC64FFA9714} = {95C16AED-FD57-42A0-86C3-2CF4300A4817} {43BE9983-BD21-4474-92E5-1FFC0220B2AD} = {95C16AED-FD57-42A0-86C3-2CF4300A4817} + {D4540550-9808-4DEB-9D5E-F88E38D58A85} = {95C16AED-FD57-42A0-86C3-2CF4300A4817} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {AA51E4F8-AA75-429D-9626-12C7B4305D72} diff --git a/src/Accounts/Accounts/Accounts.csproj b/src/Accounts/Accounts/Accounts.csproj index db5804cb5603..e514c5754b5f 100644 --- a/src/Accounts/Accounts/Accounts.csproj +++ b/src/Accounts/Accounts/Accounts.csproj @@ -11,8 +11,10 @@ - - + + + + @@ -22,10 +24,12 @@ - - + + + + - @@ -43,19 +47,19 @@ - + - + - + ..\..\lib\FuzzySharp.dll - + True @@ -63,7 +67,7 @@ Resources.resx - + ResXFileCodeGenerator diff --git a/src/Accounts/Accounts/Az.Accounts.psd1 b/src/Accounts/Accounts/Az.Accounts.psd1 index ae636beebfc7..e6f132707d1e 100644 --- a/src/Accounts/Accounts/Az.Accounts.psd1 +++ b/src/Accounts/Accounts/Az.Accounts.psd1 @@ -56,7 +56,8 @@ DotNetFrameworkVersion = '4.7.2' # RequiredModules = @() # Assemblies that must be loaded prior to importing this module -RequiredAssemblies = 'Microsoft.Azure.PowerShell.Authentication.Abstractions.dll', +RequiredAssemblies = 'Microsoft.Azure.PowerShell.AssemblyLoading.dll', + 'Microsoft.Azure.PowerShell.Authentication.Abstractions.dll', 'Microsoft.Azure.PowerShell.Authentication.dll', 'Microsoft.Azure.PowerShell.Authenticators.dll', 'Microsoft.Azure.PowerShell.Authentication.ResourceManager.dll', diff --git a/src/Accounts/Accounts/ChangeLog.md b/src/Accounts/Accounts/ChangeLog.md index e55384f8c14c..ffc42d25ef14 100644 --- a/src/Accounts/Accounts/ChangeLog.md +++ b/src/Accounts/Accounts/ChangeLog.md @@ -19,6 +19,7 @@ --> ## Upcoming Release +* Optimized the mechanism for assembly loading. * Enabled AzKeyStore with keyring in Linux. ## Version 2.10.4 diff --git a/src/Accounts/Accounts/StartupScripts/InitializeAssemblyResolver.ps1 b/src/Accounts/Accounts/StartupScripts/InitializeAssemblyResolver.ps1 index 3b1b77952550..d146fd3ae8d7 100644 --- a/src/Accounts/Accounts/StartupScripts/InitializeAssemblyResolver.ps1 +++ b/src/Accounts/Accounts/StartupScripts/InitializeAssemblyResolver.ps1 @@ -1,4 +1,8 @@ -if ($PSEdition -eq 'Desktop') { +$assemblyRootPath = [System.IO.Path]::Combine($PSScriptRoot, "..", "lib") +$conditionalAssemblyContext = [Microsoft.Azure.PowerShell.AssemblyLoading.ConditionalAssemblyContext]::new($Host.Version) +[Microsoft.Azure.PowerShell.AssemblyLoading.ConditionalAssemblyProvider]::Initialize($assemblyRootPath, $conditionalAssemblyContext) + +if ($PSEdition -eq 'Desktop') { try { [Microsoft.Azure.Commands.Profile.Utilities.CustomAssemblyResolver]::Initialize() } @@ -9,9 +13,8 @@ else { try { Add-Type -Path ([System.IO.Path]::Combine($PSScriptRoot, "..", "Microsoft.Azure.PowerShell.AuthenticationAssemblyLoadContext.dll")) | Out-Null - $assemblyLoadContextFolder = [System.IO.Path]::Combine($PSScriptRoot, "..", "AzSharedAlcAssemblies") - Write-Debug "Registering Az shared AssemblyLoadContext for path: '$assemblyLoadContextFolder'." - [Microsoft.Azure.PowerShell.AuthenticationAssemblyLoadContext.AzAssemblyLoadContextInitializer]::RegisterAzSharedAssemblyLoadContext($assemblyLoadContextFolder) + Write-Debug "Registering Az shared AssemblyLoadContext." + [Microsoft.Azure.PowerShell.AuthenticationAssemblyLoadContext.AzAssemblyLoadContextInitializer]::RegisterAzSharedAssemblyLoadContext() Write-Debug "AssemblyLoadContext registered." } catch { diff --git a/src/Accounts/AssemblyLoading.Test/AssemblyLoading.Test.csproj b/src/Accounts/AssemblyLoading.Test/AssemblyLoading.Test.csproj new file mode 100644 index 000000000000..2c6e5cfe652c --- /dev/null +++ b/src/Accounts/AssemblyLoading.Test/AssemblyLoading.Test.csproj @@ -0,0 +1,17 @@ + + + Accounts + + + + + + Microsoft.Azure.PowerShell.AssemblyLoading.Test + Microsoft.Azure.PowerShell.AssemblyLoading.Test + + + + + + + diff --git a/src/Accounts/AssemblyLoading.Test/Mocks/MockConditionalAssembly.cs b/src/Accounts/AssemblyLoading.Test/Mocks/MockConditionalAssembly.cs new file mode 100644 index 000000000000..5e3d1718412b --- /dev/null +++ b/src/Accounts/AssemblyLoading.Test/Mocks/MockConditionalAssembly.cs @@ -0,0 +1,40 @@ +// ---------------------------------------------------------------------------------- +// +// Copyright Microsoft Corporation +// 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; + +namespace Microsoft.Azure.PowerShell.AssemblyLoading.Test.Mocks +{ + internal class MockConditionalAssembly : IConditionalAssembly + { + public MockConditionalAssembly(IConditionalAssemblyContext context) + { + Context = context; + } + public bool ShouldLoad { get; set; } = true; + + public string Name { get; set; } + + public string Path { get; set; } + + public Version Version { get; set; } + + public IConditionalAssemblyContext Context { get; set; } + + public void UpdateShouldLoad(bool shouldLoad) + { + ShouldLoad = ShouldLoad && shouldLoad; + } + } +} diff --git a/src/Accounts/AssemblyLoading.Test/Mocks/MockConditionalAssemblyContext.cs b/src/Accounts/AssemblyLoading.Test/Mocks/MockConditionalAssemblyContext.cs new file mode 100644 index 000000000000..21fd73f86ad8 --- /dev/null +++ b/src/Accounts/AssemblyLoading.Test/Mocks/MockConditionalAssemblyContext.cs @@ -0,0 +1,31 @@ +// ---------------------------------------------------------------------------------- +// +// Copyright Microsoft Corporation +// 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; +using System.Runtime.InteropServices; + +namespace Microsoft.Azure.PowerShell.AssemblyLoading.Test.Mocks +{ + internal class MockConditionalAssemblyContext : IConditionalAssemblyContext + { + public Version PSVersion { get; set; } + public Architecture OSArchitecture { get; set; } + public OSPlatform OS { get; set; } + + public bool IsOSPlatform(OSPlatform os) + { + return OS.Equals(os); + } + } +} diff --git a/src/Accounts/AssemblyLoading.Test/UnitTests/ConditionalAssemblyExtensionsTests.cs b/src/Accounts/AssemblyLoading.Test/UnitTests/ConditionalAssemblyExtensionsTests.cs new file mode 100644 index 000000000000..0476b8ccbaee --- /dev/null +++ b/src/Accounts/AssemblyLoading.Test/UnitTests/ConditionalAssemblyExtensionsTests.cs @@ -0,0 +1,82 @@ +// ---------------------------------------------------------------------------------- +// +// Copyright Microsoft Corporation +// 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.Azure.PowerShell.AssemblyLoading.Test.Mocks; +using Microsoft.WindowsAzure.Commands.ScenarioTest; +using System; +using System.Runtime.InteropServices; +using Xunit; + +namespace Microsoft.Azure.PowerShell.AssemblyLoading.Test.UnitTests +{ + public class ConditionalAssemblyExtensionsTests + { + [Fact] + [Trait(Category.AcceptanceType, Category.CheckIn)] + public void CanWorkWithPSVersion() + { + var windowsPSContext = new MockConditionalAssemblyContext() + { + PSVersion = Version.Parse("5.1.22621.608") + }; + var windowsPSAssembly = new MockConditionalAssembly(windowsPSContext) + .WithWindowsPowerShell(); + var psCoreAssembly = new MockConditionalAssembly( + windowsPSContext) + .WithPowerShellCore(); + Assert.True(windowsPSAssembly.ShouldLoad); + Assert.False(psCoreAssembly.ShouldLoad); + + var ps7Context = new MockConditionalAssemblyContext() + { + PSVersion = Version.Parse("7.3.0") + }; + windowsPSAssembly = new MockConditionalAssembly( + ps7Context) + .WithWindowsPowerShell(); + psCoreAssembly = new MockConditionalAssembly( + ps7Context) + .WithPowerShellCore(); + Assert.True(psCoreAssembly.ShouldLoad); + Assert.False(windowsPSAssembly.ShouldLoad); + } + + [Fact] + [Trait(Category.AcceptanceType, Category.CheckIn)] + public void CanWorkWithOS() + { + var windowsContext = new MockConditionalAssemblyContext() + { + OS = OSPlatform.Windows + }; + var windowsAssembly = new MockConditionalAssembly(windowsContext) + .WithWindows(); + var linuxAssembly = new MockConditionalAssembly(windowsContext) + .WithLinux(); + Assert.True(windowsAssembly.ShouldLoad); + Assert.False(linuxAssembly.ShouldLoad); + + var linuxContext = new MockConditionalAssemblyContext() + { + OS = OSPlatform.Linux + }; + windowsAssembly = new MockConditionalAssembly(linuxContext) + .WithWindows(); + linuxAssembly = new MockConditionalAssembly(linuxContext) + .WithLinux(); + Assert.False(windowsAssembly.ShouldLoad); + Assert.True(linuxAssembly.ShouldLoad); + } + } +} diff --git a/src/Accounts/AssemblyLoading.Test/UnitTests/ConditionalAssemblyProviderTests.cs b/src/Accounts/AssemblyLoading.Test/UnitTests/ConditionalAssemblyProviderTests.cs new file mode 100644 index 000000000000..35db84a69923 --- /dev/null +++ b/src/Accounts/AssemblyLoading.Test/UnitTests/ConditionalAssemblyProviderTests.cs @@ -0,0 +1,80 @@ +// ---------------------------------------------------------------------------------- +// +// Copyright Microsoft Corporation +// 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.Azure.PowerShell.AssemblyLoading.Test.Mocks; +using Microsoft.WindowsAzure.Commands.ScenarioTest; +using System; +using System.IO; +using System.Runtime.InteropServices; +using Xunit; + +namespace Microsoft.Azure.PowerShell.AssemblyLoading.Test.UnitTests +{ + public class ConditionalAssemblyProviderTests + { + private const string NetFx = "netfx"; + private const string NetStandard20 = "netstandard2.0"; + private const string NetCoreApp21 = "netcoreapp2.1"; + private const string RootPath = "root"; + + [Fact] + [Trait(Category.AcceptanceType, Category.CheckIn)] + public void CanGetAssembliesOnWindowsPowerShell() + { + var context = new MockConditionalAssemblyContext() + { + OS = OSPlatform.Windows, + PSVersion = Version.Parse("5.1.22621.608"), + OSArchitecture = Architecture.X64 + }; + ConditionalAssemblyProvider.Initialize(RootPath, context); + var assemblies = ConditionalAssemblyProvider.GetAssemblies(); + + Assert.True(assemblies.TryGetValue("Azure.Core", out var azureCore)); + Assert.Equal(GetExpectedAssemblyPath(NetFx, "Azure.Core"), azureCore.Path); + Assert.True(assemblies.TryGetValue("Newtonsoft.Json", out var newtonsoftJson)); + Assert.Equal(GetExpectedAssemblyPath(NetFx, "Newtonsoft.Json"), newtonsoftJson.Path); + + Assert.True(assemblies.TryGetValue("Azure.Identity", out var azureIdentity)); + Assert.Equal(GetExpectedAssemblyPath(NetStandard20, "Azure.Identity"), azureIdentity.Path); + } + + [Fact] + [Trait(Category.AcceptanceType, Category.CheckIn)] + public void CanGetAssembliesOnPowerShellCorePlus() + { + var context = new MockConditionalAssemblyContext() + { + OS = OSPlatform.Windows, + PSVersion = Version.Parse("7.3.0"), + OSArchitecture = Architecture.X64 + }; + ConditionalAssemblyProvider.Initialize(RootPath, context); + var assemblies = ConditionalAssemblyProvider.GetAssemblies(); + + Assert.True(assemblies.TryGetValue("Azure.Core", out var azureCore)); + Assert.Equal(GetExpectedAssemblyPath(NetCoreApp21, "Azure.Core"), azureCore.Path); + + Assert.False(assemblies.TryGetValue("Newtonsoft.Json", out _)); + + Assert.True(assemblies.TryGetValue("Azure.Identity", out var azureIdentity)); + Assert.Equal(GetExpectedAssemblyPath(NetStandard20, "Azure.Identity"), azureIdentity.Path); + } + + private string GetExpectedAssemblyPath(string framework, string assemblyName) + { + return Path.Combine(RootPath, framework, $"{assemblyName}.dll"); + } + } +} diff --git a/src/Accounts/AssemblyLoading.Test/UnitTests/ConditionalAssemblyTests.cs b/src/Accounts/AssemblyLoading.Test/UnitTests/ConditionalAssemblyTests.cs new file mode 100644 index 000000000000..f5f97643f265 --- /dev/null +++ b/src/Accounts/AssemblyLoading.Test/UnitTests/ConditionalAssemblyTests.cs @@ -0,0 +1,172 @@ +// ---------------------------------------------------------------------------------- +// +// Copyright Microsoft Corporation +// 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.Azure.PowerShell.AssemblyLoading.Test.Mocks; +using Microsoft.WindowsAzure.Commands.ScenarioTest; +using System; +using System.Runtime.InteropServices; +using Xunit; + +namespace Microsoft.Azure.PowerShell.AssemblyLoading.Test.UnitTests +{ + public class ConditionalAssemblyTests + { + [Fact] + [Trait(Category.AcceptanceType, Category.CheckIn)] + public void ShouldLoadAssemblyWithoutConstraint() + { + var context = new MockConditionalAssemblyContext(); + var assembly = NewDummyAssembly(context); + Assert.True(assembly.ShouldLoad); + } + + [Fact] + [Trait(Category.AcceptanceType, Category.CheckIn)] + public void ShouldLoadAssemblyAccordingToPSVersion() + { + // windows powershell + var context = new MockConditionalAssemblyContext() + { PSVersion = Version.Parse("5.1.22621.608") }; + var windowsPSAssembly = NewDummyAssembly(context).WithWindowsPowerShell(); + var psCoreAssembly = NewDummyAssembly(context).WithPowerShellCore(); + var neturalAssembly = NewDummyAssembly(context); + Assert.True(windowsPSAssembly.ShouldLoad); + Assert.False(psCoreAssembly.ShouldLoad); + Assert.True(neturalAssembly.ShouldLoad); + + // powershell core and 7+ + context.PSVersion = Version.Parse("7.3.0"); + windowsPSAssembly = NewDummyAssembly(context).WithWindowsPowerShell(); + psCoreAssembly = NewDummyAssembly(context).WithPowerShellCore(); + neturalAssembly = NewDummyAssembly(context); + Assert.False(windowsPSAssembly.ShouldLoad); + Assert.True(psCoreAssembly.ShouldLoad); + Assert.True(neturalAssembly.ShouldLoad); + } + + [Fact] + [Trait(Category.AcceptanceType, Category.CheckIn)] + public void ShouldLoadAssemblyAccordingToOS() + { + // windows + var context = new MockConditionalAssemblyContext() + { OS = OSPlatform.Windows }; + var windowsAssembly = NewDummyAssembly(context).WithWindows(); + var linuxAssembly = NewDummyAssembly(context).WithLinux(); + var macOSAssembly = NewDummyAssembly(context).WithMacOS(); + var neturalAssembly = NewDummyAssembly(context); + Assert.True(windowsAssembly.ShouldLoad); + Assert.False(linuxAssembly.ShouldLoad); + Assert.False(macOSAssembly.ShouldLoad); + Assert.True(neturalAssembly.ShouldLoad); + + // linux + context = new MockConditionalAssemblyContext() + { OS = OSPlatform.Linux }; + windowsAssembly = NewDummyAssembly(context).WithWindows(); + linuxAssembly = NewDummyAssembly(context).WithLinux(); + macOSAssembly = NewDummyAssembly(context).WithMacOS(); + neturalAssembly = NewDummyAssembly(context); + Assert.False(windowsAssembly.ShouldLoad); + Assert.True(linuxAssembly.ShouldLoad); + Assert.False(macOSAssembly.ShouldLoad); + Assert.True(neturalAssembly.ShouldLoad); + + // macos + context = new MockConditionalAssemblyContext() + { OS = OSPlatform.OSX }; + windowsAssembly = NewDummyAssembly(context).WithWindows(); + linuxAssembly = NewDummyAssembly(context).WithLinux(); + macOSAssembly = NewDummyAssembly(context).WithMacOS(); + neturalAssembly = NewDummyAssembly(context); + Assert.False(windowsAssembly.ShouldLoad); + Assert.False(linuxAssembly.ShouldLoad); + Assert.True(macOSAssembly.ShouldLoad); + Assert.True(neturalAssembly.ShouldLoad); + } + + [Fact] + [Trait(Category.AcceptanceType, Category.CheckIn)] + public void ShouldLoadAssemblyAccordingToCpuArch() + { + // x86 + var context = new MockConditionalAssemblyContext() + { OSArchitecture = Architecture.X86 }; + var x86Assembly = NewDummyAssembly(context).WithX86(); + var x64Assembly = NewDummyAssembly(context).WithX64(); + var arm64Assembly = NewDummyAssembly(context).WithArm64(); + var neturalAssembly = NewDummyAssembly(context); + Assert.True(x86Assembly.ShouldLoad); + Assert.False(x64Assembly.ShouldLoad); + Assert.False(arm64Assembly.ShouldLoad); + Assert.True(neturalAssembly.ShouldLoad); + + // x64 + context = new MockConditionalAssemblyContext() + { OSArchitecture = Architecture.X64 }; + x86Assembly = NewDummyAssembly(context).WithX86(); + x64Assembly = NewDummyAssembly(context).WithX64(); + arm64Assembly = NewDummyAssembly(context).WithArm64(); + neturalAssembly = NewDummyAssembly(context); + Assert.False(x86Assembly.ShouldLoad); + Assert.True(x64Assembly.ShouldLoad); + Assert.False(arm64Assembly.ShouldLoad); + Assert.True(neturalAssembly.ShouldLoad); + + // arm64 + context = new MockConditionalAssemblyContext() + { OSArchitecture = Architecture.Arm64 }; + x86Assembly = NewDummyAssembly(context).WithX86(); + x64Assembly = NewDummyAssembly(context).WithX64(); + arm64Assembly = NewDummyAssembly(context).WithArm64(); + neturalAssembly = NewDummyAssembly(context); + Assert.False(x86Assembly.ShouldLoad); + Assert.False(x64Assembly.ShouldLoad); + Assert.True(arm64Assembly.ShouldLoad); + Assert.True(neturalAssembly.ShouldLoad); + } + + [Fact] + [Trait(Category.AcceptanceType, Category.CheckIn)] + public void ShouldSupportMultipleConstraints() + { + // assembly requires powershell 7+ on linux + // if both meet, it should be loaded + var context = new MockConditionalAssemblyContext(); + context.OS = OSPlatform.Linux; + context.PSVersion = Version.Parse("7.3.0"); + var assembly = NewDummyAssembly(context).WithLinux().WithPowerShellVersion(Version.Parse("7.0.0")); + Assert.True(assembly.ShouldLoad); + + // otherwise it shouldn't + context.OS = OSPlatform.Windows; + context.PSVersion = Version.Parse("7.3.0"); + assembly = NewDummyAssembly(context).WithLinux().WithPowerShellVersion(Version.Parse("7.0.0")); + Assert.False(assembly.ShouldLoad); + + context.OS = OSPlatform.Linux; + context.PSVersion = Version.Parse("6.2.0"); + Assert.False(assembly.ShouldLoad); + + context.OS = OSPlatform.Windows; + context.PSVersion = Version.Parse("5.1.0"); + Assert.False(assembly.ShouldLoad); + } + + private static ConditionalAssembly NewDummyAssembly(MockConditionalAssemblyContext context) + { + return new ConditionalAssembly(context, "name", "path", new Version(1, 0, 0)); + } + } +} diff --git a/src/Accounts/AssemblyLoading/AssemblyLoading.csproj b/src/Accounts/AssemblyLoading/AssemblyLoading.csproj new file mode 100644 index 000000000000..b9c2e2d65331 --- /dev/null +++ b/src/Accounts/AssemblyLoading/AssemblyLoading.csproj @@ -0,0 +1,14 @@ + + + Accounts + + + + + + Microsoft.Azure.PowerShell.AssemblyLoading + Microsoft.Azure.PowerShell.AssemblyLoading + + + + diff --git a/src/Accounts/AssemblyLoading/ConditionalAssembly.cs b/src/Accounts/AssemblyLoading/ConditionalAssembly.cs new file mode 100644 index 000000000000..78a00f9e50cc --- /dev/null +++ b/src/Accounts/AssemblyLoading/ConditionalAssembly.cs @@ -0,0 +1,52 @@ +// ---------------------------------------------------------------------------------- +// +// Copyright Microsoft Corporation +// 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; + +namespace Microsoft.Azure.PowerShell.AssemblyLoading +{ + /// + public class ConditionalAssembly : IConditionalAssembly + { + public ConditionalAssembly(IConditionalAssemblyContext context, string name, string path, Version version) + { + Context = context; + Name = name; + Path = path; + Version = version; + ShouldLoad = true; + } + + /// + public bool ShouldLoad { get; private set; } + + /// + public void UpdateShouldLoad(bool shouldLoad) + { + ShouldLoad = ShouldLoad && shouldLoad; + } + + /// + public IConditionalAssemblyContext Context { get; } + + /// + public Version Version { get; } + + /// + public string Name { get; } + + /// + public string Path { get; } + } +} diff --git a/src/Accounts/AssemblyLoading/ConditionalAssemblyContext.cs b/src/Accounts/AssemblyLoading/ConditionalAssemblyContext.cs new file mode 100644 index 000000000000..5896b59c33b3 --- /dev/null +++ b/src/Accounts/AssemblyLoading/ConditionalAssemblyContext.cs @@ -0,0 +1,37 @@ +// ---------------------------------------------------------------------------------- +// +// Copyright Microsoft Corporation +// 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; +using System.Runtime.InteropServices; + +namespace Microsoft.Azure.PowerShell.AssemblyLoading +{ + /// + public class ConditionalAssemblyContext : IConditionalAssemblyContext + { + public ConditionalAssemblyContext(Version psVersion) + { + PSVersion = psVersion; + } + + /// + public Version PSVersion { get; private set; } + + /// + public bool IsOSPlatform(OSPlatform os) => RuntimeInformation.IsOSPlatform(os); + + /// + public Architecture OSArchitecture => RuntimeInformation.OSArchitecture; + } +} diff --git a/src/Accounts/AssemblyLoading/ConditionalAssemblyExtensions.cs b/src/Accounts/AssemblyLoading/ConditionalAssemblyExtensions.cs new file mode 100644 index 000000000000..e4ba071f9ba6 --- /dev/null +++ b/src/Accounts/AssemblyLoading/ConditionalAssemblyExtensions.cs @@ -0,0 +1,107 @@ +// ---------------------------------------------------------------------------------- +// +// Copyright Microsoft Corporation +// 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; +using System.Runtime.InteropServices; + +namespace Microsoft.Azure.PowerShell.AssemblyLoading +{ + /// + /// Contains a set of definitions of constraints of conditional assemblies here, using builder pattern. + /// + public static class ConditionalAssemblyExtensions + { + /// + /// The given assembly should be loaded in Windows PowerShell. + /// + public static IConditionalAssembly WithWindowsPowerShell(this IConditionalAssembly assembly) + { + return assembly.WithPowerShellVersion(new Version("5.0.0"), new Version("6.0.0")); + } + + /// + /// The given assembly should be loaded in PowerShell Core (6+). + /// + public static IConditionalAssembly WithPowerShellCore(this IConditionalAssembly assembly) + { + return assembly.WithPowerShellVersion(new Version("6.0.0")); + } + + /// + /// The given assembly should be loaded when the version of PowerShell lies in + /// [, ). + /// + /// + /// Lower limit of PowerShell version, inclusive. + /// Upper limit of PowerShell version, exclusive. + public static IConditionalAssembly WithPowerShellVersion(this IConditionalAssembly assembly, Version lower, Version upper = null) + { + bool shouldLoad = lower <= assembly.Context.PSVersion; + if (upper != null) + { + shouldLoad = shouldLoad && assembly.Context.PSVersion < upper; + } + assembly.UpdateShouldLoad(shouldLoad); + return assembly; + } + + /// + /// The given assembly should be loaded on Windows. + /// + public static IConditionalAssembly WithWindows(this IConditionalAssembly assembly) + => assembly.WithOS(OSPlatform.Windows); + + /// + /// The given assembly should be loaded on Mac OS. + /// + public static IConditionalAssembly WithMacOS(this IConditionalAssembly assembly) + => assembly.WithOS(OSPlatform.OSX); + + /// + /// The given assembly should be loaded on Linux. + /// + public static IConditionalAssembly WithLinux(this IConditionalAssembly assembly) + => assembly.WithOS(OSPlatform.Linux); + + private static IConditionalAssembly WithOS(this IConditionalAssembly assembly, OSPlatform os) + { + assembly.UpdateShouldLoad(assembly.Context.IsOSPlatform(os)); + return assembly; + } + + /// + /// The given assembly should be loaded on x86 platform. + /// + public static IConditionalAssembly WithX86(this IConditionalAssembly assembly) + => assembly.WithOSArchitecture(Architecture.X86); + + /// + /// The given assembly should be loaded on x64 platform. + /// + public static IConditionalAssembly WithX64(this IConditionalAssembly assembly) + => assembly.WithOSArchitecture(Architecture.X64); + + /// + /// The given assembly should be loaded on ARM64 platform. + /// + public static IConditionalAssembly WithArm64(this IConditionalAssembly assembly) + => assembly.WithOSArchitecture(Architecture.Arm64); + + private static IConditionalAssembly WithOSArchitecture(this IConditionalAssembly assembly, Architecture arch) + { + assembly.UpdateShouldLoad(assembly.Context.OSArchitecture.Equals(arch)); + return assembly; + } + } +} diff --git a/src/Accounts/AssemblyLoading/ConditionalAssemblyProvider.cs b/src/Accounts/AssemblyLoading/ConditionalAssemblyProvider.cs new file mode 100644 index 000000000000..23f580c58c3d --- /dev/null +++ b/src/Accounts/AssemblyLoading/ConditionalAssemblyProvider.cs @@ -0,0 +1,97 @@ +// ---------------------------------------------------------------------------------- +// +// Copyright Microsoft Corporation +// 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; +using System.Collections.Generic; +using System.IO; +using System.Linq; + +namespace Microsoft.Azure.PowerShell.AssemblyLoading +{ + /// + /// Provides a set of assemblies to load for different environments. + /// + public static class ConditionalAssemblyProvider + { + private static IConditionalAssemblyContext _context = null; + private static IEnumerable _assemblies = null; + private static string _rootPath = null; + + /// + /// Initializes the conditional assembly provider. + /// + /// Root path of the assemblies. + /// Context according to which an assembly is loaded or not. + public static void Initialize(string rootPath, IConditionalAssemblyContext context) + { + _rootPath = rootPath ?? throw new ArgumentNullException(nameof(rootPath)); + _context = context ?? throw new ArgumentNullException(nameof(context)); + _assemblies = new List() + { + // todo: add a tool to update assembly versions after replacing the assemblies. (Can it support newly introduced assemblies?) + // todo: consider moving the list to a standalone config file + #region AssemblyList + CreateAssembly("netcoreapp2.1", "Azure.Core", "1.25.0.0").WithPowerShellCore(), + CreateAssembly("netcoreapp2.1", "Microsoft.Identity.Client", "4.46.2.0").WithPowerShellCore(), + CreateAssembly("netcoreapp3.1", "Microsoft.Identity.Client.Extensions.Msal", "2.23.0.0").WithPowerShellCore(), + + CreateAssembly("netstandard2.0", "Azure.Identity", "1.6.1.0"), + CreateAssembly("netstandard2.0", "Microsoft.Bcl.AsyncInterfaces", "1.0.0.0"), + CreateAssembly("netstandard2.0", "Microsoft.IdentityModel.Abstractions", "6.22.1.0"), + CreateAssembly("netstandard2.0", "System.Memory.Data", "1.0.2.0"), + CreateAssembly("netstandard2.0", "System.Text.Json", "4.0.1.2"), + CreateAssembly("netstandard2.0", "System.Buffers", "4.0.3.0").WithWindowsPowerShell(), + CreateAssembly("netstandard2.0", "System.Memory", "4.0.1.1").WithWindowsPowerShell(), + CreateAssembly("netstandard2.0", "System.Net.Http.WinHttpHandler", "4.0.2.0").WithWindowsPowerShell(), + CreateAssembly("netstandard2.0", "System.Private.ServiceModel", "4.7.0.0").WithWindowsPowerShell(), + CreateAssembly("netstandard2.0", "System.Security.AccessControl", "4.1.1.0").WithWindowsPowerShell(), + CreateAssembly("netstandard2.0", "System.Security.Permissions", "4.0.1.0").WithWindowsPowerShell(), + CreateAssembly("netstandard2.0", "System.Security.Principal.Windows", "4.1.1.0").WithWindowsPowerShell(), + CreateAssembly("netstandard2.0", "System.ServiceModel.Primitives", "4.7.0.0").WithWindowsPowerShell(), + CreateAssembly("netstandard2.0", "System.Threading.Tasks.Extensions", "4.2.0.1").WithWindowsPowerShell(), + + CreateAssembly("netfx", "Azure.Core", "1.25.0.0").WithWindowsPowerShell(), + CreateAssembly("netfx", "Microsoft.Identity.Client", "4.46.2.0").WithWindowsPowerShell(), + CreateAssembly("netfx", "Microsoft.Identity.Client.Extensions.Msal", "2.23.0.0").WithWindowsPowerShell(), + CreateAssembly("netfx", "Newtonsoft.Json", "12.0.0.0").WithWindowsPowerShell(), + CreateAssembly("netfx", "System.Diagnostics.DiagnosticSource", "4.0.4.0").WithWindowsPowerShell(), + CreateAssembly("netfx", "System.Numerics.Vectors", "4.1.4.0").WithWindowsPowerShell(), + CreateAssembly("netfx", "System.Reflection.DispatchProxy", "4.0.4.0").WithWindowsPowerShell(), + CreateAssembly("netfx", "System.Runtime.CompilerServices.Unsafe", "4.0.6.0").WithWindowsPowerShell(), + CreateAssembly("netfx", "System.Security.Cryptography.Cng", "4.3.0.0").WithWindowsPowerShell(), + CreateAssembly("netfx", "System.Text.Encodings.Web", "4.0.5.1").WithWindowsPowerShell(), + CreateAssembly("netfx", "System.Xml.ReaderWriter", "4.1.0.0").WithWindowsPowerShell(), + #endregion + }; + } + + /// + /// Shorthand syntax to define a conditional assembly. + /// + private static ConditionalAssembly CreateAssembly(string framework, string name, string version) + { + string path = Path.Combine(_rootPath, framework, $"{name}.dll"); + return new ConditionalAssembly(_context, name, path, new Version(version)); + } + + /// + /// Returns a set of assemblies that should be loaded into the current environment. + /// + public static IDictionary GetAssemblies() + { + if (_context == null || _assemblies == null) throw new InvalidOperationException($"Call {nameof(Initialize)}() first."); + return _assemblies.Where(x => x.ShouldLoad).ToDictionary(x => x.Name, x => (x.Path, x.Version)); + } + } +} diff --git a/src/Accounts/AssemblyLoading/IConditionalAssembly.cs b/src/Accounts/AssemblyLoading/IConditionalAssembly.cs new file mode 100644 index 000000000000..6dd2ffd7133c --- /dev/null +++ b/src/Accounts/AssemblyLoading/IConditionalAssembly.cs @@ -0,0 +1,60 @@ +// ---------------------------------------------------------------------------------- +// +// Copyright Microsoft Corporation +// 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; + +namespace Microsoft.Azure.PowerShell.AssemblyLoading +{ + /// + /// Represents a dependency assembly of Az modules. + /// + public interface IConditionalAssembly + { + /// + /// Whether the assembly should be loaded given the constraints it comes with + /// and the current . + /// + bool ShouldLoad { get; } + + /// + /// Name of the assembly. Should be its file name without extension. + /// + string Name { get; } + + /// + /// Path to the assembly file. + /// + string Path { get; } + + /// + /// Assembly version. + /// + Version Version { get; } + + /// + /// Context with information about the current environment. + /// Used to calculate if this assembly should be loaded. + /// + IConditionalAssemblyContext Context { get; } + + /// + /// Update . + /// + /// + /// This method shortcuts, meaning if is false, it can never be updated. + /// + /// The new value of + void UpdateShouldLoad(bool shouldLoad); + } +} diff --git a/src/Accounts/AssemblyLoading/IConditionalAssemblyContext.cs b/src/Accounts/AssemblyLoading/IConditionalAssemblyContext.cs new file mode 100644 index 000000000000..7b8c9bedb013 --- /dev/null +++ b/src/Accounts/AssemblyLoading/IConditionalAssemblyContext.cs @@ -0,0 +1,43 @@ +// ---------------------------------------------------------------------------------- +// +// Copyright Microsoft Corporation +// 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; +using System.Runtime.InteropServices; + +namespace Microsoft.Azure.PowerShell.AssemblyLoading +{ + /// + /// Context with information about the current environment. + /// Used to calculate if this assembly should be loaded. + /// + public interface IConditionalAssemblyContext + { + /// + /// Version of PowerShell. For example "5.1.22621.608". + /// + Version PSVersion { get; } + + /// + /// Returns if the current operating system matches . + /// + /// The expected operating system + bool IsOSPlatform(OSPlatform os); + + /// + /// OS Architecture. For example "X86". + /// + /// + Architecture OSArchitecture { get; } + } +} diff --git a/src/Accounts/Authentication/Authentication.csproj b/src/Accounts/Authentication/Authentication.csproj index 53aef056e27b..d8b7284460d3 100644 --- a/src/Accounts/Authentication/Authentication.csproj +++ b/src/Accounts/Authentication/Authentication.csproj @@ -17,6 +17,10 @@ + + + + True diff --git a/src/Accounts/Authentication/Utilities/CustomAssemblyResolver.cs b/src/Accounts/Authentication/Utilities/CustomAssemblyResolver.cs index f599c219167e..f99a50fc26ce 100644 --- a/src/Accounts/Authentication/Utilities/CustomAssemblyResolver.cs +++ b/src/Accounts/Authentication/Utilities/CustomAssemblyResolver.cs @@ -12,53 +12,23 @@ // limitations under the License. // ---------------------------------------------------------------------------------- +using Microsoft.Azure.PowerShell.AssemblyLoading; using System; using System.Collections.Generic; -using System.IO; using System.Reflection; namespace Microsoft.Azure.Commands.Profile.Utilities { + /// + /// Handles how common dependency assemblies like Azure.Core are loaded on .NET framework. + /// public static class CustomAssemblyResolver { - private static IDictionary NetFxPreloadAssemblies = - new Dictionary(StringComparer.InvariantCultureIgnoreCase) - { - {"Azure.Core", new Version("1.25.0.0")}, - {"Azure.Identity", new Version("1.6.1.0")}, - {"Microsoft.Bcl.AsyncInterfaces", new Version("1.1.1.0")}, - {"Microsoft.Identity.Client", new Version("4.46.2.0") }, - {"Microsoft.Identity.Client.Extensions.Msal", new Version("2.23.0.0") }, - {"Microsoft.IdentityModel.Abstractions", new Version("6.22.1.0") }, - - {"Newtonsoft.Json", new Version("10.0.0.0")}, - {"System.Buffers", new Version("4.0.3.0")}, - {"System.Diagnostics.DiagnosticSource", new Version("4.0.4.0")}, - {"System.Memory", new Version("4.0.1.1")}, - {"System.Memory.Data", new Version("1.0.2.0")}, - {"System.Net.Http.WinHttpHandler", new Version("4.0.2.0")}, - {"System.Numerics.Vectors", new Version("4.1.4.0")}, - {"System.Private.ServiceModel", new Version("4.7.0.0")}, //used by Compute - {"System.Reflection.DispatchProxy", new Version("4.0.4.0")}, - {"System.Runtime.CompilerServices.Unsafe", new Version("4.0.6.0")}, - {"System.Security.AccessControl", new Version("4.1.1.0")}, - {"System.Security.Cryptography.Cng", new Version("4.3.0.0")}, - {"System.Security.Permissions", new Version("4.0.1.0")}, - {"System.Security.Principal.Windows", new Version("4.1.1.0")}, - {"System.ServiceModel.Primitives", new Version("4.7.0.0")}, //used by Compute - {"System.Text.Encodings.Web", new Version("4.0.5.1")}, - {"System.Text.Json", new Version("4.0.1.2")}, - {"System.Threading.Tasks.Extensions", new Version("4.2.0.1")}, - {"System.Xml.ReaderWriter", new Version("4.1.0.0")} - }; - - private static string PreloadAssemblyFolder { get; set; } + private static IDictionary NetFxPreloadAssemblies = ConditionalAssemblyProvider.GetAssemblies(); public static void Initialize() { //This function is call before loading assemblies in PreloadAssemblies folder, so NewtonSoft.Json could not be used here - var accountFolder = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); - PreloadAssemblyFolder = Path.Combine(accountFolder, "PreloadAssemblies"); AppDomain.CurrentDomain.AssemblyResolve += CurrentDomain_AssemblyResolve; } @@ -70,13 +40,14 @@ public static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEvent try { AssemblyName name = new AssemblyName(args.Name); - if (NetFxPreloadAssemblies.TryGetValue(name.Name, out Version version)) + if (NetFxPreloadAssemblies.TryGetValue(name.Name, out var assembly)) { //For Newtonsoft.Json, allow to use bigger version to replace smaller version - if (version >= name.Version && (version.Major == name.Version.Major || string.Equals(name.Name, "Newtonsoft.Json", StringComparison.OrdinalIgnoreCase))) + if (assembly.Version >= name.Version + && (assembly.Version.Major == name.Version.Major + || string.Equals(name.Name, "Newtonsoft.Json", StringComparison.OrdinalIgnoreCase))) { - string requiredAssembly = Path.Combine(PreloadAssemblyFolder, $"{name.Name}.dll"); - return Assembly.LoadFrom(requiredAssembly); + return Assembly.LoadFrom(assembly.Path); } } } diff --git a/src/Accounts/AuthenticationAssemblyLoadContext/AuthenticationAssemblyLoadContext.csproj b/src/Accounts/AuthenticationAssemblyLoadContext/AuthenticationAssemblyLoadContext.csproj index cc38ef9b6755..69e04117b36e 100644 --- a/src/Accounts/AuthenticationAssemblyLoadContext/AuthenticationAssemblyLoadContext.csproj +++ b/src/Accounts/AuthenticationAssemblyLoadContext/AuthenticationAssemblyLoadContext.csproj @@ -23,5 +23,8 @@ TRACE;RELEASE;SIGN + + + diff --git a/src/Accounts/AuthenticationAssemblyLoadContext/AzAssemblyLoadContext.cs b/src/Accounts/AuthenticationAssemblyLoadContext/AzAssemblyLoadContext.cs index 0023a97aa0d7..8e3e2dab975e 100644 --- a/src/Accounts/AuthenticationAssemblyLoadContext/AzAssemblyLoadContext.cs +++ b/src/Accounts/AuthenticationAssemblyLoadContext/AzAssemblyLoadContext.cs @@ -12,7 +12,6 @@ // limitations under the License. // ---------------------------------------------------------------------------------- -using System; using System.Collections.Concurrent; using System.IO; using System.Reflection; @@ -20,18 +19,27 @@ namespace Microsoft.Azure.PowerShell.AuthenticationAssemblyLoadContext { - internal class AzAssemblyLoadContext : AssemblyLoadContext + /// + /// Assembly load context of a service module. + /// The way of looking for assemblies is based on directory. + /// + internal class AzAssemblyLoadContext : AzAssemblyLoadContextBase { private string AssemblyDirectory { get; set; } - private ConcurrentDictionary AssemblyCache { get; set; } = - new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); + private static readonly ConcurrentDictionary DependencyLoadContexts = new ConcurrentDictionary(); - private static readonly ConcurrentDictionary DependencyLoadContexts = new ConcurrentDictionary(); - - internal static AzAssemblyLoadContext GetForDirectory(string directoryPath) + /// + /// Get an ALC for a certain directory that contains assemblies. + /// + /// + /// There are two types of possible value for : + /// 1. which will create if not exist and return an ALC for shared libraries. + /// 2. A directory in a service module that contains the assemblies to be loaded into the ALC of the service module. + /// + internal static AssemblyLoadContext GetForDirectory(string directoryPath) { - return DependencyLoadContexts.GetOrAdd(directoryPath, (path) => new AzAssemblyLoadContext(path)); + return DependencyLoadContexts.GetOrAdd(directoryPath, path => path.Equals(AzSharedAssemblyLoadContext.Key) ? new AzSharedAssemblyLoadContext() : (AssemblyLoadContext)new AzAssemblyLoadContext(directoryPath)); } /// @@ -39,21 +47,13 @@ internal static AzAssemblyLoadContext GetForDirectory(string directoryPath) /// /// Root directory to look for assembly. /// - public AzAssemblyLoadContext(string directory) + public AzAssemblyLoadContext(string directory) : base(directory) { AssemblyDirectory = directory; } - protected override Assembly Load(AssemblyName requestedAssemblyName) + protected override Assembly LoadAfterCacheMiss(AssemblyName requestedAssemblyName) { - if (AssemblyCache.TryGetValue(requestedAssemblyName.Name, out Assembly assembly)) - { - if (IsAssemblyMatching(requestedAssemblyName, assembly.GetName())) - { - return assembly; - } - } - string assemblyFileName = $"{requestedAssemblyName.Name}.dll"; // Now try to load the assembly from the dependency directory @@ -65,51 +65,12 @@ protected override Assembly Load(AssemblyName requestedAssemblyName) var loadedAssemblyName = loadedAssembly.GetName(); if (IsAssemblyMatching(requestedAssemblyName, loadedAssemblyName)) { - AssemblyCache.TryAdd(loadedAssemblyName.Name, loadedAssembly); + CacheAssembly(loadedAssemblyName.Name, loadedAssembly); } return loadedAssembly; } return null; } - - private bool IsAssemblyMatching(AssemblyName requestedAssembly, AssemblyName loadedAssembly) - { - // We use the same rules as CoreCLR loader to compare the requested assembly and loaded assembly: - // 1. If 'Version' of the requested assembly is specified, then the requested version should be less or equal to the loaded version; - // 2. If 'CultureName' of the requested assembly is specified (not NullOrEmpty), then the CultureName of the loaded assembly should be the same; - // 3. If 'PublicKeyToken' of the requested assembly is specified (not Null or EmptyArray), then the PublicKenToken of the loaded assembly should be the same. - - // Version of the requested assembly should be the same or before the version of loaded assembly - if (requestedAssembly.Version != null && requestedAssembly.Version.CompareTo(loadedAssembly.Version) > 0) - { - return false; - } - - // CultureName of requested assembly and loaded assembly should be the same - string requestedCultureName = requestedAssembly.CultureName; - if (!string.IsNullOrEmpty(requestedCultureName) && !requestedCultureName.Equals(loadedAssembly.CultureName, StringComparison.OrdinalIgnoreCase)) - { - return false; - } - - // PublicKeyToken should be the same, unless it's not specified in the requested assembly - byte[] requestedPublicKeyToken = requestedAssembly.GetPublicKeyToken(); - byte[] loadedPublicKeyToken = loadedAssembly.GetPublicKeyToken(); - - if (requestedPublicKeyToken != null && requestedPublicKeyToken.Length > 0) - { - if (loadedPublicKeyToken == null || requestedPublicKeyToken.Length != loadedPublicKeyToken.Length) - return false; - - for (int i = 0; i < requestedPublicKeyToken.Length; i++) - { - if (requestedPublicKeyToken[i] != loadedPublicKeyToken[i]) - return false; - } - } - - return true; - } } } diff --git a/src/Accounts/AuthenticationAssemblyLoadContext/AzAssemblyLoadContextBase.cs b/src/Accounts/AuthenticationAssemblyLoadContext/AzAssemblyLoadContextBase.cs new file mode 100644 index 000000000000..5b9032b6be73 --- /dev/null +++ b/src/Accounts/AuthenticationAssemblyLoadContext/AzAssemblyLoadContextBase.cs @@ -0,0 +1,99 @@ +// ---------------------------------------------------------------------------------- +// +// Copyright Microsoft Corporation +// 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; +using System.Collections.Concurrent; +using System.Reflection; +using System.Runtime.Loader; + +namespace Microsoft.Azure.PowerShell.AuthenticationAssemblyLoadContext +{ + /// + /// Base class providing caching capability. + /// + internal abstract class AzAssemblyLoadContextBase : AssemblyLoadContext + { + private ConcurrentDictionary AssemblyCache { get; set; } = + new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); + + protected AzAssemblyLoadContextBase(string name) : base(name) { } + + protected void CacheAssembly(string name, Assembly assembly) + { + AssemblyCache.TryAdd(name, assembly); + } + + protected sealed override Assembly Load(AssemblyName requestedAssemblyName) + { + if (AssemblyCache.TryGetValue(requestedAssemblyName.Name, out Assembly assembly)) + { + if (IsAssemblyMatching(requestedAssemblyName, assembly.GetName())) + { + return assembly; + } + } + return LoadAfterCacheMiss(requestedAssemblyName); + } + + /// + /// Override this method to provide custom ways to load an assembly by name. + /// + /// + /// Call to add the loaded assembly to cache. + /// + protected virtual Assembly LoadAfterCacheMiss(AssemblyName requestAssemblyName) + { + return null; + } + + protected bool IsAssemblyMatching(AssemblyName requestedAssembly, AssemblyName loadedAssembly) + { + // We use the same rules as CoreCLR loader to compare the requested assembly and loaded assembly: + // 1. If 'Version' of the requested assembly is specified, then the requested version should be less or equal to the loaded version; + // 2. If 'CultureName' of the requested assembly is specified (not NullOrEmpty), then the CultureName of the loaded assembly should be the same; + // 3. If 'PublicKeyToken' of the requested assembly is specified (not Null or EmptyArray), then the PublicKenToken of the loaded assembly should be the same. + + // Version of the requested assembly should be the same or before the version of loaded assembly + if (requestedAssembly.Version != null && requestedAssembly.Version.CompareTo(loadedAssembly.Version) > 0) + { + return false; + } + + // CultureName of requested assembly and loaded assembly should be the same + string requestedCultureName = requestedAssembly.CultureName; + if (!string.IsNullOrEmpty(requestedCultureName) && !requestedCultureName.Equals(loadedAssembly.CultureName, StringComparison.OrdinalIgnoreCase)) + { + return false; + } + + // PublicKeyToken should be the same, unless it's not specified in the requested assembly + byte[] requestedPublicKeyToken = requestedAssembly.GetPublicKeyToken(); + byte[] loadedPublicKeyToken = loadedAssembly.GetPublicKeyToken(); + + if (requestedPublicKeyToken != null && requestedPublicKeyToken.Length > 0) + { + if (loadedPublicKeyToken == null || requestedPublicKeyToken.Length != loadedPublicKeyToken.Length) + return false; + + for (int i = 0; i < requestedPublicKeyToken.Length; i++) + { + if (requestedPublicKeyToken[i] != loadedPublicKeyToken[i]) + return false; + } + } + + return true; + } + } +} diff --git a/src/Accounts/AuthenticationAssemblyLoadContext/AzAssemblyLoadContextInitializer.cs b/src/Accounts/AuthenticationAssemblyLoadContext/AzAssemblyLoadContextInitializer.cs index 38a52e6de9f5..b95cc61db8eb 100644 --- a/src/Accounts/AuthenticationAssemblyLoadContext/AzAssemblyLoadContextInitializer.cs +++ b/src/Accounts/AuthenticationAssemblyLoadContext/AzAssemblyLoadContextInitializer.cs @@ -14,50 +14,34 @@ using System; using System.Collections.Concurrent; -using System.Collections.Generic; using System.IO; using System.Runtime.Loader; +using Microsoft.Azure.PowerShell.AssemblyLoading; namespace Microsoft.Azure.PowerShell.AuthenticationAssemblyLoadContext { public static class AzAssemblyLoadContextInitializer { - private static string AzSharedAssemblyDirectory { get; set; } - private static ConcurrentDictionary AzSharedAssemblyMap { get; set; } + private static ConcurrentDictionary AzSharedAssemblyMap { get; set; } private static ConcurrentDictionary ModuleAlcEntryAssemblyMap { get; set; } static AzAssemblyLoadContextInitializer() { - //TODO: Generate assembly version info into AzSharedAssemblies.json during build - var azSharedAssemblies = new Dictionary() - { - {"Azure.Core", new Version("1.25.0.0")}, - {"Azure.Identity", new Version("1.6.1.0")}, - {"Microsoft.Bcl.AsyncInterfaces", new Version("1.1.1.0")}, - {"Microsoft.Identity.Client", new Version("4.46.2.0") }, - {"Microsoft.Identity.Client.Extensions.Msal", new Version("2.23.0.0") }, - {"Microsoft.IdentityModel.Abstractions", new Version("6.22.1.0") }, - {"System.Memory.Data", new Version("1.0.2.0")}, - {"System.Text.Json", new Version("4.0.1.2")}, - }; - - AzSharedAssemblyMap = new ConcurrentDictionary(azSharedAssemblies, StringComparer.OrdinalIgnoreCase); - + var azSharedAssemblies = ConditionalAssemblyProvider.GetAssemblies(); + AzSharedAssemblyMap = new ConcurrentDictionary(azSharedAssemblies, StringComparer.OrdinalIgnoreCase); ModuleAlcEntryAssemblyMap = new ConcurrentDictionary(); } /// /// Registers the shared ALC and listen to assembly resolving event of the default ALC. /// - /// Root directory to look for assemblies. - public static void RegisterAzSharedAssemblyLoadContext(string azSharedAssemblyDirectory) + public static void RegisterAzSharedAssemblyLoadContext() { - AzSharedAssemblyDirectory = azSharedAssemblyDirectory; AssemblyLoadContext.Default.Resolving += Default_Resolving; } /// - /// Registers an ALC to be instanciated later. + /// Registers an ALC to be instantiated later. /// /// Name of the entry assembly, typically "{Module}.AlcWrapper.dll". It must be unique for each module. /// Root directory to look for assemblies. @@ -68,9 +52,9 @@ public static void RegisterModuleAssemblyLoadContext(string contextEntryAssembly private static System.Reflection.Assembly Default_Resolving(AssemblyLoadContext context, System.Reflection.AssemblyName assemblyName) { - if (AzSharedAssemblyMap.ContainsKey(assemblyName.Name) && AzSharedAssemblyMap[assemblyName.Name] >= assemblyName.Version) + if (AzSharedAssemblyMap.TryGetValue(assemblyName.Name, out var azSharedAssembly) && azSharedAssembly.Version >= assemblyName.Version) { - return AzAssemblyLoadContext.GetForDirectory(AzSharedAssemblyDirectory).LoadFromAssemblyName(assemblyName); + return AzAssemblyLoadContext.GetForDirectory(AzSharedAssemblyLoadContext.Key).LoadFromAssemblyName(assemblyName); } if (ModuleAlcEntryAssemblyMap.TryGetValue(assemblyName.Name, out string moduleLoadContextDirectory)) diff --git a/src/Accounts/AuthenticationAssemblyLoadContext/AzSharedAssemblyLoadContext.cs b/src/Accounts/AuthenticationAssemblyLoadContext/AzSharedAssemblyLoadContext.cs new file mode 100644 index 000000000000..0daf3d982133 --- /dev/null +++ b/src/Accounts/AuthenticationAssemblyLoadContext/AzSharedAssemblyLoadContext.cs @@ -0,0 +1,57 @@ +// ---------------------------------------------------------------------------------- +// +// Copyright Microsoft Corporation +// 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.Azure.PowerShell.AssemblyLoading; +using System; +using System.Collections.Concurrent; +using System.IO; +using System.Reflection; + +namespace Microsoft.Azure.PowerShell.AuthenticationAssemblyLoadContext +{ + /// + /// Assembly load context of the shared assemblies in Az.Accounts module. + /// Assemblies are provided by . + /// + internal class AzSharedAssemblyLoadContext : AzAssemblyLoadContextBase + { + /// + /// Key to get the shared ALC. + /// + public const string Key = nameof(AzSharedAssemblyLoadContext); + + private ConcurrentDictionary _assemblies; + + public AzSharedAssemblyLoadContext() : base(Key) + { + _assemblies = new ConcurrentDictionary(ConditionalAssemblyProvider.GetAssemblies(), StringComparer.OrdinalIgnoreCase); + } + + protected override Assembly LoadAfterCacheMiss(AssemblyName requestedAssemblyName) + { + if (_assemblies.TryGetValue(requestedAssemblyName.Name, out var assembly) + && File.Exists(assembly.Path)) + { + var loadedAssembly = LoadFromAssemblyPath(assembly.Path); + var loadedAssemblyName = loadedAssembly.GetName(); + if (IsAssemblyMatching(requestedAssemblyName, loadedAssemblyName)) + { + CacheAssembly(loadedAssemblyName.Name, loadedAssembly); + } + return loadedAssembly; + } + return null; + } + } +} diff --git a/src/Accounts/Authenticators/Authenticators.csproj b/src/Accounts/Authenticators/Authenticators.csproj index 7311b2b5ca4a..013d0badaf13 100644 --- a/src/Accounts/Authenticators/Authenticators.csproj +++ b/src/Accounts/Authenticators/Authenticators.csproj @@ -26,13 +26,6 @@ - - - - - - - diff --git a/src/Storage/Storage/Storage.csproj b/src/Storage/Storage/Storage.csproj index 0ca0120999c9..2c1e7b7dbc3f 100644 --- a/src/Storage/Storage/Storage.csproj +++ b/src/Storage/Storage/Storage.csproj @@ -23,11 +23,7 @@ - - - - - + $(RepoSrc)lib\Microsoft.Azure.Storage.DataMovement.dll diff --git a/src/lib/NetFxPreloadAssemblies/Azure.Identity.dll b/src/lib/NetFxPreloadAssemblies/Azure.Identity.dll deleted file mode 100644 index 5662a76ad958..000000000000 Binary files a/src/lib/NetFxPreloadAssemblies/Azure.Identity.dll and /dev/null differ diff --git a/src/lib/NetFxPreloadAssemblies/Microsoft.Bcl.AsyncInterfaces.dll b/src/lib/NetFxPreloadAssemblies/Microsoft.Bcl.AsyncInterfaces.dll deleted file mode 100644 index 869ac1b86c57..000000000000 Binary files a/src/lib/NetFxPreloadAssemblies/Microsoft.Bcl.AsyncInterfaces.dll and /dev/null differ diff --git a/src/lib/NetFxPreloadAssemblies/Microsoft.IdentityModel.Abstractions.dll b/src/lib/NetFxPreloadAssemblies/Microsoft.IdentityModel.Abstractions.dll deleted file mode 100644 index ab35e1c9e9f7..000000000000 Binary files a/src/lib/NetFxPreloadAssemblies/Microsoft.IdentityModel.Abstractions.dll and /dev/null differ diff --git a/src/lib/NetFxPreloadAssemblies/Newtonsoft.Json.dll b/src/lib/NetFxPreloadAssemblies/Newtonsoft.Json.dll deleted file mode 100644 index 77a5d89e605c..000000000000 Binary files a/src/lib/NetFxPreloadAssemblies/Newtonsoft.Json.dll and /dev/null differ diff --git a/src/lib/NetFxPreloadAssemblies/System.Memory.Data.dll b/src/lib/NetFxPreloadAssemblies/System.Memory.Data.dll deleted file mode 100644 index 5aa381018c00..000000000000 Binary files a/src/lib/NetFxPreloadAssemblies/System.Memory.Data.dll and /dev/null differ diff --git a/src/lib/NetFxPreloadAssemblies/System.Text.Json.dll b/src/lib/NetFxPreloadAssemblies/System.Text.Json.dll deleted file mode 100644 index a3a85c2b7255..000000000000 Binary files a/src/lib/NetFxPreloadAssemblies/System.Text.Json.dll and /dev/null differ diff --git a/src/lib/NetCorePreloadAssemblies/Azure.Core.dll b/src/lib/netcoreapp2.1/Azure.Core.dll similarity index 100% rename from src/lib/NetCorePreloadAssemblies/Azure.Core.dll rename to src/lib/netcoreapp2.1/Azure.Core.dll diff --git a/src/lib/NetCorePreloadAssemblies/Microsoft.Identity.Client.dll b/src/lib/netcoreapp2.1/Microsoft.Identity.Client.dll similarity index 100% rename from src/lib/NetCorePreloadAssemblies/Microsoft.Identity.Client.dll rename to src/lib/netcoreapp2.1/Microsoft.Identity.Client.dll diff --git a/src/lib/NetCorePreloadAssemblies/Microsoft.Identity.Client.Extensions.Msal.dll b/src/lib/netcoreapp3.1/Microsoft.Identity.Client.Extensions.Msal.dll similarity index 100% rename from src/lib/NetCorePreloadAssemblies/Microsoft.Identity.Client.Extensions.Msal.dll rename to src/lib/netcoreapp3.1/Microsoft.Identity.Client.Extensions.Msal.dll diff --git a/src/lib/NetFxPreloadAssemblies/Azure.Core.dll b/src/lib/netfx/Azure.Core.dll similarity index 100% rename from src/lib/NetFxPreloadAssemblies/Azure.Core.dll rename to src/lib/netfx/Azure.Core.dll diff --git a/src/lib/NetFxPreloadAssemblies/Microsoft.Identity.Client.Extensions.Msal.dll b/src/lib/netfx/Microsoft.Identity.Client.Extensions.Msal.dll similarity index 100% rename from src/lib/NetFxPreloadAssemblies/Microsoft.Identity.Client.Extensions.Msal.dll rename to src/lib/netfx/Microsoft.Identity.Client.Extensions.Msal.dll diff --git a/src/lib/NetFxPreloadAssemblies/Microsoft.Identity.Client.dll b/src/lib/netfx/Microsoft.Identity.Client.dll similarity index 100% rename from src/lib/NetFxPreloadAssemblies/Microsoft.Identity.Client.dll rename to src/lib/netfx/Microsoft.Identity.Client.dll diff --git a/src/lib/NetFxPreloadAssemblies/Newtonsoft.Json.12.0.3.dll b/src/lib/netfx/Newtonsoft.Json.dll similarity index 100% rename from src/lib/NetFxPreloadAssemblies/Newtonsoft.Json.12.0.3.dll rename to src/lib/netfx/Newtonsoft.Json.dll diff --git a/src/lib/NetFxPreloadAssemblies/System.Diagnostics.DiagnosticSource.dll b/src/lib/netfx/System.Diagnostics.DiagnosticSource.dll similarity index 100% rename from src/lib/NetFxPreloadAssemblies/System.Diagnostics.DiagnosticSource.dll rename to src/lib/netfx/System.Diagnostics.DiagnosticSource.dll diff --git a/src/lib/NetFxPreloadAssemblies/System.Numerics.Vectors.dll b/src/lib/netfx/System.Numerics.Vectors.dll similarity index 100% rename from src/lib/NetFxPreloadAssemblies/System.Numerics.Vectors.dll rename to src/lib/netfx/System.Numerics.Vectors.dll diff --git a/src/lib/NetFxPreloadAssemblies/System.Reflection.DispatchProxy.dll b/src/lib/netfx/System.Reflection.DispatchProxy.dll similarity index 100% rename from src/lib/NetFxPreloadAssemblies/System.Reflection.DispatchProxy.dll rename to src/lib/netfx/System.Reflection.DispatchProxy.dll diff --git a/src/lib/NetFxPreloadAssemblies/System.Runtime.CompilerServices.Unsafe.dll b/src/lib/netfx/System.Runtime.CompilerServices.Unsafe.dll similarity index 100% rename from src/lib/NetFxPreloadAssemblies/System.Runtime.CompilerServices.Unsafe.dll rename to src/lib/netfx/System.Runtime.CompilerServices.Unsafe.dll diff --git a/src/lib/NetFxPreloadAssemblies/System.Security.Cryptography.Cng.dll b/src/lib/netfx/System.Security.Cryptography.Cng.dll similarity index 100% rename from src/lib/NetFxPreloadAssemblies/System.Security.Cryptography.Cng.dll rename to src/lib/netfx/System.Security.Cryptography.Cng.dll diff --git a/src/lib/NetFxPreloadAssemblies/System.Text.Encodings.Web.dll b/src/lib/netfx/System.Text.Encodings.Web.dll similarity index 100% rename from src/lib/NetFxPreloadAssemblies/System.Text.Encodings.Web.dll rename to src/lib/netfx/System.Text.Encodings.Web.dll diff --git a/src/lib/NetFxPreloadAssemblies/System.Xml.ReaderWriter.dll b/src/lib/netfx/System.Xml.ReaderWriter.dll similarity index 100% rename from src/lib/NetFxPreloadAssemblies/System.Xml.ReaderWriter.dll rename to src/lib/netfx/System.Xml.ReaderWriter.dll diff --git a/src/lib/NetCorePreloadAssemblies/Azure.Identity.dll b/src/lib/netstandard2.0/Azure.Identity.dll similarity index 100% rename from src/lib/NetCorePreloadAssemblies/Azure.Identity.dll rename to src/lib/netstandard2.0/Azure.Identity.dll diff --git a/src/lib/NetCorePreloadAssemblies/Microsoft.Bcl.AsyncInterfaces.dll b/src/lib/netstandard2.0/Microsoft.Bcl.AsyncInterfaces.dll similarity index 100% rename from src/lib/NetCorePreloadAssemblies/Microsoft.Bcl.AsyncInterfaces.dll rename to src/lib/netstandard2.0/Microsoft.Bcl.AsyncInterfaces.dll diff --git a/src/lib/NetCorePreloadAssemblies/Microsoft.IdentityModel.Abstractions.dll b/src/lib/netstandard2.0/Microsoft.IdentityModel.Abstractions.dll similarity index 100% rename from src/lib/NetCorePreloadAssemblies/Microsoft.IdentityModel.Abstractions.dll rename to src/lib/netstandard2.0/Microsoft.IdentityModel.Abstractions.dll diff --git a/src/lib/NetFxPreloadAssemblies/System.Buffers.dll b/src/lib/netstandard2.0/System.Buffers.dll similarity index 100% rename from src/lib/NetFxPreloadAssemblies/System.Buffers.dll rename to src/lib/netstandard2.0/System.Buffers.dll diff --git a/src/lib/NetCorePreloadAssemblies/System.Memory.Data.dll b/src/lib/netstandard2.0/System.Memory.Data.dll similarity index 100% rename from src/lib/NetCorePreloadAssemblies/System.Memory.Data.dll rename to src/lib/netstandard2.0/System.Memory.Data.dll diff --git a/src/lib/NetFxPreloadAssemblies/System.Memory.dll b/src/lib/netstandard2.0/System.Memory.dll similarity index 100% rename from src/lib/NetFxPreloadAssemblies/System.Memory.dll rename to src/lib/netstandard2.0/System.Memory.dll diff --git a/src/lib/NetFxPreloadAssemblies/System.Net.Http.WinHttpHandler.dll b/src/lib/netstandard2.0/System.Net.Http.WinHttpHandler.dll similarity index 100% rename from src/lib/NetFxPreloadAssemblies/System.Net.Http.WinHttpHandler.dll rename to src/lib/netstandard2.0/System.Net.Http.WinHttpHandler.dll diff --git a/src/lib/NetFxPreloadAssemblies/System.Private.ServiceModel.dll b/src/lib/netstandard2.0/System.Private.ServiceModel.dll similarity index 100% rename from src/lib/NetFxPreloadAssemblies/System.Private.ServiceModel.dll rename to src/lib/netstandard2.0/System.Private.ServiceModel.dll diff --git a/src/lib/NetFxPreloadAssemblies/System.Security.AccessControl.dll b/src/lib/netstandard2.0/System.Security.AccessControl.dll similarity index 100% rename from src/lib/NetFxPreloadAssemblies/System.Security.AccessControl.dll rename to src/lib/netstandard2.0/System.Security.AccessControl.dll diff --git a/src/lib/NetFxPreloadAssemblies/System.Security.Permissions.dll b/src/lib/netstandard2.0/System.Security.Permissions.dll similarity index 100% rename from src/lib/NetFxPreloadAssemblies/System.Security.Permissions.dll rename to src/lib/netstandard2.0/System.Security.Permissions.dll diff --git a/src/lib/NetFxPreloadAssemblies/System.Security.Principal.Windows.dll b/src/lib/netstandard2.0/System.Security.Principal.Windows.dll similarity index 100% rename from src/lib/NetFxPreloadAssemblies/System.Security.Principal.Windows.dll rename to src/lib/netstandard2.0/System.Security.Principal.Windows.dll diff --git a/src/lib/NetFxPreloadAssemblies/System.ServiceModel.Primitives.dll b/src/lib/netstandard2.0/System.ServiceModel.Primitives.dll similarity index 100% rename from src/lib/NetFxPreloadAssemblies/System.ServiceModel.Primitives.dll rename to src/lib/netstandard2.0/System.ServiceModel.Primitives.dll diff --git a/src/lib/NetCorePreloadAssemblies/System.Text.Json.dll b/src/lib/netstandard2.0/System.Text.Json.dll similarity index 100% rename from src/lib/NetCorePreloadAssemblies/System.Text.Json.dll rename to src/lib/netstandard2.0/System.Text.Json.dll diff --git a/src/lib/NetFxPreloadAssemblies/System.Threading.Tasks.Extensions.dll b/src/lib/netstandard2.0/System.Threading.Tasks.Extensions.dll similarity index 100% rename from src/lib/NetFxPreloadAssemblies/System.Threading.Tasks.Extensions.dll rename to src/lib/netstandard2.0/System.Threading.Tasks.Extensions.dll diff --git a/src/lib/test.net472.config b/src/lib/test.net472.config index 131361166d84..593b17b73709 100644 --- a/src/lib/test.net472.config +++ b/src/lib/test.net472.config @@ -7,9 +7,9 @@ publicKeyToken="30ad4fe6b2a6aeed" culture="neutral" /> + href="../../../../../lib/netfx/Newtonsoft.Json.9.dll"/> + href="../../../../../lib/netfx/Newtonsoft.Json.dll"/> diff --git a/tools/AzureRM.Example.psm1 b/tools/AzureRM.Example.psm1 index 1404de564b79..b064db1aa2a0 100644 --- a/tools/AzureRM.Example.psm1 +++ b/tools/AzureRM.Example.psm1 @@ -44,7 +44,7 @@ function Preload-Assembly { } } catch {} - } + } } if (%ISAZMODULE% -and ($PSEdition -eq 'Desktop')) @@ -74,36 +74,6 @@ if (Get-Module %AZORAZURERM%.profile -ErrorAction Ignore) "If you are running in Azure Automation, take care that none of your runbooks import both Az and AzureRM modules. More information can be found here: https://aka.ms/azps-migration-guide.") } -$preloadPath = (Join-Path $PSScriptRoot -ChildPath "PreloadAssemblies") -Preload-Assembly -AssemblyDirectory $preloadPath -$preloadPath = (Join-Path $PSScriptRoot -ChildPath "ModuleAlcAssemblies") -Preload-Assembly -AssemblyDirectory $preloadPath - -$netCorePath = (Join-Path $PSScriptRoot -ChildPath "NetCoreAssemblies") -if($PSEdition -eq 'Core' -and (Test-Path $netCorePath -ErrorAction Ignore)) -{ - try - { - $loadedAssemblies = ([System.AppDomain]::CurrentDomain.GetAssemblies() | ForEach-Object {New-Object -TypeName System.Reflection.AssemblyName -ArgumentList $_.FullName} ) - Get-ChildItem -ErrorAction Stop -Path $netCorePath -Filter "*.dll" | ForEach-Object { - $assemblyName = ([System.Reflection.AssemblyName]::GetAssemblyName($_.FullName)) - $matches = ($loadedAssemblies | Where-Object {$_.Name -eq $assemblyName.Name}) - if (-not $matches) - { - try - { - Add-Type -Path $_.FullName -ErrorAction Ignore | Out-Null - } - catch { - Write-Verbose $_ - } - } - } - } - catch {} -} - - %IMPORTED-DEPENDENCIES% if (Test-Path -Path "$PSScriptRoot\PostImportScripts" -ErrorAction Ignore) diff --git a/tools/CheckAssemblies.ps1 b/tools/CheckAssemblies.ps1 index 90f030bcd96c..62a988a283e4 100644 --- a/tools/CheckAssemblies.ps1 +++ b/tools/CheckAssemblies.ps1 @@ -19,23 +19,18 @@ param( function Get-PreloadAssemblies{ param( + [Parameter(Mandatory)] + [string] $BuildFolder, [Parameter(Mandatory=$True)] [string] $ModuleFolder ) - - if($PSEdition -eq 'Core') { - $preloadFolderName = @("NetCoreAssemblies", "AzSharedAlcAssemblies") - } else { - $preloadFolderName = "PreloadAssemblies" - } - $preloadFolderName | ForEach-Object { - $preloadAssemblies = @() - $preloadFolder = [System.IO.Path]::Combine($ModuleFolder, $_) - if(Test-Path $preloadFolder){ - $preloadAssemblies = (Get-ChildItem $preloadFolder -Filter "*.dll").Name | ForEach-Object { $_ -replace ".dll", ""} - } - $preloadAssemblies - } + Write-Host "Getting preload assemblies in $BuildFolder for $ModuleFolder" + Add-Type -Path ([System.IO.Path]::Combine($BuildFolder, "Az.Accounts", "Microsoft.Azure.PowerShell.AssemblyLoading.dll")) + $assemblyRootPath = [System.IO.Path]::Combine($BuildFolder, "Az.Accounts", "lib") + $conditionalAssemblyContext = [Microsoft.Azure.PowerShell.AssemblyLoading.ConditionalAssemblyContext]::new($Host.Version) + [Microsoft.Azure.PowerShell.AssemblyLoading.ConditionalAssemblyProvider]::Initialize($assemblyRootPath, $conditionalAssemblyContext) + $assemblyDict = [Microsoft.Azure.PowerShell.AssemblyLoading.ConditionalAssemblyProvider]::GetAssemblies() + return $assemblyDict.Keys } $ProjectPaths = @( "$PSScriptRoot\..\artifacts\$BuildConfig" ) @@ -66,7 +61,7 @@ foreach ($ModuleManifest in $ModuleManifestFiles) { $LoadedAssemblies += $ModuleMetadata.RequiredAssemblies } - $LoadedAssemblies += Get-PreloadAssemblies $ModuleManifest.Directory + $LoadedAssemblies += Get-PreloadAssemblies -BuildFolder "$PSScriptRoot\..\artifacts\$BuildConfig" -ModuleFolder $ModuleManifest.Directory $LoadedAssemblies += $ModuleMetadata.NestedModules if ($ModuleMetadata.RequiredModules) { @@ -87,7 +82,7 @@ foreach ($ModuleManifest in $ModuleManifestFiles) { } $LoadedAssemblies += $ModuleMetadata.NestedModules } - $LoadedAssemblies += Get-PreloadAssemblies $RequiredModuleManifest.Directory + $LoadedAssemblies += Get-PreloadAssemblies -BuildFolder "$PSScriptRoot\..\artifacts\$BuildConfig" -ModuleFolder $RequiredModuleManifest.Directory } } diff --git a/tools/CleanupBuild.ps1 b/tools/CleanupBuild.ps1 index a39cc4cfbdc5..d766254ddc07 100644 --- a/tools/CleanupBuild.ps1 +++ b/tools/CleanupBuild.ps1 @@ -111,6 +111,9 @@ foreach($RMPath in $resourceManagerPaths) Write-Host "Removing redundant dlls in $($RMFolder.Name)" $removedDlls = Get-ChildItem -Path $RMFolder.FullName -Filter "*.dll" -Recurse | where { $acceptedDlls -notcontains $_.Name -and !$_.FullName.Contains("Assemblies") } + # do not remove lib dlls (for example Az.Accounts/lib/netcoreapp2.1/Azure.Core.dll) + $libPattern = [System.IO.Path]::DirectorySeparatorChar + "lib" + [System.IO.Path]::DirectorySeparatorChar; + $removedDlls = $removedDlls | Where-Object { -not $_.FullName.Contains($libPattern) } $removedDlls | % { Write-Host "Removing $($_.Name)"; Remove-Item $_.FullName -Force } Write-Host "Removing scripts and psd1 in $($RMFolder.FullName)" diff --git a/tools/StaticAnalysis/ExampleAnalyzer/Measure-MarkdownOrScript.ps1 b/tools/StaticAnalysis/ExampleAnalyzer/Measure-MarkdownOrScript.ps1 index 6c023bc3ac34..ef2d69283e9d 100644 --- a/tools/StaticAnalysis/ExampleAnalyzer/Measure-MarkdownOrScript.ps1 +++ b/tools/StaticAnalysis/ExampleAnalyzer/Measure-MarkdownOrScript.ps1 @@ -64,7 +64,7 @@ $null = New-Item -ItemType File $TempScriptPath if ($PSCmdlet.ParameterSetName -eq "Markdown") { # When the input $MarkdownPaths is the path of txt file contained markdown paths if ((Test-Path $MarkdownPaths -PathType Leaf) -and $MarkdownPaths.EndsWith(".txt")) { - $MarkdownPath = Get-Content $MarkdownPaths | Where-Object { $_.StartsWith("src") } + $MarkdownPath = Get-Content $MarkdownPaths | Where-Object { $_.StartsWith("src") -and (Test-Path $_) } } # When the input $MarkdownPaths is the path of a folder else { diff --git a/tools/Tools.Common/Loaders/CmdletLoader.cs b/tools/Tools.Common/Loaders/CmdletLoader.cs index 39bfed4a1be2..d1e4f566b6f2 100644 --- a/tools/Tools.Common/Loaders/CmdletLoader.cs +++ b/tools/Tools.Common/Loaders/CmdletLoader.cs @@ -44,6 +44,10 @@ public ModuleMetadata GetModuleMetadata(string assemblyPath, List common { dll = Directory.GetFiles(commonOutputFolder + "\\PreloadAssemblies", "*.dll").FirstOrDefault(f => Path.GetFileNameWithoutExtension(f) == assemblyName); } + if (dll == null && Directory.Exists(commonOutputFolder + "\\lib")) + { + dll = Directory.GetFiles(commonOutputFolder + "\\lib", "*.dll", SearchOption.AllDirectories).FirstOrDefault(f => Path.GetFileNameWithoutExtension(f) == assemblyName); + } if (dll == null) { continue; diff --git a/tools/Tools.Common/Loaders/SharedAssemblyLoader.cs b/tools/Tools.Common/Loaders/SharedAssemblyLoader.cs index 5eec07ca0671..92fe561de783 100644 --- a/tools/Tools.Common/Loaders/SharedAssemblyLoader.cs +++ b/tools/Tools.Common/Loaders/SharedAssemblyLoader.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.IO; +using System.Linq; using System.Reflection; using System.Text; @@ -8,12 +9,16 @@ namespace Tools.Common.Loaders { public class SharedAssemblyLoader { + private const string NetCoreApp21 = "netcoreapp2.1"; + private const string NetCoreApp31 = "netcoreapp3.1"; + private const string NetStandard20 = "netstandard2.0"; + private static readonly IEnumerable Frameworks = new string[] { NetCoreApp21, NetCoreApp31, NetStandard20 }; public static HashSet ProcessedFolderSet = new HashSet(); public static void Load(string directory) { directory = Path.GetFullPath(directory); - if(!ProcessedFolderSet.Contains(directory)) + if (!ProcessedFolderSet.Contains(directory)) { ProcessedFolderSet.Add(directory); PreloadSharedAssemblies(directory); @@ -22,25 +27,29 @@ public static void Load(string directory) private static void PreloadSharedAssemblies(string directory) { - var sharedAssemblyFolder = Path.Combine(directory, "Az.Accounts", "AzSharedAlcAssemblies"); - if (Directory.Exists(sharedAssemblyFolder)) + var libFolder = Path.Combine(directory, "Az.Accounts", "lib"); + foreach (string framework in Frameworks) { - foreach (var file in Directory.GetFiles(sharedAssemblyFolder)) + var sharedAssemblyFolder = Path.Combine(libFolder, framework); + if (Directory.Exists(sharedAssemblyFolder)) { - try + foreach (var file in Directory.EnumerateFiles(sharedAssemblyFolder, "*.dll")) { - Console.WriteLine($"PreloadSharedAssemblies: Starting to load assembly {file}."); - Assembly.LoadFrom(file); - } - catch (Exception e) - { - Console.WriteLine($"PreloadSharedAssemblies: Failed (but could be IGNORED) to load assembly {Path.GetFileNameWithoutExtension(file)} with {e}"); + try + { + Console.WriteLine($"PreloadSharedAssemblies: Starting to load assembly {file}."); + Assembly.LoadFrom(file); + } + catch (Exception e) + { + Console.WriteLine($"PreloadSharedAssemblies: Failed (but could be IGNORED) to load assembly {Path.GetFileNameWithoutExtension(file)} with {e}"); + } } } - } - else - { - Console.WriteLine($"PreloadSharedAssemblies: Could not find directory {sharedAssemblyFolder}."); + else + { + Console.WriteLine($"PreloadSharedAssemblies: Could not find directory {libFolder}."); + } } } }