From 5f82f62e56cddab462a9dc83f10f9514e7bae080 Mon Sep 17 00:00:00 2001 From: Dean Ellis Date: Wed, 20 Sep 2023 14:40:53 +0100 Subject: [PATCH 01/12] Include Bindings code in Design Time builds --- .../Xamarin.Android.Bindings.Core.targets | 20 ++++++++++++++++++- .../Microsoft.Android.Sdk.BuildOrder.targets | 1 + .../BindingBuildTest.cs | 2 ++ 3 files changed, 22 insertions(+), 1 deletion(-) diff --git a/src/Xamarin.Android.Build.Tasks/MSBuild/Xamarin/Android/Xamarin.Android.Bindings.Core.targets b/src/Xamarin.Android.Build.Tasks/MSBuild/Xamarin/Android/Xamarin.Android.Bindings.Core.targets index fd0674e9946..c5bbcbfefda 100644 --- a/src/Xamarin.Android.Build.Tasks/MSBuild/Xamarin/Android/Xamarin.Android.Bindings.Core.targets +++ b/src/Xamarin.Android.Build.Tasks/MSBuild/Xamarin/Android/Xamarin.Android.Bindings.Core.targets @@ -41,6 +41,15 @@ It is shared between "legacy" binding projects and .NET 5 projects. + + + <_GeneratedManagedBindingFiles Include="$(IntermediateOutputPath)generated\*\*.cs" /> + + + + @@ -107,11 +116,20 @@ It is shared between "legacy" binding projects and .NET 5 projects. + + + + + + + + true + diff --git a/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.BuildOrder.targets b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.BuildOrder.targets index 24d8d93c9c4..346c89aa99b 100644 --- a/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.BuildOrder.targets +++ b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.BuildOrder.targets @@ -118,6 +118,7 @@ properties that determine build ordering. _BuildResourceDesigner; UpdateAndroidInterfaceProxies; _SetAndroidGenerateManagedBindings; + _ClearGeneratedManagedBindings; AddBindingsToCompile; _CheckForInvalidDesignerConfig; diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/BindingBuildTest.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/BindingBuildTest.cs index 70e42e8c71f..7fb2f61bfd7 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/BindingBuildTest.cs +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/BindingBuildTest.cs @@ -534,6 +534,8 @@ public void AppWithSingleJar () var builder = CreateApkBuilder (); Assert.IsTrue (builder.Build (proj), "first build should succeed"); + Assert.IsTrue (builder.DesignTimeBuild (proj), "Design time build should succeed."); + Assert.IsFalse (builder.Output.IsTargetSkipped ("AddBindingsToCompile"), "AddBindingsToCompile should run."); var assemblyPath = Path.Combine (Root, builder.ProjectDirectory, proj.OutputPath, $"{proj.ProjectName}.dll"); var typeName = "Com.Xamarin.Android.Test.Msbuildtest.JavaSourceJarTest"; From 1fb07ca9ba48976ebfdf1edc2f413f87a40809d4 Mon Sep 17 00:00:00 2001 From: Dean Ellis Date: Thu, 21 Sep 2023 15:42:31 +0100 Subject: [PATCH 02/12] Changes --- .../Xamarin.Android.Bindings.Core.targets | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/Xamarin.Android.Build.Tasks/MSBuild/Xamarin/Android/Xamarin.Android.Bindings.Core.targets b/src/Xamarin.Android.Build.Tasks/MSBuild/Xamarin/Android/Xamarin.Android.Bindings.Core.targets index c5bbcbfefda..39f7ae022f4 100644 --- a/src/Xamarin.Android.Build.Tasks/MSBuild/Xamarin/Android/Xamarin.Android.Bindings.Core.targets +++ b/src/Xamarin.Android.Build.Tasks/MSBuild/Xamarin/Android/Xamarin.Android.Bindings.Core.targets @@ -41,12 +41,16 @@ It is shared between "legacy" binding projects and .NET 5 projects. - + <_GeneratedManagedBindingFiles Include="$(IntermediateOutputPath)generated\*\*.cs" /> + + + @@ -124,15 +128,15 @@ It is shared between "legacy" binding projects and .NET 5 projects. + Condition=" '$(_AndroidGenerateManagedBindings)' == 'true' Or Exists ('@(_GeneratedManagedBindingFiles)') " + DependsOnTargets="GenerateBindings;_CollectGeneratedManagedBindingFiles"> true - + From 8b8356db7129d541f0df4b1080e2163f4489d37c Mon Sep 17 00:00:00 2001 From: Dean Ellis Date: Thu, 21 Sep 2023 16:14:26 +0100 Subject: [PATCH 03/12] fix --- .../Xamarin/Android/Xamarin.Android.Bindings.Core.targets | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Xamarin.Android.Build.Tasks/MSBuild/Xamarin/Android/Xamarin.Android.Bindings.Core.targets b/src/Xamarin.Android.Build.Tasks/MSBuild/Xamarin/Android/Xamarin.Android.Bindings.Core.targets index 39f7ae022f4..971724b5d09 100644 --- a/src/Xamarin.Android.Build.Tasks/MSBuild/Xamarin/Android/Xamarin.Android.Bindings.Core.targets +++ b/src/Xamarin.Android.Build.Tasks/MSBuild/Xamarin/Android/Xamarin.Android.Bindings.Core.targets @@ -128,8 +128,8 @@ It is shared between "legacy" binding projects and .NET 5 projects. + Condition=" '$(_AndroidGenerateManagedBindings)' == 'true' Or '@(_GeneratedManagedBindingFiles->Count())' != '0' " + DependsOnTargets="GenerateBindings"> true From 7c5a6b3864d20e9e883876436ef6764ba09bf1cc Mon Sep 17 00:00:00 2001 From: Dean Ellis Date: Thu, 21 Sep 2023 16:35:38 +0100 Subject: [PATCH 04/12] This works --- .../Xamarin/Android/Xamarin.Android.Bindings.Core.targets | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Xamarin.Android.Build.Tasks/MSBuild/Xamarin/Android/Xamarin.Android.Bindings.Core.targets b/src/Xamarin.Android.Build.Tasks/MSBuild/Xamarin/Android/Xamarin.Android.Bindings.Core.targets index 971724b5d09..a137e5961c1 100644 --- a/src/Xamarin.Android.Build.Tasks/MSBuild/Xamarin/Android/Xamarin.Android.Bindings.Core.targets +++ b/src/Xamarin.Android.Build.Tasks/MSBuild/Xamarin/Android/Xamarin.Android.Bindings.Core.targets @@ -129,14 +129,14 @@ It is shared between "legacy" binding projects and .NET 5 projects. + DependsOnTargets="GenerateBindings;_CollectGeneratedManagedBindingFiles"> true - + From 8a38214ea1b728e25a69022108730c1653b7c91e Mon Sep 17 00:00:00 2001 From: Dean Ellis Date: Fri, 22 Sep 2023 15:51:03 +0100 Subject: [PATCH 05/12] Fix wildcards --- .../Xamarin/Android/Xamarin.Android.Bindings.Core.targets | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Xamarin.Android.Build.Tasks/MSBuild/Xamarin/Android/Xamarin.Android.Bindings.Core.targets b/src/Xamarin.Android.Build.Tasks/MSBuild/Xamarin/Android/Xamarin.Android.Bindings.Core.targets index a137e5961c1..8780eaea0fe 100644 --- a/src/Xamarin.Android.Build.Tasks/MSBuild/Xamarin/Android/Xamarin.Android.Bindings.Core.targets +++ b/src/Xamarin.Android.Build.Tasks/MSBuild/Xamarin/Android/Xamarin.Android.Bindings.Core.targets @@ -43,7 +43,7 @@ It is shared between "legacy" binding projects and .NET 5 projects. - <_GeneratedManagedBindingFiles Include="$(IntermediateOutputPath)generated\*\*.cs" /> + <_GeneratedManagedBindingFiles Include="$(IntermediateOutputPath)generated\**\*.cs" /> @@ -121,7 +121,7 @@ It is shared between "legacy" binding projects and .NET 5 projects. - + From 99d46dacacb51307a2327d5b86b9c7a11367b251 Mon Sep 17 00:00:00 2001 From: Dean Ellis Date: Fri, 22 Sep 2023 15:52:04 +0100 Subject: [PATCH 06/12] Formatting --- .../Xamarin/Android/Xamarin.Android.Bindings.Core.targets | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Xamarin.Android.Build.Tasks/MSBuild/Xamarin/Android/Xamarin.Android.Bindings.Core.targets b/src/Xamarin.Android.Build.Tasks/MSBuild/Xamarin/Android/Xamarin.Android.Bindings.Core.targets index 8780eaea0fe..0af29e0ab61 100644 --- a/src/Xamarin.Android.Build.Tasks/MSBuild/Xamarin/Android/Xamarin.Android.Bindings.Core.targets +++ b/src/Xamarin.Android.Build.Tasks/MSBuild/Xamarin/Android/Xamarin.Android.Bindings.Core.targets @@ -48,8 +48,8 @@ It is shared between "legacy" binding projects and .NET 5 projects. From 55f0a7df37ff8772b4a4e82575960280d1870e09 Mon Sep 17 00:00:00 2001 From: Dean Ellis Date: Thu, 28 Sep 2023 14:09:40 +0100 Subject: [PATCH 07/12] Try using the full path --- .../Xamarin/Android/Xamarin.Android.Bindings.Core.targets | 4 ++-- .../Tasks/GenerateLayoutBindings.CSharpBindingGenerator.cs | 5 +++-- .../Tests/Xamarin.Android.Build.Tests/CodeBehindTests.cs | 2 +- .../Xamarin.Android.Common.targets | 2 +- 4 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/Xamarin.Android.Build.Tasks/MSBuild/Xamarin/Android/Xamarin.Android.Bindings.Core.targets b/src/Xamarin.Android.Build.Tasks/MSBuild/Xamarin/Android/Xamarin.Android.Bindings.Core.targets index 0af29e0ab61..7b6191bf61e 100644 --- a/src/Xamarin.Android.Build.Tasks/MSBuild/Xamarin/Android/Xamarin.Android.Bindings.Core.targets +++ b/src/Xamarin.Android.Build.Tasks/MSBuild/Xamarin/Android/Xamarin.Android.Bindings.Core.targets @@ -43,7 +43,7 @@ It is shared between "legacy" binding projects and .NET 5 projects. - <_GeneratedManagedBindingFiles Include="$(IntermediateOutputPath)generated\**\*.cs" /> + <_GeneratedManagedBindingFiles Include="$(GeneratedOutputPath)**\*.cs" /> @@ -121,7 +121,7 @@ It is shared between "legacy" binding projects and .NET 5 projects. - + diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateLayoutBindings.CSharpBindingGenerator.cs b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateLayoutBindings.CSharpBindingGenerator.cs index c5dc083427d..1f4e2c2f66e 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateLayoutBindings.CSharpBindingGenerator.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateLayoutBindings.CSharpBindingGenerator.cs @@ -1,6 +1,8 @@ using System; using System.IO; using System.Linq; +using Xamarin.Android.Tasks.LLVMIR; +using Xamarin.Android.Tools; namespace Xamarin.Android.Tasks { @@ -9,7 +11,6 @@ partial class GenerateLayoutBindings sealed class CSharpBindingGenerator : BindingGenerator { const string BindingPartialClassBackingFieldName = "__layout_binding"; - protected override string LineCommentString => "//"; protected override string DocCommentString => "///"; public override string LanguageName => "C#"; @@ -239,7 +240,7 @@ protected override void WriteLocationDirective (State state, LayoutWidget widget if (loc == null) return; - WriteLineIndent (state, $"#line {loc.Line} \"{loc.FilePath}\""); + WriteLineIndent (state, $"#line {loc.Line} \"{Path.GetFullPath (loc.FilePath)}\""); state.WriteLine (); } diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/CodeBehindTests.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/CodeBehindTests.cs index a64a11f7bce..6c87875bfa0 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/CodeBehindTests.cs +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/CodeBehindTests.cs @@ -124,7 +124,7 @@ public TestProjectInfo (string projectName, string testName, string rootDirector ProjectName = projectName; ObjPath = Path.Combine (rootDirectory, "obj"); - GeneratedPath = Path.Combine (ObjPath, XABuildPaths.Configuration, "generated"); + GeneratedPath = Path.Combine (ObjPath, XABuildPaths.Configuration, "codebehind"); BinPath = Path.Combine (rootDirectory, "bin", XABuildPaths.Configuration); ProjectPath = Path.Combine (rootDirectory, $"{projectName}.csproj"); diff --git a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets index 71e64a76db7..de211f691a2 100644 --- a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets +++ b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets @@ -917,7 +917,7 @@ because xbuild doesn't support framework reference assemblies. Resources $(IntermediateOutputPath) - $(IntermediateOutputPath)generated + $(IntermediateOutputPath)codebehind $(IntermediateOutputPath)resourcecache <_AndroidIntermediateJavaSourceDirectory>$(IntermediateOutputPath)android\src\ <_AndroidIntermediateDexOutputDirectory>$(IntermediateOutputPath)android\bin\ From 5fb62001faddcc378b43e4c2d0602049db8e80f1 Mon Sep 17 00:00:00 2001 From: Dean Ellis Date: Fri, 29 Sep 2023 13:22:02 +0100 Subject: [PATCH 08/12] Fix test --- .../Xamarin.Android.Build.Tests/AndroidUpdateResourcesTest.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/AndroidUpdateResourcesTest.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/AndroidUpdateResourcesTest.cs index c653639bfd0..99e9a963422 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/AndroidUpdateResourcesTest.cs +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/AndroidUpdateResourcesTest.cs @@ -1247,9 +1247,9 @@ protected override void OnCreate (Bundle bundle) proj.SetProperty ("AndroidGenerateLayoutBindings", "True"); using (var builder = CreateApkBuilder (path)) { Assert.IsTrue (builder.Build (proj), "Build should have succeeded."); - FileAssert.Exists (Path.Combine (Root, path, proj.IntermediateOutputPath, "generated", "Binding.Main.g.cs")); + FileAssert.Exists (Path.Combine (Root, path, proj.IntermediateOutputPath, "codebehind", "Binding.Main.g.cs")); Assert.IsTrue (builder.Build (proj), "Second build should have succeeded."); - FileAssert.Exists (Path.Combine (Root, path, proj.IntermediateOutputPath, "generated", "Binding.Main.g.cs")); + FileAssert.Exists (Path.Combine (Root, path, proj.IntermediateOutputPath, "codebehind", "Binding.Main.g.cs")); } } From 1836d28cf6e235522dce129fbafabeb40728bb11 Mon Sep 17 00:00:00 2001 From: Dean Ellis Date: Mon, 2 Oct 2023 13:17:39 +0100 Subject: [PATCH 09/12] Move RelativePath --- .../xaprepare/Application/Utilities.cs | 149 +--------- .../xaprepare/xaprepare/xaprepare.csproj | 4 + ...GenerateLayoutBindings.BindingGenerator.cs | 2 + ...teLayoutBindings.CSharpBindingGenerator.cs | 10 +- .../Tasks/GenerateLayoutBindings.cs | 2 + .../Utilities/PathUtil.cs | 269 ++++++++++++++++++ 6 files changed, 287 insertions(+), 149 deletions(-) create mode 100644 src/Xamarin.Android.Build.Tasks/Utilities/PathUtil.cs diff --git a/build-tools/xaprepare/xaprepare/Application/Utilities.cs b/build-tools/xaprepare/xaprepare/Application/Utilities.cs index eb2a9393525..e594209db44 100644 --- a/build-tools/xaprepare/xaprepare/Application/Utilities.cs +++ b/build-tools/xaprepare/xaprepare/Application/Utilities.cs @@ -867,154 +867,7 @@ void LogError (string message) public static string GetRelativePath (string relativeTo, string path) { - return GetRelativePath (relativeTo, path, Context.Instance.OS.DefaultStringComparison); + return Xamarin.Android.Tools.PathUtil.GetRelativePath (relativeTo, path, Context.Instance.OS.DefaultStringComparison); } - - // Adapted from CoreFX sources - public static string GetRelativePath (string relativeTo, string path, StringComparison comparisonType) - { - if (String.IsNullOrEmpty (relativeTo)) - throw new ArgumentException ("must not be null or empty", nameof (relativeTo)); - - if (String.IsNullOrEmpty (path)) - throw new ArgumentException ("must not be null or empty", nameof (path)); - - relativeTo = Path.GetFullPath (relativeTo); - path = Path.GetFullPath (path); - - // Need to check if the roots are different- if they are we need to return the "to" path. - if (!AreRootsEqual (relativeTo, path, comparisonType)) - return path; - - int commonLength = GetCommonPathLength (relativeTo, path, ignoreCase: comparisonType == StringComparison.OrdinalIgnoreCase); - - // If there is nothing in common they can't share the same root, return the "to" path as is. - if (commonLength == 0) - return path; - - // Trailing separators aren't significant for comparison - int relativeToLength = relativeTo.Length; - if (EndsInDirectorySeparator (relativeTo)) - relativeToLength--; - - bool pathEndsInSeparator = EndsInDirectorySeparator (path); - int pathLength = path.Length; - if (pathEndsInSeparator) - pathLength--; - - // If we have effectively the same path, return "." - if (relativeToLength == pathLength && commonLength >= relativeToLength) - return "."; - - // We have the same root, we need to calculate the difference now using the - // common Length and Segment count past the length. - // - // Some examples: - // - // C:\Foo C:\Bar L3, S1 -> ..\Bar - // C:\Foo C:\Foo\Bar L6, S0 -> Bar - // C:\Foo\Bar C:\Bar\Bar L3, S2 -> ..\..\Bar\Bar - // C:\Foo\Foo C:\Foo\Bar L7, S1 -> ..\Bar - - var sb = new StringBuilder (Math.Max (relativeTo.Length, path.Length)); - - // Add parent segments for segments past the common on the "from" path - if (commonLength < relativeToLength) { - sb.Append (".."); - - for (int i = commonLength + 1; i < relativeToLength; i++) { - if (IsDirectorySeparator (relativeTo[i])) { - sb.Append (Path.DirectorySeparatorChar); - sb.Append (".."); - } - } - } else if (IsDirectorySeparator (path[commonLength])) { - // No parent segments and we need to eat the initial separator - // (C:\Foo C:\Foo\Bar case) - commonLength++; - } - - // Now add the rest of the "to" path, adding back the trailing separator - int differenceLength = pathLength - commonLength; - if (pathEndsInSeparator) - differenceLength++; - - if (differenceLength > 0) { - if (sb.Length > 0) { - sb.Append (Path.DirectorySeparatorChar); - } - - sb.Append(path, commonLength, differenceLength); - } - - return sb.ToString (); - } - - // Adapted from CoreFX sources - static bool AreRootsEqual (string first, string second, StringComparison comparisonType) - { - int firstRootLength = GetRootLength (first); - int secondRootLength = GetRootLength (second); - - return firstRootLength == secondRootLength - && String.Compare ( - strA: first, - indexA: 0, - strB: second, - indexB: 0, - length: firstRootLength, - comparisonType: comparisonType) == 0; - } - - // Adapted from CoreFX sources - static int GetCommonPathLength (string first, string second, bool ignoreCase) - { - int commonChars = EqualStartingCharacterCount (first, second, ignoreCase: ignoreCase); - - // If nothing matches - if (commonChars == 0) - return commonChars; - - // Or we're a full string and equal length or match to a separator - if (commonChars == first.Length && (commonChars == second.Length || IsDirectorySeparator (second[commonChars]))) - return commonChars; - - if (commonChars == second.Length && IsDirectorySeparator (first[commonChars])) - return commonChars; - - // It's possible we matched somewhere in the middle of a segment e.g. C:\Foodie and C:\Foobar. - while (commonChars > 0 && !IsDirectorySeparator (first[commonChars - 1])) - commonChars--; - - return commonChars; - } - - // Adapted from CoreFX sources - static unsafe int EqualStartingCharacterCount (string first, string second, bool ignoreCase) - { - if (String.IsNullOrEmpty (first) || string.IsNullOrEmpty (second)) - return 0; - - int commonChars = 0; - fixed (char* f = first) { - fixed (char* s = second) { - char* l = f; - char* r = s; - char* leftEnd = l + first.Length; - char* rightEnd = r + second.Length; - - while (l != leftEnd && r != rightEnd && (*l == *r || (ignoreCase && char.ToUpperInvariant ((*l)) == char.ToUpperInvariant ((*r))))) { - commonChars++; - l++; - r++; - } - } - } - - return commonChars; - } - - // Adapted from CoreFX sources - static bool EndsInDirectorySeparator (string path) => path.Length > 0 && IsDirectorySeparator (path[path.Length - 1]); } } diff --git a/build-tools/xaprepare/xaprepare/xaprepare.csproj b/build-tools/xaprepare/xaprepare/xaprepare.csproj index 2d5dc260e70..6730c7adae9 100644 --- a/build-tools/xaprepare/xaprepare/xaprepare.csproj +++ b/build-tools/xaprepare/xaprepare/xaprepare.csproj @@ -44,6 +44,10 @@ + + + + diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateLayoutBindings.BindingGenerator.cs b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateLayoutBindings.BindingGenerator.cs index fd9c73a4298..b404d91156e 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateLayoutBindings.BindingGenerator.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateLayoutBindings.BindingGenerator.cs @@ -127,6 +127,8 @@ public State BeginPartialClassFile (StreamWriter writer, string bindingClassName return state; } + public abstract void SetCodeBehindDir (string path); + protected abstract void BeginPartialClassFile (State state, string classNamespace, string className); public abstract void EndPartialClassFile (State state); public abstract void WritePartialClassProperty (State state, LayoutWidget widget); diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateLayoutBindings.CSharpBindingGenerator.cs b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateLayoutBindings.CSharpBindingGenerator.cs index 1f4e2c2f66e..bc2b9ff6db4 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateLayoutBindings.CSharpBindingGenerator.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateLayoutBindings.CSharpBindingGenerator.cs @@ -11,6 +11,8 @@ partial class GenerateLayoutBindings sealed class CSharpBindingGenerator : BindingGenerator { const string BindingPartialClassBackingFieldName = "__layout_binding"; + + string codebehindDir = string.Empty; protected override string LineCommentString => "//"; protected override string DocCommentString => "///"; public override string LanguageName => "C#"; @@ -29,6 +31,11 @@ protected override void BeginPartialClassFile (State state, string classNamespac state.WriteLine (); } + public override void SetCodeBehindDir(string path) + { + codebehindDir = Path.GetFullPath (path); + } + public override void EndPartialClassFile (State state) { EndBindingFile (state); // currently they're identical @@ -240,7 +247,8 @@ protected override void WriteLocationDirective (State state, LayoutWidget widget if (loc == null) return; - WriteLineIndent (state, $"#line {loc.Line} \"{Path.GetFullPath (loc.FilePath)}\""); + var relativePath = PathUtil.GetRelativePath (codebehindDir, Path.GetFullPath (loc.FilePath)); + WriteLineIndent (state, $"#line {loc.Line} \"{relativePath}\""); state.WriteLine (); } diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateLayoutBindings.cs b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateLayoutBindings.cs index 161fc7e374c..1c721326b42 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateLayoutBindings.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateLayoutBindings.cs @@ -107,6 +107,8 @@ public async override System.Threading.Tasks.Task RunTaskAsync () return; } + generator.SetCodeBehindDir (MonoAndroidCodeBehindDir); + LogDebugMessage ($"Generating {generator.LanguageName} binding sources"); var layoutGroups = new Dictionary (StringComparer.Ordinal); diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/PathUtil.cs b/src/Xamarin.Android.Build.Tasks/Utilities/PathUtil.cs new file mode 100644 index 00000000000..23878c8c4b2 --- /dev/null +++ b/src/Xamarin.Android.Build.Tasks/Utilities/PathUtil.cs @@ -0,0 +1,269 @@ +using System; +using System.IO; +using System.Text; +using System.Runtime.InteropServices; + +namespace Xamarin.Android.Tools { + public static class PathUtil { + + const int DevicePrefixLength = 4; + const int UncPrefixLength = 2; + const int UncExtendedPrefixLength = 8; + + internal const char VolumeSeparatorChar = ':'; + + static bool IsWindows = Path.DirectorySeparatorChar == '\\'; + // Adapted from CoreFX sources + public static string GetRelativePath (string relativeTo, string path, StringComparison comparisonType = StringComparison.OrdinalIgnoreCase) + { + if (String.IsNullOrEmpty (relativeTo)) + throw new ArgumentException ("must not be null or empty", nameof (relativeTo)); + + if (String.IsNullOrEmpty (path)) + throw new ArgumentException ("must not be null or empty", nameof (path)); + + relativeTo = Path.GetFullPath (relativeTo); + path = Path.GetFullPath (path); + + // Need to check if the roots are different- if they are we need to return the "to" path. + if (!AreRootsEqual (relativeTo, path, comparisonType)) + return path; + + int commonLength = GetCommonPathLength (relativeTo, path, ignoreCase: comparisonType == StringComparison.OrdinalIgnoreCase); + + // If there is nothing in common they can't share the same root, return the "to" path as is. + if (commonLength == 0) + return path; + + // Trailing separators aren't significant for comparison + int relativeToLength = relativeTo.Length; + if (EndsInDirectorySeparator (relativeTo)) + relativeToLength--; + + bool pathEndsInSeparator = EndsInDirectorySeparator (path); + int pathLength = path.Length; + if (pathEndsInSeparator) + pathLength--; + + // If we have effectively the same path, return "." + if (relativeToLength == pathLength && commonLength >= relativeToLength) + return "."; + + // We have the same root, we need to calculate the difference now using the + // common Length and Segment count past the length. + // + // Some examples: + // + // C:\Foo C:\Bar L3, S1 -> ..\Bar + // C:\Foo C:\Foo\Bar L6, S0 -> Bar + // C:\Foo\Bar C:\Bar\Bar L3, S2 -> ..\..\Bar\Bar + // C:\Foo\Foo C:\Foo\Bar L7, S1 -> ..\Bar + + var sb = new StringBuilder (Math.Max (relativeTo.Length, path.Length)); + + // Add parent segments for segments past the common on the "from" path + if (commonLength < relativeToLength) { + sb.Append (".."); + + for (int i = commonLength + 1; i < relativeToLength; i++) { + if (IsDirectorySeparator (relativeTo[i])) { + sb.Append (Path.DirectorySeparatorChar); + sb.Append (".."); + } + } + } else if (IsDirectorySeparator (path[commonLength])) { + // No parent segments and we need to eat the initial separator + // (C:\Foo C:\Foo\Bar case) + commonLength++; + } + + // Now add the rest of the "to" path, adding back the trailing separator + int differenceLength = pathLength - commonLength; + if (pathEndsInSeparator) + differenceLength++; + + if (differenceLength > 0) { + if (sb.Length > 0) { + sb.Append (Path.DirectorySeparatorChar); + } + + sb.Append(path, commonLength, differenceLength); + } + + return sb.ToString (); + } + + // Adapted from CoreFX sources + static bool AreRootsEqual (string first, string second, StringComparison comparisonType) + { + int firstRootLength = GetRootLength (first); + int secondRootLength = GetRootLength (second); + + return firstRootLength == secondRootLength + && String.Compare ( + strA: first, + indexA: 0, + strB: second, + indexB: 0, + length: firstRootLength, + comparisonType: comparisonType) == 0; + } + + // Adapted from CoreFX sources + static int GetCommonPathLength (string first, string second, bool ignoreCase) + { + int commonChars = EqualStartingCharacterCount (first, second, ignoreCase: ignoreCase); + + // If nothing matches + if (commonChars == 0) + return commonChars; + + // Or we're a full string and equal length or match to a separator + if (commonChars == first.Length && (commonChars == second.Length || IsDirectorySeparator (second[commonChars]))) + return commonChars; + + if (commonChars == second.Length && IsDirectorySeparator (first[commonChars])) + return commonChars; + + // It's possible we matched somewhere in the middle of a segment e.g. C:\Foodie and C:\Foobar. + while (commonChars > 0 && !IsDirectorySeparator (first[commonChars - 1])) + commonChars--; + + return commonChars; + } + + // Adapted from CoreFX sources + static unsafe int EqualStartingCharacterCount (string first, string second, bool ignoreCase) + { + if (String.IsNullOrEmpty (first) || string.IsNullOrEmpty (second)) + return 0; + + int commonChars = 0; + fixed (char* f = first) { + fixed (char* s = second) { + char* l = f; + char* r = s; + char* leftEnd = l + first.Length; + char* rightEnd = r + second.Length; + + while (l != leftEnd && r != rightEnd && (*l == *r || (ignoreCase && char.ToUpperInvariant ((*l)) == char.ToUpperInvariant ((*r))))) { + commonChars++; + l++; + r++; + } + } + } + + return commonChars; + } + + // Adapted from CoreFX sources + static bool EndsInDirectorySeparator (string path) => path.Length > 0 && IsDirectorySeparator (path[path.Length - 1]); + + static bool IsDirectorySeparator (char c) + { + if (!IsWindows) + return c == Path.DirectorySeparatorChar; + return c == Path.DirectorySeparatorChar || c == Path.AltDirectorySeparatorChar; + } + + // Adapted from CoreFX sources + static int GetRootLength(string path) + { + if (!IsWindows) + return path.Length > 0 && IsDirectorySeparator (path[0]) ? 1 : 0; + return GetRootLength_ForWindows (path); + } + + internal static bool IsValidDriveChar(char value) + { + return ((value >= 'A' && value <= 'Z') || (value >= 'a' && value <= 'z')); + } + + // Adapted from CoreFX sources + static bool IsExtended (string path) + { + // While paths like "//?/C:/" will work, they're treated the same as "\\.\" paths. + // Skipping of normalization will *only* occur if back slashes ('\') are used. + return path.Length >= DevicePrefixLength + && path[0] == '\\' + && (path[1] == '\\' || path[1] == '?') + && path[2] == '?' + && path[3] == '\\'; + } + + // Adapted from CoreFX sources + static bool IsDevice (string path) + { + // If the path begins with any two separators is will be recognized and normalized and prepped with + // "\??\" for internal usage correctly. "\??\" is recognized and handled, "/??/" is not. + return IsExtended (path) || + ( + path.Length >= DevicePrefixLength + && IsDirectorySeparator (path[0]) + && IsDirectorySeparator (path[1]) + && (path[2] == '.' || path[2] == '?') + && IsDirectorySeparator (path[3]) + ); + } + + // Adapted from CoreFX sources + static bool IsDeviceUNC (string path) + { + return path.Length >= UncExtendedPrefixLength + && IsDevice (path) + && IsDirectorySeparator (path[7]) + && path[4] == 'U' + && path[5] == 'N' + && path[6] == 'C'; + } + + // Adapted from CoreFX sources + static int GetRootLength_ForWindows (string path) + { + int pathLength = path.Length; + int i = 0; + + bool deviceSyntax = IsDevice (path); + bool deviceUnc = deviceSyntax && IsDeviceUNC(path); + + if ((!deviceSyntax || deviceUnc) && pathLength > 0 && IsDirectorySeparator (path[0])) { + // UNC or simple rooted path (e.g. "\foo", NOT "\\?\C:\foo") + if (deviceUnc || (pathLength > 1 && IsDirectorySeparator (path[1]))) { + // UNC (\\?\UNC\ or \\), scan past server\share + + // Start past the prefix ("\\" or "\\?\UNC\") + i = deviceUnc ? UncExtendedPrefixLength : UncPrefixLength; + + // Skip two separators at most + int n = 2; + while (i < pathLength && (!IsDirectorySeparator (path[i]) || --n > 0)) + i++; + } else { + // Current drive rooted (e.g. "\foo") + i = 1; + } + } else if (deviceSyntax) { + // Device path (e.g. "\\?\.", "\\.\") + // Skip any characters following the prefix that aren't a separator + i = DevicePrefixLength; + while (i < pathLength && !IsDirectorySeparator(path[i])) + i++; + + // If there is another separator take it, as long as we have had at least one + // non-separator after the prefix (e.g. don't take "\\?\\", but take "\\?\a\") + if (i < pathLength && i > DevicePrefixLength && IsDirectorySeparator(path[i])) + i++; + } else if (pathLength >= 2 && path[1] == VolumeSeparatorChar && IsValidDriveChar (path[0])) { + // Valid drive specified path ("C:", "D:", etc.) + i = 2; + + // If the colon is followed by a directory separator, move past it (e.g "C:\") + if (pathLength > 2 && IsDirectorySeparator(path[2])) + i++; + } + + return i; + } + } +} \ No newline at end of file From dd21218c9575724279be34f305782a212bdafb2c Mon Sep 17 00:00:00 2001 From: Dean Ellis Date: Mon, 2 Oct 2023 14:16:59 +0100 Subject: [PATCH 10/12] Add generated and codebehind folders to incremental clean --- src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets index de211f691a2..238942cb71d 100644 --- a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets +++ b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets @@ -2550,6 +2550,8 @@ because xbuild doesn't support framework reference assemblies. + + From 7099297dff20eb4fcd56e23b65f7e56e6ed5f919 Mon Sep 17 00:00:00 2001 From: Dean Ellis Date: Mon, 2 Oct 2023 14:54:40 +0100 Subject: [PATCH 11/12] fix whitespace --- .../Xamarin.Android.Common.targets | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets index 238942cb71d..b639a9dd4ba 100644 --- a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets +++ b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets @@ -2550,8 +2550,8 @@ because xbuild doesn't support framework reference assemblies. - - + + From 4caef47d93abaeb283b6769fa2d7d19085074138 Mon Sep 17 00:00:00 2001 From: Dean Ellis Date: Mon, 2 Oct 2023 15:05:06 +0100 Subject: [PATCH 12/12] Fix formatting --- .../Utilities/PathUtil.cs | 162 ++++++++++-------- 1 file changed, 91 insertions(+), 71 deletions(-) diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/PathUtil.cs b/src/Xamarin.Android.Build.Tasks/Utilities/PathUtil.cs index 23878c8c4b2..5156ca8aa9a 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/PathUtil.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/PathUtil.cs @@ -3,33 +3,35 @@ using System.Text; using System.Runtime.InteropServices; -namespace Xamarin.Android.Tools { - public static class PathUtil { +namespace Xamarin.Android.Tools +{ + public static class PathUtil + { - const int DevicePrefixLength = 4; + const int DevicePrefixLength = 4; const int UncPrefixLength = 2; const int UncExtendedPrefixLength = 8; internal const char VolumeSeparatorChar = ':'; - static bool IsWindows = Path.DirectorySeparatorChar == '\\'; + static bool IsWindows = Path.DirectorySeparatorChar == '\\'; // Adapted from CoreFX sources - public static string GetRelativePath (string relativeTo, string path, StringComparison comparisonType = StringComparison.OrdinalIgnoreCase) + public static string GetRelativePath(string relativeTo, string path, StringComparison comparisonType = StringComparison.OrdinalIgnoreCase) { - if (String.IsNullOrEmpty (relativeTo)) - throw new ArgumentException ("must not be null or empty", nameof (relativeTo)); + if (String.IsNullOrEmpty(relativeTo)) + throw new ArgumentException("must not be null or empty", nameof(relativeTo)); - if (String.IsNullOrEmpty (path)) - throw new ArgumentException ("must not be null or empty", nameof (path)); + if (String.IsNullOrEmpty(path)) + throw new ArgumentException("must not be null or empty", nameof(path)); - relativeTo = Path.GetFullPath (relativeTo); - path = Path.GetFullPath (path); + relativeTo = Path.GetFullPath(relativeTo); + path = Path.GetFullPath(path); // Need to check if the roots are different- if they are we need to return the "to" path. - if (!AreRootsEqual (relativeTo, path, comparisonType)) + if (!AreRootsEqual(relativeTo, path, comparisonType)) return path; - int commonLength = GetCommonPathLength (relativeTo, path, ignoreCase: comparisonType == StringComparison.OrdinalIgnoreCase); + int commonLength = GetCommonPathLength(relativeTo, path, ignoreCase: comparisonType == StringComparison.OrdinalIgnoreCase); // If there is nothing in common they can't share the same root, return the "to" path as is. if (commonLength == 0) @@ -37,10 +39,10 @@ public static string GetRelativePath (string relativeTo, string path, StringComp // Trailing separators aren't significant for comparison int relativeToLength = relativeTo.Length; - if (EndsInDirectorySeparator (relativeTo)) + if (EndsInDirectorySeparator(relativeTo)) relativeToLength--; - bool pathEndsInSeparator = EndsInDirectorySeparator (path); + bool pathEndsInSeparator = EndsInDirectorySeparator(path); int pathLength = path.Length; if (pathEndsInSeparator) pathLength--; @@ -59,19 +61,24 @@ public static string GetRelativePath (string relativeTo, string path, StringComp // C:\Foo\Bar C:\Bar\Bar L3, S2 -> ..\..\Bar\Bar // C:\Foo\Foo C:\Foo\Bar L7, S1 -> ..\Bar - var sb = new StringBuilder (Math.Max (relativeTo.Length, path.Length)); + var sb = new StringBuilder(Math.Max(relativeTo.Length, path.Length)); // Add parent segments for segments past the common on the "from" path - if (commonLength < relativeToLength) { - sb.Append (".."); - - for (int i = commonLength + 1; i < relativeToLength; i++) { - if (IsDirectorySeparator (relativeTo[i])) { - sb.Append (Path.DirectorySeparatorChar); - sb.Append (".."); + if (commonLength < relativeToLength) + { + sb.Append(".."); + + for (int i = commonLength + 1; i < relativeToLength; i++) + { + if (IsDirectorySeparator(relativeTo[i])) + { + sb.Append(Path.DirectorySeparatorChar); + sb.Append(".."); } } - } else if (IsDirectorySeparator (path[commonLength])) { + } + else if (IsDirectorySeparator(path[commonLength])) + { // No parent segments and we need to eat the initial separator // (C:\Foo C:\Foo\Bar case) commonLength++; @@ -82,25 +89,27 @@ public static string GetRelativePath (string relativeTo, string path, StringComp if (pathEndsInSeparator) differenceLength++; - if (differenceLength > 0) { - if (sb.Length > 0) { - sb.Append (Path.DirectorySeparatorChar); + if (differenceLength > 0) + { + if (sb.Length > 0) + { + sb.Append(Path.DirectorySeparatorChar); } sb.Append(path, commonLength, differenceLength); } - return sb.ToString (); + return sb.ToString(); } - // Adapted from CoreFX sources - static bool AreRootsEqual (string first, string second, StringComparison comparisonType) + // Adapted from CoreFX sources + static bool AreRootsEqual(string first, string second, StringComparison comparisonType) { - int firstRootLength = GetRootLength (first); - int secondRootLength = GetRootLength (second); + int firstRootLength = GetRootLength(first); + int secondRootLength = GetRootLength(second); return firstRootLength == secondRootLength - && String.Compare ( + && String.Compare( strA: first, indexA: 0, strB: second, @@ -110,43 +119,46 @@ static bool AreRootsEqual (string first, string second, StringComparison compari } // Adapted from CoreFX sources - static int GetCommonPathLength (string first, string second, bool ignoreCase) + static int GetCommonPathLength(string first, string second, bool ignoreCase) { - int commonChars = EqualStartingCharacterCount (first, second, ignoreCase: ignoreCase); + int commonChars = EqualStartingCharacterCount(first, second, ignoreCase: ignoreCase); // If nothing matches if (commonChars == 0) return commonChars; // Or we're a full string and equal length or match to a separator - if (commonChars == first.Length && (commonChars == second.Length || IsDirectorySeparator (second[commonChars]))) + if (commonChars == first.Length && (commonChars == second.Length || IsDirectorySeparator(second[commonChars]))) return commonChars; - if (commonChars == second.Length && IsDirectorySeparator (first[commonChars])) + if (commonChars == second.Length && IsDirectorySeparator(first[commonChars])) return commonChars; // It's possible we matched somewhere in the middle of a segment e.g. C:\Foodie and C:\Foobar. - while (commonChars > 0 && !IsDirectorySeparator (first[commonChars - 1])) + while (commonChars > 0 && !IsDirectorySeparator(first[commonChars - 1])) commonChars--; return commonChars; } // Adapted from CoreFX sources - static unsafe int EqualStartingCharacterCount (string first, string second, bool ignoreCase) + static unsafe int EqualStartingCharacterCount(string first, string second, bool ignoreCase) { - if (String.IsNullOrEmpty (first) || string.IsNullOrEmpty (second)) + if (String.IsNullOrEmpty(first) || string.IsNullOrEmpty(second)) return 0; int commonChars = 0; - fixed (char* f = first) { - fixed (char* s = second) { + fixed (char* f = first) + { + fixed (char* s = second) + { char* l = f; char* r = s; char* leftEnd = l + first.Length; char* rightEnd = r + second.Length; - while (l != leftEnd && r != rightEnd && (*l == *r || (ignoreCase && char.ToUpperInvariant ((*l)) == char.ToUpperInvariant ((*r))))) { + while (l != leftEnd && r != rightEnd && (*l == *r || (ignoreCase && char.ToUpperInvariant((*l)) == char.ToUpperInvariant((*r))))) + { commonChars++; l++; r++; @@ -158,30 +170,30 @@ static unsafe int EqualStartingCharacterCount (string first, string second, bool } // Adapted from CoreFX sources - static bool EndsInDirectorySeparator (string path) => path.Length > 0 && IsDirectorySeparator (path[path.Length - 1]); + static bool EndsInDirectorySeparator(string path) => path.Length > 0 && IsDirectorySeparator(path[path.Length - 1]); - static bool IsDirectorySeparator (char c) + static bool IsDirectorySeparator(char c) { - if (!IsWindows) - return c == Path.DirectorySeparatorChar; - return c == Path.DirectorySeparatorChar || c == Path.AltDirectorySeparatorChar; + if (!IsWindows) + return c == Path.DirectorySeparatorChar; + return c == Path.DirectorySeparatorChar || c == Path.AltDirectorySeparatorChar; } // Adapted from CoreFX sources static int GetRootLength(string path) { - if (!IsWindows) - return path.Length > 0 && IsDirectorySeparator (path[0]) ? 1 : 0; - return GetRootLength_ForWindows (path); + if (!IsWindows) + return path.Length > 0 && IsDirectorySeparator(path[0]) ? 1 : 0; + return GetRootLength_ForWindows(path); } - internal static bool IsValidDriveChar(char value) + internal static bool IsValidDriveChar(char value) { return ((value >= 'A' && value <= 'Z') || (value >= 'a' && value <= 'z')); } - // Adapted from CoreFX sources - static bool IsExtended (string path) + // Adapted from CoreFX sources + static bool IsExtended(string path) { // While paths like "//?/C:/" will work, they're treated the same as "\\.\" paths. // Skipping of normalization will *only* occur if back slashes ('\') are used. @@ -193,43 +205,45 @@ static bool IsExtended (string path) } // Adapted from CoreFX sources - static bool IsDevice (string path) + static bool IsDevice(string path) { // If the path begins with any two separators is will be recognized and normalized and prepped with // "\??\" for internal usage correctly. "\??\" is recognized and handled, "/??/" is not. - return IsExtended (path) || + return IsExtended(path) || ( path.Length >= DevicePrefixLength - && IsDirectorySeparator (path[0]) - && IsDirectorySeparator (path[1]) + && IsDirectorySeparator(path[0]) + && IsDirectorySeparator(path[1]) && (path[2] == '.' || path[2] == '?') - && IsDirectorySeparator (path[3]) + && IsDirectorySeparator(path[3]) ); } // Adapted from CoreFX sources - static bool IsDeviceUNC (string path) + static bool IsDeviceUNC(string path) { return path.Length >= UncExtendedPrefixLength - && IsDevice (path) - && IsDirectorySeparator (path[7]) + && IsDevice(path) + && IsDirectorySeparator(path[7]) && path[4] == 'U' && path[5] == 'N' && path[6] == 'C'; } // Adapted from CoreFX sources - static int GetRootLength_ForWindows (string path) + static int GetRootLength_ForWindows(string path) { int pathLength = path.Length; int i = 0; - bool deviceSyntax = IsDevice (path); + bool deviceSyntax = IsDevice(path); bool deviceUnc = deviceSyntax && IsDeviceUNC(path); - if ((!deviceSyntax || deviceUnc) && pathLength > 0 && IsDirectorySeparator (path[0])) { + if ((!deviceSyntax || deviceUnc) && pathLength > 0 && IsDirectorySeparator(path[0])) + { // UNC or simple rooted path (e.g. "\foo", NOT "\\?\C:\foo") - if (deviceUnc || (pathLength > 1 && IsDirectorySeparator (path[1]))) { + if (deviceUnc || (pathLength > 1 && IsDirectorySeparator(path[1]))) + { // UNC (\\?\UNC\ or \\), scan past server\share // Start past the prefix ("\\" or "\\?\UNC\") @@ -237,13 +251,17 @@ static int GetRootLength_ForWindows (string path) // Skip two separators at most int n = 2; - while (i < pathLength && (!IsDirectorySeparator (path[i]) || --n > 0)) + while (i < pathLength && (!IsDirectorySeparator(path[i]) || --n > 0)) i++; - } else { + } + else + { // Current drive rooted (e.g. "\foo") i = 1; } - } else if (deviceSyntax) { + } + else if (deviceSyntax) + { // Device path (e.g. "\\?\.", "\\.\") // Skip any characters following the prefix that aren't a separator i = DevicePrefixLength; @@ -254,7 +272,9 @@ static int GetRootLength_ForWindows (string path) // non-separator after the prefix (e.g. don't take "\\?\\", but take "\\?\a\") if (i < pathLength && i > DevicePrefixLength && IsDirectorySeparator(path[i])) i++; - } else if (pathLength >= 2 && path[1] == VolumeSeparatorChar && IsValidDriveChar (path[0])) { + } + else if (pathLength >= 2 && path[1] == VolumeSeparatorChar && IsValidDriveChar(path[0])) + { // Valid drive specified path ("C:", "D:", etc.) i = 2; @@ -265,5 +285,5 @@ static int GetRootLength_ForWindows (string path) return i; } - } + } } \ No newline at end of file