xamarin / xamarin-macios

.NET for iOS, Mac Catalyst, macOS, and tvOS provide open-source bindings of the Apple SDKs for use with .NET managed languages such as C#
Other
2.47k stars 513 forks source link

iOS SDK fails to build .ipa when Build target is built first #20958

Open AArnott opened 3 months ago

AArnott commented 3 months ago

The .ipa package is not constructed, and an incomplete log message is emitted claiming success, when an iOS project is built with Build then Publish target order.

Repro steps

  1. Create a new Maui project.
  2. Connect Visual Studio to a local mac.
  3. Set the debug target dropdown to an iPhone emulator.
  4. Build the project. This should succeed.
  5. Open up the Developer PowerShell terminal and proceed with the following commands.
  6. dotnet build -f net8.0-ios -r ios-arm64 succeeds and builds just the .dll
  7. dotnet build -f net8.0-ios -r ios-arm64 -t:publish succeeds and creates the .ipa package:

    Created the package: bin\Debug\net8.0-ios\ios-arm64\publish\MauiApp2.ipa

  8. But now build such that the Build target runs before the publish target: dotnet build -f net8.0-ios -r ios-arm64 -t:build,publish

Expected

The build succeeds as well as the publish target, which constructs the .ipa package.

Actual

The build succeeds, but the Publish target skips building the .ipa package, yet prints an incomplete message trying to say that it did (though it didn't):

MauiApp2 -> C:\temp\MauiApp2\MauiApp2\bin\Debug\net8.0-ios\ios-arm64\MauiApp2.dll Created the package:

msbuild.binlog.zip

Analysis

This happens because the Build target leads the _CoreCreateIpa target to execute due to target dependencies, but that target is skipped because its condition ('$(BuildIpa)' == 'true') is false. BuildIpa is a property that is only set to true by the _PrePublish target. But that target doesn't run as part of Build -- only as part of Publish. So as long as the Publish target is listed first on the command line or as the only target, it works because _PrePublish executes before _CoreCreateIpa. But it is vulnerable to target execution order and publish never happens if Build is also built (first).

This is particularly a problem when a repo is built through traversal projects (Microsoft.Build.Traversal SDK) because even if I ask the dirs.proj to build targets in "Publish,Build" order, the traversal only propagates those targets to the iOS/Maui project in Build,Publish order.

In general, MSBuild .targets files should be authored such that if the order in which the Targets execute is significant, that order is guaranteed rather than just relying on chance. While docs may suggest publishing an iOS app with dotnet publish, doing so leads to overbuilding a repo when the iOS project has already been largely built (and signed) by a prior step in the build. Builds can be much faster when they never build the same target twice, and traversal projects have allowed me to do that.

Workaround

I've hacked a workaround for the time being that ensures target order matches the Microsoft.iOS.SDK's assumptions. It relies on the fact that I set the CI msbuild property to true in CI builds where I build traversal projects.

The workaround is to add this target to my iOS project:

  <Target Name="PrePublishBeforeBuildHack" Condition=" '$(CI)' == 'true' ">
    <PropertyGroup>
      <!-- Hack workaround to ensure the _PrePublish target runs before the _CoreCreateIpa target runs (and skips). -->
      <CreateIpaDependsOn>_PrePublish;$(CreateIpaDependsOn)</CreateIpaDependsOn>
    </PropertyGroup>
  </Target>
rolfbjarne commented 3 months ago

A trivial workaround would be to set BuildIpa yourself:

dotnet build -f net8.0-ios -r ios-arm64 -p:BuildIpa=true

since Publish is pretty much just Build with BuildIpa=true.

You might have to set IpaPackageDir to some value too, to get the .ipa in the directory where you expect it:

dotnet build -f net8.0-ios -r ios-arm64 -p:BuildIpa=true -p:IpaPackageDir=publishdir

baskren commented 2 months ago

Wow. This is so much not what is shown in the official docs: https://learn.microsoft.com/en-us/dotnet/maui/ios/deployment/publish-cli?view=net-maui-8.0

I came here because the official documented approach does not produce the .ipa. The method give here does. Thank you @AArnott .