Skip to content

Conversation

@eriknilsontenstar
Copy link

@eriknilsontenstar eriknilsontenstar commented Nov 25, 2025

This aims to fix #145. At my work, we use Unity Version Control (previously Plastic SCM) https://www.plasticscm.com/ and we would like to be able to use MSBuildCache for our C++ projects. Using the cm ls command https://docs.unity.com/en-us/unity-version-control/uvcs-cli/list, you get similar behaviour as with git ls-files, one big difference being that cm ls can returns hash as a base64 string. For example, the command cm ls -R --format="{path}\t{hash}" would return something like \tzt+fx36QelZDAkae84vEaQ==.

@eriknilsontenstar
Copy link
Author

@microsoft-github-policy-service agree company="Tenstar"

return (null, null);

static string? GetRepoRootInternal(string path)
static (string?, VersionControl?) GetRepoRootInternal(string path, PluginLoggerBase logger)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should the return value instead be (string, VersionControl)?? They're either both null or neither are null, so the tuple itself being nullable instead of each value seems to express the behavior better.

Copy link
Author

@eriknilsontenstar eriknilsontenstar Dec 2, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, I couldn't get that to compile when reading the value from the nullable tuple, as in (string, VersionControl) = GetRepoInfo(context, logger);

Instead, I opted for a simpler container class called RepoInfo, code is updated.

}
else
{
_logger.LogMessage($"{file} is missing a hash and will be rehashed.");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just checking since I'm not familiar with unity, is this the real behavior? For untracked files it will return a line with just the file name and nothing else?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes haha, that is the real behaviour as you can see in the picture. Do you think that will affect something?
image

StringBuilder? line;
while ((line = reader.ReadLine()) != null)
{
var splitLine = line.ToString().Split('\t');
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can this be refactored to do less allocation? Splitting a string like this allocates an array and new strings.

I imagine this is a somewhat hot path, especially for larger repos, so we should try and make this code as efficient as possible.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed the array allocation from string.Split


internal Task GitHashObjectAsync(string basePath, List<string> filesToRehash, Dictionary<string, byte[]> filehashes, CancellationToken cancellationToken)
{
return Git.RunAsync(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems weird to fall back to git here. Does Unity not have an equivalent command to use?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have not found a suitable command within the cm program that is equivalent. The full list is here https://docs.unity.com/en-us/unity-version-control/uvcs-cli/version-control-cli

cancellationToken);
}

private sealed class UnityVersionContorlLsFileOutputReader : IDisposable
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some (all?) of this appears to be copied from the git impl. Can you extract it to for code reuse?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Extracted out GitHashObjectAsync.
The Reader has a difference in that it doesn't launch a Task to start the parsing. This helped me work around issue where the UnityVersionContorlLsFileOutputReader's reader got Disposed before PopulateAsync was done

@dfederm
Copy link
Member

dfederm commented Nov 28, 2025

First off, thank you for your contribution!!

Can you comment on the kind of testing you've done? And also attach some logs and/or screenshots as evidence of functionality?

@eriknilsontenstar
Copy link
Author

First off, thank you for your contribution!!

Can you comment on the kind of testing you've done? And also attach some logs and/or screenshots as evidence of functionality?

Hi, thank you, and thank you for the feedback, I will take a look at it this week.

Yes, of course! I've only tested with the Local cache with MSBuild version 17.14.23+b0019275e.

To figure out #146 I made two brand new VS projects, one static lib and one console app. From there I got my first successful use of the cache and then I added more and more of our internal projects as dependencies to the console app. Once I understood that the solution has to be above its projects I changed our real project to behave the same. Below you can see screenshot of one build populating the cache and the second a clean build on the same changes.

image

From there I've gotten it to run after the build agents as a test, its not "live" internally yet. A couple of snags I hit were

  • Need to configure the build agent to use the x64 version of msbuild. The way I did it now is to change where the PATH points
  • Need shallower paths than before because of what I suspect is Unity Version Control hitting max path limitation or something. Its like the output from cm ls doesn't get properly read line by line by the MSBuildCache implementation...

Below is a snippet from the initial log from a build using the cache plugin. As you can see we only tweak the log and cache directory settings. Question: Do you recommend ignoring more types of files, like .pngs and stuff? Or are they already not considered by MSBuildCache?

Static graph loaded in 0.189 seconds: 15 nodes, 39 edges
Loading the following project cache plugin: MSBuildCacheLocalPlugin
Repo root: J:\BuildDirectory
Effective plugin settings:
LogDirectory: J:\MSBuildCacheLogs\BuildDirectory
CacheUniverse: default
MaxConcurrentCacheContentOperations: 64
LocalCacheRootPath: J:\MSBuildCache\BuildDirectory
LocalCacheSizeInMegabytes: 102400
IgnoredInputPatterns:
IgnoredOutputPatterns: J:\BuildDirectory**.assets.cache;J:\BuildDirectory**assemblyreference.cache
IdenticalDuplicateOutputPatterns:
RemoteCacheIsReadOnly: False
AsyncCachePublishing: True
AsyncCacheMaterialization: True
AllowFileAccessAfterProjectFinishProcessPatterns: **\vctip.exe
AllowFileAccessAfterProjectFinishFilePatterns:
AllowProcessCloseAfterProjectFinishProcessPatterns: **\mspdbsrv.exe
GlobalPropertiesToIgnore: CurrentSolutionConfigurationContents;ShouldUnsetParentConfigurationAndPlatform;BuildingInsideVisualStudio;BuildingSolutionFile;SolutionDir;SolutionExt;SolutionFileName;SolutionName;SolutionPath;_MSDeployUserAgent;MSBuildCacheEnabled;MSBuildCacheLogDirectory;MSBuildCacheCacheUniverse;MSBuildCacheMaxConcurrentCacheContentOperations;MSBuildCacheLocalCacheRootPath;MSBuildCacheLocalCacheSizeInMegabytes;MSBuildCacheIgnoredInputPatterns;MSBuildCacheIgnoredOutputPatterns;MSBuildCacheIdenticalDuplicateOutputPatterns;MSBuildCacheRemoteCacheIsReadOnly;MSBuildCacheAsyncCachePublishing;MSBuildCacheAsyncCacheMaterialization;MSBuildCacheAllowFileAccessAfterProjectFinishProcessPatterns;MSBuildCacheAllowFileAccessAfterProjectFinishFilePatterns;MSBuildCacheAllowProcessCloseAfterProjectFinishProcessPatterns;MSBuildCacheGlobalPropertiesToIgnore;MSBuildCacheGetResultsForUnqueriedDependencies;MSBuildCacheTargetsToIgnore;MSBuildCacheIgnoreDotNetSdkPatchVersion;MSBuildCacheSkipUnchangedOutputFiles;MSBuildCacheTouchOutputFiles
GetResultsForUnqueriedDependencies: False
TargetsToIgnore: GetTargetFrameworks;GetNativeManifest;GetCopyToOutputDirectoryItems;GetTargetFrameworksWithPlatformForSingleTargetFramework
IgnoreDotNetSdkPatchVersion: False
SkipUnchangedOutputFiles: False
TouchOutputFiles: False

Source Control: Getting hashes from uvcs
cm.exe ls -R --format="{path} {hash}" (@j:\BuildDirectory) took 1813 msec and returned 0.
Project graph with 15 nodes provided.
Source Control: File hashes query took 1900 ms
Executed project prediction on 15 nodes in 0.09s.
BeginBuildAsync succeeded after 2537 ms.

Please let me know if there is some more specific testing you'd like to see or if something so far seems off. I will get back here with some more data from the CI builds when its "live" internally.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Feasability to support a different version control than git

2 participants