未验证 提交 fa49dbc3 编写于 作者: M Mateo Torres-Ruiz 提交者: GitHub

Remove unnecessary realpath calls from host probing logic (#50671)

* Skip test on windows

* Delay file existence check

* Check file existence if there are additional probbing paths

* PR feedback

* Add needs_file_existence_checks

* Clean symlinks causing CI problems with stat

* Switch back to libc

* Ignore symlink deletion in osx
上级 a551a99e
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System;
namespace StandaloneApp
{
public static class Program
{
public static void Main(string[] args)
{
Console.WriteLine("Hello World!");
}
}
}
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<AssemblyName>StandaloneApp</AssemblyName>
<TargetFrameworks>net6.0</TargetFrameworks>
<OutputType>Exe</OutputType>
<RuntimeIdentifier>$(TestTargetRid)</RuntimeIdentifier>
</PropertyGroup>
</Project>
...@@ -59,7 +59,7 @@ public void ComponentWithNoDependenciesCaseChangedOnAsm() ...@@ -59,7 +59,7 @@ public void ComponentWithNoDependenciesCaseChangedOnAsm()
// Linux: we fail // Linux: we fail
// Windows and Mac, probing succeeds but // Windows and Mac, probing succeeds but
// Windows: probing returns the original name // Windows: probing returns the original name
// Mac: probing return the new name including 2 assembly probing with the same new name and the changed deps file // Mac: probing return the new name including 2 assembly probing with the original and new name, and the changed deps file
var component = sharedTestState.ComponentWithNoDependencies.Copy(); var component = sharedTestState.ComponentWithNoDependencies.Copy();
...@@ -89,7 +89,7 @@ public void ComponentWithNoDependenciesCaseChangedOnAsm() ...@@ -89,7 +89,7 @@ public void ComponentWithNoDependenciesCaseChangedOnAsm()
sharedTestState.RunComponentResolutionTest(component) sharedTestState.RunComponentResolutionTest(component)
.Should().Pass() .Should().Pass()
.And.HaveStdOutContaining("corehost_resolve_component_dependencies:Success") .And.HaveStdOutContaining("corehost_resolve_component_dependencies:Success")
.And.HaveStdOutContaining($"corehost_resolve_component_dependencies assemblies:[{changeFile}{Path.PathSeparator}{changeFile}{Path.PathSeparator}]") .And.HaveStdOutContaining($"corehost_resolve_component_dependencies assemblies:[{component.AppDll}{Path.PathSeparator}{changeFile}{Path.PathSeparator}]")
.And.HaveStdErrContaining($"app_root='{component.Location}{Path.DirectorySeparatorChar}'") .And.HaveStdErrContaining($"app_root='{component.Location}{Path.DirectorySeparatorChar}'")
.And.HaveStdErrContaining($"deps='{changeDepsFile}'") .And.HaveStdErrContaining($"deps='{changeDepsFile}'")
.And.HaveStdErrContaining($"mgd_app='{changeFile}'"); .And.HaveStdErrContaining($"mgd_app='{changeFile}'");
...@@ -115,7 +115,7 @@ public void ComponentWithNoDependenciesCaseChangedOnDepsAndAsm() ...@@ -115,7 +115,7 @@ public void ComponentWithNoDependenciesCaseChangedOnDepsAndAsm()
// Linux: we fail // Linux: we fail
// Windows and Mac, probing succeeds but // Windows and Mac, probing succeeds but
// Windows: probing returns the original name // Windows: probing returns the original name
// Mac: probing return the new name including 2 assembly probing with the same new name and the changed deps file // Mac: probing return the new name including 2 assembly probing with the original and new name, and the changed deps file
var component = sharedTestState.ComponentWithNoDependencies.Copy(); var component = sharedTestState.ComponentWithNoDependencies.Copy();
...@@ -146,7 +146,7 @@ public void ComponentWithNoDependenciesCaseChangedOnDepsAndAsm() ...@@ -146,7 +146,7 @@ public void ComponentWithNoDependenciesCaseChangedOnDepsAndAsm()
sharedTestState.RunComponentResolutionTest(component) sharedTestState.RunComponentResolutionTest(component)
.Should().Pass() .Should().Pass()
.And.HaveStdOutContaining("corehost_resolve_component_dependencies:Success") .And.HaveStdOutContaining("corehost_resolve_component_dependencies:Success")
.And.HaveStdOutContaining($"corehost_resolve_component_dependencies assemblies:[{changeFile}{Path.PathSeparator}{changeFile}{Path.PathSeparator}]") .And.HaveStdOutContaining($"corehost_resolve_component_dependencies assemblies:[{component.AppDll}{Path.PathSeparator}{changeFile}{Path.PathSeparator}]")
.And.HaveStdErrContaining($"app_root='{component.Location}{Path.DirectorySeparatorChar}'") .And.HaveStdErrContaining($"app_root='{component.Location}{Path.DirectorySeparatorChar}'")
.And.HaveStdErrContaining($"deps='{changeDepsFile}'") .And.HaveStdErrContaining($"deps='{changeDepsFile}'")
.And.HaveStdErrContaining($"mgd_app='{changeFile}'"); .And.HaveStdErrContaining($"mgd_app='{changeFile}'");
...@@ -242,7 +242,8 @@ public void ComponentWithDependencies() ...@@ -242,7 +242,8 @@ public void ComponentWithDependencies()
$"{Path.Combine(sharedTestState.ComponentWithDependencies.Location, "Newtonsoft.Json.dll")}{Path.PathSeparator}]") $"{Path.Combine(sharedTestState.ComponentWithDependencies.Location, "Newtonsoft.Json.dll")}{Path.PathSeparator}]")
.And.HaveStdOutContaining( .And.HaveStdOutContaining(
$"corehost_resolve_component_dependencies native_search_paths:[" + $"corehost_resolve_component_dependencies native_search_paths:[" +
$"{ExpectedProbingPaths(Path.Combine(sharedTestState.ComponentWithDependencies.Location, "runtimes", "win10-x86", "native"))}]"); $"{Path.Combine(sharedTestState.ComponentWithDependencies.Location, "runtimes", "win10-x86", "native")}" +
$"{Path.DirectorySeparatorChar}{Path.PathSeparator}]");
} }
[Fact] [Fact]
...@@ -251,7 +252,6 @@ public void ComponentWithDependenciesAndDependencyRemoved() ...@@ -251,7 +252,6 @@ public void ComponentWithDependenciesAndDependencyRemoved()
var component = sharedTestState.ComponentWithDependencies.Copy(); var component = sharedTestState.ComponentWithDependencies.Copy();
// Remove a dependency // Remove a dependency
// This will cause the resolution to fail
File.Delete(Path.Combine(component.Location, "ComponentDependency.dll")); File.Delete(Path.Combine(component.Location, "ComponentDependency.dll"));
sharedTestState.RunComponentResolutionTest(component) sharedTestState.RunComponentResolutionTest(component)
...@@ -259,6 +259,7 @@ public void ComponentWithDependenciesAndDependencyRemoved() ...@@ -259,6 +259,7 @@ public void ComponentWithDependenciesAndDependencyRemoved()
.And.HaveStdOutContaining("corehost_resolve_component_dependencies:Success") .And.HaveStdOutContaining("corehost_resolve_component_dependencies:Success")
.And.HaveStdOutContaining( .And.HaveStdOutContaining(
$"corehost_resolve_component_dependencies assemblies:[" + $"corehost_resolve_component_dependencies assemblies:[" +
$"{Path.Combine(component.Location, "ComponentDependency.dll")}{Path.PathSeparator}" +
$"{component.AppDll}{Path.PathSeparator}" + $"{component.AppDll}{Path.PathSeparator}" +
$"{Path.Combine(component.Location, "Newtonsoft.Json.dll")}{Path.PathSeparator}]"); $"{Path.Combine(component.Location, "Newtonsoft.Json.dll")}{Path.PathSeparator}]");
} }
...@@ -369,36 +370,8 @@ public void ComponentWithResourcesShouldReportResourceSearchPaths() ...@@ -369,36 +370,8 @@ public void ComponentWithResourcesShouldReportResourceSearchPaths()
.Should().Pass() .Should().Pass()
.And.HaveStdOutContaining("corehost_resolve_component_dependencies:Success") .And.HaveStdOutContaining("corehost_resolve_component_dependencies:Success")
.And.HaveStdOutContaining($"corehost_resolve_component_dependencies resource_search_paths:[" + .And.HaveStdOutContaining($"corehost_resolve_component_dependencies resource_search_paths:[" +
$"{ExpectedProbingPaths(sharedTestState.ComponentWithResources.Location)}]"); $"{sharedTestState.ComponentWithResources.Location}" +
} $"{Path.DirectorySeparatorChar}{Path.PathSeparator}]");
private string ExpectedProbingPaths(params string[] paths)
{
string result = string.Empty;
foreach (string path in paths)
{
string expectedPath = path;
if (expectedPath.EndsWith(Path.DirectorySeparatorChar))
{
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
// On non-windows the paths are normalized to not end with a /
expectedPath = expectedPath.Substring(0, expectedPath.Length - 1);
}
}
else
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
// On windows all paths are normalized to end with a \
expectedPath += Path.DirectorySeparatorChar;
}
}
result += expectedPath + Path.PathSeparator;
}
return result;
} }
[Fact] [Fact]
...@@ -425,7 +398,8 @@ public void MultiThreadedComponentDependencyResolutionWhichSucceeeds() ...@@ -425,7 +398,8 @@ public void MultiThreadedComponentDependencyResolutionWhichSucceeeds()
.And.HaveStdOutContaining($"ComponentA: corehost_resolve_component_dependencies assemblies:[{sharedTestState.ComponentWithNoDependencies.AppDll}{Path.PathSeparator}]") .And.HaveStdOutContaining($"ComponentA: corehost_resolve_component_dependencies assemblies:[{sharedTestState.ComponentWithNoDependencies.AppDll}{Path.PathSeparator}]")
.And.HaveStdOutContaining($"ComponentB: corehost_resolve_component_dependencies:Success") .And.HaveStdOutContaining($"ComponentB: corehost_resolve_component_dependencies:Success")
.And.HaveStdOutContaining($"ComponentB: corehost_resolve_component_dependencies resource_search_paths:[" + .And.HaveStdOutContaining($"ComponentB: corehost_resolve_component_dependencies resource_search_paths:[" +
$"{ExpectedProbingPaths(sharedTestState.ComponentWithResources.Location)}]"); $"{sharedTestState.ComponentWithResources.Location}" +
$"{Path.DirectorySeparatorChar}{Path.PathSeparator}]");
} }
[Fact] [Fact]
......
...@@ -40,7 +40,7 @@ public void BasicApp() ...@@ -40,7 +40,7 @@ public void BasicApp()
CommandResult result = sharedState.CreateNativeHostCommand(args, sharedState.DotNet.BinPath) CommandResult result = sharedState.CreateNativeHostCommand(args, sharedState.DotNet.BinPath)
.Execute(); .Execute();
string pathSuffix = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? Path.DirectorySeparatorChar.ToString() : string.Empty; string pathSuffix = Path.DirectorySeparatorChar.ToString();
string expectedSearchDirectories = string expectedSearchDirectories =
Path.GetDirectoryName(sharedState.AppPath) + pathSuffix + Path.PathSeparator + Path.GetDirectoryName(sharedState.AppPath) + pathSuffix + Path.PathSeparator +
Path.Combine(sharedState.DotNet.BinPath, "shared", "Microsoft.NETCore.App", SharedTestState.NetCoreAppVersion) + pathSuffix + Path.PathSeparator; Path.Combine(sharedState.DotNet.BinPath, "shared", "Microsoft.NETCore.App", SharedTestState.NetCoreAppVersion) + pathSuffix + Path.PathSeparator;
......
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using FluentAssertions;
using Microsoft.DotNet.Cli.Build.Framework;
using Microsoft.DotNet.CoreSetup.Test;
using System;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using Xunit;
namespace Microsoft.NET.HostModel.Tests
{
public class AppHostUsedWithSymbolicLinks : IClassFixture<AppHostUsedWithSymbolicLinks.SharedTestState>
{
private SharedTestState sharedTestState;
private void CreateSymbolicLink(string source, string target)
{
if (!SymbolicLinking.MakeSymbolicLink(source, target, out var errorString))
throw new Exception($"Failed to create symbolic link '{source}' targeting: '{target}': {errorString}");
}
public AppHostUsedWithSymbolicLinks(AppHostUsedWithSymbolicLinks.SharedTestState fixture)
{
sharedTestState = fixture;
}
[Theory]
[InlineData ("a/b/SymlinkToApphost")]
[InlineData ("a/SymlinkToApphost")]
public void Run_apphost_behind_symlink(string symlinkRelativePath)
{
// Creating symbolic links requires administrative privilege on Windows, so skip test.
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
return;
var fixture = sharedTestState.StandaloneAppFixture_Published
.Copy();
var appExe = fixture.TestProject.AppExe;
var testDir = Directory.GetParent(fixture.TestProject.Location).ToString();
Directory.CreateDirectory(Path.Combine(testDir, Path.GetDirectoryName(symlinkRelativePath)));
var symlinkFullPath = Path.Combine(testDir, symlinkRelativePath);
CreateSymbolicLink(symlinkFullPath, appExe);
Command.Create(symlinkFullPath)
.CaptureStdErr()
.CaptureStdOut()
.Execute()
.Should().Pass()
.And.HaveStdOutContaining("Hello World");
}
[Theory]
[InlineData ("a/b/FirstSymlink", "c/d/SecondSymlink")]
[InlineData ("a/b/FirstSymlink", "c/SecondSymlink")]
[InlineData ("a/FirstSymlink", "c/d/SecondSymlink")]
[InlineData ("a/FirstSymlink", "c/SecondSymlink")]
public void Run_apphost_behind_transitive_symlinks(string firstSymlinkRelativePath, string secondSymlinkRelativePath)
{
// Creating symbolic links requires administrative privilege on Windows, so skip test.
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
return;
var fixture = sharedTestState.StandaloneAppFixture_Published
.Copy();
var appExe = fixture.TestProject.AppExe;
var testDir = Directory.GetParent(fixture.TestProject.Location).ToString();
Directory.CreateDirectory(Path.Combine(testDir, Path.GetDirectoryName(firstSymlinkRelativePath)));
Directory.CreateDirectory(Path.Combine(testDir, Path.GetDirectoryName(secondSymlinkRelativePath)));
// second symlink -> apphost
string secondSymbolicLink = Path.Combine(testDir, secondSymlinkRelativePath);
CreateSymbolicLink(secondSymbolicLink, appExe);
// first symlink -> second symlink
string firstSymbolicLink = Path.Combine(testDir, firstSymlinkRelativePath);
CreateSymbolicLink(firstSymbolicLink, secondSymbolicLink);
Command.Create(firstSymbolicLink)
.CaptureStdErr()
.CaptureStdOut()
.Execute()
.Should().Pass()
.And.HaveStdOutContaining("Hello World");
if (!RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
{
Directory.Delete(firstSymbolicLink);
Directory.Delete(secondSymbolicLink);
}
}
//[Theory]
//[InlineData("a/b/SymlinkToFrameworkDependentApp")]
//[InlineData("a/SymlinkToFrameworkDependentApp")]
[Fact(Skip = "Currently failing in OSX with \"No such file or directory\" when running Command.Create. " +
"CI failing to use stat on symbolic links on Linux (permission denied).")]
public void Run_framework_dependent_app_behind_symlink(/* string symlinkRelativePath */)
{
// Creating symbolic links requires administrative privilege on Windows, so skip test.
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
return;
string symlinkRelativePath = string.Empty;
var fixture = sharedTestState.FrameworkDependentAppFixture_Published
.Copy();
var appExe = fixture.TestProject.AppExe;
var builtDotnet = fixture.BuiltDotnet.BinPath;
var testDir = Directory.GetParent(fixture.TestProject.Location).ToString();
Directory.CreateDirectory(Path.Combine(testDir, Path.GetDirectoryName(symlinkRelativePath)));
var symlinkFullPath = Path.Combine(testDir, symlinkRelativePath);
CreateSymbolicLink(symlinkFullPath, appExe);
Command.Create(symlinkFullPath)
.CaptureStdErr()
.CaptureStdOut()
.EnvironmentVariable("DOTNET_ROOT", builtDotnet)
.EnvironmentVariable("DOTNET_ROOT(x86)", builtDotnet)
.Execute()
.Should().Pass()
.And.HaveStdOutContaining("Hello World");
}
[Fact(Skip = "Currently failing in OSX with \"No such file or directory\" when running Command.Create. " +
"CI failing to use stat on symbolic links on Linux (permission denied).")]
public void Run_framework_dependent_app_with_runtime_behind_symlink()
{
// Creating symbolic links requires administrative privilege on Windows, so skip test.
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
return;
var fixture = sharedTestState.FrameworkDependentAppFixture_Published
.Copy();
var appExe = fixture.TestProject.AppExe;
var testDir = Directory.GetParent(fixture.TestProject.Location).ToString();
var dotnetSymlink = Path.Combine(testDir, "dotnet");
var dotnetDir = fixture.BuiltDotnet.BinPath;
CreateSymbolicLink(dotnetSymlink, dotnetDir);
Command.Create(appExe)
.EnvironmentVariable("DOTNET_ROOT", dotnetSymlink)
.CaptureStdErr()
.CaptureStdOut()
.Execute()
.Should().Pass()
.And.HaveStdOutContaining("Hello World");
}
[Fact]
public void Put_app_directory_behind_symlink()
{
// Creating symbolic links requires administrative privilege on Windows, so skip test.
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
return;
var fixture = sharedTestState.StandaloneAppFixture_Published
.Copy();
var appExe = fixture.TestProject.AppExe;
var binDir = fixture.TestProject.OutputDirectory;
var binDirNewPath = Path.Combine(Directory.GetParent(fixture.TestProject.Location).ToString(), "PutTheBinDirSomewhereElse");
Directory.Move(binDir, binDirNewPath);
CreateSymbolicLink(binDir, binDirNewPath);
Command.Create(appExe)
.CaptureStdErr()
.CaptureStdOut()
.Execute()
.Should().Pass()
.And.HaveStdOutContaining("Hello World");
}
[Fact]
public void Put_dotnet_behind_symlink()
{
// Creating symbolic links requires administrative privilege on Windows, so skip test.
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
return;
var fixture = sharedTestState.StandaloneAppFixture_Published
.Copy();
var appDll = fixture.TestProject.AppDll;
var dotnetExe = fixture.BuiltDotnet.DotnetExecutablePath;
var testDir = Directory.GetParent(fixture.TestProject.Location).ToString();
var dotnetSymlink = Path.Combine(testDir, "dotnet");
CreateSymbolicLink(dotnetSymlink, dotnetExe);
Command.Create(dotnetSymlink, fixture.TestProject.AppDll)
.CaptureStdErr()
.CaptureStdOut()
.Execute()
.Should().Pass()
.And.HaveStdOutContaining("Hello World");
if (!RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
{
Directory.Delete(dotnetSymlink);
}
}
[Fact]
public void Put_app_directory_behind_symlink_and_use_dotnet()
{
// Creating symbolic links requires administrative privilege on Windows, so skip test.
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
return;
var fixture = sharedTestState.StandaloneAppFixture_Published
.Copy();
var dotnet = fixture.BuiltDotnet;
var binDir = fixture.TestProject.OutputDirectory;
var binDirNewPath = Path.Combine(Directory.GetParent(fixture.TestProject.Location).ToString(), "PutTheBinDirSomewhereElse");
Directory.Move(binDir, binDirNewPath);
CreateSymbolicLink(binDir, binDirNewPath);
dotnet.Exec(fixture.TestProject.AppDll)
.CaptureStdErr()
.CaptureStdOut()
.Execute()
.Should().Pass()
.And.HaveStdOutContaining("Hello World");
}
[Fact]
public void Put_app_directory_behind_symlink_and_use_dotnet_run()
{
// Creating symbolic links requires administrative privilege on Windows, so skip test.
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
return;
var fixture = sharedTestState.StandaloneAppFixture_Published
.Copy();
var dotnet = fixture.SdkDotnet;
var binDir = fixture.TestProject.OutputDirectory;
var binDirNewPath = Path.Combine(Directory.GetParent(fixture.TestProject.Location).ToString(), "PutTheBinDirSomewhereElse");
Directory.Move(binDir, binDirNewPath);
CreateSymbolicLink(binDir, binDirNewPath);
dotnet.Exec("run")
.WorkingDirectory(fixture.TestProject.Location)
.CaptureStdErr()
.CaptureStdOut()
.Execute()
.Should().Pass()
.And.HaveStdOutContaining("Hello World");
}
[Fact]
public void Put_satellite_assembly_behind_symlink()
{
// Creating symbolic links requires administrative privilege on Windows, so skip test.
// If enabled, this tests will need to set the console code page to output unicode characters:
// Command.Create("chcp 65001").Execute();
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
return;
var fixture = sharedTestState.StandaloneAppFixture_Localized
.Copy();
var appExe = fixture.TestProject.AppExe;
var binDir = fixture.TestProject.OutputDirectory;
var satellitesDir = Path.Combine(Directory.GetParent(fixture.TestProject.Location).ToString(), "PutSatellitesSomewhereElse");
Directory.CreateDirectory(satellitesDir);
var firstSatelliteDir = Directory.GetDirectories(binDir).Single(dir => dir.Contains("kn-IN"));
var firstSatelliteNewDir = Path.Combine(satellitesDir, "kn-IN");
Directory.Move(firstSatelliteDir, firstSatelliteNewDir);
CreateSymbolicLink(firstSatelliteDir, firstSatelliteNewDir);
var secondSatelliteDir = Directory.GetDirectories(binDir).Single(dir => dir.Contains("ta-IN"));
var secondSatelliteNewDir = Path.Combine(satellitesDir, "ta-IN");
Directory.Move(secondSatelliteDir, secondSatelliteNewDir);
CreateSymbolicLink(secondSatelliteDir, secondSatelliteNewDir);
Command.Create(appExe)
.CaptureStdErr()
.CaptureStdOut()
.Execute()
.Should().Pass()
.And.HaveStdOutContaining("ನಮಸ್ಕಾರ! வணக்கம்! Hello!");
}
public class SharedTestState : IDisposable
{
public TestProjectFixture StandaloneAppFixture_Localized { get; }
public TestProjectFixture StandaloneAppFixture_Published { get; }
public TestProjectFixture FrameworkDependentAppFixture_Published { get; }
public RepoDirectoriesProvider RepoDirectories { get; }
public SharedTestState()
{
RepoDirectories = new RepoDirectoriesProvider();
var localizedFixture = new TestProjectFixture("LocalizedApp", RepoDirectories);
localizedFixture
.EnsureRestoredForRid(localizedFixture.CurrentRid)
.PublishProject(runtime: localizedFixture.CurrentRid);
var publishFixture = new TestProjectFixture("StandaloneApp", RepoDirectories);
publishFixture
.EnsureRestoredForRid(publishFixture.CurrentRid)
.PublishProject(runtime: publishFixture.CurrentRid);
var fwPublishedFixture = new TestProjectFixture("PortableApp", RepoDirectories);
fwPublishedFixture
.EnsureRestored()
.PublishProject();
StandaloneAppFixture_Localized = localizedFixture;
StandaloneAppFixture_Published = publishFixture;
FrameworkDependentAppFixture_Published = fwPublishedFixture;
}
public void Dispose()
{
StandaloneAppFixture_Localized.Dispose();
StandaloneAppFixture_Published.Dispose();
FrameworkDependentAppFixture_Published.Dispose();
}
}
}
}
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System;
using System.Runtime.InteropServices;
namespace Microsoft.DotNet.CoreSetup.Test
{
public static class SymbolicLinking
{
static class Kernel32
{
[Flags]
internal enum SymbolicLinkFlag
{
IsFile = 0x0,
IsDirectory = 0x1,
AllowUnprivilegedCreate = 0x2
}
[DllImport("kernel32.dll", SetLastError = true)]
internal static extern bool CreateSymbolicLink(
string symbolicLinkName,
string targetFileName,
SymbolicLinkFlag flags);
}
static class libc
{
[DllImport("libc", SetLastError = true)]
internal static extern int symlink(
string targetFileName,
string linkPath);
[DllImport("libc", CharSet = CharSet.Ansi)]
internal static extern IntPtr strerror(int errnum);
}
public static bool MakeSymbolicLink(string symbolicLinkName, string targetFileName, out string errorMessage)
{
errorMessage = string.Empty;
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
if (!Kernel32.CreateSymbolicLink(symbolicLinkName, targetFileName, Kernel32.SymbolicLinkFlag.IsFile))
{
int errno = Marshal.GetLastWin32Error();
errorMessage = $"CreateSymbolicLink failed with error number {errno}";
return false;
}
}
else
{
if (libc.symlink(targetFileName, symbolicLinkName) == -1)
{
int errno = Marshal.GetLastWin32Error();
errorMessage = Marshal.PtrToStringAnsi(libc.strerror(errno));
return false;
}
}
return true;
}
}
}
...@@ -30,17 +30,15 @@ static pal::string_t normalize_dir_separator(const pal::string_t& path) ...@@ -30,17 +30,15 @@ static pal::string_t normalize_dir_separator(const pal::string_t& path)
// Parameters: // Parameters:
// base - The base directory to look for the relative path of this entry // base - The base directory to look for the relative path of this entry
// ietf_dir - If this is a resource asset, the IETF intermediate directory // ietf_dir - If this is a resource asset, the IETF intermediate directory
// look_in_base - Whether to search as a relative path
// look_in_bundle - Whether to look within the single-file bundle
// is_servicing - Whether the base directory is the core-servicing directory
// str - (out parameter) If the method returns true, contains the file path for this deps entry // str - (out parameter) If the method returns true, contains the file path for this deps entry
// search_options - Flags to instruct where to look for this deps entry
// found_in_bundle - (out parameter) True if the candidate is located within the single-file bundle. // found_in_bundle - (out parameter) True if the candidate is located within the single-file bundle.
// //
// Returns: // Returns:
// If the file exists in the path relative to the "base" directory within the // If the file exists in the path relative to the "base" directory within the
// single-file or on disk. // single-file or on disk.
bool deps_entry_t::to_path(const pal::string_t& base, const pal::string_t& ietf_dir, bool look_in_base, bool look_in_bundle, bool is_servicing, pal::string_t* str, bool &found_in_bundle) const bool deps_entry_t::to_path(const pal::string_t& base, const pal::string_t& ietf_dir, pal::string_t* str, uint32_t search_options, bool &found_in_bundle) const
{ {
pal::string_t& candidate = *str; pal::string_t& candidate = *str;
...@@ -58,6 +56,9 @@ bool deps_entry_t::to_path(const pal::string_t& base, const pal::string_t& ietf_ ...@@ -58,6 +56,9 @@ bool deps_entry_t::to_path(const pal::string_t& base, const pal::string_t& ietf_
// Reserve space for the path below // Reserve space for the path below
candidate.reserve(base.length() + ietf_dir.length() + normalized_path.length() + 3); candidate.reserve(base.length() + ietf_dir.length() + normalized_path.length() + 3);
bool look_in_base = search_options & deps_entry_t::search_options::look_in_base;
bool look_in_bundle = search_options & deps_entry_t::search_options::look_in_bundle;
bool is_servicing = search_options & deps_entry_t::search_options::is_servicing;
pal::string_t file_path = look_in_base ? get_filename(normalized_path) : normalized_path; pal::string_t file_path = look_in_base ? get_filename(normalized_path) : normalized_path;
pal::string_t sub_path = ietf_dir; pal::string_t sub_path = ietf_dir;
append_path(&sub_path, file_path.c_str()); append_path(&sub_path, file_path.c_str());
...@@ -94,36 +95,41 @@ bool deps_entry_t::to_path(const pal::string_t& base, const pal::string_t& ietf_ ...@@ -94,36 +95,41 @@ bool deps_entry_t::to_path(const pal::string_t& base, const pal::string_t& ietf_
candidate.assign(base); candidate.assign(base);
append_path(&candidate, sub_path.c_str()); append_path(&candidate, sub_path.c_str());
bool exists = pal::file_exists(candidate);
const pal::char_t* query_type = look_in_base ? _X("Local") : _X("Relative"); const pal::char_t* query_type = look_in_base ? _X("Local") : _X("Relative");
if (!exists) if (search_options & deps_entry_t::search_options::file_existence)
{ {
trace::verbose(_X(" %s path query did not exist %s"), query_type, candidate.c_str()); if (!pal::file_exists(candidate))
candidate.clear(); {
trace::verbose(_X(" %s path query did not exist %s"), query_type, candidate.c_str());
candidate.clear();
return false;
}
trace::verbose(_X(" %s path query exists %s"), query_type, candidate.c_str());
} }
else else
{ {
trace::verbose(_X(" %s path query exists %s"), query_type, candidate.c_str()); trace::verbose(_X(" %s path query %s (skipped file existence check)"), query_type, candidate.c_str());
}
// If a file is resolved to the servicing directory, mark it as disabled in the bundle. // If a file is resolved to the servicing directory, mark it as disabled in the bundle.
// This step is necessary because runtime will try to resolve assemblies from the bundle // This step is necessary because runtime will try to resolve assemblies from the bundle
// before it uses the TPA. So putting the servicing entry into TPA is not enough, since runtime would // before it uses the TPA. So putting the servicing entry into TPA is not enough, since runtime would
// resolve it from the bundle first anyway. Disabling the file's entry in the bundle // resolve it from the bundle first anyway. Disabling the file's entry in the bundle
// ensures that the servicing entry in the TPA gets priority. // ensures that the servicing entry in the TPA gets priority.
if (is_servicing && bundle::info_t::is_single_file_bundle()) if (is_servicing && bundle::info_t::is_single_file_bundle())
{ {
bundle::runner_t* app = bundle::runner_t::mutable_app(); bundle::runner_t* app = bundle::runner_t::mutable_app();
assert(!app->has_base(base)); assert(!app->has_base(base));
assert(!found_in_bundle); assert(!found_in_bundle);
if (app->disable(sub_path)) if (app->disable(sub_path))
{ {
trace::verbose(_X(" %s disabled in bundle because of servicing override %s"), sub_path.c_str(), candidate.c_str()); trace::verbose(_X(" %s disabled in bundle because of servicing override %s"), sub_path.c_str(), candidate.c_str());
}
} }
} }
return exists; return true;
} }
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
...@@ -132,12 +138,13 @@ bool deps_entry_t::to_path(const pal::string_t& base, const pal::string_t& ietf_ ...@@ -132,12 +138,13 @@ bool deps_entry_t::to_path(const pal::string_t& base, const pal::string_t& ietf_
// Parameters: // Parameters:
// base - The base directory to look for the relative path of this entry // base - The base directory to look for the relative path of this entry
// str - If the method returns true, contains the file path for this deps entry // str - If the method returns true, contains the file path for this deps entry
// search_options - Flags to instruct where to look for this deps entry
// look_in_bundle - Whether to look within the single-file bundle // look_in_bundle - Whether to look within the single-file bundle
// //
// Returns: // Returns:
// If the file exists in the path relative to the "base" directory. // If the file exists in the path relative to the "base" directory.
// //
bool deps_entry_t::to_dir_path(const pal::string_t& base, bool look_in_bundle, pal::string_t* str, bool& found_in_bundle) const bool deps_entry_t::to_dir_path(const pal::string_t& base, pal::string_t* str, uint32_t search_options, bool& found_in_bundle) const
{ {
pal::string_t ietf_dir; pal::string_t ietf_dir;
...@@ -159,7 +166,10 @@ bool deps_entry_t::to_dir_path(const pal::string_t& base, bool look_in_bundle, p ...@@ -159,7 +166,10 @@ bool deps_entry_t::to_dir_path(const pal::string_t& base, bool look_in_bundle, p
base.c_str(), ietf_dir.c_str(), asset.name.c_str()); base.c_str(), ietf_dir.c_str(), asset.name.c_str());
} }
return to_path(base, ietf_dir, true, look_in_bundle, false, str, found_in_bundle);
search_options |= deps_entry_t::search_options::look_in_base;
search_options &= ~deps_entry_t::search_options::is_servicing;
return to_path(base, ietf_dir, str, search_options, found_in_bundle);
} }
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
...@@ -169,16 +179,16 @@ bool deps_entry_t::to_dir_path(const pal::string_t& base, bool look_in_bundle, p ...@@ -169,16 +179,16 @@ bool deps_entry_t::to_dir_path(const pal::string_t& base, bool look_in_bundle, p
// Parameters: // Parameters:
// base - The base directory to look for the relative path of this entry // base - The base directory to look for the relative path of this entry
// str - If the method returns true, contains the file path for this deps entry // str - If the method returns true, contains the file path for this deps entry
// look_in_bundle - Whether to look within the single-file bundle // search_options - Flags to instruct where to look for this deps entry
// is_servicing - Whether the base directory is the core-servicing directory
// //
// Returns: // Returns:
// If the file exists in the path relative to the "base" directory. // If the file exists in the path relative to the "base" directory.
// //
bool deps_entry_t::to_rel_path(const pal::string_t& base, bool look_in_bundle, bool is_servicing, pal::string_t* str) const bool deps_entry_t::to_rel_path(const pal::string_t& base, pal::string_t* str, uint32_t search_options) const
{ {
bool found_in_bundle; bool found_in_bundle;
bool result = to_path(base, _X(""), false, look_in_bundle, is_servicing, str, found_in_bundle); search_options &= ~deps_entry_t::search_options::look_in_base;
bool result = to_path(base, _X(""), str, search_options, found_in_bundle);
assert(!found_in_bundle); assert(!found_in_bundle);
return result; return result;
} }
...@@ -190,12 +200,12 @@ bool deps_entry_t::to_rel_path(const pal::string_t& base, bool look_in_bundle, b ...@@ -190,12 +200,12 @@ bool deps_entry_t::to_rel_path(const pal::string_t& base, bool look_in_bundle, b
// Parameters: // Parameters:
// base - The base directory to look for the relative path of this entry // base - The base directory to look for the relative path of this entry
// str - If the method returns true, contains the file path for this deps entry // str - If the method returns true, contains the file path for this deps entry
// is_servicing - Whether the base directory is the core-servicing directory // search_options - Flags to instruct where to look for this deps entry
// //
// Returns: // Returns:
// If the file exists in the path relative to the "base" directory. // If the file exists in the path relative to the "base" directory.
// //
bool deps_entry_t::to_full_path(const pal::string_t& base, bool is_servicing, pal::string_t* str) const bool deps_entry_t::to_full_path(const pal::string_t& base, pal::string_t* str, uint32_t search_options) const
{ {
str->clear(); str->clear();
...@@ -217,5 +227,6 @@ bool deps_entry_t::to_full_path(const pal::string_t& base, bool is_servicing, pa ...@@ -217,5 +227,6 @@ bool deps_entry_t::to_full_path(const pal::string_t& base, bool is_servicing, pa
append_path(&new_base, library_path.c_str()); append_path(&new_base, library_path.c_str());
} }
return to_rel_path(new_base, false, is_servicing, str); search_options &= ~deps_entry_t::search_options::look_in_bundle;
return to_rel_path(new_base, str, search_options);
} }
...@@ -36,6 +36,15 @@ struct deps_entry_t ...@@ -36,6 +36,15 @@ struct deps_entry_t
count count
}; };
enum search_options : uint32_t
{
none = 0x0,
look_in_base = 0x1, // Search entry as a relative path
look_in_bundle = 0x2, // Look for entry within the single-file bundle
is_servicing = 0x4, // Whether the base directory is the core-servicing directory
file_existence = 0x8, // Check for entry file existence
};
static const std::array<const pal::char_t*, deps_entry_t::asset_types::count> s_known_asset_types; static const std::array<const pal::char_t*, deps_entry_t::asset_types::count> s_known_asset_types;
pal::string_t deps_file; pal::string_t deps_file;
...@@ -52,18 +61,19 @@ struct deps_entry_t ...@@ -52,18 +61,19 @@ struct deps_entry_t
bool is_rid_specific; bool is_rid_specific;
// Given a "base" dir, yield the file path within this directory or single-file bundle. // Given a "base" dir, yield the file path within this directory or single-file bundle.
bool to_dir_path(const pal::string_t& base, bool look_in_bundle, pal::string_t* str, bool& found_in_bundle) const; bool to_dir_path(const pal::string_t& base, pal::string_t* str, uint32_t search_options, bool& found_in_bundle) const;
// Given a "base" dir, yield the relative path in the package layout or servicing directory. // Given a "base" dir, yield the relative path in the package layout or servicing directory.
bool to_rel_path(const pal::string_t& base, bool look_in_bundle, bool is_servicing, pal::string_t* str) const; bool to_rel_path(const pal::string_t& base, pal::string_t* str, uint32_t search_options) const;
// Given a "base" dir, yield the relative path with package name/version in the package layout or servicing location. // Given a "base" dir, yield the relative path with package name/version in the package layout or servicing location.
bool to_full_path(const pal::string_t& base, bool is_servicing, pal::string_t* str) const; bool to_full_path(const pal::string_t& base, pal::string_t* str, uint32_t search_options) const;
private: private:
// Given a "base" dir, yield the filepath within this directory or relative to this directory based on "look_in_base" // Given a "base" dir, yield the filepath within this directory or relative to this directory based on "look_in_base"
// flag in "search_options".
// Returns a path within the single-file bundle, or a file on disk, // Returns a path within the single-file bundle, or a file on disk,
bool to_path(const pal::string_t& base, const pal::string_t& ietf_code, bool look_in_base, bool look_in_bundle, bool is_servicing, pal::string_t* str, bool & found_in_bundle) const; bool to_path(const pal::string_t& base, const pal::string_t& ietf_code, pal::string_t* str, uint32_t search_options, bool & found_in_bundle) const;
}; };
......
...@@ -43,29 +43,28 @@ void add_unique_path( ...@@ -43,29 +43,28 @@ void add_unique_path(
pal::string_t* non_serviced, pal::string_t* non_serviced,
const pal::string_t& svc_dir) const pal::string_t& svc_dir)
{ {
// Resolve sym links. // To optimize startup time, we avoid calling realpath here.
pal::string_t real = path; // Because of this, there might be duplicates in the output
pal::realpath(&real); // whenever path is eiter non-normalized or a symbolic link.
if (existing->count(path))
if (existing->count(real))
{ {
return; return;
} }
trace::verbose(_X("Adding to %s path: %s"), deps_entry_t::s_known_asset_types[asset_type], real.c_str()); trace::verbose(_X("Adding to %s path: %s"), deps_entry_t::s_known_asset_types[asset_type], path.c_str());
if (starts_with(real, svc_dir, false)) if (starts_with(path, svc_dir, false))
{ {
serviced->append(real); serviced->append(path);
serviced->push_back(PATH_SEPARATOR); serviced->push_back(PATH_SEPARATOR);
} }
else else
{ {
non_serviced->append(real); non_serviced->append(path);
non_serviced->push_back(PATH_SEPARATOR); non_serviced->push_back(PATH_SEPARATOR);
} }
existing->insert(real); existing->insert(path);
} }
// Return the filename from deps path; a deps path always uses a '/' for the separator. // Return the filename from deps path; a deps path always uses a '/' for the separator.
...@@ -185,6 +184,7 @@ void deps_resolver_t::setup_shared_store_probes( ...@@ -185,6 +184,7 @@ void deps_resolver_t::setup_shared_store_probes(
{ {
// Shared Store probe: DOTNET_SHARED_STORE environment variable // Shared Store probe: DOTNET_SHARED_STORE environment variable
m_probes.push_back(probe_config_t::lookup(shared)); m_probes.push_back(probe_config_t::lookup(shared));
m_needs_file_existence_checks = true;
} }
} }
...@@ -192,6 +192,7 @@ void deps_resolver_t::setup_shared_store_probes( ...@@ -192,6 +192,7 @@ void deps_resolver_t::setup_shared_store_probes(
{ {
// Path relative to the location of "dotnet.exe" if it's being used to run the app // Path relative to the location of "dotnet.exe" if it's being used to run the app
m_probes.push_back(probe_config_t::lookup(args.dotnet_shared_store)); m_probes.push_back(probe_config_t::lookup(args.dotnet_shared_store));
m_needs_file_existence_checks = true;
} }
for (const auto& global_shared : args.global_shared_stores) for (const auto& global_shared : args.global_shared_stores)
...@@ -200,6 +201,7 @@ void deps_resolver_t::setup_shared_store_probes( ...@@ -200,6 +201,7 @@ void deps_resolver_t::setup_shared_store_probes(
{ {
// Global store probe: the global location // Global store probe: the global location
m_probes.push_back(probe_config_t::lookup(global_shared)); m_probes.push_back(probe_config_t::lookup(global_shared));
m_needs_file_existence_checks = true;
} }
} }
} }
...@@ -236,6 +238,8 @@ void deps_resolver_t::setup_probe_config( ...@@ -236,6 +238,8 @@ void deps_resolver_t::setup_probe_config(
pal::string_t ext_pkgs = args.core_servicing; pal::string_t ext_pkgs = args.core_servicing;
append_path(&ext_pkgs, _X("pkgs")); append_path(&ext_pkgs, _X("pkgs"));
m_probes.push_back(probe_config_t::svc(ext_pkgs)); m_probes.push_back(probe_config_t::svc(ext_pkgs));
m_needs_file_existence_checks = true;
} }
// The published deps directory to be probed: either app or FX directory. // The published deps directory to be probed: either app or FX directory.
...@@ -253,10 +257,15 @@ void deps_resolver_t::setup_probe_config( ...@@ -253,10 +257,15 @@ void deps_resolver_t::setup_probe_config(
setup_shared_store_probes(args); setup_shared_store_probes(args);
for (const auto& probe : m_additional_probes) if (m_additional_probes.size() > 0)
{ {
// Additional paths for (const auto& probe : m_additional_probes)
m_probes.push_back(probe_config_t::lookup(probe)); {
// Additional paths
m_probes.push_back(probe_config_t::lookup(probe));
}
m_needs_file_existence_checks = true;
} }
if (trace::is_enabled()) if (trace::is_enabled())
...@@ -304,6 +313,11 @@ bool deps_resolver_t::probe_deps_entry(const deps_entry_t& entry, const pal::str ...@@ -304,6 +313,11 @@ bool deps_resolver_t::probe_deps_entry(const deps_entry_t& entry, const pal::str
continue; continue;
} }
pal::string_t probe_dir = config.probe_dir; pal::string_t probe_dir = config.probe_dir;
uint32_t search_options = deps_entry_t::search_options::none;
if (needs_file_existence_checks())
{
search_options |= deps_entry_t::search_options::file_existence;
}
if (config.is_fx()) if (config.is_fx())
{ {
...@@ -318,7 +332,7 @@ bool deps_resolver_t::probe_deps_entry(const deps_entry_t& entry, const pal::str ...@@ -318,7 +332,7 @@ bool deps_resolver_t::probe_deps_entry(const deps_entry_t& entry, const pal::str
// If the deps json has the package name and version, then someone has already done rid selection and // If the deps json has the package name and version, then someone has already done rid selection and
// put the right asset in the dir. So checking just package name and version would suffice. // put the right asset in the dir. So checking just package name and version would suffice.
// No need to check further for the exact asset relative sub path. // No need to check further for the exact asset relative sub path.
if (config.probe_deps_json->has_package(entry.library_name, entry.library_version) && entry.to_dir_path(probe_dir, false, candidate, found_in_bundle)) if (config.probe_deps_json->has_package(entry.library_name, entry.library_version) && entry.to_dir_path(probe_dir, candidate, search_options, found_in_bundle))
{ {
assert(!found_in_bundle); assert(!found_in_bundle);
trace::verbose(_X(" Probed deps json and matched '%s'"), candidate->c_str()); trace::verbose(_X(" Probed deps json and matched '%s'"), candidate->c_str());
...@@ -337,7 +351,7 @@ bool deps_resolver_t::probe_deps_entry(const deps_entry_t& entry, const pal::str ...@@ -337,7 +351,7 @@ bool deps_resolver_t::probe_deps_entry(const deps_entry_t& entry, const pal::str
{ {
if (entry.is_rid_specific) if (entry.is_rid_specific)
{ {
if (entry.to_rel_path(deps_dir, true, false, candidate)) if (entry.to_rel_path(deps_dir, candidate, search_options | deps_entry_t::search_options::look_in_bundle))
{ {
trace::verbose(_X(" Probed deps dir and matched '%s'"), candidate->c_str()); trace::verbose(_X(" Probed deps dir and matched '%s'"), candidate->c_str());
return true; return true;
...@@ -346,7 +360,7 @@ bool deps_resolver_t::probe_deps_entry(const deps_entry_t& entry, const pal::str ...@@ -346,7 +360,7 @@ bool deps_resolver_t::probe_deps_entry(const deps_entry_t& entry, const pal::str
else else
{ {
// Non-rid assets, lookup in the published dir. // Non-rid assets, lookup in the published dir.
if (entry.to_dir_path(deps_dir, true, candidate, found_in_bundle)) if (entry.to_dir_path(deps_dir, candidate, search_options | deps_entry_t::search_options::look_in_bundle, found_in_bundle))
{ {
trace::verbose(_X(" Probed deps dir and matched '%s'"), candidate->c_str()); trace::verbose(_X(" Probed deps dir and matched '%s'"), candidate->c_str());
return true; return true;
...@@ -356,7 +370,7 @@ bool deps_resolver_t::probe_deps_entry(const deps_entry_t& entry, const pal::str ...@@ -356,7 +370,7 @@ bool deps_resolver_t::probe_deps_entry(const deps_entry_t& entry, const pal::str
trace::verbose(_X(" Skipping... not found in deps dir '%s'"), deps_dir.c_str()); trace::verbose(_X(" Skipping... not found in deps dir '%s'"), deps_dir.c_str());
} }
else if (entry.to_full_path(probe_dir, config.only_serviceable_assets, candidate)) else if (entry.to_full_path(probe_dir, candidate, search_options | (config.only_serviceable_assets ? deps_entry_t::search_options::is_servicing : 0)))
{ {
trace::verbose(_X(" Probed package dir and matched '%s'"), candidate->c_str()); trace::verbose(_X(" Probed package dir and matched '%s'"), candidate->c_str());
return true; return true;
...@@ -588,10 +602,7 @@ bool deps_resolver_t::resolve_tpa_list( ...@@ -588,10 +602,7 @@ bool deps_resolver_t::resolve_tpa_list(
// Convert the paths into a string and return it // Convert the paths into a string and return it
for (const auto& item : items) for (const auto& item : items)
{ {
// Workaround for CoreFX not being able to resolve sym links. output->append(item.second.resolved_path);
pal::string_t real_asset_path = item.second.resolved_path;
pal::realpath(&real_asset_path);
output->append(real_asset_path);
output->push_back(PATH_SEPARATOR); output->push_back(PATH_SEPARATOR);
} }
......
...@@ -52,6 +52,7 @@ public: ...@@ -52,6 +52,7 @@ public:
, m_managed_app(args.managed_application) , m_managed_app(args.managed_application)
, m_core_servicing(args.core_servicing) , m_core_servicing(args.core_servicing)
, m_is_framework_dependent(is_framework_dependent) , m_is_framework_dependent(is_framework_dependent)
, m_needs_file_existence_checks(false)
{ {
int lowest_framework = static_cast<int>(m_fx_definitions.size()) - 1; int lowest_framework = static_cast<int>(m_fx_definitions.size()) - 1;
int root_framework = -1; int root_framework = -1;
...@@ -90,6 +91,11 @@ public: ...@@ -90,6 +91,11 @@ public:
setup_additional_probes(args.probe_paths); setup_additional_probes(args.probe_paths);
setup_probe_config(args); setup_probe_config(args);
if (m_additional_deps.size() > 0)
{
m_needs_file_existence_checks = true;
}
} }
bool valid(pal::string_t* errors) bool valid(pal::string_t* errors)
...@@ -172,6 +178,11 @@ public: ...@@ -172,6 +178,11 @@ public:
return m_is_framework_dependent; return m_is_framework_dependent;
} }
bool needs_file_existence_checks() const
{
return m_needs_file_existence_checks;
}
void get_app_dir(pal::string_t *app_dir) const void get_app_dir(pal::string_t *app_dir) const
{ {
if (m_host_mode == host_mode_t::libhost) if (m_host_mode == host_mode_t::libhost)
...@@ -272,6 +283,9 @@ private: ...@@ -272,6 +283,9 @@ private:
// Is the deps file for an app using shared frameworks? // Is the deps file for an app using shared frameworks?
const bool m_is_framework_dependent; const bool m_is_framework_dependent;
// File existence checks must be performed for probed paths.This will cause symlinks to be resolved.
bool m_needs_file_existence_checks;
}; };
#endif // DEPS_RESOLVER_H #endif // DEPS_RESOLVER_H
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册