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

Сurrent directory virtualization for RAR task #6316

Closed
wants to merge 21 commits into from

Conversation

AR-May
Copy link
Member

@AR-May AR-May commented Apr 1, 2021

Fixes #6218

Context

This PR is part of RAR as a service user story #3139. The current design supposes RAR tasks run concurrently in threads. However, the RAR task also depends on the current directory which is shared between the threads.

Changes Made

In the PR we implement current directory virtualization.
For all the inputs we either absolutize the paths at the start of the RAR task or if we are unable to recognize the input's type, absolutize an entry when we use it, i.e. when we know the type of the entry (path/assembly name/etc).

Testing

There already were unit tests for relative inputs in Miscellaneous.cs, we leverage them to check that the virtualization of the current directory works well.

Notes

@AR-May AR-May marked this pull request as draft April 1, 2021 09:19
@AR-May AR-May force-pushed the current-diretory-virtualization-3 branch from 5765e1c to 5db2554 Compare April 5, 2021 15:15
@AR-May AR-May added the WIP label Apr 9, 2021
@AR-May AR-May changed the title [DRAFT] current directory virtualization for RAR task Сurrent directory virtualization for RAR task Apr 13, 2021
@AR-May AR-May marked this pull request as ready for review April 13, 2021 12:12
// However, if the combined path consists of different path separators (both windows and unix style),
// then the behavior of Path.GetFullPath differs in windows and unix systems, as in Windows both Windows and Unix style separators works and in Unix - not.
// Windows' function eleminates the internal "./" and "../", Unix's function does not. We are using FixFilePath to remove windows-style separators when on unix machine.
return Path.GetFullPath(Path.Combine(taskExecutionContext.StartupDirectory, FileUtilities.FixFilePath(path)));
Copy link
Member Author

Choose a reason for hiding this comment

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

Need to consider possible switch from Path.GetFullPath -> PathInternal.RemoveRelativeSegments to avoid extra actions in Path.GetFullPath. Unfortunately, RemoveRelativeSegments is not yet available in the Path (dotnet/runtime#2162), so it might only be code duplication.


namespace Microsoft.Build.Tasks
{
public static class TaskExecutionContextExtension
Copy link
Member Author

Choose a reason for hiding this comment

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

Do not need public here?

Copy link
Member

Choose a reason for hiding this comment

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

I don't think so, at least as long as these methods are fixed rather than user-manipulatable. Should this explicitly extend TaskExecutionContext?

Copy link
Member Author

Choose a reason for hiding this comment

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

yes, there was no need for "?" mark in my comment, actually, this has to be private. That is a reminder for myself to fix this together with the other changes.

@AR-May AR-May removed the WIP label Apr 15, 2021
Copy link
Member

@Forgind Forgind left a comment

Choose a reason for hiding this comment

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

In the issue, you mentioned both normalizing and denormalizing as necessary. I saw the first but not the second. Did you determine it's unnecessary?

@@ -0,0 +1,190 @@
using System;
Copy link
Member

Choose a reason for hiding this comment

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

copyright header


string testPath = Path.Combine(Path.GetTempPath(), @"RawFileNameRelative");

Directory.CreateDirectory(testPath);
Copy link
Member

Choose a reason for hiding this comment

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

Creating a temporary directory can be a little cleaner with TestEnvironment, though that might be overkill here.

[Fact]
[Trait("Category", "mono-osx-failing")]
[Trait("Category", "netcore-osx-failing")]
[Trait("Category", "netcore-linux-failing")]
Copy link
Member

Choose a reason for hiding this comment

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

Why is this failing on linux/mac?

Copy link
Member Author

Choose a reason for hiding this comment

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

There is a problem I recently discovered with unit tests for RAR in unix. ResolveAssemblyReferenceTestFixture does not work correctly with unix paths. A lot of tests currently are just excluded for unix/mac because of that (well, that is my suggestion). I managed to fix ResolveAssemblyReferenceTestFixture for all other tests I introduced except this one. This one (and other tests that are currently just do not run in unix) need much more fixes in ResolveAssemblyReferenceTestFixture. I think we should fix it in another PR.

Copy link
Member Author

Choose a reason for hiding this comment

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

so, the problem is in the testing system, with file system's mocking, not with RAR.

Copy link
Member

Choose a reason for hiding this comment

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

I tried to glance through that to see if it was a quick fix, and it's clear that some parts were designed with cross-plat in mind...but that's a really big class, and a lot of it wasn't. Sounds like a lot of work for not enough return on investment to me.

Copy link
Member Author

@AR-May AR-May Apr 20, 2021

Choose a reason for hiding this comment

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

hmm, so far I saw the only problem with this class which prevents it to be cross-plat: the comparison of paths (well, at least tests I saw were failing because of that). Yes, this is a big class and you need to fix it in a lot of places, that's why I did not want to do this in this PR. This is very technical fix and I did not think it will take so much time. But maybe you are right, there also might be other changes needed to make it cross platform, I did not thought much about that.


Execute(rarTask);

Assert.Single(rarTask.ResolvedFiles);
Copy link
Member

Choose a reason for hiding this comment

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

nit: please use Shouldly for asserts.

@@ -1,4 +1,5 @@
using Microsoft.Build.Shared;
using Microsoft.Build.Framework;
Copy link
Member

Choose a reason for hiding this comment

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

copyright header

@@ -438,7 +445,7 @@ string assemblyFileName

if (!Path.IsPathRooted(assemblyFileName))
{
reference.FullPath = Path.GetFullPath(assemblyFileName);
reference.FullPath = Path.GetFullPath(_executionContext.GetFullPath(assemblyFileName));
Copy link
Member

Choose a reason for hiding this comment

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

This is calling GetFullPath twice. Simplify? (Same for other occurrences.)

Copy link
Member Author

Choose a reason for hiding this comment

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

Nope, _executionContext.GetFullPath sometimes, if we do not have any execution context, should not absolutize the path. This is to keep previous behavior as an escape hatch when we do not use RAR as a service. Maybe I should rename the func to clarify this.

Copy link
Member

Choose a reason for hiding this comment

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

Maybe renaming to _executionContext.MapPath can imply it is not just FullPath what can happen.

/// Execution context used when task is supposed to run concurrently in multiple threads.
/// If null hosting process do not run this task concurrently and set it execution context on process level.
/// </summary>
public TaskExecutionContext ConcurrencyExecutionContext
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 an auto-property?

@@ -3049,5 +3074,60 @@ public override bool Execute()
}

#endregion

void IConcurrentTask.ConfigureForConcurrentExecution(TaskExecutionContext executionContext)
Copy link
Member

Choose a reason for hiding this comment

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

nit: private

/// <returns></returns>
public static string GetFullPath(this TaskExecutionContext taskExecutionContext, string path)
{
if (String.IsNullOrEmpty(taskExecutionContext.StartupDirectory) || String.IsNullOrEmpty(path))
Copy link
Member

Choose a reason for hiding this comment

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

This is almost identical to FileUtilities.GetFullPath, though that does some extra escaping/unescaping. Do you think any of that is necessary here, and if so, should we just call that?

Copy link
Member

Choose a reason for hiding this comment

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

FileUtilities.GetFullPath take path and if it is relative absolutize it based on running process 'current directory' while
method takes path and if it is relative absolutize it based on taskExecutionContext.StartupDirectory.
@Forgind What escaping do you have on mind on particular?

Copy link
Member Author

Choose a reason for hiding this comment

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

FileUtilities.GetFullPath gives absolute path using the wrong current directory, without virtualization. Here we use taskExecutionContext.StartupDirectory as our current directory to complete the relative path (if it exists).

Copy link
Member

Choose a reason for hiding this comment

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

FileUtilities has an overload to let you specify currentDirectory:

internal static string GetFullPath(string fileSpec, string currentDirectory)
{
// Sending data out of the engine into the filesystem, so time to unescape.
fileSpec = FixFilePath(EscapingUtilities.UnescapeAll(fileSpec));
// Data coming back from the filesystem into the engine, so time to escape it back.
string fullPath = EscapingUtilities.Escape(NormalizePath(Path.Combine(currentDirectory, fileSpec)));
if (NativeMethodsShared.IsWindows && !EndsWithSlash(fullPath))
{
if (FileUtilitiesRegex.IsDrivePattern(fileSpec) ||
FileUtilitiesRegex.IsUncPattern(fullPath))
{
// append trailing slash if Path.GetFullPath failed to (this happens with drive-specs and UNC shares)
fullPath += Path.DirectorySeparatorChar;
}
}
return fullPath;
}

Copy link
Member

Choose a reason for hiding this comment

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

Ohh. I wasn't aware of that. I believe this method is to be used during evaluation or other processing on unescaped data. Its name should be GetFullPathEscaped. Since changed code under review deals with already unescaped Task inputs, we can't use FileUtilities.GetFullPath for that matter.


namespace Microsoft.Build.Tasks
{
public static class TaskExecutionContextExtension
Copy link
Member

Choose a reason for hiding this comment

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

I don't think so, at least as long as these methods are fixed rather than user-manipulatable. Should this explicitly extend TaskExecutionContext?

@AR-May
Copy link
Member Author

AR-May commented Apr 19, 2021

In the issue, you mentioned both normalizing and denormalizing as necessary. I saw the first but not the second. Did you determine it's unnecessary?

I currently think denormalizing is not necessary, but I would like to ask about that more. It is my current understanding that all output is currently supposed to be absolute.
In the exp branch I also checked if we get any problems if we normalize all the output and insertion VS tests does not show any.

@AR-May AR-May marked this pull request as draft April 19, 2021 15:34
@AR-May
Copy link
Member Author

AR-May commented Apr 19, 2021

Moved this PR to draft. We should merge this only with RAR as a Service feature PR, as it is meaningless to have this code otherwise. The future of RAR as a Service feature (when/if it will be merged) is currently unclear.

}
}

// TODO: move to own file
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
// TODO: move to own file

Copy link
Member

Choose a reason for hiding this comment

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

Lets keep it here.

Comment on lines +64 to +65
string directory = FullSearchPath;
string resolvedPath = ResolveFromDirectory(assemblyName, isPrimaryProjectReference, wantSpecificVersion, executableExtensions, directory, assembliesConsideredAndRejected);
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
string directory = FullSearchPath;
string resolvedPath = ResolveFromDirectory(assemblyName, isPrimaryProjectReference, wantSpecificVersion, executableExtensions, directory, assembliesConsideredAndRejected);
string resolvedPath = ResolveFromDirectory(assemblyName, isPrimaryProjectReference, wantSpecificVersion, executableExtensions, directory: FullSearchPath, assembliesConsideredAndRejected);

On the second though, I think we can have it even more readable by above changes:

/// <returns></returns>
public static string GetFullPath(this TaskExecutionContext taskExecutionContext, string path)
{
if (String.IsNullOrEmpty(taskExecutionContext.StartupDirectory) || String.IsNullOrEmpty(path))
Copy link
Member

Choose a reason for hiding this comment

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

FileUtilities.GetFullPath take path and if it is relative absolutize it based on running process 'current directory' while
method takes path and if it is relative absolutize it based on taskExecutionContext.StartupDirectory.
@Forgind What escaping do you have on mind on particular?

/// <returns></returns>
public static string GetFullPath(this TaskExecutionContext taskExecutionContext, string path)
{
if (String.IsNullOrEmpty(taskExecutionContext.StartupDirectory) || String.IsNullOrEmpty(path))
Copy link
Member

Choose a reason for hiding this comment

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

Ohh. I wasn't aware of that. I believe this method is to be used during evaluation or other processing on unescaped data. Its name should be GetFullPathEscaped. Since changed code under review deals with already unescaped Task inputs, we can't use FileUtilities.GetFullPath for that matter.

Comment on lines +67 to +69

rawFileNameCandidate = executionContext.GetFullPath(rawFileNameCandidate);

Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
rawFileNameCandidate = executionContext.GetFullPath(rawFileNameCandidate);
rawFileNameCandidate = executionContext.GetFullPath(rawFileNameCandidate);

NIT: unnecessary empty lines

}
else
{
_concurrencyExecutionContext = new TaskExecutionContext();
Copy link
Member

Choose a reason for hiding this comment

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

Please comment here the intent of using default TaskExecutionContext behavior i.e. using 'process current directory' for relative paths.

@@ -438,7 +445,7 @@ string assemblyFileName

if (!Path.IsPathRooted(assemblyFileName))
{
reference.FullPath = Path.GetFullPath(assemblyFileName);
reference.FullPath = Path.GetFullPath(_executionContext.GetFullPath(assemblyFileName));
Copy link
Member

Choose a reason for hiding this comment

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

Maybe renaming to _executionContext.MapPath can imply it is not just FullPath what can happen.

@@ -1266,8 +1273,7 @@ private void ResolveReference
(
AssemblyNameExtension assemblyName,
string rawFileNameCandidate,
Reference reference
)
Reference reference)
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
Reference reference)
Reference reference
)

@Forgind
Copy link
Member

Forgind commented Jun 1, 2021

Is the future of RARaaS more clear? Wondering if we should close this temporarily until we're ready to go back to it.

@AR-May
Copy link
Member Author

AR-May commented Jun 2, 2021

Work on "RAR as a service" was cut off, I close this PR.

@AR-May AR-May closed this Jun 2, 2021
@AR-May AR-May deleted the current-diretory-virtualization-3 branch March 1, 2024 15:30
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.

RAR Concurrency : current directory virtualization.
3 participants