diff --git a/.gitlab/.gitlab-ci.yml b/.gitlab/.gitlab-ci.yml
index eddcaa849..f15ca4303 100644
--- a/.gitlab/.gitlab-ci.yml
+++ b/.gitlab/.gitlab-ci.yml
@@ -34,11 +34,14 @@ compilation:windows:
compilation:mac:
extends: .compilation:base
tags: [square_mac]
+ variables:
+ ANKA_TEMPLATE_UUID: '1c28e1d9-d8cf-4a36-89e5-b915ccb5f62f'
+ ANKA_TAG_NAME: '7.0.0'
compilation:linux:
extends: .compilation:base
tags: [square-linux-k8s-compil]
- image: mcr.microsoft.com/dotnet/sdk:6.0
+ image: mcr.microsoft.com/dotnet/sdk:8.0
generate_samples_pipeline:
stage: build
diff --git a/Directory.Build.props b/Directory.Build.props
index 206c47b7a..df4e2764b 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -15,7 +15,7 @@
true
- 10.0
+ 11.0
net6.0
true
strict
diff --git a/Sharpmake.UnitTests/UtilTest.cs b/Sharpmake.UnitTests/UtilTest.cs
index 9f9e407ad..2544a78bc 100644
--- a/Sharpmake.UnitTests/UtilTest.cs
+++ b/Sharpmake.UnitTests/UtilTest.cs
@@ -41,6 +41,14 @@ public void NiceTypeNameOnGenericType()
public class PathMakeStandard
{
+ [Test]
+ public void ThrowWhenPathIsNull()
+ {
+ string nullPath = null;
+
+ Assert.Catch(() => Util.PathMakeStandard(nullPath));
+ }
+
[Test]
public void LeavesEmptyStringsUntouched()
{
@@ -58,6 +66,47 @@ public void LeavesVariablesUntouched()
Assert.That(Util.PathMakeStandard("$(Console_SdkPackagesRoot)"), Is.EqualTo(expectedResult));
}
+ [Test]
+ public void LeaveUnixRootPathUntouched()
+ {
+ var notFullyQualifiedUnixPath = "MountedDiskName:";
+ var fullyQualifiedRoot = Path.DirectorySeparatorChar.ToString();
+
+ Assert.AreEqual(fullyQualifiedRoot, Util.PathMakeStandard(fullyQualifiedRoot));
+
+ // Check case sensitivness on Unix
+ if (!Util.IsRunningOnUnix())
+ notFullyQualifiedUnixPath = notFullyQualifiedUnixPath.ToLower();
+
+ Assert.AreEqual(notFullyQualifiedUnixPath, Util.PathMakeStandard(notFullyQualifiedUnixPath));
+ }
+
+ [Test]
+ public void LeaveDriveRelativePathAsNotFullyQualified()
+ {
+ // For information about what is a drive relative path please check https://learn.microsoft.com/en-us/dotnet/standard/io/file-path-formats
+
+ var expectedResult = Path.Combine("d:toto", "tata");
+ var driveRelativePath = @"d:toto\tata\";
+ var fullyQualifiedPath = Path.Combine("d:", "toto", "tata");
+
+ Assert.AreEqual(expectedResult, Util.PathMakeStandard(driveRelativePath));
+ Assert.AreNotEqual(fullyQualifiedPath, Util.PathMakeStandard(driveRelativePath));
+ }
+
+ [Test]
+ public void ReturnFullyQualifiedRootPathOnWindows()
+ {
+ if (!Util.IsRunningOnUnix())
+ {
+ var notFullyQualifiedRoot = "d:";
+ var fullyQualifiedRoot = @"d:\";
+
+ Assert.AreEqual(fullyQualifiedRoot, Util.PathMakeStandard(notFullyQualifiedRoot));
+ Assert.AreEqual(fullyQualifiedRoot, Util.PathMakeStandard(fullyQualifiedRoot));
+ }
+ }
+
[Test]
public void ProcessesPathWithTrailingBackslash()
{
@@ -426,13 +475,8 @@ public void DifferentDrives()
[Test]
public void OnlyRoot()
{
- Assert.AreEqual(
- Util.PathMakeStandard(@"C:"),
- Util.FindCommonRootPath(new[] {
- @"C:\bla",
- @"C:\bli"
- })
- );
+ Assert.AreEqual(Util.PathMakeStandard("/"), Util.FindCommonRootPath(new[] {"/bla", "/bli"}));
+ Assert.AreEqual(Util.PathMakeStandard(@"c:\"), Util.FindCommonRootPath(new[] {@"c:\bla", @"c:\bli"}));
}
[Test]
@@ -1713,6 +1757,25 @@ public void RootDirectoryPathOneIntersectionAway()
Assert.IsFalse(Util.PathIsUnderRoot(rootWithExtraDir, pathNotUnderRoot));
}
+
+ [Test]
+ public void RootIsDrive()
+ {
+ if (Util.IsRunningOnUnix())
+ {
+ var fullyQualifiedRoot = Util.UnixSeparator.ToString();
+ var pathUnderRoot = "/versioncontrol/solutionname/projectname/src/code/factory.cs";
+
+ Assert.IsTrue(Util.PathIsUnderRoot(fullyQualifiedRoot, pathUnderRoot));
+ }
+ else
+ {
+ var fullyQualifiedRoot = @"D:\";
+ var pathUnderRoot = @"D:\versioncontrol\solutionname\projectname\src\code\factory.cs";
+
+ Assert.IsTrue(Util.PathIsUnderRoot(fullyQualifiedRoot, pathUnderRoot));
+ }
+ }
}
[TestFixture]
diff --git a/Sharpmake/PathUtil.cs b/Sharpmake/PathUtil.cs
index ebc5bb593..7d2698d32 100644
--- a/Sharpmake/PathUtil.cs
+++ b/Sharpmake/PathUtil.cs
@@ -13,9 +13,9 @@ namespace Sharpmake
{
public static partial class Util
{
- public static readonly char UnixSeparator = '/';
- public static readonly char WindowsSeparator = '\\';
- private static readonly string s_unixMountPointForWindowsDrives = "/mnt/";
+ public const char UnixSeparator = '/';
+ public const char WindowsSeparator = '\\';
+ private const string s_unixMountPointForWindowsDrives = "/mnt/";
public static readonly bool UsesUnixSeparator = Path.DirectorySeparatorChar == UnixSeparator;
@@ -34,11 +34,32 @@ public static string PathMakeStandard(string path)
return PathMakeStandard(path, !Util.IsRunningOnUnix());
}
+ ///
+ /// Cleanup the path by replacing the other separator by the correct one for the current OS
+ /// then trim every trailing separators, except if is a root (i.e. 'C:\' or '/')
+ ///
+ /// Note that if the given is a drive letter with volume separator,
+ /// without slash/backslash, a directory separator will be added to make the path fully qualified.
+ ///
But if the given is not just a drive letter and also has missing slash/backslah
+ /// after volume separator (for example "C:toto/tata/"), then the return path won't be fully qualified
+ /// (see here for more information on drive relative paths )
+ /// Note that Windows paths on Unix will have slashes (and vice versa)
+ /// Note that network paths (like NAS) starting with "\\" are not supported
+ ///
public static string PathMakeStandard(string path, bool forceToLower)
{
- // cleanup the path by replacing the other separator by the correct one for this OS
- // then trim every trailing separators
- var standardPath = path.Replace(OtherSeparator, Path.DirectorySeparatorChar).TrimEnd(Path.DirectorySeparatorChar);
+ ArgumentNullException.ThrowIfNull(path, nameof(path));
+
+ var standardPath = path.Replace(OtherSeparator, Path.DirectorySeparatorChar);
+
+ standardPath = standardPath switch
+ {
+ [WindowsSeparator or UnixSeparator] => standardPath,
+ [_, ':'] => IsRunningOnUnix() ? standardPath : standardPath + Path.DirectorySeparatorChar,
+ [_, ':', WindowsSeparator or UnixSeparator] => standardPath,
+ _ => standardPath.TrimEnd(Path.DirectorySeparatorChar),
+ };
+
return forceToLower ? standardPath.ToLower() : standardPath;
}
@@ -142,7 +163,7 @@ public static string GetConvertedRelativePath(
return newRelativePath;
}
- private static ConcurrentDictionary s_cachedSimplifiedPaths = new ConcurrentDictionary();
+ private static readonly ConcurrentDictionary s_cachedSimplifiedPaths = new ConcurrentDictionary();
///
/// Take a path and compute a canonical version of it. It removes any extra: "..", ".", directory separators...
@@ -341,7 +362,7 @@ public static unsafe string PathGetRelative(string relativeTo, string path, bool
[Obsolete("Directly use 'char.IsAsciiLetter()' in 'IsCharEqual()' bellow (char.IsAsciiLetter() is available starting net7)")]
#endif
static bool IsAsciiLetter(char c) => (uint)((c | 0x20) - 'a') <= 'z' - 'a';
- static bool IsCharEqual(char a, char b, bool ignoreCase) => a == b || (ignoreCase && (a | 0x20) == (b | 0x20) && IsAsciiLetter(a));
+ static bool IsCharEqual(char a, char b, bool ignoreCase) => a == b || (ignoreCase && (a | 0x20) == (b | 0x20) && IsAsciiLetter(a));
// Check if both paths are the same (ignoring the last directory separator if any)
if ((relativeToLength == commonPartLength && pathLength == commonPartLength)
@@ -434,7 +455,7 @@ public static List PathGetAbsolute(string sourceFullPath, Strings destFu
return result;
}
- private static ConcurrentDictionary s_cachedCombinedToAbsolute = new ConcurrentDictionary();
+ private static readonly ConcurrentDictionary s_cachedCombinedToAbsolute = new ConcurrentDictionary();
public static string PathGetAbsolute(string absolutePath, string relativePath)
{
@@ -446,14 +467,12 @@ public static string PathGetAbsolute(string absolutePath, string relativePath)
return relativePath;
string cleanRelative = SimplifyPath(relativePath);
- if (Path.IsPathRooted(cleanRelative))
+ if (Path.IsPathFullyQualified(cleanRelative))
return cleanRelative;
string resultPath = s_cachedCombinedToAbsolute.GetOrAdd(string.Format("{0}|{1}", absolutePath, relativePath), combined =>
{
string firstPart = PathMakeStandard(absolutePath);
- if (firstPart.Last() == Path.VolumeSeparatorChar)
- firstPart += Path.DirectorySeparatorChar;
string result = Path.Combine(firstPart, cleanRelative);
return Path.GetFullPath(result);
@@ -616,7 +635,7 @@ private static string GetProperFilePathCapitalization(string filename)
return Path.Combine(builder.ToString(), properFileName);
}
- private static ConcurrentDictionary s_capitalizedPaths = new ConcurrentDictionary();
+ private static readonly ConcurrentDictionary s_capitalizedPaths = new ConcurrentDictionary();
private static void GetProperDirectoryCapitalization(DirectoryInfo dirInfo, DirectoryInfo childInfo, ref StringBuilder pathBuilder)
{
@@ -771,37 +790,65 @@ public static string ReplaceHeadPath(this string fullInputPath, string inputHead
public static string FindCommonRootPath(IEnumerable paths)
{
- var pathsChunks = paths.Select(p => PathMakeStandard(p).Split(Util._pathSeparators, StringSplitOptions.RemoveEmptyEntries)).Where(p => p.Any());
+ paths = paths.Select(PathMakeStandard);
+ var pathsChunks = paths.Select(p => p.Split(Path.DirectorySeparatorChar, StringSplitOptions.RemoveEmptyEntries)).Where(p => p.Any());
+
if (pathsChunks.Any())
{
- bool firstCharIsPathSeparator = UsesUnixSeparator ? paths.Any(p => p[0] == UnixSeparator) : false;
- var firstPathChunks = pathsChunks.First();
- bool foundSomeCommonChunks = false;
- int commonPathIndex = 0;
- do
+ var sb = new StringBuilder();
+ var isFirst = true;
+ var chunkStartIndex = 0;
+
+ // Handle fully qualified paths
+ var fullyQualifiedPath = paths.FirstOrDefault(p => p is ([UnixSeparator or WindowsSeparator, ..]) or ([_, ':', UnixSeparator or WindowsSeparator, ..]));
+ if (fullyQualifiedPath is not null)
{
- if (firstPathChunks.Length > commonPathIndex)
+ if (fullyQualifiedPath[0] == Path.DirectorySeparatorChar)
{
- string reference = firstPathChunks[commonPathIndex];
- if (!pathsChunks.Any(p => !string.Equals(p.Length > commonPathIndex ? p[commonPathIndex] : string.Empty, reference, StringComparison.OrdinalIgnoreCase)))
- {
- ++commonPathIndex;
- foundSomeCommonChunks = true;
- }
- else
- break;
+ sb.Append(Path.DirectorySeparatorChar);
+ }
+ else
+ {
+ sb.Append(fullyQualifiedPath[0]);
+ sb.Append(':');
+ sb.Append(Path.DirectorySeparatorChar);
+ chunkStartIndex++;
+ }
+
+ // All path should start with the same root path, else there is nothing in common
+ var rootPath = sb.ToString();
+ if (paths.Any(p => !p.StartsWith(rootPath, StringComparison.OrdinalIgnoreCase)))
+ {
+ return null;
+ }
+ }
+
+ var referenceChunks = pathsChunks.First();
+ int smallestChunksCount = pathsChunks.Min(p => p.Length);
+ for (var i = chunkStartIndex; i < smallestChunksCount; ++i)
+ {
+ var reference = referenceChunks[i];
+ if (pathsChunks.All(p => string.Equals(p[i], reference, StringComparison.OrdinalIgnoreCase)))
+ {
+ if (!isFirst)
+ sb.Append(Path.DirectorySeparatorChar);
+ isFirst = false;
+
+ sb.Append(reference);
}
else
+ {
break;
+ }
}
- while (true);
+ var foundSomeCommonChunks = sb.Length != 0;
if (foundSomeCommonChunks)
{
- var commonRootPath = string.Join(Path.DirectorySeparatorChar.ToString(), firstPathChunks.Take(commonPathIndex));
- return firstCharIsPathSeparator ? UnixSeparator.ToString() + commonRootPath : commonRootPath;
+ return sb.ToString();
}
}
+
return null;
}
@@ -813,7 +860,7 @@ public static string FindCommonRootPath(IEnumerable paths)
/// An absolute or relative path to a file or directory to be tested.
///
///
- public static bool PathIsUnderRoot(string rootPath, string pathToTest)
+ public static bool PathIsUnderRoot(string rootPath, string pathToTest)
{
if (!Path.IsPathFullyQualified(rootPath))
throw new ArgumentException("rootPath needs to be absolute.", nameof(rootPath));
@@ -823,20 +870,20 @@ public static bool PathIsUnderRoot(string rootPath, string pathToTest)
var intersection = GetPathIntersection(rootPath, pathToTest);
- if(string.IsNullOrEmpty(intersection))
+ if (string.IsNullOrEmpty(intersection))
return false;
if (!Util.PathIsSame(intersection, rootPath))
{
- if(rootPath.EndsWith(Path.DirectorySeparatorChar))
+ if (rootPath.EndsWith(Path.DirectorySeparatorChar))
return false;
// only way to make sure path point to file is to check on disk
// if file doesn't exist, treats this edge case as if path wasn't a file path
var fileInfo = new FileInfo(rootPath);
- if(fileInfo.Exists && Util.PathIsSame(intersection, fileInfo.DirectoryName))
- return true;
-
+ if (fileInfo.Exists && Util.PathIsSame(intersection, fileInfo.DirectoryName))
+ return true;
+
return false;
}