Azure / Azure-Functions

1.12k stars 199 forks source link

Azure Function Projects Do Not Support Incremental Builds #789

Open Mike-E-angelo opened 6 years ago

Mike-E-angelo commented 6 years ago

When I do a CTRL-SHIFT-B to build my solution, all projects that reference Microsoft.NET.Sdk.Functions get included in the build output every time. Is this a known issue? I did look around and nothing obvious turned up, so apologies if this is already captured somewhere.

This is my generated output in a solution with 20 projects. Do note that I am on .NET Core Preview 2:

1>------ Build started: Project: Project.Services.Application, Configuration: Release Any CPU ------
1>You are working with a preview version of the .NET Core SDK. You can define the SDK version via a global.json file in the current project. More at https://go.microsoft.com/fwlink/?linkid=869452
1>Project.Services.Application -> D:\Project\Project.Services.Application\bin\Release\netstandard2.0\bin\Project.Services.Application.dll
========== Build: 1 succeeded, 0 failed, 20 up-to-date, 0 skipped ==========
kashimiz commented 6 years ago

Could you provide more information about the steps that you're following? Are you trying to exclude the projects that reference Microsoft.NET.Sdk.Functions from your build, and if so, how? What behavior are you expecting?

Mike-E-angelo commented 6 years ago

Hi @kashimiz thank you for your reply. If I open up a new Azure Function application v2 from scratch in Visual Studio 2017 15.7 Preview, and simply build it. This is the output I see:

1>------ Build started: Project: FunctionApp1, Configuration: Debug Any CPU ------
1>You are working with a preview version of the .NET Core SDK. You can define the SDK version via a global.json file in the current project. More at https://go.microsoft.com/fwlink/?linkid=869452
1>FunctionApp1 -> c:\users\..\source\repos\FunctionApp1\FunctionApp1\bin\Debug\netstandard2.0\bin\FunctionApp1.dll
========== Build: 1 succeeded, 0 failed, 0 up-to-date, 0 skipped ==========

If I build again, this is what I see:

1>------ Build started: Project: FunctionApp1, Configuration: Debug Any CPU ------
1>You are working with a preview version of the .NET Core SDK. You can define the SDK version via a global.json file in the current project. More at https://go.microsoft.com/fwlink/?linkid=869452
1>FunctionApp1 -> c:\users\..\source\repos\FunctionApp1\FunctionApp1\bin\Debug\netstandard2.0\bin\FunctionApp1.dll
========== Build: 1 succeeded, 0 failed, 0 up-to-date, 0 skipped ==========

Conversely, if I do the same with a new console application, this is what I see:

1>------ Build started: Project: ConsoleApp1, Configuration: Debug Any CPU ------
1>You are working with a preview version of the .NET Core SDK. You can define the SDK version via a global.json file in the current project. More at https://go.microsoft.com/fwlink/?linkid=869452
1>ConsoleApp1 -> C:\Users\..\source\repos\ConsoleApp1\ConsoleApp1\bin\Debug\netcoreapp2.0\ConsoleApp1.dll
========== Build: 1 succeeded, 0 failed, 0 up-to-date, 0 skipped ==========

And on the 2nd attempt:

========== Build: 0 succeeded, 0 failed, 1 up-to-date, 0 skipped ==========

Please let me know if this helps clarify the issue and/or if you have any additional questions.

ColbyTresness commented 5 years ago

Is this still an issue? Sorry for the delay in responding.

Mike-E-angelo commented 5 years ago

I was wondering the same thing the other day, @ColbyTresness. 😅 Unfortunately, I have not been around Visual Studio much these days due to Real Life projects but should get into it in the next month or so after holidays are over.

I am OK with closing this for now and then revisiting it once I get the opportunity to try it again. 👍

LeGaryGary commented 5 years ago

I believe this is still an issue, it causes any projects that reference an azure functions project to rebuild every time too

Mike-E-angelo commented 5 years ago

Hello... yes I tried doing this with Visual Studio 2019 16.1.2 with the default Azure Function template, tried compiling a few times, and can confirm this is still occurring with each build:

1>------ Build started: Project: FunctionApp1, Configuration: Debug Any CPU ------
1>C:\Program Files\dotnet\sdk\3.0.100-preview5-011568\Sdks\Microsoft.NET.Sdk\targets\Microsoft.NET.RuntimeIdentifierInference.targets(157,5): message NETSDK1057: You are using a preview version of .NET Core. See: https://aka.ms/dotnet-core-preview
1>FunctionApp1 -> C:\users\..\FunctionApp1\FunctionApp1\bin\Debug\netcoreapp2.1\bin\FunctionApp1.dll
========== Build: 1 succeeded, 0 failed, 0 up-to-date, 0 skipped ==========
kashimiz commented 5 years ago

Hi @Mike-EEE,

From the Visual Studio team: this is expected behavior. Incremental build is currently not supported for Azure Functions projects because their output needs to be copied to the additional bin folder.

ColbyTresness commented 5 years ago

@kashimiz should we leave this open to track support for incremental build then?

Mike-E-angelo commented 5 years ago

Agreeing with @ColbyTresness... I've updated the title to be better reflective of the issue.

MaxGuernseyIII commented 4 years ago

Hi @Mike-EEE,

From the Visual Studio team: this is expected behavior. Incremental build is currently not supported for Azure Functions projects because their output needs to be copied to the additional bin folder.

Is this regarded as a fundamentally unsolvable problem or just a feature that isn't a high-enough priority to have been implemented, yet?

MaxGuernseyIII commented 4 years ago

I have a hypothesis about how to build a PR around this but I'm having difficulty locating the tests that validate the build targets. So I imagine the theory will be difficult to validate or refute. Any suggestions on where I look?

This feels like where the tests should live near this project: https://github.com/Azure/azure-functions-vs-build-sdk/tree/master/test/Microsoft.NET.Sdk.Functions.MSBuild.Tests

...but I don't think those tests will do the trick because I can't find anywhere that they load the .targets files that I think needs to change.

Anyway, I'll just post my thought, here, and someone who knows more about the system can tell me why I'm wrong.

It seems like the solution is to build the contents of IntermediateOutputPath correctly in the first place, then set up inputs and outputs on various targets as appropriate. I imagine that would require a few changes:

  1. Create tests for the incremental-build scenarios, wherever those live.
  2. Do the backup/restore trick for IntermediateOutputPath instead of OutDir and do it before/after the intermediate stuff is generated.
  3. Generate the rest of the materials into the IntermediateOutputPath (all the little functions.json files).
  4. Add inputs & outputs for the generation step (maybe touch a file that gets deleted just before copying files to the output directory?).
  5. Ensure that everything in the IntermediateOutputPath can be copied (there's something about PDBs not being copied correctly I saw in there?).

That's just a rough idea of what I would do. Before I go building a PR, I want to get some feedback from someone who really understands this codebase.

Is there something wrong with this idea? Are there existing tests for the build system I can use to get me started?

Mike-E-angelo commented 4 years ago

From the Visual Studio team: this is expected behavior. Incremental build is currently not supported for Azure Functions projects because their output needs to be copied to the additional bin folder.

I am not sure how this is different from any other project type? Every project creates deliverables that are ultimately copied to the bin folder. In this case, it would seem that the deliverables are created each and every time, without any necessary checking performed on differences between the latest output and what was previously created (if applicable).

What is it about Azure Function projects that they are not performing the necessary diligence to ensure a copy is performed each and every time?

atifaziz commented 3 years ago

A workaround I've been able to use for this is to change the project reference to conditionally exclude build assets:

<PackageReference Include="Microsoft.NET.Sdk.Functions" Version="3.0.7">
    <ExcludeAssets Condition="'$(ExcludeAzureFunctionsSdkBuildAssets)' == 'true'">build</ExcludeAssets>
</PackageReference>

The build assets from Microsoft.NET.Sdk.Functions (like *.prop and *.targets) are what really trump incremental building due to the build outputs (the cheese) being moved.

By default, the above condition won't do anything since ExcludeAzureFunctionsSdkBuildAssets won't be defined anywhere. You can then create a file sitting next to your project file, bearing the same name but with the .user extension and the following content:

<Project>
  <PropertyGroup>
    <ExcludeAzureFunctionsSdkBuildAssets>true</ExcludeAzureFunctionsSdkBuildAssets>
  </PropertyGroup>
</Project>

If your project is called Func.csproj, the file must be named Func.csproj.user. In you're using Git as your VCS, then you'll want to add *.user to your .gitignore. User-specific project files allow private customisations. If you have multiple projects using the Azure Function SDK, you can control optimising for incremental building per project.

Instead of *.user files, you can also centrally control and flip the ExcludeAzureFunctionsSdkBuildAssets switch for all projects in a solution by using Directory.Build.props. For example, you could add a Directory.Build.props to the root of your solution with the following content:

<Project>
  <Import Project="$(MSBuildThisFile).user" Condition="Exists('$(MSBuildThisFile).user')" />
</Project>

Again the above won't do anything by itself so you can safely check it into source control. However, what it will enable is creating a Directory.Build.props.user with the same content as shown earlier to (locally) disable build assets from the SDK:

<Project>
  <PropertyGroup>
    <ExcludeAzureFunctionsSdkBuildAssets>true</ExcludeAzureFunctionsSdkBuildAssets>
  </PropertyGroup>
</Project>

If you find that the above properties are not taking effect, redo a restore via dotnet restore or dotnet restore --force.

This technique described here is merely an optimisation to speed up your inner dev loop. Hope it helps.