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

Make Build Acceleration support symbolic/hard links #9548

Open
Mariachi1231 opened this issue Oct 6, 2024 · 6 comments
Open

Make Build Acceleration support symbolic/hard links #9548

Mariachi1231 opened this issue Oct 6, 2024 · 6 comments
Assignees
Labels
Feature-Build-Acceleration Build Acceleration can skip calls to MSBuild within Visual Studio Triage-Approved Reviewed and prioritized
Milestone

Comments

@Mariachi1231
Copy link

Mariachi1231 commented Oct 6, 2024

Visual Studio Version

Version 17.11.4

Summary

Is it possible to make FUTDC (Fast Up-to-Date Check) work properly with symbolic links?

In my large project, I’m using MSBuild properties for symbolic links, such as:

  • CreateSymbolicLinksForCopyFilesToOutputDirectoryIfPossible
  • CreateSymbolicLinksForCopyAdditionalFilesIfPossible
  • CreateSymbolicLinksForCopyLocalIfPossible
  • CreateSymbolicLinksForAdditionalFilesIfPossible
  • CreateSymbolicLinksForPublishFilesIfPossible

This helps save disk space and avoids unnecessary copying of files.

By default, this doesn't work in Visual Studio, so have a couple of hacks to make it work, like next:

<Target Name="RestoreSymlinksInsideVisualStudio_FilesToOutputDirectory"
			BeforeTargets="CopyFilesToOutputDirectory"
			Condition="'$(EnableSymbolicLinks)' == 'true' and '$(BuildingInsideVisualStudio)' == 'true'">
		<PropertyGroup>
			<BuildingInsideVisualStudio>false</BuildingInsideVisualStudio>
			<WasBuildingInsideVisualStudio>true</WasBuildingInsideVisualStudio>
		</PropertyGroup>
</Target>
<Target Name="Rollback_RestoreSymlinksInsideVisualStudio_FilesToOutputDirectory"
			AfterTargets="CopyFilesToOutputDirectory"
			Condition="'$(EnableSymbolicLinks)' == 'true' and '$(WasBuildingInsideVisualStudio)' == 'true'">
		<PropertyGroup>
			<BuildingInsideVisualStudio>true</BuildingInsideVisualStudio>
		</PropertyGroup>
</Target>

Actual Behavior

When I build the project, it seems that file copying related to the up-to-date check fails, likely due to the symbolic link. Here's an excerpt from the log:

13>FastUpToDate: Copying 4 files to accelerate build (https://aka.ms/vs-build-acceleration): (MyPrefix.ProjectA)
13>FastUpToDate:     From 'C:\src\MyPrefix.ProjectA\bin\MyPrefix.ProjectADependency.dll' to 'C:\src\MyPrefix.ProjectA\bin\MyPrefix.ProjectADependency.dll'. (MyPrefix.ProjectA)
13>FastUpToDate: Exception copying file, scheduling MSBuild: System.IO.IOException: The process cannot access the file 'C:\src\MyPrefix.ProjectA\bin\MyPrefix.ProjectADependency.dll' because it is being used by another process.
13>   at System.IO.__Error.WinIOError(Int32 errorCode, String maybeFullPath)
13>   at System.IO.File.InternalCopy(String sourceFileName, String destFileName, Boolean overwrite, Boolean checkHost)
13>   at Microsoft.VisualStudio.IO.Win32FileSystem.CopyFile(String source, String destination, Boolean overwrite, Boolean clearReadOnly)
13>   at Microsoft.VisualStudio.ProjectSystem.VS.UpToDate.BuildUpToDateCheck.FileSystemOperationAggregator.TryApplyFileSystemOperations() (MyPrefix.ProjectA)
13>FastUpToDate: Up-to-date check completed in 5.7 ms (MyPrefix.ProjectA)
13>------ Build started: Project: MyPrefix.ProjectA, Configuration: Debug Any CPU ------

Where C:\src\MyPrefix.ProjectA\bin\MyPrefix.ProjectADependency.dll is a symbol link in the project output dir from previous builds.

Expected Behavior

It's unclear to me what the ideal expected behavior should be in this case, but it would be great if this error could be avoided and the MSBuild call skipped because in general it can be accelarated to my mind.

Steps To Reproduce

Since the sln is quite complicated I don't provide the exact steps at that time, but if you will say that it makes sense, I can try to provide repo which reproduces behavior.

User Impact

In my case, this issue triggers the build of subsequent projects because of the failure to copy files during the up-to-date check.

MSBuild in past i think worked on a similar problem here

@drewnoakes
Copy link
Member

@Mariachi1231 this line in the log looks suspicious:

13>FastUpToDate: From 'C:\src\MyPrefix.ProjectA\bin\MyPrefix.ProjectADependency.dll' to 'C:\src\MyPrefix.ProjectA\bin\MyPrefix.ProjectADependency.dll'. (MyPrefix.ProjectA)

The source and destination of that copy are identical. Can you confirm whether that's the case in your logs? I want to rule out the possibility that the log changed when you were redacting private details.

@drewnoakes drewnoakes added the Feature-Build-Acceleration Build Acceleration can skip calls to MSBuild within Visual Studio label Oct 10, 2024
@drewnoakes drewnoakes self-assigned this Oct 10, 2024
@drewnoakes drewnoakes added this to the 17.13 milestone Oct 10, 2024
@drewnoakes drewnoakes added the Triage-Investigate Reviewed and investigation needed by dev team label Oct 10, 2024
@Mariachi1231
Copy link
Author

Hi @drewnoakes,

I’ve created a repro with the actual logs. You can check it out here.

@drewnoakes
Copy link
Member

Thanks for the repro. I see the same error when following the steps you've provided.

I'm not familiar with using symlinks during build, but my first instinct here is that we should just disable build acceleration when a project appears to be using symlinks. Symlinks seem to be solving the same issue as BA here anyway, namely making it cheap to have the same file in multiple projects.

Perhaps there's an API that lets us follow a symlink to its original path, in which case I think the copy would be identified as having the same source/destination. However there's likely overhead in that. I'm inclined to just have VS disable BA when the project uses symlinks.

What do you think would be the best way for us to detect this? Is there a single MSBuild property, or would we need to look for all of these:

  • CreateSymbolicLinksForCopyFilesToOutputDirectoryIfPossible
  • CreateSymbolicLinksForCopyAdditionalFilesIfPossible
  • CreateSymbolicLinksForCopyLocalIfPossible
  • CreateSymbolicLinksForAdditionalFilesIfPossible
  • CreateSymbolicLinksForPublishFilesIfPossible

@drewnoakes
Copy link
Member

It seems that adding this to your Directory.Build.props fixes the issue:

<PropertyGroup>
  <UseCommonOutputDirectory>true</UseCommonOutputDirectory>
</PropertyGroup>

I'm not sure why.

@drewnoakes
Copy link
Member

If we do sniff for those properties, we may also need to check the hard link equivalents, such as:

  • CreateHardLinksForCopyFilesToOutputDirectoryIfPossible
  • CreateHardLinksForCopyAdditionalFilesIfPossible
  • CreateHardLinksForCopyLocalIfPossible
  • CreateHardLinksForPublishFilesIfPossible

@Mariachi1231
Copy link
Author

Mariachi1231 commented Oct 15, 2024

@drewnoakes , thanks for an investigation!

I'm not familiar with using symlinks during build, but my first instinct here is that we should just disable build acceleration when a project appears to be using symlinks. Symlinks seem to be solving the same issue as BA here anyway, namely making it cheap to have the same file in multiple projects.

While both symlinks and BA aim to avoid redundant copying and speed up the build, symlinks are simply a way to avoid non-cheap copying and save disk space. In contrast, BA offers logging and a complex comparison of inputs and outputs to determine whether something actually needs to trigger MSBuild. In my case, the main goal is to avoid triggering MSBuild altogether, but when errors occur during the copy process in BA, MSBuild now invoked, which is unplesant. Once invoked, it can perform many tasks (even without needing to do CoreCompile), such as evaluating target platforms, frameworks, etc., which can take significant time—especially in large solutions with complex ProjectReference graphs. Avoiding unnecessary MSBuild calls early on through BA would be a real benefit in comparison with just usages of symlinks.

What would be ideal is if, during a copy, we could detect that the destination path already contains a symlink. If the symlink points to the same target, we could safely skip the copy. Checking for a symlink does introduce some performance overhead, but in theory, it would only affect users leveraging symlinks, since before checking for the symlink, we could perform a cheaper check to see if the build is using symbolic links at all (based on flags that you already noticed). This would limit the performance impact to only those builds using symlinks, while saving time by avoiding MSBuild executions for such clients.

Perhaps there's an API that lets us follow a symlink to its original path, in which case I think the copy would be identified as having the same source/destination. However there's likely overhead in that. I'm inclined to just have VS disable BA when the project uses symlinks.

Yes, but the overhead could theoretically be minimized by checking only if the build is running with Create(Hard/Symbolic)Links flags in the first place.

What do you think would be the best way for us to detect this? Is there a single MSBuild property, or would we need to look for all of these:

  • CreateSymbolicLinksForCopyFilesToOutputDirectoryIfPossible
  • CreateSymbolicLinksForCopyAdditionalFilesIfPossible
  • CreateSymbolicLinksForCopyLocalIfPossible
  • CreateSymbolicLinksForAdditionalFilesIfPossible
  • CreateSymbolicLinksForPublishFilesIfPossible

From what I know, there isn't a single property for this — only this level of granularity. But from the perspective of BA, it doesn't really matter to my mind, If let's say any of these flags is true, we could set a custom property to true in order to do a perf overhead of symlink checks otherwise just do as it's now. Alternatively, as you saying, you could just disable BA entirely (which, of course, isn't the best option for me 😔).

It seems that adding this to your Directory.Build.props fixes the issue:

true I'm not sure why.

I can imagine why, but unfortunately, I can’t use this in real scenarios where the solution consists of hundreds of projects of different types. These projects are aggregated into a single solution to correctly manage dependencies and leverage the benefits of ProjectReferences during MSBuild and single output in this case of course not an option.

And of course as it's mentioned here, VS is not a friend for symlink usages.

@drewnoakes drewnoakes changed the title Fast Up-To-Date Check: FUTDC and symbolic links Make Build Acceleration support symbolic/hard links Oct 16, 2024
@drewnoakes drewnoakes modified the milestones: 17.13, Backlog Oct 16, 2024
@drewnoakes drewnoakes added Triage-Approved Reviewed and prioritized and removed Triage-Investigate Reviewed and investigation needed by dev team labels Oct 16, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Feature-Build-Acceleration Build Acceleration can skip calls to MSBuild within Visual Studio Triage-Approved Reviewed and prioritized
Projects
None yet
Development

No branches or pull requests

2 participants