dotnet / sdk

Core functionality needed to create .NET Core projects, that is shared between Visual Studio and CLI
https://dot.net/core
MIT License
2.65k stars 1.06k forks source link

MAUI Apps cannot be restored in locked mode. #41317

Open RobTF opened 7 months ago

RobTF commented 7 months ago

Description

When specifying locked mode for project restore an app or library using .NET MAUI will always fail. This is a fairly standard thing for CI builds to make them more reproducible and all non MAUI apps/libraries work fine.

Steps to Reproduce

Add the following into a project;

<RestoreLockedMode>true</RestoreLockedMode>

Note the failures which appear to suggest that the MAUI build tools add two packages named Microsoft.NET.ILLink.Analyzers and Microsoft.NET.ILLink.Tasks. The locked mode restore detects a change and fails as the build does not match the NuGet lock file.

Link to public reproduction project repository

No response

Version with bug

8.0.6

Is this a regression from previous behavior?

Not sure, did not test other versions

Last version that worked well

Unknown/Other

Affected platforms

iOS, Android

Affected platform versions

All versions of iOS, Android. Probably all others too.

Did you find any workaround?

Disable locked restore mode.

Relevant log output

Build FAILED.

       "D:\a\1\s\MyProject\MyProject.csproj" (Restore target) (1) ->
       (Restore target) -> 
         D:\a\1\s\MyProject\MyProject.csproj : error NU1004: The package references have changed for net7.0-ios16.1. Lock file's package references:
            CommunityToolkit.Maui:[4.0.0, ),
            Microsoft.CodeAnalysis.NetAnalyzers:[8.0.0, ),
            Microsoft.Extensions.Configuration:[7.0.0, ),
            Microsoft.Extensions.DependencyInjection:[7.0.0, ),
            Microsoft.Extensions.Logging:[7.0.0, ),
            Microsoft.Maui.Graphics:[7.0.101, ),
            Microsoft.NET.ILLink.Analyzers:[7.0.100-1.23211.1, ),
            Microsoft.NET.ILLink.Tasks:[7.0.100-1.23211.1, ),
            StyleCop.Analyzers.Unstable:[1.2.0.556, ),
        project's package references:
            CommunityToolkit.Maui:[4.0.0, ),
            Microsoft.CodeAnalysis.NetAnalyzers:[8.0.0, ),
            Microsoft.Extensions.Configuration:[7.0.0, ),
            Microsoft.Extensions.DependencyInjection:[7.0.0, ),
            Microsoft.Extensions.Logging:[7.0.0, ),
            Microsoft.Maui.Graphics:[7.0.101, ),
            StyleCop.Analyzers.Unstable:[1.2.0.556, ).

        The packages lock file is inconsistent with the project dependencies so restore can't be run in locked mode. Disable the RestoreLockedMode MSBuild property or pass an explicit --force-evaluate option to run restore to update the lock file.

    0 Warning(s)
    1 Error(s)
jaosnz-rep commented 5 months ago

Verified on 8.0.14, add <RestoreLockedMode>true</RestoreLockedMode> in csproj, then Cl build can be successful.

RobTF commented 5 months ago

Still getting a problem, albeit a different problem; Builds fine locally but fails on DevOps pipeline.

     "D:\a\1\s\Myproj\Myproj.csproj" (Restore target) (1) ->
       (Restore target) -> 
         D:\a\1\s\Myproj\Myproj.csproj : error NU1004: The package reference Microsoft.NET.ILLink.Tasks version has changed from [8.0.2, ) to [8.0.3, ).The packages lock file is inconsistent with the project dependencies so restore can't be run in locked mode. Disable the RestoreLockedMode MSBuild property or pass an explicit --force-evaluate option to run restore to update the lock file.

.csproj content;

<Project Sdk="Microsoft.NET.Sdk">

    <PropertyGroup>
        <TargetFrameworks>net8.0;net8.0-android;net8.0-ios</TargetFrameworks>
        <UseMaui>true</UseMaui>
        <SingleProject>true</SingleProject>
        <ImplicitUsings>enable</ImplicitUsings>

        <GeneratePackageOnBuild>false</GeneratePackageOnBuild>
        <Product>My Library</Product>
        <Description>Cool library.</Description>
        <PublishRepositoryUrl>true</PublishRepositoryUrl>
        <EmbedUntrackedSources>true</EmbedUntrackedSources>
        <AllowedOutputExtensionsInPackageBuildOutputFolder>$(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb</AllowedOutputExtensionsInPackageBuildOutputFolder>

        <SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'ios'">14.2</SupportedOSPlatformVersion>
        <SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'android'">21.0</SupportedOSPlatformVersion>
    </PropertyGroup>

    <ItemGroup>
        <Compile Remove="Platforms\**" />
        <EmbeddedResource Remove="Platforms\**" />
        <MauiCss Remove="Platforms\**" />
        <MauiXaml Remove="Platforms\**" />
        <None Remove="Platforms\**" />
    </ItemGroup>

    <ItemGroup>
        <PackageReference Include="Microsoft.Maui.Controls" Version="8.0.14" />
        <PackageReference Include="Microsoft.Maui.Controls.Compatibility" Version="8.0.14" />
        <PackageReference Include="CommunityToolkit.Maui" Version="7.0.1" />
    </ItemGroup>

</Project>

Directory.build.props

<Project>
    <PropertyGroup>
        <Authors>Rob</Authors>
        <DocumentationFile>bin\$(Configuration)\$(TargetFramework)\$(MSBuildProjectName).xml</DocumentationFile>
        <LangVersion>11.0</LangVersion>
    </PropertyGroup>

    <PropertyGroup>
        <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
        <WarningsAsErrors />
        <Nullable>enable</Nullable>
        <AnalysisLevel>latest-recommended</AnalysisLevel>
        <AnalysisMode>AllEnabledByDefault</AnalysisMode>
        <RestorePackagesWithLockFile>true</RestorePackagesWithLockFile>
        <DisableImplicitNuGetFallbackFolder>true</DisableImplicitNuGetFallbackFolder>
        <ContinuousIntegrationBuild Condition="'$(TF_BUILD)' == 'true'">True</ContinuousIntegrationBuild>
        <Deterministic Condition="'$(ContinuousIntegrationBuild)' == 'true'">true</Deterministic>
        <RestoreLockedMode Condition="'$(ContinuousIntegrationBuild)' == 'true'">true</RestoreLockedMode>
    </PropertyGroup>

    <ItemGroup>
        <AdditionalFiles Include="$(MSBuildThisFileDirectory)stylecop.json" />
    </ItemGroup>

    <ItemGroup>
        <PackageReference Include="Microsoft.CodeAnalysis.NetAnalyzers" Version="8.0.0">
            <PrivateAssets>all</PrivateAssets>
            <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
        </PackageReference>
        <PackageReference Include="StyleCop.Analyzers.Unstable" Version="1.2.0.556">
            <PrivateAssets>all</PrivateAssets>
            <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
        </PackageReference>
    </ItemGroup>
</Project>

The devops pipeline file;

trigger:
- master
- develop
- features/*

variables:
  - name: NUGET_PACKAGES
    value: $(Pipeline.Workspace)/.nuget/packages

  - name: environment
    ${{ if eq(variables['Build.SourceBranch'], 'refs/heads/master') }}:
      value: 'production'
    ${{ else }}:
      value: 'development'

  - name: buildConfiguration
    ${{ if eq(variables.environment, 'development') }}:
      value: 'Debug'
    ${{ else }}:
      value: 'Release'

  # Agent VM image name
  - name: vmImageName
    value: 'windows-latest'

stages:
- stage: Build
  displayName: Build
  jobs:
  - job: build_and_pack_all
    displayName: "Build"
    pool:
        vmImage: $(vmImageName)

    steps:
    - task: PowerShell@2
      displayName: "Set Build Configuration"
      inputs:
        targetType: 'inline'
        script: |
          $branch = "$(Build.SourceBranchName)"
          if($branch -eq "master")
          {
              Write-Host "##vso[task.setvariable variable=buildConfiguration]Release"
          }
          else
          {
              Write-Host "##vso[task.setvariable variable=buildConfiguration]Debug"
          }

    - task: gitversion/setup@0
      displayName: "Setup Git versioning"
      inputs:
        versionSpec: '5.x'

    - task: gitversion/execute@0
      displayName: "Execute Git versioning"
      inputs:
        useConfigFile: true
        configFilePath: 'GitVersion.yml'

    - task: NuGetToolInstaller@1
      displayName: "Use Nuget 6.x"
      inputs:
        versionSpec: '6.x'

    - task: UseDotNet@2
      displayName: "Install .NET SDK"
      inputs:
        version: 8.x
        performMultiLevelLookup: true

    - task: Bash@3
      displayName: Install .NET MAUI
      inputs:
        targetType: 'inline'
        script: |
          dotnet nuget locals all --clear 
          dotnet workload install android ios maccatalyst maui-tizen macos maui --source https://aka.ms/dotnet6/nuget/index.json --source https://api.nuget.org/v3/index.json

    - task: JavaToolInstaller@0
      displayName: 'Use Java 17'
      inputs:
       versionSpec: 17
       jdkArchitectureOption: x64
       jdkSourceOption: PreInstalled

    - task: Cache@2
      displayName: "Restore NuGet Package Cache"
      inputs:
        key: 'nuget | "$(Agent.OS)" | **/packages.lock.json, !**/bin/**'
        path: '$(NUGET_PACKAGES)'
        restoreKeys: |
          nuget | "$(Agent.OS)"
          nuget
        cacheHitVar: 'CACHE_RESTORED'

    - task: DotNetCoreCLI@2
      displayName: "Restore NuGet Packages"
      inputs:
        command: 'restore'
        projects: '**/Myproj.*.csproj'
        feedsToUse: 'select'
        vstsFeed: 'REDACTED'

    - task: DotNetCoreCLI@2
      displayName: "Build projects"
      inputs:
        command: 'build'
        projects: '**/Myproj.*.csproj'
        arguments: '--no-restore --configuration $(buildConfiguration) /p:Version=$(GitVersion.NuGetVersion)' 
        versioningScheme: byBuildNumber

    - task: CopyFiles@2
      displayName: 'Copy symbols'
      inputs:
        contents: '**/bin/**/*.pdb'
        targetFolder: '$(Build.ArtifactStagingDirectory)/symbols'

    - task: DotNetCoreCLI@2
      displayName: 'Run Unit Tests'
      inputs:
        command: test
        projects: '**/*Tests.csproj'
        arguments: '--no-build --configuration $(buildConfiguration)'
        nobuild: true

    - task: DotNetCoreCLI@2
      displayName: "Nuget Pack"
      inputs:
        command: 'pack'
        nobuild: true
        packagesToPack: '**/Myproj*.csproj'
        includesymbols: true
        configuration: '$(buildConfiguration)'
        outputDir: '$(Build.ArtifactStagingDirectory)/package'
        arguments: '--no-restore'
        versioningScheme: byEnvVar 
        versionEnvVar: GitVersion.NuGetVersion

    - task: PublishPipelineArtifact@1
      displayName: "Publish Nuget package artifacts"
      inputs:
        targetPath: $(Build.ArtifactStagingDirectory)/package
        artifact: 'package'
        publishLocation: 'pipeline'

    - task: PublishPipelineArtifact@1
      displayName: "Publish debug symbol artifacts"
      inputs:
        targetPath: '$(Build.ArtifactStagingDirectory)/symbols'
        artifact: 'symbols'
        publishLocation: 'pipeline'

- stage: Deploy
  dependsOn: Build
  condition: and(succeeded(), ne(variables['Build.Reason'], 'PullRequest'))
  displayName: Deploy
  jobs:
  - deployment: 'Deploy'
    displayName: "Deploy"
    environment: ${{ variables.environment }}
    pool:
      vmImage: $(vmImageName)
    strategy:
      runOnce:
        deploy:
          steps:
            - task: PublishSymbols@2
              displayName: "Publish Symbols"
              inputs:
                SearchPattern: '$(Pipeline.Workspace)/symbols/**/*.pdb'
                SymbolServerType: 'TeamServices'

            - task: NuGetCommand@2
              displayName: "Nuget Push"
              inputs:
                command: 'push'
                arguments: '-SkipDuplicate'
                packagesToPush: '$(Pipeline.Workspace)/**/Myproj*.nupkg;!$(Pipeline.Workspace)/**/Myproj*.symbols.nupkg'
                nuGetFeedType: 'internal'
                publishVstsFeed: 'REDACTED'
                allowPackageConflicts: false

            - task: rvo-vsts-promotepackage-task@3
              condition: and(succeeded(), eq('${{ variables.environment }}', 'production'))
              displayName: "Add packages to release view"
              inputs:
                feed: 'REDACTED'
                inputType: 'packageFiles'
                packagesDirectory: '$(Pipeline.Workspace)'
                packagesPattern: |
                  **/*.nupkg
                  !**/*.symbols.nupkg
                  **/*.tgz
                releaseView: 'REDACTED'
nil-vr commented 2 months ago

It's the same with wasm-tools. dotnet workload restore installs the latest version of the workload and a Microsoft.NET.ILLink.Tasks package dependency leaks from the workload into the packages.lock.json file. This creates a package restore error every time the workload developers change their dependency.