diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/Aapt2.cs b/src/Xamarin.Android.Build.Tasks/Tasks/Aapt2.cs index 97d631aa698..2ac351070a6 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/Aapt2.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/Aapt2.cs @@ -22,7 +22,6 @@ namespace Xamarin.Android.Tasks { public abstract class Aapt2 : AsyncTask { private const int MAX_PATH = 260; - private const int ASCII_MAX_CHAR = 127; private static readonly int DefaultMaxAapt2Daemons = 6; protected Dictionary _resource_name_case_map; @@ -52,6 +51,17 @@ public abstract class Aapt2 : AsyncTask { public static bool IsInvalidFilename (string path) => Path.GetFileName (path).StartsWith (".", StringComparison.Ordinal); + /// + /// Returns if is an ASCII + /// character ([ U+0000..U+007F ]). + /// + /// + /// Per http://www.unicode.org/glossary/#ASCII, ASCII is only U+0000..U+007F. + /// We cannot use Char.IsAscii cos we are .netstandard2.0 + /// Source https://github.com/dotnet/runtime/blob/1d1bf92fcf43aa6981804dc53c5174445069c9e4/src/libraries/System.Private.CoreLib/src/System/Char.cs#L91 + /// + public static bool IsAscii(char c) => (uint)c <= '\x007f'; + protected string ResourceDirectoryFullPath (string resourceDirectory) { return (Path.IsPathRooted (resourceDirectory) ? resourceDirectory : Path.Combine (WorkingDirectory, resourceDirectory)).TrimEnd ('\\'); @@ -226,13 +236,13 @@ static bool IsFilePathToLong (string filePath) return false; } - static bool IsPathOnlyASCII (string filePath) + static protected bool IsPathOnlyASCII (string filePath) { if (!OS.IsWindows) return true; foreach (var c in filePath) - if (c > ASCII_MAX_CHAR) // cannot use Char.IsAscii cos we are .netstandard2.0 + if (!IsAscii (c)) return false; return true; } diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/Aapt2Link.cs b/src/Xamarin.Android.Build.Tasks/Tasks/Aapt2Link.cs index 4dd27c19321..4c6397a111c 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/Aapt2Link.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/Aapt2Link.cs @@ -272,11 +272,16 @@ string [] GenerateCommandLineCommands (string ManifestFile, string currentAbi, s } } + var hasAssetsErrors = false; // When adding Assets the first item found takes precedence. - // So we need to add the applicaiton Assets first. + // So we need to add the application Assets first. if (!string.IsNullOrEmpty (AssetsDirectory)) { var assetDir = GetFullPath (AssetsDirectory.TrimEnd ('\\')); if (Directory.Exists (assetDir)) { + if (OS.IsWindows && !IsPathOnlyASCII (assetDir)) { + hasAssetsErrors = true; + LogCodedError ("APT2265", Properties.Resources.APT2265, assetDir); + } cmd.Add ("-A"); cmd.Add (assetDir); } else { @@ -289,6 +294,11 @@ string [] GenerateCommandLineCommands (string ManifestFile, string currentAbi, s var assetDir = GetFullPath (AdditionalAndroidAssetPaths [i].ItemSpec.TrimEnd ('\\')); if (!string.IsNullOrWhiteSpace (assetDir)) { if (Directory.Exists (assetDir)) { + if (OS.IsWindows && !IsPathOnlyASCII (assetDir)) { + hasAssetsErrors = true; + LogCodedError ("APT2265", Properties.Resources.APT2265, assetDir); + continue; + } cmd.Add ("-A"); cmd.Add (GetFullPath (assetDir)); } else { @@ -298,6 +308,10 @@ string [] GenerateCommandLineCommands (string ManifestFile, string currentAbi, s } } + if (hasAssetsErrors) { + return Array.Empty (); + } + if (!string.IsNullOrEmpty (ProguardRuleOutput)) { cmd.Add ("--proguard"); cmd.Add (GetFullPath (GetManifestRulesFile (manifestDir))); diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/Aapt2LinkAssetPack.cs b/src/Xamarin.Android.Build.Tasks/Tasks/Aapt2LinkAssetPack.cs index 7c9a1f16a5a..6e6b437a870 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/Aapt2LinkAssetPack.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/Aapt2LinkAssetPack.cs @@ -63,8 +63,13 @@ protected string[] GenerateCommandLineCommands (ITaskItem manifest, ITaskItem ou cmd.Add ("--custom-package"); cmd.Add (PackageName); foreach (var assetDirectory in AssetDirectories) { + var fullPath = GetFullPath (assetDirectory.ItemSpec); + if (OS.IsWindows && !IsPathOnlyASCII (fullPath)) { + LogCodedError ("APT2265", Properties.Resources.APT2265, fullPath); + continue; + } cmd.Add ("-A"); - cmd.Add (GetFullPath (assetDirectory.ItemSpec)); + cmd.Add (fullPath); } cmd.Add ($"-o"); cmd.Add (GetFullPath (output.ItemSpec)); diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/BuildAssetsTest.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/BuildAssetsTest.cs index 734848651bd..87339309d10 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/BuildAssetsTest.cs +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/BuildAssetsTest.cs @@ -133,6 +133,26 @@ public void CheckAssetsAreIncludedInAPK () } } + [Test] + public void InvalidAssetDirectoryWithNonASCIIChars () + { + var proj = new XamarinAndroidApplicationProject () { + ProjectName = "App1", + IsRelease = true, + OtherBuildItems = { + new AndroidItem.AndroidAsset ("Assets\\asset1.txt") { + TextContent = () => "Asset1", + Encoding = Encoding.ASCII, + }, + }, + }; + using (var b = CreateApkBuilder (Path.Combine ("temp", "InvalidAssetDirectoryWithNonASCIIChars_Ümläüt", proj.ProjectName))) { + b.ThrowOnBuildFailure = false; + Assert.AreEqual (!IsWindows, b.Build (proj), $"{proj.ProjectName} should {(IsWindows ? "not " : "")}have built successfully."); + Assert.AreEqual (IsWindows, b.LastBuildOutput.ContainsText ("APT2265"), $"APT2265 should {(IsWindows ? "" : "not ")}have been raised."); + } + } + [Test] public void FullPath () { diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/BuildTest.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/BuildTest.cs index 20abc9bfe34..5fa64aa2305 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/BuildTest.cs +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/BuildTest.cs @@ -659,7 +659,7 @@ public void BuildApplicationWithSpacesInPath ([Values (true, false)] bool enable IsRelease = true, AotAssemblies = true, LinkTool = linkTool, - References = { new BuildItem ("ProjectReference", $"..\\{folderName}Library1\\Library1.csproj") } + References = { new BuildItem ("ProjectReference", $"..\\{folderName}Library1\\Library1.csproj") }, }; proj.OtherBuildItems.Add (new BuildItem ("AndroidJavaLibrary", "Hello (World).jar") { BinaryContent = () => Convert.FromBase64String (@" UEsDBBQACAgIAMl8lUsAAAAAAAAAAAAAAAAJAAQATUVUQS1JTkYv/soAAAMAUEsHCAAAAAACAAAAA