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

[msbuild] Add @(XcodeProject) action #21232

Open
wants to merge 23 commits into
base: net9.0
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
6fb1013
[msbuild] Add @(MaciOSXcodeProject) action
pjcollins Sep 12, 2024
9c16cdf
Remove unused props
pjcollins Sep 12, 2024
7a5cc7f
Auto-format source code
Sep 12, 2024
d800655
Apply suggestions from code review
pjcollins Sep 13, 2024
0259cfe
Initial feedback
pjcollins Sep 13, 2024
140be1f
Auto-format source code
Sep 13, 2024
38f8bbf
Feedback, conditionally import new targets, fix tests
pjcollins Sep 17, 2024
6e1c2b3
[net9.0] Update dependencies from dotnet/sdk (#21085)
dotnet-maestro[bot] Sep 16, 2024
12cc5ee
Merge remote-tracking branch 'origin/net9.0' into dev/pjc/xcodeprojref
pjcollins Sep 17, 2024
5d9d89e
Auto-format source code
Sep 17, 2024
adb905e
Remove sharpie, update tests
pjcollins Sep 19, 2024
80590d7
Auto-format source code
Sep 19, 2024
28b6a88
Add docs
pjcollins Sep 19, 2024
7502a22
Add remaining tests
pjcollins Sep 19, 2024
e007778
Merge remote-tracking branch 'origin/net9.0' into dev/pjc/xcodeprojref
pjcollins Sep 19, 2024
dd55345
Merge branch 'net9.0' into dev/pjc/xcodeprojref
dalexsoto Sep 20, 2024
f704df4
Update tests/dotnet/UnitTests/XcodeProjectTests.cs
pjcollins Sep 20, 2024
af944b3
Auto-format source code
Sep 20, 2024
6f6167c
Apply test feedback
pjcollins Sep 20, 2024
c379fb5
Merge remote-tracking branch 'origin/net9.0' into dev/pjc/xcodeprojref
pjcollins Sep 20, 2024
a7ef8a2
Update loc comment
pjcollins Sep 20, 2024
fe25552
Create multitargeting test template based on first enabled platform
pjcollins Sep 23, 2024
446d6ef
Fix multitargeting test
pjcollins Sep 23, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,6 @@ provision-xcode.csx
mono_crash.*.json
*.binlog
.vscode
# Xcode
xcuserdata/
.build/
39 changes: 39 additions & 0 deletions docs/build-apps/build-items.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
---
title: .NET for iOS, Mac Catalyst, macOS, and tvOS Build Items
description: .NET for iOS, Mac Catalyst, macOS, and tvOS Build Items
ms.date: 09/19/2024
---

# Build Items

Build items control how .NET for iOS, Mac Catalyst, macOS, and tvOS
application or library projects are built.

## XcodeProject

`<XcodeProject>` can be used to build and consume the outputs
of Xcode framework projects created in Xcode or elsewehere.

The `Include` metadata should point to the path of the XCODEPROJ file to be built.

```xml
<ItemGroup>
<XcodeProject Include="path/to/MyProject.xcodeproj" SchemeName="MyLibrary" />
</ItemGroup>
```

The following MSBuild metadata are supported:

- `%(SchemeName)`: The name of the build scheme or target that should be used to build the project.

- `%(Configuration)`: The name of the configuration to use to build the project.
The default value is `Release`.

- `%(CreateNativeReference)`: Output XCFRAMEWORK files will be added as a `@(NativeReference)` to the project.
Metadata supported by `@(NativeReference)` like `%(Kind)`, `%(Frameworks)`, or `%(SmartLink)` will be forwarded if set.
The default value is `true`.

- `%(OutputPath)`: Can be set to override the XCARCHIVE and XCFRAMEWORK output path of the Xcode project.
The default value is `$(IntermediateOutputPath)xcode/{SchemeName}-{Hash}`.

This build action was introduced in .NET 9.
32 changes: 32 additions & 0 deletions docs/build-apps/build-properties.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
---
title: .NET for iOS, Mac Catalyst, macOS, and tvOS Build Properties
description: .NET for iOS, Mac Catalyst, macOS, and tvOS Build Properties
ms.date: 09/19/2024
---

# Build Properties

MSBuild properties control the behavior of the
[targets](build-targets.md).
They're specified within the project file, for example **MyApp.csproj**, within
an MSBuild PropertyGroup.

## MaciOSPrepareForBuildDependsOn

A semi-colon delimited property that can be used to extend the build process.
MSBuild targets added to this property will execute early in the build for both
application and library project types. This property is empty by default.

Example:

```xml
<PropertyGroup>
<MaciOSPrepareForBuildDependsOn>MyCustomTarget</MaciOSPrepareForBuildDependsOn>
</PropertyGroup>

<Target Name="MyCustomTarget" >
<Message Text="Running target: 'MyCustomTarget'" Importance="high" />
</Target>
```

This property was introduced in .NET 9.
26 changes: 26 additions & 0 deletions docs/build-apps/build-targets.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
---
title: .NET for iOS, Mac Catalyst, macOS, and tvOS Build Targets
description: .NET for iOS, Mac Catalyst, macOS, and tvOS Build Targets
ms.date: 09/19/2024
---

# Build Targets

The following build targets are defined in .NET for iOS, Mac Catalyst, macOS, and tvOS projects.

## Build (Default)

Builds the source code within a project and all dependencies.

## Clean

Removes all files generated by the build process.

## Run

Builds the source code within a project and all dependencies, and then deploys and runs it
on a default simulator/device. A specific deployment target can be set by using the `$(_DeviceName)` property.

```
dotnet build -t:Run project.csproj -p:_DeviceName=$(MY_DEVICE_UDID)
```
30 changes: 30 additions & 0 deletions docs/native-library-interop.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# NativeLibraryInterop

## Overview
Native Library Interop (formerly referred to as the "Slim Binding" approach), refers to a
pattern for accessing native SDKs in .NET for iOS, Mac Catalyst, macOS, and tvOS projects.

Starting in .NET 9, the .NET for iOS, Mac Catalyst, macOS, and tvOS SDKs support building
Xcode framework projects by using the `@(XcodeProjet)` build action. This is declared in
an MSBuild ItemGroup in a project file:

```xml
<ItemGroup>
<XcodeProject Include="path/to/MyProject.xcodeproj" SchemeName="MyLibrary" />
</ItemGroup>
```

When an `@(XcodeProject)` item is added to a .NET for iOS, Mac Catalyst, macOS, or tvOS binding project,
the build process will attempt to create an XCFramework from the specified Xcode project. The XCFramework
output will be added as a `@(NativeReference)` to the .NET project so that it can be bound and have its
API surfaced via an [API definition][0] file.

Please see the [build-items](build-apps/build-items.md) docs for more information about
the `@(XcodeProjet)` build action.

Additional documentation and references can be found below:

* https://learn.microsoft.com/en-us/dotnet/communitytoolkit/maui/native-library-interop
* https://github.com/CommunityToolkit/Maui.NativeLibraryInterop

[0]: https://learn.microsoft.com/en-us/previous-versions/xamarin/cross-platform/macios/binding/objective-c-libraries?tabs=macos#The_API_definition_file
1 change: 1 addition & 0 deletions dotnet/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ $(1)_NUGET_TARGETS = \
$(DOTNET_DESTDIR)/$($(1)_NUGET_SDK_NAME)/targets/Xamarin.Shared.Sdk.Trimming.props \
$(DOTNET_DESTDIR)/$($(1)_NUGET_SDK_NAME)/targets/Xamarin.Shared.Sdk.targets \
$(DOTNET_DESTDIR)/$($(1)_NUGET_SDK_NAME)/targets/dotnet-xcsync.targets \
$(DOTNET_DESTDIR)/$($(1)_NUGET_SDK_NAME)/targets/Microsoft.MaciOS.Sdk.Xcode.targets \

endef
$(foreach platform,$(DOTNET_PLATFORMS),$(eval $(call DefineTargets,$(platform))))
Expand Down
146 changes: 146 additions & 0 deletions dotnet/targets/Microsoft.MaciOS.Sdk.Xcode.targets
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
<!--
***********************************************************************************************
Microsoft.MaciOS.Sdk.Xcode.targets
This file contains MSBuild targets that support building Xcode framework projects.
***********************************************************************************************
-->

<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">

<UsingTask TaskName="Xamarin.MacDev.Tasks.CreateXcArchive" AssemblyFile="$(_XamarinTaskAssembly)"/>
<UsingTask TaskName="Xamarin.MacDev.Tasks.CreateXcFramework" AssemblyFile="$(_XamarinTaskAssembly)"/>

<PropertyGroup>
<_XcodeProjectDefaultOutputPathRoot>$(MSBuildProjectDirectory)/$(IntermediateOutputPath)xcode/</_XcodeProjectDefaultOutputPathRoot>
<_BuildXcodeProjectsStamp>$(_MaciOSStampDirectory)_BuildXcodeProjects.stamp</_BuildXcodeProjectsStamp>
</PropertyGroup>

<ItemDefinitionGroup>
<XcodeProject>
<Configuration>Release</Configuration>
<CreateNativeReference>true</CreateNativeReference>
<ForceLoad></ForceLoad>
<Frameworks></Frameworks>
<Kind>Framework</Kind>
<OutputPath></OutputPath>
<SchemeName></SchemeName>
<SmartLink></SmartLink>
<Visible></Visible>
</XcodeProject>
</ItemDefinitionGroup>


<Target Name="_GetBuildXcodeProjectsInputs"
Condition=" '@(XcodeProject->Count())' != '0' " >
<ItemGroup>
<_AllXcbFiles Include="%(XcodeProject.RootDir)%(XcodeProject.Directory)/**/*"
Condition= " Exists('%(XcodeProject.RootDir)%(XcodeProject.Directory)') "/>
<_XcbInputs Include="@(_AllXcbFiles)"
Condition=" '%(Extension)' == '.swift'
or '%(Extension)' == '.h'
or '%(Extension)' == '.pbxproj'
or '%(Extension)' == '.xcworkspace' " />
<_XcbInputs Include="$(MSBuildProjectFullPath)" />
</ItemGroup>
</Target>

<Target Name="_CalculateXcodeProjectOutputPaths"
Condition=" '@(XcodeProject->Count())' != '0' "
Outputs="%(XcodeProject.Identity)" >
<Hash
ItemsToHash="%(XcodeProject.Identity)"
IgnoreCase="true" >
<Output TaskParameter="HashResult" PropertyName="_XcodeProjectHash" />
</Hash>
<ItemGroup>
<XcodeProject Condition=" '%(XcodeProject.OutputPath)' == '' " >
<OutputPath>$(_XcodeProjectDefaultOutputPathRoot)%(SchemeName)-$([System.String]::Copy($(_XcodeProjectHash)).Substring(0, 5))/</OutputPath>
</XcodeProject>
</ItemGroup>
</Target>

<Target Name="_BuildXcodeProjects"
Condition=" '@(XcodeProject->Count())' != '0' "
DependsOnTargets="_GetBuildXcodeProjectsInputs;_CalculateXcodeProjectOutputPaths;_DetectSdkLocations"
Inputs="@(_XcbInputs)"
Outputs="$(_BuildXcodeProjectsStamp)" >
<RemoveDir Directories="@(XcodeProject->'%(OutputPath)archives')" />
<RemoveDir Directories="@(XcodeProject->'%(OutputPath)xcframeworks')" />

<PropertyGroup>
<_XcArchivePlatform Condition=" '$(TargetPlatformIdentifier)' == 'maccatalyst' ">generic/platform=macOS,variant=Mac Catalyst</_XcArchivePlatform>
<_XcArchivePlatform Condition=" '$(TargetPlatformIdentifier)' == 'tvos' ">generic/platform=iOS</_XcArchivePlatform>
<_XcArchivePlatform Condition=" '$(_XcArchivePlatform)' == '' ">generic/platform=$(TargetPlatformIdentifier)</_XcArchivePlatform>
</PropertyGroup>

<!-- Create XCARCHIVE for $(TargetPlatformIdentifier) -->
<!-- The derivedDataPath and packageCachePath arguments are used to better support multitargeting projects building in parallel -->
<CreateXcArchive
ProjectPath="%(XcodeProject.FullPath)"
SchemeName="%(XcodeProject.SchemeName)"
Configuration="%(XcodeProject.Configuration)"
ArchivePlatform="$(_XcArchivePlatform)"
OutputPath="%(XcodeProject.OutputPath)archives/%(XcodeProject.SchemeName)$(TargetPlatformIdentifier).xcarchive"
DerivedDataPath="$(_XcodeProjectDefaultOutputPathRoot)DerivedData"
PackageCachePath="$(_XcodeProjectDefaultOutputPathRoot)Cache"
SdkDevPath="$(_SdkDevPath)"
WorkingDirectory="%(XcodeProject.RootDir)%(XcodeProject.Directory)" >
</CreateXcArchive>

<!-- Create simulator XCARCHIVE for $(TargetPlatformIdentifier) -->
<CreateXcArchive
Condition=" '$(TargetPlatformIdentifier)' == 'ios' or '$(TargetPlatformIdentifier)' == 'tvos' "
ProjectPath="%(XcodeProject.FullPath)"
SchemeName="%(XcodeProject.SchemeName)"
Configuration="%(XcodeProject.Configuration)"
ArchivePlatform="$(_XcArchivePlatform) Simulator"
DerivedDataPath="$(_XcodeProjectDefaultOutputPathRoot)DerivedData"
PackageCachePath="$(_XcodeProjectDefaultOutputPathRoot)Cache"
OutputPath="%(XcodeProject.OutputPath)archives/%(XcodeProject.SchemeName)$(TargetPlatformIdentifier)Simulator.xcarchive"
SdkDevPath="$(_SdkDevPath)"
WorkingDirectory="%(XcodeProject.RootDir)%(XcodeProject.Directory)" >
</CreateXcArchive>

<!-- Create XCFRAMEWORK for $(TargetPlatformIdentifier) -->
<CreateXcFramework
XcArchivePath="%(XcodeProject.OutputPath)archives"
FrameworkName="%(XcodeProject.SchemeName).framework"
OutputPath="%(XcodeProject.OutputPath)xcframeworks/%(XcodeProject.SchemeName)$(TargetPlatformIdentifier).xcframework"
SdkDevPath="$(_SdkDevPath)"
WorkingDirectory="%(XcodeProject.RootDir)%(XcodeProject.Directory)" >
</CreateXcFramework>

<ItemGroup>
<_XcodeProjectXcFrameworkOutputs Include="%(XcodeProject.OutputPath)xcframeworks/%(XcodeProject.SchemeName)$(TargetPlatformIdentifier).xcframework"
Condition=" '%(CreateNativeReference)' == 'true' "
ForceLoad="%(ForceLoad)"
Frameworks="%(Frameworks)"
Kind="%(Kind)"
SmartLink="%(SmartLink)"
Visible="%(Visible)" />
</ItemGroup>

<MacDevMessage ResourceName="XcodeBuild_CreateNativeRef"
FormatArguments="%(_XcodeProjectXcFrameworkOutputs.Identity)"
Condition=" '@(_XcodeProjectXcFrameworkOutputs->Count())' != '0' "
/>

<Touch Files="$(_BuildXcodeProjectsStamp)" AlwaysCreate="true" />
<ItemGroup>
<!-- Add stamp and build outputs to FileWrites so they are not deleted on IncrementalClean -->
<FileWrites Include="$(_BuildXcodeProjectsStamp);%(XcodeProject.OutputPath)**/*" />
<!-- Automatically add XCFRAMEWORK outputs to be bound or otherwise processed later in the build -->
<NativeReference Include="@(_XcodeProjectXcFrameworkOutputs)" Kind="%(Kind)" ForceLoad="%(ForceLoad)" Frameworks="%(Frameworks)" SmartLink="%(SmartLink)" Visible="%(Visible)" />
</ItemGroup>
</Target>


<Target Name="_CleanXcodeProjects"
Condition=" '@(XcodeProject->Count())' != '0' "
DependsOnTargets="_GetBuildXcodeProjectsInputs;_CalculateXcodeProjectOutputPaths" >
<RemoveDir Directories="@(XcodeProject->'%(OutputPath)')" />
</Target>

</Project>
21 changes: 21 additions & 0 deletions dotnet/targets/Xamarin.Shared.Sdk.targets
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
<UsingTask TaskName="Xamarin.MacDev.Tasks.LinkNativeCode" AssemblyFile="$(_XamarinTaskAssembly)" />
<UsingTask TaskName="Xamarin.MacDev.Tasks.MergeAppBundles" AssemblyFile="$(_XamarinTaskAssembly)" />
<UsingTask TaskName="Xamarin.MacDev.Tasks.MobileILStrip" AssemblyFile="$(_XamarinTaskAssembly)" />
<UsingTask TaskName="Xamarin.MacDev.Tasks.MacDevMessage" AssemblyFile="$(_XamarinTaskAssembly)" />

<!-- Project types and how do we distinguish between them

Expand Down Expand Up @@ -163,6 +164,9 @@
(in other words: once we drop support for legacy Xamarin, we can remove the other definition) -->
<_CanOutputAppBundle Condition="'$(_CanOutputAppBundle)' == '' And ('$(OutputType)' == 'Exe' Or '$(IsAppExtension)' == 'true' Or '$(IsWatchApp)' == 'true')">true</_CanOutputAppBundle>
<_CanOutputAppBundle Condition="'$(_CanOutputAppBundle)' == ''">false</_CanOutputAppBundle>

<!-- Directory to store project specific stamp files used by certain targets for incremental builds -->
<_MaciOSStampDirectory>$(IntermediateOutputPath)stamp\</_MaciOSStampDirectory>
</PropertyGroup>

<PropertyGroup>
Expand All @@ -186,6 +190,8 @@
<PropertyGroup Condition="'$(_CanOutputAppBundle)' != 'true'">
<BuildDependsOn>
BuildOnlySettings;
MaciOSPrepareForBuild;
_BuildXcodeProjects;
_CollectBundleResources;
_PackLibraryResources;
$(BuildDependsOn);
Expand All @@ -197,6 +203,8 @@
_WarnRuntimeIdentifiersClash;
_ComputePublishTrimmed;
BuildOnlySettings;
MaciOSPrepareForBuild;
_BuildXcodeProjects;
_CollectBundleResources;
_PackLibraryResources;
_UnpackLibraryResources;
Expand All @@ -213,6 +221,8 @@
_ErrorRuntimeIdentifiersClash;
_ComputePublishTrimmed;
BuildOnlySettings;
MaciOSPrepareForBuild;
_BuildXcodeProjects;
_CollectBundleResources;
_PackLibraryResources;
_UnpackLibraryResources;
Expand Down Expand Up @@ -286,6 +296,15 @@
</CreateAppBundleDependsOn>
</PropertyGroup>

<!-- Used to extend the build process, .csproj files can declare <MaciOSPrepareForBuildDependsOn>SomeTarget</MaciOSPrepareForBuildDependsOn> to run a target at the beginning of the build -->
<Target Name="MaciOSPrepareForBuild"
DependsOnTargets="$(MaciOSPrepareForBuildDependsOn)" >
<MakeDir
Condition=" '$(_MaciOSStampDirectory)' != '' and !Exists('$(_MaciOSStampDirectory)') "
Directories="$(_MaciOSStampDirectory)"
/>
</Target>

<Target Name="_CreateAssetPackManifestMobile" Condition="'$(_PlatformName)' != 'macOS'" DependsOnTargets="_CreateAssetPackManifest" />

<!-- PublishTrimmed must be calculated as part of a target because IsMacEnabled on Windows will be set after connecting to the Mac -->
Expand Down Expand Up @@ -2456,4 +2475,6 @@ global using nfloat = global::System.Runtime.InteropServices.NFloat%3B
<Import Project="$(_TargetsDirectory)Xamarin.MacCatalyst.ObjCBinding.$(_ProjectLanguage).targets" Condition="'$(_ProjectType)' == 'MacCatalystBindingProject' " />

<Import Project="dotnet-xcsync.targets" />
<Import Project="Microsoft.MaciOS.Sdk.Xcode.targets" Condition=" $([MSBuild]::IsOSPlatform('osx')) " />

</Project>
16 changes: 16 additions & 0 deletions msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx
Original file line number Diff line number Diff line change
Expand Up @@ -1576,4 +1576,20 @@
<comment>RuntimeIdentifier: don't translate (it's the name of a MSBuild property)</comment>
</data>

<data name="XcodeBuild_CreateNativeRef" xml:space="preserve">
<value>Adding reference to Xcode project output: '{0}'. The '%(CreateNativeReference)' metadata can be set to 'false' to opt out of this behavior.</value>
<comment>
The following are literal names and should not be translated: Xcode, '%(CreateNativeReference)', metadata, 'false'
{0}: The path to the XCFramework output that will be referenced
</comment>
</data>

<data name="XcodeBuild_InvalidItem" xml:space="preserve">
<value>The Xcode project item: '{0}' could not be found. Please update the 'Include' value to a path containing a valid '.xcodeproj' file.</value>
<comment>
The following are literal names and should not be translated: Xcode, 'Include', '.xcodeproj'
{0}: An invalid path to an Xcode project file
</comment>
</data>

</root>
Loading