Reputation: 4821
I'm in the process of upgrading a Xamarin Native solution to run on .net 6 using SDK style multi-targetted projects.
As I understand it, there are two ways of writing platform-dependent code:
a) Code inside the appropriate platform directory (e.g. "iOS" or "MacCatalyst") is always compiled conditionally for that platform.
b) Code can be explicitly conditionally compiled via e.g., #if IOS
.
Neither of these seems ideal when there is a large body of code that is shared between two platforms. In (a) the code is duplicated and in (b) the code is strewn with #if
s.
Is there a better way to share code between these two platforms?
Upvotes: 3
Views: 378
Reputation: 4206
Late joining the discussion. Here's the solution I came up with. The following snippet intelligently copies over those .cs files that have changed in iOS and makes sure to delete any .cs files under MacCatalyst that no longer have an equivalent sibling file under iOS.
Note that if you are to use this in your CI pipeline you will have to invoke the target 'CloneIOSSourceCodeToMacCatalyst_Impl' explicitly and separately before launching the build on the .csproj like so:
<!-- its vital to call this target explicitly before compilation kicks in -->
<Message Importance="High" Text="** [Builder] Copying iOS *.cs -> MacCatalyst"/>
<MSBuild Projects="path/to/your/project.csproj" Targets="CloneIOSSourceCodeToMacCatalyst_Impl"/>
<!-- COMPILE -->
<MSBuild Projects="path/to/your/project.csproj" Targets="Build"/>
And snippet itself:
<!-- this is meant to be an automation automatically invoked by the IDE itself to make our lives a bit easier -->
<Target Condition=" $([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'maccatalyst' "
Name="CloneIOSSourceCodeToMacCatalyst"
BeforeTargets="CoreCompile">
<CallTarget Targets="CloneIOSSourceCodeToMacCatalyst_Impl"/>
</Target>
<!-- we made this a separate target without conditions so that it will be directly invocable by our build script -->
<Target Name="CloneIOSSourceCodeToMacCatalyst_Impl">
<Message Importance="High" Text="**** [CloneIOSSourceCodeToMacCatalyst_Impl] Copying iOS *.cs -> MacCatalyst"/>
<!-- intelligently copy over only the files that changed from ios -->
<ItemGroup>
<iOSFiles Include="Platforms\iOS\**\*.*"/>
</ItemGroup>
<Copy SourceFiles="@(iOSFiles)"
DestinationFolder="Platforms\MacCatalyst\%(RecursiveDir)"
SkipUnchangedFiles="true"/>
<!-- and now delete all those files under mac-catalyst that no longer have an equivalent sibling file under ios -->
<ItemGroup>
<MacCatalystSourceFiles Include="$([System.IO.Directory]::GetFiles('Platforms\MacCatalyst', '*.cs', SearchOption.AllDirectories))" />
<MacCatalystSourceFiles>
<RespectiveIosFilePath>$([System.String]::Copy('%(Identity)').Replace('MacCatalyst', 'iOS'))</RespectiveIosFilePath>
</MacCatalystSourceFiles>
<RespectiveIosFiles Include="@(MacCatalystSourceFiles->'%(RespectiveIosFilePath)')" />
<RespectiveIosFilesThatDontExist Include="@(RespectiveIosFiles)" Condition=" !Exists(%(RespectiveIosFiles.Identity)) " />
<RespectiveIosFilesThatDontExist>
<RespectiveMacCatalystFilePath>$([System.String]::Copy('%(Identity)').Replace('iOS', 'MacCatalyst'))</RespectiveMacCatalystFilePath>
</RespectiveIosFilesThatDontExist>
<MacCatalystStrayFilesToDelete Include="@(RespectiveIosFilesThatDontExist->'%(RespectiveMacCatalystFilePath)')" />
</ItemGroup>
<Delete Files="@(MacCatalystStrayFilesToDelete)" />
<Touch Files="Platforms\MacCatalyst\_DONT_EDIT_ANY_FILES_IN_HERE__ITS_POINTLESS_" AlwaysCreate="true" Condition=" !Exists('Platforms\MacCatalyst\_DONT_EDIT_ANY_FILES_IN_HERE__ITS_POINTLESS_') " />
</Target>
Upvotes: 0
Reputation: 21341
MSBuild Conditions can be used to control build actions under different circumstances.
Given the default value true
of <EnableDefaultCompileItems>
, the easiest approach is to REMOVE a folder from targets that shouldn't have it.
The goal is to have (most) iOS code be included on both iOS and MacCatalyst; therefore exclude it from all other target frameworks.
Steps:
iosShared
.YourProject.csproj
: <ItemGroup Condition="'$(TargetFramework)' != 'net6.0-ios' And
'$(TargetFramework)' != 'net6.0-maccatalyst'">
<Compile Remove="iosShared\**\*" />
</ItemGroup>
Upvotes: 2
Reputation: 4821
I'm considering creating a "Shared" folder under Platforms/iOS
and symlinking that folder to Platforms/MacCatalyst/Shared
.
Still holding out for other answers, as well as comments on this one.
EDIT: I tried this on Visual Studio For Mac 2022. The symlinked folder at Platforms/MacCatalyst/Shared
appears as a single file with a reddish title in Solution Explorer, i.e., its subtree isn't visible. However, the project complies fine for both iOS and Mac Catalyst. Since the shared code is accessible from its original location at Platforms/iOS/Shared
, this looks like valid albeit somewhat unsupported strategy.
Upvotes: 0