xamarin-macios icon indicating copy to clipboard operation
xamarin-macios copied to clipboard

The new _CopyLocalBindingResources target has potential MAX_PATH issues on Windows

Open rolfbjarne opened this issue 2 years ago • 3 comments

The _CopyLocalBindingResources target was added to ensure binding resources are copied along with the binding assembly when the binding assembly is copied around.

A problem arises on Windows however, because some binding resources have long paths, and we end up with MAX_PATH problems.

Ref: https://dev.azure.com/devdiv/DevDiv/_workitems/edit/1832539

A temporary workaround is being added here, to opt out of the _CopyLocalBindingResources target (by setting a property in the user's csproj without having to edit the Xamarin.Shared.Sdk.targets file): https://github.com/xamarin/xamarin-macios/pull/18443, but that doesn't fix the real problem.

One idea might be to detect any potential MAX_PATH issues, and if so, instead of copying all the binding resource files, create a compressed binding resource package instead on the fly (basically zip up the binding resource files).

Current workaround

Delete the _CopyLocalBindingResources target in the Xamarin.Shared.Sdk.targets file.

The path is something like this:

C:\Program Files\dotnet\packs\Microsoft.iOS.Sdk<version>\targets\Xamarin.Shared.Sdk.targets

This is the target to delete (the exact contents might vary a bit depending on the version installed, but it should be very similar):

<Target Name="_CopyLocalBindingResources" AfterTargets="ResolveAssemblyReferences" BeforeTargets="CopyFilesToOutputDirectory">
	<!--

		We need to copy binding resource packages (either zipped or as a
		.resources bundle) together with the binding assembly when the
		binding assembly is copied around. This typically happens when
		another library project references the binding project, in which
		case the ResolveAssemblyReference task will add the binding
		library to the ReferenceCopyLocalPaths item group (when building
		the library project), which will cause the binding assembly to be
		copied to the output directory for the other library project.

		If this isn't done, then any projects referencing the library
		project (and not the binding project), won't see any binding
		resource packages from the binding project.

		This is not good (https://github.com/xamarin/xamarin-macios/issues/13910).

		This is a target that runs after the ReferenceCopyLocalPaths item
		group has been populated by the ResolveAssemblyReferences target,
		and will add any binding resource packages to the
		ReferenceCopyLocalPaths item group.

		This is somewhat complicated by the fact that we can have
		.resources bundles (i.e. directories), so we need to expand those
		to all their contained files.

	-->

	<ItemGroup>
		<!-- List all potential compressed binding resource packages next to the assemblies we're copying to the output directory -->
		<_CompressedBindingPackagesFromReferencedAssembliesCandidates Include="@(ReferenceCopyLocalPaths -> '%(RootDir)%(Directory)%(Filename).resources.zip')" />
		<!-- We only care about those that actually exist, and we don't want duplicates either -->
		<_CompressedBindingPackagesFromReferencedAssemblies Include="@(_CompressedBindingPackagesFromReferencedAssembliesCandidates->Distinct())" Condition="Exists('%(Identity)')" />

		<!-- List all potential binding resource packages next to the assemblies we're copying to the output directory -->
		<_BindingPackagesFromReferencedAssembliesDirectoriesCandidates Include="@(ReferenceCopyLocalPaths -> '%(RootDir)%(Directory)%(Filename).resources')" />
		<!-- We only care about those that actually exist, and we don't want duplicates either -->
		<_BindingPackagesFromReferencedAssembliesDirectoriesExists Include="@(_BindingPackagesFromReferencedAssembliesDirectoriesCandidates->Distinct())" Condition="Exists('%(Identity)')" />
	</ItemGroup>

	<!--
		We need to expand items in an item group using globs, which is
		kind of tricky, because globs in item transformations aren't
		treated as globs. The workaround is to use a custom task for this.

		Note that this task should run remotely from Windows when there's
		a Mac connected, but locally when there's not a Mac connected (for
		Hot Restart builds for instance), so we're not conditioning this
		task on IsMacEnabled.

	-->
	<GetFileSystemEntries
		SessionId="$(BuildSessionId)"
		DirectoryPath="@(_BindingPackagesFromReferencedAssembliesDirectoriesExists)"
		Pattern="*"
		Recursive="true"
		IncludeDirectories="false"
		>
		<Output TaskParameter="Entries" ItemName="_BindingPackagesFromReferencedAssemblies" />
	</GetFileSystemEntries>

	<ItemGroup>
		<!-- We need to set the 'DestinationSubDirectory' metadata to indicate the actual target directory for items we expanded using a wildcard -->
		<_BindingPackagesFromReferencedAssembliesWithDestinationDir Include="@(_BindingPackagesFromReferencedAssemblies)">
			<DestinationSubDirectory>$([System.IO.Path]::GetFileName('%(OriginalItemSpec)'))\</DestinationSubDirectory>
			<DestinationSubDirectory Condition="'%(RecursiveDir)' != ''">$([System.IO.Path]::GetFileName('%(OriginalItemSpec)'))\$([System.IO.Path]::GetDirectoryName('%(RecursiveDir)'))\</DestinationSubDirectory>
		</_BindingPackagesFromReferencedAssembliesWithDestinationDir>
		<!-- Add what we found to ReferenceCopyLocalPaths. Note that binding resource packages should generally not be published, so we're setting PublishFolderType=None -->
		<ReferenceCopyLocalPaths Include="@(_CompressedBindingPackagesFromReferencedAssemblies)">
			<PublishFolderType>None</PublishFolderType>
		</ReferenceCopyLocalPaths>
		<ReferenceCopyLocalPaths Include="@(_BindingPackagesFromReferencedAssembliesWithDestinationDir)">
			<PublishFolderType>None</PublishFolderType>
		</ReferenceCopyLocalPaths>
	</ItemGroup>
</Target>

The original version of the file can be found in the targets/ directory in the NuGet here: https://nuget.info/packages/Microsoft.iOS.Sdk/16.4.7054 (replace the version with the one from the path to Xamarin.Shared.Sdk.targets above).

rolfbjarne avatar Jun 14 '23 10:06 rolfbjarne

I recently ran into a similar issue on Windows using the GeniusScanSDK.Scanflow.iOS Nuget package. On Mac this package can be added fine but trying to add/restore this package or build the project on Windows leads to the following error:

Could not find a part of the path 'C:\Development\packages\geniusscansdk.scanflow.ios\4.12.0\lib\net7.0-ios16.1\GeniusScanSDK.ScanFlow.iOS.resources\GSSDKCore.xcframework\ios-arm64_x86_64-simulator\GSSDKCore.framework\Modules\GSSDKCore.swiftmodule\arm64-apple-ios-simulator.abi.json'.

(C:\Development\packages is my NuGet globalPackagesFolder)

Is this related to this specific issue (considering the path is 263 characters, just over the 260 of MAX_PATH) or is this more NuGet related in general?

leonluc-dev avatar Jul 11 '23 15:07 leonluc-dev

@leonluc-dev I had this exact problem and it was 100% the MAX_PATH issue with my binding project that uses .xcframeworks. Since I have full control of it, I squished the file names down to be nearly unreadable but it got the path under 260 characters. That got past this problem.

It sounds like a workaround is if you enable long paths in Windows (it's a registry setting) and you do dotnet restore at the command line, it will actually respect the long path setting and restore the packages. The 260 limit seems to be hard and fast in VS builds. I haven't tried the workaround myself though.

Another thing you could do is to shorten your NuGet package path. I believe a lot of people have had success doing that.

brunck avatar Jul 11 '23 21:07 brunck

For the workaround to work, I had to delete the folder "C:\Users<USER>\AppData\Local\Temp\Xamarin\HotRestart\Signing<ASSEMBLY>.app" so that it was completely regenerated when rebuilding the project

dbbill avatar Jul 24 '23 17:07 dbbill