Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Compiling Azure Functions with NuGet package dependencies fails #154

Open
TylerBrinks opened this issue Apr 30, 2020 · 13 comments
Open

Compiling Azure Functions with NuGet package dependencies fails #154

TylerBrinks opened this issue Apr 30, 2020 · 13 comments

Comments

@TylerBrinks
Copy link

TylerBrinks commented Apr 30, 2020

I have a solution with multiple Azure Function HTTP APIs. They were previously using a shared base class in the same project and were able to build without issue. Now, the shared base class has been moved to a NuGet package. Using the base class from the installed NuGet package (even with symbols) fails to compile with the following message:

Unexpected error: Could not load file or assembly 'Microsoft.Extensions.DependencyInjection.Abstractions, Version=3.1.3.0, Culture=neutral, PublicKeyToken=adb9793829ddae60'. Could not find or load a specific file. (0x80131621)  
 at Blueprint.Serverless.ServerlessApiBuilder.Build(ServerlessApi api)   
at Blueprint.Forms.Api.FunctionAppConfiguration.Build(IFunctionHostBuilder builder) 
in [...]\FunctionAppConfiguration.cs:line 33   at FunctionMonkey.Compiler.Core.Compiler.Compile() 
in /Users/jamesrandall/code/functionMonkey/library/Source/FunctionMonkey.Compiler.Core/Compiler.cs:line 91   at FunctionMonkey.Compiler.Program.Main(String[] args) 
in /Users/jamesrandall/code/functionMonkey/library/Source/FunctionMonkey.Compiler/Program.cs:line 56 | Blueprint.Forms.Api | [...].nuget\packages\functionmonkey.compiler\4.0.56-beta.4\build\netstandard1.0\FunctionMonkey.Compiler.targets
@AKomyshan
Copy link
Contributor

AKomyshan commented Apr 30, 2020

Could you please expand a little more information to reproduce your issue? Base class for what for the commands or for the command handlers?

Your project that you publish in NuGet refers Microsoft. Extensions.Dependency Injection.Abstractions or FunctionMonkey nuget packages?

@TylerBrinks
Copy link
Author

Typically each Azure Function would implement IFunctionAppConfiguration and fill in the Build method with function configurations to make use of FunctionMonkey. In my case, they're mostly Http Route functions with a handful of ServiceBus topic triggers.

Our particular setup is using MediatR along with several service registrations that are common to all the Azure Function projects. Because we have multiple Azure Functions, we created a base class that abstracts the common setup like logging, validation, and pipeline creation.

It looks like this for any given Azure Function:

public class FunctionAppConfiguration : ApiBootstrap
{
...
}

public class ApiBootstrap : IFunctionAppConfiguration
{
     public void Build(IFunctionHostBuilder builder)
     {
            builder.Setup(services =>
            {
              ....
            }
    }
}

Originally both classes were part of the same solution. Since then, we've moved our core libraries to a NuGet feed. The ApiBootstrap (and several project dependencies and service implementations) are now referenced in the project via NuGet instead of a direct project reference.

After that one change, the FunctionMoneky compiler isn't able to compile the functions. We've traced it down to any external class or interface that's now referenced in a NuGet package. Keep in mind, these classes haven't changed at all; only the way they're referenced in each project has changed.

It appears that the FunctionMonkey target can't compile the code when it tries to find implementations.

For example, we have a class called DatabaseInitializer that implements an interface IDatabaseInitializer that run when a function starts up. The interface is shared via one of our NuGet packages. When we compile, we get the following error:

Unexpected error: ReflectionTypeLoadException: Unable to load one or more of the requested types.Could not load file or assembly 'Microsoft.Extensions.DependencyInjection.Abstractions, Version=3.1.3.0, Culture=neutral, PublicKeyToken=adb9793829ddae60'. 
Could not find or load a specific file. (0x80131621)Unable to load types:    Blueprint.Forms.Api.IDatabaseInitializer    Blueprint.Forms.Api.DatabaseInitializer    Blueprint.Forms.Api.DatabaseInitializer+<SeedAsync>d__3    Blueprint.Forms.Api.DatabaseInitializer+<SeedData>d__4    Blueprint.Forms.Api.FunctionAppConfiguration+<>c    null type 
in ReflectionTypeLoadExceptionWith errors:    FileLoadException: Could not load file or assembly 'Microsoft.Extensions.DependencyInjection.Abstractions, Version=3.1.3.0, Culture=neutral, PublicKeyToken=adb9793829ddae60'. 
Could not find or load a specific file. (0x80131621)   at FunctionMonkey.ConfigurationLocator.Find[TType](Assembly assembly) 
in /Users/jamesrandall/code/functionMonkey/library/Source/FunctionMonkey/ConfigurationLocator.cs:line 202   at FunctionMonkey.ConfigurationLocator.Scan[TType](MethodInfo& linkBackInfo, TType& findConfiguration)
 in /Users/jamesrandall/code/functionMonkey/library/Source/FunctionMonkey/ConfigurationLocator.cs:line 155   at FunctionMonkey.ConfigurationLocator.Find[TType]() 
in /Users/jamesrandall/code/functionMonkey/library/Source/FunctionMonkey/ConfigurationLocator.cs:line 129   at FunctionMonkey.ConfigurationLocator.FindFunctionAppHost(Assembly assembly) 
in /Users/jamesrandall/code/functionMonkey/library/Source/FunctionMonkey/ConfigurationLocator.cs:line 18   at FunctionMonkey.Compiler.Core.Compiler.Compile() 
in /Users/jamesrandall/code/functionMonkey/library/Source/FunctionMonkey.Compiler.Core/Compiler.cs:line 57   at FunctionMonkey.Compiler.Program.Main(String[] args) 
in /Users/jamesrandall/code/functionMonkey/library/Source/FunctionMonkey.Compiler/Program.cs:line 56 | Blueprint.Forms.Api | C:\Users\Tyler\.nuget\packages\functionmonkey.compiler\4.0.56-beta.4\build\netstandard1.0\FunctionMonkey.Compiler.targets

Note that the error says it can't find Blueprint.Forms.Api.IDatabaseInitializer.

When we remove the code that registers the the instance, we get a slightly different, but still failing error:

Unexpected error: ReflectionTypeLoadException: Unable to load one or more of the requested types.Could not load file or assembly 'Microsoft.Extensions.DependencyInjection.Abstractions, Version=3.1.3.0, Culture=neutral, PublicKeyToken=adb9793829ddae60'. 
Could not find or load a specific file. (0x80131621)Unable to load types:    Blueprint.Forms.Api.FunctionAppConfiguration+<>c    null type 
in ReflectionTypeLoadExceptionWith errors:    FileLoadException: Could not load file or assembly 'Microsoft.Extensions.DependencyInjection.Abstractions, Version=3.1.3.0, Culture=neutral, PublicKeyToken=adb9793829ddae60'. 
Could not find or load a specific file. (0x80131621)   at FunctionMonkey.ConfigurationLocator.Find[TType](Assembly assembly) 
in /Users/jamesrandall/code/functionMonkey/library/Source/FunctionMonkey/ConfigurationLocator.cs:line 202   at FunctionMonkey.ConfigurationLocator.Scan[TType](MethodInfo& linkBackInfo, TType& findConfiguration) 
in /Users/jamesrandall/code/functionMonkey/library/Source/FunctionMonkey/ConfigurationLocator.cs:line 155   at FunctionMonkey.ConfigurationLocator.Find[TType]() 
in /Users/jamesrandall/code/functionMonkey/library/Source/FunctionMonkey/ConfigurationLocator.cs:line 129   at FunctionMonkey.ConfigurationLocator.FindFunctionAppHost(Assembly assembly) in /Users/jamesrandall/code/functionMonkey/library/Source/FunctionMonkey/ConfigurationLocator.cs:line 18   at FunctionMonkey.Compiler.Core.Compiler.Compile() 
in /Users/jamesrandall/code/functionMonkey/library/Source/FunctionMonkey.Compiler.Core/Compiler.cs:line 57   at FunctionMonkey.Compiler.Program.Main(String[] args) 
in /Users/jamesrandall/code/functionMonkey/library/Source/FunctionMonkey.Compiler/Program.cs:line 56 | Blueprint.Forms.Api | C:\Users\Tyler\.nuget\packages\functionmonkey.compiler\4.0.56-beta.4\build\netstandard1.0\FunctionMonkey.Compiler.targets

In this case, the FunctionMonkey compiler still cannot find the type that the FunctionAppConfiguration inherits from.

Again, when all the code is in a single project it works fine. But moving base classes to a NuGet package is enough to cause FunctionMonkey to fail during compilation.

@TylerBrinks
Copy link
Author

Could you please expand a little more information to reproduce your issue? Base class for what for the commands or for the command handlers?

Your project that you publish in NuGet refers Microsoft. Extensions.Dependency Injection.Abstractions or FunctionMonkey nuget packages?

Yes, one of the NuGet packages references FunctionMonkey as a NuGet project reference

@TylerBrinks
Copy link
Author

I pulled down the FunctionMonkey source and ran against an empty project with the same error. That's good news; it's not hard to reproduce.

The code fails in FunctionMonkey.Compiler when loading assemblies even though the assembly is there in the correct directory

Specifically, the failure is thrown by Assembly referencedAssembly = context.LoadFromAssemblyPath(path); when loading Microsoft.Extensions.DependencyInjection.Abstractions

AssemblyLoadContext.Default.Resolving += (context, name) =>
                    {
                        string path = Path.Combine(outputBinaryDirectory, $"{name.Name}.dll");
                        //string path = $"{outputBinaryDirectory}\\{name.Name}.dll";
                        if (File.Exists(path))
                        {
                            Assembly referencedAssembly = context.LoadFromAssemblyPath(path);
                            return referencedAssembly;
                        }                
                        return null;
                    };

@TylerBrinks
Copy link
Author

TylerBrinks commented May 1, 2020

Uncovering more as I dig deeper. I found a couple odd behaviors I can't yet explain.

First, the assembly resolving fails outright to load the Microsoft.Extensions.DependencyInjection.Abstractions DLL in the Resolving lambda. I modified it as follows to allow the assembly to load, and so far it seems to work:

AssemblyLoadContext.Default.Resolving += (context, name) =>
                    {
                        string path = Path.Combine(outputBinaryDirectory, $"{name.Name}.dll");
                        //string path = $"{outputBinaryDirectory}\\{name.Name}.dll";
                        if (File.Exists(path))
                        {
                            Assembly referencedAssembly = null;
                            try
                            {
                                referencedAssembly = context.LoadFromAssemblyPath(path);
                            }
                            catch
                            {
                                return Assembly.LoadFile(path);
                            }
                            return referencedAssembly;
                        }
                        return null;
                    };

That modification at least resolves and successfully loads the assembly, but it's not sufficient to get the compilation to work.

What follows after assembly and type loading (i.e. after the call to FindFunctionAppHost) is something I can't yet explain. I'm not at all new to reflection, but this is something I've not encountered before.

Around line 90 in the compiler, the compiler attempts to invoke configuration.Build(builder) which was working for me when all code was in a single project. Remember, the Build method is in a base class that in the NuGet package's library.

Now, when the Build is invoked, reflection cannot find the method and throws an exception when attempting to call it. The strange thing is that the method is there. To test further, I added a method called "BuildTest" to my Azure function, and then instead of calling configuration.Build(builder) , I instead used reflection to invoke my new method. In my dummy method, I'm simply calling the base class' Build method with the IFunctionHostBuilder argument.

The temporary function looks like this:

public void BuildTest(IFunctionHostBuilder builder)
{
    base.Build(builder);
}

Here's where I'm stumped - I set a breakpoint on the base.Build(...) line, and when I try to call the base class' Build method, a missing method exception is thrown. It makes no sense to me because the method is clearly there. The code compiles. Intellisense works and see the method and points to the symbols included in the NuGet package. It seems like the process of resolving assembly dependencies isn't loading the base class' DLL as a dependency and is therefore failing to call the base class Build(..) method.

I will keep digging, but this one is a first for me. It may have to do with .Net Core and package management in some way. All the projects are .Net Core 3.1; perhaps there's something about that which prevents the method invocation.

@mmeckes
Copy link

mmeckes commented May 1, 2020

Not sure if this is related, but I had a similar issue when upgrading to Microsoft.NET.Sdk.Functions 3.0.6 with various nuget packages failing with "Unexpected error: Could not load file or assembly" errors.

I was able to resolve them by adding
<_FunctionsSkipCleanOutput>true</_FunctionsSkipCleanOutput>
as a property in my cproj file.

@AKomyshan
Copy link
Contributor

I tested my project with a version of Microsoft.NET.Sdk.Functions greater than 3.0.3 and started getting the same error "Could not load file or assembly"... @TylerBrinks could you please describe which version of Microsoft.NET.Sdk.Functions you are using?

@TylerBrinks
Copy link
Author

TylerBrinks commented May 1, 2020

I tested my project with a version of Microsoft.NET.Sdk.Functions greater than 3.0.3 and started getting the same error "Could not load file or assembly"... @TylerBrinks could you please describe which version of Microsoft.NET.Sdk.Functions you are using?

I'm using 3.0.6 but I'll try the 3.0.6 release.

@TylerBrinks
Copy link
Author

Not sure if this is related, but I had a similar issue when upgrading to Microsoft.NET.Sdk.Functions 3.0.6 with various nuget packages failing with "Unexpected error: Could not load file or assembly" errors.

I was able to resolve them by adding
<_FunctionsSkipCleanOutput>true</_FunctionsSkipCleanOutput>
as a property in my cproj file.

I made that change and something changed. I'm able to access my base class' methods now from the derived class. I still can't invoke them from the FunctionMonkey compiler, but it's a step closer.

@TylerBrinks
Copy link
Author

I double checked my original solution (as opposed to my scratch project in the FunctionMonkey solution) and I was already on 3.0.6 and happened to have the <_FunctionsSkipCleanOutput>true</_FunctionsSkipCleanOutput> already in place.

One fact that may have some relevance, when the assemblies are loaded mine are all .NETCoreApp,v=3.1 where the Microsoft dependency injection assembly is .NETStandard,v2.0

@AKomyshan
Copy link
Contributor

@TylerBrinks Try to downgrade Microsoft.NET.Sdk.Functions to 3.0.3 version.

@TylerBrinks
Copy link
Author

@TylerBrinks Try to downgrade Microsoft.NET.Sdk.Functions to 3.0.3 version.

Just did that in both my actual project and a scratch project in the FunctionMonkey code with same error. I'm not sure if much will work if the compiler can't get past the inability to load the Microsoft.Extensions.DependencyInjection.Abstractions library.

@MarkusBernhardt
Copy link
Contributor

MarkusBernhardt commented May 3, 2020

We are using FunctionMonkey built with Azure Pipelines in production. I believe I can remember these problems. The root cause was that parts of the required libraries are loaded from the SDK and in an Azure Pipeline build this works somehow different from a local build.

  1. It is important you install the correct .Net Core SDK on the build machine. Use the task: "Use .NET Core", choose "SDK" and enter the exact version. We use 3.1.201.

  2. Add to all your .csproj files:

<ItemGroup>
    <FrameworkReference Include="Microsoft.AspNetCore.App" />
</ItemGroup>

Our full YAML pipeline file as reference (some things overwritten with xxxxxxxxxxxxxxxx):

pool:
name: Azure Pipelines
demands:

  • msbuild
  • visualstudio
  • vstest

variables:
BuildPlatform: 'any cpu'
BuildConfiguration: 'debug'

steps:

  • task: UseDotNet@2
    displayName: 'Use .Net Core sdk 3.1.201'
    inputs:
    version: 3.1.201

  • task: NuGetToolInstaller@0
    displayName: 'Use NuGet 4.x'
    inputs:
    versionSpec: 4.x

  • task: NuGetCommand@2
    displayName: 'NuGet restore'
    inputs:
    restoreSolution: 'xxxxxxxxxxxxxxxx.sln'
    vstsFeed: 'xxxxxxxxxxxxxxxx'

  • task: VSBuild@1
    displayName: 'Build solution'
    inputs:
    solution: 'xxxxxxxxxxxxxxxx.sln'
    msbuildArgs: '/p:DeployOnBuild=true /p:WebPublishMethod=Package /p:PackageAsSingleFile=true /p:SkipInvalidConfigurations=true /p:PackageLocation="$(build.artifactstagingdirectory)\"'
    platform: '$(BuildPlatform)'
    configuration: '$(BuildConfiguration)'

  • task: VSTest@2
    displayName: 'Test assemblies'
    inputs:
    testAssemblyVer2: |
    *test.dll
    !
    *TestAdapter.dll
    !
    \obj**
    !
    **.Test.Base.dll
    platform: '$(BuildPlatform)'
    configuration: '$(BuildConfiguration)'
    diagnosticsEnabled: True

  • task: PublishSymbols@2
    displayName: 'Publish symbols path'
    inputs:
    PublishSymbols: false

  • task: PublishBuildArtifacts@1
    displayName: 'Publish Artifact: xxxxxxxxxxxxxxxx'
    inputs:
    ArtifactName: xxxxxxxxxxxxxxxx

Hope this helps somehow and good luck,
Markus

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants