diff --git a/.external b/.external deleted file mode 100644 index 95a3956a53a..00000000000 --- a/.external +++ /dev/null @@ -1 +0,0 @@ -DevDiv/android-platform-support:main@9c8f9978a9806230f520469b38a9596ed3a607f8 diff --git a/.gdn/policheck/PoliCheck.Exclusions.xml b/.gdn/policheck/PoliCheck.Exclusions.xml index 6300aae539a..c88953595cb 100644 --- a/.gdn/policheck/PoliCheck.Exclusions.xml +++ b/.gdn/policheck/PoliCheck.Exclusions.xml @@ -3,7 +3,7 @@ LICENSE-DATA|LOCALIZE - src\Mono.Android\Profiles|src\Mono.Android\PublicAPI + Localize\loc|src\Mono.Android\Profiles|src\Mono.Android\PublicAPI|src\Xamarin.Installer.AndroidSDK\Feeds|src\Xamarin.Installer.AndroidSDK\Resources|src\Xamarin.Installer.AndroidSDK\Xamarin.Installer.AndroidSDK\Kajabity.Tools.Java diff --git a/.gitignore b/.gitignore index 77ef9330505..61bad7b5f60 100644 --- a/.gitignore +++ b/.gitignore @@ -22,9 +22,7 @@ apk-sizes-*.txt *.binlog *.ProjectImports.zip *~ -external/android-platform-support/ external/monodroid/ -external/android-platform-support/ external/mono/ tests/api-compatibility/reference/*/*.dll tests/api-compatibility/reference/*/*.cs diff --git a/.gitmodules b/.gitmodules index c5443e9db15..760c2b84aeb 100644 --- a/.gitmodules +++ b/.gitmodules @@ -34,3 +34,6 @@ path = external/xxHash url = https://github.com/Cyan4973/xxHash.git branch = release +[submodule "external/termux-elf-cleaner"] + path = external/termux-elf-cleaner + url = https://github.com/termux/termux-elf-cleaner diff --git a/Directory.Build.props b/Directory.Build.props index f80effb6135..a33b7f5ea0d 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -57,6 +57,16 @@ 2.17.3 2.14.1 5.9.3 + + 2.3.0 + 2.5.2 + 5.0.0 + 7.1.0-final.1.21458.1 + 7.2.4 + 3.2.26 + 3.2.26 + 1.3.3 + 7.0.0 @@ -75,6 +85,8 @@ <_AllowProjectWarnings Condition=" '$(MSBuildProjectFile)' == 'Xamarin.Android.Build.Tests.csproj' ">true <_AllowProjectWarnings Condition=" '$(MSBuildProjectFile)' == 'Xamarin.Android.JcwGen-Tests.csproj' ">true <_AllowProjectWarnings Condition=" '$(MSBuildProjectFile)' == 'Xamarin.Android.NUnitLite.NET.csproj' ">true + <_AllowProjectWarnings Condition=" '$(MSBuildProjectFile)' == 'Xamarin.Installer.AndroidSDK.csproj' ">true + <_AllowProjectWarnings Condition=" '$(MSBuildProjectFile)' == 'Xamarin.Installer.Common.csproj' ">true <_AllowProjectWarnings Condition=" '$(MSBuildProjectFile)' == 'Xamarin.ProjectTools.csproj' ">true true diff --git a/Documentation/building/unix/instructions.md b/Documentation/building/unix/instructions.md index e2e848b5d64..7283fa25f3c 100644 --- a/Documentation/building/unix/instructions.md +++ b/Documentation/building/unix/instructions.md @@ -16,16 +16,7 @@ can also be used by setting the `$(MSBUILD)` make variable to `xbuild`. 4. (Optional) [Configure the build](../configuration.md). - 5. (For Microsoft team members only) (Optional) Prepare external - proprietary git dependencies - - make prepare-external-git-dependencies - - This will clone or update a monodroid checkout in `external` and - ensure that subsequent `prepare` and `make` invocations will build - proprietary components. - - 6. Prepare the project: + 5. Prepare the project: make prepare # -or- @@ -108,9 +99,6 @@ Alternatively, .NET for Android workload packs can be built with: Several `.nupkg` files will be output in `./bin/Build$(Configuration)/nuget-unsigned`. -Commercial installers will be created by this command if the -`make prepare-external-git-dependencies` command was ran before building. - # Running Unit Tests diff --git a/Documentation/building/windows/instructions.md b/Documentation/building/windows/instructions.md index 70a214b821f..9fe2b5a8657 100644 --- a/Documentation/building/windows/instructions.md +++ b/Documentation/building/windows/instructions.md @@ -30,16 +30,7 @@ be available within the Command-Line environment. dotnet-local.cmd build Xamarin.Android.sln - 7. (For Microsoft team members only - Optional) In a [Developer Command - Prompt][developer-prompt], build external proprietary git - dependencies: - - dotnet-local.cmd build Xamarin.Android.sln -t:BuildExternal - - This will clone and build external proprietary components such as - the `android-platform-support` repository in Azure DevOps. - - 8. Configure local `android` workload: + 7. Configure local `android` workload: dotnet-local.cmd build build-tools/create-packs/Microsoft.Android.Sdk.proj -t:ConfigureLocalWorkload @@ -114,10 +105,6 @@ Once `dotnet msbuild Xamarin.Android.sln -t:Prepare` is complete, Several `.nupkg` files will be output in `.\bin\Build$(Configuration)\nuget-unsigned`. -Commercial packages will be created by this command if the -`dotnet-local.cmd build Xamarin.Android.sln -t:BuildExternal` -command was ran before building. - ## Building Unit Tests Once `dotnet-local.cmd build Xamarin.Android.sln` has completed, the unit tests may diff --git a/Documentation/guides/vscode-support.md b/Documentation/guides/vscode-support.md index 38ceb27b73d..6d0a2d94620 100644 --- a/Documentation/guides/vscode-support.md +++ b/Documentation/guides/vscode-support.md @@ -16,7 +16,6 @@ Select **Build All .NET for Android**. You will then be presented with a list of options: * `Prepare` - Installs the required Dependencies. -* `PrepareExternal` - Installs the required Commercial Dependencies (Xamarin Team Members Only) * `Build` - Build .NET for Android. * `Pack` - Create the NuGet Packages. * `Everything` - Calls Prepare, Build and Pack. @@ -25,10 +24,6 @@ The normal order is `Prepare`, `Build` then `Pack`. This will result in a usable copy of .NET for Android. You can now use it to build apps and run the unit tests. -Note: `PrepareExternal` is for internal Xamarin Team members only, this sets up -the commercial parts of the repository. Trying to use this command when you -do not have access to the required repositories will result in a failure. - ## Running/Debugging Unit Tests Xamarin.Android Legacy uses the diff --git a/Localize/LocProject.json b/Localize/LocProject.json index 430efece892..2d7569187a5 100644 --- a/Localize/LocProject.json +++ b/Localize/LocProject.json @@ -12,6 +12,36 @@ "CopyOption": "LangIDOnName", "SourceFile": ".\\src\\Xamarin.Android.Build.Tasks\\Properties\\Resources.resx", "OutputPath": ".\\src\\Xamarin.Android.Build.Tasks\\Properties\\" + }, + { + "CopyOption": "LangIDOnName", + "SourceFile": ".\\src\\Xamarin.Installer.Common\\Properties\\Resources.resx", + "OutputPath": ".\\src\\Xamarin.Installer.Common\\Properties\\", + "LclFile": "Localize\\loc\\{Lang}\\Xamarin.Installer.Common\\Properties\\Resources.resx.lcl" + }, + { + "CopyOption": "LangIDOnName", + "SourceFile": ".\\src\\Xamarin.Installer.AndroidSDK\\Properties\\Resources.resx", + "OutputPath": ".\\src\\Xamarin.Installer.AndroidSDK\\Properties\\", + "LclFile": "Localize\\loc\\{Lang}\\Xamarin.Installer.AndroidSDK\\Properties\\Resources.resx.lcl" + }, + { + "CopyOption": "LangIDOnName", + "SourceFile": ".\\src\\Xamarin.Installer.Build.Tasks\\Properties\\Resources.resx", + "OutputPath": ".\\src\\Xamarin.Installer.Build.Tasks\\Properties\\", + "LclFile": "Localize\\loc\\{Lang}\\Xamarin.Installer.Build.Tasks\\Properties\\Resources.resx.lcl" + }, + { + "CopyOption": "LangIDOnName", + "SourceFile": ".\\src\\Xamarin.AndroidTools\\Properties\\Resources.resx", + "OutputPath": ".\\src\\Xamarin.AndroidTools\\Properties\\", + "LclFile": "Localize\\loc\\{Lang}\\Xamarin.AndroidTools\\Properties\\Resources.resx.lcl" + }, + { + "CopyOption": "LangIDOnName", + "SourceFile": ".\\src\\Xamarin.Android.Build.Debugging.Tasks\\Properties\\Resources.resx", + "OutputPath": ".\\src\\Xamarin.Android.Build.Debugging.Tasks\\Properties\\", + "LclFile": "Localize\\loc\\{Lang}\\tools\\msbuild\\Properties\\Resources.resx.lcl" } ] } diff --git a/Localize/loc/cs/Xamarin.AndroidTools/Properties/Resources.resx.lcl b/Localize/loc/cs/Xamarin.AndroidTools/Properties/Resources.resx.lcl new file mode 100644 index 00000000000..bf0bdc4c106 --- /dev/null +++ b/Localize/loc/cs/Xamarin.AndroidTools/Properties/Resources.resx.lcl @@ -0,0 +1,146 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Localize/loc/cs/Xamarin.Installer.AndroidSDK/Properties/Resources.resx.lcl b/Localize/loc/cs/Xamarin.Installer.AndroidSDK/Properties/Resources.resx.lcl new file mode 100644 index 00000000000..9eeec9c1b19 --- /dev/null +++ b/Localize/loc/cs/Xamarin.Installer.AndroidSDK/Properties/Resources.resx.lcl @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Localize/loc/cs/Xamarin.Installer.Build.Tasks/Properties/Resources.resx.lcl b/Localize/loc/cs/Xamarin.Installer.Build.Tasks/Properties/Resources.resx.lcl new file mode 100644 index 00000000000..d4b9a89379c --- /dev/null +++ b/Localize/loc/cs/Xamarin.Installer.Build.Tasks/Properties/Resources.resx.lcl @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Localize/loc/cs/Xamarin.Installer.Common/Properties/Resources.resx.lcl b/Localize/loc/cs/Xamarin.Installer.Common/Properties/Resources.resx.lcl new file mode 100644 index 00000000000..6184e829c3f --- /dev/null +++ b/Localize/loc/cs/Xamarin.Installer.Common/Properties/Resources.resx.lcl @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Localize/loc/cs/tools/msbuild/Properties/Resources.resx.lcl b/Localize/loc/cs/tools/msbuild/Properties/Resources.resx.lcl new file mode 100644 index 00000000000..47766a3804d --- /dev/null +++ b/Localize/loc/cs/tools/msbuild/Properties/Resources.resx.lcl @@ -0,0 +1,191 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Send Feedback->Report a Problem' menu item in Visual Studio]D;]A; or 'Help->Report a Problem' in Visual Studio for Mac. If possible attach a full diagnostic build]D;]A; log to the feedback report as this will help us diagnose the issue.]]> + + Odeslat zpětnou vazbu->Nahlásit problém v aplikaci Visual Studio]D;]A; nebo Nápověda->Nahlásit problém v aplikaci Visual Studio pro Mac. Pokud je to možné, připojte úplný diagnostický]D;]A; protokol buildu k sestavě zpětné vazby, protože to nám pomůže diagnostikovat problém.]]> + + + + + + + + + + + + + + + + Send Feedback->Report a Problem' menu item in Visual Studio]D;]A;or 'Help->Report a Problem' in Visual Studio for Mac.]D;]A;Please set the 'EmbedAssembliesIntoApk' MSBuild property to 'true' to disable Fast Deployment in the Visual Studio project property pages, or edit the project file in a text editor.]]> + + Odeslat zpětnou vazbu->Nahlásit problém v aplikaci Visual Studio]D;]A;nebo Nápověda->Nahlásit problém v aplikaci Visual Studio pro Mac.]D;]A;Pokud chcete zakázat rychlé nasazení na stránkách vlastností projektu Visual Studio, nastavte vlastnost MSBuild EmbedAssembliesIntoApk na hodnotu true, nebo upravte soubor projektu v textovém editoru.]]> + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Localize/loc/de/Xamarin.AndroidTools/Properties/Resources.resx.lcl b/Localize/loc/de/Xamarin.AndroidTools/Properties/Resources.resx.lcl new file mode 100644 index 00000000000..8cb023d9455 --- /dev/null +++ b/Localize/loc/de/Xamarin.AndroidTools/Properties/Resources.resx.lcl @@ -0,0 +1,146 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Localize/loc/de/Xamarin.Installer.AndroidSDK/Properties/Resources.resx.lcl b/Localize/loc/de/Xamarin.Installer.AndroidSDK/Properties/Resources.resx.lcl new file mode 100644 index 00000000000..32a8dcd3a6f --- /dev/null +++ b/Localize/loc/de/Xamarin.Installer.AndroidSDK/Properties/Resources.resx.lcl @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Localize/loc/de/Xamarin.Installer.Build.Tasks/Properties/Resources.resx.lcl b/Localize/loc/de/Xamarin.Installer.Build.Tasks/Properties/Resources.resx.lcl new file mode 100644 index 00000000000..b6aa0d8d034 --- /dev/null +++ b/Localize/loc/de/Xamarin.Installer.Build.Tasks/Properties/Resources.resx.lcl @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Localize/loc/de/Xamarin.Installer.Common/Properties/Resources.resx.lcl b/Localize/loc/de/Xamarin.Installer.Common/Properties/Resources.resx.lcl new file mode 100644 index 00000000000..10a0243eed7 --- /dev/null +++ b/Localize/loc/de/Xamarin.Installer.Common/Properties/Resources.resx.lcl @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Localize/loc/de/tools/msbuild/Properties/Resources.resx.lcl b/Localize/loc/de/tools/msbuild/Properties/Resources.resx.lcl new file mode 100644 index 00000000000..91dddd38ca0 --- /dev/null +++ b/Localize/loc/de/tools/msbuild/Properties/Resources.resx.lcl @@ -0,0 +1,191 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Send Feedback->Report a Problem' menu item in Visual Studio]D;]A; or 'Help->Report a Problem' in Visual Studio for Mac. If possible attach a full diagnostic build]D;]A; log to the feedback report as this will help us diagnose the issue.]]> + + Feedback senden->Ein Problem melden“ in Visual Studio]D;]A; oder „Hilfe->Ein Problem melden“ in Visual Studio für Mac. Fügen Sie nach Möglichkeit ein vollständiges Diagnosebuild]D;]A; Protokoll im Feedbackbericht an, da uns dies bei der Diagnose des Problems hilft.]]> + + + + + + + + + + + + + + + + Send Feedback->Report a Problem' menu item in Visual Studio]D;]A;or 'Help->Report a Problem' in Visual Studio for Mac.]D;]A;Please set the 'EmbedAssembliesIntoApk' MSBuild property to 'true' to disable Fast Deployment in the Visual Studio project property pages, or edit the project file in a text editor.]]> + + Feedback senden->Ein Problem melden“ in Visual Studio]D;]A;oder „Hilfe->Ein Problem melden“ in Visual Studio für Mac.]D;]A;Legen Sie die MSBuild-Eigenschaft „EmbedAssembliesIntoApk“ auf WAHR fest, um die Schnelle Bereitstellung auf den Visual Studio-Projekteigenschaftenseiten zu deaktivieren, oder bearbeiten Sie die Projektdatei in einem Text-Editor.]]> + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Localize/loc/es/Xamarin.AndroidTools/Properties/Resources.resx.lcl b/Localize/loc/es/Xamarin.AndroidTools/Properties/Resources.resx.lcl new file mode 100644 index 00000000000..c4092ea900e --- /dev/null +++ b/Localize/loc/es/Xamarin.AndroidTools/Properties/Resources.resx.lcl @@ -0,0 +1,146 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Localize/loc/es/Xamarin.Installer.AndroidSDK/Properties/Resources.resx.lcl b/Localize/loc/es/Xamarin.Installer.AndroidSDK/Properties/Resources.resx.lcl new file mode 100644 index 00000000000..cd436a3e0c1 --- /dev/null +++ b/Localize/loc/es/Xamarin.Installer.AndroidSDK/Properties/Resources.resx.lcl @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Localize/loc/es/Xamarin.Installer.Build.Tasks/Properties/Resources.resx.lcl b/Localize/loc/es/Xamarin.Installer.Build.Tasks/Properties/Resources.resx.lcl new file mode 100644 index 00000000000..2c01886e191 --- /dev/null +++ b/Localize/loc/es/Xamarin.Installer.Build.Tasks/Properties/Resources.resx.lcl @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Localize/loc/es/Xamarin.Installer.Common/Properties/Resources.resx.lcl b/Localize/loc/es/Xamarin.Installer.Common/Properties/Resources.resx.lcl new file mode 100644 index 00000000000..0866890b955 --- /dev/null +++ b/Localize/loc/es/Xamarin.Installer.Common/Properties/Resources.resx.lcl @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Localize/loc/es/tools/msbuild/Properties/Resources.resx.lcl b/Localize/loc/es/tools/msbuild/Properties/Resources.resx.lcl new file mode 100644 index 00000000000..e9a1dad5bc6 --- /dev/null +++ b/Localize/loc/es/tools/msbuild/Properties/Resources.resx.lcl @@ -0,0 +1,191 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Send Feedback->Report a Problem' menu item in Visual Studio]D;]A; or 'Help->Report a Problem' in Visual Studio for Mac. If possible attach a full diagnostic build]D;]A; log to the feedback report as this will help us diagnose the issue.]]> + + Send Feedback->Report a Problem" en Visual Studio]D;]A; o "Help->Report a Problem" en Visual Studio para Mac. Si es posible, adjunte una compilación de diagnóstico completa]D;]A; inicie sesión en el informe de comentarios, ya que esto nos ayudará a diagnosticar el problema.]]> + + + + + + + + + + + + + + + + Send Feedback->Report a Problem' menu item in Visual Studio]D;]A;or 'Help->Report a Problem' in Visual Studio for Mac.]D;]A;Please set the 'EmbedAssembliesIntoApk' MSBuild property to 'true' to disable Fast Deployment in the Visual Studio project property pages, or edit the project file in a text editor.]]> + + Send Feedback->Report a Problem" en Visual Studio]D;]A;o "Help->Report a Problem" en Visual Studio para Mac.]D;]A;Establezca la propiedad "EmbedAssembliesIntoApk" de MSBuild en "true" para deshabilitar la implementación rápida en las páginas de propiedades del proyecto de Visual Studio, o bien edite el archivo del proyecto en un editor de texto.]]> + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Localize/loc/fr/Xamarin.AndroidTools/Properties/Resources.resx.lcl b/Localize/loc/fr/Xamarin.AndroidTools/Properties/Resources.resx.lcl new file mode 100644 index 00000000000..04011c4e2a8 --- /dev/null +++ b/Localize/loc/fr/Xamarin.AndroidTools/Properties/Resources.resx.lcl @@ -0,0 +1,146 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Localize/loc/fr/Xamarin.Installer.AndroidSDK/Properties/Resources.resx.lcl b/Localize/loc/fr/Xamarin.Installer.AndroidSDK/Properties/Resources.resx.lcl new file mode 100644 index 00000000000..7ad9fb3b555 --- /dev/null +++ b/Localize/loc/fr/Xamarin.Installer.AndroidSDK/Properties/Resources.resx.lcl @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Localize/loc/fr/Xamarin.Installer.Build.Tasks/Properties/Resources.resx.lcl b/Localize/loc/fr/Xamarin.Installer.Build.Tasks/Properties/Resources.resx.lcl new file mode 100644 index 00000000000..518cac0c4c4 --- /dev/null +++ b/Localize/loc/fr/Xamarin.Installer.Build.Tasks/Properties/Resources.resx.lcl @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Localize/loc/fr/Xamarin.Installer.Common/Properties/Resources.resx.lcl b/Localize/loc/fr/Xamarin.Installer.Common/Properties/Resources.resx.lcl new file mode 100644 index 00000000000..cfe52251cca --- /dev/null +++ b/Localize/loc/fr/Xamarin.Installer.Common/Properties/Resources.resx.lcl @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Localize/loc/fr/tools/msbuild/Properties/Resources.resx.lcl b/Localize/loc/fr/tools/msbuild/Properties/Resources.resx.lcl new file mode 100644 index 00000000000..cee0e747579 --- /dev/null +++ b/Localize/loc/fr/tools/msbuild/Properties/Resources.resx.lcl @@ -0,0 +1,191 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Send Feedback->Report a Problem' menu item in Visual Studio]D;]A; or 'Help->Report a Problem' in Visual Studio for Mac. If possible attach a full diagnostic build]D;]A; log to the feedback report as this will help us diagnose the issue.]]> + + Send Feedback->Report a Problem » dans Visual Studio]D;]A; ou « Help->Reporter un problème » dans Visual Studio pour Mac. Si possible, attachez une build de diagnostic complète]D;]A; connectez-vous au rapport de commentaires, car cela nous aidera à diagnostiquer le problème.]]> + + + + + + + + + + + + + + + + Send Feedback->Report a Problem' menu item in Visual Studio]D;]A;or 'Help->Report a Problem' in Visual Studio for Mac.]D;]A;Please set the 'EmbedAssembliesIntoApk' MSBuild property to 'true' to disable Fast Deployment in the Visual Studio project property pages, or edit the project file in a text editor.]]> + + Send Feedback->Report a Problem » dans Visual Studio]D;]A;ou « Help->Reporter un problème » dans Visual Studio pour Mac.]D;]A;Veuillez définir la propriété MSBuild « EmbedAssembliesIntoApk » sur « true » pour désactiver le déploiement rapide dans les pages de propriétés du projet Visual Studio, ou modifiez le fichier projet dans un éditeur de texte.]]> + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Localize/loc/it/Xamarin.AndroidTools/Properties/Resources.resx.lcl b/Localize/loc/it/Xamarin.AndroidTools/Properties/Resources.resx.lcl new file mode 100644 index 00000000000..895f2b27b8c --- /dev/null +++ b/Localize/loc/it/Xamarin.AndroidTools/Properties/Resources.resx.lcl @@ -0,0 +1,146 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Localize/loc/it/Xamarin.Installer.AndroidSDK/Properties/Resources.resx.lcl b/Localize/loc/it/Xamarin.Installer.AndroidSDK/Properties/Resources.resx.lcl new file mode 100644 index 00000000000..daf185bfef5 --- /dev/null +++ b/Localize/loc/it/Xamarin.Installer.AndroidSDK/Properties/Resources.resx.lcl @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Localize/loc/it/Xamarin.Installer.Build.Tasks/Properties/Resources.resx.lcl b/Localize/loc/it/Xamarin.Installer.Build.Tasks/Properties/Resources.resx.lcl new file mode 100644 index 00000000000..3401f80618d --- /dev/null +++ b/Localize/loc/it/Xamarin.Installer.Build.Tasks/Properties/Resources.resx.lcl @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Localize/loc/it/Xamarin.Installer.Common/Properties/Resources.resx.lcl b/Localize/loc/it/Xamarin.Installer.Common/Properties/Resources.resx.lcl new file mode 100644 index 00000000000..9247a68a6f9 --- /dev/null +++ b/Localize/loc/it/Xamarin.Installer.Common/Properties/Resources.resx.lcl @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Localize/loc/it/tools/msbuild/Properties/Resources.resx.lcl b/Localize/loc/it/tools/msbuild/Properties/Resources.resx.lcl new file mode 100644 index 00000000000..2056de6cd0e --- /dev/null +++ b/Localize/loc/it/tools/msbuild/Properties/Resources.resx.lcl @@ -0,0 +1,191 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Send Feedback->Report a Problem' menu item in Visual Studio]D;]A; or 'Help->Report a Problem' in Visual Studio for Mac. If possible attach a full diagnostic build]D;]A; log to the feedback report as this will help us diagnose the issue.]]> + + Invia Feedback->Segnala un problema' in Visual Studio]D;]A; o 'Guida->Segnala un problema' in Visual Studio per Mac. Se possibile, allegare un log di compilazione diagnostica completa]D;]A; il report di feedback, perché questo aiuterà a diagnosticare il problema.]]> + + + + + + + + + + + + + + + + Send Feedback->Report a Problem' menu item in Visual Studio]D;]A;or 'Help->Report a Problem' in Visual Studio for Mac.]D;]A;Please set the 'EmbedAssembliesIntoApk' MSBuild property to 'true' to disable Fast Deployment in the Visual Studio project property pages, or edit the project file in a text editor.]]> + + Invia feedback->Segnala un problema" in Visual Studio]D;]A;o 'Guida->Segnala un problema' in Visual Studio per Mac.]D;]A;Impostare la proprietà MSBuild 'EmbedAssembliesIntoApk' su 'true' per disabilitare la distribuzione rapida nelle pagine delle proprietà del progetto Visual Studio o modificare il file di progetto in un editor di testo.]]> + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Localize/loc/ja/Xamarin.AndroidTools/Properties/Resources.resx.lcl b/Localize/loc/ja/Xamarin.AndroidTools/Properties/Resources.resx.lcl new file mode 100644 index 00000000000..62e490fd48c --- /dev/null +++ b/Localize/loc/ja/Xamarin.AndroidTools/Properties/Resources.resx.lcl @@ -0,0 +1,146 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Localize/loc/ja/Xamarin.Installer.AndroidSDK/Properties/Resources.resx.lcl b/Localize/loc/ja/Xamarin.Installer.AndroidSDK/Properties/Resources.resx.lcl new file mode 100644 index 00000000000..5384cf83641 --- /dev/null +++ b/Localize/loc/ja/Xamarin.Installer.AndroidSDK/Properties/Resources.resx.lcl @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Localize/loc/ja/Xamarin.Installer.Build.Tasks/Properties/Resources.resx.lcl b/Localize/loc/ja/Xamarin.Installer.Build.Tasks/Properties/Resources.resx.lcl new file mode 100644 index 00000000000..9505e9c2b52 --- /dev/null +++ b/Localize/loc/ja/Xamarin.Installer.Build.Tasks/Properties/Resources.resx.lcl @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Localize/loc/ja/Xamarin.Installer.Common/Properties/Resources.resx.lcl b/Localize/loc/ja/Xamarin.Installer.Common/Properties/Resources.resx.lcl new file mode 100644 index 00000000000..3f1b05254c5 --- /dev/null +++ b/Localize/loc/ja/Xamarin.Installer.Common/Properties/Resources.resx.lcl @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Localize/loc/ja/tools/msbuild/Properties/Resources.resx.lcl b/Localize/loc/ja/tools/msbuild/Properties/Resources.resx.lcl new file mode 100644 index 00000000000..4c7f4c3adca --- /dev/null +++ b/Localize/loc/ja/tools/msbuild/Properties/Resources.resx.lcl @@ -0,0 +1,191 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Send Feedback->Report a Problem' menu item in Visual Studio]D;]A; or 'Help->Report a Problem' in Visual Studio for Mac. If possible attach a full diagnostic build]D;]A; log to the feedback report as this will help us diagnose the issue.]]> + + [フィードバックを送信]5D; -> [問題を報告]5D; メニュー項目を使用するか]D;]A; または Visual Studio for Mac で [ヘルプ]5D; -> [問題を報告]5D; を使用して実際のエラー メッセージを付けて問題を申請してください。可能ならば、完全な診断ビルド ]D;]A; ログをフィードバック レポートに添付してください。これにより Microsoft が問題を診断するのに役立ちます。]]> + + + + + + + + + + + + + + + + Send Feedback->Report a Problem' menu item in Visual Studio]D;]A;or 'Help->Report a Problem' in Visual Studio for Mac.]D;]A;Please set the 'EmbedAssembliesIntoApk' MSBuild property to 'true' to disable Fast Deployment in the Visual Studio project property pages, or edit the project file in a text editor.]]> + + [フィードバックを送信]5D; -> [問題を報告]5D; メニュー項目を使用するか]D;]A;または Visual Studio for Mac で [ヘルプ]5D; -> [問題を報告]5D; を使用して実際のエラー メッセージを付けて問題を申請してください。]D;]A;'EmbedAssembliesIntoApk' MSBuild プロパティを 'true' に設定して、Visual Studio プロジェクト プロパティ ページで高速展開を無効にするか、テキスト エディターでプロジェクト ファイルを編集してください。]]> + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Localize/loc/ko/Xamarin.AndroidTools/Properties/Resources.resx.lcl b/Localize/loc/ko/Xamarin.AndroidTools/Properties/Resources.resx.lcl new file mode 100644 index 00000000000..94b9203e2d0 --- /dev/null +++ b/Localize/loc/ko/Xamarin.AndroidTools/Properties/Resources.resx.lcl @@ -0,0 +1,146 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Localize/loc/ko/Xamarin.Installer.AndroidSDK/Properties/Resources.resx.lcl b/Localize/loc/ko/Xamarin.Installer.AndroidSDK/Properties/Resources.resx.lcl new file mode 100644 index 00000000000..195652d4a3b --- /dev/null +++ b/Localize/loc/ko/Xamarin.Installer.AndroidSDK/Properties/Resources.resx.lcl @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Localize/loc/ko/Xamarin.Installer.Build.Tasks/Properties/Resources.resx.lcl b/Localize/loc/ko/Xamarin.Installer.Build.Tasks/Properties/Resources.resx.lcl new file mode 100644 index 00000000000..65617509d92 --- /dev/null +++ b/Localize/loc/ko/Xamarin.Installer.Build.Tasks/Properties/Resources.resx.lcl @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Localize/loc/ko/Xamarin.Installer.Common/Properties/Resources.resx.lcl b/Localize/loc/ko/Xamarin.Installer.Common/Properties/Resources.resx.lcl new file mode 100644 index 00000000000..44b35e33dd2 --- /dev/null +++ b/Localize/loc/ko/Xamarin.Installer.Common/Properties/Resources.resx.lcl @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Localize/loc/ko/tools/msbuild/Properties/Resources.resx.lcl b/Localize/loc/ko/tools/msbuild/Properties/Resources.resx.lcl new file mode 100644 index 00000000000..e3477528367 --- /dev/null +++ b/Localize/loc/ko/tools/msbuild/Properties/Resources.resx.lcl @@ -0,0 +1,191 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Send Feedback->Report a Problem' menu item in Visual Studio]D;]A; or 'Help->Report a Problem' in Visual Studio for Mac. If possible attach a full diagnostic build]D;]A; log to the feedback report as this will help us diagnose the issue.]]> + + 피드백 보내기->문제 보고' 메뉴 항목 또는 Mac용 Visual Studio의 '도움말->문제 보고'를 사용하여]D;]A; 정확한 오류 메시지와 함께 문제를 제출하세요. 가능한 경우 문제를 진단하는 데 도움이 되므로]D;]A; 전체 진단 빌드 로그를 피드백 보고서에 첨부하세요.]]> + + + + + + + + + + + + + + + + Send Feedback->Report a Problem' menu item in Visual Studio]D;]A;or 'Help->Report a Problem' in Visual Studio for Mac.]D;]A;Please set the 'EmbedAssembliesIntoApk' MSBuild property to 'true' to disable Fast Deployment in the Visual Studio project property pages, or edit the project file in a text editor.]]> + + 피드백 보내기->문제 보고' 메뉴 항목 또는 Mac용 Visual Studio의 '도움말->문제 보고'를 사용하여]D;]A;정확한 오류 메시지와 함께 문제를 제출하세요.]D;]A;'EmbedAssembliesIntoApk' MSBuild 속성을 'true'로 설정하여 Visual Studio 프로젝트 속성 페이지에서 빠른 배포를 사용하지 않도록 설정하거나 텍스트 편집기에서 프로젝트 파일을 편집하세요.]]> + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Localize/loc/pl/Xamarin.AndroidTools/Properties/Resources.resx.lcl b/Localize/loc/pl/Xamarin.AndroidTools/Properties/Resources.resx.lcl new file mode 100644 index 00000000000..715960688e1 --- /dev/null +++ b/Localize/loc/pl/Xamarin.AndroidTools/Properties/Resources.resx.lcl @@ -0,0 +1,146 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Localize/loc/pl/Xamarin.Installer.AndroidSDK/Properties/Resources.resx.lcl b/Localize/loc/pl/Xamarin.Installer.AndroidSDK/Properties/Resources.resx.lcl new file mode 100644 index 00000000000..413de8fb44c --- /dev/null +++ b/Localize/loc/pl/Xamarin.Installer.AndroidSDK/Properties/Resources.resx.lcl @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Localize/loc/pl/Xamarin.Installer.Build.Tasks/Properties/Resources.resx.lcl b/Localize/loc/pl/Xamarin.Installer.Build.Tasks/Properties/Resources.resx.lcl new file mode 100644 index 00000000000..f1b5b0f2436 --- /dev/null +++ b/Localize/loc/pl/Xamarin.Installer.Build.Tasks/Properties/Resources.resx.lcl @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Localize/loc/pl/Xamarin.Installer.Common/Properties/Resources.resx.lcl b/Localize/loc/pl/Xamarin.Installer.Common/Properties/Resources.resx.lcl new file mode 100644 index 00000000000..265ebda77d2 --- /dev/null +++ b/Localize/loc/pl/Xamarin.Installer.Common/Properties/Resources.resx.lcl @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Localize/loc/pl/tools/msbuild/Properties/Resources.resx.lcl b/Localize/loc/pl/tools/msbuild/Properties/Resources.resx.lcl new file mode 100644 index 00000000000..d6ef80a59e5 --- /dev/null +++ b/Localize/loc/pl/tools/msbuild/Properties/Resources.resx.lcl @@ -0,0 +1,191 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Send Feedback->Report a Problem' menu item in Visual Studio]D;]A; or 'Help->Report a Problem' in Visual Studio for Mac. If possible attach a full diagnostic build]D;]A; log to the feedback report as this will help us diagnose the issue.]]> + + Prześlij opinię->Zgłoś problem” w programie Visual Studio]D;]A; lub „Pomoc->Zgłoś problem” w programie Visual Studio dla komputerów Mac. Jeśli to możliwe, dołącz dziennik kompilacji]D;]A; pełnej diagnostyki do raportu opinii, ponieważ pomoże nam to zdiagnozować problem.]]> + + + + + + + + + + + + + + + + Send Feedback->Report a Problem' menu item in Visual Studio]D;]A;or 'Help->Report a Problem' in Visual Studio for Mac.]D;]A;Please set the 'EmbedAssembliesIntoApk' MSBuild property to 'true' to disable Fast Deployment in the Visual Studio project property pages, or edit the project file in a text editor.]]> + + Prześlij opinię->Zgłoś problem” w programie Visual Studio]D;]A;lub „Pomoc->Zgłoś problem” w programie Visual Studio dla komputerów Mac.]D;]A;Ustaw właściwość MSBuild „EmbedAssembliesIntoApk” na wartość „true”, aby wyłączyć szybkie wdrażanie na stronach właściwości projektu programu Visual Studio, lub edytuj plik projektu w edytorze tekstów.]]> + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Localize/loc/pt-BR/Xamarin.AndroidTools/Properties/Resources.resx.lcl b/Localize/loc/pt-BR/Xamarin.AndroidTools/Properties/Resources.resx.lcl new file mode 100644 index 00000000000..ed3221e0458 --- /dev/null +++ b/Localize/loc/pt-BR/Xamarin.AndroidTools/Properties/Resources.resx.lcl @@ -0,0 +1,146 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Localize/loc/pt-BR/Xamarin.Installer.AndroidSDK/Properties/Resources.resx.lcl b/Localize/loc/pt-BR/Xamarin.Installer.AndroidSDK/Properties/Resources.resx.lcl new file mode 100644 index 00000000000..d8f908257d0 --- /dev/null +++ b/Localize/loc/pt-BR/Xamarin.Installer.AndroidSDK/Properties/Resources.resx.lcl @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Localize/loc/pt-BR/Xamarin.Installer.Build.Tasks/Properties/Resources.resx.lcl b/Localize/loc/pt-BR/Xamarin.Installer.Build.Tasks/Properties/Resources.resx.lcl new file mode 100644 index 00000000000..541815d4dcb --- /dev/null +++ b/Localize/loc/pt-BR/Xamarin.Installer.Build.Tasks/Properties/Resources.resx.lcl @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Localize/loc/pt-BR/Xamarin.Installer.Common/Properties/Resources.resx.lcl b/Localize/loc/pt-BR/Xamarin.Installer.Common/Properties/Resources.resx.lcl new file mode 100644 index 00000000000..258228d651a --- /dev/null +++ b/Localize/loc/pt-BR/Xamarin.Installer.Common/Properties/Resources.resx.lcl @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Localize/loc/pt-BR/tools/msbuild/Properties/Resources.resx.lcl b/Localize/loc/pt-BR/tools/msbuild/Properties/Resources.resx.lcl new file mode 100644 index 00000000000..df071c7430d --- /dev/null +++ b/Localize/loc/pt-BR/tools/msbuild/Properties/Resources.resx.lcl @@ -0,0 +1,191 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Send Feedback->Report a Problem' menu item in Visual Studio]D;]A; or 'Help->Report a Problem' in Visual Studio for Mac. If possible attach a full diagnostic build]D;]A; log to the feedback report as this will help us diagnose the issue.]]> + + Enviar comentários->Relatar um Problema" no Visual Studio]D;]A; ou "Ajuda->Relatar um problema" no Visual Studio para Mac. Se possível, anexe um log de compilação de diagnóstico completo]D;]A; ao relatório de comentários, pois isso nos ajudará a diagnosticar o problema.]]> + + + + + + + + + + + + + + + + Send Feedback->Report a Problem' menu item in Visual Studio]D;]A;or 'Help->Report a Problem' in Visual Studio for Mac.]D;]A;Please set the 'EmbedAssembliesIntoApk' MSBuild property to 'true' to disable Fast Deployment in the Visual Studio project property pages, or edit the project file in a text editor.]]> + + Enviar comentários->Relatar um Problema" no Visual Studio]D;]A;ou "Ajuda->Relatar um problema" no Visual Studio para Mac.]D;]A;Defina a propriedade MSBuild "EmbedAssembliesIntoApk" como "true" para desabilitar a Implantação Rápida nas páginas de propriedades do projeto do Visual Studio ou edite o arquivo do projeto em um editor de texto.]]> + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Localize/loc/ru/Xamarin.AndroidTools/Properties/Resources.resx.lcl b/Localize/loc/ru/Xamarin.AndroidTools/Properties/Resources.resx.lcl new file mode 100644 index 00000000000..4dcba6674fc --- /dev/null +++ b/Localize/loc/ru/Xamarin.AndroidTools/Properties/Resources.resx.lcl @@ -0,0 +1,146 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Localize/loc/ru/Xamarin.Installer.AndroidSDK/Properties/Resources.resx.lcl b/Localize/loc/ru/Xamarin.Installer.AndroidSDK/Properties/Resources.resx.lcl new file mode 100644 index 00000000000..a5dbc2d8fa3 --- /dev/null +++ b/Localize/loc/ru/Xamarin.Installer.AndroidSDK/Properties/Resources.resx.lcl @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Localize/loc/ru/Xamarin.Installer.Build.Tasks/Properties/Resources.resx.lcl b/Localize/loc/ru/Xamarin.Installer.Build.Tasks/Properties/Resources.resx.lcl new file mode 100644 index 00000000000..e65ab669b3c --- /dev/null +++ b/Localize/loc/ru/Xamarin.Installer.Build.Tasks/Properties/Resources.resx.lcl @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Localize/loc/ru/Xamarin.Installer.Common/Properties/Resources.resx.lcl b/Localize/loc/ru/Xamarin.Installer.Common/Properties/Resources.resx.lcl new file mode 100644 index 00000000000..9da4d924910 --- /dev/null +++ b/Localize/loc/ru/Xamarin.Installer.Common/Properties/Resources.resx.lcl @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Localize/loc/ru/tools/msbuild/Properties/Resources.resx.lcl b/Localize/loc/ru/tools/msbuild/Properties/Resources.resx.lcl new file mode 100644 index 00000000000..5765d778145 --- /dev/null +++ b/Localize/loc/ru/tools/msbuild/Properties/Resources.resx.lcl @@ -0,0 +1,191 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Send Feedback->Report a Problem' menu item in Visual Studio]D;]A; or 'Help->Report a Problem' in Visual Studio for Mac. If possible attach a full diagnostic build]D;]A; log to the feedback report as this will help us diagnose the issue.]]> + + Отправить отзыв->Сообщить о проблеме" в Visual Studio. ]D;]A; или "Справка->Сообщить о проблеме" в Visual Studio для Mac. Если возможно, прикрепите к отзыву ]D;]A; полный журнал диагностической сборки, так как это поможет нам диагностировать проблему.]]> + + + + + + + + + + + + + + + + Send Feedback->Report a Problem' menu item in Visual Studio]D;]A;or 'Help->Report a Problem' in Visual Studio for Mac.]D;]A;Please set the 'EmbedAssembliesIntoApk' MSBuild property to 'true' to disable Fast Deployment in the Visual Studio project property pages, or edit the project file in a text editor.]]> + + Отправить отзыв->Сообщить о проблеме" в Visual Studio ]D;]A;или "Справка->Сообщить о проблеме" в Visual Studio для Mac. ]D;]A;Установите для свойства MSBuild "EmbedAssembliesIntoApk" значение "true", чтобы отключить быстрое развертывание на страницах свойств проекта Visual Studio, или отредактируйте файл проекта в текстовом редакторе.]]> + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Localize/loc/tr/Xamarin.AndroidTools/Properties/Resources.resx.lcl b/Localize/loc/tr/Xamarin.AndroidTools/Properties/Resources.resx.lcl new file mode 100644 index 00000000000..480c29ce322 --- /dev/null +++ b/Localize/loc/tr/Xamarin.AndroidTools/Properties/Resources.resx.lcl @@ -0,0 +1,146 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Localize/loc/tr/Xamarin.Installer.AndroidSDK/Properties/Resources.resx.lcl b/Localize/loc/tr/Xamarin.Installer.AndroidSDK/Properties/Resources.resx.lcl new file mode 100644 index 00000000000..6b35cd450d7 --- /dev/null +++ b/Localize/loc/tr/Xamarin.Installer.AndroidSDK/Properties/Resources.resx.lcl @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Localize/loc/tr/Xamarin.Installer.Build.Tasks/Properties/Resources.resx.lcl b/Localize/loc/tr/Xamarin.Installer.Build.Tasks/Properties/Resources.resx.lcl new file mode 100644 index 00000000000..325bb8d1aa5 --- /dev/null +++ b/Localize/loc/tr/Xamarin.Installer.Build.Tasks/Properties/Resources.resx.lcl @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Localize/loc/tr/Xamarin.Installer.Common/Properties/Resources.resx.lcl b/Localize/loc/tr/Xamarin.Installer.Common/Properties/Resources.resx.lcl new file mode 100644 index 00000000000..3bc61a64c30 --- /dev/null +++ b/Localize/loc/tr/Xamarin.Installer.Common/Properties/Resources.resx.lcl @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Localize/loc/tr/tools/msbuild/Properties/Resources.resx.lcl b/Localize/loc/tr/tools/msbuild/Properties/Resources.resx.lcl new file mode 100644 index 00000000000..40280f0e578 --- /dev/null +++ b/Localize/loc/tr/tools/msbuild/Properties/Resources.resx.lcl @@ -0,0 +1,191 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Send Feedback->Report a Problem' menu item in Visual Studio]D;]A; or 'Help->Report a Problem' in Visual Studio for Mac. If possible attach a full diagnostic build]D;]A; log to the feedback report as this will help us diagnose the issue.]]> + + Geri Bildirim->Sorun Bildir” veya Mac için Visual Studio’da “Yardım->Sorun Bildir” menü öğesini]D;]A; kullanarak tam hata iletisini içeren bir sorun bildirin. Mümkünse sorunun tanılanmasına yardımcı olması için]D;]A; rapora tam tanılama derleme günlüğünü ekleyin.]]> + + + + + + + + + + + + + + + + Send Feedback->Report a Problem' menu item in Visual Studio]D;]A;or 'Help->Report a Problem' in Visual Studio for Mac.]D;]A;Please set the 'EmbedAssembliesIntoApk' MSBuild property to 'true' to disable Fast Deployment in the Visual Studio project property pages, or edit the project file in a text editor.]]> + + Geri Bildirim Gönder->Sorun Bildir” veya Mac için Visual Studio’da “Yardım->Sorun Bildir” menü öğesini]D;]A;kullanarak tam hata iletisini içeren bir sorun bildirin.]D;]A;Hızlı Dağıtımı devre dışı bırakmak için lütfen Visual Studio proje özelliği sayfalarında veya proje dosyasını bir metin düzenleyicisinde düzenleyerek “EmbedAssembliesIntoApk” MSBuild özelliğini “true” olarak ayarlayın.]]> + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Localize/loc/zh-Hans/Xamarin.AndroidTools/Properties/Resources.resx.lcl b/Localize/loc/zh-Hans/Xamarin.AndroidTools/Properties/Resources.resx.lcl new file mode 100644 index 00000000000..fd679ff8fc7 --- /dev/null +++ b/Localize/loc/zh-Hans/Xamarin.AndroidTools/Properties/Resources.resx.lcl @@ -0,0 +1,146 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Localize/loc/zh-Hans/Xamarin.Installer.AndroidSDK/Properties/Resources.resx.lcl b/Localize/loc/zh-Hans/Xamarin.Installer.AndroidSDK/Properties/Resources.resx.lcl new file mode 100644 index 00000000000..f21aa856123 --- /dev/null +++ b/Localize/loc/zh-Hans/Xamarin.Installer.AndroidSDK/Properties/Resources.resx.lcl @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Localize/loc/zh-Hans/Xamarin.Installer.Build.Tasks/Properties/Resources.resx.lcl b/Localize/loc/zh-Hans/Xamarin.Installer.Build.Tasks/Properties/Resources.resx.lcl new file mode 100644 index 00000000000..4abdbad66b8 --- /dev/null +++ b/Localize/loc/zh-Hans/Xamarin.Installer.Build.Tasks/Properties/Resources.resx.lcl @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Localize/loc/zh-Hans/Xamarin.Installer.Common/Properties/Resources.resx.lcl b/Localize/loc/zh-Hans/Xamarin.Installer.Common/Properties/Resources.resx.lcl new file mode 100644 index 00000000000..fc8a681aaed --- /dev/null +++ b/Localize/loc/zh-Hans/Xamarin.Installer.Common/Properties/Resources.resx.lcl @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Localize/loc/zh-Hans/tools/msbuild/Properties/Resources.resx.lcl b/Localize/loc/zh-Hans/tools/msbuild/Properties/Resources.resx.lcl new file mode 100644 index 00000000000..722e13703db --- /dev/null +++ b/Localize/loc/zh-Hans/tools/msbuild/Properties/Resources.resx.lcl @@ -0,0 +1,191 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Send Feedback->Report a Problem' menu item in Visual Studio]D;]A; or 'Help->Report a Problem' in Visual Studio for Mac. If possible attach a full diagnostic build]D;]A; log to the feedback report as this will help us diagnose the issue.]]> + + “发送反馈”->“报告问题”菜单项]D;]A; 或 Visual Studio for Mac 中的“帮助”->“报告问题”,提交包含确切错误消息的问题反馈。如果可能,请在反馈报告中附上完整的诊断生成]D;]A; 日志,因为这将有助于我们诊断问题。]]> + + + + + + + + + + + + + + + + Send Feedback->Report a Problem' menu item in Visual Studio]D;]A;or 'Help->Report a Problem' in Visual Studio for Mac.]D;]A;Please set the 'EmbedAssembliesIntoApk' MSBuild property to 'true' to disable Fast Deployment in the Visual Studio project property pages, or edit the project file in a text editor.]]> + + 发送反馈 -> 报告问题”菜单项]D;]A;或 Visual Studio for Mac 中的“帮助”->“报告问题”,提交包含确切错误消息的问题反馈。]D;]A;请将“EmbedAssembliesIntoApk”MSBuild 属性设置为“true”,以在 Visual Studio 项目属性页中禁用快速部署,或在文本编辑器中编辑项目文件。]]> + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Localize/loc/zh-Hant/Xamarin.AndroidTools/Properties/Resources.resx.lcl b/Localize/loc/zh-Hant/Xamarin.AndroidTools/Properties/Resources.resx.lcl new file mode 100644 index 00000000000..358349cbb7a --- /dev/null +++ b/Localize/loc/zh-Hant/Xamarin.AndroidTools/Properties/Resources.resx.lcl @@ -0,0 +1,146 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Localize/loc/zh-Hant/Xamarin.Installer.AndroidSDK/Properties/Resources.resx.lcl b/Localize/loc/zh-Hant/Xamarin.Installer.AndroidSDK/Properties/Resources.resx.lcl new file mode 100644 index 00000000000..df7d8355bf3 --- /dev/null +++ b/Localize/loc/zh-Hant/Xamarin.Installer.AndroidSDK/Properties/Resources.resx.lcl @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Localize/loc/zh-Hant/Xamarin.Installer.Build.Tasks/Properties/Resources.resx.lcl b/Localize/loc/zh-Hant/Xamarin.Installer.Build.Tasks/Properties/Resources.resx.lcl new file mode 100644 index 00000000000..d5b34139c4b --- /dev/null +++ b/Localize/loc/zh-Hant/Xamarin.Installer.Build.Tasks/Properties/Resources.resx.lcl @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Localize/loc/zh-Hant/Xamarin.Installer.Common/Properties/Resources.resx.lcl b/Localize/loc/zh-Hant/Xamarin.Installer.Common/Properties/Resources.resx.lcl new file mode 100644 index 00000000000..4013ec8cedf --- /dev/null +++ b/Localize/loc/zh-Hant/Xamarin.Installer.Common/Properties/Resources.resx.lcl @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Localize/loc/zh-Hant/tools/msbuild/Properties/Resources.resx.lcl b/Localize/loc/zh-Hant/tools/msbuild/Properties/Resources.resx.lcl new file mode 100644 index 00000000000..eed29c21e57 --- /dev/null +++ b/Localize/loc/zh-Hant/tools/msbuild/Properties/Resources.resx.lcl @@ -0,0 +1,191 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Send Feedback->Report a Problem' menu item in Visual Studio]D;]A; or 'Help->Report a Problem' in Visual Studio for Mac. If possible attach a full diagnostic build]D;]A; log to the feedback report as this will help us diagnose the issue.]]> + + 傳送意見反應->回報問題' 功能表項目,或在 Visual Studio for Mac 中的]D;]A; '說明->回報問題' 中提交包含完整錯誤訊息的問題報告。可能的話,請附加完整的診斷組建]D;]A; 記錄到意見反應報告,以協助我們診斷問題。]]> + + + + + + + + + + + + + + + + Send Feedback->Report a Problem' menu item in Visual Studio]D;]A;or 'Help->Report a Problem' in Visual Studio for Mac.]D;]A;Please set the 'EmbedAssembliesIntoApk' MSBuild property to 'true' to disable Fast Deployment in the Visual Studio project property pages, or edit the project file in a text editor.]]> + + 傳送意見反應->回報問題' 功能表項目,或在 Visual Studio for Mac 中的]D;]A;'說明->回報問題' 中提交包含完整錯誤訊息的問題報告。]D;]A;請將 'EmbedAssembliesIntoApk' MSBuild 屬性設定為 'true',以停用 Visual Studio 專案屬性頁面中的快速部署,或在文字編輯器中編輯專案檔案。]]> + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Makefile b/Makefile index 8ca80967153..06c3078f549 100644 --- a/Makefile +++ b/Makefile @@ -132,10 +132,6 @@ prepare: prepare-help: $(call SYSTEM_DOTNET_BINLOG,prepare-help,run) --project "$(PREPARE_PROJECT)" --framework $(PREPARE_NET_FX) -- -h -prepare-external-git-dependencies: - $(call SYSTEM_DOTNET_BINLOG,prepare-external-git-dependencies,run) --project "$(PREPARE_PROJECT)" --framework $(PREPARE_NET_FX) \ - -- -s:PrepareExternalGitDependencies $(_PREPARE_ARGS) - APK_SIZES_REFERENCE_DIR=tests/apk-sizes-reference update-apk-sizes-reference: diff --git a/THIRD-PARTY-NOTICES.TXT b/THIRD-PARTY-NOTICES.TXT index d2635a4ac93..b8a4ff3a1ad 100644 --- a/THIRD-PARTY-NOTICES.TXT +++ b/THIRD-PARTY-NOTICES.TXT @@ -1766,6 +1766,31 @@ The MIT License (MIT) END OF JamesNK/Newtonsoft.Json NOTICES AND INFORMATION +%% Kajabity/Kajabity.Tools.Java NOTICES AND INFORMATION BEGIN HERE +================================================================== +Copyright 2009-15 Williams Technologies Limited. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Kajbity is a trademark of Williams Technologies Limited. + +http://www.kajabity.com + + +================================================================== +END OF Kajabity/Kajabity.Tools.Java NOTICES AND INFORMATION + + %% KonradKuczynski/ELFSharp NOTICES AND INFORMATION BEGIN HERE ============================================================== Copyright (c) 2011 Konrad Kruczyński and other contributors diff --git a/Xamarin.Android.sln b/Xamarin.Android.sln index 82205d0743a..543fcad8123 100644 --- a/Xamarin.Android.sln +++ b/Xamarin.Android.sln @@ -145,268 +145,882 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "proguard-android", "src\pro EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Android.Runtime.NativeAOT", "src\Microsoft.Android.Runtime.NativeAOT\Microsoft.Android.Runtime.NativeAOT.csproj", "{E8831F32-11D7-D42C-E43C-711998BC357A}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Xamarin.Installer.Common", "src\Xamarin.Installer.Common\Xamarin.Installer.Common.csproj", "{0DCDA993-A945-44D0-B9C0-FF9FAB127C7E}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Mono.AndroidTools", "src\Mono.AndroidTools\Mono.AndroidTools.csproj", "{3AAA188D-5F22-4596-8C4F-76C7AA899EAA}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Xamarin.AndroidTools", "src\Xamarin.AndroidTools\Xamarin.AndroidTools.csproj", "{65633E01-3521-4219-BBE1-9A4DB4009A91}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Xamarin.Installer.AndroidSDK", "src\Xamarin.Installer.AndroidSDK\Xamarin.Installer.AndroidSDK.csproj", "{D8B7DCCF-E27A-4BDF-B0A0-40D1F2F758EB}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Xamarin.Installer.Build.Tasks", "src\Xamarin.Installer.Build.Tasks\Xamarin.Installer.Build.Tasks.csproj", "{79BBD075-BC73-4D35-8DE6-BE70C9AFC708}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Xamarin.Android.Build.Debugging.Tasks", "src\Xamarin.Android.Build.Debugging.Tasks\Xamarin.Android.Build.Debugging.Tasks.csproj", "{624C58EB-57CF-4754-95EF-E9893E584296}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "fastdevtools", "tools\fastdev\fastdevtools.csproj", "{4723786E-4BB5-4939-BBFD-3C2D0EAE236B}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|AnyCPU = Debug|AnyCPU - Release|AnyCPU = Release|AnyCPU + Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 + Release|x86 = Release|x86 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {1D4FC8F1-0DA4-4F38-BE68-11AEBA9A0EA4}.Debug|AnyCPU.ActiveCfg = Debug|Any CPU - {1D4FC8F1-0DA4-4F38-BE68-11AEBA9A0EA4}.Debug|AnyCPU.Build.0 = Debug|Any CPU - {1D4FC8F1-0DA4-4F38-BE68-11AEBA9A0EA4}.Release|AnyCPU.ActiveCfg = Release|Any CPU - {1D4FC8F1-0DA4-4F38-BE68-11AEBA9A0EA4}.Release|AnyCPU.Build.0 = Release|Any CPU - {AFB8F6D1-6EA9-42C3-950B-98F34C669AD2}.Debug|AnyCPU.ActiveCfg = Debug|Any CPU - {AFB8F6D1-6EA9-42C3-950B-98F34C669AD2}.Debug|AnyCPU.Build.0 = Debug|Any CPU - {AFB8F6D1-6EA9-42C3-950B-98F34C669AD2}.Release|AnyCPU.ActiveCfg = Release|Any CPU - {AFB8F6D1-6EA9-42C3-950B-98F34C669AD2}.Release|AnyCPU.Build.0 = Release|Any CPU - {3FC3E78B-F7D4-42EA-BBE8-4535DF42BFF8}.Debug|AnyCPU.ActiveCfg = Debug|Any CPU - {3FC3E78B-F7D4-42EA-BBE8-4535DF42BFF8}.Debug|AnyCPU.Build.0 = Debug|Any CPU - {3FC3E78B-F7D4-42EA-BBE8-4535DF42BFF8}.Release|AnyCPU.ActiveCfg = Release|Any CPU - {3FC3E78B-F7D4-42EA-BBE8-4535DF42BFF8}.Release|AnyCPU.Build.0 = Release|Any CPU - {94BD81F7-B06F-4295-9636-F8A3B6BDC762}.Debug|AnyCPU.ActiveCfg = Debug|Any CPU - {94BD81F7-B06F-4295-9636-F8A3B6BDC762}.Debug|AnyCPU.Build.0 = Debug|Any CPU - {94BD81F7-B06F-4295-9636-F8A3B6BDC762}.Release|AnyCPU.ActiveCfg = Release|Any CPU - {94BD81F7-B06F-4295-9636-F8A3B6BDC762}.Release|AnyCPU.Build.0 = Release|Any CPU - {D14A1B5C-2060-4930-92BE-F7190256C735}.Debug|AnyCPU.ActiveCfg = Debug|Any CPU - {D14A1B5C-2060-4930-92BE-F7190256C735}.Debug|AnyCPU.Build.0 = Debug|Any CPU - {D14A1B5C-2060-4930-92BE-F7190256C735}.Release|AnyCPU.ActiveCfg = Release|Any CPU - {D14A1B5C-2060-4930-92BE-F7190256C735}.Release|AnyCPU.Build.0 = Release|Any CPU - {D27AD8F7-7710-40BE-B03B-55EFBEC13C44}.Debug|AnyCPU.ActiveCfg = Debug|Any CPU - {D27AD8F7-7710-40BE-B03B-55EFBEC13C44}.Debug|AnyCPU.Build.0 = Debug|Any CPU - {D27AD8F7-7710-40BE-B03B-55EFBEC13C44}.Release|AnyCPU.ActiveCfg = Release|Any CPU - {D27AD8F7-7710-40BE-B03B-55EFBEC13C44}.Release|AnyCPU.Build.0 = Release|Any CPU - {3F1F2F50-AF1A-4A5A-BEDB-193372F068D7}.Debug|AnyCPU.ActiveCfg = Debug|Any CPU - {3F1F2F50-AF1A-4A5A-BEDB-193372F068D7}.Debug|AnyCPU.Build.0 = Debug|Any CPU - {3F1F2F50-AF1A-4A5A-BEDB-193372F068D7}.Release|AnyCPU.ActiveCfg = Release|Any CPU - {3F1F2F50-AF1A-4A5A-BEDB-193372F068D7}.Release|AnyCPU.Build.0 = Release|Any CPU - {A1B2C3D4-E5F6-4A5B-8C9D-0E1F2A3B4C5D}.Debug|AnyCPU.ActiveCfg = Debug|Any CPU - {A1B2C3D4-E5F6-4A5B-8C9D-0E1F2A3B4C5D}.Debug|AnyCPU.Build.0 = Debug|Any CPU - {A1B2C3D4-E5F6-4A5B-8C9D-0E1F2A3B4C5D}.Release|AnyCPU.ActiveCfg = Release|Any CPU - {A1B2C3D4-E5F6-4A5B-8C9D-0E1F2A3B4C5D}.Release|AnyCPU.Build.0 = Release|Any CPU - {71FE54FA-0BF5-48EF-ACAA-17557B28C9F4}.Debug|AnyCPU.ActiveCfg = Debug|Any CPU - {71FE54FA-0BF5-48EF-ACAA-17557B28C9F4}.Debug|AnyCPU.Build.0 = Debug|Any CPU - {71FE54FA-0BF5-48EF-ACAA-17557B28C9F4}.Release|AnyCPU.ActiveCfg = Release|Any CPU - {71FE54FA-0BF5-48EF-ACAA-17557B28C9F4}.Release|AnyCPU.Build.0 = Release|Any CPU - {B17475BC-45A2-47A3-B8FC-62F3A0959EE0}.Debug|AnyCPU.ActiveCfg = Debug|Any CPU - {B17475BC-45A2-47A3-B8FC-62F3A0959EE0}.Debug|AnyCPU.Build.0 = Debug|Any CPU - {B17475BC-45A2-47A3-B8FC-62F3A0959EE0}.Release|AnyCPU.ActiveCfg = Release|Any CPU - {B17475BC-45A2-47A3-B8FC-62F3A0959EE0}.Release|AnyCPU.Build.0 = Release|Any CPU - {52C7D9B6-E8C8-47D0-9471-652D278D7D77}.Debug|AnyCPU.ActiveCfg = Debug|Any CPU - {52C7D9B6-E8C8-47D0-9471-652D278D7D77}.Debug|AnyCPU.Build.0 = Debug|Any CPU - {52C7D9B6-E8C8-47D0-9471-652D278D7D77}.Release|AnyCPU.ActiveCfg = Release|Any CPU - {52C7D9B6-E8C8-47D0-9471-652D278D7D77}.Release|AnyCPU.Build.0 = Release|Any CPU - {D18FCF91-8876-48A0-A693-2DC1E7D3D80A}.Debug|AnyCPU.ActiveCfg = Debug|Any CPU - {D18FCF91-8876-48A0-A693-2DC1E7D3D80A}.Debug|AnyCPU.Build.0 = Debug|Any CPU - {D18FCF91-8876-48A0-A693-2DC1E7D3D80A}.Release|AnyCPU.ActiveCfg = Release|Any CPU - {D18FCF91-8876-48A0-A693-2DC1E7D3D80A}.Release|AnyCPU.Build.0 = Release|Any CPU - {1268EADF-8344-431C-81F6-FCB7CBC99F49}.Debug|AnyCPU.ActiveCfg = Debug|Any CPU - {1268EADF-8344-431C-81F6-FCB7CBC99F49}.Debug|AnyCPU.Build.0 = Debug|Any CPU - {1268EADF-8344-431C-81F6-FCB7CBC99F49}.Release|AnyCPU.ActiveCfg = Release|Any CPU - {1268EADF-8344-431C-81F6-FCB7CBC99F49}.Release|AnyCPU.Build.0 = Release|Any CPU - {07BC4495-1267-4B78-9EA6-B76FEEA2A64A}.Debug|AnyCPU.ActiveCfg = Debug|Any CPU - {07BC4495-1267-4B78-9EA6-B76FEEA2A64A}.Debug|AnyCPU.Build.0 = Debug|Any CPU - {07BC4495-1267-4B78-9EA6-B76FEEA2A64A}.Release|AnyCPU.ActiveCfg = Release|Any CPU - {07BC4495-1267-4B78-9EA6-B76FEEA2A64A}.Release|AnyCPU.Build.0 = Release|Any CPU - {64CC4E44-CE3A-4319-BF3F-6CF8BD513870}.Debug|AnyCPU.ActiveCfg = Debug|Any CPU - {64CC4E44-CE3A-4319-BF3F-6CF8BD513870}.Debug|AnyCPU.Build.0 = Debug|Any CPU - {64CC4E44-CE3A-4319-BF3F-6CF8BD513870}.Release|AnyCPU.ActiveCfg = Release|Any CPU - {64CC4E44-CE3A-4319-BF3F-6CF8BD513870}.Release|AnyCPU.Build.0 = Release|Any CPU - {D48EE8D0-0A0A-4493-AEF5-DAF5F8CF86AD}.Debug|AnyCPU.ActiveCfg = Debug|Any CPU - {D48EE8D0-0A0A-4493-AEF5-DAF5F8CF86AD}.Debug|AnyCPU.Build.0 = Debug|Any CPU - {D48EE8D0-0A0A-4493-AEF5-DAF5F8CF86AD}.Release|AnyCPU.ActiveCfg = Release|Any CPU - {D48EE8D0-0A0A-4493-AEF5-DAF5F8CF86AD}.Release|AnyCPU.Build.0 = Release|Any CPU - {53EE4C57-1C03-405A-8243-8DA539546C88}.Debug|AnyCPU.ActiveCfg = Debug|Any CPU - {53EE4C57-1C03-405A-8243-8DA539546C88}.Debug|AnyCPU.Build.0 = Debug|Any CPU - {53EE4C57-1C03-405A-8243-8DA539546C88}.Release|AnyCPU.ActiveCfg = Release|Any CPU - {53EE4C57-1C03-405A-8243-8DA539546C88}.Release|AnyCPU.Build.0 = Release|Any CPU - {9B42A5BB-74CB-46E2-BCE0-AF763A792551}.Debug|AnyCPU.ActiveCfg = Debug|Any CPU - {9B42A5BB-74CB-46E2-BCE0-AF763A792551}.Debug|AnyCPU.Build.0 = Debug|Any CPU - {9B42A5BB-74CB-46E2-BCE0-AF763A792551}.Release|AnyCPU.ActiveCfg = Release|Any CPU - {9B42A5BB-74CB-46E2-BCE0-AF763A792551}.Release|AnyCPU.Build.0 = Release|Any CPU - {645E1718-C8C4-4C23-8A49-5A37E4ECF7ED}.Debug|AnyCPU.ActiveCfg = Debug|Any CPU - {645E1718-C8C4-4C23-8A49-5A37E4ECF7ED}.Debug|AnyCPU.Build.0 = Debug|Any CPU - {645E1718-C8C4-4C23-8A49-5A37E4ECF7ED}.Release|AnyCPU.ActiveCfg = Release|Any CPU - {645E1718-C8C4-4C23-8A49-5A37E4ECF7ED}.Release|AnyCPU.Build.0 = Release|Any CPU - {2DD1EE75-6D8D-4653-A800-0A24367F7F38}.Debug|AnyCPU.ActiveCfg = Debug|Any CPU - {2DD1EE75-6D8D-4653-A800-0A24367F7F38}.Debug|AnyCPU.Build.0 = Debug|Any CPU - {2DD1EE75-6D8D-4653-A800-0A24367F7F38}.Release|AnyCPU.ActiveCfg = Release|Any CPU - {2DD1EE75-6D8D-4653-A800-0A24367F7F38}.Release|AnyCPU.Build.0 = Release|Any CPU - {53E4ABF0-1085-45F9-B964-DCAE4B819998}.Debug|AnyCPU.ActiveCfg = Debug|Any CPU - {53E4ABF0-1085-45F9-B964-DCAE4B819998}.Debug|AnyCPU.Build.0 = Debug|Any CPU - {53E4ABF0-1085-45F9-B964-DCAE4B819998}.Release|AnyCPU.ActiveCfg = Release|Any CPU - {53E4ABF0-1085-45F9-B964-DCAE4B819998}.Release|AnyCPU.Build.0 = Release|Any CPU - {507759AE-93DF-411B-8645-31F680319F5C}.Debug|AnyCPU.ActiveCfg = Debug|Any CPU - {507759AE-93DF-411B-8645-31F680319F5C}.Debug|AnyCPU.Build.0 = Debug|Any CPU - {507759AE-93DF-411B-8645-31F680319F5C}.Release|AnyCPU.ActiveCfg = Release|Any CPU - {507759AE-93DF-411B-8645-31F680319F5C}.Release|AnyCPU.Build.0 = Release|Any CPU - {F9CD012E-67AC-4A4E-B2A7-252387F91256}.Debug|AnyCPU.ActiveCfg = Debug|Any CPU - {F9CD012E-67AC-4A4E-B2A7-252387F91256}.Debug|AnyCPU.Build.0 = Debug|Any CPU - {F9CD012E-67AC-4A4E-B2A7-252387F91256}.Release|AnyCPU.ActiveCfg = Release|Any CPU - {F9CD012E-67AC-4A4E-B2A7-252387F91256}.Release|AnyCPU.Build.0 = Release|Any CPU - {C5A44686-3469-45A7-B6AB-2798BA0625BC}.Debug|AnyCPU.ActiveCfg = Debug|Any CPU - {C5A44686-3469-45A7-B6AB-2798BA0625BC}.Debug|AnyCPU.Build.0 = Debug|Any CPU - {C5A44686-3469-45A7-B6AB-2798BA0625BC}.Release|AnyCPU.ActiveCfg = Release|Any CPU - {C5A44686-3469-45A7-B6AB-2798BA0625BC}.Release|AnyCPU.Build.0 = Release|Any CPU - {A14CB0A1-7A05-4F27-88B2-383798CE1DEE}.Debug|AnyCPU.ActiveCfg = Debug|Any CPU - {A14CB0A1-7A05-4F27-88B2-383798CE1DEE}.Debug|AnyCPU.Build.0 = Debug|Any CPU - {A14CB0A1-7A05-4F27-88B2-383798CE1DEE}.Release|AnyCPU.ActiveCfg = Release|Any CPU - {A14CB0A1-7A05-4F27-88B2-383798CE1DEE}.Release|AnyCPU.Build.0 = Release|Any CPU - {2498F8A0-AA04-40EF-8691-59BBD2396B4D}.Debug|AnyCPU.ActiveCfg = Debug|Any CPU - {2498F8A0-AA04-40EF-8691-59BBD2396B4D}.Debug|AnyCPU.Build.0 = Debug|Any CPU - {2498F8A0-AA04-40EF-8691-59BBD2396B4D}.Release|AnyCPU.ActiveCfg = Release|Any CPU - {2498F8A0-AA04-40EF-8691-59BBD2396B4D}.Release|AnyCPU.Build.0 = Release|Any CPU - {38C762AB-8FD1-44DE-9855-26AAE7129DC3}.Debug|AnyCPU.ActiveCfg = Debug|Any CPU - {38C762AB-8FD1-44DE-9855-26AAE7129DC3}.Debug|AnyCPU.Build.0 = Debug|Any CPU - {38C762AB-8FD1-44DE-9855-26AAE7129DC3}.Release|AnyCPU.ActiveCfg = Release|Any CPU - {38C762AB-8FD1-44DE-9855-26AAE7129DC3}.Release|AnyCPU.Build.0 = Release|Any CPU - {7387E151-48E3-4885-B2CA-A74434A34045}.Debug|AnyCPU.ActiveCfg = Debug|Any CPU - {7387E151-48E3-4885-B2CA-A74434A34045}.Debug|AnyCPU.Build.0 = Debug|Any CPU - {7387E151-48E3-4885-B2CA-A74434A34045}.Release|AnyCPU.ActiveCfg = Release|Any CPU - {7387E151-48E3-4885-B2CA-A74434A34045}.Release|AnyCPU.Build.0 = Release|Any CPU - {8A6CB07C-E493-4A4F-AB94-038645A27118}.Debug|AnyCPU.ActiveCfg = Debug|Any CPU - {8A6CB07C-E493-4A4F-AB94-038645A27118}.Debug|AnyCPU.Build.0 = Debug|Any CPU - {8A6CB07C-E493-4A4F-AB94-038645A27118}.Release|AnyCPU.ActiveCfg = Release|Any CPU - {8A6CB07C-E493-4A4F-AB94-038645A27118}.Release|AnyCPU.Build.0 = Release|Any CPU - {66CF299A-CE95-4131-BCD8-DB66E30C4BF7}.Debug|AnyCPU.ActiveCfg = Debug|Any CPU - {66CF299A-CE95-4131-BCD8-DB66E30C4BF7}.Debug|AnyCPU.Build.0 = Debug|Any CPU - {66CF299A-CE95-4131-BCD8-DB66E30C4BF7}.Release|AnyCPU.ActiveCfg = Release|Any CPU - {66CF299A-CE95-4131-BCD8-DB66E30C4BF7}.Release|AnyCPU.Build.0 = Release|Any CPU - {B8105878-D423-4159-A3E7-028298281EC6}.Debug|AnyCPU.ActiveCfg = Debug|Any CPU - {B8105878-D423-4159-A3E7-028298281EC6}.Debug|AnyCPU.Build.0 = Debug|Any CPU - {B8105878-D423-4159-A3E7-028298281EC6}.Release|AnyCPU.ActiveCfg = Release|Any CPU - {B8105878-D423-4159-A3E7-028298281EC6}.Release|AnyCPU.Build.0 = Release|Any CPU - {43564FB3-0F79-4FF4-A2B0-B1637072FF01}.Debug|AnyCPU.ActiveCfg = Debug|Any CPU - {43564FB3-0F79-4FF4-A2B0-B1637072FF01}.Debug|AnyCPU.Build.0 = Debug|Any CPU - {43564FB3-0F79-4FF4-A2B0-B1637072FF01}.Release|AnyCPU.ActiveCfg = Release|Any CPU - {43564FB3-0F79-4FF4-A2B0-B1637072FF01}.Release|AnyCPU.Build.0 = Release|Any CPU - {3DE17662-DCD6-4F49-AF06-D39AACC8649A}.Debug|AnyCPU.ActiveCfg = Debug|Any CPU - {3DE17662-DCD6-4F49-AF06-D39AACC8649A}.Debug|AnyCPU.Build.0 = Debug|Any CPU - {3DE17662-DCD6-4F49-AF06-D39AACC8649A}.Release|AnyCPU.ActiveCfg = Release|Any CPU - {3DE17662-DCD6-4F49-AF06-D39AACC8649A}.Release|AnyCPU.Build.0 = Release|Any CPU - {E34BCFA0-CAA4-412C-AA1C-75DB8D67D157}.Debug|AnyCPU.ActiveCfg = Debug|Any CPU - {E34BCFA0-CAA4-412C-AA1C-75DB8D67D157}.Debug|AnyCPU.Build.0 = Debug|Any CPU - {E34BCFA0-CAA4-412C-AA1C-75DB8D67D157}.Release|AnyCPU.ActiveCfg = Release|Any CPU - {E34BCFA0-CAA4-412C-AA1C-75DB8D67D157}.Release|AnyCPU.Build.0 = Release|Any CPU - {1E5501E8-49C1-4659-838D-CC9720C5208F}.Debug|AnyCPU.ActiveCfg = Debug|Any CPU - {1E5501E8-49C1-4659-838D-CC9720C5208F}.Debug|AnyCPU.Build.0 = Debug|Any CPU - {1E5501E8-49C1-4659-838D-CC9720C5208F}.Release|AnyCPU.ActiveCfg = Release|Any CPU - {1E5501E8-49C1-4659-838D-CC9720C5208F}.Release|AnyCPU.Build.0 = Release|Any CPU - {1BAFA0CC-0377-46CE-AB7B-7BB2E7B62F63}.Debug|AnyCPU.ActiveCfg = Debug|Any CPU - {1BAFA0CC-0377-46CE-AB7B-7BB2E7B62F63}.Debug|AnyCPU.Build.0 = Debug|Any CPU - {1BAFA0CC-0377-46CE-AB7B-7BB2E7B62F63}.Release|AnyCPU.ActiveCfg = Release|Any CPU - {1BAFA0CC-0377-46CE-AB7B-7BB2E7B62F63}.Release|AnyCPU.Build.0 = Release|Any CPU - {AF8AC493-40AC-4195-82F6-B08EE4B4E49E}.Debug|AnyCPU.ActiveCfg = Debug|Any CPU - {AF8AC493-40AC-4195-82F6-B08EE4B4E49E}.Debug|AnyCPU.Build.0 = Debug|Any CPU - {AF8AC493-40AC-4195-82F6-B08EE4B4E49E}.Release|AnyCPU.ActiveCfg = Release|Any CPU - {AF8AC493-40AC-4195-82F6-B08EE4B4E49E}.Release|AnyCPU.Build.0 = Release|Any CPU - {0C31DE30-F9DF-4312-BFFE-DCAD558CCF08}.Debug|AnyCPU.ActiveCfg = Debug|Any CPU - {0C31DE30-F9DF-4312-BFFE-DCAD558CCF08}.Debug|AnyCPU.Build.0 = Debug|Any CPU - {0C31DE30-F9DF-4312-BFFE-DCAD558CCF08}.Release|AnyCPU.ActiveCfg = Release|Any CPU - {0C31DE30-F9DF-4312-BFFE-DCAD558CCF08}.Release|AnyCPU.Build.0 = Release|Any CPU - {A0AEF446-3368-4591-9DE6-BC3B2B33337D}.Debug|AnyCPU.ActiveCfg = Debug|Any CPU - {A0AEF446-3368-4591-9DE6-BC3B2B33337D}.Debug|AnyCPU.Build.0 = Debug|Any CPU - {A0AEF446-3368-4591-9DE6-BC3B2B33337D}.Release|AnyCPU.ActiveCfg = Release|Any CPU - {A0AEF446-3368-4591-9DE6-BC3B2B33337D}.Release|AnyCPU.Build.0 = Release|Any CPU - {B2BC20D1-F468-46A9-B9B0-1C80CC4D4F36}.Debug|AnyCPU.ActiveCfg = Debug|Any CPU - {B2BC20D1-F468-46A9-B9B0-1C80CC4D4F36}.Debug|AnyCPU.Build.0 = Debug|Any CPU - {B2BC20D1-F468-46A9-B9B0-1C80CC4D4F36}.Release|AnyCPU.ActiveCfg = Release|Any CPU - {B2BC20D1-F468-46A9-B9B0-1C80CC4D4F36}.Release|AnyCPU.Build.0 = Release|Any CPU - {7A180B05-DE3F-4D89-9F40-03A8E186AE82}.Debug|AnyCPU.ActiveCfg = Debug|Any CPU - {7A180B05-DE3F-4D89-9F40-03A8E186AE82}.Debug|AnyCPU.Build.0 = Debug|Any CPU - {7A180B05-DE3F-4D89-9F40-03A8E186AE82}.Release|AnyCPU.ActiveCfg = Release|Any CPU - {7A180B05-DE3F-4D89-9F40-03A8E186AE82}.Release|AnyCPU.Build.0 = Release|Any CPU - {3E45D81B-4C4A-4E3F-B891-A0D8A3E2F0C1}.Debug|AnyCPU.ActiveCfg = Debug|Any CPU - {3E45D81B-4C4A-4E3F-B891-A0D8A3E2F0C1}.Debug|AnyCPU.Build.0 = Debug|Any CPU - {3E45D81B-4C4A-4E3F-B891-A0D8A3E2F0C1}.Release|AnyCPU.ActiveCfg = Release|Any CPU - {3E45D81B-4C4A-4E3F-B891-A0D8A3E2F0C1}.Release|AnyCPU.Build.0 = Release|Any CPU - {F3CFF31C-037B-450F-B22D-1D6E529B2DCC}.Debug|AnyCPU.ActiveCfg = Debug|Any CPU - {F3CFF31C-037B-450F-B22D-1D6E529B2DCC}.Debug|AnyCPU.Build.0 = Debug|Any CPU - {F3CFF31C-037B-450F-B22D-1D6E529B2DCC}.Release|AnyCPU.ActiveCfg = Release|Any CPU - {F3CFF31C-037B-450F-B22D-1D6E529B2DCC}.Release|AnyCPU.Build.0 = Release|Any CPU - {16DB2680-399B-4111-AA26-6CDBBFA334D8}.Debug|AnyCPU.ActiveCfg = Debug|Any CPU - {16DB2680-399B-4111-AA26-6CDBBFA334D8}.Debug|AnyCPU.Build.0 = Debug|Any CPU - {16DB2680-399B-4111-AA26-6CDBBFA334D8}.Release|AnyCPU.ActiveCfg = Release|Any CPU - {16DB2680-399B-4111-AA26-6CDBBFA334D8}.Release|AnyCPU.Build.0 = Release|Any CPU - {372E8E3E-29D5-4B4D-88A2-4711CD628C4E}.Debug|AnyCPU.ActiveCfg = Debug|Any CPU - {372E8E3E-29D5-4B4D-88A2-4711CD628C4E}.Debug|AnyCPU.Build.0 = Debug|Any CPU - {372E8E3E-29D5-4B4D-88A2-4711CD628C4E}.Release|AnyCPU.ActiveCfg = Release|Any CPU - {372E8E3E-29D5-4B4D-88A2-4711CD628C4E}.Release|AnyCPU.Build.0 = Release|Any CPU - {90C99ADB-7D4B-4EB4-98C2-40BD1B14C7D2}.Debug|AnyCPU.ActiveCfg = Debug|Any CPU - {90C99ADB-7D4B-4EB4-98C2-40BD1B14C7D2}.Debug|AnyCPU.Build.0 = Debug|Any CPU - {90C99ADB-7D4B-4EB4-98C2-40BD1B14C7D2}.Release|AnyCPU.ActiveCfg = Release|Any CPU - {90C99ADB-7D4B-4EB4-98C2-40BD1B14C7D2}.Release|AnyCPU.Build.0 = Release|Any CPU - {DE40756E-57F6-4AF2-B155-55E3A88CCED8}.Debug|AnyCPU.ActiveCfg = Debug|Any CPU - {DE40756E-57F6-4AF2-B155-55E3A88CCED8}.Debug|AnyCPU.Build.0 = Debug|Any CPU - {DE40756E-57F6-4AF2-B155-55E3A88CCED8}.Release|AnyCPU.ActiveCfg = Release|Any CPU - {DE40756E-57F6-4AF2-B155-55E3A88CCED8}.Release|AnyCPU.Build.0 = Release|Any CPU - {6410DA0F-5E14-4FC0-9AEE-F4C542C96C7A}.Debug|AnyCPU.ActiveCfg = Debug|Any CPU - {6410DA0F-5E14-4FC0-9AEE-F4C542C96C7A}.Debug|AnyCPU.Build.0 = Debug|Any CPU - {6410DA0F-5E14-4FC0-9AEE-F4C542C96C7A}.Release|AnyCPU.ActiveCfg = Release|Any CPU - {6410DA0F-5E14-4FC0-9AEE-F4C542C96C7A}.Release|AnyCPU.Build.0 = Release|Any CPU - {D28957BF-5E66-4D60-B528-22820C60AC82}.Debug|AnyCPU.ActiveCfg = Debug|Any CPU - {D28957BF-5E66-4D60-B528-22820C60AC82}.Debug|AnyCPU.Build.0 = Debug|Any CPU - {D28957BF-5E66-4D60-B528-22820C60AC82}.Release|AnyCPU.ActiveCfg = Release|Any CPU - {D28957BF-5E66-4D60-B528-22820C60AC82}.Release|AnyCPU.Build.0 = Release|Any CPU - {1FED3F23-1175-42AA-BE87-EF1E8DB52F8B}.Debug|AnyCPU.ActiveCfg = Debug|Any CPU - {1FED3F23-1175-42AA-BE87-EF1E8DB52F8B}.Debug|AnyCPU.Build.0 = Debug|Any CPU - {1FED3F23-1175-42AA-BE87-EF1E8DB52F8B}.Release|AnyCPU.ActiveCfg = Release|Any CPU - {1FED3F23-1175-42AA-BE87-EF1E8DB52F8B}.Release|AnyCPU.Build.0 = Release|Any CPU - {2CE4CD4B-B7B7-4EAE-A9BE-2699824D6096}.Debug|AnyCPU.ActiveCfg = Debug|Any CPU - {2CE4CD4B-B7B7-4EAE-A9BE-2699824D6096}.Debug|AnyCPU.Build.0 = Debug|Any CPU - {2CE4CD4B-B7B7-4EAE-A9BE-2699824D6096}.Release|AnyCPU.ActiveCfg = Release|Any CPU - {2CE4CD4B-B7B7-4EAE-A9BE-2699824D6096}.Release|AnyCPU.Build.0 = Release|Any CPU - {86A8DEFE-7ABB-4097-9389-C249581E243D}.Debug|AnyCPU.ActiveCfg = Debug|Any CPU - {86A8DEFE-7ABB-4097-9389-C249581E243D}.Debug|AnyCPU.Build.0 = Debug|Any CPU - {86A8DEFE-7ABB-4097-9389-C249581E243D}.Release|AnyCPU.ActiveCfg = Release|Any CPU - {86A8DEFE-7ABB-4097-9389-C249581E243D}.Release|AnyCPU.Build.0 = Release|Any CPU - {37FCD325-1077-4603-98E7-4509CAD648D6}.Debug|AnyCPU.ActiveCfg = Debug|Any CPU - {37FCD325-1077-4603-98E7-4509CAD648D6}.Debug|AnyCPU.Build.0 = Debug|Any CPU - {37FCD325-1077-4603-98E7-4509CAD648D6}.Release|AnyCPU.ActiveCfg = Release|Any CPU - {37FCD325-1077-4603-98E7-4509CAD648D6}.Release|AnyCPU.Build.0 = Release|Any CPU - {88B746FF-8D6E-464D-9D66-FF2ECCF148E0}.Debug|AnyCPU.ActiveCfg = Debug|Any CPU - {88B746FF-8D6E-464D-9D66-FF2ECCF148E0}.Debug|AnyCPU.Build.0 = Debug|Any CPU - {88B746FF-8D6E-464D-9D66-FF2ECCF148E0}.Release|AnyCPU.ActiveCfg = Release|Any CPU - {88B746FF-8D6E-464D-9D66-FF2ECCF148E0}.Release|AnyCPU.Build.0 = Release|Any CPU - {1A273ED2-AE84-48E9-9C23-E978C2D0CB34}.Debug|AnyCPU.ActiveCfg = Debug|anycpu - {1A273ED2-AE84-48E9-9C23-E978C2D0CB34}.Debug|AnyCPU.Build.0 = Debug|anycpu - {1A273ED2-AE84-48E9-9C23-E978C2D0CB34}.Release|AnyCPU.ActiveCfg = Release|anycpu - {1A273ED2-AE84-48E9-9C23-E978C2D0CB34}.Release|AnyCPU.Build.0 = Release|anycpu - {DA50FC92-7FE7-48B5-BDB6-CDA57B37BB51}.Debug|AnyCPU.ActiveCfg = Debug|anycpu - {DA50FC92-7FE7-48B5-BDB6-CDA57B37BB51}.Debug|AnyCPU.Build.0 = Debug|anycpu - {DA50FC92-7FE7-48B5-BDB6-CDA57B37BB51}.Release|AnyCPU.ActiveCfg = Release|anycpu - {DA50FC92-7FE7-48B5-BDB6-CDA57B37BB51}.Release|AnyCPU.Build.0 = Release|anycpu - {4EFCED6E-9A6B-453A-94E4-CE4B736EC684}.Debug|AnyCPU.ActiveCfg = Debug|Any CPU - {4EFCED6E-9A6B-453A-94E4-CE4B736EC684}.Debug|AnyCPU.Build.0 = Debug|Any CPU - {4EFCED6E-9A6B-453A-94E4-CE4B736EC684}.Release|AnyCPU.ActiveCfg = Release|Any CPU - {4EFCED6E-9A6B-453A-94E4-CE4B736EC684}.Release|AnyCPU.Build.0 = Release|Any CPU - {D8E14B43-E929-4C18-9FA6-2C3DC47EFC17}.Debug|AnyCPU.ActiveCfg = Debug|Any CPU - {D8E14B43-E929-4C18-9FA6-2C3DC47EFC17}.Debug|AnyCPU.Build.0 = Debug|Any CPU - {D8E14B43-E929-4C18-9FA6-2C3DC47EFC17}.Release|AnyCPU.ActiveCfg = Release|Any CPU - {D8E14B43-E929-4C18-9FA6-2C3DC47EFC17}.Release|AnyCPU.Build.0 = Release|Any CPU - {C0E44558-FEE3-4DD3-986A-3F46DD1BF41B}.Debug|AnyCPU.ActiveCfg = Debug|Any CPU - {C0E44558-FEE3-4DD3-986A-3F46DD1BF41B}.Debug|AnyCPU.Build.0 = Debug|Any CPU - {C0E44558-FEE3-4DD3-986A-3F46DD1BF41B}.Release|AnyCPU.ActiveCfg = Release|Any CPU - {C0E44558-FEE3-4DD3-986A-3F46DD1BF41B}.Release|AnyCPU.Build.0 = Release|Any CPU - {BA4D889D-066B-4C2C-A973-09E319CBC396}.Debug|AnyCPU.ActiveCfg = Debug|Any CPU - {BA4D889D-066B-4C2C-A973-09E319CBC396}.Debug|AnyCPU.Build.0 = Debug|Any CPU - {BA4D889D-066B-4C2C-A973-09E319CBC396}.Release|AnyCPU.ActiveCfg = Release|Any CPU - {BA4D889D-066B-4C2C-A973-09E319CBC396}.Release|AnyCPU.Build.0 = Release|Any CPU - {A39B6D7C-6616-40D6-8AE4-C6CEE93D2708}.Debug|AnyCPU.ActiveCfg = Debug|Any CPU - {A39B6D7C-6616-40D6-8AE4-C6CEE93D2708}.Debug|AnyCPU.Build.0 = Debug|Any CPU - {A39B6D7C-6616-40D6-8AE4-C6CEE93D2708}.Release|AnyCPU.ActiveCfg = Release|Any CPU - {A39B6D7C-6616-40D6-8AE4-C6CEE93D2708}.Release|AnyCPU.Build.0 = Release|Any CPU - {5E806C9F-1B67-4B6B-A6AB-258834250DBB}.Debug|AnyCPU.ActiveCfg = Debug|Any CPU - {5E806C9F-1B67-4B6B-A6AB-258834250DBB}.Debug|AnyCPU.Build.0 = Debug|Any CPU - {5E806C9F-1B67-4B6B-A6AB-258834250DBB}.Release|AnyCPU.ActiveCfg = Release|Any CPU - {5E806C9F-1B67-4B6B-A6AB-258834250DBB}.Release|AnyCPU.Build.0 = Release|Any CPU - {5FD0133B-69E5-4474-9B67-9FD1D0150C70}.Debug|AnyCPU.ActiveCfg = Debug|Any CPU - {5FD0133B-69E5-4474-9B67-9FD1D0150C70}.Debug|AnyCPU.Build.0 = Debug|Any CPU - {5FD0133B-69E5-4474-9B67-9FD1D0150C70}.Release|AnyCPU.ActiveCfg = Release|Any CPU - {5FD0133B-69E5-4474-9B67-9FD1D0150C70}.Release|AnyCPU.Build.0 = Release|Any CPU - {E8831F32-11D7-D42C-E43C-711998BC357A}.Debug|AnyCPU.ActiveCfg = Debug|Any CPU - {E8831F32-11D7-D42C-E43C-711998BC357A}.Debug|AnyCPU.Build.0 = Debug|Any CPU - {E8831F32-11D7-D42C-E43C-711998BC357A}.Release|AnyCPU.ActiveCfg = Release|Any CPU - {E8831F32-11D7-D42C-E43C-711998BC357A}.Release|AnyCPU.Build.0 = Release|Any CPU + {1D4FC8F1-0DA4-4F38-BE68-11AEBA9A0EA4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1D4FC8F1-0DA4-4F38-BE68-11AEBA9A0EA4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1D4FC8F1-0DA4-4F38-BE68-11AEBA9A0EA4}.Debug|x64.ActiveCfg = Debug|Any CPU + {1D4FC8F1-0DA4-4F38-BE68-11AEBA9A0EA4}.Debug|x64.Build.0 = Debug|Any CPU + {1D4FC8F1-0DA4-4F38-BE68-11AEBA9A0EA4}.Debug|x86.ActiveCfg = Debug|Any CPU + {1D4FC8F1-0DA4-4F38-BE68-11AEBA9A0EA4}.Debug|x86.Build.0 = Debug|Any CPU + {1D4FC8F1-0DA4-4F38-BE68-11AEBA9A0EA4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1D4FC8F1-0DA4-4F38-BE68-11AEBA9A0EA4}.Release|Any CPU.Build.0 = Release|Any CPU + {1D4FC8F1-0DA4-4F38-BE68-11AEBA9A0EA4}.Release|x64.ActiveCfg = Release|Any CPU + {1D4FC8F1-0DA4-4F38-BE68-11AEBA9A0EA4}.Release|x64.Build.0 = Release|Any CPU + {1D4FC8F1-0DA4-4F38-BE68-11AEBA9A0EA4}.Release|x86.ActiveCfg = Release|Any CPU + {1D4FC8F1-0DA4-4F38-BE68-11AEBA9A0EA4}.Release|x86.Build.0 = Release|Any CPU + {AFB8F6D1-6EA9-42C3-950B-98F34C669AD2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AFB8F6D1-6EA9-42C3-950B-98F34C669AD2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AFB8F6D1-6EA9-42C3-950B-98F34C669AD2}.Debug|x64.ActiveCfg = Debug|Any CPU + {AFB8F6D1-6EA9-42C3-950B-98F34C669AD2}.Debug|x64.Build.0 = Debug|Any CPU + {AFB8F6D1-6EA9-42C3-950B-98F34C669AD2}.Debug|x86.ActiveCfg = Debug|Any CPU + {AFB8F6D1-6EA9-42C3-950B-98F34C669AD2}.Debug|x86.Build.0 = Debug|Any CPU + {AFB8F6D1-6EA9-42C3-950B-98F34C669AD2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AFB8F6D1-6EA9-42C3-950B-98F34C669AD2}.Release|Any CPU.Build.0 = Release|Any CPU + {AFB8F6D1-6EA9-42C3-950B-98F34C669AD2}.Release|x64.ActiveCfg = Release|Any CPU + {AFB8F6D1-6EA9-42C3-950B-98F34C669AD2}.Release|x64.Build.0 = Release|Any CPU + {AFB8F6D1-6EA9-42C3-950B-98F34C669AD2}.Release|x86.ActiveCfg = Release|Any CPU + {AFB8F6D1-6EA9-42C3-950B-98F34C669AD2}.Release|x86.Build.0 = Release|Any CPU + {3FC3E78B-F7D4-42EA-BBE8-4535DF42BFF8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3FC3E78B-F7D4-42EA-BBE8-4535DF42BFF8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3FC3E78B-F7D4-42EA-BBE8-4535DF42BFF8}.Debug|x64.ActiveCfg = Debug|Any CPU + {3FC3E78B-F7D4-42EA-BBE8-4535DF42BFF8}.Debug|x64.Build.0 = Debug|Any CPU + {3FC3E78B-F7D4-42EA-BBE8-4535DF42BFF8}.Debug|x86.ActiveCfg = Debug|Any CPU + {3FC3E78B-F7D4-42EA-BBE8-4535DF42BFF8}.Debug|x86.Build.0 = Debug|Any CPU + {3FC3E78B-F7D4-42EA-BBE8-4535DF42BFF8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3FC3E78B-F7D4-42EA-BBE8-4535DF42BFF8}.Release|Any CPU.Build.0 = Release|Any CPU + {3FC3E78B-F7D4-42EA-BBE8-4535DF42BFF8}.Release|x64.ActiveCfg = Release|Any CPU + {3FC3E78B-F7D4-42EA-BBE8-4535DF42BFF8}.Release|x64.Build.0 = Release|Any CPU + {3FC3E78B-F7D4-42EA-BBE8-4535DF42BFF8}.Release|x86.ActiveCfg = Release|Any CPU + {3FC3E78B-F7D4-42EA-BBE8-4535DF42BFF8}.Release|x86.Build.0 = Release|Any CPU + {94BD81F7-B06F-4295-9636-F8A3B6BDC762}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {94BD81F7-B06F-4295-9636-F8A3B6BDC762}.Debug|Any CPU.Build.0 = Debug|Any CPU + {94BD81F7-B06F-4295-9636-F8A3B6BDC762}.Debug|x64.ActiveCfg = Debug|Any CPU + {94BD81F7-B06F-4295-9636-F8A3B6BDC762}.Debug|x64.Build.0 = Debug|Any CPU + {94BD81F7-B06F-4295-9636-F8A3B6BDC762}.Debug|x86.ActiveCfg = Debug|Any CPU + {94BD81F7-B06F-4295-9636-F8A3B6BDC762}.Debug|x86.Build.0 = Debug|Any CPU + {94BD81F7-B06F-4295-9636-F8A3B6BDC762}.Release|Any CPU.ActiveCfg = Release|Any CPU + {94BD81F7-B06F-4295-9636-F8A3B6BDC762}.Release|Any CPU.Build.0 = Release|Any CPU + {94BD81F7-B06F-4295-9636-F8A3B6BDC762}.Release|x64.ActiveCfg = Release|Any CPU + {94BD81F7-B06F-4295-9636-F8A3B6BDC762}.Release|x64.Build.0 = Release|Any CPU + {94BD81F7-B06F-4295-9636-F8A3B6BDC762}.Release|x86.ActiveCfg = Release|Any CPU + {94BD81F7-B06F-4295-9636-F8A3B6BDC762}.Release|x86.Build.0 = Release|Any CPU + {D14A1B5C-2060-4930-92BE-F7190256C735}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D14A1B5C-2060-4930-92BE-F7190256C735}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D14A1B5C-2060-4930-92BE-F7190256C735}.Debug|x64.ActiveCfg = Debug|Any CPU + {D14A1B5C-2060-4930-92BE-F7190256C735}.Debug|x64.Build.0 = Debug|Any CPU + {D14A1B5C-2060-4930-92BE-F7190256C735}.Debug|x86.ActiveCfg = Debug|Any CPU + {D14A1B5C-2060-4930-92BE-F7190256C735}.Debug|x86.Build.0 = Debug|Any CPU + {D14A1B5C-2060-4930-92BE-F7190256C735}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D14A1B5C-2060-4930-92BE-F7190256C735}.Release|Any CPU.Build.0 = Release|Any CPU + {D14A1B5C-2060-4930-92BE-F7190256C735}.Release|x64.ActiveCfg = Release|Any CPU + {D14A1B5C-2060-4930-92BE-F7190256C735}.Release|x64.Build.0 = Release|Any CPU + {D14A1B5C-2060-4930-92BE-F7190256C735}.Release|x86.ActiveCfg = Release|Any CPU + {D14A1B5C-2060-4930-92BE-F7190256C735}.Release|x86.Build.0 = Release|Any CPU + {D27AD8F7-7710-40BE-B03B-55EFBEC13C44}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D27AD8F7-7710-40BE-B03B-55EFBEC13C44}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D27AD8F7-7710-40BE-B03B-55EFBEC13C44}.Debug|x64.ActiveCfg = Debug|Any CPU + {D27AD8F7-7710-40BE-B03B-55EFBEC13C44}.Debug|x64.Build.0 = Debug|Any CPU + {D27AD8F7-7710-40BE-B03B-55EFBEC13C44}.Debug|x86.ActiveCfg = Debug|Any CPU + {D27AD8F7-7710-40BE-B03B-55EFBEC13C44}.Debug|x86.Build.0 = Debug|Any CPU + {D27AD8F7-7710-40BE-B03B-55EFBEC13C44}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D27AD8F7-7710-40BE-B03B-55EFBEC13C44}.Release|Any CPU.Build.0 = Release|Any CPU + {D27AD8F7-7710-40BE-B03B-55EFBEC13C44}.Release|x64.ActiveCfg = Release|Any CPU + {D27AD8F7-7710-40BE-B03B-55EFBEC13C44}.Release|x64.Build.0 = Release|Any CPU + {D27AD8F7-7710-40BE-B03B-55EFBEC13C44}.Release|x86.ActiveCfg = Release|Any CPU + {D27AD8F7-7710-40BE-B03B-55EFBEC13C44}.Release|x86.Build.0 = Release|Any CPU + {3F1F2F50-AF1A-4A5A-BEDB-193372F068D7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3F1F2F50-AF1A-4A5A-BEDB-193372F068D7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3F1F2F50-AF1A-4A5A-BEDB-193372F068D7}.Debug|x64.ActiveCfg = Debug|Any CPU + {3F1F2F50-AF1A-4A5A-BEDB-193372F068D7}.Debug|x64.Build.0 = Debug|Any CPU + {3F1F2F50-AF1A-4A5A-BEDB-193372F068D7}.Debug|x86.ActiveCfg = Debug|Any CPU + {3F1F2F50-AF1A-4A5A-BEDB-193372F068D7}.Debug|x86.Build.0 = Debug|Any CPU + {3F1F2F50-AF1A-4A5A-BEDB-193372F068D7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3F1F2F50-AF1A-4A5A-BEDB-193372F068D7}.Release|Any CPU.Build.0 = Release|Any CPU + {3F1F2F50-AF1A-4A5A-BEDB-193372F068D7}.Release|x64.ActiveCfg = Release|Any CPU + {3F1F2F50-AF1A-4A5A-BEDB-193372F068D7}.Release|x64.Build.0 = Release|Any CPU + {3F1F2F50-AF1A-4A5A-BEDB-193372F068D7}.Release|x86.ActiveCfg = Release|Any CPU + {3F1F2F50-AF1A-4A5A-BEDB-193372F068D7}.Release|x86.Build.0 = Release|Any CPU + {A1B2C3D4-E5F6-4A5B-8C9D-0E1F2A3B4C5D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A1B2C3D4-E5F6-4A5B-8C9D-0E1F2A3B4C5D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A1B2C3D4-E5F6-4A5B-8C9D-0E1F2A3B4C5D}.Debug|x64.ActiveCfg = Debug|Any CPU + {A1B2C3D4-E5F6-4A5B-8C9D-0E1F2A3B4C5D}.Debug|x64.Build.0 = Debug|Any CPU + {A1B2C3D4-E5F6-4A5B-8C9D-0E1F2A3B4C5D}.Debug|x86.ActiveCfg = Debug|Any CPU + {A1B2C3D4-E5F6-4A5B-8C9D-0E1F2A3B4C5D}.Debug|x86.Build.0 = Debug|Any CPU + {A1B2C3D4-E5F6-4A5B-8C9D-0E1F2A3B4C5D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A1B2C3D4-E5F6-4A5B-8C9D-0E1F2A3B4C5D}.Release|Any CPU.Build.0 = Release|Any CPU + {A1B2C3D4-E5F6-4A5B-8C9D-0E1F2A3B4C5D}.Release|x64.ActiveCfg = Release|Any CPU + {A1B2C3D4-E5F6-4A5B-8C9D-0E1F2A3B4C5D}.Release|x64.Build.0 = Release|Any CPU + {A1B2C3D4-E5F6-4A5B-8C9D-0E1F2A3B4C5D}.Release|x86.ActiveCfg = Release|Any CPU + {A1B2C3D4-E5F6-4A5B-8C9D-0E1F2A3B4C5D}.Release|x86.Build.0 = Release|Any CPU + {71FE54FA-0BF5-48EF-ACAA-17557B28C9F4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {71FE54FA-0BF5-48EF-ACAA-17557B28C9F4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {71FE54FA-0BF5-48EF-ACAA-17557B28C9F4}.Debug|x64.ActiveCfg = Debug|Any CPU + {71FE54FA-0BF5-48EF-ACAA-17557B28C9F4}.Debug|x64.Build.0 = Debug|Any CPU + {71FE54FA-0BF5-48EF-ACAA-17557B28C9F4}.Debug|x86.ActiveCfg = Debug|Any CPU + {71FE54FA-0BF5-48EF-ACAA-17557B28C9F4}.Debug|x86.Build.0 = Debug|Any CPU + {71FE54FA-0BF5-48EF-ACAA-17557B28C9F4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {71FE54FA-0BF5-48EF-ACAA-17557B28C9F4}.Release|Any CPU.Build.0 = Release|Any CPU + {71FE54FA-0BF5-48EF-ACAA-17557B28C9F4}.Release|x64.ActiveCfg = Release|Any CPU + {71FE54FA-0BF5-48EF-ACAA-17557B28C9F4}.Release|x64.Build.0 = Release|Any CPU + {71FE54FA-0BF5-48EF-ACAA-17557B28C9F4}.Release|x86.ActiveCfg = Release|Any CPU + {71FE54FA-0BF5-48EF-ACAA-17557B28C9F4}.Release|x86.Build.0 = Release|Any CPU + {B17475BC-45A2-47A3-B8FC-62F3A0959EE0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B17475BC-45A2-47A3-B8FC-62F3A0959EE0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B17475BC-45A2-47A3-B8FC-62F3A0959EE0}.Debug|x64.ActiveCfg = Debug|Any CPU + {B17475BC-45A2-47A3-B8FC-62F3A0959EE0}.Debug|x64.Build.0 = Debug|Any CPU + {B17475BC-45A2-47A3-B8FC-62F3A0959EE0}.Debug|x86.ActiveCfg = Debug|Any CPU + {B17475BC-45A2-47A3-B8FC-62F3A0959EE0}.Debug|x86.Build.0 = Debug|Any CPU + {B17475BC-45A2-47A3-B8FC-62F3A0959EE0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B17475BC-45A2-47A3-B8FC-62F3A0959EE0}.Release|Any CPU.Build.0 = Release|Any CPU + {B17475BC-45A2-47A3-B8FC-62F3A0959EE0}.Release|x64.ActiveCfg = Release|Any CPU + {B17475BC-45A2-47A3-B8FC-62F3A0959EE0}.Release|x64.Build.0 = Release|Any CPU + {B17475BC-45A2-47A3-B8FC-62F3A0959EE0}.Release|x86.ActiveCfg = Release|Any CPU + {B17475BC-45A2-47A3-B8FC-62F3A0959EE0}.Release|x86.Build.0 = Release|Any CPU + {52C7D9B6-E8C8-47D0-9471-652D278D7D77}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {52C7D9B6-E8C8-47D0-9471-652D278D7D77}.Debug|Any CPU.Build.0 = Debug|Any CPU + {52C7D9B6-E8C8-47D0-9471-652D278D7D77}.Debug|x64.ActiveCfg = Debug|Any CPU + {52C7D9B6-E8C8-47D0-9471-652D278D7D77}.Debug|x64.Build.0 = Debug|Any CPU + {52C7D9B6-E8C8-47D0-9471-652D278D7D77}.Debug|x86.ActiveCfg = Debug|Any CPU + {52C7D9B6-E8C8-47D0-9471-652D278D7D77}.Debug|x86.Build.0 = Debug|Any CPU + {52C7D9B6-E8C8-47D0-9471-652D278D7D77}.Release|Any CPU.ActiveCfg = Release|Any CPU + {52C7D9B6-E8C8-47D0-9471-652D278D7D77}.Release|Any CPU.Build.0 = Release|Any CPU + {52C7D9B6-E8C8-47D0-9471-652D278D7D77}.Release|x64.ActiveCfg = Release|Any CPU + {52C7D9B6-E8C8-47D0-9471-652D278D7D77}.Release|x64.Build.0 = Release|Any CPU + {52C7D9B6-E8C8-47D0-9471-652D278D7D77}.Release|x86.ActiveCfg = Release|Any CPU + {52C7D9B6-E8C8-47D0-9471-652D278D7D77}.Release|x86.Build.0 = Release|Any CPU + {D18FCF91-8876-48A0-A693-2DC1E7D3D80A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D18FCF91-8876-48A0-A693-2DC1E7D3D80A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D18FCF91-8876-48A0-A693-2DC1E7D3D80A}.Debug|x64.ActiveCfg = Debug|Any CPU + {D18FCF91-8876-48A0-A693-2DC1E7D3D80A}.Debug|x64.Build.0 = Debug|Any CPU + {D18FCF91-8876-48A0-A693-2DC1E7D3D80A}.Debug|x86.ActiveCfg = Debug|Any CPU + {D18FCF91-8876-48A0-A693-2DC1E7D3D80A}.Debug|x86.Build.0 = Debug|Any CPU + {D18FCF91-8876-48A0-A693-2DC1E7D3D80A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D18FCF91-8876-48A0-A693-2DC1E7D3D80A}.Release|Any CPU.Build.0 = Release|Any CPU + {D18FCF91-8876-48A0-A693-2DC1E7D3D80A}.Release|x64.ActiveCfg = Release|Any CPU + {D18FCF91-8876-48A0-A693-2DC1E7D3D80A}.Release|x64.Build.0 = Release|Any CPU + {D18FCF91-8876-48A0-A693-2DC1E7D3D80A}.Release|x86.ActiveCfg = Release|Any CPU + {D18FCF91-8876-48A0-A693-2DC1E7D3D80A}.Release|x86.Build.0 = Release|Any CPU + {1268EADF-8344-431C-81F6-FCB7CBC99F49}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1268EADF-8344-431C-81F6-FCB7CBC99F49}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1268EADF-8344-431C-81F6-FCB7CBC99F49}.Debug|x64.ActiveCfg = Debug|Any CPU + {1268EADF-8344-431C-81F6-FCB7CBC99F49}.Debug|x64.Build.0 = Debug|Any CPU + {1268EADF-8344-431C-81F6-FCB7CBC99F49}.Debug|x86.ActiveCfg = Debug|Any CPU + {1268EADF-8344-431C-81F6-FCB7CBC99F49}.Debug|x86.Build.0 = Debug|Any CPU + {1268EADF-8344-431C-81F6-FCB7CBC99F49}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1268EADF-8344-431C-81F6-FCB7CBC99F49}.Release|Any CPU.Build.0 = Release|Any CPU + {1268EADF-8344-431C-81F6-FCB7CBC99F49}.Release|x64.ActiveCfg = Release|Any CPU + {1268EADF-8344-431C-81F6-FCB7CBC99F49}.Release|x64.Build.0 = Release|Any CPU + {1268EADF-8344-431C-81F6-FCB7CBC99F49}.Release|x86.ActiveCfg = Release|Any CPU + {1268EADF-8344-431C-81F6-FCB7CBC99F49}.Release|x86.Build.0 = Release|Any CPU + {07BC4495-1267-4B78-9EA6-B76FEEA2A64A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {07BC4495-1267-4B78-9EA6-B76FEEA2A64A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {07BC4495-1267-4B78-9EA6-B76FEEA2A64A}.Debug|x64.ActiveCfg = Debug|Any CPU + {07BC4495-1267-4B78-9EA6-B76FEEA2A64A}.Debug|x64.Build.0 = Debug|Any CPU + {07BC4495-1267-4B78-9EA6-B76FEEA2A64A}.Debug|x86.ActiveCfg = Debug|Any CPU + {07BC4495-1267-4B78-9EA6-B76FEEA2A64A}.Debug|x86.Build.0 = Debug|Any CPU + {07BC4495-1267-4B78-9EA6-B76FEEA2A64A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {07BC4495-1267-4B78-9EA6-B76FEEA2A64A}.Release|Any CPU.Build.0 = Release|Any CPU + {07BC4495-1267-4B78-9EA6-B76FEEA2A64A}.Release|x64.ActiveCfg = Release|Any CPU + {07BC4495-1267-4B78-9EA6-B76FEEA2A64A}.Release|x64.Build.0 = Release|Any CPU + {07BC4495-1267-4B78-9EA6-B76FEEA2A64A}.Release|x86.ActiveCfg = Release|Any CPU + {07BC4495-1267-4B78-9EA6-B76FEEA2A64A}.Release|x86.Build.0 = Release|Any CPU + {64CC4E44-CE3A-4319-BF3F-6CF8BD513870}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {64CC4E44-CE3A-4319-BF3F-6CF8BD513870}.Debug|Any CPU.Build.0 = Debug|Any CPU + {64CC4E44-CE3A-4319-BF3F-6CF8BD513870}.Debug|x64.ActiveCfg = Debug|Any CPU + {64CC4E44-CE3A-4319-BF3F-6CF8BD513870}.Debug|x64.Build.0 = Debug|Any CPU + {64CC4E44-CE3A-4319-BF3F-6CF8BD513870}.Debug|x86.ActiveCfg = Debug|Any CPU + {64CC4E44-CE3A-4319-BF3F-6CF8BD513870}.Debug|x86.Build.0 = Debug|Any CPU + {64CC4E44-CE3A-4319-BF3F-6CF8BD513870}.Release|Any CPU.ActiveCfg = Release|Any CPU + {64CC4E44-CE3A-4319-BF3F-6CF8BD513870}.Release|Any CPU.Build.0 = Release|Any CPU + {64CC4E44-CE3A-4319-BF3F-6CF8BD513870}.Release|x64.ActiveCfg = Release|Any CPU + {64CC4E44-CE3A-4319-BF3F-6CF8BD513870}.Release|x64.Build.0 = Release|Any CPU + {64CC4E44-CE3A-4319-BF3F-6CF8BD513870}.Release|x86.ActiveCfg = Release|Any CPU + {64CC4E44-CE3A-4319-BF3F-6CF8BD513870}.Release|x86.Build.0 = Release|Any CPU + {D48EE8D0-0A0A-4493-AEF5-DAF5F8CF86AD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D48EE8D0-0A0A-4493-AEF5-DAF5F8CF86AD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D48EE8D0-0A0A-4493-AEF5-DAF5F8CF86AD}.Debug|x64.ActiveCfg = Debug|Any CPU + {D48EE8D0-0A0A-4493-AEF5-DAF5F8CF86AD}.Debug|x64.Build.0 = Debug|Any CPU + {D48EE8D0-0A0A-4493-AEF5-DAF5F8CF86AD}.Debug|x86.ActiveCfg = Debug|Any CPU + {D48EE8D0-0A0A-4493-AEF5-DAF5F8CF86AD}.Debug|x86.Build.0 = Debug|Any CPU + {D48EE8D0-0A0A-4493-AEF5-DAF5F8CF86AD}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D48EE8D0-0A0A-4493-AEF5-DAF5F8CF86AD}.Release|Any CPU.Build.0 = Release|Any CPU + {D48EE8D0-0A0A-4493-AEF5-DAF5F8CF86AD}.Release|x64.ActiveCfg = Release|Any CPU + {D48EE8D0-0A0A-4493-AEF5-DAF5F8CF86AD}.Release|x64.Build.0 = Release|Any CPU + {D48EE8D0-0A0A-4493-AEF5-DAF5F8CF86AD}.Release|x86.ActiveCfg = Release|Any CPU + {D48EE8D0-0A0A-4493-AEF5-DAF5F8CF86AD}.Release|x86.Build.0 = Release|Any CPU + {53EE4C57-1C03-405A-8243-8DA539546C88}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {53EE4C57-1C03-405A-8243-8DA539546C88}.Debug|Any CPU.Build.0 = Debug|Any CPU + {53EE4C57-1C03-405A-8243-8DA539546C88}.Debug|x64.ActiveCfg = Debug|Any CPU + {53EE4C57-1C03-405A-8243-8DA539546C88}.Debug|x64.Build.0 = Debug|Any CPU + {53EE4C57-1C03-405A-8243-8DA539546C88}.Debug|x86.ActiveCfg = Debug|Any CPU + {53EE4C57-1C03-405A-8243-8DA539546C88}.Debug|x86.Build.0 = Debug|Any CPU + {53EE4C57-1C03-405A-8243-8DA539546C88}.Release|Any CPU.ActiveCfg = Release|Any CPU + {53EE4C57-1C03-405A-8243-8DA539546C88}.Release|Any CPU.Build.0 = Release|Any CPU + {53EE4C57-1C03-405A-8243-8DA539546C88}.Release|x64.ActiveCfg = Release|Any CPU + {53EE4C57-1C03-405A-8243-8DA539546C88}.Release|x64.Build.0 = Release|Any CPU + {53EE4C57-1C03-405A-8243-8DA539546C88}.Release|x86.ActiveCfg = Release|Any CPU + {53EE4C57-1C03-405A-8243-8DA539546C88}.Release|x86.Build.0 = Release|Any CPU + {9B42A5BB-74CB-46E2-BCE0-AF763A792551}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9B42A5BB-74CB-46E2-BCE0-AF763A792551}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9B42A5BB-74CB-46E2-BCE0-AF763A792551}.Debug|x64.ActiveCfg = Debug|Any CPU + {9B42A5BB-74CB-46E2-BCE0-AF763A792551}.Debug|x64.Build.0 = Debug|Any CPU + {9B42A5BB-74CB-46E2-BCE0-AF763A792551}.Debug|x86.ActiveCfg = Debug|Any CPU + {9B42A5BB-74CB-46E2-BCE0-AF763A792551}.Debug|x86.Build.0 = Debug|Any CPU + {9B42A5BB-74CB-46E2-BCE0-AF763A792551}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9B42A5BB-74CB-46E2-BCE0-AF763A792551}.Release|Any CPU.Build.0 = Release|Any CPU + {9B42A5BB-74CB-46E2-BCE0-AF763A792551}.Release|x64.ActiveCfg = Release|Any CPU + {9B42A5BB-74CB-46E2-BCE0-AF763A792551}.Release|x64.Build.0 = Release|Any CPU + {9B42A5BB-74CB-46E2-BCE0-AF763A792551}.Release|x86.ActiveCfg = Release|Any CPU + {9B42A5BB-74CB-46E2-BCE0-AF763A792551}.Release|x86.Build.0 = Release|Any CPU + {645E1718-C8C4-4C23-8A49-5A37E4ECF7ED}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {645E1718-C8C4-4C23-8A49-5A37E4ECF7ED}.Debug|Any CPU.Build.0 = Debug|Any CPU + {645E1718-C8C4-4C23-8A49-5A37E4ECF7ED}.Debug|x64.ActiveCfg = Debug|Any CPU + {645E1718-C8C4-4C23-8A49-5A37E4ECF7ED}.Debug|x64.Build.0 = Debug|Any CPU + {645E1718-C8C4-4C23-8A49-5A37E4ECF7ED}.Debug|x86.ActiveCfg = Debug|Any CPU + {645E1718-C8C4-4C23-8A49-5A37E4ECF7ED}.Debug|x86.Build.0 = Debug|Any CPU + {645E1718-C8C4-4C23-8A49-5A37E4ECF7ED}.Release|Any CPU.ActiveCfg = Release|Any CPU + {645E1718-C8C4-4C23-8A49-5A37E4ECF7ED}.Release|Any CPU.Build.0 = Release|Any CPU + {645E1718-C8C4-4C23-8A49-5A37E4ECF7ED}.Release|x64.ActiveCfg = Release|Any CPU + {645E1718-C8C4-4C23-8A49-5A37E4ECF7ED}.Release|x64.Build.0 = Release|Any CPU + {645E1718-C8C4-4C23-8A49-5A37E4ECF7ED}.Release|x86.ActiveCfg = Release|Any CPU + {645E1718-C8C4-4C23-8A49-5A37E4ECF7ED}.Release|x86.Build.0 = Release|Any CPU + {2DD1EE75-6D8D-4653-A800-0A24367F7F38}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2DD1EE75-6D8D-4653-A800-0A24367F7F38}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2DD1EE75-6D8D-4653-A800-0A24367F7F38}.Debug|x64.ActiveCfg = Debug|Any CPU + {2DD1EE75-6D8D-4653-A800-0A24367F7F38}.Debug|x64.Build.0 = Debug|Any CPU + {2DD1EE75-6D8D-4653-A800-0A24367F7F38}.Debug|x86.ActiveCfg = Debug|Any CPU + {2DD1EE75-6D8D-4653-A800-0A24367F7F38}.Debug|x86.Build.0 = Debug|Any CPU + {2DD1EE75-6D8D-4653-A800-0A24367F7F38}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2DD1EE75-6D8D-4653-A800-0A24367F7F38}.Release|Any CPU.Build.0 = Release|Any CPU + {2DD1EE75-6D8D-4653-A800-0A24367F7F38}.Release|x64.ActiveCfg = Release|Any CPU + {2DD1EE75-6D8D-4653-A800-0A24367F7F38}.Release|x64.Build.0 = Release|Any CPU + {2DD1EE75-6D8D-4653-A800-0A24367F7F38}.Release|x86.ActiveCfg = Release|Any CPU + {2DD1EE75-6D8D-4653-A800-0A24367F7F38}.Release|x86.Build.0 = Release|Any CPU + {53E4ABF0-1085-45F9-B964-DCAE4B819998}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {53E4ABF0-1085-45F9-B964-DCAE4B819998}.Debug|Any CPU.Build.0 = Debug|Any CPU + {53E4ABF0-1085-45F9-B964-DCAE4B819998}.Debug|x64.ActiveCfg = Debug|Any CPU + {53E4ABF0-1085-45F9-B964-DCAE4B819998}.Debug|x64.Build.0 = Debug|Any CPU + {53E4ABF0-1085-45F9-B964-DCAE4B819998}.Debug|x86.ActiveCfg = Debug|Any CPU + {53E4ABF0-1085-45F9-B964-DCAE4B819998}.Debug|x86.Build.0 = Debug|Any CPU + {53E4ABF0-1085-45F9-B964-DCAE4B819998}.Release|Any CPU.ActiveCfg = Release|Any CPU + {53E4ABF0-1085-45F9-B964-DCAE4B819998}.Release|Any CPU.Build.0 = Release|Any CPU + {53E4ABF0-1085-45F9-B964-DCAE4B819998}.Release|x64.ActiveCfg = Release|Any CPU + {53E4ABF0-1085-45F9-B964-DCAE4B819998}.Release|x64.Build.0 = Release|Any CPU + {53E4ABF0-1085-45F9-B964-DCAE4B819998}.Release|x86.ActiveCfg = Release|Any CPU + {53E4ABF0-1085-45F9-B964-DCAE4B819998}.Release|x86.Build.0 = Release|Any CPU + {507759AE-93DF-411B-8645-31F680319F5C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {507759AE-93DF-411B-8645-31F680319F5C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {507759AE-93DF-411B-8645-31F680319F5C}.Debug|x64.ActiveCfg = Debug|Any CPU + {507759AE-93DF-411B-8645-31F680319F5C}.Debug|x64.Build.0 = Debug|Any CPU + {507759AE-93DF-411B-8645-31F680319F5C}.Debug|x86.ActiveCfg = Debug|Any CPU + {507759AE-93DF-411B-8645-31F680319F5C}.Debug|x86.Build.0 = Debug|Any CPU + {507759AE-93DF-411B-8645-31F680319F5C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {507759AE-93DF-411B-8645-31F680319F5C}.Release|Any CPU.Build.0 = Release|Any CPU + {507759AE-93DF-411B-8645-31F680319F5C}.Release|x64.ActiveCfg = Release|Any CPU + {507759AE-93DF-411B-8645-31F680319F5C}.Release|x64.Build.0 = Release|Any CPU + {507759AE-93DF-411B-8645-31F680319F5C}.Release|x86.ActiveCfg = Release|Any CPU + {507759AE-93DF-411B-8645-31F680319F5C}.Release|x86.Build.0 = Release|Any CPU + {F9CD012E-67AC-4A4E-B2A7-252387F91256}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F9CD012E-67AC-4A4E-B2A7-252387F91256}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F9CD012E-67AC-4A4E-B2A7-252387F91256}.Debug|x64.ActiveCfg = Debug|Any CPU + {F9CD012E-67AC-4A4E-B2A7-252387F91256}.Debug|x64.Build.0 = Debug|Any CPU + {F9CD012E-67AC-4A4E-B2A7-252387F91256}.Debug|x86.ActiveCfg = Debug|Any CPU + {F9CD012E-67AC-4A4E-B2A7-252387F91256}.Debug|x86.Build.0 = Debug|Any CPU + {F9CD012E-67AC-4A4E-B2A7-252387F91256}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F9CD012E-67AC-4A4E-B2A7-252387F91256}.Release|Any CPU.Build.0 = Release|Any CPU + {F9CD012E-67AC-4A4E-B2A7-252387F91256}.Release|x64.ActiveCfg = Release|Any CPU + {F9CD012E-67AC-4A4E-B2A7-252387F91256}.Release|x64.Build.0 = Release|Any CPU + {F9CD012E-67AC-4A4E-B2A7-252387F91256}.Release|x86.ActiveCfg = Release|Any CPU + {F9CD012E-67AC-4A4E-B2A7-252387F91256}.Release|x86.Build.0 = Release|Any CPU + {C5A44686-3469-45A7-B6AB-2798BA0625BC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C5A44686-3469-45A7-B6AB-2798BA0625BC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C5A44686-3469-45A7-B6AB-2798BA0625BC}.Debug|x64.ActiveCfg = Debug|Any CPU + {C5A44686-3469-45A7-B6AB-2798BA0625BC}.Debug|x64.Build.0 = Debug|Any CPU + {C5A44686-3469-45A7-B6AB-2798BA0625BC}.Debug|x86.ActiveCfg = Debug|Any CPU + {C5A44686-3469-45A7-B6AB-2798BA0625BC}.Debug|x86.Build.0 = Debug|Any CPU + {C5A44686-3469-45A7-B6AB-2798BA0625BC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C5A44686-3469-45A7-B6AB-2798BA0625BC}.Release|Any CPU.Build.0 = Release|Any CPU + {C5A44686-3469-45A7-B6AB-2798BA0625BC}.Release|x64.ActiveCfg = Release|Any CPU + {C5A44686-3469-45A7-B6AB-2798BA0625BC}.Release|x64.Build.0 = Release|Any CPU + {C5A44686-3469-45A7-B6AB-2798BA0625BC}.Release|x86.ActiveCfg = Release|Any CPU + {C5A44686-3469-45A7-B6AB-2798BA0625BC}.Release|x86.Build.0 = Release|Any CPU + {A14CB0A1-7A05-4F27-88B2-383798CE1DEE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A14CB0A1-7A05-4F27-88B2-383798CE1DEE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A14CB0A1-7A05-4F27-88B2-383798CE1DEE}.Debug|x64.ActiveCfg = Debug|Any CPU + {A14CB0A1-7A05-4F27-88B2-383798CE1DEE}.Debug|x64.Build.0 = Debug|Any CPU + {A14CB0A1-7A05-4F27-88B2-383798CE1DEE}.Debug|x86.ActiveCfg = Debug|Any CPU + {A14CB0A1-7A05-4F27-88B2-383798CE1DEE}.Debug|x86.Build.0 = Debug|Any CPU + {A14CB0A1-7A05-4F27-88B2-383798CE1DEE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A14CB0A1-7A05-4F27-88B2-383798CE1DEE}.Release|Any CPU.Build.0 = Release|Any CPU + {A14CB0A1-7A05-4F27-88B2-383798CE1DEE}.Release|x64.ActiveCfg = Release|Any CPU + {A14CB0A1-7A05-4F27-88B2-383798CE1DEE}.Release|x64.Build.0 = Release|Any CPU + {A14CB0A1-7A05-4F27-88B2-383798CE1DEE}.Release|x86.ActiveCfg = Release|Any CPU + {A14CB0A1-7A05-4F27-88B2-383798CE1DEE}.Release|x86.Build.0 = Release|Any CPU + {2498F8A0-AA04-40EF-8691-59BBD2396B4D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2498F8A0-AA04-40EF-8691-59BBD2396B4D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2498F8A0-AA04-40EF-8691-59BBD2396B4D}.Debug|x64.ActiveCfg = Debug|Any CPU + {2498F8A0-AA04-40EF-8691-59BBD2396B4D}.Debug|x64.Build.0 = Debug|Any CPU + {2498F8A0-AA04-40EF-8691-59BBD2396B4D}.Debug|x86.ActiveCfg = Debug|Any CPU + {2498F8A0-AA04-40EF-8691-59BBD2396B4D}.Debug|x86.Build.0 = Debug|Any CPU + {2498F8A0-AA04-40EF-8691-59BBD2396B4D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2498F8A0-AA04-40EF-8691-59BBD2396B4D}.Release|Any CPU.Build.0 = Release|Any CPU + {2498F8A0-AA04-40EF-8691-59BBD2396B4D}.Release|x64.ActiveCfg = Release|Any CPU + {2498F8A0-AA04-40EF-8691-59BBD2396B4D}.Release|x64.Build.0 = Release|Any CPU + {2498F8A0-AA04-40EF-8691-59BBD2396B4D}.Release|x86.ActiveCfg = Release|Any CPU + {2498F8A0-AA04-40EF-8691-59BBD2396B4D}.Release|x86.Build.0 = Release|Any CPU + {38C762AB-8FD1-44DE-9855-26AAE7129DC3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {38C762AB-8FD1-44DE-9855-26AAE7129DC3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {38C762AB-8FD1-44DE-9855-26AAE7129DC3}.Debug|x64.ActiveCfg = Debug|Any CPU + {38C762AB-8FD1-44DE-9855-26AAE7129DC3}.Debug|x64.Build.0 = Debug|Any CPU + {38C762AB-8FD1-44DE-9855-26AAE7129DC3}.Debug|x86.ActiveCfg = Debug|Any CPU + {38C762AB-8FD1-44DE-9855-26AAE7129DC3}.Debug|x86.Build.0 = Debug|Any CPU + {38C762AB-8FD1-44DE-9855-26AAE7129DC3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {38C762AB-8FD1-44DE-9855-26AAE7129DC3}.Release|Any CPU.Build.0 = Release|Any CPU + {38C762AB-8FD1-44DE-9855-26AAE7129DC3}.Release|x64.ActiveCfg = Release|Any CPU + {38C762AB-8FD1-44DE-9855-26AAE7129DC3}.Release|x64.Build.0 = Release|Any CPU + {38C762AB-8FD1-44DE-9855-26AAE7129DC3}.Release|x86.ActiveCfg = Release|Any CPU + {38C762AB-8FD1-44DE-9855-26AAE7129DC3}.Release|x86.Build.0 = Release|Any CPU + {7387E151-48E3-4885-B2CA-A74434A34045}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7387E151-48E3-4885-B2CA-A74434A34045}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7387E151-48E3-4885-B2CA-A74434A34045}.Debug|x64.ActiveCfg = Debug|Any CPU + {7387E151-48E3-4885-B2CA-A74434A34045}.Debug|x64.Build.0 = Debug|Any CPU + {7387E151-48E3-4885-B2CA-A74434A34045}.Debug|x86.ActiveCfg = Debug|Any CPU + {7387E151-48E3-4885-B2CA-A74434A34045}.Debug|x86.Build.0 = Debug|Any CPU + {7387E151-48E3-4885-B2CA-A74434A34045}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7387E151-48E3-4885-B2CA-A74434A34045}.Release|Any CPU.Build.0 = Release|Any CPU + {7387E151-48E3-4885-B2CA-A74434A34045}.Release|x64.ActiveCfg = Release|Any CPU + {7387E151-48E3-4885-B2CA-A74434A34045}.Release|x64.Build.0 = Release|Any CPU + {7387E151-48E3-4885-B2CA-A74434A34045}.Release|x86.ActiveCfg = Release|Any CPU + {7387E151-48E3-4885-B2CA-A74434A34045}.Release|x86.Build.0 = Release|Any CPU + {8A6CB07C-E493-4A4F-AB94-038645A27118}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8A6CB07C-E493-4A4F-AB94-038645A27118}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8A6CB07C-E493-4A4F-AB94-038645A27118}.Debug|x64.ActiveCfg = Debug|Any CPU + {8A6CB07C-E493-4A4F-AB94-038645A27118}.Debug|x64.Build.0 = Debug|Any CPU + {8A6CB07C-E493-4A4F-AB94-038645A27118}.Debug|x86.ActiveCfg = Debug|Any CPU + {8A6CB07C-E493-4A4F-AB94-038645A27118}.Debug|x86.Build.0 = Debug|Any CPU + {8A6CB07C-E493-4A4F-AB94-038645A27118}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8A6CB07C-E493-4A4F-AB94-038645A27118}.Release|Any CPU.Build.0 = Release|Any CPU + {8A6CB07C-E493-4A4F-AB94-038645A27118}.Release|x64.ActiveCfg = Release|Any CPU + {8A6CB07C-E493-4A4F-AB94-038645A27118}.Release|x64.Build.0 = Release|Any CPU + {8A6CB07C-E493-4A4F-AB94-038645A27118}.Release|x86.ActiveCfg = Release|Any CPU + {8A6CB07C-E493-4A4F-AB94-038645A27118}.Release|x86.Build.0 = Release|Any CPU + {66CF299A-CE95-4131-BCD8-DB66E30C4BF7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {66CF299A-CE95-4131-BCD8-DB66E30C4BF7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {66CF299A-CE95-4131-BCD8-DB66E30C4BF7}.Debug|x64.ActiveCfg = Debug|Any CPU + {66CF299A-CE95-4131-BCD8-DB66E30C4BF7}.Debug|x64.Build.0 = Debug|Any CPU + {66CF299A-CE95-4131-BCD8-DB66E30C4BF7}.Debug|x86.ActiveCfg = Debug|Any CPU + {66CF299A-CE95-4131-BCD8-DB66E30C4BF7}.Debug|x86.Build.0 = Debug|Any CPU + {66CF299A-CE95-4131-BCD8-DB66E30C4BF7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {66CF299A-CE95-4131-BCD8-DB66E30C4BF7}.Release|Any CPU.Build.0 = Release|Any CPU + {66CF299A-CE95-4131-BCD8-DB66E30C4BF7}.Release|x64.ActiveCfg = Release|Any CPU + {66CF299A-CE95-4131-BCD8-DB66E30C4BF7}.Release|x64.Build.0 = Release|Any CPU + {66CF299A-CE95-4131-BCD8-DB66E30C4BF7}.Release|x86.ActiveCfg = Release|Any CPU + {66CF299A-CE95-4131-BCD8-DB66E30C4BF7}.Release|x86.Build.0 = Release|Any CPU + {B8105878-D423-4159-A3E7-028298281EC6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B8105878-D423-4159-A3E7-028298281EC6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B8105878-D423-4159-A3E7-028298281EC6}.Debug|x64.ActiveCfg = Debug|Any CPU + {B8105878-D423-4159-A3E7-028298281EC6}.Debug|x64.Build.0 = Debug|Any CPU + {B8105878-D423-4159-A3E7-028298281EC6}.Debug|x86.ActiveCfg = Debug|Any CPU + {B8105878-D423-4159-A3E7-028298281EC6}.Debug|x86.Build.0 = Debug|Any CPU + {B8105878-D423-4159-A3E7-028298281EC6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B8105878-D423-4159-A3E7-028298281EC6}.Release|Any CPU.Build.0 = Release|Any CPU + {B8105878-D423-4159-A3E7-028298281EC6}.Release|x64.ActiveCfg = Release|Any CPU + {B8105878-D423-4159-A3E7-028298281EC6}.Release|x64.Build.0 = Release|Any CPU + {B8105878-D423-4159-A3E7-028298281EC6}.Release|x86.ActiveCfg = Release|Any CPU + {B8105878-D423-4159-A3E7-028298281EC6}.Release|x86.Build.0 = Release|Any CPU + {43564FB3-0F79-4FF4-A2B0-B1637072FF01}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {43564FB3-0F79-4FF4-A2B0-B1637072FF01}.Debug|Any CPU.Build.0 = Debug|Any CPU + {43564FB3-0F79-4FF4-A2B0-B1637072FF01}.Debug|x64.ActiveCfg = Debug|Any CPU + {43564FB3-0F79-4FF4-A2B0-B1637072FF01}.Debug|x64.Build.0 = Debug|Any CPU + {43564FB3-0F79-4FF4-A2B0-B1637072FF01}.Debug|x86.ActiveCfg = Debug|Any CPU + {43564FB3-0F79-4FF4-A2B0-B1637072FF01}.Debug|x86.Build.0 = Debug|Any CPU + {43564FB3-0F79-4FF4-A2B0-B1637072FF01}.Release|Any CPU.ActiveCfg = Release|Any CPU + {43564FB3-0F79-4FF4-A2B0-B1637072FF01}.Release|Any CPU.Build.0 = Release|Any CPU + {43564FB3-0F79-4FF4-A2B0-B1637072FF01}.Release|x64.ActiveCfg = Release|Any CPU + {43564FB3-0F79-4FF4-A2B0-B1637072FF01}.Release|x64.Build.0 = Release|Any CPU + {43564FB3-0F79-4FF4-A2B0-B1637072FF01}.Release|x86.ActiveCfg = Release|Any CPU + {43564FB3-0F79-4FF4-A2B0-B1637072FF01}.Release|x86.Build.0 = Release|Any CPU + {3DE17662-DCD6-4F49-AF06-D39AACC8649A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3DE17662-DCD6-4F49-AF06-D39AACC8649A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3DE17662-DCD6-4F49-AF06-D39AACC8649A}.Debug|x64.ActiveCfg = Debug|Any CPU + {3DE17662-DCD6-4F49-AF06-D39AACC8649A}.Debug|x64.Build.0 = Debug|Any CPU + {3DE17662-DCD6-4F49-AF06-D39AACC8649A}.Debug|x86.ActiveCfg = Debug|Any CPU + {3DE17662-DCD6-4F49-AF06-D39AACC8649A}.Debug|x86.Build.0 = Debug|Any CPU + {3DE17662-DCD6-4F49-AF06-D39AACC8649A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3DE17662-DCD6-4F49-AF06-D39AACC8649A}.Release|Any CPU.Build.0 = Release|Any CPU + {3DE17662-DCD6-4F49-AF06-D39AACC8649A}.Release|x64.ActiveCfg = Release|Any CPU + {3DE17662-DCD6-4F49-AF06-D39AACC8649A}.Release|x64.Build.0 = Release|Any CPU + {3DE17662-DCD6-4F49-AF06-D39AACC8649A}.Release|x86.ActiveCfg = Release|Any CPU + {3DE17662-DCD6-4F49-AF06-D39AACC8649A}.Release|x86.Build.0 = Release|Any CPU + {E34BCFA0-CAA4-412C-AA1C-75DB8D67D157}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E34BCFA0-CAA4-412C-AA1C-75DB8D67D157}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E34BCFA0-CAA4-412C-AA1C-75DB8D67D157}.Debug|x64.ActiveCfg = Debug|Any CPU + {E34BCFA0-CAA4-412C-AA1C-75DB8D67D157}.Debug|x64.Build.0 = Debug|Any CPU + {E34BCFA0-CAA4-412C-AA1C-75DB8D67D157}.Debug|x86.ActiveCfg = Debug|Any CPU + {E34BCFA0-CAA4-412C-AA1C-75DB8D67D157}.Debug|x86.Build.0 = Debug|Any CPU + {E34BCFA0-CAA4-412C-AA1C-75DB8D67D157}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E34BCFA0-CAA4-412C-AA1C-75DB8D67D157}.Release|Any CPU.Build.0 = Release|Any CPU + {E34BCFA0-CAA4-412C-AA1C-75DB8D67D157}.Release|x64.ActiveCfg = Release|Any CPU + {E34BCFA0-CAA4-412C-AA1C-75DB8D67D157}.Release|x64.Build.0 = Release|Any CPU + {E34BCFA0-CAA4-412C-AA1C-75DB8D67D157}.Release|x86.ActiveCfg = Release|Any CPU + {E34BCFA0-CAA4-412C-AA1C-75DB8D67D157}.Release|x86.Build.0 = Release|Any CPU + {1E5501E8-49C1-4659-838D-CC9720C5208F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1E5501E8-49C1-4659-838D-CC9720C5208F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1E5501E8-49C1-4659-838D-CC9720C5208F}.Debug|x64.ActiveCfg = Debug|Any CPU + {1E5501E8-49C1-4659-838D-CC9720C5208F}.Debug|x64.Build.0 = Debug|Any CPU + {1E5501E8-49C1-4659-838D-CC9720C5208F}.Debug|x86.ActiveCfg = Debug|Any CPU + {1E5501E8-49C1-4659-838D-CC9720C5208F}.Debug|x86.Build.0 = Debug|Any CPU + {1E5501E8-49C1-4659-838D-CC9720C5208F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1E5501E8-49C1-4659-838D-CC9720C5208F}.Release|Any CPU.Build.0 = Release|Any CPU + {1E5501E8-49C1-4659-838D-CC9720C5208F}.Release|x64.ActiveCfg = Release|Any CPU + {1E5501E8-49C1-4659-838D-CC9720C5208F}.Release|x64.Build.0 = Release|Any CPU + {1E5501E8-49C1-4659-838D-CC9720C5208F}.Release|x86.ActiveCfg = Release|Any CPU + {1E5501E8-49C1-4659-838D-CC9720C5208F}.Release|x86.Build.0 = Release|Any CPU + {1BAFA0CC-0377-46CE-AB7B-7BB2E7B62F63}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1BAFA0CC-0377-46CE-AB7B-7BB2E7B62F63}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1BAFA0CC-0377-46CE-AB7B-7BB2E7B62F63}.Debug|x64.ActiveCfg = Debug|Any CPU + {1BAFA0CC-0377-46CE-AB7B-7BB2E7B62F63}.Debug|x64.Build.0 = Debug|Any CPU + {1BAFA0CC-0377-46CE-AB7B-7BB2E7B62F63}.Debug|x86.ActiveCfg = Debug|Any CPU + {1BAFA0CC-0377-46CE-AB7B-7BB2E7B62F63}.Debug|x86.Build.0 = Debug|Any CPU + {1BAFA0CC-0377-46CE-AB7B-7BB2E7B62F63}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1BAFA0CC-0377-46CE-AB7B-7BB2E7B62F63}.Release|Any CPU.Build.0 = Release|Any CPU + {1BAFA0CC-0377-46CE-AB7B-7BB2E7B62F63}.Release|x64.ActiveCfg = Release|Any CPU + {1BAFA0CC-0377-46CE-AB7B-7BB2E7B62F63}.Release|x64.Build.0 = Release|Any CPU + {1BAFA0CC-0377-46CE-AB7B-7BB2E7B62F63}.Release|x86.ActiveCfg = Release|Any CPU + {1BAFA0CC-0377-46CE-AB7B-7BB2E7B62F63}.Release|x86.Build.0 = Release|Any CPU + {AF8AC493-40AC-4195-82F6-B08EE4B4E49E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AF8AC493-40AC-4195-82F6-B08EE4B4E49E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AF8AC493-40AC-4195-82F6-B08EE4B4E49E}.Debug|x64.ActiveCfg = Debug|Any CPU + {AF8AC493-40AC-4195-82F6-B08EE4B4E49E}.Debug|x64.Build.0 = Debug|Any CPU + {AF8AC493-40AC-4195-82F6-B08EE4B4E49E}.Debug|x86.ActiveCfg = Debug|Any CPU + {AF8AC493-40AC-4195-82F6-B08EE4B4E49E}.Debug|x86.Build.0 = Debug|Any CPU + {AF8AC493-40AC-4195-82F6-B08EE4B4E49E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AF8AC493-40AC-4195-82F6-B08EE4B4E49E}.Release|Any CPU.Build.0 = Release|Any CPU + {AF8AC493-40AC-4195-82F6-B08EE4B4E49E}.Release|x64.ActiveCfg = Release|Any CPU + {AF8AC493-40AC-4195-82F6-B08EE4B4E49E}.Release|x64.Build.0 = Release|Any CPU + {AF8AC493-40AC-4195-82F6-B08EE4B4E49E}.Release|x86.ActiveCfg = Release|Any CPU + {AF8AC493-40AC-4195-82F6-B08EE4B4E49E}.Release|x86.Build.0 = Release|Any CPU + {0C31DE30-F9DF-4312-BFFE-DCAD558CCF08}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0C31DE30-F9DF-4312-BFFE-DCAD558CCF08}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0C31DE30-F9DF-4312-BFFE-DCAD558CCF08}.Debug|x64.ActiveCfg = Debug|Any CPU + {0C31DE30-F9DF-4312-BFFE-DCAD558CCF08}.Debug|x64.Build.0 = Debug|Any CPU + {0C31DE30-F9DF-4312-BFFE-DCAD558CCF08}.Debug|x86.ActiveCfg = Debug|Any CPU + {0C31DE30-F9DF-4312-BFFE-DCAD558CCF08}.Debug|x86.Build.0 = Debug|Any CPU + {0C31DE30-F9DF-4312-BFFE-DCAD558CCF08}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0C31DE30-F9DF-4312-BFFE-DCAD558CCF08}.Release|Any CPU.Build.0 = Release|Any CPU + {0C31DE30-F9DF-4312-BFFE-DCAD558CCF08}.Release|x64.ActiveCfg = Release|Any CPU + {0C31DE30-F9DF-4312-BFFE-DCAD558CCF08}.Release|x64.Build.0 = Release|Any CPU + {0C31DE30-F9DF-4312-BFFE-DCAD558CCF08}.Release|x86.ActiveCfg = Release|Any CPU + {0C31DE30-F9DF-4312-BFFE-DCAD558CCF08}.Release|x86.Build.0 = Release|Any CPU + {A0AEF446-3368-4591-9DE6-BC3B2B33337D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A0AEF446-3368-4591-9DE6-BC3B2B33337D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A0AEF446-3368-4591-9DE6-BC3B2B33337D}.Debug|x64.ActiveCfg = Debug|Any CPU + {A0AEF446-3368-4591-9DE6-BC3B2B33337D}.Debug|x64.Build.0 = Debug|Any CPU + {A0AEF446-3368-4591-9DE6-BC3B2B33337D}.Debug|x86.ActiveCfg = Debug|Any CPU + {A0AEF446-3368-4591-9DE6-BC3B2B33337D}.Debug|x86.Build.0 = Debug|Any CPU + {A0AEF446-3368-4591-9DE6-BC3B2B33337D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A0AEF446-3368-4591-9DE6-BC3B2B33337D}.Release|Any CPU.Build.0 = Release|Any CPU + {A0AEF446-3368-4591-9DE6-BC3B2B33337D}.Release|x64.ActiveCfg = Release|Any CPU + {A0AEF446-3368-4591-9DE6-BC3B2B33337D}.Release|x64.Build.0 = Release|Any CPU + {A0AEF446-3368-4591-9DE6-BC3B2B33337D}.Release|x86.ActiveCfg = Release|Any CPU + {A0AEF446-3368-4591-9DE6-BC3B2B33337D}.Release|x86.Build.0 = Release|Any CPU + {B2BC20D1-F468-46A9-B9B0-1C80CC4D4F36}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B2BC20D1-F468-46A9-B9B0-1C80CC4D4F36}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B2BC20D1-F468-46A9-B9B0-1C80CC4D4F36}.Debug|x64.ActiveCfg = Debug|Any CPU + {B2BC20D1-F468-46A9-B9B0-1C80CC4D4F36}.Debug|x64.Build.0 = Debug|Any CPU + {B2BC20D1-F468-46A9-B9B0-1C80CC4D4F36}.Debug|x86.ActiveCfg = Debug|Any CPU + {B2BC20D1-F468-46A9-B9B0-1C80CC4D4F36}.Debug|x86.Build.0 = Debug|Any CPU + {B2BC20D1-F468-46A9-B9B0-1C80CC4D4F36}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B2BC20D1-F468-46A9-B9B0-1C80CC4D4F36}.Release|Any CPU.Build.0 = Release|Any CPU + {B2BC20D1-F468-46A9-B9B0-1C80CC4D4F36}.Release|x64.ActiveCfg = Release|Any CPU + {B2BC20D1-F468-46A9-B9B0-1C80CC4D4F36}.Release|x64.Build.0 = Release|Any CPU + {B2BC20D1-F468-46A9-B9B0-1C80CC4D4F36}.Release|x86.ActiveCfg = Release|Any CPU + {B2BC20D1-F468-46A9-B9B0-1C80CC4D4F36}.Release|x86.Build.0 = Release|Any CPU + {7A180B05-DE3F-4D89-9F40-03A8E186AE82}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7A180B05-DE3F-4D89-9F40-03A8E186AE82}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7A180B05-DE3F-4D89-9F40-03A8E186AE82}.Debug|x64.ActiveCfg = Debug|Any CPU + {7A180B05-DE3F-4D89-9F40-03A8E186AE82}.Debug|x64.Build.0 = Debug|Any CPU + {7A180B05-DE3F-4D89-9F40-03A8E186AE82}.Debug|x86.ActiveCfg = Debug|Any CPU + {7A180B05-DE3F-4D89-9F40-03A8E186AE82}.Debug|x86.Build.0 = Debug|Any CPU + {7A180B05-DE3F-4D89-9F40-03A8E186AE82}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7A180B05-DE3F-4D89-9F40-03A8E186AE82}.Release|Any CPU.Build.0 = Release|Any CPU + {7A180B05-DE3F-4D89-9F40-03A8E186AE82}.Release|x64.ActiveCfg = Release|Any CPU + {7A180B05-DE3F-4D89-9F40-03A8E186AE82}.Release|x64.Build.0 = Release|Any CPU + {7A180B05-DE3F-4D89-9F40-03A8E186AE82}.Release|x86.ActiveCfg = Release|Any CPU + {7A180B05-DE3F-4D89-9F40-03A8E186AE82}.Release|x86.Build.0 = Release|Any CPU + {3E45D81B-4C4A-4E3F-B891-A0D8A3E2F0C1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3E45D81B-4C4A-4E3F-B891-A0D8A3E2F0C1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3E45D81B-4C4A-4E3F-B891-A0D8A3E2F0C1}.Debug|x64.ActiveCfg = Debug|Any CPU + {3E45D81B-4C4A-4E3F-B891-A0D8A3E2F0C1}.Debug|x64.Build.0 = Debug|Any CPU + {3E45D81B-4C4A-4E3F-B891-A0D8A3E2F0C1}.Debug|x86.ActiveCfg = Debug|Any CPU + {3E45D81B-4C4A-4E3F-B891-A0D8A3E2F0C1}.Debug|x86.Build.0 = Debug|Any CPU + {3E45D81B-4C4A-4E3F-B891-A0D8A3E2F0C1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3E45D81B-4C4A-4E3F-B891-A0D8A3E2F0C1}.Release|Any CPU.Build.0 = Release|Any CPU + {3E45D81B-4C4A-4E3F-B891-A0D8A3E2F0C1}.Release|x64.ActiveCfg = Release|Any CPU + {3E45D81B-4C4A-4E3F-B891-A0D8A3E2F0C1}.Release|x64.Build.0 = Release|Any CPU + {3E45D81B-4C4A-4E3F-B891-A0D8A3E2F0C1}.Release|x86.ActiveCfg = Release|Any CPU + {3E45D81B-4C4A-4E3F-B891-A0D8A3E2F0C1}.Release|x86.Build.0 = Release|Any CPU + {F3CFF31C-037B-450F-B22D-1D6E529B2DCC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F3CFF31C-037B-450F-B22D-1D6E529B2DCC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F3CFF31C-037B-450F-B22D-1D6E529B2DCC}.Debug|x64.ActiveCfg = Debug|Any CPU + {F3CFF31C-037B-450F-B22D-1D6E529B2DCC}.Debug|x64.Build.0 = Debug|Any CPU + {F3CFF31C-037B-450F-B22D-1D6E529B2DCC}.Debug|x86.ActiveCfg = Debug|Any CPU + {F3CFF31C-037B-450F-B22D-1D6E529B2DCC}.Debug|x86.Build.0 = Debug|Any CPU + {F3CFF31C-037B-450F-B22D-1D6E529B2DCC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F3CFF31C-037B-450F-B22D-1D6E529B2DCC}.Release|Any CPU.Build.0 = Release|Any CPU + {F3CFF31C-037B-450F-B22D-1D6E529B2DCC}.Release|x64.ActiveCfg = Release|Any CPU + {F3CFF31C-037B-450F-B22D-1D6E529B2DCC}.Release|x64.Build.0 = Release|Any CPU + {F3CFF31C-037B-450F-B22D-1D6E529B2DCC}.Release|x86.ActiveCfg = Release|Any CPU + {F3CFF31C-037B-450F-B22D-1D6E529B2DCC}.Release|x86.Build.0 = Release|Any CPU + {16DB2680-399B-4111-AA26-6CDBBFA334D8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {16DB2680-399B-4111-AA26-6CDBBFA334D8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {16DB2680-399B-4111-AA26-6CDBBFA334D8}.Debug|x64.ActiveCfg = Debug|Any CPU + {16DB2680-399B-4111-AA26-6CDBBFA334D8}.Debug|x64.Build.0 = Debug|Any CPU + {16DB2680-399B-4111-AA26-6CDBBFA334D8}.Debug|x86.ActiveCfg = Debug|Any CPU + {16DB2680-399B-4111-AA26-6CDBBFA334D8}.Debug|x86.Build.0 = Debug|Any CPU + {16DB2680-399B-4111-AA26-6CDBBFA334D8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {16DB2680-399B-4111-AA26-6CDBBFA334D8}.Release|Any CPU.Build.0 = Release|Any CPU + {16DB2680-399B-4111-AA26-6CDBBFA334D8}.Release|x64.ActiveCfg = Release|Any CPU + {16DB2680-399B-4111-AA26-6CDBBFA334D8}.Release|x64.Build.0 = Release|Any CPU + {16DB2680-399B-4111-AA26-6CDBBFA334D8}.Release|x86.ActiveCfg = Release|Any CPU + {16DB2680-399B-4111-AA26-6CDBBFA334D8}.Release|x86.Build.0 = Release|Any CPU + {372E8E3E-29D5-4B4D-88A2-4711CD628C4E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {372E8E3E-29D5-4B4D-88A2-4711CD628C4E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {372E8E3E-29D5-4B4D-88A2-4711CD628C4E}.Debug|x64.ActiveCfg = Debug|Any CPU + {372E8E3E-29D5-4B4D-88A2-4711CD628C4E}.Debug|x64.Build.0 = Debug|Any CPU + {372E8E3E-29D5-4B4D-88A2-4711CD628C4E}.Debug|x86.ActiveCfg = Debug|Any CPU + {372E8E3E-29D5-4B4D-88A2-4711CD628C4E}.Debug|x86.Build.0 = Debug|Any CPU + {372E8E3E-29D5-4B4D-88A2-4711CD628C4E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {372E8E3E-29D5-4B4D-88A2-4711CD628C4E}.Release|Any CPU.Build.0 = Release|Any CPU + {372E8E3E-29D5-4B4D-88A2-4711CD628C4E}.Release|x64.ActiveCfg = Release|Any CPU + {372E8E3E-29D5-4B4D-88A2-4711CD628C4E}.Release|x64.Build.0 = Release|Any CPU + {372E8E3E-29D5-4B4D-88A2-4711CD628C4E}.Release|x86.ActiveCfg = Release|Any CPU + {372E8E3E-29D5-4B4D-88A2-4711CD628C4E}.Release|x86.Build.0 = Release|Any CPU + {90C99ADB-7D4B-4EB4-98C2-40BD1B14C7D2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {90C99ADB-7D4B-4EB4-98C2-40BD1B14C7D2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {90C99ADB-7D4B-4EB4-98C2-40BD1B14C7D2}.Debug|x64.ActiveCfg = Debug|Any CPU + {90C99ADB-7D4B-4EB4-98C2-40BD1B14C7D2}.Debug|x64.Build.0 = Debug|Any CPU + {90C99ADB-7D4B-4EB4-98C2-40BD1B14C7D2}.Debug|x86.ActiveCfg = Debug|Any CPU + {90C99ADB-7D4B-4EB4-98C2-40BD1B14C7D2}.Debug|x86.Build.0 = Debug|Any CPU + {90C99ADB-7D4B-4EB4-98C2-40BD1B14C7D2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {90C99ADB-7D4B-4EB4-98C2-40BD1B14C7D2}.Release|Any CPU.Build.0 = Release|Any CPU + {90C99ADB-7D4B-4EB4-98C2-40BD1B14C7D2}.Release|x64.ActiveCfg = Release|Any CPU + {90C99ADB-7D4B-4EB4-98C2-40BD1B14C7D2}.Release|x64.Build.0 = Release|Any CPU + {90C99ADB-7D4B-4EB4-98C2-40BD1B14C7D2}.Release|x86.ActiveCfg = Release|Any CPU + {90C99ADB-7D4B-4EB4-98C2-40BD1B14C7D2}.Release|x86.Build.0 = Release|Any CPU + {DE40756E-57F6-4AF2-B155-55E3A88CCED8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DE40756E-57F6-4AF2-B155-55E3A88CCED8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DE40756E-57F6-4AF2-B155-55E3A88CCED8}.Debug|x64.ActiveCfg = Debug|Any CPU + {DE40756E-57F6-4AF2-B155-55E3A88CCED8}.Debug|x64.Build.0 = Debug|Any CPU + {DE40756E-57F6-4AF2-B155-55E3A88CCED8}.Debug|x86.ActiveCfg = Debug|Any CPU + {DE40756E-57F6-4AF2-B155-55E3A88CCED8}.Debug|x86.Build.0 = Debug|Any CPU + {DE40756E-57F6-4AF2-B155-55E3A88CCED8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DE40756E-57F6-4AF2-B155-55E3A88CCED8}.Release|Any CPU.Build.0 = Release|Any CPU + {DE40756E-57F6-4AF2-B155-55E3A88CCED8}.Release|x64.ActiveCfg = Release|Any CPU + {DE40756E-57F6-4AF2-B155-55E3A88CCED8}.Release|x64.Build.0 = Release|Any CPU + {DE40756E-57F6-4AF2-B155-55E3A88CCED8}.Release|x86.ActiveCfg = Release|Any CPU + {DE40756E-57F6-4AF2-B155-55E3A88CCED8}.Release|x86.Build.0 = Release|Any CPU + {6410DA0F-5E14-4FC0-9AEE-F4C542C96C7A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6410DA0F-5E14-4FC0-9AEE-F4C542C96C7A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6410DA0F-5E14-4FC0-9AEE-F4C542C96C7A}.Debug|x64.ActiveCfg = Debug|Any CPU + {6410DA0F-5E14-4FC0-9AEE-F4C542C96C7A}.Debug|x64.Build.0 = Debug|Any CPU + {6410DA0F-5E14-4FC0-9AEE-F4C542C96C7A}.Debug|x86.ActiveCfg = Debug|Any CPU + {6410DA0F-5E14-4FC0-9AEE-F4C542C96C7A}.Debug|x86.Build.0 = Debug|Any CPU + {6410DA0F-5E14-4FC0-9AEE-F4C542C96C7A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6410DA0F-5E14-4FC0-9AEE-F4C542C96C7A}.Release|Any CPU.Build.0 = Release|Any CPU + {6410DA0F-5E14-4FC0-9AEE-F4C542C96C7A}.Release|x64.ActiveCfg = Release|Any CPU + {6410DA0F-5E14-4FC0-9AEE-F4C542C96C7A}.Release|x64.Build.0 = Release|Any CPU + {6410DA0F-5E14-4FC0-9AEE-F4C542C96C7A}.Release|x86.ActiveCfg = Release|Any CPU + {6410DA0F-5E14-4FC0-9AEE-F4C542C96C7A}.Release|x86.Build.0 = Release|Any CPU + {D28957BF-5E66-4D60-B528-22820C60AC82}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D28957BF-5E66-4D60-B528-22820C60AC82}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D28957BF-5E66-4D60-B528-22820C60AC82}.Debug|x64.ActiveCfg = Debug|Any CPU + {D28957BF-5E66-4D60-B528-22820C60AC82}.Debug|x64.Build.0 = Debug|Any CPU + {D28957BF-5E66-4D60-B528-22820C60AC82}.Debug|x86.ActiveCfg = Debug|Any CPU + {D28957BF-5E66-4D60-B528-22820C60AC82}.Debug|x86.Build.0 = Debug|Any CPU + {D28957BF-5E66-4D60-B528-22820C60AC82}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D28957BF-5E66-4D60-B528-22820C60AC82}.Release|Any CPU.Build.0 = Release|Any CPU + {D28957BF-5E66-4D60-B528-22820C60AC82}.Release|x64.ActiveCfg = Release|Any CPU + {D28957BF-5E66-4D60-B528-22820C60AC82}.Release|x64.Build.0 = Release|Any CPU + {D28957BF-5E66-4D60-B528-22820C60AC82}.Release|x86.ActiveCfg = Release|Any CPU + {D28957BF-5E66-4D60-B528-22820C60AC82}.Release|x86.Build.0 = Release|Any CPU + {1FED3F23-1175-42AA-BE87-EF1E8DB52F8B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1FED3F23-1175-42AA-BE87-EF1E8DB52F8B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1FED3F23-1175-42AA-BE87-EF1E8DB52F8B}.Debug|x64.ActiveCfg = Debug|Any CPU + {1FED3F23-1175-42AA-BE87-EF1E8DB52F8B}.Debug|x64.Build.0 = Debug|Any CPU + {1FED3F23-1175-42AA-BE87-EF1E8DB52F8B}.Debug|x86.ActiveCfg = Debug|Any CPU + {1FED3F23-1175-42AA-BE87-EF1E8DB52F8B}.Debug|x86.Build.0 = Debug|Any CPU + {1FED3F23-1175-42AA-BE87-EF1E8DB52F8B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1FED3F23-1175-42AA-BE87-EF1E8DB52F8B}.Release|Any CPU.Build.0 = Release|Any CPU + {1FED3F23-1175-42AA-BE87-EF1E8DB52F8B}.Release|x64.ActiveCfg = Release|Any CPU + {1FED3F23-1175-42AA-BE87-EF1E8DB52F8B}.Release|x64.Build.0 = Release|Any CPU + {1FED3F23-1175-42AA-BE87-EF1E8DB52F8B}.Release|x86.ActiveCfg = Release|Any CPU + {1FED3F23-1175-42AA-BE87-EF1E8DB52F8B}.Release|x86.Build.0 = Release|Any CPU + {2CE4CD4B-B7B7-4EAE-A9BE-2699824D6096}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2CE4CD4B-B7B7-4EAE-A9BE-2699824D6096}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2CE4CD4B-B7B7-4EAE-A9BE-2699824D6096}.Debug|x64.ActiveCfg = Debug|Any CPU + {2CE4CD4B-B7B7-4EAE-A9BE-2699824D6096}.Debug|x64.Build.0 = Debug|Any CPU + {2CE4CD4B-B7B7-4EAE-A9BE-2699824D6096}.Debug|x86.ActiveCfg = Debug|Any CPU + {2CE4CD4B-B7B7-4EAE-A9BE-2699824D6096}.Debug|x86.Build.0 = Debug|Any CPU + {2CE4CD4B-B7B7-4EAE-A9BE-2699824D6096}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2CE4CD4B-B7B7-4EAE-A9BE-2699824D6096}.Release|Any CPU.Build.0 = Release|Any CPU + {2CE4CD4B-B7B7-4EAE-A9BE-2699824D6096}.Release|x64.ActiveCfg = Release|Any CPU + {2CE4CD4B-B7B7-4EAE-A9BE-2699824D6096}.Release|x64.Build.0 = Release|Any CPU + {2CE4CD4B-B7B7-4EAE-A9BE-2699824D6096}.Release|x86.ActiveCfg = Release|Any CPU + {2CE4CD4B-B7B7-4EAE-A9BE-2699824D6096}.Release|x86.Build.0 = Release|Any CPU + {86A8DEFE-7ABB-4097-9389-C249581E243D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {86A8DEFE-7ABB-4097-9389-C249581E243D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {86A8DEFE-7ABB-4097-9389-C249581E243D}.Debug|x64.ActiveCfg = Debug|Any CPU + {86A8DEFE-7ABB-4097-9389-C249581E243D}.Debug|x64.Build.0 = Debug|Any CPU + {86A8DEFE-7ABB-4097-9389-C249581E243D}.Debug|x86.ActiveCfg = Debug|Any CPU + {86A8DEFE-7ABB-4097-9389-C249581E243D}.Debug|x86.Build.0 = Debug|Any CPU + {86A8DEFE-7ABB-4097-9389-C249581E243D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {86A8DEFE-7ABB-4097-9389-C249581E243D}.Release|Any CPU.Build.0 = Release|Any CPU + {86A8DEFE-7ABB-4097-9389-C249581E243D}.Release|x64.ActiveCfg = Release|Any CPU + {86A8DEFE-7ABB-4097-9389-C249581E243D}.Release|x64.Build.0 = Release|Any CPU + {86A8DEFE-7ABB-4097-9389-C249581E243D}.Release|x86.ActiveCfg = Release|Any CPU + {86A8DEFE-7ABB-4097-9389-C249581E243D}.Release|x86.Build.0 = Release|Any CPU + {37FCD325-1077-4603-98E7-4509CAD648D6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {37FCD325-1077-4603-98E7-4509CAD648D6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {37FCD325-1077-4603-98E7-4509CAD648D6}.Debug|x64.ActiveCfg = Debug|Any CPU + {37FCD325-1077-4603-98E7-4509CAD648D6}.Debug|x64.Build.0 = Debug|Any CPU + {37FCD325-1077-4603-98E7-4509CAD648D6}.Debug|x86.ActiveCfg = Debug|Any CPU + {37FCD325-1077-4603-98E7-4509CAD648D6}.Debug|x86.Build.0 = Debug|Any CPU + {37FCD325-1077-4603-98E7-4509CAD648D6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {37FCD325-1077-4603-98E7-4509CAD648D6}.Release|Any CPU.Build.0 = Release|Any CPU + {37FCD325-1077-4603-98E7-4509CAD648D6}.Release|x64.ActiveCfg = Release|Any CPU + {37FCD325-1077-4603-98E7-4509CAD648D6}.Release|x64.Build.0 = Release|Any CPU + {37FCD325-1077-4603-98E7-4509CAD648D6}.Release|x86.ActiveCfg = Release|Any CPU + {37FCD325-1077-4603-98E7-4509CAD648D6}.Release|x86.Build.0 = Release|Any CPU + {88B746FF-8D6E-464D-9D66-FF2ECCF148E0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {88B746FF-8D6E-464D-9D66-FF2ECCF148E0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {88B746FF-8D6E-464D-9D66-FF2ECCF148E0}.Debug|x64.ActiveCfg = Debug|Any CPU + {88B746FF-8D6E-464D-9D66-FF2ECCF148E0}.Debug|x64.Build.0 = Debug|Any CPU + {88B746FF-8D6E-464D-9D66-FF2ECCF148E0}.Debug|x86.ActiveCfg = Debug|Any CPU + {88B746FF-8D6E-464D-9D66-FF2ECCF148E0}.Debug|x86.Build.0 = Debug|Any CPU + {88B746FF-8D6E-464D-9D66-FF2ECCF148E0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {88B746FF-8D6E-464D-9D66-FF2ECCF148E0}.Release|Any CPU.Build.0 = Release|Any CPU + {88B746FF-8D6E-464D-9D66-FF2ECCF148E0}.Release|x64.ActiveCfg = Release|Any CPU + {88B746FF-8D6E-464D-9D66-FF2ECCF148E0}.Release|x64.Build.0 = Release|Any CPU + {88B746FF-8D6E-464D-9D66-FF2ECCF148E0}.Release|x86.ActiveCfg = Release|Any CPU + {88B746FF-8D6E-464D-9D66-FF2ECCF148E0}.Release|x86.Build.0 = Release|Any CPU + {1A273ED2-AE84-48E9-9C23-E978C2D0CB34}.Debug|Any CPU.ActiveCfg = Debug|anycpu + {1A273ED2-AE84-48E9-9C23-E978C2D0CB34}.Debug|Any CPU.Build.0 = Debug|anycpu + {1A273ED2-AE84-48E9-9C23-E978C2D0CB34}.Debug|x64.ActiveCfg = Debug|Any CPU + {1A273ED2-AE84-48E9-9C23-E978C2D0CB34}.Debug|x64.Build.0 = Debug|Any CPU + {1A273ED2-AE84-48E9-9C23-E978C2D0CB34}.Debug|x86.ActiveCfg = Debug|Any CPU + {1A273ED2-AE84-48E9-9C23-E978C2D0CB34}.Debug|x86.Build.0 = Debug|Any CPU + {1A273ED2-AE84-48E9-9C23-E978C2D0CB34}.Release|Any CPU.ActiveCfg = Release|anycpu + {1A273ED2-AE84-48E9-9C23-E978C2D0CB34}.Release|Any CPU.Build.0 = Release|anycpu + {1A273ED2-AE84-48E9-9C23-E978C2D0CB34}.Release|x64.ActiveCfg = Release|Any CPU + {1A273ED2-AE84-48E9-9C23-E978C2D0CB34}.Release|x64.Build.0 = Release|Any CPU + {1A273ED2-AE84-48E9-9C23-E978C2D0CB34}.Release|x86.ActiveCfg = Release|Any CPU + {1A273ED2-AE84-48E9-9C23-E978C2D0CB34}.Release|x86.Build.0 = Release|Any CPU + {DA50FC92-7FE7-48B5-BDB6-CDA57B37BB51}.Debug|Any CPU.ActiveCfg = Debug|anycpu + {DA50FC92-7FE7-48B5-BDB6-CDA57B37BB51}.Debug|Any CPU.Build.0 = Debug|anycpu + {DA50FC92-7FE7-48B5-BDB6-CDA57B37BB51}.Debug|x64.ActiveCfg = Debug|Any CPU + {DA50FC92-7FE7-48B5-BDB6-CDA57B37BB51}.Debug|x64.Build.0 = Debug|Any CPU + {DA50FC92-7FE7-48B5-BDB6-CDA57B37BB51}.Debug|x86.ActiveCfg = Debug|Any CPU + {DA50FC92-7FE7-48B5-BDB6-CDA57B37BB51}.Debug|x86.Build.0 = Debug|Any CPU + {DA50FC92-7FE7-48B5-BDB6-CDA57B37BB51}.Release|Any CPU.ActiveCfg = Release|anycpu + {DA50FC92-7FE7-48B5-BDB6-CDA57B37BB51}.Release|Any CPU.Build.0 = Release|anycpu + {DA50FC92-7FE7-48B5-BDB6-CDA57B37BB51}.Release|x64.ActiveCfg = Release|Any CPU + {DA50FC92-7FE7-48B5-BDB6-CDA57B37BB51}.Release|x64.Build.0 = Release|Any CPU + {DA50FC92-7FE7-48B5-BDB6-CDA57B37BB51}.Release|x86.ActiveCfg = Release|Any CPU + {DA50FC92-7FE7-48B5-BDB6-CDA57B37BB51}.Release|x86.Build.0 = Release|Any CPU + {4EFCED6E-9A6B-453A-94E4-CE4B736EC684}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4EFCED6E-9A6B-453A-94E4-CE4B736EC684}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4EFCED6E-9A6B-453A-94E4-CE4B736EC684}.Debug|x64.ActiveCfg = Debug|Any CPU + {4EFCED6E-9A6B-453A-94E4-CE4B736EC684}.Debug|x64.Build.0 = Debug|Any CPU + {4EFCED6E-9A6B-453A-94E4-CE4B736EC684}.Debug|x86.ActiveCfg = Debug|Any CPU + {4EFCED6E-9A6B-453A-94E4-CE4B736EC684}.Debug|x86.Build.0 = Debug|Any CPU + {4EFCED6E-9A6B-453A-94E4-CE4B736EC684}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4EFCED6E-9A6B-453A-94E4-CE4B736EC684}.Release|Any CPU.Build.0 = Release|Any CPU + {4EFCED6E-9A6B-453A-94E4-CE4B736EC684}.Release|x64.ActiveCfg = Release|Any CPU + {4EFCED6E-9A6B-453A-94E4-CE4B736EC684}.Release|x64.Build.0 = Release|Any CPU + {4EFCED6E-9A6B-453A-94E4-CE4B736EC684}.Release|x86.ActiveCfg = Release|Any CPU + {4EFCED6E-9A6B-453A-94E4-CE4B736EC684}.Release|x86.Build.0 = Release|Any CPU + {D8E14B43-E929-4C18-9FA6-2C3DC47EFC17}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D8E14B43-E929-4C18-9FA6-2C3DC47EFC17}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D8E14B43-E929-4C18-9FA6-2C3DC47EFC17}.Debug|x64.ActiveCfg = Debug|Any CPU + {D8E14B43-E929-4C18-9FA6-2C3DC47EFC17}.Debug|x64.Build.0 = Debug|Any CPU + {D8E14B43-E929-4C18-9FA6-2C3DC47EFC17}.Debug|x86.ActiveCfg = Debug|Any CPU + {D8E14B43-E929-4C18-9FA6-2C3DC47EFC17}.Debug|x86.Build.0 = Debug|Any CPU + {D8E14B43-E929-4C18-9FA6-2C3DC47EFC17}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D8E14B43-E929-4C18-9FA6-2C3DC47EFC17}.Release|Any CPU.Build.0 = Release|Any CPU + {D8E14B43-E929-4C18-9FA6-2C3DC47EFC17}.Release|x64.ActiveCfg = Release|Any CPU + {D8E14B43-E929-4C18-9FA6-2C3DC47EFC17}.Release|x64.Build.0 = Release|Any CPU + {D8E14B43-E929-4C18-9FA6-2C3DC47EFC17}.Release|x86.ActiveCfg = Release|Any CPU + {D8E14B43-E929-4C18-9FA6-2C3DC47EFC17}.Release|x86.Build.0 = Release|Any CPU + {C0E44558-FEE3-4DD3-986A-3F46DD1BF41B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C0E44558-FEE3-4DD3-986A-3F46DD1BF41B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C0E44558-FEE3-4DD3-986A-3F46DD1BF41B}.Debug|x64.ActiveCfg = Debug|Any CPU + {C0E44558-FEE3-4DD3-986A-3F46DD1BF41B}.Debug|x64.Build.0 = Debug|Any CPU + {C0E44558-FEE3-4DD3-986A-3F46DD1BF41B}.Debug|x86.ActiveCfg = Debug|Any CPU + {C0E44558-FEE3-4DD3-986A-3F46DD1BF41B}.Debug|x86.Build.0 = Debug|Any CPU + {C0E44558-FEE3-4DD3-986A-3F46DD1BF41B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C0E44558-FEE3-4DD3-986A-3F46DD1BF41B}.Release|Any CPU.Build.0 = Release|Any CPU + {C0E44558-FEE3-4DD3-986A-3F46DD1BF41B}.Release|x64.ActiveCfg = Release|Any CPU + {C0E44558-FEE3-4DD3-986A-3F46DD1BF41B}.Release|x64.Build.0 = Release|Any CPU + {C0E44558-FEE3-4DD3-986A-3F46DD1BF41B}.Release|x86.ActiveCfg = Release|Any CPU + {C0E44558-FEE3-4DD3-986A-3F46DD1BF41B}.Release|x86.Build.0 = Release|Any CPU + {BA4D889D-066B-4C2C-A973-09E319CBC396}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BA4D889D-066B-4C2C-A973-09E319CBC396}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BA4D889D-066B-4C2C-A973-09E319CBC396}.Debug|x64.ActiveCfg = Debug|Any CPU + {BA4D889D-066B-4C2C-A973-09E319CBC396}.Debug|x64.Build.0 = Debug|Any CPU + {BA4D889D-066B-4C2C-A973-09E319CBC396}.Debug|x86.ActiveCfg = Debug|Any CPU + {BA4D889D-066B-4C2C-A973-09E319CBC396}.Debug|x86.Build.0 = Debug|Any CPU + {BA4D889D-066B-4C2C-A973-09E319CBC396}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BA4D889D-066B-4C2C-A973-09E319CBC396}.Release|Any CPU.Build.0 = Release|Any CPU + {BA4D889D-066B-4C2C-A973-09E319CBC396}.Release|x64.ActiveCfg = Release|Any CPU + {BA4D889D-066B-4C2C-A973-09E319CBC396}.Release|x64.Build.0 = Release|Any CPU + {BA4D889D-066B-4C2C-A973-09E319CBC396}.Release|x86.ActiveCfg = Release|Any CPU + {BA4D889D-066B-4C2C-A973-09E319CBC396}.Release|x86.Build.0 = Release|Any CPU + {A39B6D7C-6616-40D6-8AE4-C6CEE93D2708}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A39B6D7C-6616-40D6-8AE4-C6CEE93D2708}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A39B6D7C-6616-40D6-8AE4-C6CEE93D2708}.Debug|x64.ActiveCfg = Debug|Any CPU + {A39B6D7C-6616-40D6-8AE4-C6CEE93D2708}.Debug|x64.Build.0 = Debug|Any CPU + {A39B6D7C-6616-40D6-8AE4-C6CEE93D2708}.Debug|x86.ActiveCfg = Debug|Any CPU + {A39B6D7C-6616-40D6-8AE4-C6CEE93D2708}.Debug|x86.Build.0 = Debug|Any CPU + {A39B6D7C-6616-40D6-8AE4-C6CEE93D2708}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A39B6D7C-6616-40D6-8AE4-C6CEE93D2708}.Release|Any CPU.Build.0 = Release|Any CPU + {A39B6D7C-6616-40D6-8AE4-C6CEE93D2708}.Release|x64.ActiveCfg = Release|Any CPU + {A39B6D7C-6616-40D6-8AE4-C6CEE93D2708}.Release|x64.Build.0 = Release|Any CPU + {A39B6D7C-6616-40D6-8AE4-C6CEE93D2708}.Release|x86.ActiveCfg = Release|Any CPU + {A39B6D7C-6616-40D6-8AE4-C6CEE93D2708}.Release|x86.Build.0 = Release|Any CPU + {5E806C9F-1B67-4B6B-A6AB-258834250DBB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5E806C9F-1B67-4B6B-A6AB-258834250DBB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5E806C9F-1B67-4B6B-A6AB-258834250DBB}.Debug|x64.ActiveCfg = Debug|Any CPU + {5E806C9F-1B67-4B6B-A6AB-258834250DBB}.Debug|x64.Build.0 = Debug|Any CPU + {5E806C9F-1B67-4B6B-A6AB-258834250DBB}.Debug|x86.ActiveCfg = Debug|Any CPU + {5E806C9F-1B67-4B6B-A6AB-258834250DBB}.Debug|x86.Build.0 = Debug|Any CPU + {5E806C9F-1B67-4B6B-A6AB-258834250DBB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5E806C9F-1B67-4B6B-A6AB-258834250DBB}.Release|Any CPU.Build.0 = Release|Any CPU + {5E806C9F-1B67-4B6B-A6AB-258834250DBB}.Release|x64.ActiveCfg = Release|Any CPU + {5E806C9F-1B67-4B6B-A6AB-258834250DBB}.Release|x64.Build.0 = Release|Any CPU + {5E806C9F-1B67-4B6B-A6AB-258834250DBB}.Release|x86.ActiveCfg = Release|Any CPU + {5E806C9F-1B67-4B6B-A6AB-258834250DBB}.Release|x86.Build.0 = Release|Any CPU + {5FD0133B-69E5-4474-9B67-9FD1D0150C70}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5FD0133B-69E5-4474-9B67-9FD1D0150C70}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5FD0133B-69E5-4474-9B67-9FD1D0150C70}.Debug|x64.ActiveCfg = Debug|Any CPU + {5FD0133B-69E5-4474-9B67-9FD1D0150C70}.Debug|x64.Build.0 = Debug|Any CPU + {5FD0133B-69E5-4474-9B67-9FD1D0150C70}.Debug|x86.ActiveCfg = Debug|Any CPU + {5FD0133B-69E5-4474-9B67-9FD1D0150C70}.Debug|x86.Build.0 = Debug|Any CPU + {5FD0133B-69E5-4474-9B67-9FD1D0150C70}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5FD0133B-69E5-4474-9B67-9FD1D0150C70}.Release|Any CPU.Build.0 = Release|Any CPU + {5FD0133B-69E5-4474-9B67-9FD1D0150C70}.Release|x64.ActiveCfg = Release|Any CPU + {5FD0133B-69E5-4474-9B67-9FD1D0150C70}.Release|x64.Build.0 = Release|Any CPU + {5FD0133B-69E5-4474-9B67-9FD1D0150C70}.Release|x86.ActiveCfg = Release|Any CPU + {5FD0133B-69E5-4474-9B67-9FD1D0150C70}.Release|x86.Build.0 = Release|Any CPU + {E8831F32-11D7-D42C-E43C-711998BC357A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E8831F32-11D7-D42C-E43C-711998BC357A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E8831F32-11D7-D42C-E43C-711998BC357A}.Debug|x64.ActiveCfg = Debug|Any CPU + {E8831F32-11D7-D42C-E43C-711998BC357A}.Debug|x64.Build.0 = Debug|Any CPU + {E8831F32-11D7-D42C-E43C-711998BC357A}.Debug|x86.ActiveCfg = Debug|Any CPU + {E8831F32-11D7-D42C-E43C-711998BC357A}.Debug|x86.Build.0 = Debug|Any CPU + {E8831F32-11D7-D42C-E43C-711998BC357A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E8831F32-11D7-D42C-E43C-711998BC357A}.Release|Any CPU.Build.0 = Release|Any CPU + {E8831F32-11D7-D42C-E43C-711998BC357A}.Release|x64.ActiveCfg = Release|Any CPU + {E8831F32-11D7-D42C-E43C-711998BC357A}.Release|x64.Build.0 = Release|Any CPU + {E8831F32-11D7-D42C-E43C-711998BC357A}.Release|x86.ActiveCfg = Release|Any CPU + {E8831F32-11D7-D42C-E43C-711998BC357A}.Release|x86.Build.0 = Release|Any CPU + {0DCDA993-A945-44D0-B9C0-FF9FAB127C7E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0DCDA993-A945-44D0-B9C0-FF9FAB127C7E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0DCDA993-A945-44D0-B9C0-FF9FAB127C7E}.Debug|x64.ActiveCfg = Debug|Any CPU + {0DCDA993-A945-44D0-B9C0-FF9FAB127C7E}.Debug|x64.Build.0 = Debug|Any CPU + {0DCDA993-A945-44D0-B9C0-FF9FAB127C7E}.Debug|x86.ActiveCfg = Debug|Any CPU + {0DCDA993-A945-44D0-B9C0-FF9FAB127C7E}.Debug|x86.Build.0 = Debug|Any CPU + {0DCDA993-A945-44D0-B9C0-FF9FAB127C7E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0DCDA993-A945-44D0-B9C0-FF9FAB127C7E}.Release|Any CPU.Build.0 = Release|Any CPU + {0DCDA993-A945-44D0-B9C0-FF9FAB127C7E}.Release|x64.ActiveCfg = Release|Any CPU + {0DCDA993-A945-44D0-B9C0-FF9FAB127C7E}.Release|x64.Build.0 = Release|Any CPU + {0DCDA993-A945-44D0-B9C0-FF9FAB127C7E}.Release|x86.ActiveCfg = Release|Any CPU + {0DCDA993-A945-44D0-B9C0-FF9FAB127C7E}.Release|x86.Build.0 = Release|Any CPU + {3AAA188D-5F22-4596-8C4F-76C7AA899EAA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3AAA188D-5F22-4596-8C4F-76C7AA899EAA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3AAA188D-5F22-4596-8C4F-76C7AA899EAA}.Debug|x64.ActiveCfg = Debug|Any CPU + {3AAA188D-5F22-4596-8C4F-76C7AA899EAA}.Debug|x64.Build.0 = Debug|Any CPU + {3AAA188D-5F22-4596-8C4F-76C7AA899EAA}.Debug|x86.ActiveCfg = Debug|Any CPU + {3AAA188D-5F22-4596-8C4F-76C7AA899EAA}.Debug|x86.Build.0 = Debug|Any CPU + {3AAA188D-5F22-4596-8C4F-76C7AA899EAA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3AAA188D-5F22-4596-8C4F-76C7AA899EAA}.Release|Any CPU.Build.0 = Release|Any CPU + {3AAA188D-5F22-4596-8C4F-76C7AA899EAA}.Release|x64.ActiveCfg = Release|Any CPU + {3AAA188D-5F22-4596-8C4F-76C7AA899EAA}.Release|x64.Build.0 = Release|Any CPU + {3AAA188D-5F22-4596-8C4F-76C7AA899EAA}.Release|x86.ActiveCfg = Release|Any CPU + {3AAA188D-5F22-4596-8C4F-76C7AA899EAA}.Release|x86.Build.0 = Release|Any CPU + {65633E01-3521-4219-BBE1-9A4DB4009A91}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {65633E01-3521-4219-BBE1-9A4DB4009A91}.Debug|Any CPU.Build.0 = Debug|Any CPU + {65633E01-3521-4219-BBE1-9A4DB4009A91}.Debug|x64.ActiveCfg = Debug|Any CPU + {65633E01-3521-4219-BBE1-9A4DB4009A91}.Debug|x64.Build.0 = Debug|Any CPU + {65633E01-3521-4219-BBE1-9A4DB4009A91}.Debug|x86.ActiveCfg = Debug|Any CPU + {65633E01-3521-4219-BBE1-9A4DB4009A91}.Debug|x86.Build.0 = Debug|Any CPU + {65633E01-3521-4219-BBE1-9A4DB4009A91}.Release|Any CPU.ActiveCfg = Release|Any CPU + {65633E01-3521-4219-BBE1-9A4DB4009A91}.Release|Any CPU.Build.0 = Release|Any CPU + {65633E01-3521-4219-BBE1-9A4DB4009A91}.Release|x64.ActiveCfg = Release|Any CPU + {65633E01-3521-4219-BBE1-9A4DB4009A91}.Release|x64.Build.0 = Release|Any CPU + {65633E01-3521-4219-BBE1-9A4DB4009A91}.Release|x86.ActiveCfg = Release|Any CPU + {65633E01-3521-4219-BBE1-9A4DB4009A91}.Release|x86.Build.0 = Release|Any CPU + {D8B7DCCF-E27A-4BDF-B0A0-40D1F2F758EB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D8B7DCCF-E27A-4BDF-B0A0-40D1F2F758EB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D8B7DCCF-E27A-4BDF-B0A0-40D1F2F758EB}.Debug|x64.ActiveCfg = Debug|Any CPU + {D8B7DCCF-E27A-4BDF-B0A0-40D1F2F758EB}.Debug|x64.Build.0 = Debug|Any CPU + {D8B7DCCF-E27A-4BDF-B0A0-40D1F2F758EB}.Debug|x86.ActiveCfg = Debug|Any CPU + {D8B7DCCF-E27A-4BDF-B0A0-40D1F2F758EB}.Debug|x86.Build.0 = Debug|Any CPU + {D8B7DCCF-E27A-4BDF-B0A0-40D1F2F758EB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D8B7DCCF-E27A-4BDF-B0A0-40D1F2F758EB}.Release|Any CPU.Build.0 = Release|Any CPU + {D8B7DCCF-E27A-4BDF-B0A0-40D1F2F758EB}.Release|x64.ActiveCfg = Release|Any CPU + {D8B7DCCF-E27A-4BDF-B0A0-40D1F2F758EB}.Release|x64.Build.0 = Release|Any CPU + {D8B7DCCF-E27A-4BDF-B0A0-40D1F2F758EB}.Release|x86.ActiveCfg = Release|Any CPU + {D8B7DCCF-E27A-4BDF-B0A0-40D1F2F758EB}.Release|x86.Build.0 = Release|Any CPU + {79BBD075-BC73-4D35-8DE6-BE70C9AFC708}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {79BBD075-BC73-4D35-8DE6-BE70C9AFC708}.Debug|Any CPU.Build.0 = Debug|Any CPU + {79BBD075-BC73-4D35-8DE6-BE70C9AFC708}.Debug|x64.ActiveCfg = Debug|Any CPU + {79BBD075-BC73-4D35-8DE6-BE70C9AFC708}.Debug|x64.Build.0 = Debug|Any CPU + {79BBD075-BC73-4D35-8DE6-BE70C9AFC708}.Debug|x86.ActiveCfg = Debug|Any CPU + {79BBD075-BC73-4D35-8DE6-BE70C9AFC708}.Debug|x86.Build.0 = Debug|Any CPU + {79BBD075-BC73-4D35-8DE6-BE70C9AFC708}.Release|Any CPU.ActiveCfg = Release|Any CPU + {79BBD075-BC73-4D35-8DE6-BE70C9AFC708}.Release|Any CPU.Build.0 = Release|Any CPU + {79BBD075-BC73-4D35-8DE6-BE70C9AFC708}.Release|x64.ActiveCfg = Release|Any CPU + {79BBD075-BC73-4D35-8DE6-BE70C9AFC708}.Release|x64.Build.0 = Release|Any CPU + {79BBD075-BC73-4D35-8DE6-BE70C9AFC708}.Release|x86.ActiveCfg = Release|Any CPU + {79BBD075-BC73-4D35-8DE6-BE70C9AFC708}.Release|x86.Build.0 = Release|Any CPU + {624C58EB-57CF-4754-95EF-E9893E584296}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {624C58EB-57CF-4754-95EF-E9893E584296}.Debug|Any CPU.Build.0 = Debug|Any CPU + {624C58EB-57CF-4754-95EF-E9893E584296}.Debug|x64.ActiveCfg = Debug|Any CPU + {624C58EB-57CF-4754-95EF-E9893E584296}.Debug|x64.Build.0 = Debug|Any CPU + {624C58EB-57CF-4754-95EF-E9893E584296}.Debug|x86.ActiveCfg = Debug|Any CPU + {624C58EB-57CF-4754-95EF-E9893E584296}.Debug|x86.Build.0 = Debug|Any CPU + {624C58EB-57CF-4754-95EF-E9893E584296}.Release|Any CPU.ActiveCfg = Release|Any CPU + {624C58EB-57CF-4754-95EF-E9893E584296}.Release|Any CPU.Build.0 = Release|Any CPU + {624C58EB-57CF-4754-95EF-E9893E584296}.Release|x64.ActiveCfg = Release|Any CPU + {624C58EB-57CF-4754-95EF-E9893E584296}.Release|x64.Build.0 = Release|Any CPU + {624C58EB-57CF-4754-95EF-E9893E584296}.Release|x86.ActiveCfg = Release|Any CPU + {624C58EB-57CF-4754-95EF-E9893E584296}.Release|x86.Build.0 = Release|Any CPU + {4723786E-4BB5-4939-BBFD-3C2D0EAE236B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4723786E-4BB5-4939-BBFD-3C2D0EAE236B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4723786E-4BB5-4939-BBFD-3C2D0EAE236B}.Debug|x64.ActiveCfg = Debug|Any CPU + {4723786E-4BB5-4939-BBFD-3C2D0EAE236B}.Debug|x64.Build.0 = Debug|Any CPU + {4723786E-4BB5-4939-BBFD-3C2D0EAE236B}.Debug|x86.ActiveCfg = Debug|Any CPU + {4723786E-4BB5-4939-BBFD-3C2D0EAE236B}.Debug|x86.Build.0 = Debug|Any CPU + {4723786E-4BB5-4939-BBFD-3C2D0EAE236B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4723786E-4BB5-4939-BBFD-3C2D0EAE236B}.Release|Any CPU.Build.0 = Release|Any CPU + {4723786E-4BB5-4939-BBFD-3C2D0EAE236B}.Release|x64.ActiveCfg = Release|Any CPU + {4723786E-4BB5-4939-BBFD-3C2D0EAE236B}.Release|x64.Build.0 = Release|Any CPU + {4723786E-4BB5-4939-BBFD-3C2D0EAE236B}.Release|x86.ActiveCfg = Release|Any CPU + {4723786E-4BB5-4939-BBFD-3C2D0EAE236B}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -474,6 +1088,13 @@ Global {5E806C9F-1B67-4B6B-A6AB-258834250DBB} = {FFCF518F-2A4A-40A2-9174-2EE13B76C723} {5FD0133B-69E5-4474-9B67-9FD1D0150C70} = {FFCF518F-2A4A-40A2-9174-2EE13B76C723} {E8831F32-11D7-D42C-E43C-711998BC357A} = {04E3E11E-B47D-4599-8AFC-50515A95E715} + {0DCDA993-A945-44D0-B9C0-FF9FAB127C7E} = {FFCF518F-2A4A-40A2-9174-2EE13B76C723} + {3AAA188D-5F22-4596-8C4F-76C7AA899EAA} = {FFCF518F-2A4A-40A2-9174-2EE13B76C723} + {65633E01-3521-4219-BBE1-9A4DB4009A91} = {FFCF518F-2A4A-40A2-9174-2EE13B76C723} + {D8B7DCCF-E27A-4BDF-B0A0-40D1F2F758EB} = {FFCF518F-2A4A-40A2-9174-2EE13B76C723} + {79BBD075-BC73-4D35-8DE6-BE70C9AFC708} = {FFCF518F-2A4A-40A2-9174-2EE13B76C723} + {624C58EB-57CF-4754-95EF-E9893E584296} = {FFCF518F-2A4A-40A2-9174-2EE13B76C723} + {4723786E-4BB5-4939-BBFD-3C2D0EAE236B} = {FFCF518F-2A4A-40A2-9174-2EE13B76C723} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {53A1F287-EFB2-4D97-A4BB-4A5E145613F6} diff --git a/build-tools/android-platform-support/AndroidTools.GitInfo.targets b/build-tools/android-platform-support/AndroidTools.GitInfo.targets new file mode 100644 index 00000000000..c943f9a4c56 --- /dev/null +++ b/build-tools/android-platform-support/AndroidTools.GitInfo.targets @@ -0,0 +1,45 @@ + + + + + false + main + + $(MSBuildThisFileDirectory)GitInfo.txt + + + + + $(GitSemVerMajor).$(GitSemVerMinor).$(GitSemVerPatch) + $(Version); git-rev-head:$(GitCommit); git-branch:$(GitBranch) + + + + + + $(MicroBuild_SigningEnabled) + false + + + $(TargetsTriggeredByCompilation); + EnableMicroBuildSigningPostCompile + + + + + + $(MicroBuild_SigningEnabled_Old) + + + diff --git a/build-tools/android-platform-support/GitInfo.txt b/build-tools/android-platform-support/GitInfo.txt new file mode 100644 index 00000000000..9459d4ba2a0 --- /dev/null +++ b/build-tools/android-platform-support/GitInfo.txt @@ -0,0 +1 @@ +1.1 diff --git a/build-tools/android-platform-support/version.txt b/build-tools/android-platform-support/version.txt new file mode 100644 index 00000000000..d4df1049f25 --- /dev/null +++ b/build-tools/android-platform-support/version.txt @@ -0,0 +1 @@ +18.1.0 diff --git a/build-tools/automation/azure-pipelines-nightly.yaml b/build-tools/automation/azure-pipelines-nightly.yaml index fcce624fdc5..97811fe5a0a 100644 --- a/build-tools/automation/azure-pipelines-nightly.yaml +++ b/build-tools/automation/azure-pipelines-nightly.yaml @@ -15,10 +15,6 @@ resources: type: git name: DevDiv/Xamarin.yaml-templates ref: refs/heads/main - - repository: android-platform-support - type: git - name: DevDiv/android-platform-support - ref: refs/heads/main - repository: maui type: github name: dotnet/maui diff --git a/build-tools/automation/azure-pipelines.yaml b/build-tools/automation/azure-pipelines.yaml index b73b4f5ee57..f11fdd1d613 100644 --- a/build-tools/automation/azure-pipelines.yaml +++ b/build-tools/automation/azure-pipelines.yaml @@ -19,10 +19,6 @@ resources: type: git name: DevDiv/Xamarin.yaml-templates ref: refs/heads/main - - repository: android-platform-support - type: git - name: DevDiv/android-platform-support - ref: refs/heads/main - repository: maui type: github name: dotnet/maui @@ -75,9 +71,6 @@ extends: image: $(WindowsPoolImage1ESPT) os: windows sourceRepositoriesToScan: - include: - - ${{ if ne(variables['System.PullRequest.IsFork'], 'True') }}: - - repository: android-platform-support exclude: - repository: yaml-templates - repository: maui diff --git a/build-tools/automation/yaml-templates/build-linux.yaml b/build-tools/automation/yaml-templates/build-linux.yaml index 53cd23ea184..d53525fb085 100644 --- a/build-tools/automation/yaml-templates/build-linux.yaml +++ b/build-tools/automation/yaml-templates/build-linux.yaml @@ -55,21 +55,9 @@ stages: # https://learn.microsoft.com/en-us/azure/devops/pipelines/repos/multi-repo-checkout?view=azure-devops#checkout-path - checkout: maui - - ${{ if ne(variables['System.PullRequest.IsFork'], 'True') }}: - - checkout: android-platform-support - clean: true - submodules: recursive - path: s/android/external/android-platform-support - persistCredentials: true - - template: /build-tools/automation/yaml-templates/build-linux-steps.yaml parameters: buildResultArtifactName: ${{ parameters.buildResultArtifactName }} xaSourcePath: ${{ parameters.xaSourcePath }} nugetArtifactName: ${{ parameters.nugetArtifactName }} use1ESTemplate: ${{ parameters.use1ESTemplate }} - - - ${{ if ne(variables['System.PullRequest.IsFork'], 'True') }}: - - script: make prepare-external-git-dependencies PREPARE_CI=1 CONFIGURATION=$(XA.Build.Configuration) - workingDirectory: ${{ parameters.xaSourcePath }} - displayName: make prepare-external-git-dependencies diff --git a/build-tools/automation/yaml-templates/build-macos.yaml b/build-tools/automation/yaml-templates/build-macos.yaml index 3dae9491c34..040d4934bb7 100644 --- a/build-tools/automation/yaml-templates/build-macos.yaml +++ b/build-tools/automation/yaml-templates/build-macos.yaml @@ -77,12 +77,6 @@ stages: # https://learn.microsoft.com/en-us/azure/devops/pipelines/repos/multi-repo-checkout?view=azure-devops#checkout-path - checkout: maui - - ${{ if ne(variables['System.PullRequest.IsFork'], 'True') }}: - - checkout: android-platform-support - submodules: recursive - path: s/android/external/android-platform-support - persistCredentials: true - - task: CodeQL3000Init@0 displayName: CodeQL 3000 Init condition: and(succeeded(), eq(variables['Codeql.Enabled'], 'true'), eq(variables['Build.SourceBranch'], 'refs/heads/main')) @@ -98,11 +92,6 @@ stages: buildResultArtifactName: ${{ parameters.buildResultArtifactName }} use1ESTemplate: ${{ parameters.use1ESTemplate }} - - ${{ if ne(variables['System.PullRequest.IsFork'], 'True') }}: - - script: make prepare-external-git-dependencies PREPARE_CI=1 CONFIGURATION=$(XA.Build.Configuration) - workingDirectory: ${{ parameters.xaSourcePath }} - displayName: make prepare-external-git-dependencies - - task: CodeQL3000Finalize@0 displayName: CodeQL 3000 Finalize condition: and(succeededOrFailed(), eq(variables['Codeql.Enabled'], 'true'), eq(variables['Build.SourceBranch'], 'refs/heads/main')) diff --git a/build-tools/automation/yaml-templates/commercial-build.yaml b/build-tools/automation/yaml-templates/commercial-build.yaml index c91210eb1b8..35af92b8d57 100644 --- a/build-tools/automation/yaml-templates/commercial-build.yaml +++ b/build-tools/automation/yaml-templates/commercial-build.yaml @@ -26,16 +26,6 @@ steps: # https://learn.microsoft.com/en-us/azure/devops/pipelines/repos/multi-repo-checkout?view=azure-devops#checkout-path - checkout: maui -- ${{ if ne(variables['System.PullRequest.IsFork'], 'True') }}: - - checkout: android-platform-support - submodules: recursive - path: s/android/external/android-platform-support - persistCredentials: true - - - script: make prepare-external-git-dependencies PREPARE_CI=1 CONFIGURATION=$(XA.Build.Configuration) - workingDirectory: ${{ parameters.xaSourcePath }} - displayName: make prepare-external-git-dependencies - - task: CodeQL3000Init@0 displayName: CodeQL 3000 Init condition: and(succeeded(), eq(variables['Codeql.Enabled'], 'true'), eq(variables['Build.SourceBranch'], 'refs/heads/main')) diff --git a/build-tools/installers/create-installers.targets b/build-tools/installers/create-installers.targets index 77fedd4c0c5..50d387f7983 100644 --- a/build-tools/installers/create-installers.targets +++ b/build-tools/installers/create-installers.targets @@ -19,7 +19,6 @@ dll False <_HasCommercialFiles Condition="Exists('$(MicrosoftAndroidSdkOutDir)Xamarin.Android.Common.Debugging.targets')">True - <_MonoDroidPath Condition=" '$(_MonoDroidPath)' == '' ">..\..\external\android-platform-support @@ -245,9 +244,38 @@ - - - + + + <_MSBuildFiles Include="$(MicrosoftAndroidSdkOutDir)ICSharpCode.SharpZipLib.dll" /> + <_MSBuildFiles Include="$(MicrosoftAndroidSdkOutDir)ICSharpCode.SharpZipLib.pdb" /> + <_MSBuildFiles Include="$(MicrosoftAndroidSdkOutDir)INIFileParser.dll" /> + <_MSBuildFiles Include="$(MicrosoftAndroidSdkOutDir)Mono.AndroidTools.dll" /> + <_MSBuildFiles Include="$(MicrosoftAndroidSdkOutDir)Mono.AndroidTools.pdb" /> + <_MSBuildFiles Include="$(MicrosoftAndroidSdkOutDir)Xamarin.Android.Build.Debugging.Tasks.dll" /> + <_MSBuildFiles Include="$(MicrosoftAndroidSdkOutDir)Xamarin.Android.Build.Debugging.Tasks.pdb" /> + <_MSBuildFiles Include="@(_LocalizationLanguages->'$(MicrosoftAndroidSdkOutDir)%(Identity)\Xamarin.Android.Build.Debugging.Tasks.resources.dll')" /> + <_MSBuildFiles Include="$(MicrosoftAndroidSdkOutDir)Xamarin.Android.Common.Debugging.props" /> + <_MSBuildFiles Include="$(MicrosoftAndroidSdkOutDir)Xamarin.Android.Common.Debugging.targets" /> + <_MSBuildFiles Include="$(MicrosoftAndroidSdkOutDir)Xamarin.AndroidTools.dll" /> + <_MSBuildFiles Include="$(MicrosoftAndroidSdkOutDir)Xamarin.AndroidTools.pdb" /> + <_MSBuildFiles Include="$(MicrosoftAndroidSdkOutDir)Xamarin.Installer.AndroidSDK.dll" /> + <_MSBuildFiles Include="$(MicrosoftAndroidSdkOutDir)Xamarin.Installer.AndroidSDK.pdb" /> + <_MSBuildFiles Include="$(MicrosoftAndroidSdkOutDir)Xamarin.Installer.Build.Tasks.dll" /> + <_MSBuildFiles Include="$(MicrosoftAndroidSdkOutDir)Xamarin.Installer.Build.Tasks.pdb" /> + <_MSBuildFiles Include="$(MicrosoftAndroidSdkOutDir)Xamarin.Installer.Common.dll" /> + <_MSBuildFiles Include="$(MicrosoftAndroidSdkOutDir)Xamarin.Installer.Common.pdb" /> + <_MSBuildFiles Include="$(MicrosoftAndroidSdkOutDir)Xamarin.Installer.Common.props" /> + <_MSBuildFiles Include="$(MicrosoftAndroidSdkOutDir)Xamarin.Installer.Common.targets" /> + + + <_MSBuildFiles Include="@(AndroidSupportedTargetJitAbi->'$(MicrosoftAndroidSdkOutDir)lib\%(Identity)\xamarin.sync')" /> + <_MSBuildFiles Include="@(AndroidSupportedTargetJitAbi->'$(MicrosoftAndroidSdkOutDir)lib\%(Identity)\xamarin.find')" /> + <_MSBuildFiles Include="@(AndroidSupportedTargetJitAbi->'$(MicrosoftAndroidSdkOutDir)lib\%(Identity)\xamarin.stat')" /> + <_MSBuildFiles Include="@(AndroidSupportedTargetJitAbi->'$(MicrosoftAndroidSdkOutDir)lib\%(Identity)\xamarin.cp')" /> + maui-android - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Databáze adb není spuštěna. {0} + The following are literal names and should not be translated: adb +{0} - further detailed error message + + + Neplatný název cíle adb {0}. Názvy cílů musí začínat na -d, -e nebo -s. + The following are literal names and should not be translated: adb, `-d`, `-e`, `-s` +{0} - the target name the user supplied + + + Žádné dostupné zařízení. + + + Vybrané zařízení není spuštěné. + + + Nelze načíst rozhraní Android ABI pro připojené zařízení nebo emulátor {0}. Znovu připojte zařízení nebo restartujte emulátor a zkuste to znovu. + The following are literal names and should not be translated: ABI +ABI - is an acronym for Application Binary Interface. +{0} - the target name the user supplied + + + + Nepovedlo se zkopírovat nástroje rychlého nasazení do {0}/{1} na cílovém zařízení. +Toto zařízení pravděpodobně není kompatibilní s rychlým nasazením. +Pokud chcete zakázat rychlé nasazení na stránkách vlastností projektu Visual Studio, nastavte vlastnost MSBuild EmbedAssembliesIntoApk na hodnotu true, nebo upravte soubor projektu v textovém editoru. + The following are literal names and should not be translated: +{0} - A device path +{1} - A filename + + + Při nasazování {0} pomocí {1} došlo k chybě: {2}. +Pokud chcete zakázat rychlé nasazení na stránkách vlastností projektu Visual Studio, nastavte vlastnost MSBuild EmbedAssembliesIntoApk na hodnotu true, nebo upravte soubor projektu v textovém editoru. +The following are literal names and should not be translated: +{0} - A filename +{1} - The tool that was used to deploy the file. +{2} - The actual error that occured. + + + Systémová vlastnost log.redirect-stdio zařízení Android je na cílovém zařízení nebo emulátoru nastavená na true. +Pokud chcete povolit kompatibilitu s rychlým nasazením, nastavte vlastnost na false nebo restartujte zařízení nebo emulátor, aby se hodnota vymazala. + + + Při nasazování {0} došlo k chybě. +Pokud chcete zakázat rychlé nasazení na stránkách vlastností projektu Visual Studio, nastavte vlastnost MSBuild EmbedAssembliesIntoApk na hodnotu true, nebo upravte soubor projektu v textovém editoru. + The following are literal names and should not be translated: +{0} - A filename + + + Omlouváme se. Rychlé nasazení se podporuje jenom na zařízeních s operačním systémem Android 5.0 (rozhraní API úrovně 21) nebo vyšším. +Pokud chcete zakázat rychlé nasazení na stránkách vlastností projektu Visual Studio, nastavte vlastnost MSBuild EmbedAssembliesIntoApk na hodnotu true, nebo upravte soubor projektu v textovém editoru. + + + Nástroj run-as byl na tomto zařízení zakázán. Buď ho povolte aktivací možností pro vývojáře na zařízení, nebo nastavením ro.boot.disable_runas na false. + The following are literal names and should not be translated: +'run-as' - a native android tool +'ro.boot.disable_runas' - the android property which disables the `run-as` tool. + + + + Balíček nebyl nainstalován. Zkontrolujte prosím, že ho nemáte nainstalovaný pod žádným jiným uživatelem. +Pokud se balíček na zařízení zobrazí, zkuste ho odinstalovat ručně a zkuste to znovu. +Aplikaci byste měli být schopni odinstalovat prostřednictvím aplikace Nastavení na zařízení. + The following are literal names and should not be translated: + + + + Nástroj run-as vyžadovaný systémem rychlého nasazení byl na tomto zařízení zakázán výrobcem. +Zakažte rychlé nasazení na stránkách vlastností projektu Visual Studio nebo upravte soubor projektu v textovém editoru a nastavte vlastnost MSBuild EmbedAssembliesIntoApk na hodnotu true. + The following are literal names and should not be translated: +'run-as' - a native android tool + + + + Aplikace nemá v androidManifest.xml nastaven atribut android:debuggable. +Aby rychlé nasazení fungovalo, je to nutné. Ve výchozím nastavení je tato možnost standardně povolená +sestavovacím systémem .NET for Android pro sestavení Debug (ladění). Ověřte, jestli nemáte tento atribut nastavený +pro element application v souboru AndroidManifest.xml. +Pokud máte třídu, která se odvozuje z Android.App.Application a používáte [Application], ujistěte se, že vlastnost +Debuggable není vůbec nastavená, protože přepíše hodnotu pro sestavení ladění. + + The following are literal names and should not be translated: +AndroidManifest.xml - A xml file +application - A xml element within a xml file. +Application - A code attribute. +Debuggable - A code property +android:debuggable - An attribute in a xml file. + + + + Balíček je systémová aplikace. Jedná se o aplikace, které se instalují v rámci + systémového uživatele na zařízení. Tyto typy aplikací nemohou používat příkaz run-as. + Systém rychlého nasazení měl tuto konkrétní chybu zpracovat automaticky + a použít záložní instalační cestu. Pokud se vám ale tato chyba zobrazuje, vytvořte prosím + problém s přesnou chybovou zprávou pomocí položky nabídky Nápověda->Odeslat zpětnou vazbu->Nahlásit problém v aplikaci Visual Studio + nebo Nápověda->Nahlásit problém v aplikaci Visual Studio pro Mac. Pokud je to možné, připojte úplný diagnostický + protokol buildu k sestavě zpětné vazby, protože to nám pomůže diagnostikovat problém. + The following are literal names and should not be translated: + + + + Příkaz run-as selhal s chybou {0}. +Aktuální instalace balíčku je poškozena. Ručně odinstalujte balíček +od všech uživatelů na zařízení a zkuste to znovu. Pokud to nepomůže, můžete rychlé nasazení zakázat. +Rychlé nasazení lze zakázat na stránkách vlastností projektu Visual Studio nebo upravit soubor projektu v textovém editoru a nastavit vlastnost MSBuild EmbedAssembliesIntoApk na hodnotu true. + + The following are literal names and should not be translated: +'run-as' - a native android tool +{0} - The error message raised by the program. + + + + Příkaz run-as selhal s chybou {0}. +Rychlé nasazení se v současné době na tomto zařízení nepodporuje. +Nahlaste prosím problém s přesnou chybovou zprávou pomocí položky nabídky Nápověda->Odeslat zpětnou vazbu->Nahlásit problém v aplikaci Visual Studio +nebo Nápověda->Nahlásit problém v aplikaci Visual Studio pro Mac. +Pokud chcete zakázat rychlé nasazení na stránkách vlastností projektu Visual Studio, nastavte vlastnost MSBuild EmbedAssembliesIntoApk na hodnotu true, nebo upravte soubor projektu v textovém editoru. + The following are literal names and should not be translated: +'run-as' - a native android tool +{0} - The error message raised by the program. + + + + Při nasazování {0} pomocí {1} došlo k chybě: {2}. +To je obvykle způsobeno chybou v adb, když se data souboru odesílají do zařízení. +Obvyklou příčinou je uvolněný nebo nekvalitní kabel USB. Zkuste prosím použít jiný kabel. +Případně zkuste použít ladění Wifi, které může být u některých zařízení spolehlivější než ladění USB. +Pokud se žádným z těchto způsobů nepovede problém vyřešit, nastavte vlastnost MSBuild EmbedAssembliesIntoApk na true, aby se rychlé nasazení zakázalo na stránkách vlastností projektu Visual Studio, nebo upravte soubor projektu v textovém editoru. +The following are literal names and should not be translated: +{0} - A filename +{1} - The tool that was used to deploy the file. +{2} - The actual error that occured. + + + Spouští se ladicí program na {0}:{1} +Spusťte sdb a pak spusťte následující příkaz + connect {0} {1} + + The following are literal names and should not be translated: +{0} - An ip address of a computer. +{1} - An interger value describing the port to use. + + + \ No newline at end of file diff --git a/src/Xamarin.Android.Build.Debugging.Tasks/Properties/Resources.de.resx b/src/Xamarin.Android.Build.Debugging.Tasks/Properties/Resources.de.resx new file mode 100644 index 00000000000..88b2f2d3ec8 --- /dev/null +++ b/src/Xamarin.Android.Build.Debugging.Tasks/Properties/Resources.de.resx @@ -0,0 +1,266 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + adb wird nicht ausgeführt. {0} + The following are literal names and should not be translated: adb +{0} - further detailed error message + + + Ungültiger adb-Zielname '{0}'. Zielnamen müssen mit "-d", "-e" oder "-s" beginnen. + The following are literal names and should not be translated: adb, `-d`, `-e`, `-s` +{0} - the target name the user supplied + + + Kein Gerät verfügbar. + + + Ausgewähltes Gerät wird nicht ausgeführt. + + + Die Android-ABI für das angeschlossene Gerät oder den Emulator "{0}" konnte nicht abgerufen werden. Schließen Sie das Gerät erneut an, oder starten Sie den Emulator neu, und versuchen Sie es noch mal. + The following are literal names and should not be translated: ABI +ABI - is an acronym for Application Binary Interface. +{0} - the target name the user supplied + + + + Die Tools für die Schnelle Bereitstellung können auf dem Zielgerät nicht nach „{0}/{1}“ kopiert werden. +Dieses Gerät ist möglicherweise nicht mit der Schnellen Bereitstellung kompatibel. +Legen Sie die MSBuild-Eigenschaft „EmbedAssembliesIntoApk“ auf WAHR fest, um die Schnelle Bereitstellung auf den Visual Studio-Projekteigenschaftenseiten zu deaktivieren, oder bearbeiten Sie die Projektdatei in einem Text-Editor. + The following are literal names and should not be translated: +{0} - A device path +{1} - A filename + + + Fehler beim Bereitstellen von „{0}“ mit „{1}: {2}“. +Legen Sie die MSBuild-Eigenschaft „EmbedAssembliesIntoApk“ auf WAHR fest, um die Schnelle Bereitstellung auf den Visual Studio-Projekteigenschaftenseiten zu deaktivieren, oder bearbeiten Sie die Projektdatei in einem Text-Editor. +The following are literal names and should not be translated: +{0} - A filename +{1} - The tool that was used to deploy the file. +{2} - The actual error that occured. + + + Die Android-Systemeigenschaft „log.redirect-stdio“ ist auf dem Zielgerät oder Emulator auf WAHR festgelegt. +Um die Kompatibilität mit der Schnellen Bereitstellung zu aktivieren, legen Sie die Eigenschaft auf FALSCH fest, oder starten Sie das Gerät oder den Emulator neu, um den Wert zu löschen. + + + Fehler beim Bereitstellen von „{0}“. +Legen Sie die MSBuild-Eigenschaft „EmbedAssembliesIntoApk“ auf WAHR fest, um die Schnelle Bereitstellung auf den Visual Studio-Projekteigenschaftenseiten zu deaktivieren, oder bearbeiten Sie die Projektdatei in einem Text-Editor. + The following are literal names and should not be translated: +{0} - A filename + + + Entschuldigung. Die Schnelle Bereitstellung wird nur auf Geräten mit Android 5.0 (API-Ebene 21) oder höher unterstützt. +Legen Sie die MSBuild-Eigenschaft „EmbedAssembliesIntoApk“ auf WAHR fest, um die Schnelle Bereitstellung auf den Visual Studio-Projekteigenschaftenseiten zu deaktivieren, oder bearbeiten Sie die Projektdatei in einem Text-Editor. + + + Das "run-as"-Tool wurde auf diesem Gerät deaktiviert. Aktivieren Sie die Option, indem Sie die Entwickleroptionen auf dem Gerät aktivieren oder "ro.boot.disable_runas" auf "false" festlegen. + The following are literal names and should not be translated: +'run-as' - a native android tool +'ro.boot.disable_runas' - the android property which disables the `run-as` tool. + + + + Das Paket wurde nicht installiert. Überprüfen Sie, dass es unter keinem anderen Benutzenden installiert ist. +Wenn das Paket auf dem Gerät angezeigt wird, deinstallieren Sie es manuell, und versuchen Sie es dann erneut. +Sie sollten die App über die Einstellungs-App auf dem Gerät deinstallieren können. + The following are literal names and should not be translated: + + + + Das Tool „run-as“, das für das System für Schnelle Bereitstellung erforderlich ist, wurde auf diesem Gerät vom Hersteller deaktiviert. +Deaktivieren Sie die Schnelle Bereitstellung auf den Visual Studio-Projekteigenschaftenseiten, oder bearbeiten Sie die Projektdatei in einem Text-Editor, und legen Sie die MSBuild-Eigenschaft „EmbedAssembliesIntoApk“ auf WAHR fest. + The following are literal names and should not be translated: +'run-as' - a native android tool + + + + Für die Anwendung ist das Attribut „android:debuggable“ im AndroidManifest.xml nicht festgelegt. +Dies ist erforderlich, damit die Schnelle Bereitstellung funktioniert. Dies ist in der Regel standardmäßig aktiviert durch +das .NET für Android-Buildsystem für Debugbuilds. Stellen Sie sicher, dass dieses Attribut +nicht auf das Element „application“ in Ihrem „AndroidManifest.xml“ festgelegt ist. +Wenn Sie über eine Klasse verfügen, die von „Android.App.Application“ abgeleitet ist und „[Application]“ verwenden, stellen Sie sicher, dass die +Eigenschaft „Debuggable“ absolut nicht festgelegt ist, da sie den Wert für Debugbuilds überschreibt. + + The following are literal names and should not be translated: +AndroidManifest.xml - A xml file +application - A xml element within a xml file. +Application - A code attribute. +Debuggable - A code property +android:debuggable - An attribute in a xml file. + + + + Das Paket ist eine „system“-Anwendung. Dies sind Anwendungen, die unter + der „system“-Benutzende auf einem Gerät installiert werden. Diese Anwendungstypen können „run-as“ nicht verwenden. + Der spezielle Fehler sollte vom System für Schnelle Bereitstellung automatisch behandelt worden sein + und hätte einen Sicherungsinstallationspfad verwenden sollen. Wenn dieser Fehler angezeigt wird, erstellen Sie + ein Ticket mit der genauen Fehlermeldung mithilfe des Menüelements „Hilfe->Feedback senden->Ein Problem melden“ in Visual Studio + oder „Hilfe->Ein Problem melden“ in Visual Studio für Mac. Fügen Sie nach Möglichkeit ein vollständiges Diagnosebuild + Protokoll im Feedbackbericht an, da uns dies bei der Diagnose des Problems hilft. + The following are literal names and should not be translated: + + + + Fehler beim Befehl „run-as“ mit „{0}“. +Die aktuelle Installation des Pakets ist beschädigt. Deinstallieren Sie das +Paket von allen Benutzenden auf dem Gerät, und versuchen Sie es noch mal. Wenn dies nicht funktioniert, können Sie die Schnelle Bereitstellung deaktivieren. +Die Schnelle Bereitstellung kann auf den Visual Studio-Projekteigenschaftenseiten deaktiviert werden oder die Projektdatei in einem Text-Editor bearbeiten und die MSBuild-Eigenschaft „EmbedAssembliesIntoApk“ auf WAHR festlegen. + + The following are literal names and should not be translated: +'run-as' - a native android tool +{0} - The error message raised by the program. + + + + Fehler beim Befehl „run-as“ mit „{0}“. +Die Schnelle Bereitstellung wird auf diesem Gerät derzeit nicht unterstützt. +Melden Sie ein Problem mit der genauen Fehlermeldung mithilfe des Menüelements „Hilfe->Feedback senden->Ein Problem melden“ in Visual Studio +oder „Hilfe->Ein Problem melden“ in Visual Studio für Mac. +Legen Sie die MSBuild-Eigenschaft „EmbedAssembliesIntoApk“ auf WAHR fest, um die Schnelle Bereitstellung auf den Visual Studio-Projekteigenschaftenseiten zu deaktivieren, oder bearbeiten Sie die Projektdatei in einem Text-Editor. + The following are literal names and should not be translated: +'run-as' - a native android tool +{0} - The error message raised by the program. + + + + Fehler beim Bereitstellen von „{0}“ mit „{1}: {2}“. +Dies ist in der Regel auf einen Fehler in „adb“ zurückzuführen, wenn die Dateidaten an das Gerät gesendet werden. +Die normale Ursache hierfür ist ein USB-Kabel, das lose oder von schlechter Qualität ist. Versuchen Sie es mit einem anderen Kabel. +Versuchen Sie alternativ, das WLAN-Debuggen zu verwenden. Dies kann für bestimmte Geräte zuverlässiger als das USB-Debuggen sein. +Wenn all dies fehlschlägt, legen Sie die MSBuild-Eigenschaft „EmbedAssembliesIntoApk“ auf WAHR fest, um die Schnelle Bereitstellung auf den Visual Studio-Projekteigenschaftenseiten zu deaktivieren, oder bearbeiten Sie die Projektdatei in einem Text-Editor. +The following are literal names and should not be translated: +{0} - A filename +{1} - The tool that was used to deploy the file. +{2} - The actual error that occured. + + + Debugger wird auf {0} gestartet:{1} +Starten Sie „sdb“, und führen Sie dann die folgende Befehls- +- + Verbindung {0} {1} + aus. + + The following are literal names and should not be translated: +{0} - An ip address of a computer. +{1} - An interger value describing the port to use. + + + \ No newline at end of file diff --git a/src/Xamarin.Android.Build.Debugging.Tasks/Properties/Resources.es.resx b/src/Xamarin.Android.Build.Debugging.Tasks/Properties/Resources.es.resx new file mode 100644 index 00000000000..8bef707e01b --- /dev/null +++ b/src/Xamarin.Android.Build.Debugging.Tasks/Properties/Resources.es.resx @@ -0,0 +1,264 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + adb no se está ejecutando. {0} + The following are literal names and should not be translated: adb +{0} - further detailed error message + + + El nombre de destino adb “{0}” no es válido. Los nombres de destino deben empezar por “-d”, “-e” o “-s”. + The following are literal names and should not be translated: adb, `-d`, `-e`, `-s` +{0} - the target name the user supplied + + + No hay ningún dispositivo disponible. + + + El dispositivo seleccionado no se está ejecutando. + + + No se pudo recuperar la ABI de Android para el dispositivo o emulador adjunto “{0}”. Vuelva a conectar el dispositivo o reinicie el emulador e inténtelo de nuevo. + The following are literal names and should not be translated: ABI +ABI - is an acronym for Application Binary Interface. +{0} - the target name the user supplied + + + + No se pueden copiar las herramientas de implementación rápida en "{0}/{1}" en el dispositivo de destino. +Es posible que este dispositivo no sea compatible con la implementación rápida. +Establezca la propiedad "EmbedAssembliesIntoApk" de MSBuild en "true" para deshabilitar la implementación rápida en las páginas de propiedades del proyecto de Visual Studio, o bien edite el archivo del proyecto en un editor de texto. + The following are literal names and should not be translated: +{0} - A device path +{1} - A filename + + + Error al implementar '{0}' mediante '{1}: {2}'. +Establezca la propiedad "EmbedAssembliesIntoApk" de MSBuild en "true" para deshabilitar la implementación rápida en las páginas de propiedades del proyecto de Visual Studio, o bien edite el archivo del proyecto en un editor de texto. +The following are literal names and should not be translated: +{0} - A filename +{1} - The tool that was used to deploy the file. +{2} - The actual error that occured. + + + La propiedad del sistema Android "log.redirect-stdio" se establece en "true" en el emulador o dispositivo de destino. +Para habilitar la compatibilidad con la implementación rápida, establezca la propiedad en "false" o reinicie el dispositivo o emulador para borrar el valor. + + + Error al implementar '{0}'. +Establezca la propiedad "EmbedAssembliesIntoApk" de MSBuild en "true" para deshabilitar la implementación rápida en las páginas de propiedades del proyecto de Visual Studio, o bien edite el archivo del proyecto en un editor de texto. + The following are literal names and should not be translated: +{0} - A filename + + + Lo siento. La implementación rápida solo se admite en dispositivos que ejecutan Android 5.0 (nivel de API 21) o superior. +Establezca la propiedad "EmbedAssembliesIntoApk" de MSBuild en "true" para deshabilitar la implementación rápida en las páginas de propiedades del proyecto de Visual Studio, o bien edite el archivo del proyecto en un editor de texto. + + + La herramienta “ejecutar como” se ha deshabilitado en este dispositivo. Habilítela activando las opciones de desarrollador en el dispositivo o estableciendo “ro.boot.disable_runas” en “false”. + The following are literal names and should not be translated: +'run-as' - a native android tool +'ro.boot.disable_runas' - the android property which disables the `run-as` tool. + + + + El paquete no se instaló. Compruebe que no lo tiene instalado en ningún otro usuario. +Si el paquete aparece en el dispositivo, intenta desinstalarlo manualmente e inténtalo de nuevo. +Debería poder desinstalar la aplicación a través de la aplicación Configuración en el dispositivo. + The following are literal names and should not be translated: + + + + El fabricante deshabilitó la herramienta "run-as" requerida por el sistema de implementación rápida en este dispositivo. +Deshabilite la implementación rápida en las páginas de propiedades del proyecto Visual Studio o edite el archivo de proyecto en un editor de texto y establezca la propiedad "EmbedAssembliesIntoApk" de MSBuild en "true". + The following are literal names and should not be translated: +'run-as' - a native android tool + + + + La aplicación no tiene el atributo "android:debuggable" establecido en el AndroidManifest.xml. +Esto es necesario para que la implementación rápida funcione. Normalmente, esta opción está habilitada de forma predeterminada por +el sistema de compilación de .NET para Android para compilaciones de depuración. Compruebe que no tiene este atributo +se establece en el elemento "application" de la "AndroidManifest.xml". +Si tiene una clase que deriva de "Android.App.Application" y usa "[Application]", asegúrese de que el +La propiedad 'Debuggable' no está establecida en absoluto, ya que invalidará el valor de las compilaciones de depuración. + + The following are literal names and should not be translated: +AndroidManifest.xml - A xml file +application - A xml element within a xml file. +Application - A code attribute. +Debuggable - A code property +android:debuggable - An attribute in a xml file. + + + + El paquete es una aplicación del “sistema”. Se trata de aplicaciones que se instalan en + el usuario "system" en un dispositivo. Estos tipos de aplicaciones no pueden usar "run-as". + El sistema de implementación rápida debería haber controlado automáticamente este error en particular + y usó una ruta de instalación de copia de seguridad. Sin embargo, si ve este error, archive + un problema con el mensaje de error exacto mediante el elemento de menú "Help->Send Feedback->Report a Problem" en Visual Studio + o "Help->Report a Problem" en Visual Studio para Mac. Si es posible, adjunte una compilación de diagnóstico completa + inicie sesión en el informe de comentarios, ya que esto nos ayudará a diagnosticar el problema. + The following are literal names and should not be translated: + + + + Error del comando "run-as" con "{0}". +La instalación actual del paquete está dañada. Desinstale manualmente el +paquete de todos los usuarios en el dispositivo e inténtelo de nuevo. Si esto no funciona, puede deshabilitar la implementación rápida. +La implementación rápida se puede deshabilitar en las páginas de propiedades del proyecto Visual Studio o editar el archivo de proyecto en un editor de texto y establecer la propiedad "EmbedAssembliesIntoApk" de MSBuild en "true". + + The following are literal names and should not be translated: +'run-as' - a native android tool +{0} - The error message raised by the program. + + + + Error del comando "run-as" con "{0}". +La implementación rápida no se admite actualmente en este dispositivo. +Presente un problema con el mensaje de error exacto mediante el elemento de menú "Help->Send Feedback->Report a Problem" en Visual Studio +o "Help->Report a Problem" en Visual Studio para Mac. +Establezca la propiedad "EmbedAssembliesIntoApk" de MSBuild en "true" para deshabilitar la implementación rápida en las páginas de propiedades del proyecto de Visual Studio, o bien edite el archivo del proyecto en un editor de texto. + The following are literal names and should not be translated: +'run-as' - a native android tool +{0} - The error message raised by the program. + + + + Error al implementar '{0}' con '{1}: {2}'. +Esto suele deberse a un error en "adb" cuando los datos del archivo se envían al dispositivo. +La causa normal de esto es un cable USB flexible o de baja calidad. Pruebe con otro cable. +Como alternativa, pruebe a usar la depuración Wi-Fi, esto puede ser más confiable que la depuración USB para determinados dispositivos. +Si se produce un error en todo esto, establezca la propiedad MSBuild "EmbedAssembliesIntoApk" en "true" para deshabilitar la implementación rápida en las páginas de propiedades del proyecto de Visual Studio o edite el archivo de proyecto en un editor de texto. +The following are literal names and should not be translated: +{0} - A filename +{1} - The tool that was used to deploy the file. +{2} - The actual error that occured. + + + Iniciando depurador en {0}:{1} +Inicie sdb y ejecute el siguiente comando + conectar {0} {1} + + The following are literal names and should not be translated: +{0} - An ip address of a computer. +{1} - An interger value describing the port to use. + + + \ No newline at end of file diff --git a/src/Xamarin.Android.Build.Debugging.Tasks/Properties/Resources.fr.resx b/src/Xamarin.Android.Build.Debugging.Tasks/Properties/Resources.fr.resx new file mode 100644 index 00000000000..0fb87334572 --- /dev/null +++ b/src/Xamarin.Android.Build.Debugging.Tasks/Properties/Resources.fr.resx @@ -0,0 +1,264 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + adb n’est pas en cours d’exécution. {0} + The following are literal names and should not be translated: adb +{0} - further detailed error message + + + Nom de cible adb non valide « {0} ». Les noms cibles doivent commencer par `-d`, `-e`, ou `-s`. + The following are literal names and should not be translated: adb, `-d`, `-e`, `-s` +{0} - the target name the user supplied + + + Aucun appareil disponible. + + + L’appareil sélectionné n’est pas en cours d’exécution. + + + Impossible de récupérer l’ABI Android pour l’appareil ou l’émulateur attaché `{0}`. Reconnectez l’appareil ou redémarrez l’émulateur, puis réessayez. + The following are literal names and should not be translated: ABI +ABI - is an acronym for Application Binary Interface. +{0} - the target name the user supplied + + + + Impossible de copier les outils de déploiement rapide vers « {0}/{1} » sur l’appareil cible. +Cet appareil n’est peut-être pas compatible avec le déploiement rapide. +Veuillez définir la propriété MSBuild « EmbedAssembliesIntoApk » sur « true » pour désactiver le déploiement rapide dans les pages de propriétés du projet Visual Studio, ou modifiez le fichier projet dans un éditeur de texte. + The following are literal names and should not be translated: +{0} - A device path +{1} - A filename + + + Erreur lors du déploiement de « {0} » en utilisant « {1} : {2} ». +Veuillez définir la propriété MSBuild « EmbedAssembliesIntoApk » sur « true » pour désactiver le déploiement rapide dans les pages de propriétés du projet Visual Studio, ou modifiez le fichier projet dans un éditeur de texte. +The following are literal names and should not be translated: +{0} - A filename +{1} - The tool that was used to deploy the file. +{2} - The actual error that occured. + + + La propriété système Android « log.redirect-stdio » a la valeur « true » sur l’appareil ou l’émulateur cible. +Pour activer la compatibilité avec le déploiement rapide, définissez la propriété sur « false » ou redémarrez l’appareil ou l’émulateur pour effacer la valeur. + + + Erreur de déploiement « {0} ». +Veuillez définir la propriété MSBuild « EmbedAssembliesIntoApk » sur « true » pour désactiver le déploiement rapide dans les pages de propriétés du projet Visual Studio, ou modifiez le fichier projet dans un éditeur de texte. + The following are literal names and should not be translated: +{0} - A filename + + + Désolé. Le déploiement rapide est uniquement pris en charge sur les appareils exécutant Android 5.0 (niveau d’API 21) ou supérieur. +Veuillez définir la propriété MSBuild « EmbedAssembliesIntoApk » sur « true » pour désactiver le déploiement rapide dans les pages de propriétés du projet Visual Studio, ou modifiez le fichier projet dans un éditeur de texte. + + + L’outil d’identification a été désactivé sur cet appareil. Activez-le en activant les options de développeur sur l’appareil ou en définissant « ro.boot.disable_runas » sur « false ». + The following are literal names and should not be translated: +'run-as' - a native android tool +'ro.boot.disable_runas' - the android property which disables the `run-as` tool. + + + + Le package n’a pas été installé. Vérifiez qu’il n’est pas installé sous un(e) autre utilisateur(-trice). +Si le package s’affiche sur l’appareil, essayez de le désinstaller manuellement, puis réessayez. +Vous devez être en mesure de désinstaller l’application via l’application Paramètres sur l’appareil. + The following are literal names and should not be translated: + + + + L’outil d’identification requis par le système de déploiement rapide a été désactivé sur cet appareil par le fabricant. +Veuillez désactiver le déploiement rapide dans les pages de propriétés du projet Visual Studio, ou modifiez le fichier projet dans un éditeur de texte et définir la propriété MSBuild « EmbedAssembliesIntoApk » à « true ». + The following are literal names and should not be translated: +'run-as' - a native android tool + + + + L’application n’a pas l’attribut « android:debuggable » défini dans le AndroidManifest.xml. +Cela est nécessaire pour que le déploiement rapide fonctionne. Cette option est normalement activée par défaut par +le système de build .NET pour Android pour les builds de débogage. Vérifiez que vous n’avez pas cet attribut +défini sur l’élément « application » dans votre « AndroidManifest.xml ». +Si vous avez une classe qui dérive de « Android.App.Application » et qui utilise « [Application] », assurez-vous que le +La propriété « Debuggable » n’est pas définie du tout, car elle remplace la valeur pour les builds de débogage. + + The following are literal names and should not be translated: +AndroidManifest.xml - A xml file +application - A xml element within a xml file. +Application - A code attribute. +Debuggable - A code property +android:debuggable - An attribute in a xml file. + + + + Le package est une application « system ». Il s’agit des applications qui s’installent sous + l’utilisateur « système » sur un appareil. Ces types d’applications ne peuvent pas utiliser « run-as ». + Le système de déploiement rapide aurait dû gérer cette erreur particulière automatiquement + et utilisé un chemin d’installation de sauvegarde. Toutefois, si vous voyez cette erreur, veuillez + un problème lié au message d’erreur exact à l’aide de l’élément de menu « Help->Send Feedback->Report a Problem » dans Visual Studio + ou « Help->Reporter un problème » dans Visual Studio pour Mac. Si possible, attachez une build de diagnostic complète + connectez-vous au rapport de commentaires, car cela nous aidera à diagnostiquer le problème. + The following are literal names and should not be translated: + + + + La commande « run-as » a échoué avec « {0} ». +L’installation du package est actuellement endommagée. Désinstallez manuellement le +package de tous les utilisateurs sur l’appareil et réessayez. Si cela ne fonctionne pas, vous pouvez désactiver le déploiement rapide. +Fast Deployment peut être désactivé dans les pages de propriétés du projet Visual Studio ou éditer le fichier du projet dans un éditeur de texte et définir la propriété MSBuild « EmbedAssembliesIntoApk » sur « true ». + + The following are literal names and should not be translated: +'run-as' - a native android tool +{0} - The error message raised by the program. + + + + La commande « run-as » a échoué avec « {0} ». +Le déploiement rapide n’est actuellement pas pris en charge sur cet appareil. +Veuillez porter un problème lié au message d’erreur exact à l’aide de l’élément de menu « Help->Send Feedback->Report a Problem » dans Visual Studio +ou « Help->Reporter un problème » dans Visual Studio pour Mac. +Veuillez définir la propriété MSBuild « EmbedAssembliesIntoApk » sur « true » pour désactiver le déploiement rapide dans les pages de propriétés du projet Visual Studio, ou modifiez le fichier projet dans un éditeur de texte. + The following are literal names and should not be translated: +'run-as' - a native android tool +{0} - The error message raised by the program. + + + + Erreur lors du déploiement de « {0} » en utilisant « {1} : {2} ». +Cela est généralement dû à une erreur dans « adb » lorsque les données de fichier sont envoyées à l’appareil. +La cause normale de cette erreur est un câble USB faible ou de faible qualité. Veuillez essayer un autre câble. +Vous pouvez également essayer d’utiliser le débogage Wifi, ce qui peut être plus fiable que le débogage USB pour certains appareils. +Si tout cela échoue, définissez la propriété MSBuild « EmbedAssembliesIntoApk » sur « true » pour désactiver Fast Deployment dans les pages de propriétés du projet Visual Studio, ou éditez le fichier du projet dans un éditeur de texte. +The following are literal names and should not be translated: +{0} - A filename +{1} - The tool that was used to deploy the file. +{2} - The actual error that occured. + + + Démarrage du débogueur sur {0} :{1} +Lancez sdb puis exécutez la commande suivante + Se connecter {0} {1} + + The following are literal names and should not be translated: +{0} - An ip address of a computer. +{1} - An interger value describing the port to use. + + + \ No newline at end of file diff --git a/src/Xamarin.Android.Build.Debugging.Tasks/Properties/Resources.it.resx b/src/Xamarin.Android.Build.Debugging.Tasks/Properties/Resources.it.resx new file mode 100644 index 00000000000..ccfa3324de3 --- /dev/null +++ b/src/Xamarin.Android.Build.Debugging.Tasks/Properties/Resources.it.resx @@ -0,0 +1,264 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + adb non è in esecuzione. {0} + The following are literal names and should not be translated: adb +{0} - further detailed error message + + + Il nome di destinazione adb '{0}' non è valido. I nomi di destinazione devono iniziare con '-d', '-e' o '-s'. + The following are literal names and should not be translated: adb, `-d`, `-e`, `-s` +{0} - the target name the user supplied + + + Nessun dispositivo disponibile. + + + Il dispositivo selezionato non è in esecuzione. + + + Non è stato possibile recuperare l'ABI Android per il dispositivo o l'emulatore collegato {0}. Riconnettere il dispositivo o riavviare l'emulatore e riprovare. + The following are literal names and should not be translated: ABI +ABI - is an acronym for Application Binary Interface. +{0} - the target name the user supplied + + + + Non è possibile copiare gli strumenti di distribuzione rapida in '{0}/{1}' nel dispositivo di destinazione. +Questo dispositivo potrebbe non essere compatibile con la distribuzione rapida. +Impostare la proprietà MSBuild 'EmbedAssembliesIntoApk' su 'true' per disabilitare la distribuzione rapida nelle pagine delle proprietà del progetto Visual Studio o modificare il file di progetto in un editor di testo. + The following are literal names and should not be translated: +{0} - A device path +{1} - A filename + + + Errore durante la distribuzione di '{0}' con '{1}: {2}'. +Impostare la proprietà MSBuild 'EmbedAssembliesIntoApk' su 'true' per disabilitare la distribuzione rapida nelle pagine delle proprietà del progetto Visual Studio o modificare il file di progetto in un editor di testo. +The following are literal names and should not be translated: +{0} - A filename +{1} - The tool that was used to deploy the file. +{2} - The actual error that occured. + + + La proprietà di sistema Android 'log.redirect-stdio' è impostata su 'true' nell'emulatore o nel dispositivo di destinazione. +Per abilitare la compatibilità con la distribuzione rapida, impostare la proprietà su 'false' o riavviare il dispositivo o l'emulatore per cancellare il valore. + + + Errore durante la distribuzione di '{0}'. +Impostare la proprietà MSBuild 'EmbedAssembliesIntoApk' su 'true' per disabilitare la distribuzione rapida nelle pagine delle proprietà del progetto Visual Studio o modificare il file di progetto in un editor di testo. + The following are literal names and should not be translated: +{0} - A filename + + + Siamo spiacenti. La distribuzione rapida è supportata solo nei dispositivi che eseguono Android 5.0 (livello API 21) o versione successiva. +Impostare la proprietà MSBuild 'EmbedAssembliesIntoApk' su 'true' per disabilitare la distribuzione rapida nelle pagine delle proprietà del progetto Visual Studio o modificare il file di progetto in un editor di testo. + + + Lo strumento 'run-as' è stato disabilitato in questo dispositivo. Abilitarlo attivando le opzioni per sviluppatori sul dispositivo o impostando 'ro.boot.disable_runas' su 'false'. + The following are literal names and should not be translated: +'run-as' - a native android tool +'ro.boot.disable_runas' - the android property which disables the `run-as` tool. + + + + Il pacchetto non è stato installato. Verificare che non sia installato per un altro utente. +Se il pacchetto viene visualizzato nel dispositivo, provare a disinstallarlo manualmente, quindi riprovare. +Dovrebbe essere possibile disinstallare l'app tramite l'app Impostazioni nel dispositivo. + The following are literal names and should not be translated: + + + + Lo strumento "run-as" richiesto dal sistema di distribuzione rapida è stato disabilitato in questo dispositivo dal produttore. +Disabilitare la distribuzione rapida nelle pagine delle proprietà del progetto Visual Studio o modificare il file di progetto in un editor di testo e impostare la proprietà MSBuild 'EmbedAssembliesIntoApk' su 'true'. + The following are literal names and should not be translated: +'run-as' - a native android tool + + + + L'applicazione non dispone dell'attributo 'android:debuggable' impostato in AndroidManifest.xml. +Questo è necessario per il funzionamento della distribuzione rapida. Questa funzionalità è in genere abilitata per impostazione predefinita dal +sistema di compilazione .NET per le compilazioni di debug. Verificare che questo attributo non sia impostato +sull'elemento 'application' nel file 'AndroidManifest.xml'. +Se si dispone di una classe che deriva da 'Android.App.Application' e si usa '[Application]' assicurarsi che +La proprietà 'Debuggable' non sia impostata perché sostituirà il valore per le compilazioni di debug. + + The following are literal names and should not be translated: +AndroidManifest.xml - A xml file +application - A xml element within a xml file. +Application - A code attribute. +Debuggable - A code property +android:debuggable - An attribute in a xml file. + + + + Il pacchetto è un'applicazione 'system'. Si tratta di applicazioni che si installano in + l'utente 'system' in un dispositivo. Questi tipi di applicazioni non possono usare 'run-as'. + Il sistema di distribuzione rapida dovrebbe aver gestito automaticamente questo particolare errore + e usato un percorso di installazione di backup. Tuttavia, se viene visualizzato questo errore, segnalare + un problema con il messaggio di errore esatto usando la voce di menu 'Guida->Invia Feedback->Segnala un problema' in Visual Studio + o 'Guida->Segnala un problema' in Visual Studio per Mac. Se possibile, allegare un log di compilazione diagnostica completa + il report di feedback, perché questo aiuterà a diagnosticare il problema. + The following are literal names and should not be translated: + + + + Il comando 'run-as' non è riuscito con errore '{0}'. +L'installazione corrente del pacchetto è danneggiata. Disinstallare manualmente il pacchetto +da tutti gli utenti nel dispositivo e riprovare. Se non funziona, è possibile disabilitare la distribuzione rapida. +La distribuzione rapida può essere disabilitata nelle pagine delle proprietà del progetto Visual Studio o modificando il file di progetto in un editor di testo e impostando la proprietà MSBuild 'EmbedAssembliesIntoApk' su 'true'. + + The following are literal names and should not be translated: +'run-as' - a native android tool +{0} - The error message raised by the program. + + + + Il comando 'run-as' non è riuscito con errore '{0}'. +La distribuzione rapida non è attualmente supportata in questo dispositivo. +Segnalare un problema con il messaggio di errore esatto usando la voce di menu "Guida->Invia feedback->Segnala un problema" in Visual Studio +o 'Guida->Segnala un problema' in Visual Studio per Mac. +Impostare la proprietà MSBuild 'EmbedAssembliesIntoApk' su 'true' per disabilitare la distribuzione rapida nelle pagine delle proprietà del progetto Visual Studio o modificare il file di progetto in un editor di testo. + The following are literal names and should not be translated: +'run-as' - a native android tool +{0} - The error message raised by the program. + + + + Errore durante la distribuzione di '{0}' con '{1}: {2}'. +Ciò è in genere dovuto a un errore in 'adb' quando i dati del file vengono inviati al dispositivo. +La causa di solito è un cavo USB allentato o di bassa qualità. Provare un cavo diverso. +In alternativa, provare a usare il debug Wi-Fi, che può essere più affidabile del debug USB per determinati dispositivi. +Se il problema persiste, impostare la proprietà MSBuild 'EmbedAssembliesIntoApk' su 'true' per disabilitare la distribuzione rapida nelle pagine delle proprietà del progetto Visual Studio o modificare il file di progetto in un editor di testo. +The following are literal names and should not be translated: +{0} - A filename +{1} - The tool that was used to deploy the file. +{2} - The actual error that occured. + + + Avvio del debugger in {0}:{1} +Avviare sdb, quindi eseguire il comando seguente + connetti {0} {1} + + The following are literal names and should not be translated: +{0} - An ip address of a computer. +{1} - An interger value describing the port to use. + + + \ No newline at end of file diff --git a/src/Xamarin.Android.Build.Debugging.Tasks/Properties/Resources.ja.resx b/src/Xamarin.Android.Build.Debugging.Tasks/Properties/Resources.ja.resx new file mode 100644 index 00000000000..7b8e627289f --- /dev/null +++ b/src/Xamarin.Android.Build.Debugging.Tasks/Properties/Resources.ja.resx @@ -0,0 +1,265 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + adb が実行されていません。{0} + The following are literal names and should not be translated: adb +{0} - further detailed error message + + + adb ターゲット名 '{0}' が無効です。ターゲット名は、'-d'、'-e'、または '-s' で始まる必要があります。 + The following are literal names and should not be translated: adb, `-d`, `-e`, `-s` +{0} - the target name the user supplied + + + 使用可能なデバイスがありません。 + + + 選択したデバイスは実行されていません。 + + + アタッチされたデバイスまたはエミュレーターの Android ABI `{0}` を取得できませんでした。デバイスを再接続するか、エミュレーターを再起動してから、もう一度お試しください。 + The following are literal names and should not be translated: ABI +ABI - is an acronym for Application Binary Interface. +{0} - the target name the user supplied + + + + 高速展開ツールをターゲット デバイスの '{0}/{1}' にコピーできません。 +このデバイスは高速展開と互換性がない可能性があります。 +'EmbedAssembliesIntoApk' MSBuild プロパティを 'true' に設定して、Visual Studio プロジェクト プロパティ ページで高速展開を無効にするか、テキスト エディターでプロジェクト ファイルを編集してください。 + The following are literal names and should not be translated: +{0} - A device path +{1} - A filename + + + '{1}:{2}' を使用して '{0}' を展開中にエラーが発生しました。 +'EmbedAssembliesIntoApk' MSBuild プロパティを 'true' に設定して、Visual Studio プロジェクト プロパティ ページで高速展開を無効にするか、テキスト エディターでプロジェクト ファイルを編集してください。 +The following are literal names and should not be translated: +{0} - A filename +{1} - The tool that was used to deploy the file. +{2} - The actual error that occured. + + + ターゲット デバイスまたはエミュレーターで、'log.redirect-stdio' Android システム プロパティが 'true' に設定されています。 +高速展開との互換性を有効にするには、プロパティを 'false' に設定するか、デバイスまたはエミュレーターを再起動して値をクリアします。 + + + '{0}' の展開中にエラーが発生しました。 +'EmbedAssembliesIntoApk' MSBuild プロパティを 'true' に設定して、Visual Studio プロジェクト プロパティ ページで高速展開を無効にするか、テキスト エディターでプロジェクト ファイルを編集してください。 + The following are literal names and should not be translated: +{0} - A filename + + + 申し訳ありません。高速デプロイは、Android 5.0 (API レベル 21) 以上を実行しているデバイスでのみサポートされます。 +'EmbedAssembliesIntoApk' MSBuild プロパティを 'true' に設定して、Visual Studio プロジェクト プロパティ ページで高速展開を無効にするか、テキスト エディターでプロジェクト ファイルを編集してください。 + + + このデバイスで 'run-as' ツールが無効になっています。デバイスの開発者オプションをアクティブ化するか、'ro.boot.disable_runas' を 'false' に設定して有効にします。 + The following are literal names and should not be translated: +'run-as' - a native android tool +'ro.boot.disable_runas' - the android property which disables the `run-as` tool. + + + + パッケージはインストールされませんでした。他のユーザーの下にインストールされていないか確認してください。 +パッケージがデバイスに表示される場合は、手動でアンインストールしてから、もう一度お試しください。 +デバイスの設定アプリを使用してアプリをアンインストールできます。 + The following are literal names and should not be translated: + + + + 高速展開システムに必要な 'run-as' ツールは、製造元によってこのデバイスで無効になっています。 +'EmbedAssembliesIntoApk' MSBuild プロパティを 'true' に設定して、Visual Studio プロジェクト プロパティ ページで高速展開を無効にするか、テキスト エディターでプロジェクト ファイルを編集してください。 + The following are literal names and should not be translated: +'run-as' - a native android tool + + + + アプリケーションの AndroidManifest.xml に 'android:debuggable' 属性が設定されていません。 +これは、高速展開を機能させるために必要です。これは通常、デバッグ ビルドの .NET for Android ビルド +システムにより既定で有効化されます。'AndroidManifest.xml' の 'application' 要素に +この属性が設定されていないことをご確認ください。 +'Android.App.Application' から派生し、'[Application]' を使用しているクラスがある場合は、 +'Debuggable' プロパティは、デバッグ ビルドの値をオーバーライドするため、まったく設定されていません。 + + The following are literal names and should not be translated: +AndroidManifest.xml - A xml file +application - A xml element within a xml file. +Application - A code attribute. +Debuggable - A code property +android:debuggable - An attribute in a xml file. + + + + パッケージは 'system' アプリケーションです。これらは、デバイス上の 'system' ユーザー + の下にインストールされるアプリケーションです。これらの種類のアプリケーションでは、'run-as' を使用できません。 + 高速展開システムは、この特定のエラーを自動的に処理し、 + バックアップのインストールパスを使用すべきでした。ただし、このエラーが表示される場合は、 + Visual Studio で [ヘルプ] -> [フィードバックを送信] -> [問題を報告] メニュー項目を使用するか + または Visual Studio for Mac で [ヘルプ] -> [問題を報告] を使用して実際のエラー メッセージを付けて問題を申請してください。可能ならば、完全な診断ビルド + ログをフィードバック レポートに添付してください。これにより Microsoft が問題を診断するのに役立ちます。 + The following are literal names and should not be translated: + + + + 'run-as' コマンドが '{0}' で失敗しました。 +現在破損しているパッケージのインストール。デバイス上のすべてのユーザーから +パッケージを手動でパッケージをアンインストールし、もう一度やり直してください。問題が解決しない場合は、高速展開を無効にできます。 +高速配置は、Visual Studio プロジェクトのプロパティ ページで無効にしたり、テキスト エディターでプロジェクト ファイルを編集したり、'EmbedAssembliesIntoのまま' MSBuild プロパティを 'true' に設定したりできます。 + + The following are literal names and should not be translated: +'run-as' - a native android tool +{0} - The error message raised by the program. + + + + 'run-as' コマンドが '{0}' で失敗しました。 +このデバイスでは、高速展開は現在サポートされていません。 +Visual Studio で [ヘルプ] -> [フィードバックを送信] -> [問題を報告] メニュー項目を使用するか +または Visual Studio for Mac で [ヘルプ] -> [問題を報告] を使用して実際のエラー メッセージを付けて問題を申請してください。 +'EmbedAssembliesIntoApk' MSBuild プロパティを 'true' に設定して、Visual Studio プロジェクト プロパティ ページで高速展開を無効にするか、テキスト エディターでプロジェクト ファイルを編集してください。 + The following are literal names and should not be translated: +'run-as' - a native android tool +{0} - The error message raised by the program. + + + + '{1}:{2}' を使用して ' {0}' を展開中にエラーが発生しました。 +これは通常、ファイル データがデバイスに送信されるときに 'adb' でエラーが発生したことが原因です。 +この問題の通常の原因は、緩い USB ケーブルまたは低品質の USB ケーブルです。別のケーブルをお試しください。 +または、Wifi デバッグを使用してみてください。これは、特定のデバイスの USB デバッグよりも信頼性が高い可能性があります。 +そのすべてが失敗する場合は、'EmbedAssembliesIntoApk' MSBuild プロパティを 'true' に設定して、Visual Studio プロジェクト プロパティ ページで高速展開を無効にするか、テキスト エディターでプロジェクト ファイルを編集してください。 +The following are literal names and should not be translated: +{0} - A filename +{1} - The tool that was used to deploy the file. +{2} - The actual error that occured. + + + {0}:{1} + でデバッガーを起動しています +sdb を起動し、次のコマンドを実行し + {0} {1} を接続します + + The following are literal names and should not be translated: +{0} - An ip address of a computer. +{1} - An interger value describing the port to use. + + + \ No newline at end of file diff --git a/src/Xamarin.Android.Build.Debugging.Tasks/Properties/Resources.ko.resx b/src/Xamarin.Android.Build.Debugging.Tasks/Properties/Resources.ko.resx new file mode 100644 index 00000000000..eff4ff345a6 --- /dev/null +++ b/src/Xamarin.Android.Build.Debugging.Tasks/Properties/Resources.ko.resx @@ -0,0 +1,264 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + adb가 실행되고 있지 않습니다. {0} + The following are literal names and should not be translated: adb +{0} - further detailed error message + + + 잘못된 adb 대상 이름 '{0}'입니다. 대상 이름은 '-d', '-e' 또는 '-s'로 시작해야 합니다. + The following are literal names and should not be translated: adb, `-d`, `-e`, `-s` +{0} - the target name the user supplied + + + 사용 가능한 디바이스가 없습니다. + + + 선택한 디바이스가 실행되고 있지 않습니다. + + + 연결된 디바이스 또는 에뮬레이터 `{0}`에 대한 Android ABI를 검색할 수 없습니다. 디바이스를 다시 연결하거나 에뮬레이터를 다시 시작한 후 다시 시도하세요. + The following are literal names and should not be translated: ABI +ABI - is an acronym for Application Binary Interface. +{0} - the target name the user supplied + + + + 대상 디바이스의 '{0}/{1}'에 빠른 배포 도구를 복사할 수 없습니다. +이 디바이스는 빠른 배포와 호환되지 않을 수 있습니다. +'EmbedAssembliesIntoApk' MSBuild 속성을 'true'로 설정하여 Visual Studio 프로젝트 속성 페이지에서 빠른 배포를 사용하지 않도록 설정하거나 텍스트 편집기에서 프로젝트 파일을 편집하세요. + The following are literal names and should not be translated: +{0} - A device path +{1} - A filename + + + ‘{1}: {2}’을(를) 사용하여 '{0}'을(를) 배포하는 동안 오류가 발생했습니다. +'EmbedAssembliesIntoApk' MSBuild 속성을 'true'로 설정하여 Visual Studio 프로젝트 속성 페이지에서 빠른 배포를 사용하지 않도록 설정하거나 텍스트 편집기에서 프로젝트 파일을 편집하세요. +The following are literal names and should not be translated: +{0} - A filename +{1} - The tool that was used to deploy the file. +{2} - The actual error that occured. + + + 대상 디바이스 또는 에뮬레이터에서 'log.redirect-stdio' Android 시스템 속성이 'true'로 설정됩니다. +빠른 배포와의 호환성을 활성화하려면 속성을 'false'로 설정하거나 디바이스 또는 에뮬레이터를 다시 부팅하여 값을 지웁니다. + + + '{0}'을(를) 배포하는 동안 오류가 발생했습니다. +'EmbedAssembliesIntoApk' MSBuild 속성을 'true'로 설정하여 Visual Studio 프로젝트 속성 페이지에서 빠른 배포를 사용하지 않도록 설정하거나 텍스트 편집기에서 프로젝트 파일을 편집하세요. + The following are literal names and should not be translated: +{0} - A filename + + + 죄송합니다. 빠른 배포는 Android 5.0(API 수준 21) 이상을 실행하는 디바이스에서만 지원됩니다. +'EmbedAssembliesIntoApk' MSBuild 속성을 'true'로 설정하여 Visual Studio 프로젝트 속성 페이지에서 빠른 배포를 사용하지 않도록 설정하거나 텍스트 편집기에서 프로젝트 파일을 편집하세요. + + + 이 디바이스에서 'run-as' 도구를 사용할 수 없습니다. 디바이스에서 개발자 옵션을 활성화하거나 'ro.boot.disable_runas'를 'false'로 설정하여 사용하도록 설정합니다. + The following are literal names and should not be translated: +'run-as' - a native android tool +'ro.boot.disable_runas' - the android property which disables the `run-as` tool. + + + + 패키지가 설치되지 않았습니다. 다른 사용자 아래에 설치되어 있지 않은지 확인하세요. +패키지가 디바이스에 표시되는 경우 수동으로 제거한 후 다시 시도하세요. +디바이스에서 설정 앱을 통해 앱을 제거할 수 있어야 합니다. + The following are literal names and should not be translated: + + + + 제조업체에서 빠른 배포 시스템에 필요한 'run-as' 도구를 이 디바이스에서 비활성화했습니다. +Visual Studio 프로젝트 속성 페이지에서 빠른 배포를 비활성화하거나 텍스트 편집기에서 프로젝트 파일을 편집하고 'EmbedAssembliesIntoApk' MSBuild 속성을 'true'로 설정하세요. + The following are literal names and should not be translated: +'run-as' - a native android tool + + + + 애플리케이션에 AndroidManifest.xml에 설정된 'android:debuggable' 특성이 없습니다. +빠른 배포가 작동하려면 이 특성이 필요합니다. 이는 일반적으로 디버그 빌드를 위한 Android 빌드 +시스템용 .NET에 의해 기본적으로 활성화되어 있습니다. 'AndroidManifest.xml'의 'application' 요소에 +이 특성이 설정되어 있는지 확인하세요. +'Android.App.Application'에서 파생된 클래스가 있고 '[Application]'을 사용하는 경우 +'Debuggable' 속성이 전혀 설정되지 않았는지 확인하세요. 이는 디버그 빌드에 대한 값을 재정의합니다. + + The following are literal names and should not be translated: +AndroidManifest.xml - A xml file +application - A xml element within a xml file. +Application - A code attribute. +Debuggable - A code property +android:debuggable - An attribute in a xml file. + + + + 패키지는 'system' 애플리케이션입니다. 이러한 애플리케이션은 디바이스의 + 'system' 사용자로 설치됩니다. 이러한 유형의 애플리케이션은 'run-as'를 사용할 수 없습니다. + 빠른 배포 시스템에서는 이 특정 오류를 자동으로 처리하고 + 백업 설치 경로를 사용했어야 합니다. 그러나 이 오류가 표시되면 + Visual Studio의 '도움말->피드백 보내기->문제 보고' 메뉴 항목 또는 Mac용 Visual Studio의 '도움말->문제 보고'를 사용하여 + 정확한 오류 메시지와 함께 문제를 제출하세요. 가능한 경우 문제를 진단하는 데 도움이 되므로 + 전체 진단 빌드 로그를 피드백 보고서에 첨부하세요. + The following are literal names and should not be translated: + + + + 'run-as' 명령이 '{0}'(으)로 실패했습니다. +패키지의 현재 설치가 손상되었습니다. 디바이스의 모든 사용자로부터 패키지를 +수동으로 제거하고 다시 시도하세요. 작동하지 않으면 빠른 배포를 비활성화할 수 있습니다. +빠른 배포는 Visual Studio 프로젝트 속성 페이지에서 비활성화하거나 텍스트 편집기에서 프로젝트 파일을 편집하고 'EmbedAssembliesIntoApk' MSBuild 속성을 'true'로 설정할 수 있습니다. + + The following are literal names and should not be translated: +'run-as' - a native android tool +{0} - The error message raised by the program. + + + + 'run-as' 명령이 '{0}'(으)로 실패했습니다. +빠른 배포는 현재 이 디바이스에서 지원되지 않습니다. +Visual Studio의 '도움말->피드백 보내기->문제 보고' 메뉴 항목 또는 Mac용 Visual Studio의 '도움말->문제 보고'를 사용하여 +정확한 오류 메시지와 함께 문제를 제출하세요. +'EmbedAssembliesIntoApk' MSBuild 속성을 'true'로 설정하여 Visual Studio 프로젝트 속성 페이지에서 빠른 배포를 사용하지 않도록 설정하거나 텍스트 편집기에서 프로젝트 파일을 편집하세요. + The following are literal names and should not be translated: +'run-as' - a native android tool +{0} - The error message raised by the program. + + + + ‘{1}: {2}’을(를) 사용하여 '{0}'을(를) 배포하는 동안 오류가 발생했습니다. +이는 일반적으로 파일 데이터가 디바이스로 전송될 때 'adb'의 오류 때문입니다. +일반적인 원인은 느리거나 품질이 낮은 USB 케이블입니다. 다른 케이블을 사용해 보세요. +또는 Wifi 디버깅을 사용해 보세요. 이는 특정 디바이스에 대한 USB 디버깅보다 더 안정적일 수 있습니다. +이 모든 작업이 실패하면 'EmbedAssembliesIntoApk' MSBuild 속성을 'true'로 설정하여 Visual Studio 프로젝트 속성 페이지에서 빠른 배포를 사용하지 않도록 설정하거나 텍스트 편집기에서 프로젝트 파일을 편집하세요. +The following are literal names and should not be translated: +{0} - A filename +{1} - The tool that was used to deploy the file. +{2} - The actual error that occured. + + + {0}에서 디버거를 시작하는 중:{1} +sdb를 시작한 다음, 다음 명령을 실행합니다. + connect {0} {1} + + The following are literal names and should not be translated: +{0} - An ip address of a computer. +{1} - An interger value describing the port to use. + + + \ No newline at end of file diff --git a/src/Xamarin.Android.Build.Debugging.Tasks/Properties/Resources.pl.resx b/src/Xamarin.Android.Build.Debugging.Tasks/Properties/Resources.pl.resx new file mode 100644 index 00000000000..47bba6316f2 --- /dev/null +++ b/src/Xamarin.Android.Build.Debugging.Tasks/Properties/Resources.pl.resx @@ -0,0 +1,264 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Narzędzie adb nie jest uruchomione. {0} + The following are literal names and should not be translated: adb +{0} - further detailed error message + + + Nieprawidłowa nazwa elementu docelowego adb „{0}”. Nazwy obiektów docelowych muszą zaczynać się od `-d`, `-e`, or `-s`. + The following are literal names and should not be translated: adb, `-d`, `-e`, `-s` +{0} - the target name the user supplied + + + Brak dostępnego urządzenia. + + + Wybrane urządzenie nie jest uruchomione. + + + Nie można pobrać interfejsu ABI systemu Android dla dołączonego urządzenia lub emulatora `{0}`. Podłącz ponownie urządzenie lub uruchom ponownie emulator i spróbuj ponownie. + The following are literal names and should not be translated: ABI +ABI - is an acronym for Application Binary Interface. +{0} - the target name the user supplied + + + + Nie można skopiować narzędzi szybkiego wdrażania do folderu „{0}/{1}” na urządzeniu docelowym. +To urządzenie może nie być zgodne z szybkim wdrażaniem. +Ustaw właściwość MSBuild „EmbedAssembliesIntoApk” na wartość „true”, aby wyłączyć szybkie wdrażanie na stronach właściwości projektu programu Visual Studio, lub edytuj plik projektu w edytorze tekstów. + The following are literal names and should not be translated: +{0} - A device path +{1} - A filename + + + Błąd podczas wdrażania elementu „{0}” przy użyciu elementu „{1}: {2}„. +Ustaw właściwość MSBuild „EmbedAssembliesIntoApk” na wartość „true”, aby wyłączyć szybkie wdrażanie na stronach właściwości projektu programu Visual Studio, lub edytuj plik projektu w edytorze tekstów. +The following are literal names and should not be translated: +{0} - A filename +{1} - The tool that was used to deploy the file. +{2} - The actual error that occured. + + + Właściwość systemu Android „log.redirect-stdio” jest ustawiona na wartość „true” na urządzeniu docelowym lub emulatorze. +Aby włączyć zgodność z szybkim wdrażaniem, ustaw właściwość na wartość „false” lub uruchom ponownie urządzenie lub emulator, aby wyczyścić wartość. + + + Błąd podczas wdrażania elementu „{0}”. +Ustaw właściwość MSBuild „EmbedAssembliesIntoApk” na wartość „true”, aby wyłączyć szybkie wdrażanie na stronach właściwości projektu programu Visual Studio, lub edytuj plik projektu w edytorze tekstów. + The following are literal names and should not be translated: +{0} - A filename + + + Przepraszamy. Szybkie wdrażanie jest obsługiwane tylko na urządzeniach z systemem Android 5.0 (poziom 21 interfejsu API) lub nowszym. +Ustaw właściwość MSBuild „EmbedAssembliesIntoApk” na wartość „true”, aby wyłączyć szybkie wdrażanie na stronach właściwości projektu programu Visual Studio, lub edytuj plik projektu w edytorze tekstów. + + + Narzędzie „run-as” zostało wyłączone na tym urządzeniu. Włącz je, aktywując opcje dewelopera na urządzeniu lub ustawiając „ro.boot.disable_runas na wartość „false”. + The following are literal names and should not be translated: +'run-as' - a native android tool +'ro.boot.disable_runas' - the android property which disables the `run-as` tool. + + + + Pakiet nie został zainstalowany. Sprawdź, czy nie masz zainstalowanej aplikacji pod żadnym innym użytkownikiem. +Jeśli pakiet jest wyświetlany na urządzeniu, spróbuj odinstalować go ręcznie, a następnie spróbuj ponownie. +Powinno być możliwe odinstalowanie aplikacji za pomocą aplikacji Ustawienia na urządzeniu. + The following are literal names and should not be translated: + + + + Narzędzie „Run-as” wymagane przez system szybkiego wdrażania zostało wyłączone na tym urządzeniu przez producenta. +Wyłącz szybkie wdrażanie na stronach właściwości projektu programu Visual Studio lub edytuj plik projektu w edytorze tekstów i ustaw właściwość MSBuild „EmbedAssembliesIntoApk” na wartość „true”. + The following are literal names and should not be translated: +'run-as' - a native android tool + + + + Aplikacja nie ma atrybutu „android:debuggable” ustawionego w AndroidManifest.xml. +Jest to wymagane, aby szybkie wdrażanie działało. Ta opcja jest domyślnie włączona przez +platformę .NET dla systemu Android na potrzeby kompilacji debugowania. Sprawdź, czy nie masz tego atrybutu +ustawionego w elemencie „application" w elemencie „AndroidManifest.xml”. +Jeśli masz klasę pochodzącą od elementu „Android.App.Application” i używasz „[Application]”, upewnij się, że +Właściwość „Debuggable” nie jest w ogóle ustawiona, ponieważ zastąpi wartość kompilacji debugowania. + + The following are literal names and should not be translated: +AndroidManifest.xml - A xml file +application - A xml element within a xml file. +Application - A code attribute. +Debuggable - A code property +android:debuggable - An attribute in a xml file. + + + + Pakiet jest aplikacją „systemową”. Są to aplikacje instalowane w obszarze + użytkownika „systemowego” na urządzeniu. Tego typu aplikacje nie mogą używać polecenia „run-as”. + System szybkiego wdrażania powinien automatycznie obsłużyć ten konkretny błąd + i użyć ścieżki instalacji kopii zapasowej. Jeśli jednak widzisz ten błąd, zgłoś + problem z dokładnym komunikatem o błędzie przy użyciu elementu menu „Pomoc->Prześlij opinię->Zgłoś problem” w programie Visual Studio + lub „Pomoc->Zgłoś problem” w programie Visual Studio dla komputerów Mac. Jeśli to możliwe, dołącz dziennik kompilacji + pełnej diagnostyki do raportu opinii, ponieważ pomoże nam to zdiagnozować problem. + The following are literal names and should not be translated: + + + + Wykonanie polecenia „run-as” nie powiodło się z „{0}”. +Bieżąca instalacja pakietu jest uszkodzona. Ręcznie odinstaluj +pakiet ze wszystkich użytkowników na urządzeniu i spróbuj ponownie. Jeśli to nie zadziała, możesz wyłączyć szybkie wdrażanie. +Szybkie wdrażanie można wyłączyć na stronach właściwości projektu programu Visual Studio lub edytować plik projektu w edytorze tekstów i ustawić właściwość „EmbedAssembliesIntoApk” programu MSBuild na wartość „true”. + + The following are literal names and should not be translated: +'run-as' - a native android tool +{0} - The error message raised by the program. + + + + Wykonanie polecenia „run-as” nie powiodło się z „{0}”. +Szybkie wdrażanie nie jest obecnie obsługiwane na tym urządzeniu. +Zgłoś problem z dokładnym komunikatem o błędzie, używając elementu menu „Pomoc-> Prześlij opinię->Zgłoś problem” w programie Visual Studio +lub „Pomoc->Zgłoś problem” w programie Visual Studio dla komputerów Mac. +Ustaw właściwość MSBuild „EmbedAssembliesIntoApk” na wartość „true”, aby wyłączyć szybkie wdrażanie na stronach właściwości projektu programu Visual Studio, lub edytuj plik projektu w edytorze tekstów. + The following are literal names and should not be translated: +'run-as' - a native android tool +{0} - The error message raised by the program. + + + + Błąd podczas wdrażania elementu „{0}” przy użyciu elementu „{1}: {2}”. +Zwykle jest to spowodowane błędem w pliku „adb”, gdy dane pliku są wysyłane do urządzenia. +Normalną przyczyną takiego działania jest luźny lub niskiej jakości kabel USB. Spróbuj użyć innego kabla. +Możesz też spróbować użyć debugowania sieci Wi-Fi, ponieważ może to być bardziej niezawodne niż debugowanie USB dla niektórych urządzeń. +Jeśli to wszystko się nie powiedzie, ustaw właściwość MSBuild „EmbedAssembliesIntoApk” na wartość „true”, aby wyłączyć szybkie wdrażanie na stronach właściwości projektu programu Visual Studio lub edytować plik projektu w edytorze tekstów. +The following are literal names and should not be translated: +{0} - A filename +{1} - The tool that was used to deploy the file. +{2} - The actual error that occured. + + + Uruchamianie debugera w {0}:{1} +Uruchom bazę SDB, a następnie uruchom następujące polecenie + połącz {0} {1} + + The following are literal names and should not be translated: +{0} - An ip address of a computer. +{1} - An interger value describing the port to use. + + + \ No newline at end of file diff --git a/src/Xamarin.Android.Build.Debugging.Tasks/Properties/Resources.pt-BR.resx b/src/Xamarin.Android.Build.Debugging.Tasks/Properties/Resources.pt-BR.resx new file mode 100644 index 00000000000..fa96dcde4e6 --- /dev/null +++ b/src/Xamarin.Android.Build.Debugging.Tasks/Properties/Resources.pt-BR.resx @@ -0,0 +1,264 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + o adb não está em execução. {0} + The following are literal names and should not be translated: adb +{0} - further detailed error message + + + O nome '{0}' de destino do adb é inválido. Os nomes de destino devem iniciar com `-d`, `-e` ou `-s`. + The following are literal names and should not be translated: adb, `-d`, `-e`, `-s` +{0} - the target name the user supplied + + + Nenhum dispositivo disponível. + + + O dispositivo selecionado não está em execução. + + + Não foi possível recuperar a ABI do Android para o dispositivo ou emulador anexado `{0}`. Reconecte o dispositivo ou reinicie o emulador e tente novamente. + The following are literal names and should not be translated: ABI +ABI - is an acronym for Application Binary Interface. +{0} - the target name the user supplied + + + + Não é possível copiar as ferramentas de Implantação Rápida para "{0}/{1}" no dispositivo de destino. +Este dispositivo pode não ser compatível com a Implantação Rápida. +Defina a propriedade MSBuild "EmbedAssembliesIntoApk" como "true" para desabilitar a Implantação Rápida nas páginas de propriedades do projeto do Visual Studio ou edite o arquivo do projeto em um editor de texto. + The following are literal names and should not be translated: +{0} - A device path +{1} - A filename + + + Erro ao implantar "{0}" usando "{1}: {2}". +Defina a propriedade MSBuild "EmbedAssembliesIntoApk" como "true" para desabilitar a Implantação Rápida nas páginas de propriedades do projeto do Visual Studio ou edite o arquivo do projeto em um editor de texto. +The following are literal names and should not be translated: +{0} - A filename +{1} - The tool that was used to deploy the file. +{2} - The actual error that occured. + + + A propriedade do sistema Android "log.redirect-stdio" está definida como "true" no dispositivo de destino ou no emulador. +Para habilitar a compatibilidade com a Implantação Rápida, defina a propriedade como "false" ou reinicie o dispositivo ou o emulador para limpar o valor. + + + Erro ao implantar "{0}". +Defina a propriedade MSBuild "EmbedAssembliesIntoApk" como "true" para desabilitar a Implantação Rápida nas páginas de propriedades do projeto do Visual Studio ou edite o arquivo do projeto em um editor de texto. + The following are literal names and should not be translated: +{0} - A filename + + + Lamentamos. A Implantação Rápida só tem suporte em dispositivos Android 5.0 (nível de API 21) ou superior. +Defina a propriedade MSBuild "EmbedAssembliesIntoApk" como "true" para desabilitar a Implantação Rápida nas páginas de propriedades do projeto do Visual Studio ou edite o arquivo do projeto em um editor de texto. + + + A ferramenta 'executar como' foi desabilitada neste dispositivo. Habilite-a ativando as opções de desenvolvedor no dispositivo ou definindo `ro.boot.disable_runas` como `false`. + The following are literal names and should not be translated: +'run-as' - a native android tool +'ro.boot.disable_runas' - the android property which disables the `run-as` tool. + + + + O pacote não foi instalado. Verifique se ele não está instalado em outro usuário. +Se o pacote aparecer no dispositivo, tente desinstalá-lo manualmente e tente novamente. +Você deve conseguir desinstalar o aplicativo por meio do aplicativo Configurações no dispositivo. + The following are literal names and should not be translated: + + + + A ferramenta "run-as" exigida pelo sistema de Implantação Rápida foi desabilitada neste dispositivo pelo fabricante. +Desabilite a Implantação Rápida nas páginas de propriedades do projeto do Visual Studio ou edite o arquivo do projeto em um editor de texto e defina a propriedade MSBuild "EmbedAssembliesIntoApk" como "true". + The following are literal names and should not be translated: +'run-as' - a native android tool + + + + O aplicativo não tem o atributo "android:debuggable" definido no AndroidManifest.xml. +Isso é necessário para que a Implantação Rápida funcione. Normalmente, isso é habilitado por padrão por +o sistema de compilação do .NET para Android para compilações de Depuração. Verifique se você não tem este atributo +definido no elemento "application" em seu "AndroidManifest.xml". +Se você tiver uma classe derivada de "Android.App.Application" e estiver usando o "[Application]", verifique se a propriedade "Debuggable" +não está definida, pois substituirá o valor de compilações de depuração. + + The following are literal names and should not be translated: +AndroidManifest.xml - A xml file +application - A xml element within a xml file. +Application - A code attribute. +Debuggable - A code property +android:debuggable - An attribute in a xml file. + + + + O pacote é um aplicativo "system". Esses são os aplicativos instalados no + usuário "system" em um dispositivo. Esses tipos de aplicativos não podem usar "run-as". + O sistema de Implantação Rápida deve ter tratado esse erro específico automaticamente + e usado um caminho de instalação de backup. No entanto, se você estiver vendo esse erro, registre + um problema com a mensagem de erro exata usando o item de menu "Ajuda->Enviar comentários->Relatar um Problema" no Visual Studio + ou "Ajuda->Relatar um problema" no Visual Studio para Mac. Se possível, anexe um log de compilação de diagnóstico completo + ao relatório de comentários, pois isso nos ajudará a diagnosticar o problema. + The following are literal names and should not be translated: + + + + O comando "run-as" falhou com "{0}". +A instalação atual do pacote está corrompida. Desinstale manualmente o pacote +de todos os usuários no dispositivo e tente novamente. Se isso não funcionar, você poderá desabilitar a Implantação Rápida. +A Implantação Rápida pode ser desabilitada nas páginas de propriedades do projeto do Visual Studio ou edite o arquivo do projeto em um editor de texto e defina a propriedade MSBuild "EmbedAssembliesIntoApk" como "true". + + The following are literal names and should not be translated: +'run-as' - a native android tool +{0} - The error message raised by the program. + + + + O comando "run-as" falhou com "{0}". +No momento, não há suporte para a Implantação Rápida neste dispositivo. +Registre um problema com a mensagem de erro exata usando o item de menu "Ajuda->Enviar comentários->Relatar um Problema" no Visual Studio +ou "Ajuda->Relatar um problema" no Visual Studio para Mac. +Defina a propriedade MSBuild "EmbedAssembliesIntoApk" como "true" para desabilitar a Implantação Rápida nas páginas de propriedades do projeto do Visual Studio ou edite o arquivo do projeto em um editor de texto. + The following are literal names and should not be translated: +'run-as' - a native android tool +{0} - The error message raised by the program. + + + + Erro ao implantar "{0}" usando "{1}: {2}". +Isso geralmente ocorre devido a um erro em `adb` quando os dados do arquivo são enviados para o dispositivo. +A causa normal para isso é um cabo USB solto ou de baixa qualidade. Tente um cabo diferente. +Como alternativa, tente usar a Depuração por Wi-Fi, isso pode ser mais confiável do que a depuração por USB para determinados dispositivos. +Se tudo falhar, defina a propriedade MSBuild "EmbedAssembliesIntoApk" como "true" para desabilitar a Implantação Rápida nas páginas de propriedades do projeto do Visual Studio ou edite o arquivo do projeto em um editor de texto. +The following are literal names and should not be translated: +{0} - A filename +{1} - The tool that was used to deploy the file. +{2} - The actual error that occured. + + + Iniciando o depurador em {0}:{1} +Inicie o sdb e depois execute o seguinte comando + conectar {0} {1} + + The following are literal names and should not be translated: +{0} - An ip address of a computer. +{1} - An interger value describing the port to use. + + + \ No newline at end of file diff --git a/src/Xamarin.Android.Build.Debugging.Tasks/Properties/Resources.resx b/src/Xamarin.Android.Build.Debugging.Tasks/Properties/Resources.resx new file mode 100644 index 00000000000..f4c6ea5ca2e --- /dev/null +++ b/src/Xamarin.Android.Build.Debugging.Tasks/Properties/Resources.resx @@ -0,0 +1,264 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + adb is not running. {0} + The following are literal names and should not be translated: adb +{0} - further detailed error message + + + Invalid adb target name '{0}'. Target names must start with `-d`, `-e`, or `-s`. + The following are literal names and should not be translated: adb, `-d`, `-e`, `-s` +{0} - the target name the user supplied + + + No available device. + + + Selected device is not running. + + + Could not retrieve the Android ABI for the attached device or emulator `{0}`. Please reconnect the device or restart the emulator, and try again. + The following are literal names and should not be translated: ABI +ABI - is an acronym for Application Binary Interface. +{0} - the target name the user supplied + + + + Unable to copy Fast Deployment tools to '{0}/{1}' on the target device. +This device might not be compatible with Fast Deployment. +Please set the 'EmbedAssembliesIntoApk' MSBuild property to 'true' to disable Fast Deployment in the Visual Studio project property pages, or edit the project file in a text editor. + The following are literal names and should not be translated: +{0} - A device path +{1} - A filename + + + Error deploying '{0}' using '{1}: {2}'. +Please set the 'EmbedAssembliesIntoApk' MSBuild property to 'true' to disable Fast Deployment in the Visual Studio project property pages, or edit the project file in a text editor. +The following are literal names and should not be translated: +{0} - A filename +{1} - The tool that was used to deploy the file. +{2} - The actual error that occured. + + + The 'log.redirect-stdio' Android system property is set to 'true' on the target device or emulator. +To enable compatibility with Fast Deployment, set the property to 'false' or reboot the device or emulator to clear the value. + + + Error deploying '{0}'. +Please set the 'EmbedAssembliesIntoApk' MSBuild property to 'true' to disable Fast Deployment in the Visual Studio project property pages, or edit the project file in a text editor. + The following are literal names and should not be translated: +{0} - A filename + + + Sorry. Fast Deployment is only supported on devices running Android 5.0 (API level 21) or higher. +Please set the 'EmbedAssembliesIntoApk' MSBuild property to 'true' to disable Fast Deployment in the Visual Studio project property pages, or edit the project file in a text editor. + + + The 'run-as' tool has been disabled on this device. Either enable it by activating the developer options on the device or by setting `ro.boot.disable_runas` to `false`. + The following are literal names and should not be translated: +'run-as' - a native android tool +'ro.boot.disable_runas' - the android property which disables the `run-as` tool. + + + + The package was not installed. Please check you do not have it installed under any other user. +If the package does show up on the device, try manually uninstalling it then try again. +You should be able to uninstall the app via the Settings app on the device. + The following are literal names and should not be translated: + + + + The 'run-as' tool required by the Fast Deployment system has been disabled on this device by the manufacturer. +Please disable Fast Deployment in the Visual Studio project property pages or edit the project file in a text editor and set the 'EmbedAssembliesIntoApk' MSBuild property to 'true'. + The following are literal names and should not be translated: +'run-as' - a native android tool + + + + The application does not have the 'android:debuggable' attribute set in the AndroidManifest.xml. +This is required in order for Fast Deployment to work. This is normally enabled by default by +the .NET for Android build system for Debug builds. Please check that you to not have this attribute +set on the 'application' element in your 'AndroidManifest.xml'. +If you have a class that derives from 'Android.App.Application' and are using the '[Application]' make sure the +'Debuggable' property is not set at all as it will override the value for debug builds. + + The following are literal names and should not be translated: +AndroidManifest.xml - A xml file +application - A xml element within a xml file. +Application - A code attribute. +Debuggable - A code property +android:debuggable - An attribute in a xml file. + + + + The package is a 'system' application. These are applications which install under + the 'system' user on a device. These types of applications cannot use 'run-as'. + The Fast Deployment system should have handled this particular error automatically + and used a backup installation path. However if you are seeing this error please file + an issue with the exact error message using the 'Help->Send Feedback->Report a Problem' menu item in Visual Studio + or 'Help->Report a Problem' in Visual Studio for Mac. If possible attach a full diagnostic build + log to the feedback report as this will help us diagnose the issue. + The following are literal names and should not be translated: + + + + The 'run-as' command failed with '{0}'. +The currently installation of the package in corrupt. Please manually uninstall the +package from all the users on device and try again. If that does not work you can disable Fast Deployment. +Fast Deployment can be disabled in the Visual Studio project property pages or edit the project file in a text editor and set the 'EmbedAssembliesIntoApk' MSBuild property to 'true'. + + The following are literal names and should not be translated: +'run-as' - a native android tool +{0} - The error message raised by the program. + + + + The 'run-as' command failed with '{0}'. +Fast Deployment is not currently supported on this device. +Please file an issue with the exact error message using the 'Help->Send Feedback->Report a Problem' menu item in Visual Studio +or 'Help->Report a Problem' in Visual Studio for Mac. +Please set the 'EmbedAssembliesIntoApk' MSBuild property to 'true' to disable Fast Deployment in the Visual Studio project property pages, or edit the project file in a text editor. + The following are literal names and should not be translated: +'run-as' - a native android tool +{0} - The error message raised by the program. + + + + Error deploying '{0}' using '{1}: {2}'. +This is usually due to an error in `adb` when the file data is sent over to the device. +The normal cause for this is a loose or low quality USB cable. Please try a different cable. +Alternatively try using Wifi Debugging, this can be more reliable than USB debugging for certain devices. +If all of that fails please set the 'EmbedAssembliesIntoApk' MSBuild property to 'true' to disable Fast Deployment in the Visual Studio project property pages, or edit the project file in a text editor. +The following are literal names and should not be translated: +{0} - A filename +{1} - The tool that was used to deploy the file. +{2} - The actual error that occured. + + + Starting Debugger on {0}:{1} +Start sdb then run the following command + connect {0} {1} + + The following are literal names and should not be translated: +{0} - An ip address of a computer. +{1} - An interger value describing the port to use. + + + \ No newline at end of file diff --git a/src/Xamarin.Android.Build.Debugging.Tasks/Properties/Resources.ru.resx b/src/Xamarin.Android.Build.Debugging.Tasks/Properties/Resources.ru.resx new file mode 100644 index 00000000000..a7e76696433 --- /dev/null +++ b/src/Xamarin.Android.Build.Debugging.Tasks/Properties/Resources.ru.resx @@ -0,0 +1,264 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + adb не запущен. {0} + The following are literal names and should not be translated: adb +{0} - further detailed error message + + + Недопустимое имя целевого объекта ADB "{0}". Имена целевых объектов должны начинаться с "-d", "-e" или "-s". + The following are literal names and should not be translated: adb, `-d`, `-e`, `-s` +{0} - the target name the user supplied + + + Нет доступного устройства. + + + Выбранное устройство не запущено. + + + Не удалось получить Android ABI для подключенного устройства или эмулятора "{0}". Подключите устройство повторно или перезапустите эмулятор и повторите попытку. + The following are literal names and should not be translated: ABI +ABI - is an acronym for Application Binary Interface. +{0} - the target name the user supplied + + + + Невозможно скопировать инструменты быстрого развертывания в " {0} / {1} " на целевом устройстве. +Это устройство может быть несовместимо с функцией быстрого развертывания. +Установите для свойства MSBuild "EmbedAssembliesIntoApk" значение "true", чтобы отключить быстрое развертывание на страницах свойств проекта Visual Studio, или отредактируйте файл проекта в текстовом редакторе. + The following are literal names and should not be translated: +{0} - A device path +{1} - A filename + + + Ошибка развертывания " {0} " с использованием "{1} : {2} ". +Установите для свойства MSBuild "EmbedAssembliesIntoApk" значение "true", чтобы отключить быстрое развертывание на страницах свойств проекта Visual Studio, или отредактируйте файл проекта в текстовом редакторе. +The following are literal names and should not be translated: +{0} - A filename +{1} - The tool that was used to deploy the file. +{2} - The actual error that occured. + + + Системное свойство Android "log.redirect-stdio" установлено в значение "true" на целевом устройстве или эмуляторе. +Чтобы включить совместимость с быстрым развертыванием, установите для свойства значение "false" или перезагрузите устройство или эмулятор, чтобы очистить значение. + + + Ошибка развертывания ' {0} '. +Установите для свойства MSBuild "EmbedAssembliesIntoApk" значение "true", чтобы отключить быстрое развертывание на страницах свойств проекта Visual Studio, или отредактируйте файл проекта в текстовом редакторе. + The following are literal names and should not be translated: +{0} - A filename + + + Приносим свои извинения. Быстрое развертывание поддерживается только на устройствах под управлением Android 5.0 (уровень API 21) или выше. +Установите для свойства MSBuild "EmbedAssembliesIntoApk" значение "true", чтобы отключить быстрое развертывание на страницах свойств проекта Visual Studio, или отредактируйте файл проекта в текстовом редакторе. + + + Средство запуска от имени отключено на этом устройстве. Включите его, активировав параметры разработчика на устройстве или установив для ro.boot.disable_runas значение ЛОЖЬ. + The following are literal names and should not be translated: +'run-as' - a native android tool +'ro.boot.disable_runas' - the android property which disables the `run-as` tool. + + + + Пакет не был установлен. Проверьте, не установлен ли он под другим пользователем. +Если пакет отображается на устройстве, попробуйте удалить его вручную, а затем повторите попытку. +Вы можете удалить приложение через приложение "Настройки" на устройстве. + The following are literal names and should not be translated: + + + + Инструмент "запустить как", необходимый для системы быстрого развертывания, был отключен на этом устройстве производителем. +Отключите быстрое развертывание на страницах свойств проекта Visual Studio или отредактируйте файл проекта в текстовом редакторе и установите для свойства MSBuild "EmbedAssembliesIntoApk" значение "true". + The following are literal names and should not be translated: +'run-as' - a native android tool + + + + В AndroidManifest.xml для приложения не установлен атрибут "android:debuggable". +Это необходимо для работы функции быстрого развертывания. Обычно эта функция включена по умолчанию +в системе сборки .NET для Android для отладочных сборок. Убедитесь, что этот атрибут не установлен +для элемента "application" в файле "AndroidManifest.xml". +Если у вас есть класс, производный от "Android.App.Application", и вы используете "[Application]", убедитесь, что +Свойство "Debuggable" вообще не задано, так как оно переопределит значение для отладочных сборок. + + The following are literal names and should not be translated: +AndroidManifest.xml - A xml file +application - A xml element within a xml file. +Application - A code attribute. +Debuggable - A code property +android:debuggable - An attribute in a xml file. + + + + Пакет представляет собой "системное" приложение. Это приложения, которые устанавливаются на + устройстве под учетной записью "системного" пользователя. Приложения такого типа не могут использовать функцию "запуск от имени". + Система быстрого развертывания должна была автоматически обработать эту конкретную ошибку + и использовать резервный путь установки. Однако если вы видите эту ошибку, + сообщите о проблеме, указав точное сообщение об ошибке, используя пункт меню "Справка->Отправить отзыв->Сообщить о проблеме" в Visual Studio. + или "Справка->Сообщить о проблеме" в Visual Studio для Mac. Если возможно, прикрепите к отзыву + полный журнал диагностической сборки, так как это поможет нам диагностировать проблему. + The following are literal names and should not be translated: + + + + Команда "запустить как" не выполнена с " {0} ". +Текущая установка пакета повреждена. Вручную удалите +пакет от всех пользователей на устройстве и повторите попытку. Если это не сработает, вы можете отключить быстрое развертывание. +Быстрое развертывание можно отключить на страницах свойств проекта Visual Studio или отредактировать файл проекта в текстовом редакторе и установить для свойства MSBuild "EmbedAssembliesIntoApk" значение "true". + + The following are literal names and should not be translated: +'run-as' - a native android tool +{0} - The error message raised by the program. + + + + Команда "запустить как" не выполнена с " {0} ". +Быстрое развертывание в настоящее время не поддерживается на этом устройстве. +Отправьте сообщение об ошибке с точным текстом сообщения об ошибке, используя пункт меню "Справка->Отправить отзыв->Сообщить о проблеме" в Visual Studio +или "Справка->Сообщить о проблеме" в Visual Studio для Mac. +Установите для свойства MSBuild "EmbedAssembliesIntoApk" значение "true", чтобы отключить быстрое развертывание на страницах свойств проекта Visual Studio, или отредактируйте файл проекта в текстовом редакторе. + The following are literal names and should not be translated: +'run-as' - a native android tool +{0} - The error message raised by the program. + + + + Ошибка развертывания " {0} " с использованием " {1}" : "{2} ". +Обычно это происходит из-за ошибки в `adb` при отправке данных файла на устройство. +Обычной причиной этого является неплотно подключенный или некачественный USB-кабель. Попробуйте использовать другой кабель. +В качестве альтернативы попробуйте использовать отладку по Wi-Fi, для некоторых устройств это может быть более надежно, чем отладка по USB. +Если все это не помогло, установите для свойства MSBuild "EmbedAssembliesIntoApk" значение "true", чтобы отключить быстрое развертывание на страницах свойств проекта Visual Studio, или отредактируйте файл проекта в текстовом редакторе. +The following are literal names and should not be translated: +{0} - A filename +{1} - The tool that was used to deploy the file. +{2} - The actual error that occured. + + + Запуск отладчика на {0} : {1} +Запустите sdb, затем выполните следующую команду + подключить {0} {1} + + The following are literal names and should not be translated: +{0} - An ip address of a computer. +{1} - An interger value describing the port to use. + + + \ No newline at end of file diff --git a/src/Xamarin.Android.Build.Debugging.Tasks/Properties/Resources.tr.resx b/src/Xamarin.Android.Build.Debugging.Tasks/Properties/Resources.tr.resx new file mode 100644 index 00000000000..1ac47a03b85 --- /dev/null +++ b/src/Xamarin.Android.Build.Debugging.Tasks/Properties/Resources.tr.resx @@ -0,0 +1,264 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + adb çalışmıyor. {0} + The following are literal names and should not be translated: adb +{0} - further detailed error message + + + Geçersiz adb hedef adı '{0}'. Hedef adları '-d', '-e' veya '-s' ile başlamalıdır. + The following are literal names and should not be translated: adb, `-d`, `-e`, `-s` +{0} - the target name the user supplied + + + Kullanılabilir cihaz yok. + + + Seçili cihaz çalışmıyor. + + + İliştirilmiş cihaz veya `{0}` öykünücüsü için Android ABI alınamadı. Lütfen cihazı yeniden bağlayın veya öykünücüyü yeniden başlatıp tekrar deneyin. + The following are literal names and should not be translated: ABI +ABI - is an acronym for Application Binary Interface. +{0} - the target name the user supplied + + + + Hızlı Dağıtım araçları hedef cihazda “{0}/{1}” hedefine kopyalanamıyor. +Bu cihaz Hızlı Dağıtım ile uyumlu olmayabilir. +Hızlı Dağıtımı devre dışı bırakmak için lütfen Visual Studio proje özelliği sayfalarında veya proje dosyasını bir metin düzenleyicisinde düzenleyerek “EmbedAssembliesIntoApk” MSBuild özelliğini “true” olarak ayarlayın. + The following are literal names and should not be translated: +{0} - A device path +{1} - A filename + + + “{1}: {2}” kullanılarak “{0}” dağıtılırken hata oluştu. +Hızlı Dağıtımı devre dışı bırakmak için lütfen Visual Studio proje özelliği sayfalarında veya proje dosyasını bir metin düzenleyicisinde düzenleyerek “EmbedAssembliesIntoApk” MSBuild özelliğini “true” olarak ayarlayın. +The following are literal names and should not be translated: +{0} - A filename +{1} - The tool that was used to deploy the file. +{2} - The actual error that occured. + + + “log.redirect-stdio” Android sistem özelliği hedef cihazda veya emülatörde “true” olarak ayarlanmış. +Hızlı Dağıtım ile uyumluluğu etkinleştirmek için özelliği “false” olarak ayarlayın veya değeri temizlemek için cihazı ya da emülatörü yeniden başlatın. + + + “{0}” dağıtılırken hata oluştu. +Hızlı Dağıtımı devre dışı bırakmak için lütfen Visual Studio proje özelliği sayfalarında veya proje dosyasını bir metin düzenleyicisinde düzenleyerek “EmbedAssembliesIntoApk” MSBuild özelliğini “true” olarak ayarlayın. + The following are literal names and should not be translated: +{0} - A filename + + + Üzgünüz. Hızlı Dağıtım yalnızca Android 5.0 (API düzeyi 21) veya üzerini çalıştıran cihazlarda desteklenir. +Hızlı Dağıtımı devre dışı bırakmak için lütfen Visual Studio proje özelliği sayfalarında veya proje dosyasını bir metin düzenleyicisinde düzenleyerek “EmbedAssembliesIntoApk” MSBuild özelliğini “true” olarak ayarlayın. + + + 'farklı çalıştır' aracı bu cihazda devre dışı bırakıldı. Cihazda geliştirici seçeneklerini etkinleştirerek veya 'ro.boot.disable_runas' değerini 'false' olarak ayarlayarak etkinleştirin. + The following are literal names and should not be translated: +'run-as' - a native android tool +'ro.boot.disable_runas' - the android property which disables the `run-as` tool. + + + + Paket yüklenmedi. Lütfen başka bir kullanıcı altında yüklü olmadığından emin olun. +Paket cihazda görünüyorsa el ile kaldırıp yeniden başlatmayı deneyin. +Uygulamayı cihazdaki Ayarlar uygulaması aracılığıyla kaldırabilirsiniz. + The following are literal names and should not be translated: + + + + Hızlı Dağıtım sistemi için gereken “run-as” aracı üretici tarafından bu cihazda devre dışı bırakıldı. +Visual Studio proje özelliği sayfalarında Hızlı Dağıtımı devre dışı bırakın veya proje dosyasını bir metin düzenleyicisinde düzenleyerek “EmbedAssembliesIntoApk” MSBuild özelliğini “true” olarak ayarlayın. + The following are literal names and should not be translated: +'run-as' - a native android tool + + + + AndroidManifest.xml dosyasında uygulamanın “android:debuggable” özniteliği ayarlanmamış. +Hızlı Dağıtımın çalışması için bu gereklidir. Bu, normalde Hata ayıklama derlemelerine yönelik olarak Android derleme sistemi için +.NET tarafından varsayılan değer olarak ayarlanır. Lütfen bu özniteliğin +“AndroidManifest.xml” içindeki “application” öğesinde ayarlanmadığından emin olun. +“Android.App.Application” öğesinden türeyen ve “[Application]” kullanan bir sınıfa sahipseniz +hata ayıklama derlemeleri için değeri geçersiz kılacağından “Debuggable” özelliğinin ayarlanmadığından emin olun. + + The following are literal names and should not be translated: +AndroidManifest.xml - A xml file +application - A xml element within a xml file. +Application - A code attribute. +Debuggable - A code property +android:debuggable - An attribute in a xml file. + + + + Paket bir “sistem” uygulamasıdır. Bunlar cihaz üzerinde “sistem” kullanıcısı altında + yüklenen uygulamalardır. Bu tür uygulamalar “run-as” komutunu kullanamaz. + Hızlı Dağıtım sistemi bu hatayı otomatik olarak işlemeli + ve bir yedekleme yükleme yolu kullanmalıydı. Ancak bu hatayı görüyorsanız lütfen + Visual Studio’da “Yardım-> Geri Bildirim->Sorun Bildir” veya Mac için Visual Studio’da “Yardım->Sorun Bildir” menü öğesini + kullanarak tam hata iletisini içeren bir sorun bildirin. Mümkünse sorunun tanılanmasına yardımcı olması için + rapora tam tanılama derleme günlüğünü ekleyin. + The following are literal names and should not be translated: + + + + “run-as” komutu “{0}” hatasıyla başarısız oldu. +Paketin geçerli yüklemesi bozuk. Lütfen paketi cihazdaki tüm kullanıcılardan el ile kaldırıp +yeniden deneyin. Bu işe yaramazsa Hızlı Dağıtım'ı devre dışı bırakabilirsiniz. +Hızlı Dağıtım Visual Studio proje özelliği sayfalarında devre dışı bırakılabilir veya proje dosyasını bir metin düzenleyicisinde düzenleyerek “EmbedAssembliesIntoApk” MSBuild özelliğini “true” olarak ayarlayabilirsiniz. + + The following are literal names and should not be translated: +'run-as' - a native android tool +{0} - The error message raised by the program. + + + + “run-as” komutu “{0}” hatasıyla başarısız oldu. +Hızlı Dağıtım şu anda bu cihazda desteklenmiyor. +Visual Studio’da “Yardım-> Geri Bildirim Gönder->Sorun Bildir” veya Mac için Visual Studio’da “Yardım->Sorun Bildir” menü öğesini +kullanarak tam hata iletisini içeren bir sorun bildirin. +Hızlı Dağıtımı devre dışı bırakmak için lütfen Visual Studio proje özelliği sayfalarında veya proje dosyasını bir metin düzenleyicisinde düzenleyerek “EmbedAssembliesIntoApk” MSBuild özelliğini “true” olarak ayarlayın. + The following are literal names and should not be translated: +'run-as' - a native android tool +{0} - The error message raised by the program. + + + + “{1}: {2}” kullanılarak “{0}” dağıtılırken hata oluştu. +Bunun nedeni genellikle dosya verileri cihaza gönderildiğinde gerçekleşen “adb” içindeki bir hatadır. +Bunun normal nedeni, gevşek veya düşük kaliteli bir USB kablosudur. Lütfen farklı bir kablo deneyin. +Alternatif olarak Wi-Fi Hata Ayıklamasını kullanmayı deneyin. Bu, belirli cihazlar için USB hata ayıklamasından daha güvenilir olabilir. +Bunların tamamı başarısız olursa Hızlı Dağıtımı devre dışı bırakmak için lütfen Visual Studio proje özelliği sayfalarında veya proje dosyasını bir metin düzenleyicisinde düzenleyerek “EmbedAssembliesIntoApk” MSBuild özelliğini “true” olarak ayarlayın. +The following are literal names and should not be translated: +{0} - A filename +{1} - The tool that was used to deploy the file. +{2} - The actual error that occured. + + + {0}:{1} üzerinde Hata Ayıklayıcısı başlatılıyor +Sdb’yi başlatın, ardından şu komutu çalıştırın: + connect {0} {1} + + The following are literal names and should not be translated: +{0} - An ip address of a computer. +{1} - An interger value describing the port to use. + + + \ No newline at end of file diff --git a/src/Xamarin.Android.Build.Debugging.Tasks/Properties/Resources.zh-Hans.resx b/src/Xamarin.Android.Build.Debugging.Tasks/Properties/Resources.zh-Hans.resx new file mode 100644 index 00000000000..49bd715de19 --- /dev/null +++ b/src/Xamarin.Android.Build.Debugging.Tasks/Properties/Resources.zh-Hans.resx @@ -0,0 +1,264 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + adb 未运行。{0} + The following are literal names and should not be translated: adb +{0} - further detailed error message + + + 无效的 adb 目标名称 "{0}"。目标名称必须以 "-d"、"-e" 或 "-s" 开头。 + The following are literal names and should not be translated: adb, `-d`, `-e`, `-s` +{0} - the target name the user supplied + + + 没有可用的设备。 + + + 所选设备未运行。 + + + 无法检索附加设备或仿真器“{0}”的 Android ABI。请重新连接设备或重新启动仿真器,然后重试。 + The following are literal names and should not be translated: ABI +ABI - is an acronym for Application Binary Interface. +{0} - the target name the user supplied + + + + 无法将快速部署工具复制到目标设备上的“{0}/{1}”。 +此设备可能与快速部署不兼容。 +请将“EmbedAssembliesIntoApk”MSBuild 属性设置为“true”,以在 Visual Studio 项目属性页中禁用快速部署,或在文本编辑器中编辑项目文件。 + The following are literal names and should not be translated: +{0} - A device path +{1} - A filename + + + 使用“{1}”部署“{0}”时出错: “{2}”。 +请将“EmbedAssembliesIntoApk”MSBuild 属性设置为“true”,以在 Visual Studio 项目属性页中禁用快速部署,或在文本编辑器中编辑项目文件。 +The following are literal names and should not be translated: +{0} - A filename +{1} - The tool that was used to deploy the file. +{2} - The actual error that occured. + + + 在目标设备或模拟器上,Android 系统属性“log.redirect-stdio”设置为“true”。 +为实现与快速部署的兼容性,请将该属性设置为“false”,或者重启设备或模拟器以清除该值。 + + + 部署“{0}”时出错。 +请将“EmbedAssembliesIntoApk”MSBuild 属性设置为“true”,以在 Visual Studio 项目属性页中禁用快速部署,或在文本编辑器中编辑项目文件。 + The following are literal names and should not be translated: +{0} - A filename + + + 抱歉。快速部署仅在运行 Android 5.0 (API 级别 21)或更高版本的设备上受支持。 +请将“EmbedAssembliesIntoApk”MSBuild 属性设置为“true”,以在 Visual Studio 项目属性页中禁用快速部署,或在文本编辑器中编辑项目文件。 + + + 此设备已禁用 "run-as" 工具。请激活设备上的开发人员选项或将 "ro.boot.disable_runas" 设置为 "false" 来启用它。 + The following are literal names and should not be translated: +'run-as' - a native android tool +'ro.boot.disable_runas' - the android property which disables the `run-as` tool. + + + + 未安装包。请检查你是否未以任何其他用户的身份安装该包。 +如果该包确实出现在设备上,请尝试将其手动卸载,然后重试。 +应该可以通过设备上的“设置”应用卸载该应用。 + The following are literal names and should not be translated: + + + + 制造商已在此设备上禁用快速部署系统所需的“run-as”工具。 +请在 Visual Studio 项目属性页中禁用快速部署,或者在文本编辑器中编辑项目文件,并将“EmbedAssembliesIntoApk”MSBuild 属性设置为“true”。 + The following are literal names and should not be translated: +'run-as' - a native android tool + + + + 应用程序未在 AndroidManifest.xml 中设置“android: debuggable”属性。 +必须设置它才能使快速部署正常工作。通常,.NET for Android 生成系统 +在调试生成时默认启用此属性。请检查你是否未在“AndroidManifest.xml”的 +“application”元素上设置此属性。 +如果你有一个派生自“Android.App.Application”的类,并且正在使用“[Application]”,请确保 +未设置“Debuggable”属性,因为它会覆盖调试生成的值。 + + The following are literal names and should not be translated: +AndroidManifest.xml - A xml file +application - A xml element within a xml file. +Application - A code attribute. +Debuggable - A code property +android:debuggable - An attribute in a xml file. + + + + 该包是一个“系统”应用程序。这些是安装在设备上 + “系统”用户下的应用程序。这类应用程序不能使用“run-as”。 + 快速部署系统本应自动处理此特定错误 + 并使用备用安装路径。但是,如果看到此错误,请使用 + Visual Studio 中的“帮助”->“发送反馈”->“报告问题”菜单项 + 或 Visual Studio for Mac 中的“帮助”->“报告问题”,提交包含确切错误消息的问题反馈。如果可能,请在反馈报告中附上完整的诊断生成 + 日志,因为这将有助于我们诊断问题。 + The following are literal names and should not be translated: + + + + “run-as”命令失败,出现“{0}”。 +当前安装的包已损坏。请为设备上的所有用户 +手动卸载包,然后重试。如果此方法不起作用,可以禁用快速部署。 +可以在 Visual Studio 项目属性页中禁用快速部署,或者在文本编辑器中编辑项目文件,并将“EmbedAssembliesIntoApk”MSBuild 属性设置为“true”。 + + The following are literal names and should not be translated: +'run-as' - a native android tool +{0} - The error message raised by the program. + + + + “run-as”命令失败,出现“{0}”。 +此设备当前不支持快速部署。 +请使用 Visual Studio 中的“帮助 -> 发送反馈 -> 报告问题”菜单项 +或 Visual Studio for Mac 中的“帮助”->“报告问题”,提交包含确切错误消息的问题反馈。 +请将“EmbedAssembliesIntoApk”MSBuild 属性设置为“true”,以在 Visual Studio 项目属性页中禁用快速部署,或在文本编辑器中编辑项目文件。 + The following are literal names and should not be translated: +'run-as' - a native android tool +{0} - The error message raised by the program. + + + + 使用“{1}”部署“{0}”时出错: “{2}”。 +这通常是由于将文件数据发送到设备时,“adb”出错导致的。 +造成这种情况的常见原因是 USB 电缆松动或质量低劣。请尝试更换电缆。 +或者尝试使用 Wi-Fi 调试,对于某些设备,这可能比 USB 调试更可靠。 +如果所有这些方法都无效,请将“EmbedAssembliesIntoApk”MSBuild 属性设置为“true”,以在 Visual Studio 项目属性页中禁用快速部署,或在文本编辑器中编辑项目文件。 +The following are literal names and should not be translated: +{0} - A filename +{1} - The tool that was used to deploy the file. +{2} - The actual error that occured. + + + 在“{0}”上启动调试器: {1} +启动 sdb,然后运行以下命令 + connect {0} {1} + + The following are literal names and should not be translated: +{0} - An ip address of a computer. +{1} - An interger value describing the port to use. + + + \ No newline at end of file diff --git a/src/Xamarin.Android.Build.Debugging.Tasks/Properties/Resources.zh-Hant.resx b/src/Xamarin.Android.Build.Debugging.Tasks/Properties/Resources.zh-Hant.resx new file mode 100644 index 00000000000..76b43a32253 --- /dev/null +++ b/src/Xamarin.Android.Build.Debugging.Tasks/Properties/Resources.zh-Hant.resx @@ -0,0 +1,264 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + adb 未執行。{0} + The following are literal names and should not be translated: adb +{0} - further detailed error message + + + 不正確 adb 目標名稱 '{0}'。目標名稱必須以 '-d'、'-e' 或 '-s' 開頭。 + The following are literal names and should not be translated: adb, `-d`, `-e`, `-s` +{0} - the target name the user supplied + + + 沒有可用的裝置。 + + + 選取的裝置未執行。 + + + 無法為已連結的裝置或模擬器 `{0}` 擷取 Android ABI。請重新連接裝置或重新啟動模擬器,然後再試一次。 + The following are literal names and should not be translated: ABI +ABI - is an acronym for Application Binary Interface. +{0} - the target name the user supplied + + + + 無法將 [快速部署] 工具複製到目標裝置上的 '{0}/{1}'。 +此裝置可能與 [快速部署] 不相容。 +請將 'EmbedAssembliesIntoApk' MSBuild 屬性設定為 'true',以停用 Visual Studio 專案屬性頁面中的快速部署,或在文字編輯器中編輯專案檔案。 + The following are literal names and should not be translated: +{0} - A device path +{1} - A filename + + + 使用 '{1}: {2}' 部署 '{0}' 時發生錯誤。 +請將 'EmbedAssembliesIntoApk' MSBuild 屬性設定為 'true',以停用 Visual Studio 專案屬性頁面中的快速部署,或在文字編輯器中編輯專案檔案。 +The following are literal names and should not be translated: +{0} - A filename +{1} - The tool that was used to deploy the file. +{2} - The actual error that occured. + + + 目標裝置或模擬器上的 'log.redirect-stdio' Android 系統屬性設定為 'true'。 +若要啟用與 [快速部署] 的相容性,請將屬性設定為 'false' 或重新啟動裝置或模擬器以清除值。 + + + 部署 '{0}'時發生錯誤。 +請將 'EmbedAssembliesIntoApk' MSBuild 屬性設定為 'true',以停用 Visual Studio 專案屬性頁面中的快速部署,或在文字編輯器中編輯專案檔案。 + The following are literal names and should not be translated: +{0} - A filename + + + 抱歉。只有運行 Android 5.0 (API 層級 21) 或更高版本的裝置才支援 [快速部署]。 +請將 'EmbedAssembliesIntoApk' MSBuild 屬性設定為 'true',以停用 Visual Studio 專案屬性頁面中的快速部署,或在文字編輯器中編輯專案檔案。 + + + 此裝置上的 'run-as' 工具已停用。啟用裝置上的開發人員選項,或將 'ro.boot.disable_runas' 設為 'false' 以啟用此功能。 + The following are literal names and should not be translated: +'run-as' - a native android tool +'ro.boot.disable_runas' - the android property which disables the `run-as` tool. + + + + 未安裝套件。請檢查您並未在任何其他使用者下安裝此套件。 +如果套件確實顯示在裝置上,請嘗試手動解除安裝,然後再試一次。 +您應該可以透過裝置上的 [設定] 應用程式解除安裝應用程式。 + The following are literal names and should not be translated: + + + + 製造商已停用此裝置上的 [快速部署] 系統所需的 'run-as' 工具。 +請停用 Visual Studio 專案屬性頁面中的 [快速部署],或在文字編輯器中編輯專案檔案,並將 'EmbedAssembliesIntoApk' MSBuild 屬性設定為 'true'。 + The following are literal names and should not be translated: +'run-as' - a native android tool + + + + 應用程式未在 AndroidManifest.xml 中設定 'android: debuggable' 屬性。 +必須設定屬性,[快速部署] 才能運作。此功能通常是由 .NET for Android 的建置系統 +為 [偵錯] 組建預設啟用的功能。請檢查您的 'AndroidManifest.xml' 中的 'application' 元素 +是否未設定該屬性。 +如果您有衍生自 'Android.App.Application' 的類別,並使用了 '[Application]' 屬性,請確認 +系統未設定 'Debuggable' 屬性,因為這將會覆寫偵錯組建的值。 + + The following are literal names and should not be translated: +AndroidManifest.xml - A xml file +application - A xml element within a xml file. +Application - A code attribute. +Debuggable - A code property +android:debuggable - An attribute in a xml file. + + + + 封裝是 'system' 應用程式。這些是在裝置上以 'system' 使用者身份 + 安裝的應用程式。這些類型的應用程式無法使用 'run-as'。 + [快速部署] 系統應該會自動處理這個特定錯誤 + 並使用備份安裝路徑。不過,如果您看到此錯誤,請使用 + Visual Studio 中的 '說明->傳送意見反應->回報問題' 功能表項目,或在 Visual Studio for Mac 中的 + '說明->回報問題' 中提交包含完整錯誤訊息的問題報告。可能的話,請附加完整的診斷組建 + 記錄到意見反應報告,以協助我們診斷問題。 + The following are literal names and should not be translated: + + + + 'run-as' 命令失敗,錯誤為 '{0}'。 +套件目前的安裝已損毀。請手動在所有使用者的裝置上 +解除安裝套件,然後再試一次。如果這樣沒有用,您可以停用 [快速部署]。 +您可以停用 Visual Studio 專案屬性頁面中的 [快速部署],或在文字編輯器中編輯專案檔案,並將 'EmbedAssembliesIntoApk' MSBuild 屬性設定為 'true'。 + + The following are literal names and should not be translated: +'run-as' - a native android tool +{0} - The error message raised by the program. + + + + 'run-as' 命令失敗,錯誤為 '{0}'。 +此裝置目前不支援 [快速部署]。 +請在 Visual Studio 中的 '說明->傳送意見反應->回報問題' 功能表項目,或在 Visual Studio for Mac 中的 +'說明->回報問題' 中提交包含完整錯誤訊息的問題報告。 +請將 'EmbedAssembliesIntoApk' MSBuild 屬性設定為 'true',以停用 Visual Studio 專案屬性頁面中的快速部署,或在文字編輯器中編輯專案檔案。 + The following are literal names and should not be translated: +'run-as' - a native android tool +{0} - The error message raised by the program. + + + + 使用 '{1}: {2}' 部署 '{0}' 時發生錯誤。 +這通常是由傳送檔案資料至裝置時在 `adb` 中發生的錯誤所導致。 +此錯誤的常見原因是鬆動或品質較差的 USB 纜線。請嘗試其他纜線。 +或者,請嘗試使用 Wifi 偵錯程式,這比特定裝置上的 USB 偵錯程式更可靠。 +如果這些方式都失敗,請將 'EmbedAssembliesIntoApk' MSBuild 屬性設定為 'true',以停用 Visual Studio 專案屬性頁面中的快速部署,或在文字編輯器中編輯專案檔案。 +The following are literal names and should not be translated: +{0} - A filename +{1} - The tool that was used to deploy the file. +{2} - The actual error that occured. + + + 正在啟動 {0} 上的偵錯工具:{1} +啟動 sdb,然後執行下列命令 + 連線 {0} {1} + + The following are literal names and should not be translated: +{0} - An ip address of a computer. +{1} - An interger value describing the port to use. + + + \ No newline at end of file diff --git a/src/Xamarin.Android.Build.Debugging.Tasks/Tasks/Adb.cs b/src/Xamarin.Android.Build.Debugging.Tasks/Tasks/Adb.cs new file mode 100644 index 00000000000..3b97f742aec --- /dev/null +++ b/src/Xamarin.Android.Build.Debugging.Tasks/Tasks/Adb.cs @@ -0,0 +1,89 @@ +// Copyright (C) 2015 Xamarin, Inc. All rights reserved. + +using System; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Xml; +using System.Xml.Linq; +using Microsoft.Build.Utilities; +using Microsoft.Build.Framework; +using System.Text.RegularExpressions; +using Xamarin.AndroidTools; +using System.Collections.Generic; +using System.Text; +using Microsoft.Android.Build.Tasks; + +namespace Xamarin.Android.Tasks +{ + public class Adb : AndroidRunToolTask { + public override string TaskPrefix => "ADB"; + + [Required] + public string Command { get; set; } + + public string Filter { get; set; } + + public string Capture { get; set; } + + [Output] + public string Output { get; set; } + + Regex filter = null; + StringBuilder sb = new StringBuilder (); + + protected override string DefaultErrorCode => "ADB0000"; + + public override bool RunTask () + { + if (!string.IsNullOrWhiteSpace (Filter)) + filter = new Regex (Filter); + base.Execute (); + Output = sb.ToString().Trim (); + return !Log.HasLoggedErrors; + } + + protected override void LogEventsFromTextOutput (string singleLine, MessageImportance messageImportance) + { + if (filter == null) { + base.LogEventsFromTextOutput (singleLine, messageImportance); + sb.AppendLine (singleLine); + return; + } + var matches = filter.Matches (singleLine.Trim ()); + foreach (Match match in matches) { + if (!string.IsNullOrWhiteSpace (Capture)) { + sb.AppendLine (match.Groups [Capture].ToString ()); + } else { + foreach (Group grp in match.Groups) { + sb.AppendLine (grp.Value); + } + } + } + } + + protected virtual CommandLineBuilder CreateCommandLine() + { + var cmd = new CommandLineBuilder (); + cmd.AppendSwitch (Command); + return cmd; + } + + protected override string GenerateCommandLineCommands () + { + var cmd = CreateCommandLine (); + return cmd.ToString (); + } + + protected override string GenerateFullPathToTool () + { + return Path.Combine (ToolPath, ToolExe); + } + + protected override string ToolName + { + get { return IsWindows ? "adb.exe" : "adb"; } + } + } +} + diff --git a/src/Xamarin.Android.Build.Debugging.Tasks/Tasks/DetectIfAppWasUninstalled.cs b/src/Xamarin.Android.Build.Debugging.Tasks/Tasks/DetectIfAppWasUninstalled.cs new file mode 100644 index 00000000000..1bb2bce2d92 --- /dev/null +++ b/src/Xamarin.Android.Build.Debugging.Tasks/Tasks/DetectIfAppWasUninstalled.cs @@ -0,0 +1,81 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading; +using Microsoft.Build.Framework; +using Microsoft.Build.Utilities; +using Mono.AndroidTools; +using Microsoft.Android.Build.Tasks; + +namespace Xamarin.Android.Tasks { + public class DetectIfAppWasUninstalled : AndroidTask, ICancelableTask { + + CancellationTokenSource tcs = new CancellationTokenSource (); + public override string TaskPrefix => "DIAWI"; + + public string AdbTarget { get; set; } + + [Required] + public string PackageName { get; set; } + + [Required] + public string UploadFlagFile { get; set; } + + public string UserID { get; set; } + + public CancellationToken Token { get { return tcs.Token; } } + + internal const string GetPackagesAsyncKey = nameof (DetectIfAppWasUninstalled) + ".QueryPackages"; + + public void Cancel () + { + tcs.Cancel (); + } + + public override bool RunTask() + { + // kick off a background task to check the device via adb. + // and exit the task immediately. The background task will + // continue to run. We need to get the device on the main + // thread here, otherwise GetRegisteredTaskObject returns + // null. + var device = AndroidHelper.ParseTarget (AdbTarget, Log, logErrors: false, engine4: BuildEngine4); + if (device == null) { + Log.LogDebugMessage ($"No device found: {nameof (AdbTarget)}=\"{AdbTarget}\""); + return true; + } + Log.LogDebugMessage ($"Found device: {device.ID}"); + var flagFilePath = Path.GetFullPath (UploadFlagFile); + var task = QueryPackages (device, flagFilePath); + BuildEngine4.RegisterTaskObjectAssemblyLocal ( + ProjectSpecificTaskObjectKey (GetPackagesAsyncKey), + task, + RegisteredTaskObjectLifetime.Build, + allowEarlyCollection: false); + return !Log.HasLoggedErrors; + } + + + async System.Threading.Tasks.Task> QueryPackages (AndroidDevice device, string uploadFlagFileFullPath) + { + // DO NOT use the Log.XXXX methods in this method. + // Because this is running on a background thread they will + // end up locking the UI in VS. + try { + var pmPackages = new PmListPackagesCommand () { + RequireVersions = false, + User = UserID, + }; + var packages = await device.GetPackages (pmPackages, tcs.Token); + if (!packages.Any (x => string.Compare (x.Name, PackageName, StringComparison.OrdinalIgnoreCase) == 0)) { + File.Delete (uploadFlagFileFullPath); + } + return packages; + } catch (Exception ex) { + System.Diagnostics.Debug.WriteLine ($"DetectIfAppWasUninstalled failed with {ex}"); + } + return null; + } + } +} diff --git a/src/Xamarin.Android.Build.Debugging.Tasks/Tasks/FastDeploy.cs b/src/Xamarin.Android.Build.Debugging.Tasks/Tasks/FastDeploy.cs new file mode 100644 index 00000000000..25a13e48e81 --- /dev/null +++ b/src/Xamarin.Android.Build.Debugging.Tasks/Tasks/FastDeploy.cs @@ -0,0 +1,1251 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Text; +using System.IO; +using System.Linq; +using System.Net; +using System.Buffers; +using System.Text.Json; +using System.Text.Json.Serialization; +using System.Threading; +using System.Threading.Tasks; + +using Microsoft.Android.Build.Tasks; +using Mono.AndroidTools; +using Xamarin.Android.Build.Debugging.Tasks.Properties; + +using K4os.Compression.LZ4; + +using Microsoft.Build.Framework; + +namespace Xamarin.Android.Tasks +{ + public class FastDeploy : AsyncTask + { + const string XAToolsTempPath = "/data/local/tmp/.xatools"; + const string OverridePath = "files/.__override__"; + const string ToolsPath = "files/.__tools__"; + const int MAX_COMMAND = 4096; + const int ADB_COMMAND_PADDING = 100; + + public override string TaskPrefix => "FD"; + + public string AdbTarget { get; set; } + public string UploadFlagFile { get; set; } + public bool EmbedAssembliesIntoApk { get; set; } + public bool ReInstall { get; set; } = false; + + [Required] + public string PackageName { get; set; } + + public string PackageFile { get; set; } + + public string PrimaryCpuAbi { get; set; } + public string ToolsAbi { get; set; } + + public ITaskItem [] FastDevFiles { get; set; } + + public bool PreserveUserData { get; set; } = true; + + [Required] + public string FastDevToolPath { get; set; } + + public string FastDevTool { get; set; } = "xamarin.sync"; + public string FastDevFindTool { get; set; } = "xamarin.find"; + public string FastDevStatTool { get; set; } = "xamarin.stat"; + public string FastDevCpTool { get; set; } = "xamarin.cp"; + + [Required] + public string ToolVersion { get; set; } + + public bool DiagnosticLogging { get; set; } = false; + + public bool UsingAndroidNETSdk { get; set; } + + public string UserID { get; set; } + + public bool IsTestOnly { get; set; } + + [Required] + public string IntermediateOutputPath { get; set; } + public ITaskItem[] EnvironmentFiles { get; set; } + + AndroidDevice Device; + DateTime lastUpload = DateTime.MinValue; + + internal class PackageInfo { + string internalPath = null; + public string InternalPath { + get { return internalPath; } + set { + internalPath = value?.Trim () ?? null; + } + } + + public string ToolVersion { get; set; } + public int? BlockSize { get; set; } + public bool SupportsFastDev { get; set; } = true; + public bool IsSystemApplication { get; set; } = false; + public bool AdbIsRoot { get; set; } = false; + public string UserId { get; set; } = null; + public string PackageName { get; set; } = null; + public bool DiagnosticLogging { get; set; } = false; + public Action LogDebugMessage; + + } + + private class DiagnosticData { + [JsonPropertyName ("Task")] + public string Task { get; set; } = nameof (FastDeploy); + [JsonPropertyName ("Properties")] + public Dictionary Properties { get; set; } = new Dictionary() { + { "target.prop.ro.product.build.version.sdk", "" }, + { "target.prop.ro.product.cpu.abilist", "" }, + { "target.prop.ro.product.manufacturer", "" }, + { "target.prop.ro.product.model", "" }, + { "target.prop.ro.product.cpu.abi", ""}, + { "deploy.error.code", ""}, + { "deploy.tool", "xamarin.sync" }, + { "deploy.result", "Success" }, + { "deploy.supports.fastdev", "True" }, + { "deploy.systemapp", "False" }, + { "deploy.duration.ms", "0" }, + { "pii.deploy.error", "" }, + { "pii.deploy.file", "" }, + }; + + internal void SetProperty (string key, bool? value) + { + Properties[key] = value?.ToString () ?? "False"; + } + + internal void SetProperty (string key, int? value) + { + Properties[key] = value?.ToString () ?? "-1"; + } + + internal void SetProperty (string key, long? value) + { + Properties[key] = value?.ToString () ?? "-1"; + } + + internal void SetProperty (string key, string value) + { + Properties[key] = value ?? "unknown"; + } + } + + PackageInfo packageInfo; + Stopwatch stopWatch = new Stopwatch (); + + Queue diagnosticLogs = new Queue (); + + DiagnosticData diagnosticData = new DiagnosticData (); + + protected string ToolsFullPath { + get { return packageInfo.IsSystemApplication ? $"{packageInfo.InternalPath}/{ToolsPath}" : ToolsPath; } + } + + protected string OverrideFullPath { + get { return packageInfo.IsSystemApplication ? $"{packageInfo.InternalPath}/{OverridePath}" : OverridePath; } + } + + void StartTiming () + { + stopWatch.Restart (); + } + + long GetElapsedTimeAndRestart () + { + stopWatch.Stop (); + long elapsedTime = stopWatch.ElapsedMilliseconds; + stopWatch.Restart (); + return elapsedTime; + } + + void DebugHandler (string task, string message) + { + LogDiagnostic ($"DEBUG {task} {message} [{GetElapsedTimeAndRestart ()}ms]"); + } + + void LogDebugMessageWithTiming (string message) + { + LogDiagnostic ($"{message} [{GetElapsedTimeAndRestart ()}ms]"); + } + + void LogDiagnostic (string message) + { + if (DiagnosticLogging) { + LogDebugMessage (message); + return; + } + diagnosticLogs.Enqueue (message); + } + + void PrintDiagnostics () + { + while (diagnosticLogs.Count > 0) { + LogMessage (diagnosticLogs.Dequeue ()); + } + LogMessage ($"{diagnosticData.Task}"); + foreach (var t in diagnosticData.Properties) { + LogMessage ($"\t{t.Key}: {t.Value}"); + } + } + + void LogDiagnosticDataError (string errorCode, string error, string file = "") + { + diagnosticData.SetProperty ("deploy.result", "Failed"); + if (!string.IsNullOrEmpty (file)) + diagnosticData.SetProperty ("pii.deploy.file", file); + diagnosticData.SetProperty ("pii.deploy.error", error); + diagnosticData.SetProperty ("deploy.error.code", errorCode); + } + + void SaveDiagnosticData (long ms) + { + JsonSerializerOptions options = new JsonSerializerOptions { + WriteIndented = true + }; + diagnosticData.SetProperty ("deploy.duration.ms", ms); + string newPath = Path.Combine(IntermediateOutputPath, "diagnostics", "fastdeploy.json"); + File.WriteAllText (newPath, JsonSerializer.Serialize (diagnosticData, options)); + } + + public override bool Execute () + { + Device = AndroidHelper.ParseTarget (AdbTarget, LogMessage, LogCodedError, logErrors: true, engine4: BuildEngine4); + if (Device == null) { + PrintDiagnostics (); + return false; + } + LogMessage ($"Found device: {Device.ID}"); + + if (string.IsNullOrEmpty (PrimaryCpuAbi) && !EmbedAssembliesIntoApk) { + PrintDiagnostics (); + LogCodedError ("XA0010", Resources.XA0010_NoAbi, Device.ID); + return false; + } + + var lifetime = RegisteredTaskObjectLifetime.AppDomain; + var key = ProjectSpecificTaskObjectKey ($"{Device.ID}_{PackageName}"); + if (!File.Exists (UploadFlagFile)) { + packageInfo = new PackageInfo (); + } else { + packageInfo = BuildEngine4.GetRegisteredTaskObjectAssemblyLocal(key, lifetime) ?? new PackageInfo (); + } + packageInfo.DiagnosticLogging = DiagnosticLogging; + packageInfo.LogDebugMessage = LogDiagnostic; + AndroidLogger.Debug += DebugHandler; + try { + var flagFilePath = GetFullPath (UploadFlagFile); + lastUpload = File.GetLastWriteTimeUtc (flagFilePath); + LogDiagnostic ($"LastWriteTime of `{flagFilePath}`: {lastUpload}"); + StartTiming (); + return base.Execute (); + } finally { + BuildEngine4.RegisterTaskObjectAssemblyLocal (key, packageInfo, lifetime, allowEarlyCollection: false); + stopWatch.Stop (); + AndroidLogger.Debug -= DebugHandler; + } + } + + public async override Task RunTaskAsync () + { + var sw = new Stopwatch (); + sw.Restart (); + try { + await RunInstall (); + } catch { + PrintDiagnostics (); + throw; + } finally { + sw.Stop(); + SaveDiagnosticData (sw.ElapsedMilliseconds); + } + } + + public async Task RunInstall () + { + await Device.EnsureProperties (CancellationToken).ConfigureAwait (false); + + diagnosticData.SetProperty ("target.prop.ro.product.build.version.sdk", Device.Properties?.BuildVersionSdk); + diagnosticData.SetProperty ("target.prop.ro.product.cpu.abilist", string.Join (";", Device.Properties?.ProductCpuAbiList ?? Array.Empty ())); + diagnosticData.SetProperty ("target.prop.ro.product.cpu.abi", PrimaryCpuAbi); + diagnosticData.SetProperty ("target.prop.ro.product.manufacturer", Device.Properties?.ProductManufacturer); + diagnosticData.SetProperty ("target.prop.ro.product.model", Device.Properties?.ProductModel); + + string redirectStdio = Device.Properties.Get ("log.redirect-stdio"); + if (redirectStdio != null && string.Equals ("true", redirectStdio.Trim (), StringComparison.OrdinalIgnoreCase)) { + LogDiagnosticDataError ("XA0128", Resources.XA0128_RedirectStdioIsEnabled); + PrintDiagnostics (); + LogCodedError ($"XA0128", Resources.XA0128_RedirectStdioIsEnabled); + return; + } + + string runAsDisabled = Device.Properties.Get ("ro.boot.disable_runas"); + if (runAsDisabled != null && string.Equals ("true", runAsDisabled.Trim (), StringComparison.OrdinalIgnoreCase)) { + LogDiagnosticDataError ("XA0131", Resources.XA0131_DeveloperModeNotEnabled); + PrintDiagnostics (); + LogCodedError ($"XA0131", Resources.XA0131_DeveloperModeNotEnabled); + return; + } + + await CheckAppInstalledAndDebuggable (PackageName); + + if (EmbedAssembliesIntoApk) { + // we need to remove the .__override__ directory BEFORE we uninstall the debug apk. + // this is because run-as does NOT work on release apps. + await RemoveOverrideDirectory(); + } + + if (ReInstall && !string.IsNullOrEmpty (PackageFile)) { + await Device.UninstallPackage (PackageName, PreserveUserData, CancellationToken); + } + if (!string.IsNullOrEmpty (PackageFile) && + (packageInfo.InternalPath.IndexOf ("unknown", StringComparison.OrdinalIgnoreCase) >= 0 || ReInstall || IsPackageFileOutOfDate ())) { + try { + await InstallPackage (!(packageInfo.InternalPath.IndexOf ("unknown", StringComparison.OrdinalIgnoreCase) >= 0)); + } catch (Exception ex) { + LogDiagnosticDataError (GetErrorCode (ex), ex.ToString ()); + PrintDiagnostics (); + LogCodedError (GetErrorCode (ex), ex.ToString ()); + return; + } + if (!EmbedAssembliesIntoApk && packageInfo.InternalPath.IndexOf ("unknown", StringComparison.OrdinalIgnoreCase) >= 0) { + packageInfo.InternalPath = null; + await CheckAppInstalledAndDebuggable (PackageName); + if (RaiseRunAsError (packageInfo.InternalPath)) { + return; + } + } + } + + if (EmbedAssembliesIntoApk) + return; + + if (!await InstallFastDevTools (ToolsFullPath)) { + return; + } + + if (FastDevFiles?.Any () ?? false) { + await TerminateApp (); + await DeployFastDevFiles (ToolsFullPath, OverrideFullPath); + } + + return; + } + + bool IsPackageFileOutOfDate () + { + var packageFile = GetFullPath (PackageFile); + var lastPackage = File.GetLastWriteTimeUtc (packageFile); + LogDiagnostic ($"LastWriteTime of `{packageFile}`: {lastPackage}"); + return lastUpload < lastPackage; + } + + int CompressLZ4 (ref byte [] data, int len, ref byte [] outBuffer, LZ4Level lZ4Level = LZ4Level.L00_FAST) + { + int compressedLength = LZ4Codec.Encode (data, 0, len, outBuffer, 0, outBuffer.Length, lZ4Level); + if (compressedLength < 0 || compressedLength >= outBuffer.Length) { + if (DiagnosticLogging) + LogDebugMessage ($"Sending Data Uncompressed."); + compressedLength = outBuffer.Length; + data.CopyTo (outBuffer, 0); + } + return compressedLength; + } + + async Task CheckAppInstalledAndDebuggable (string packageName) + { + packageInfo.UserId = UserID; + packageInfo.PackageName = packageName; + await EnsureUserIsRunning (); + packageInfo.InternalPath = packageInfo.InternalPath ?? await Device.RunAs (packageInfo, "pwd"); + if (packageInfo.InternalPath.IndexOf ("Permission denied", StringComparison.OrdinalIgnoreCase) >= 0) { + packageInfo.InternalPath = await Device.RunAs (packageInfo, "readlink", "-f", "."); + } + if (packageInfo.InternalPath.IndexOf ("not an application", StringComparison.OrdinalIgnoreCase) >= 0) { + LogDiagnostic ($"Package {packageInfo.PackageName} is a system application."); + packageInfo.IsSystemApplication = true; + diagnosticData.SetProperty ("deploy.systemapp", value:true); + string whoami = await Device.RunShellCommand ("whoami"); + packageInfo.AdbIsRoot = whoami.Trim () == "root"; + LogDiagnostic ($"using {(packageInfo.AdbIsRoot ? "root" : $"su {packageInfo.UserId}")} to install fast deployment files. "); + packageInfo.InternalPath = $"/data/user/{(packageInfo.UserId ?? "0")}/{packageInfo.PackageName}"; + return; + } + if (packageInfo.InternalPath.IndexOf ("not debuggable", StringComparison.OrdinalIgnoreCase) >= 0) { + // current install is not debuggable, so lets uninstall it + LogDiagnostic ($"Package {packageInfo.PackageName} was not debuggable. Forcing ReInstall"); + ReInstall = true; + return; + } + if (packageInfo.InternalPath.IndexOf ("unknown", StringComparison.OrdinalIgnoreCase) >= 0) { + LogDiagnostic ($"Package {packageInfo.PackageName} was not installed."); + return; + } + if (packageInfo.InternalPath.IndexOf ("Permission denied", StringComparison.OrdinalIgnoreCase) >= 0) { + // run-as is probably not supported. + LogDiagnostic ("run-as not supported on this device."); + packageInfo.SupportsFastDev = false; + diagnosticData.SetProperty ("deploy.supports.fastdev", value: false); + } + return; + } + + /// + /// Ensures the secondary Android user targeted by this deployment is in the + /// 'running' state before any run-as query is issued against it. + /// + /// + /// pm install --user N <apk> registers the package but does + /// not materialize the per-user data directory /data/user/N/<pkg>; + /// that directory is only created once user N is brought to the running + /// state. Until then, every run-as <pkg> --user N invocation fails + /// with run-as: couldn't stat /data/user/N/<pkg>: No such file or + /// directory and raises XA0137. See + /// https://github.com/dotnet/android/issues/7821. + /// Empirical measurement on an arm64 API-34 emulator (N=50) showed that + /// without this step, 52% of installs fail run-as at t=0 and 30% + /// never recover even after 30 seconds of polling — i.e. polling alone is not + /// sufficient. am start-user -w N succeeds in 100/100 attempts within + /// ~134 ms median (max 363 ms), is idempotent, and is a cheap no-op when the + /// user is already running. The primary user (id 0) never requires this step, + /// so skip it to avoid any cost in the common case. + /// + async Task EnsureUserIsRunning () + { + var userId = (UserID ?? string.Empty).Trim (); + if (userId.Length == 0 || (int.TryParse (userId, out var id) && id == 0)) { + return; + } + LogDiagnostic ($"Ensuring Android user {userId} is in the 'running' state before run-as queries."); + string output = await Device.RunShellCommand (CancellationToken, "am", "start-user", "-w", userId); + // `am start-user -w` normally prints `Success: user started`. Surface any + // output (success or failure, e.g. `Error: could not start user`) at the + // diagnostic level so build logs make the cause obvious if the subsequent + // `run-as` query fails. Do not attempt to interpret the output here: the + // existing run-as error path raises XA0137 deterministically on failure, + // and parsing `am`'s output for error markers risks false positives. + LogDiagnostic ($"'am start-user -w {userId}' returned: {(string.IsNullOrWhiteSpace (output) ? "" : output.Trim ())}"); + } + + protected async Task RemoveOverrideDirectory () { + // remote //.__override__ directory has files in it. + // We can do that by using out tool stat. + string overrideExists = await Device.RunAs (packageInfo, $"{ToolsFullPath}/{FastDevStatTool}", OverrideFullPath); + if (!(overrideExists.IndexOf ("error:", StringComparison.OrdinalIgnoreCase) >= 0) && + !(overrideExists.IndexOf ("package not debuggable", StringComparison.OrdinalIgnoreCase) >= 0)) { + await Device.RunAs (packageInfo, "rm", "-Rf", OverrideFullPath); + } + } + + protected async Task TerminateApp () + { + var pid = await Device.GetProcessId (PackageName, CancellationToken); + if (pid == 0) { + LogDebugMessage ($"{PackageName} was not running, skipping kill"); + return; + } + LogDebugMessage ($"Terminating {PackageName}..."); + await Device.KillProcessAndWaitForExit (PackageName, CancellationToken); + LogDebugMessageWithTiming ($"{PackageName} Terminated."); + } + + protected async Task InstallPackage (bool installed = true) + { + LogDebugMessage ($"Installing Package {PackageName}"); + try { + await Device.PushAndInstallPackageAsync (new PushAndInstallCommand { + ApkFile = PackageFile, + PackageName = PackageName, + ReInstall = ReInstall, + User = UserID, + TestOnly = IsTestOnly, + }, token: CancellationToken); + LogDebugMessageWithTiming ($"Installed Package {PackageName}."); + } catch (Exception exception) { + var ex = exception; + if (exception is AggregateException aex) { + ex = aex.Flatten ().InnerException; + } + if (!await ShouldThrowIfPackageInstallFailed (ex as PackageAlreadyExistsException)) { + LogDebugMessageWithTiming ($"Installed Package {PackageName}."); + return; + } + throw; + } + return; + } + + async Task ShouldThrowIfPackageInstallFailed (PackageAlreadyExistsException e) + { + if (e == null) + return true; + + int s = (e.PackageFile ?? "").LastIndexOf ('/'); + string apkBasename = s >= 0 ? e.PackageFile.Substring (s+1) : e.PackageFile; + + // If the runtime already exists, ignore the error + // Sometimes android doesn't report it's installed when it is :/ + if (apkBasename != Path.GetFileName (PackageFile)) + return false; + + // Oops; things have gotten wedged (stale/interrupted install?) + // The file we tried to upload already exists on the device! + // Delete and try again. + LogDebugMessage (string.Format ("Package '{0}' already exists. Retrying...", PackageName)); + try { + // NOTE We NEED to delete the cache data too other wise the install will fail. + await Device.DeleteFile (e.PackageFile, true, CancellationToken); + } catch { + // Ebil, yes, but... + } + bool preserveData = !(e is RequiresUninstallException); + LogDebugMessage (string.Format ("Forcing complete uninstall of '{0}'... Preserving Data: {1}", PackageName, preserveData)); + var uninstallCommand = new PmUninstallCommand() { PackageName = PackageName, User = UserID, PreserveData = preserveData }; + await Device.UninstallPackage (uninstallCommand, cancellationToken: CancellationToken); + LogDebugMessage (string.Format ("Installing '{0}'...", PackageName)); + await Device.PushAndInstallPackageAsync (new PushAndInstallCommand { + ApkFile = PackageFile, + PackageName = PackageName, + ReInstall = false, + User = UserID + },token: CancellationToken); + return false; + } + + protected async Task InstallFastDevTools (string toolPath) + { + if (string.Compare (packageInfo.ToolVersion ?? string.Empty, ToolVersion, StringComparison.OrdinalIgnoreCase) == 0) { + LogDebugMessage ($"FastDev Tools already installed for the app. {packageInfo.ToolVersion}"); + return true; + } + + string output = await Device.RunAs (packageInfo, "cat", $"{toolPath}/version"); + if (string.Compare (output.Trim (), ToolVersion, StringComparison.OrdinalIgnoreCase) == 0) { + LogDebugMessage ($"FastDev Tools already installed for the app. {output}"); + packageInfo.ToolVersion = ToolVersion; + return true; + } + + output = await Device.RunAs (packageInfo, "mkdir", "-p", toolPath); + if (output.IndexOf ("run-as:", StringComparison.OrdinalIgnoreCase) >= 0 || + output.IndexOf ("mkdir:", StringComparison.OrdinalIgnoreCase) >= 0) { + if (!RaiseRunAsError (output)) { + LogDiagnosticDataError ("XA0130", output); + PrintDiagnostics (); + LogCodedError ($"XA0130", Resources.XA0130_FastDevNotSupported); + } + return false; + } + // we have to do this as a normal shell command since running + // mkdir under `run-as` will result in a `permission-denied` error. + output = await Device.RunShellCommand ("mkdir", "-p", XAToolsTempPath); + if (output.IndexOf ("mkdir:", StringComparison.OrdinalIgnoreCase) >= 0) { + if (!RaiseRunAsError (output)) { + LogDiagnosticDataError ("XA0130", output); + PrintDiagnostics (); + LogCodedError ($"XA0130", Resources.XA0130_FastDevNotSupported); + } + return false; + } + + string toolAbi = string.IsNullOrEmpty (ToolsAbi) ? PrimaryCpuAbi : ToolsAbi; + var tools = new [] { FastDevFindTool, FastDevTool, FastDevStatTool, FastDevCpTool }; + foreach (var tool in tools) { + LogDebugMessage ($"Installing FastDev Tool {toolPath}/{tool} for {toolAbi}"); + if (!await PushFileToDevice (Device, PackageName, toolPath, Path.Combine (FastDevToolPath, toolAbi, tool), $"{toolPath}/{tool}", CancellationToken)) { + LogDiagnosticDataError ("XA0126", Resources.XA0126_UnableToCopyFastDevTools); + PrintDiagnostics (); + LogCodedError ($"XA0126", Resources.XA0126_UnableToCopyFastDevTools, toolPath, tool); + return false; + } + } + LogDebugMessage ($"Setting FastDev Tools Permissions"); + await Device.RunAs (packageInfo, "chmod", "700", $"{toolPath}/{FastDevTool}", $"{toolPath}/{FastDevFindTool}", $"{toolPath}/{FastDevStatTool}", $"{toolPath}/{FastDevCpTool}"); + LogDebugMessage ($"Installing FastDev Tools to {toolPath}/version"); + await PushFileTextToDevice (Device, PackageName, ToolVersion, Encoding.ASCII, $"{toolPath}/version", token: CancellationToken); + LogDebugMessage ($"Removing FastDev Tools temp directory."); + await Device.RunShellCommand ("rm", "-Rf", XAToolsTempPath); + packageInfo.ToolVersion = ToolVersion; + return true; + } + + async Task PushFileToDevice (AndroidDevice device, string packageName, string toolPath, string file, string target, CancellationToken token) + { + if (!File.Exists (file)) { + LogDebugMessage ($"File '{file}' does not exists. Skipping."); + return false; + } + using (var fs = File.OpenRead (file)) { + if (!await PushStreamToDevice (device, packageName, toolPath, fs, target, DateTime.UtcNow, token: token)) { + return false; + } + } + return true; + } + + async Task PushFileTextToDevice (AndroidDevice device, string packageName, string fileContents, Encoding encoding, string target, CancellationToken token) + { + using (var ms = new MemoryStream ()) { + using (var sw1 = new StreamWriter (ms, encoding, 1024, leaveOpen: true)) { + sw1.WriteLine (fileContents); + sw1.Flush (); + } + ms.Position = 0; + if (!await PushStreamToDevice (device, packageName, null, ms, target, DateTime.UtcNow, token: token)) { + return false; + } + } + return true; + } + + async Task PushStreamToDeviceWithTool (AndroidDevice device, string packageName, string toolPath, Stream stream, string target, DateTimeOffset modifiedDateTime, CancellationToken token = default (CancellationToken)) + { + string targetFile = Path.GetFileName (target); + try { + long wrote = await device.Push (stream, $"{XAToolsTempPath}/{targetFile}", cancellationToken: token); + LogDiagnostic ($"Pushed {wrote} to {XAToolsTempPath}/{targetFile}"); + string r = await device.RunAs (packageInfo, $"{toolPath}/{FastDevCpTool}", $"{XAToolsTempPath}/{targetFile}", target, $"{modifiedDateTime.ToUnixTimeMilliseconds ()}"); + if (r.IndexOf ("run-as:", StringComparison.OrdinalIgnoreCase) >= 0) { + TryGetRunAsErrorCode (r, out var err); + LogDiagnosticDataError (err.code, r, targetFile); + return false; + } + LogDiagnostic ($"moved {XAToolsTempPath}/{targetFile} to {target}"); + LogDebugMessageWithTiming ($"Installed {target}."); + } catch (Exception ex) { + LogDebugMessageWithTiming ($"Failed to push {targetFile} to {target}. {ex}."); + LogDiagnosticDataError(GetErrorCode (ex),ex.ToString (), targetFile); + return false; + } + return true; + } + + async Task PushStreamToDevice (AndroidDevice device, string packageName, string toolPath, Stream stream, string target, DateTimeOffset modifiedDateTime, CancellationToken token = default (CancellationToken)) + { + string targetFile = Path.GetFileName (target); + try { + long wrote = await device.Push (stream, $"{XAToolsTempPath}/{targetFile}", cancellationToken: token); + LogDiagnostic ($"Pushed {wrote} to {XAToolsTempPath}/{targetFile}"); + string r = await device.RunAs (packageInfo, "cp", $"{XAToolsTempPath}/{targetFile}", target); + if (r.IndexOf ("run-as:", StringComparison.OrdinalIgnoreCase) >= 0) { + TryGetRunAsErrorCode (r, out var err); + LogDiagnosticDataError (err.code, r, targetFile); + return false; + } + LogDiagnostic ($"moved {XAToolsTempPath}/{targetFile} to {target}"); + await device.RunAs (packageInfo, "touch", "-t", $"{modifiedDateTime.ToString ("yyyyMMdd.HHmmss")}", target); + LogDebugMessageWithTiming ($"Installed {target}."); + } catch (Exception ex) { + LogDiagnosticDataError (GetErrorCode (ex),ex.ToString ()); + LogDebugMessageWithTiming ($"Failed to push {targetFile} to {target}. {ex}."); + return false; + } + return true; + } + + string GetTargetPath (ITaskItem file) + { + string targetPath = file.GetMetadata ("TargetPath"); + if (string.IsNullOrEmpty (targetPath)) { + // fallback to DestinationSubPath + LogDiagnostic ($"'TargetPath' meta data not found on '{file.ItemSpec}'. Falling back to'DestinationSubPath'"); + targetPath = file.GetMetadata ("DestinationSubPath"); + } + return targetPath; + } + + protected async Task DeployFastDevFiles (string toolPath, string overridePath) + { + // get the optimal blocksize from the device. This will help speed up transfer and disk writes. + LZ4Level lz4level = LZ4Level.L03_HC; + + LogDiagnostic ("Calculating subdirectories"); + HashSet directories = new HashSet (); + directories.Add (overridePath); + foreach (var file in FastDevFiles) { + string targetPath = GetTargetPath (file); + if (!string.IsNullOrEmpty (targetPath)) { + string dirName = Path.GetDirectoryName (targetPath).Replace ("\\", "/"); + if (!string.IsNullOrEmpty (dirName)) { + directories.Add ($"{overridePath}/{dirName}"); + LogDiagnostic ($"{targetPath} => {overridePath}/{dirName}"); + } + } + } + int length = ADB_COMMAND_PADDING + PackageName.Length; + List args = new List(directories.Count + 2); + args.Add ("mkdir"); + args.Add ("-p"); + foreach (var dir in directories) { + int newLength = dir.Length + 3; + if ((length + newLength) >= MAX_COMMAND) { + await Device.RunAs (packageInfo, args); + length = ADB_COMMAND_PADDING + PackageName.Length; + args.Clear (); + args.Add ("mkdir"); + args.Add ("-p"); + } + length += newLength; + args.Add (dir); + } + await Device.RunAs (packageInfo, args); + + string filelist = await Device.RunAs (packageInfo, $"{toolPath}/{FastDevFindTool}", DiagnosticLogging ? "-vd" : "-v", overridePath); + LogDiagnostic ($"{FastDevFindTool}: {filelist}"); + string [] files = Array.Empty (); + if (!(filelist.IndexOf ("error:", StringComparison.OrdinalIgnoreCase) >= 0)) { + files = filelist.Split (new char [] { '\n' }, StringSplitOptions.RemoveEmptyEntries); + } + Dictionary fileData = new Dictionary (); + foreach (var file in files) { // file size mtime + if (file.IndexOf ("\t") == -1) { + LogDebugMessage ($"{FastDevFindTool}: Ignoring line '{file}'. Line is incorrectly formatted."); + continue; + } + var entires = file.Split (new char [] { '\t' }, StringSplitOptions.RemoveEmptyEntries); + if (entires.Length != 3) { + LogDebugMessage ($"{FastDevFindTool}: Ignoring line {file}. Input does not have 3 items."); + continue; + } + if (long.TryParse (entires [1].Trim (), out long fsize) && long.TryParse (entires [2].Trim (), out long mtime)) { + DateTimeOffset offset; + try { + offset = DateTimeOffset.FromUnixTimeMilliseconds (mtime); + } catch (ArgumentOutOfRangeException) { + offset = DateTimeOffset.MinValue; + } + fileData.Add (entires [0].Replace ("./", "").Trim (), (size: fsize, mtime: offset)); + } else { + LogDebugMessage ($"Failed to parse values for line {file}. Ignoring."); + } + } + // remove known directories s they don't get deleted. + fileData.Remove ("links"); + + foreach (var file in FastDevFiles) { + if (!File.Exists (file.ItemSpec)) { + LogDebugMessage ($"File '{file.ItemSpec}' does not exists. Skipping."); + continue; + } + StartTiming (); + if (Path.GetExtension (file.ItemSpec) == ".so") { + string abi = AndroidRidAbiHelper.GetNativeLibraryAbi (file); + if (abi != PrimaryCpuAbi) { + LogDebugMessageWithTiming ($"NotifySync SkipCopyFile {file.ItemSpec} abi not suitable for this device."); + continue; + } + } + string targetPath = GetTargetPath (file); + if (!string.IsNullOrEmpty (targetPath)) { + targetPath = $"{targetPath}".Replace ("\\", "/"); + } else { + targetPath = $"{Path.GetFileName (file.ItemSpec)}"; + } + string filename = Path.GetFileName (file.ItemSpec); + var fi = new FileInfo (file.ItemSpec); + bool modified = true; + DateTimeOffset modifiedDateTime = File.GetLastWriteTimeUtc (file.ItemSpec); + DateTimeOffset remoteDateTime = DateTimeOffset.MinValue; + if (fileData.ContainsKey (targetPath)) { + remoteDateTime = fileData [targetPath].mtime; + modified = remoteDateTime.ToUnixTimeMilliseconds () < modifiedDateTime.ToUnixTimeMilliseconds () || fi.Length != fileData [targetPath].size; + } + if (!modified) { + LogDebugMessageWithTiming ($"NotifySync SkipCopyFile {file.ItemSpec}=>{targetPath} file is up to date."); + fileData.Remove (targetPath); + continue; + } + if (!await DeployFileWithFastDevTool (file, toolPath, overridePath, lz4level, modifiedDateTime)) { + diagnosticData.SetProperty ("deploy.result", "Failed"); + return; + } + LogDebugMessageWithTiming ($"NotifySync CopyFile {file.ItemSpec}."); + LogDiagnostic ($"Local Modified Time '{modifiedDateTime.ToUnixTimeMilliseconds ()}' is newer than '{remoteDateTime.ToUnixTimeMilliseconds ()}'."); + fileData.Remove (targetPath); + } + if (EnvironmentFiles?.Length > 0) { + string targetPath = $"{PrimaryCpuAbi}/environment"; + DateTimeOffset remoteDateTime = DateTimeOffset.MinValue; + if (fileData.ContainsKey (targetPath)) { + remoteDateTime = fileData [targetPath].mtime; + } + await DeployEnvironmentFiles (EnvironmentFiles, toolPath, overridePath, targetPath, remoteDateTime); + fileData.Remove (targetPath); + } + foreach (var file in fileData.Keys) { + // we need to remove unknown files from the .__override__ path + string targetFile = $"{file.Replace ("./", "")}"; + LogDebugMessage ($"Remove redundant file {OverrideFullPath}/{targetFile}"); + await Device.RunAs (packageInfo, "rm", "-Rf", $"{OverrideFullPath}/{targetFile}"); + } + // clean up the temp folder if we are not using the xamarin.sync tool + if (!packageInfo.SupportsFastDev) + await Device.RunShellCommand ("rm", "-Rf", XAToolsTempPath); + return; + } + + async Task DeployFileWithFastDevTool (ITaskItem file, string toolPath, string overridePath, LZ4Level lz4level, DateTimeOffset modifiedDateTime) + { + + using (var fs = File.OpenRead (file.ItemSpec)) { + string destination = overridePath; + // This bit handles subdirectories. + int bufferSize = LZ4Codec.MaximumOutputSize (fs.Length > int.MaxValue ? int.MaxValue : (int)fs.Length); + string targetPath = GetTargetPath (file); + if (!string.IsNullOrEmpty (targetPath)) { + destination += $"/{targetPath}".Replace ("\\", "/"); + } else { + destination += $"/{Path.GetFileName (file.ItemSpec)}"; + } + if (packageInfo.SupportsFastDev) { + byte [] buffer = ArrayPool.Shared.Rent (bufferSize); + byte [] compressed = ArrayPool.Shared.Rent (bufferSize); + try { + List args = DeviceExt.BuildArgs (DeviceExt.RunAsCommand, packageInfo); + args.AddRange (new string[] { $"{toolPath}/{FastDevTool}", $"{compressed.Length}", $"{fs.Length}", $"{destination}", $"{modifiedDateTime.ToUnixTimeMilliseconds ()}" }); + LogDiagnostic ($"executing: {string.Join (" ", args.ToArray ())}"); + var output = await Device.RunShellCommandStream (args.ToArray (), async (s) => { + int read = await fs.ReadAsync (buffer, 0, buffer.Length); + if (read == 0) + return false; + int compressedLength = CompressLZ4 (ref buffer, read, ref compressed, lz4level); + int l = IPAddress.HostToNetworkOrder (compressedLength); + var v = BitConverter.GetBytes (l); + try { + s.Write (v, 0, 4); + s.Write (compressed, 0, compressedLength); + } catch { + return false; + } + return true; + }, CancellationToken); + LogDiagnostic ($"FastDev of {file.ItemSpec} returned: {output}"); + + if (output.IndexOf ("error:", StringComparison.OrdinalIgnoreCase) >= 0) { + if (output.IndexOf ("from stdin.", StringComparison.OrdinalIgnoreCase) >= 0) { + LogDiagnostic ($"'{FastDevTool}' returned '{output}' when deploying '{destination}'. Falling back to backup deployment."); + diagnosticData.SetProperty ("pii.deploy.error", output); + diagnosticData.SetProperty ("pii.deploy.file", file.ItemSpec); + diagnosticData.SetProperty ("deploy.tool", value:"xamarin.cp"); + // Log warning and fallback to adb push style deployment. It will be slower... but it works. + } else { + LogDiagnosticDataError ("XA0127", output, file.ItemSpec); + PrintDiagnostics (); + LogCodedError ($"XA0127", Resources.XA0127_ErrorDeployingFile, destination, FastDevTool, output); + return false; + } + } + + if (output.IndexOf ($"wrote [{fs.Length}]", StringComparison.OrdinalIgnoreCase) >= 0) { + return true; + } + // we didn't write the file as we expected so use the backup path. + // this can happen is the devices supports run-as but does not support + // reading data in from stdin. Normally on older devices. + // if we get here, we will just reset the stream and drop through to the + // backup path. + packageInfo.SupportsFastDev = false; + fs.Position = 0; + } catch (Exception ex) { + LogDiagnostic ($"Hit exception. Falling back to slow deployment for {file.ItemSpec}. {ex}"); + diagnosticData.SetProperty ("pii.deploy.error", ex.ToString ()); + diagnosticData.SetProperty ("deploy.tool", value:"xamarin.cp"); + packageInfo.SupportsFastDev = false; + fs.Position = 0; + } finally { + ArrayPool.Shared.Return (buffer); + ArrayPool.Shared.Return (compressed); + } + } + if (!packageInfo.SupportsFastDev) { + if (!await PushStreamToDeviceWithTool (Device, PackageName, toolPath, fs, destination, modifiedDateTime, token: CancellationToken)) { + LogDiagnosticDataError ("XA0129", Resources.XA0129_ErrorDeployingFile, destination); + PrintDiagnostics (); + LogCodedError ($"XA0129", Resources.XA0129_ErrorDeployingFile, destination); + return false; + } + } + } + return true; + } + + async Task DeployEnvironmentFiles (ITaskItem[] environments, string toolPath, string overridePath, string targetPath, DateTimeOffset remoteFileModified) + { + int maxKeyLength = 0; + int maxValueLength = 0; + DateTimeOffset newestFileDateTime = DateTimeOffset.MinValue; + var data = new Dictionary (); + foreach (ITaskItem env in environments ?? Array.Empty ()) { + if (!File.Exists (env.ItemSpec)) + continue; + DateTimeOffset modifiedDateTime = File.GetLastWriteTimeUtc (env.ItemSpec); + if (modifiedDateTime > newestFileDateTime) + newestFileDateTime = modifiedDateTime; + foreach (string line in File.ReadLines (env.ItemSpec)) { + if (string.IsNullOrEmpty (line)) + continue; + int index = line.IndexOf ('='); + if (index == -1) { + LogDebugMessage ($"Skipping invalid environment line: {line}"); + continue; + } + var key = line.Substring (0, index); + var value = line.Substring (index + 1); + maxKeyLength = Math.Max (maxKeyLength, key.Length); + maxValueLength = Math.Max (maxValueLength, value.Length); + data [key] = value; + } + } + + // Length+1 so at least one trailing \0 for the longest value + maxKeyLength++; + maxValueLength++; + + if (newestFileDateTime <= remoteFileModified) { + LogDebugMessage ($"NotifySync SkipCopyFile @(AndroidEnvironment) files => {targetPath} file is up to date."); + return true; + } + var stream = new MemoryStream (); // dont use Pool as Device.Push dispose's the stream. + var binaryWriter = new BinaryWriter (stream, Encoding.ASCII); + binaryWriter.Write (Encoding.ASCII.GetBytes ("0x" + maxKeyLength.ToString ("X8") + '\0')); + binaryWriter.Write (Encoding.ASCII.GetBytes ("0x" + maxValueLength.ToString ("X8") + '\0')); + foreach (var kvp in data) { + binaryWriter.Write (Encoding.ASCII.GetBytes (kvp.Key.PadRight (maxKeyLength, '\0'))); + binaryWriter.Write (Encoding.ASCII.GetBytes (kvp.Value.PadRight (maxValueLength, '\0'))); + } + binaryWriter.Flush (); + binaryWriter.BaseStream.Position = 0; + await PushStreamToDeviceWithTool (Device, PackageName, toolPath, binaryWriter.BaseStream, $"{overridePath}/{targetPath}", DateTimeOffset.UtcNow, token: CancellationToken); + LogDebugMessageWithTiming ($"NotifySync CopyFile @(AndroidEnvironment) files."); + LogDiagnostic ($"Local Modified Time '{newestFileDateTime.ToUnixTimeMilliseconds ()}' is newer than '{remoteFileModified.ToUnixTimeMilliseconds ()}'."); + return true; + } + + string GetErrorCode (Exception ex) + { + switch (ex) { +#pragma warning disable CS0618 + case IncompatibleCpuAbiExceptiopn e: + return "ADB0020"; +#pragma warning restore CS0618 + case IncompatibleCpuAbiException e: + return "ADB0020"; + case RequiresUninstallException e: + return "ADB0030"; + case SdkNotSupportedException e: + return "ADB0040"; + case PackageAlreadyExistsException e: + return "ADB0050"; + case InsufficientSpaceException e: + return "ADB0060"; + //NOTE: this one is a base class + case InstallFailedException e: + return "ADB0010"; + default: + return GetErrorCode (ex.Message); + } + } + + static readonly List<(string error, string code, string message)> runas_codes = new List<(string error, string code, string message)> () { + { (error: "run-as is disabled", code: "XA0131", message: Resources.XA0131_DeveloperModeNotEnabled ) }, + { (error: "unknown", code: "XA0132", message: Resources.XA0132_PackageNotInstalled ) }, + { (error: "Permission denied", code: "XA0133", message: Resources.XA0133_RunAsPermissionDenied ) }, + { (error: "package not debuggable", code: "XA0134", message: Resources.XA0134_RunAsPackageNotDebuggable ) }, + { (error: "package not an application", code: "XA0135", message: Resources.XA0135_RunAsPackageNotAndApplication ) }, + { (error: "has corrupt installation", code: "XA0136", message: Resources.XA0136_RunAsCorruptInstallation ) }, + { (error: "users can run this program", code: "XA0137", message: Resources.XA0137_RunAsOSCorrupt ) }, + { (error: "set SELinux security context", code: "XA0137", message: Resources.XA0137_RunAsOSCorrupt ) }, + { (error: "to package's data directory", code: "XA0137", message: Resources.XA0137_RunAsOSCorrupt ) }, + { (error: "couldn't stat", code: "XA0137", message: Resources.XA0137_RunAsOSCorrupt ) }, + { (error: "has wrong owner", code: "XA0137", message: Resources.XA0137_RunAsOSCorrupt ) }, + { (error: "readable or writable by others", code: "XA0137", message: Resources.XA0137_RunAsOSCorrupt ) }, + { (error: "not a directory", code: "XA0137", message: Resources.XA0137_RunAsOSCorrupt ) }, + { (error: "run-as:", code: "XA0137", message: Resources.XA0137_RunAsOSCorrupt ) }, + }; + + bool TryGetRunAsErrorCode (string error, out (string error, string code, string message) errTuple) + { + errTuple = (error: "unknown", code: "XA0132", message: error); + foreach (var err in runas_codes) { + if (error.IndexOf (err.error, StringComparison.OrdinalIgnoreCase) >= 0) { + errTuple = err; + return true; + } + } + return false; + } + + bool RaiseRunAsError (string error) + { + if (TryGetRunAsErrorCode (error, out var err)) { + LogDiagnosticDataError (err.code, err.message); + PrintDiagnostics (); + LogCodedError (err.code, err.message, error); + return true; + } + return false; + } + + string GetFullPath (string dir) => Path.IsPathRooted (dir) ? dir : Path.GetFullPath (Path.Combine (WorkingDirectory, dir)); + + static string GetErrorCode (string message) + { + foreach (var errorCode in error_codes) + if (message.IndexOf (errorCode.message, StringComparison.OrdinalIgnoreCase) >= 0) + return errorCode.code; + + return "ADB1000"; + } + + static readonly List<(string code, string message)> error_codes = new List<(string code , string message)> () { + { (code: "ADB0010", message: nameof (InstallFailedException)) }, +#pragma warning disable CS0618 + { (code: "ADB0020", message: nameof (IncompatibleCpuAbiExceptiopn)) }, +#pragma warning restore CS0618 + { (code: "ADB0020", message: nameof (IncompatibleCpuAbiException)) }, + { (code: "ADB0030", message: nameof (RequiresUninstallException)) }, + { (code: "ADB0040", message: nameof (SdkNotSupportedException)) }, + { (code: "ADB0050", message: nameof (PackageAlreadyExistsException)) }, + { (code: "ADB0060", message: nameof (InsufficientSpaceException)) }, + { (code: "ADB1001", message: "failed to create session") }, + { (code: "ADB1002", message: "failed to finalize session") }, + { (code: "ADB1003", message: "product directory not specified; set $ANDROID_PRODUCT_OUT") }, + { (code: "ADB1004", message: "server didn't ACK") }, + { (code: "ADB1005", message: "server killed by remote request") }, + { (code: "ADB1006", message: "timed out waiting for threads to finish reading from ADB server") }, + { (code: "ADB1007", message: "usage:") }, + { (code: "ADB1008", message: "bulkIn endpoint not assigned") }, + { (code: "ADB1009", message: "bulkOut endpoint not assigned") }, + { (code: "ADB1010", message: "cannot start server on remote host") }, + { (code: "ADB1011", message: "cap_clear_flag(INHERITABLE) failed") }, + { (code: "ADB1012", message: "cap_clear_flag(PEMITTED) failed") }, + { (code: "ADB1013", message: "cap_set_proc() failed") }, + { (code: "ADB1014", message: "Client not connected") }, + { (code: "ADB1015", message: "Could not find device interface") }, + { (code: "ADB1016", message: "Could not set SELinux context") }, + { (code: "ADB1017", message: "Could not start mdnsd") }, + { (code: "ADB1018", message: "could not start server") }, + { (code: "ADB1019", message: "couldn't allocate StdinReadArgs object") }, + { (code: "ADB1020", message: "couldn't create USB matching dictionary") }, + { (code: "ADB1021", message: "daemon started successfully") }, + { (code: "ADB1022", message: "daemon still not running") }, + { (code: "ADB1023", message: "error: no emulator detected") }, + { (code: "ADB1024", message: "error: shell command too long") }, + { (code: "ADB1025", message: "Failed to allocate key") }, + { (code: "ADB1026", message: "failed to allocate memory for ShellProtocol object") }, + { (code: "ADB1027", message: "failed to allocate new subprocess") }, + { (code: "ADB1028", message: "Failed to convert to public key") }, + { (code: "ADB1029", message: "failed to create pipe to report error") }, + { (code: "ADB1030", message: "failed to create run queue notify socketpair") }, + { (code: "ADB1031", message: "failed to empty run queue notify fd") }, + { (code: "ADB1032", message: "failed to encode RSA public key") }, + { (code: "ADB1033", message: "Failed to generate new key") }, + { (code: "ADB1034", message: "failed to get matching services") }, + { (code: "ADB1035", message: "failed to get user home directory") }, + { (code: "ADB1036", message: "Failed to get user key") }, + { (code: "ADB1037", message: "failed to make run queue notify socket nonblocking") }, + { (code: "ADB1038", message: "Failed to read key") }, + { (code: "ADB1039", message: "failed to register libusb hotplug callback") }, + { (code: "ADB1040", message: "failed to start daemon") }, + { (code: "ADB1041", message: "failed to write to run queue notify fd") }, + { (code: "ADB1042", message: "Key must be a null-terminated string") }, + { (code: "ADB1043", message: "Pipe stalled, clearing stall") }, + { (code: "ADB1044", message: "Public key too large to base64 encode") }, + { (code: "ADB1045", message: "reply fd for adb server to client communication not specified") }, + { (code: "ADB1046", message: "run queue notify fd was closed") }, + { (code: "ADB1047", message: "Unable to get interface class, subclass and protocol") }, + { (code: "ADB1048", message: "usb_read interface was null") }, + { (code: "ADB1049", message: "usb_write interface was null") }, + { (code: "ADB1050", message: "cannot fit pipe handle value into 32-bits") }, + { (code: "ADB1051", message: "connect error for create") }, + { (code: "ADB1052", message: "connect error for finalize") }, + { (code: "ADB1053", message: "connect error for write") }, + { (code: "ADB1054", message: "could not open adb service") }, + { (code: "ADB1055", message: "couldn't parse 'wait-for' command") }, + { (code: "ADB1056", message: "CreateFileW 'nul' failed") }, + { (code: "ADB1057", message: "only wrote") }, + { (code: "ADB1058", message: "error response") }, + { (code: "ADB1059", message: "failed to install") }, + { (code: "ADB1060", message: "failed to read block") }, + { (code: "ADB1061", message: "failed to write data") }, + { (code: "ADB1062", message: "invalid reply fd") }, + { (code: "ADB1063", message: "pre-KitKat sideload connection failed") }, + { (code: "ADB1064", message: "doesn't match this client") }, + { (code: "ADB1065", message: "sideload connection failed") }, + { (code: "ADB1066", message: "unable to connect for backup") }, + { (code: "ADB1067", message: "unable to connect for restore") }, + { (code: "ADB1068", message: "unable to connect for") }, + { (code: "ADB1069", message: "unexpected output length for") }, + { (code: "ADB1070", message: "expected 'any', 'local', or 'usb'") }, + { (code: "ADB1071", message: "attempted to close unregistered usb_handle for") }, + { (code: "ADB1072", message: "attempted to reinitialize adb_server_socket_spec") }, + { (code: "ADB1073", message: "cannot connect to daemon at") }, + { (code: "ADB1074", message: "Cannot mkdir") }, + { (code: "ADB1075", message: "Connection banner is too long") }, + { (code: "ADB1076", message: "Could not clear pipe stall both ends") }, + { (code: "ADB1077", message: "Could not install smartsocket listener") }, + { (code: "ADB1078", message: "Could not open interface") }, + { (code: "ADB1079", message: "Could not register mDNS service") }, + { (code: "ADB1080", message: "Couldn't create a device interface") }, + { (code: "ADB1081", message: "Couldn't grab device from interface") }, + { (code: "ADB1082", message: "Couldn't query the interface") }, + { (code: "ADB1083", message: "daemon not running; starting now at") }, + { (code: "ADB1084", message: "destroying fde not created by fdevent_create") }, + { (code: "ADB1085", message: "Encountered mDNS registration error") }, + { (code: "ADB1086", message: "not implemented on Win32") }, + { (code: "ADB1087", message: "could not connect to TCP port") }, + { (code: "ADB1088", message: "no emulator connected") }, + { (code: "ADB1089", message: "only supports allocating a pty") }, + { (code: "ADB1090", message: "failed to connect to socket") }, + { (code: "ADB1091", message: "failed to convert errno") }, + { (code: "ADB1092", message: "failed to initialize libusb") }, + { (code: "ADB1093", message: "Failed to parse key") }, + { (code: "ADB1094", message: "failed to set non-blocking mode for fd") }, + { (code: "ADB1095", message: "failed to start subprocess management thread") }, + { (code: "ADB1096", message: "failed to start subprocess") }, + { (code: "ADB1097", message: "FindDeviceInterface - could not get pipe properties") }, + { (code: "ADB1098", message: "Invalid base64 key") }, + { (code: "ADB1099", message: "Key too long") }, + { (code: "ADB1100", message: "No ':' found in shell service arguments") }, + { (code: "ADB1101", message: "observed inotify event for unmonitored path") }, + { (code: "ADB1102", message: "packet data length doesn't match payload") }, + { (code: "ADB1103", message: "Unable to create a device plug-in") }, + { (code: "ADB1104", message: "Unable to create an interface plug-in") }, + { (code: "ADB1105", message: "Unable to get number of endpoints") }, + { (code: "ADB1106", message: "unexpected type for") }, + { (code: "ADB1107", message: "Unknown socket type") }, + { (code: "ADB1108", message: "Unknown trace flag") }, + { (code: "ADB1109", message: "usb_read failed with status") }, + { (code: "ADB1110", message: "usb_write failed with status") }, + { (code: "ADB1111", message: "adb_socket_accept: failed to allocate accepted socket") }, + { (code: "ADB1112", message: "cannot create service socket pair") }, + { (code: "ADB1113", message: "cannot create socket pair") }, + { (code: "ADB1114", message: "Error generating token") }, + { (code: "ADB1115", message: "Error getting user key filename") }, + { (code: "ADB1116", message: "Failed to accept") }, + { (code: "ADB1117", message: "failed to create inotify fd") }, + { (code: "ADB1118", message: "Failed to get adbd socket") }, + { (code: "ADB1119", message: "failed to shutdown writes to FD") }, + { (code: "ADB1120", message: "Failed to write PK") }, + { (code: "ADB1121", message: "failed to write the exit code packet") }, + { (code: "ADB1122", message: "read of inotify event failed") }, + { (code: "ADB1123", message: "remote usb: 1 - write terminated") }, + { (code: "ADB1124", message: "remote usb: 2 - write terminated") }, + { (code: "ADB1125", message: "remote usb: read terminated (message)") }, + { (code: "ADB1126", message: "remote usb: terminated (data)") }, + { (code: "ADB1127", message: "select failed, closing subprocess pipes") }, + { (code: "ADB1128", message: "backup unable to create file") }, + { (code: "ADB1129", message: "cannot create thread") }, + { (code: "ADB1130", message: "cannot get executable path") }, + { (code: "ADB1131", message: "cannot make handle") }, + { (code: "ADB1132", message: "CreatePipe failed") }, + { (code: "ADB1133", message: "CreateProcessW failed") }, + { (code: "ADB1134", message: "error while reading for") }, + { (code: "ADB1135", message: "execl returned") }, + { (code: "ADB1136", message: "failed to duplicate file descriptor for") }, + { (code: "ADB1137", message: "failed to get file descriptor for") }, + { (code: "ADB1138", message: "failed to open duplicate stream for") }, + { (code: "ADB1139", message: "failed to open file") }, + { (code: "ADB1140", message: "failed to read command") }, + { (code: "ADB1141", message: "failed to read data from") }, + { (code: "ADB1142", message: "failed to read from") }, + { (code: "ADB1143", message: "failed to read package block") }, + { (code: "ADB1144", message: "failed to seek to package block") }, + { (code: "ADB1145", message: "failed to set binary mode for duplicate of") }, + { (code: "ADB1146", message: "failed to stat file") }, + { (code: "ADB1147", message: "failed to stat") }, + { (code: "ADB1148", message: "failed to unbuffer") }, + { (code: "ADB1149", message: "adb_socket_accept: accept on fd") }, + { (code: "ADB1150", message: "unable to open file") }, + { (code: "ADB1151", message: "unexpected result waiting for threads") }, + { (code: "ADB1152", message: "aio: got error event on") }, + { (code: "ADB1153", message: "aio: got error submitting") }, + { (code: "ADB1154", message: "aio: got error waiting") }, + { (code: "ADB1155", message: "cannot open bulk-in endpoint") }, + { (code: "ADB1156", message: "cannot open bulk-out endpoint") }, + { (code: "ADB1157", message: "cannot open control endpoint") }, + { (code: "ADB1158", message: "Can't load") }, + { (code: "ADB1159", message: "could not read ok from ADB Server") }, + { (code: "ADB1160", message: "couldn't allocate state_info") }, + { (code: "ADB1161", message: "Couldn't read") }, + { (code: "ADB1162", message: "cannot write to emulator") }, + { (code: "ADB1163", message: "error reading output FD") }, + { (code: "ADB1164", message: "error reading protocol FD") }, + { (code: "ADB1165", message: "error reading stdin FD") }, + { (code: "ADB1166", message: "write failure during connection") }, + { (code: "ADB1167", message: "failed to fcntl(F_GETFL) for fd") }, + { (code: "ADB1168", message: "failed to fcntl(F_SETFL) for fd") }, + { (code: "ADB1169", message: "failed to inotify_add_watch on path") }, + { (code: "ADB1170", message: "Failed to listen on") }, + { (code: "ADB1171", message: "failed to open directory") }, + { (code: "ADB1172", message: "Failed to write public key to") }, + { (code: "ADB1173", message: "failure closing FD") }, + { (code: "ADB1174", message: "pipe failed in launch_server") }, + { (code: "ADB1175", message: "poll() }, ret =") }, + { (code: "ADB1176", message: "remote usb: read overflow") }, + { (code: "ADB1177", message: "received framework auth socket connection again") }, + { (code: "ADB1178", message: "failed to claim adb interface for device") }, + { (code: "ADB1179", message: "failed to clear halt on device") }, + { (code: "ADB1180", message: "failed to get active config descriptor for device at") }, + { (code: "ADB1181", message: "failed to get device descriptor for device at") }, + { (code: "ADB1182", message: "failed to get serial from device at") }, + { (code: "ADB1183", message: "failed to open usb device at") }, + { (code: "ADB1184", message: "failed to set interface alt setting for device") }, + { (code: "ADB1185", message: "failed to submit zero-length write") }, + { (code: "ADB1186", message: "failed to submit") }, + { (code: "ADB1187", message: "Ignoring unknown shell service argument") }, + { (code: "ADB1188", message: "transfer failed:") }, + { (code: "ADB1189", message: "received empty serial from device at") }, + { (code: "ADB1190", message: "refusing to recurse into directory") }, + { (code: "ADB1191", message: "unmonitored event for") }, + { (code: "ADB1192", message: "Failed to open") }, + { (code: "ADB1193", message: "failed to write") }, + }; + } + + internal static class DeviceExt + { + internal static string RunAsCommand = "run-as"; + + internal static List BuildArgs(string command, FastDeploy.PackageInfo packageInfo) + { + List args = new List (); + if (packageInfo.IsSystemApplication) { + if (!packageInfo.AdbIsRoot) { + args.Add ("su"); + args.Add (packageInfo.UserId ?? "0"); + } + return args; + } + args.Add (RunAsCommand); + args.Add (packageInfo.PackageName); + if (!string.IsNullOrEmpty (packageInfo.UserId)) { + args.Add("--user"); + args.Add(packageInfo.UserId); + } + return args; + } + + internal static async Task RunAs (this AndroidDevice Device, FastDeploy.PackageInfo packageInfo, IEnumerable arguments) + { + string [] args = arguments.ToArray (); + string result = await Device.RunAs (packageInfo, args); + packageInfo.LogDebugMessage ($"{args[0]} returned: {result}"); + return result; + } + + internal static async Task RunAs (this AndroidDevice Device, FastDeploy.PackageInfo packageInfo, params string [] arguments) + { + List args = BuildArgs(RunAsCommand, packageInfo); + args.AddRange (arguments); + string result = await Device.RunShellCommand (args.ToArray ()); + packageInfo.LogDebugMessage ($"{arguments[0]} returned: {result}"); + return result; + } + } +} diff --git a/src/Xamarin.Android.Build.Debugging.Tasks/Tasks/GenerateGdbEnvironment.cs b/src/Xamarin.Android.Build.Debugging.Tasks/Tasks/GenerateGdbEnvironment.cs new file mode 100644 index 00000000000..b31ed405454 --- /dev/null +++ b/src/Xamarin.Android.Build.Debugging.Tasks/Tasks/GenerateGdbEnvironment.cs @@ -0,0 +1,41 @@ +using System; +using System.IO; +using System.Linq; + +using Microsoft.Build.Framework; +using Microsoft.Build.Utilities; + +namespace Xamarin.Android.Tasks +{ + public class GenerateGdbEnvironment : Task { + + [Required] + public string OutputFile { get; set; } + public string GdbSymbolsPath { get; set; } + public int GdbTargetPort { get; set; } + + public override bool Execute () + { + using (var o = File.CreateText (OutputFile)) { + o.NewLine = "\n"; + + var symbolsPath = FixupPath (GdbSymbolsPath); + + o.WriteLine ("set solib-search-path {0}", FixupPath (GdbSymbolsPath)); + o.WriteLine ("file {0}", FixupPath (Path.Combine (GdbSymbolsPath, "app_process"))); + o.WriteLine ("target remote :{0}", GdbTargetPort); + o.WriteLine ("handle SIGXCPU SIG33 SIG35 SIGPWR SIGTTIN SIGTTOU SIGSYS nostop noprint"); + } + + return true; + } + + static string FixupPath (string path) + { + if (string.IsNullOrEmpty (path)) + return path; + return Path.GetFullPath (path).Replace ('\\', '/'); + } + } +} + diff --git a/src/Xamarin.Android.Build.Debugging.Tasks/Tasks/GetGdbSymbols.cs b/src/Xamarin.Android.Build.Debugging.Tasks/Tasks/GetGdbSymbols.cs new file mode 100644 index 00000000000..c84b27b1f78 --- /dev/null +++ b/src/Xamarin.Android.Build.Debugging.Tasks/Tasks/GetGdbSymbols.cs @@ -0,0 +1,66 @@ +using System; +using System.IO; +using System.Linq; +using System.Threading; +using Xamarin.Tools.Zip; +using Microsoft.Build.Framework; +using Microsoft.Build.Utilities; +using Xamarin.AndroidTools; +using Microsoft.Android.Build.Tasks; + + +namespace Xamarin.Android.Tasks +{ + public class GetGdbSymbols : AndroidTask + { + public override string TaskPrefix => "GGDB"; + + public string AdbTarget { get; set; } + + [Required] + public string GdbSymbolsPath { get; set;} + + [Required] + public string Package { get; set; } + + [Required] + public string OutputPath { get; set; } + + [Required] + public string PrimaryCpuAbi {get; set; } + + public GetGdbSymbols () + { + AdbTarget = null; + } + + public override bool RunTask () + { + var device = AndroidHelper.ParseTarget (AdbTarget, Log, logErrors: true, engine4: BuildEngine4); + if (device == null) { + return false; + } + device.EnsureProperties (CancellationToken.None).Wait (); + var arch = PrimaryCpuAbi; + Log.LogDebugMessage ("Device Abi {0}", arch); + + var apk = string.Format ("{0}{1}-Signed.apk", OutputPath, Package); + ExtractFilesFromPath (apk, string.Format ("lib/{0}/", arch)); + + return !Log.HasLoggedErrors; + } + + void ExtractFilesFromPath(string apk, string path) + { + using (var zip = ZipArchive.Open (apk, FileMode.Open)) { + foreach (var e in zip.Where (x => x.FullName.StartsWith (path, StringComparison.OrdinalIgnoreCase))) { + Log.LogDebugMessage ("Extracting {0} from {1}", e.FullName, apk); + using (var fs = new FileStream (Path.Combine (GdbSymbolsPath, Path.GetFileName (e.FullName)), FileMode.OpenOrCreate)) { + e.Extract (fs); + } + } + } + } + } +} + diff --git a/src/Xamarin.Android.Build.Debugging.Tasks/Tasks/GetPrimaryCpuAbi.cs b/src/Xamarin.Android.Build.Debugging.Tasks/Tasks/GetPrimaryCpuAbi.cs new file mode 100644 index 00000000000..c5129091d4b --- /dev/null +++ b/src/Xamarin.Android.Build.Debugging.Tasks/Tasks/GetPrimaryCpuAbi.cs @@ -0,0 +1,219 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Xml.Linq; +using System.Text.RegularExpressions; +using Microsoft.Build.Framework; +using Microsoft.Android.Build.Tasks; +using Mono.AndroidTools; +using AndroidLogger = Mono.AndroidTools.AndroidLogger; + +namespace Xamarin.Android.Tasks +{ + public class GetPrimaryCpuAbi : AsyncTask + { + static readonly Dictionary UnameAbiMapping = new Dictionary { + { "x86", "x86" }, + { "x86_64", "x86_64" }, + { "armeabi-v7a", "armeabi-v7a" }, + { "arm64-v8a", "arm64-v8a" }, + { "i386", "x86" }, + { "i686", "x86" }, + { "aarch64", "arm64-v8a" }, + { "arm", "armeabi-v7a" }, + }; + + public override string TaskPrefix => "GCPU"; + + public string AdbTarget { get; set; } + /// + /// IDEs set $(AdbTargetArchitecture) when an emulator is closed, as a performance optimization. + /// + public string AdbTargetArchitecture { get; set; } + public string AdbOptions { get ;set; } + public string AndroidPackage { get; set; } + public string DevicePropertyCache { get; set; } + public string [] RuntimeIdentifiers { get; set; } + + [Output] + public bool FoundDevices { get; set; } + [Output] + public string ResultingAbi { get; set; } + [Output] + public string ToolsAbi { get; set; } + [Output] + public string RuntimeIdentifier { get; set; } + [Output] + public int SdkVersion { get; set; } + + AndroidDevice device; + + public override bool Execute () + { + if (!string.IsNullOrEmpty (AdbTargetArchitecture)) { + LogDebugMessage ($"Using $(AdbTargetArchitecture): {AdbTargetArchitecture}"); + ResultingAbi = AdbTargetArchitecture; + RuntimeIdentifier = GetRuntimeIdentifier (); + LogOutputs (); + return true; + } + + device = AndroidHelper.ParseTarget (AdbTarget, LogDebugMessage, LogCodedError, logErrors: false, engine4: BuildEngine4); + if (device == null) { + LogDebugMessage ($"No device found: {nameof (AdbTarget)}=\"{AdbTarget}\""); + // don't stop the build if we don't have a device. + return true; + } + + LogDebugMessage ($"Found device: {device.ID}"); + FoundDevices = true; + + AndroidLogger.Error += DebugHandler; + AndroidLogger.Warning += DebugHandler; + AndroidLogger.Info += DebugHandler; + AndroidLogger.Debug += DebugHandler; + try { + bool result = base.Execute (); + return result; + } finally { + AndroidLogger.Error += DebugHandler; + AndroidLogger.Warning += DebugHandler; + AndroidLogger.Info += DebugHandler; + AndroidLogger.Debug -= DebugHandler; + } + } + + public async override System.Threading.Tasks.Task RunTaskAsync () + { + int sdkver = 0; + + XDocument doc = null; + if (File.Exists (DevicePropertyCache)) { + LogDebugMessage ($"Using cached properties: {DevicePropertyCache}"); + doc = XDocument.Load (DevicePropertyCache); + if (DeviceCache.TryGet (doc, device.ID, device.LongOutput, out var cachedAbi, out var cachedToolsAbi, out var cachedSdkVersion, Log)) { + ResultingAbi = cachedAbi; + ToolsAbi = cachedToolsAbi; + SdkVersion = cachedSdkVersion; + RuntimeIdentifier = GetRuntimeIdentifier (); + LogOutputs (); + return; + } + LogDebugMessage ($"Cache miss or stale for device {device.ID}. Refreshing."); + } else { + LogDebugMessage ($"Cached properties did not exist: {DevicePropertyCache}"); + } + + try { + await device.EnsureProperties (CancellationToken); + } catch (Exception ex) { + LogDebugMessage (ex.ToString ()); + return; + } + + sdkver = device.Properties.BuildVersionSdk; + if (sdkver <= 0) { + LogDebugMessage ($"device.Properties.BuildVersionSdk is {sdkver}. Forcing PropertyRefresh."); + await device.RefreshProperties (CancellationToken); + sdkver = device.Properties.BuildVersionSdk; + } + + if (sdkver >= 21) { + string command = "getprop ro.product.cpu.abilist64"; + string commandResult = await device.RunShellCommand (command, CancellationToken); + LogDebugMessage ($"{command} {commandResult}"); + string[] abis = commandResult.Split (new [] { ',' }, StringSplitOptions.RemoveEmptyEntries); + if (abis.Length > 0) { + ResultingAbi = abis [0].Trim (); + } + } + + if (string.IsNullOrEmpty (ResultingAbi)) { + if (string.IsNullOrEmpty (device.Properties.ProductCpuAbi)) { + LogDebugMessage ("device.Properties.ProductCpuAbi is null. Forcing PropertyRefresh."); + await device.RefreshProperties (CancellationToken); + } + ResultingAbi = device.Properties.ProductCpuAbi ?? device.Properties.ProductCpuAbi2; + } + if (string.IsNullOrEmpty (ResultingAbi) && device.IsEmulator) { + string command = "uname -m"; + string commandResult = await device.RunShellCommand (command, CancellationToken); + LogDebugMessage ($"{command} {commandResult}"); + if (!commandResult.Contains ("adb:")) { + string abi = commandResult.Trim (); + if (!UnameAbiMapping.ContainsKey (abi)) { + LogDebugMessage ($"Unexpected Abi returned from `uname -m` {abi}. Ignoring result."); + } else { + ResultingAbi = UnameAbiMapping [abi]; + } + } + } + if (string.IsNullOrEmpty (ResultingAbi) && !string.IsNullOrEmpty (AndroidPackage)) { + LogDebugMessage ($"Falling back to pm dump {AndroidPackage}."); + ResultingAbi = await GetAbiFromPmDump (device); + } + RuntimeIdentifier = GetRuntimeIdentifier (); + SdkVersion = sdkver; + LogOutputs (); + + doc = DeviceCache.Update (doc, device.ID, ResultingAbi, ToolsAbi, SdkVersion, device.LongOutput); + if (doc.SaveIfChanged (DevicePropertyCache)) { + LogDebugMessage ($"Saving: {DevicePropertyCache}"); + } + } + void DebugHandler (string task, string message) + { + LogDebugMessage ($"DEBUG {task} {message}."); + } + + void LogOutputs () + { + LogDebugMessage ($" {nameof (ResultingAbi)}: {ResultingAbi}"); + LogDebugMessage ($" {nameof (ToolsAbi)}: {ToolsAbi}"); + LogDebugMessage ($" {nameof (RuntimeIdentifier)}: {RuntimeIdentifier}"); + LogDebugMessage ($" {nameof (SdkVersion)}: {SdkVersion}"); + } + + async System.Threading.Tasks.Task GetAbiFromPmDump (AndroidDevice device) + { + var rex = new Regex ("primaryCpuAbi=(?([A-Za-z0-9_-])*)"); + string command = "pm dump packages | grep primaryCpuAbi | grep -v '=null' | sort | uniq -c"; + string result = await device.RunShellCommand (command, CancellationToken); + result = result.Trim (); + LogDebugMessage ($"{command}: {result}"); + SortedDictionary abis = new SortedDictionary (); + foreach (var line in result.Split ('\n')) { + string[] items = line.Split (new char[] {' '}, StringSplitOptions.RemoveEmptyEntries); + if (items.Length != 2) + continue; + int count; + if (!int.TryParse (items[0], out count)) + continue; + string abi = rex.Match (items[1]).Groups ["abi"].ToString (); + if (string.IsNullOrEmpty (abi)) + continue; + abis.Add (count, abi); + } + if (abis.Count == 0) + return string.Empty; + + return abis.Last ().Value; + } + + string GetRuntimeIdentifier () + { + if (string.IsNullOrEmpty (ResultingAbi)) { + return null; + } + if (RuntimeIdentifiers != null) { + foreach (var rid in RuntimeIdentifiers) { + if (AndroidRidAbiHelper.RuntimeIdentifierToAbi (rid) == ResultingAbi) { + return rid; + } + } + } + return null; + } + } +} diff --git a/src/Xamarin.Android.Build.Debugging.Tasks/Tasks/MoveSubfolder.cs b/src/Xamarin.Android.Build.Debugging.Tasks/Tasks/MoveSubfolder.cs new file mode 100644 index 00000000000..b3045c62c6e --- /dev/null +++ b/src/Xamarin.Android.Build.Debugging.Tasks/Tasks/MoveSubfolder.cs @@ -0,0 +1,68 @@ +using System.Collections.Generic; +using System.IO; +using System.Linq; +using Microsoft.Android.Build.Tasks; +using Microsoft.Build.Framework; +using Microsoft.Build.Tasks; +using Microsoft.Build.Utilities; + +namespace Xamarin.Android.Tasks +{ + /// + /// Extends the task, so we can preserve directory structure of an unknown subfolder. + /// It fills out the SourceFiles & DestinationFiles properties, clears DestinationFolder, and calls the base task. + /// + public class MoveSubfolder : Move + { + [Required] + public string SourceFolder { get; set; } + + /// + /// An optional path inside the unknown subfolder + /// + public string AdditionalPath { get; set; } + + /// + /// This is only needed to remove [Required] + /// + public new ITaskItem [] SourceFiles { + get => base.SourceFiles; + set => base.SourceFiles = value; + } + + public override bool Execute () + { + // If we encounter unexpected input, we can just call base.Execute(); + if (DestinationFolder == null) { + Log.LogDebugMessage ("DestinationFolder is null."); + return base.Execute (); + } + + var directories = Directory.GetDirectories (SourceFolder); + if (directories.Length == 0) { + Log.LogDebugMessage ("No subdirectories found."); + return !Log.HasLoggedErrors; + } + + // Full path to an unknown subfolder + var subfolder = Path.GetFullPath (directories.First ()); + if (!string.IsNullOrEmpty (AdditionalPath)) { + subfolder = Path.Combine (subfolder, AdditionalPath); + } + subfolder = subfolder.TrimEnd (Path.DirectorySeparatorChar); // trim off trailing / if present + Log.LogDebugMessage ($"Copying from: {subfolder}"); + + var sourceFiles = new List (); + var destinationFiles = new List (); + foreach (var file in Directory.GetFiles (subfolder, "*", SearchOption.AllDirectories)) { + var relativePath = file.Substring (subfolder.Length + 1); + sourceFiles.Add (new TaskItem (file)); + destinationFiles.Add (new TaskItem (Path.Combine (DestinationFolder.ItemSpec, relativePath))); + } + DestinationFolder = null; + DestinationFiles = destinationFiles.ToArray (); + SourceFiles = sourceFiles.ToArray (); + return base.Execute (); + } + } +} diff --git a/src/Xamarin.Android.Build.Debugging.Tasks/Tasks/ResolveXamarinAndroidTools.cs b/src/Xamarin.Android.Build.Debugging.Tasks/Tasks/ResolveXamarinAndroidTools.cs new file mode 100644 index 00000000000..460d054b490 --- /dev/null +++ b/src/Xamarin.Android.Build.Debugging.Tasks/Tasks/ResolveXamarinAndroidTools.cs @@ -0,0 +1,143 @@ +using System; +using System.IO; +using System.Runtime.CompilerServices; +using Microsoft.Build.Framework; +using Microsoft.Build.Utilities; +using Mono.AndroidTools; +using AT = Xamarin.AndroidTools; +using Microsoft.Android.Build.Tasks; + +namespace Xamarin.Android.Tasks +{ + /// + /// This task sets static variables on Xamarin.AndroidTools.AndroidSdk and MonoDroidSdk + /// + public class ResolveXamarinAndroidTools : Task + { + public string AndroidNdkPath { get; set; } + + public string AndroidSdkPath { get; set; } + + public string JavaSdkPath { get; set; } + + [Required] + public string MonoAndroidToolsPath { get; set; } + + [Required] + public string MonoAndroidBinDirectory { get; set; } + + [Required] + public string [] ReferenceAssemblyPaths { get; set; } + + const string AndroidSdkKey = nameof (ResolveXamarinAndroidTools) + ".Xamarin.AndroidTools." + nameof (AT.AndroidSdk); + const string MonoDroidSdkKey = nameof (ResolveXamarinAndroidTools) + ".Xamarin.AndroidTools." + nameof (AT.MonoDroidSdk); + const RegisteredTaskObjectLifetime lifetime = RegisteredTaskObjectLifetime.AppDomain; + + static ResolveXamarinAndroidTools () => AppContext.SetSwitch (AT.AndroidSdk.AutoRefreshSwitch, false); + + public override bool Execute () + { + AndroidLogger.Error += ErrorHandler; + AndroidLogger.Warning += WarningHandler; + AndroidLogger.Info += InfoHandler; + AndroidLogger.Debug += DebugHandler; + try { + return RunTask (); + } finally { + AndroidLogger.Error -= ErrorHandler; + AndroidLogger.Warning -= WarningHandler; + AndroidLogger.Info -= InfoHandler; + AndroidLogger.Debug -= DebugHandler; + } + } + + bool RunTask () + { + var engine = BuildEngine4; + var androidPaths = engine.GetRegisteredTaskObjectAssemblyLocal (AndroidSdkKey, lifetime); + + if (androidPaths == null || androidPaths.HasChanged (this)) { + AT.AndroidSdk.Refresh (AndroidSdkPath, AndroidNdkPath, JavaSdkPath); + if (!Log.HasLoggedErrors) { + engine.RegisterTaskObjectAssemblyLocal (AndroidSdkKey, new AndroidSdkPaths (this), lifetime, allowEarlyCollection: false); + } + } else { + Log.LogDebugMessage (" Using cached AndroidSdk values"); + } + Log.LogDebugMessage (" Found AndroidSdk at {0}", AT.AndroidSdk.AndroidSdkPath); + Log.LogDebugMessage (" Found AndroidNdk at {0}", AT.AndroidSdk.AndroidNdkPath); + Log.LogDebugMessage (" Found AndroidTools at {0}", string.Join (",", AT.AndroidSdk.GetCommandLineToolsPaths ())); + + var frameworkDirectory = ReferenceAssemblyPaths [0].TrimEnd (Path.DirectorySeparatorChar); + var monodroidPaths = engine.GetRegisteredTaskObjectAssemblyLocal (MonoDroidSdkKey, lifetime); + if (monodroidPaths == null || monodroidPaths.HasChanged (this, frameworkDirectory)) { + AT.MonoDroidSdk.Refresh (MonoAndroidToolsPath, MonoAndroidBinDirectory, frameworkDirectory); + if (!Log.HasLoggedErrors) { + engine.RegisterTaskObjectAssemblyLocal (MonoDroidSdkKey, new MonoDroidPaths (this, frameworkDirectory), lifetime, allowEarlyCollection: false); + } + } else { + Log.LogDebugMessage (" Using cached MonoDroidSdk values"); + } + Log.LogDebugMessage (" Found RuntimePath at {0}", AT.MonoDroidSdk.RuntimePath); + Log.LogDebugMessage (" Found FrameworkPath at {0}", AT.MonoDroidSdk.FrameworkPath); + + return !Log.HasLoggedErrors; + } + + class AndroidSdkPaths + { + public AndroidSdkPaths (ResolveXamarinAndroidTools task) + { + AndroidSdkPath = task.AndroidSdkPath; + AndroidNdkPath = task.AndroidNdkPath; + JavaSdkPath = task.JavaSdkPath; + } + + public bool HasChanged (ResolveXamarinAndroidTools task) + { + return AndroidSdkPath != task.AndroidSdkPath || AndroidNdkPath != task.AndroidNdkPath || JavaSdkPath != task.JavaSdkPath; + } + + public string AndroidSdkPath { get; private set; } + public string AndroidNdkPath { get; private set; } + public string JavaSdkPath { get; private set; } + } + + class MonoDroidPaths + { + public MonoDroidPaths (ResolveXamarinAndroidTools task, string frameworkDirectory) + { + MonoAndroidToolsPath = task.MonoAndroidToolsPath; + FrameworkDirectory = frameworkDirectory; + } + + public bool HasChanged (ResolveXamarinAndroidTools task, string frameworkDirectory) + { + return MonoAndroidToolsPath != task.MonoAndroidToolsPath || FrameworkDirectory != frameworkDirectory; + } + + public string MonoAndroidToolsPath { get; private set; } + public string FrameworkDirectory { get; private set; } + } + + void ErrorHandler (string task, string message) + { + Log.LogCodedError ("XA5300", $"{task} {message}"); + } + + void WarningHandler (string task, string message) + { + Log.LogCodedWarning ("XA5300", $"{task} {message}"); + } + + void DebugHandler (string task, string message) + { + Log.LogDebugMessage ($"DEBUG {task} {message}"); + } + + void InfoHandler (string task, string message) + { + Log.LogMessage ($"{task} {message}"); + } + } +} diff --git a/src/Xamarin.Android.Build.Debugging.Tasks/Tasks/RunActivity.cs b/src/Xamarin.Android.Build.Debugging.Tasks/Tasks/RunActivity.cs new file mode 100644 index 00000000000..b4348951ab3 --- /dev/null +++ b/src/Xamarin.Android.Build.Debugging.Tasks/Tasks/RunActivity.cs @@ -0,0 +1,113 @@ +// +// RunActivity.cs +// +// Author: +// Jonathan Pryor +// +// Copyright (c) 2013 Xamarin Inc. (http://www.xamarin.com) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +using System.IO; +using System.Xml; +using Microsoft.Build.Framework; +using Microsoft.Build.Utilities; +using Mono.AndroidTools; +using Microsoft.Android.Build.Tasks; +using Xamarin.AndroidTools; +using Xamarin.AndroidTools.Debugging; +using Xamarin.Android.Build.Debugging.Tasks.Properties; + +namespace Xamarin.Android.Tasks +{ + public class RunActivity : AsyncTask + { + public override string TaskPrefix => "RUNA"; + + [Required] + public string PackageName { get; set; } + + [Required] + public string ActivityName { get; set; } + + public string AdbTarget { get; set; } + + public bool AttachDebugger { get; set; } + + public bool Server { get; set; } + + public string Port { get; set; } + + public int UserID { get; set; } = 0; + + public bool ForceStop { get; set; } = true; + + /// + /// Gets or sets a value indicating whether Java debugging is allowed. Defaults to true, but will be available to be toggled off via -p:_AndroidAllowJavaDebugging=false. + /// + public bool AllowJavaDebugging { get; set; } = true; + + AndroidDevice Device; + + public RunActivity () + { + Port = "10000"; + } + + public override bool Execute () + { + Device = AndroidHelper.ParseTarget (AdbTarget, LogMessage, LogCodedError, logErrors: true, engine4: BuildEngine4); + if (Device == null) { + return false; + } + LogMessage ($"Found device: {Device.ID}"); + return base.Execute (); + } + + public async override System.Threading.Tasks.Task RunTaskAsync () + { + LogDebugMessage ($" ActivityName: {ActivityName}"); + + var amStartCommand = new AmStartCommand (PackageName, ActivityName); + amStartCommand.ForceStop = ForceStop; + amStartCommand.EnableDebugging = false; + amStartCommand.User = UserID.ToString (); + + amStartCommand.Action = amStartCommand.Action ?? "android.intent.action.MAIN"; + amStartCommand.Categories = amStartCommand.Categories ?? new[] { "android.intent.category.LAUNCHER" }; + + var startConfiguration = new ExecutionConfiguration (PackageName, amStartCommand); + startConfiguration.AllowJavaDebugging = AllowJavaDebugging; + startConfiguration.LogWiter = (s) => LogDebugMessage (s); + + if (AttachDebugger) { + var port = int.Parse (Port); + var ipAddress = Server ? System.Net.IPAddress.Loopback : System.Net.IPAddress.Parse("10.0.2.2"); + startConfiguration.Debugger.Address = ipAddress; + startConfiguration.Debugger.SdbPort = port; + startConfiguration.Debugger.StdoutPort = -1; + startConfiguration.Debugger.Server = Server; + LogMessage (string.Format (Resources.StartDebugger_ipAddress_port, ipAddress, port), MessageImportance.High); + await Device.StartWithDebuggingAsync (startConfiguration, CancellationToken); + } else { + await Device.StartWithoutDebuggingAsync (startConfiguration, CancellationToken); + } + } + } +} diff --git a/src/Xamarin.Android.Build.Debugging.Tasks/Tasks/RunTests.cs b/src/Xamarin.Android.Build.Debugging.Tasks/Tasks/RunTests.cs new file mode 100644 index 00000000000..03fe26b90a1 --- /dev/null +++ b/src/Xamarin.Android.Build.Debugging.Tasks/Tasks/RunTests.cs @@ -0,0 +1,38 @@ +using System; +using System.IO; +using Microsoft.Build.Utilities; +using Microsoft.Build.Framework; + +using Xamarin.AndroidTools; + +namespace Xamarin.Android.Tasks +{ + public class RunTests : ToolTask + { + public string AdbTarget { get; set; } + + public string AdbOptions { get; set; } + [Required] + public string AndroidPackage { get; set; } + + [Required] + public string TargetTestActivity { get; set; } + + protected override string ToolName { + get { return OS.IsWindows ? "adb.exe" : "adb"; } + } + + protected override string GenerateFullPathToTool () + { + return Path.Combine (ToolPath, ToolExe); + } + + protected override string GenerateCommandLineCommands () + { + // If we supported full MSBuild 4.0 expression syntax for [Class] and property access, we could write this... + // + return string.Format ("{0} {1} shell am start -a android.intent.action.MAIN -c android.intent.category.LAUNCHER --ez automated true -n {2}/{3}", + AdbTarget, AdbOptions, AndroidPackage, TargetTestActivity); + } + } +} diff --git a/src/Xamarin.Android.Build.Debugging.Tasks/Tasks/WaitForAppDetection.cs b/src/Xamarin.Android.Build.Debugging.Tasks/Tasks/WaitForAppDetection.cs new file mode 100644 index 00000000000..b4a4f11629a --- /dev/null +++ b/src/Xamarin.Android.Build.Debugging.Tasks/Tasks/WaitForAppDetection.cs @@ -0,0 +1,35 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading; +using Microsoft.Build.Framework; +using Microsoft.Build.Utilities; +using Mono.AndroidTools; +using Microsoft.Android.Build.Tasks; + +namespace Xamarin.Android.Tasks { + public class WaitForAppDetection : AsyncTask + { + public override string TaskPrefix => "WFAD"; + + System.Threading.Tasks.Task> getPackagesAsync; + + public override bool Execute () + { + var key = ProjectSpecificTaskObjectKey (DetectIfAppWasUninstalled.GetPackagesAsyncKey); + getPackagesAsync = BuildEngine4.GetRegisteredTaskObjectAssemblyLocal>> (key, RegisteredTaskObjectLifetime.Build); + return base.Execute (); + } + + public async override System.Threading.Tasks.Task RunTaskAsync () + { + LogDebugMessage ("Waiting for DetectIfAppWasUninstalled..."); + if (getPackagesAsync == null) + return; + await getPackagesAsync; + LogDebugMessage ("DetectIfAppWasUninstalled Completed."); + return; + } + } +} \ No newline at end of file diff --git a/src/Xamarin.Android.Build.Debugging.Tasks/Utilities/AndroidHelper.cs b/src/Xamarin.Android.Build.Debugging.Tasks/Utilities/AndroidHelper.cs new file mode 100644 index 00000000000..a1319037a06 --- /dev/null +++ b/src/Xamarin.Android.Build.Debugging.Tasks/Utilities/AndroidHelper.cs @@ -0,0 +1,122 @@ +using System; +using System.Linq; +using Mono.AndroidTools; +using Microsoft.Build.Framework; +using Microsoft.Build.Utilities; +using Xamarin.Android.Build.Debugging.Tasks.Properties; +using Microsoft.Android.Build.Tasks; + +namespace Xamarin.Android.Tasks +{ + public class AndroidHelper + { + const string DefaultErrorCode = "XA0010"; + const RegisteredTaskObjectLifetime Lifetime = RegisteredTaskObjectLifetime.Build; + static readonly object NullDevice = new object (); + + static Tuple GetKey (string target) => + new Tuple ($"{nameof (AndroidHelper)}_AndroidDevice", target ?? ""); + + /// + /// Stores the AndroidDevice with a lifetime for the current build + /// + static void RegisterDevice (IBuildEngine4 engine, string target, AndroidDevice device) + { + var key = GetKey (target); + engine?.RegisterTaskObjectAssemblyLocal (key, device ?? NullDevice, Lifetime, allowEarlyCollection: false); + } + + /// + /// Gets a cached AndroidDevice cached from the current build + /// + static object GetRegisteredDevice (IBuildEngine4 engine, string target) + { + var key = GetKey (target); + return engine?.GetRegisteredTaskObjectAssemblyLocal (key, Lifetime); + } + + public static AndroidDevice ParseTarget (string target, TaskLoggingHelper log, bool logErrors = true, IBuildEngine4 engine4 = null) => + ParseTarget (target, m => log.LogDebugMessage (m), (c, m) => log.LogCodedError (c, m), logErrors, engine4); + + public static AndroidDevice ParseTarget (string target, Action logMessage, Action logError, bool logErrors = true, IBuildEngine4 engine4 = null) + { + try { + var device = GetRegisteredDevice (engine4, target); + if (device != null) { + logMessage ("Using cached value from RegisterTaskObject"); + if (device == NullDevice) { + NoDeviceFound (target, logError, logErrors, engine4); + } + return device as AndroidDevice; + } + var t = AdbServer.Default.GetDevices (); + if (string.IsNullOrEmpty (target)) { + var e = t.Result.FirstOrDefault (); + if (e != null) { + // Register for a blank target and -s + RegisterDevice (engine4, target, e); + RegisterDevice (engine4, $"-s {e.ID}", e); + return e; + } else { + NoDeviceFound (target, logError, logErrors, engine4); + return null; + } + } else if (target.StartsWith ("-e")) { + var e = t.Result.Where (x => x.IsEmulator).FirstOrDefault (); + if (e != null) { + RegisterDevice (engine4, target, e); + return e; + } else { + NoDeviceFound (target, logError, logErrors, engine4); + return null; + } + } else if (target.StartsWith ("-d")) { + var e = t.Result.Where (x => !x.IsEmulator).FirstOrDefault (); + if (e != null) { + RegisterDevice (engine4, target, e); + return e; + } else { + NoDeviceFound (target, logError, logErrors, engine4); + return null; + } + } else if (target.StartsWith ("-s")) { + string deviceId = target.Substring (2).Trim (); + var e = t.Result.Where (x => deviceId == x.ID).FirstOrDefault (); + if (e != null) { + RegisterDevice (engine4, target, e); + return e; + } else { + NoDeviceFound (target, logError, logErrors, engine4); + return null; + } + } + if (logErrors) + logError (DefaultErrorCode, string.Format (Resources.XA0010_AdbTarget, target)); + } catch (Exception ex) { + // Register that no device was found for the current build + RegisterDevice (engine4, target, null); + + if (logErrors) { + logError (DefaultErrorCode, string.Format (Resources.XA0010_Adb, ex)); + } else { + logMessage (string.Format (Resources.XA0010_Adb, ex)); + } + } + return null; + } + + static void NoDeviceFound (string target, Action logError, bool logErrors, IBuildEngine4 engine4) + { + // Register that no device was found for the current build + RegisterDevice (engine4, target, null); + + if (logErrors) { + if (string.IsNullOrEmpty (target)) { + logError (DefaultErrorCode, Resources.XA0010_NoDevice); + } else { + logError (DefaultErrorCode, Resources.XA0010_Selected); + } + } + } + } +} diff --git a/src/Xamarin.Android.Build.Debugging.Tasks/Utilities/DeviceCache.cs b/src/Xamarin.Android.Build.Debugging.Tasks/Utilities/DeviceCache.cs new file mode 100644 index 00000000000..066280b3281 --- /dev/null +++ b/src/Xamarin.Android.Build.Debugging.Tasks/Utilities/DeviceCache.cs @@ -0,0 +1,97 @@ +using System.Linq; +using System.Xml.Linq; +using Microsoft.Build.Utilities; + +namespace Xamarin.Android.Tasks +{ + /// + /// Helper class for managing the device property cache XML file. + /// + public static class DeviceCache + { + /// + /// Updates or adds a device entry in the cache document. + /// + /// The XDocument to update, or null to create a new one. + /// The device ID. + /// The device's ABI. + /// The tools ABI. + /// The SDK version. + /// The long output string from adb. + /// The updated or newly created XDocument. + public static XDocument Update (XDocument doc, string deviceId, string resultingAbi, string toolsAbi, int sdkVersion, string longOutput) + { + XElement devices; + if (doc == null) { + devices = new XElement ("Devices"); + doc = new XDocument ( + new XDeclaration ("1.0", "UTF-8", null), + devices); + } + else { + devices = doc.Element ("Devices"); + } + + var deviceElement = devices + .Elements ("Device") + .FirstOrDefault (a => a.Attribute ("id")?.Value == deviceId) + ?? + new XElement ("Device", new XAttribute ("id", deviceId)); + + if (deviceElement.Parent == null) { + devices.Add (deviceElement); + } + + deviceElement.SetElementValue ("ResultingAbi", resultingAbi); + deviceElement.SetElementValue ("ToolsAbi", toolsAbi); + deviceElement.SetElementValue ("SdkVersion", sdkVersion); + deviceElement.SetElementValue ("LongOutput", longOutput); + + return doc; + } + + /// + /// Tries to get cached device properties from the document. + /// + /// The XDocument to search. + /// The device ID to look up. + /// The expected long output to validate the cache entry. + /// The cached ABI if found and valid. + /// The cached tools ABI if found and valid. + /// The cached SDK version if found and valid. + /// Optional logger for debug messages. + /// True if a valid cache entry was found, false otherwise. + public static bool TryGet (XDocument doc, string deviceId, string longOutput, out string resultingAbi, out string toolsAbi, out int sdkVersion, TaskLoggingHelper log = null) + { + resultingAbi = null; + toolsAbi = null; + sdkVersion = 0; + + if (doc == null) + return false; + + var element = doc.Elements ("Devices").Elements ("Device") + .FirstOrDefault (a => a.Attribute ("id")?.Value == deviceId); + + if (element == null) + return false; + + string cachedLongOutput = element.Element ("LongOutput")?.Value ?? string.Empty; + if (string.Compare (cachedLongOutput, longOutput, System.StringComparison.OrdinalIgnoreCase) != 0) { + // The device LongOutput changed. So lets find the device again. + // This can happen if the emulator is different but has the same id or + // if the device changed usb ports. + log?.LogMessage ($"Device LongOutput has changed. Cached '{cachedLongOutput}' does not match current '{longOutput}'. Refreshing device cache."); + return false; + } + + resultingAbi = element.Element ("ResultingAbi")?.Value; + toolsAbi = element.Element ("ToolsAbi")?.Value ?? string.Empty; + + if (!int.TryParse (element.Element ("SdkVersion")?.Value, out sdkVersion)) + return false; + + return !string.IsNullOrEmpty (resultingAbi); + } + } +} diff --git a/src/Xamarin.Android.Build.Debugging.Tasks/Xamarin.Android.Build.Debugging.Tasks.csproj b/src/Xamarin.Android.Build.Debugging.Tasks/Xamarin.Android.Build.Debugging.Tasks.csproj new file mode 100644 index 00000000000..104bd324ea9 --- /dev/null +++ b/src/Xamarin.Android.Build.Debugging.Tasks/Xamarin.Android.Build.Debugging.Tasks.csproj @@ -0,0 +1,84 @@ + + + + + $(NoWarn);CA1305 + + + + 12 + netstandard2.0 + true + $(DefineConstants);MSBUILD + $(MicrosoftAndroidSdkOutDir) + false + true + false + product.snk + + + + + + + + + + + + + + + + + + + + + + + + + + PreserveNewest + + + PreserveNewest + + + + + + True + True + Resources.resx + + + + + + PublicResXFileCodeGenerator + Resources.Designer.cs + + + + + + + + + + + + + $(ProductVersion).$(PackVersionCommitCount) + + + + + + + + diff --git a/src/Xamarin.Android.Build.Debugging.Tasks/Xamarin.Android.Common.Debugging.props b/src/Xamarin.Android.Build.Debugging.Tasks/Xamarin.Android.Common.Debugging.props new file mode 100644 index 00000000000..d976a9c31d5 --- /dev/null +++ b/src/Xamarin.Android.Build.Debugging.Tasks/Xamarin.Android.Common.Debugging.props @@ -0,0 +1,62 @@ + + + + + False + arm64-v8a;armeabi-v7a;x86 + + arm64-v8a;armeabi-v7a;x86;x86_64 + 9999 + 9999 + $(ProjectDir)\gdb-symbols + True + $(XamarinAndroidVersion) + --user $(AndroidDeviceUserId) + + + <_AfterCompileDex> + $(_AfterCompileDex); + _CompileFastDevDex + + <_BeforeBuildAdditionalResourcesCache> + $(_BeforeBuildAdditionalResourcesCache); + _SetupDesignTimeBuildForIntellisense + + <_CompileJavaDependsOnTargets> + $(_CompileJavaDependsOnTargets); + + <_BeforeDefineBuildTargetAbis> + $(_BeforeDefineBuildTargetAbis); + + <_ResolveMonoAndroidSdksDependsOn> + $(_ResolveMonoAndroidSdksDependsOn); + _GetPrimaryCpuAbi + + <_AfterResolveMonoAndroidSdks> + $(_AfterResolveMonoAndroidSdks); + _ResolveMonoAndroidSdks + + <_BeforeCompileToDalvikWithDx> + $(_BeforeCompileToDalvikWithDx); + + <_BeforeAddStaticResources> + $(_BeforeAddStaticResources); + + + + diff --git a/src/Xamarin.Android.Build.Debugging.Tasks/Xamarin.Android.Common.Debugging.targets b/src/Xamarin.Android.Build.Debugging.Tasks/Xamarin.Android.Common.Debugging.targets new file mode 100644 index 00000000000..dc270a09cda --- /dev/null +++ b/src/Xamarin.Android.Build.Debugging.Tasks/Xamarin.Android.Common.Debugging.targets @@ -0,0 +1,434 @@ + + + + + + + + + + + + + + + + + + + + + + + + true + + + + + + + <_DevicePropertyCache>$(IntermediateOutputPath)devices.cache + <_UploadFlag>$(IntermediateOutputPath)upload.flag + <_ConfigurationCacheDirectory>$(BaseIntermediateOutputPath).cache\ + + + + <_OnResolveMonoAndroidSdks> + $(_OnResolveMonoAndroidSdks) + ;_ResolveXamarinAndroidTools + + + + + + + + + + + + + + + + + <_DexFileForFastDev Include="@(_DexFile)" /> + <_DexFileForFastDevInput Include="@(_DexFile)" /> + <_AndroidNativeLibraryForFastDev Include="@(AndroidNativeLibrary)" /> + + + + + + + + + + + + + + <_PrimaryCpuAbi Condition="$(_PrimaryCpuAbi) == ''">@(_BuildTargetAbis) + + + + $(_PrimaryRuntimeIdentifier) + + $(_PrimaryCpuAbi) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <_NdkBuildNativeLibs Include="obj\local\$(_AdbTargetAbi)\lib*.so"/> + + + + + + + 10000 + 10000 + False + True + <_AndroidRunForceStop Condition="'$(_AndroidRunForceStop)' == ''">True + <_AndroidAllowJavaDebugging Condition="'$(_AndroidAllowJavaDebugging)' == ''">True + + + + + + + + + + + + + + + + + + + + + + + + + + + <_ConfigurationCacheItems Include="$(Configuration)$(Platform)$(AdbTarget)" /> + + + + + + + + + + + <_UploadInputs Include="$(ApkFileSigned)" /> + <_UploadInputs Include="@(_ResolvedAssemblies)" /> + <_UploadInputs Include="@(_AndroidResolvedSatellitePaths)" /> + <_UploadInputs Include="@(_ResolvedUserAssemblies)" /> + <_UploadInputs Include="@(_DexFile)" /> + <_UploadInputs Include="@(AndroidNativeLibrary)" /> + <_UploadInputs Include="@(AndroidEnvironment)" Condition=" '$(_AndroidFastDeployEnvironmentFiles)' == 'true' " /> + <_UploadInputs Include="@(LibraryEnvironments)" Condition=" '$(_AndroidFastDeployEnvironmentFiles)' == 'true' " /> + + + <_AndroidTypeMaps Include="@(_AndroidTypeMapping)" /> + + + + + + <_FastDevFiles Include="@(_ResolvedAssemblies)" Condition=" '%(_ResolvedAssemblies.AndroidSkipAddToPackage)' != 'True' And '%(_ResolvedAssemblies.Abi)' == '$(_PrimaryCpuAbi)' "> + %(_ResolvedAssemblies.DestinationSubDirectory)%(Filename)%(Extension) + + <_FastDevFiles Include="@(_ResolvedUserAssemblies)" Condition=" '%(_ResolvedUserAssemblies.AndroidSkipAddToPackage)' != 'True' And '%(_ResolvedUserAssemblies.Abi)' == '$(_PrimaryCpuAbi)' "> + %(_ResolvedUserAssemblies.DestinationSubDirectory)%(Filename)%(Extension) + + <_FastDevFiles Include="@(_AndroidResolvedSatellitePaths)" Condition=" '%(_AndroidResolvedSatellitePaths.AndroidSkipAddToPackage)' != 'True' And '%(_AndroidResolvedSatellitePaths.Abi)' == '$(_PrimaryCpuAbi)' "> + %(_AndroidResolvedSatellitePaths.DestinationSubDirectory)%(Filename)%(Extension) + + <_FastDevFiles Include="@(_ResolvedSymbols)" Condition=" '%(_ResolvedSymbols.Abi)' == '$(_PrimaryCpuAbi)' "> + %(_ResolvedSymbols.DestinationSubDirectory)%(Filename)%(Extension) + + + + + + <_UploadDependsOnTargets> + _PrepareAssemblies; + _GetPrimaryCpuAbi; + _DetectIfAppWasUninstalled; + _CheckForConfigurationChange; + _ReadAndroidManifest; + _GetUploadInputs; + _DeployAppBundle; + _ComputeFastDevFiles; + + + + + + <_ApkToInstall Condition=" '$(AndroidPackageFormat)' != 'aab' ">$(ApkFileSigned) + <_ReInstall Condition=" '$(_ReInstall)' == '' ">False + <_AndroidIsTestOnlyPackage Condition=" '$(_AndroidIsTestOnlyPackage)' == '' ">False + <_FastDeploymentDiagnosticLogging Condition=" '$(_FastDeploymentDiagnosticLogging)' == '' ">False + + + + + + + <_EnvironmentFiles Include="@(AndroidEnvironment);@(LibraryEnvironments)" /> + + + + + + + + + + + + + + + + SignAndroidPackage; + _Upload + + + + + + + + + + + + + + + + + + + + + + + + $(CleanDependsOn); + _CleanDebuggingCaches; + _CleanMultiDexFiles; + + + + + + + + + + + + + + + + + diff --git a/src/Xamarin.Android.Build.Debugging.Tasks/product.snk b/src/Xamarin.Android.Build.Debugging.Tasks/product.snk new file mode 100644 index 00000000000..8c04e53be9d Binary files /dev/null and b/src/Xamarin.Android.Build.Debugging.Tasks/product.snk differ diff --git a/src/Xamarin.AndroidTools/AndroidBuildToolsVersion.cs b/src/Xamarin.AndroidTools/AndroidBuildToolsVersion.cs new file mode 100644 index 00000000000..a41498a4a21 --- /dev/null +++ b/src/Xamarin.AndroidTools/AndroidBuildToolsVersion.cs @@ -0,0 +1,80 @@ +// +// AndroidBuildToolsVersion.cs +// +// Author: +// Matt Ward +// +// Copyright (c) 2015 Xamarin Inc. (http://xamarin.com) +// + +using System; + +namespace Xamarin.AndroidTools +{ + class AndroidBuildToolsVersion : IComparable + { + public AndroidBuildToolsVersion (int major, int minor, int build, string specialVersion = "") + : this (new Version (major, minor, build), specialVersion) + { + } + + public AndroidBuildToolsVersion (Version version, string specialVersion = "") + { + Version = version; + SpecialVersion = specialVersion; + } + + public Version Version { get; private set; } + public string SpecialVersion { get; private set; } + + public static AndroidBuildToolsVersion Parse (string input) + { + AndroidBuildToolsVersion buildToolsVersion = null; + + if (TryParse (input, out buildToolsVersion)) + return buildToolsVersion; + + throw new FormatException (String.Format ("Version string '{0}' is not valid.", input)); + } + + public static bool TryParse (string input, out AndroidBuildToolsVersion result) + { + result = null; + + if (String.IsNullOrEmpty (input)) + return false; + + try { + result = ParseInternal (input); + return true; + } catch (Exception) { + } + + return false; + } + + public int CompareTo (AndroidBuildToolsVersion other) + { + int result = Version.CompareTo (other.Version); + if (result != 0) + return result; + + return String.Compare (SpecialVersion, other.SpecialVersion, StringComparison.OrdinalIgnoreCase); + } + + static AndroidBuildToolsVersion ParseInternal (string input) + { + string versionText = input.Trim (); + string specialVersionText = ""; + + int index = versionText.IndexOf (' '); + if (index > 0) { + specialVersionText = versionText.Substring (index + 1).Trim (); + versionText = versionText.Substring (0, index); + } + + Version version = MonoDroidSdk.ParseVersion (versionText); + return new AndroidBuildToolsVersion (version, specialVersionText); + } + } +} diff --git a/src/Xamarin.AndroidTools/AndroidSdk.cs b/src/Xamarin.AndroidTools/AndroidSdk.cs new file mode 100644 index 00000000000..0aa9de0842e --- /dev/null +++ b/src/Xamarin.AndroidTools/AndroidSdk.cs @@ -0,0 +1,649 @@ +// +// AndroidSdk.cs +// +// Authors: +// Jonathan Pobst +// Andreia Gaita +// Michael Hutchinson +// +// Copyright 2012 Xamarin Inc. All rights reserved. +// + +using System; +using System.IO; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using Mono.AndroidTools; +using Xamarin.AndroidTools.Utilities; + +using Xamarin.Android.Tools; + +using NewVersion = Xamarin.Android.Tools.AndroidVersion; + +namespace Xamarin.AndroidTools +{ + public class AndroidSdk + { + private static AndroidSdkInfo sdk; + + public const string AutoRefreshSwitch = "Xamarin.AndroidTools.AndroidSdk.AutoRefresh"; + + static AndroidSdk () + { + var pathExt = Environment.GetEnvironmentVariable ("PATHEXT"); + var pathExts = pathExt?.Split (new char [] { Path.PathSeparator }, StringSplitOptions.RemoveEmptyEntries); + ExeExtensions = new string [(pathExts?.Length ?? 0) + 1]; + if (pathExts != null) { + Array.Copy (pathExts, 0, ExeExtensions, 0, pathExts.Length); + } + ExeExtensions [ExeExtensions.Length - 1] = null; + + // Return early if AutoRefreshSwitch is false + if (AppContext.TryGetSwitch (AutoRefreshSwitch, out var enabled) && !enabled) { + return; + } + + // Run Refresh if AutoRefreshSwitch is not set or true + Refresh (); + } + + static readonly string[] ExeExtensions; + + public static void Refresh () + { + Refresh (null, null, null); + } + +#pragma warning disable CS0618 // Type or member is obsolete + + public static void Refresh (string androidSdkPath = null, string androidNdkPath = null, string javaSdkPath = null) + { + try { + sdk = new AndroidSdkInfo (Logger, androidSdkPath, androidNdkPath, javaSdkPath); + + if (AnalyticsService.IsRegistered) + SendTelemetryEventAsync (); + + AndroidPlatformToolsPath = Path.Combine(AndroidSdkPath, "platform-tools"); + AndroidToolsPath = Path.Combine (AndroidSdkPath, "tools"); + AndroidEmulatorPath = Path.Combine (AndroidSdkPath, "emulator"); + JavaBinPath = Path.Combine (JavaSdkPath, "bin"); + + AdbExe = FindExecutableInDirectory ("adb", AndroidPlatformToolsPath).FirstOrDefault (); + AndroidExe = FindExecutableInDirectory ("android", AndroidToolsPath).FirstOrDefault (); + EmulatorExe = FindExecutableInDirectory ("emulator", AndroidEmulatorPath).FirstOrDefault () ?? FindExecutableInDirectory ("emulator", AndroidToolsPath).FirstOrDefault (); + JarsignerExe = FindExecutableInDirectory ("jarsigner", JavaBinPath).FirstOrDefault (); + JavaExe = FindExecutableInDirectory ("java", JavaBinPath).FirstOrDefault (); + JavacExe = FindExecutableInDirectory ("javac", JavaBinPath).FirstOrDefault (); + KeyToolExe = FindExecutableInDirectory ("keytool", JavaBinPath).FirstOrDefault (); + MonitorExe = FindExecutableInDirectory ("monitor", AndroidToolsPath).FirstOrDefault (); + } + catch (Exception ex) { + sdk = null; + AndroidPlatformToolsPath = AndroidToolsPath = JavaBinPath = null; + + AdbExe = AndroidExe = EmulatorExe = JarsignerExe = JavacExe = KeyToolExe = MonitorExe = null; + if (ex is InvalidOperationException && ex.Message.Contains (" Android ")) + AndroidLogger.LogError (Properties.Resources.XA5300_Android_SDK); + else if (ex is InvalidOperationException && ex.Message.Contains (" Java ")) + AndroidLogger.LogError (Properties.Resources.XA5300_Java_SDK); + else + AndroidLogger.LogError (Properties.Resources.XA5300_AndroidSdk_Refresh_Exception, ex.ToString ()); + } + } + +#pragma warning restore CS0618 + + static System.Threading.Tasks.Task SendTelemetryEventAsync() + { + return System.Threading.Tasks.Task.Run(() => { + var telemetryProperties = new Dictionary(); + + telemetryProperties["XS.Core.SDK.Android.Version"] = MonoDroidSdk.VersionString; + + TrackApiLevels(telemetryProperties); + TrackJdkInfo(telemetryProperties); + + AnalyticsService.ReportSdkVersions(telemetryProperties); + }); + } + + static void Logger (TraceLevel level, string value) + { + switch (level) { + case TraceLevel.Error: + AndroidLogger.LogError (null, "{0}", value); + break; + case TraceLevel.Info: + AndroidLogger.LogInfo (null, "{0}", value); + break; + case TraceLevel.Warning: + AndroidLogger.LogWarning (null, "{0}", value); + break; + case TraceLevel.Verbose: + default: + AndroidLogger.LogDebug (null, "{0}", value); + break; + } + } + + public static bool IsInstalled { + get { + return !string.IsNullOrEmpty (AndroidSdkPath) && IsJdkInstalled; + } + } + + public static bool IsJdkInstalled { + get { + return !string.IsNullOrEmpty (JavaSdkPath); + } + } + + [Obsolete ("Use OS.IsWindows")] + public static bool IsWindows { + get { return OS.IsWindows; } + } + + [Obsolete ("Use OS.IsMac")] + public static bool IsMac { + get { return OS.IsMac; } + } + + public static string AndroidSdkPath { + get { return sdk?.AndroidSdkPath; } + } + + [Obsolete] + public static string[] AllAndroidSdkPaths { + get { + return sdk?.AllAndroidSdkPaths ?? new string [0]; + } + } + + public static string AndroidNdkPath { + get { return sdk?.AndroidNdkPath; } + } + + public static string AndroidNdkHostPlatform { + get { return sdk?.AndroidNdkHostPlatform; } + } + + [Obsolete] + public static string[] AllAndroidNdkPaths { + get { + return new string [0]; + } + } + + public static string JavaSdkPath { + get { return sdk?.JavaSdkPath; } + } + + public static string JavaBinPath { + get; + private set; + } + + [Obsolete ("Use GetCommandLineToolsPaths().")] + public static string AndroidToolsPath { + get; + private set; + } + + public static string AndroidEmulatorPath { + get; + private set; + } + + public static string AndroidPlatformToolsPath { + get; + private set; + } + + /// The value of the PATH environment variable to be used when running SDK executables. + public static string GetEnvironmentPathOverride () + { + if (string.IsNullOrEmpty (JavaBinPath)) + return null; + + var pathEnv = Environment.GetEnvironmentVariable ("PATH"); + if (pathEnv != null) + return JavaBinPath + Path.PathSeparator + pathEnv; + + return JavaBinPath; + } + + static string GetShortPathName (string path) + { + if (OS.IsWindows) + return KernelEx.GetShortPathName (path); + return path; + } + + static IEnumerable FindExecutableInDirectory (string executable, string dir) + { + foreach (var exe in Executables (executable)) { + var e = Path.Combine (dir, exe); + if (File.Exists (e)) + yield return e; + } + } + + static IEnumerable Executables (string executable) + { + foreach (var ext in ExeExtensions) { + yield return Path.ChangeExtension (executable, ext); + } + } + + /// + /// The path and executable for adb[.exe]. + /// + public static string AdbExe { + get; + private set; + } + + /// + /// The path and executable for android[.bat]. + /// + [Obsolete ("Part of obsolete `tools` package, do not use.")] + public static string AndroidExe { + get; + private set; + } + + /// + /// The path and executable for monitor.exe/monitor. + /// + [Obsolete ("Part of obsolete `tools` package, do not use.")] + public static string MonitorExe { + get; + private set; + } + + /// + /// The path and executable for emulator.exe/android. + /// + public static string EmulatorExe { + get; + private set; + } + + public static string ZipAlignExe { + get { return GetZipAlignPath (); } + } + + [Obsolete ("Use ApkSignerJar. This returns a path to apksigner.jar.")] + public static string ApkSignerExe => GetApkSignerPath (); + + public static string ApkSignerJar => GetApkSignerPath (); + + public static string JarsignerExe { + get; + private set; + } + + public static string KeyToolExe { + get; + private set; + } + + public static string JavaExe { + get; + private set; + } + + public static string JavacExe { + get; + private set; + } + + /// + /// Gets the sdk tools version from \tools\source.properties + /// Returns null if the file was not found. + /// + [Obsolete ("The `tools` package is obsolete. Use " + nameof (GetCommandLineToolsVersion) + "(string)")] + public static string SdkToolsVersion { + get { + return GetRevisionFromSdkPackageDirectory (Path.Combine (AndroidSdkPath, "tools")); + } + } + + /// + /// Gets the sdk platform tools version from \platform-tools\source.properties. + /// Returns null if the file was not found. + /// + public static string SdkPlatformToolsVersion { + get { + return GetRevisionFromSdkPackageDirectory (Path.Combine (AndroidSdkPath, "platform-tools")); + } + } + + /// + /// Gets the sdk build tools version from \build-tools\\source.properties. + /// Returns null if the file was not found. + /// + public static string SdkBuildToolsVersion { + get { + if (AndroidSdkPath == null) + return null; + + var buildToolsDir = Path.Combine (AndroidSdkPath, "build-tools"); + if (Directory.Exists (buildToolsDir)) { + var toolsDirs = Directory.GetDirectories (buildToolsDir); + + var versions = (from dir in toolsDirs + let v = GetRevisionFromSdkPackageDirectory (dir) + let version = ParseBuildToolsVersion (v) + orderby version + select v).ToList (); + + return versions.LastOrDefault (); + } + + return null; + } + } + + /// + /// Gets the sdk cmdline-tools version from \cmdline-tools\\source.properties. + /// + /// The full path to the cmdline-tools\version folder. + /// The version of the specified cmdline-tools path, or null if the folder or file was not found. + public static string GetCommandLineToolsVersion (string commandLineToolsPath) + { + return GetRevisionFromSdkPackageDirectory (commandLineToolsPath); + } + + static string GetRevisionFromSdkPackageDirectory (string sdkPackageDirectory) + { + if (!Directory.Exists (sdkPackageDirectory)) + return null; + + return SdkBuildProperties.LoadProperties (Path.Combine (sdkPackageDirectory, "source.properties")).GetPropertyValue ("Pkg.Revision="); + } + + static void TrackJdkInfo(Dictionary telemetryProperties) + { + var props = default(JdkProperties); + + try { + props = JdkProperties.Get(JavaSdkPath); + } catch (Exception ex) + { + AndroidLogger.LogError(Properties.Resources.XA5300_AndroidSdk_JdkInfo, ex.ToString ()); + } + + telemetryProperties["Xamarin.Core.SDK.Android.JDK.Vendor"] = props?.Vendor; + telemetryProperties["Xamarin.Core.SDK.Android.JDK.Version"] = props?.Version; + } + + static void TrackApiLevels (Dictionary telemetryProperties) + { + List versions; + if (IsInstalled && MonoDroidSdk.AndroidVersions != null) { + versions = GetInstalledPlatformVersions (MonoDroidSdk.AndroidVersions).ToList (); + } + else { + versions = new List (); + } + + telemetryProperties ["Xamarin.Core.SDK.Android.Api.Count"] = versions.Count.ToString (); + if (versions.Count == 0) { + telemetryProperties ["Xamarin.Core.SDK.Android.Api.Levels"] = string.Empty; + } + else { + var sb = new System.Text.StringBuilder (); + + for (int i = 0; i < versions.Count; i++) { + sb.Append (versions [i].ApiLevel.ToString ()); + if (versions.Count > 1 && i < versions.Count - 1) { + sb.Append ("|"); + } + } + + telemetryProperties ["Xamarin.Core.SDK.Android.Api.Levels"] = sb.ToString (); + } + } + + static AndroidBuildToolsVersion ParseBuildToolsVersion (string input) + { + AndroidBuildToolsVersion version = null; + if (AndroidBuildToolsVersion.TryParse (input, out version)) + return version; + + return new AndroidBuildToolsVersion (0, 0, 0, input ?? ""); + } + + static string ValidatePath (string path) + { + if (String.IsNullOrEmpty (path)) + throw new InvalidOperationException ("This property is not valid when the SDK is not installed"); + return path; + } + + public static void SetPreferredAndroidSdkPath (string path) + { + AndroidSdkInfo.SetPreferredAndroidSdkPath (path); + + // Update everything to use new path + Refresh (); + } + + public static void SetPreferredJavaSdkPath (string path) + { + AndroidSdkInfo.SetPreferredJavaSdkPath (path); + + // Update everything to use new path + Refresh (); + } + + public static void SetPreferredAndroidNdkPath (string path) + { + AndroidSdkInfo.SetPreferredAndroidNdkPath (path); + + // Update everything to use new path + Refresh (); + } + + /// + /// Checks that a value is the location of an Android SDK. + /// + public static bool ValidateAndroidSdkLocation (string loc) + { + return !string.IsNullOrEmpty (loc) && + loc.IndexOfAny (Path.GetInvalidPathChars ()) == -1 && + FindExecutableInDirectory ("adb", Path.Combine (loc, "platform-tools")).Any (); + } + + /// + /// Checks that a value is the location of a Java SDK. + /// + public static bool ValidateJavaSdkLocation (string loc) + { + return !string.IsNullOrEmpty (loc) && + loc.IndexOfAny (Path.GetInvalidPathChars ()) == -1 && + FindExecutableInDirectory ("jarsigner", Path.Combine (loc, "bin")).Any (); + } + + /// + /// Checks that a value is the location of an Android NDK. + /// + public static bool ValidateAndroidNdkLocation (string loc) + { + return !string.IsNullOrEmpty (loc) && + loc.IndexOfAny (Path.GetInvalidPathChars ()) == -1 && + FindExecutableInDirectory ("ndk-stack", loc).Any (); + } + + public static string GetLatestPlatformDirectory () + { + var platformsDir = Path.Combine (AndroidSdkPath, "platforms"); + if (Directory.Exists (platformsDir)) { + var dirs = + from p in Directory.EnumerateDirectories (platformsDir, "android-*") + let version = ToInt32 (Path.GetFileName (p).Substring ("android-".Length)) + orderby version descending + select new { + Path = p, + Version = version, + }; + foreach (var d in dirs) + return d.Path; + } + throw new InvalidOperationException ("Could not find latest API level in: " + AndroidSdkPath); + } + + static int? ToInt32 (string value) + { + int v; + if (int.TryParse (value, out v)) + return v; + return null; + } + + public static string GetPlatformDirectory (int apiLevel) + { + return sdk?.GetPlatformDirectory (apiLevel); + } + + public static string GetPlatformDirectory (string osVersion) + { + return sdk?.TryGetPlatformDirectoryFromApiLevel (osVersion, MonoDroidSdk.AndroidVersions); + } + + public static bool IsPlatformInstalled (string osVersion) + { + if (sdk == null || MonoDroidSdk.AndroidVersions == null) + return false; + var id = MonoDroidSdk.AndroidVersions.GetIdFromFrameworkVersion (osVersion); + return sdk.TryGetPlatformDirectoryFromApiLevel (id, MonoDroidSdk.AndroidVersions) != null; + } + + public static bool IsPlatformInstalled (int apiLevel) + { + return apiLevel != 0 && Directory.Exists (GetPlatformDirectory (apiLevel)); + } + + [Obsolete ("Use " + nameof (GetInstalledPlatformVersions) + "(AndroidVersions)")] + public static IEnumerable GetInstalledPlatformVersions () + { + var knownAndInstalledSdkLevels = AndroidVersion.KnownVersions.Where (v => IsPlatformInstalled (v.ApiLevel)); + + return knownAndInstalledSdkLevels.Where (version => { + var apiLevel = MonoDroidSdk.GetApiLevelForFrameworkVersion (version.OSVersion); + return MonoDroidSdk.IsSupportedFrameworkLevel (apiLevel); + }); + } + + public static IEnumerable GetInstalledPlatformVersions (AndroidVersions versions) + { + return sdk?.GetInstalledPlatformVersions (versions) ?? Enumerable.Empty(); + } + + static string GetZipAlignPath () + { + var zipAlign = OS.IsWindows ? "zipalign.exe" : "zipalign"; + foreach (var p in GetBuildToolsPaths ()) { + var app = Path.Combine (p, "bin", zipAlign); + if (File.Exists (app)) + return app; + app = Path.Combine (p, zipAlign); + if (File.Exists (app)) + return app; + } + +#pragma warning disable CS0618 // Type or member is obsolete + var old = Path.Combine (GetShortPathName (AndroidToolsPath), zipAlign); +#pragma warning restore CS0618 + if (File.Exists (old)) + return old; + + return null; + } + + static string GetApkSignerPath () + { + const string apkSigner = "apksigner.jar"; + + foreach (var p in GetBuildToolsPaths ()) + { + var app = Path.Combine (p, "lib", apkSigner); + if (File.Exists (app)) + return app; + app = Path.Combine (p, apkSigner); + if (File.Exists (app)) + return app; + } + +#pragma warning disable CS0618 // Type or member is obsolete + var old = Path.Combine (GetShortPathName (AndroidToolsPath), apkSigner); +#pragma warning restore CS0618 + + if (File.Exists (old)) + return old; + + return null; + } + + static readonly string apkanalyzer = OS.IsWindows ? "apkanalyzer.exe" : "apkanalyzer"; + + public static string GetApkAnalyzerPath () => GetApkAnalyzerPath (preferredCommandLineToolsVersion: null); + + public static string GetApkAnalyzerPath (string preferredCommandLineToolsVersion) + { + foreach (var p in GetCommandLineToolsPaths (preferredCommandLineToolsVersion)) { + var cmdLineToolsapkanalyzerPath = FindExecutableInDirectory ("apkanalyzer", Path.Combine (p, "bin")).FirstOrDefault (); + if (File.Exists (cmdLineToolsapkanalyzerPath)) { + return cmdLineToolsapkanalyzerPath; + } + } + + return GetFallbackApkAnalyzerPath (); + } + + static string GetFallbackApkAnalyzerPath () + { +#pragma warning disable CS0618 // Type or member is obsolete + var apkanalyzerPath = Path.Combine (AndroidSdk.AndroidToolsPath, "bin", apkanalyzer); +#pragma warning restore CS0618 + + if (File.Exists (apkanalyzerPath)) + return apkanalyzerPath; + + return null; + } + + static readonly string aapt = OS.IsWindows ? "aapt.exe" : "aapt"; + + public static string GetAaptPath (string preferredBuildToolsVersion = null) + { + foreach (var p in GetBuildToolsPaths (preferredBuildToolsVersion)) { + var app = Path.Combine (p, "bin", aapt); + if (File.Exists (app)) + return app; + app = Path.Combine (p, aapt); + if (File.Exists (app)) + return app; + } + return null; + } + + public static IEnumerable GetCommandLineToolsPaths (string preferredCommandLineToolsVersion = null) + { + return sdk?.GetCommandLineToolsPaths (preferredCommandLineToolsVersion) ?? Enumerable.Empty (); + } + + public static IEnumerable GetBuildToolsPaths (string preferredBuildToolsVersion) + { + return sdk?.GetBuildToolsPaths (preferredBuildToolsVersion) ?? Enumerable.Empty (); + } + + public static IEnumerable GetBuildToolsPaths () + { + return sdk?.GetBuildToolsPaths () ?? Enumerable.Empty (); + } + } +} diff --git a/src/Xamarin.AndroidTools/AndroidSigningOptions.cs b/src/Xamarin.AndroidTools/AndroidSigningOptions.cs new file mode 100644 index 00000000000..fff5d242968 --- /dev/null +++ b/src/Xamarin.AndroidTools/AndroidSigningOptions.cs @@ -0,0 +1,55 @@ +// +// AndroidSigningOptions.cs +// +// Author: +// Michael Hutchinson +// +// Copyright (c) 2010-2011 Novell, Inc. +// Copyright (c) 2011 Xamarin Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +using System; + +namespace Xamarin.AndroidTools +{ + public enum PackageSigningAlgorithm + { + Unsupported, + RSA, + DSA, + SHA256withRSA + } + + public class AndroidSigningOptions + { + public AndroidSigningOptions () + { + SigningAlgorithm = PackageSigningAlgorithm.RSA; + } + + public string KeyStore { get; set; } + public string KeyAlias { get; set; } + public string KeyPass { get; set; } + public string StorePass { get; set; } + public string TsaUrl { get; set; } + public int? MinSdkVersion { get; set; } + public PackageSigningAlgorithm SigningAlgorithm { get; set; } + } +} \ No newline at end of file diff --git a/src/Xamarin.AndroidTools/AndroidTargetArch.cs b/src/Xamarin.AndroidTools/AndroidTargetArch.cs new file mode 100644 index 00000000000..fc1a28916a9 --- /dev/null +++ b/src/Xamarin.AndroidTools/AndroidTargetArch.cs @@ -0,0 +1,44 @@ +// +// AndroidTargetArch.cs +// +// Author: +// Atsushi Eno +// +// Copyright (c) 2012 Xamarin Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +using System; +using System.IO; +using System.Linq; + +namespace Xamarin.AndroidTools +{ + [Flags] + public enum AndroidTargetArch + { + None = 0, + Arm = 1, + X86 = 2, + Mips = 4, + Arm64 = 8, + X86_64 = 16, + Other = 0x10000 // hope it's not too optimistic + } +} + diff --git a/src/Xamarin.AndroidTools/AndroidVersion.cs b/src/Xamarin.AndroidTools/AndroidVersion.cs new file mode 100644 index 00000000000..65ea00de8d8 --- /dev/null +++ b/src/Xamarin.AndroidTools/AndroidVersion.cs @@ -0,0 +1,203 @@ +// +// AndroidVersion.cs +// +// Author: +// Michael Hutchinson +// +// Copyright (c) 2012 Xamarin Inc. (http://xamarin.com) + +using System; +using System.Linq; +using System.Collections.Generic; + +namespace Xamarin.AndroidTools +{ + [Obsolete ("Use Xamarin.Android.Tools.AndroidVersion, in Xamarin.Android.Tools.AndroidSdk.dll")] + public class AndroidVersion : IEquatable + { + public static readonly int MaxApiLevel = 26; + + public AndroidVersion (int apilevel, string osVersion) + { + this.ApiLevel = apilevel; + this.OSVersion = osVersion; + } + + internal AndroidVersion (int apilevel, string osVersion, string codeName, Version version) + { + this.ApiLevel = apilevel; + // TODO: remove osVersion from parameter list and generate from version + this.OSVersion = osVersion; + this.CodeName = codeName; + this.Version = version; + } + + public int ApiLevel { get; private set; } + public string OSVersion { get; private set; } + public string CodeName { get; private set; } + public Version Version { get; private set; } + + public static int OSVersionToApiLevel (string osVersion) + { + int ret = TryOSVersionToApiLevel (osVersion); + if (ret == 0) + throw new ArgumentOutOfRangeException ("OS version not recognized: " + osVersion); + return ret; + } + + public static int TryOSVersionToApiLevel (string frameworkVersion) + { + // Use MonoDroidSdk.GetApiLevelForFrameworkVersion because that will translate XA versions >= 5.xx to the correct api level + var apiLevelText = MonoDroidSdk.GetApiLevelForFrameworkVersion (frameworkVersion); + int apiLevel; + int.TryParse (apiLevelText, out apiLevel); + return apiLevel; + } + + public static string ApiLevelToOSVersion (int apiLevel) + { + string ret = TryApiLevelToOSVersion (apiLevel); + if (ret == null) + throw new ArgumentOutOfRangeException ("API level not recognized: " + apiLevel); + return ret; + } + + public static string TryApiLevelToOSVersion (int apiLevel) + { + var osVersion = MonoDroidSdk.GetFrameworkVersionForApiLevel (apiLevel.ToString ()); + if (!string.IsNullOrEmpty (osVersion)) + return osVersion.TrimStart ('v'); + return null; + } + + public static string TryOSVersionToCodeName (string frameworkVersion) + { + // First try to get the code name from InstalledBindingVersions + if (MonoDroidSdk.AndroidVersions != null) { + var id = MonoDroidSdk.AndroidVersions.GetIdFromFrameworkVersion(frameworkVersion); + var bindingVersion = MonoDroidSdk.AndroidVersions.InstalledBindingVersions.FirstOrDefault(v => v.Id == id); + if (bindingVersion != null) return bindingVersion.CodeName; + } + + // If for some reason there's an error there then fall back to KnownVersions + + // match on API level, the framework version might not match what we have here (>= XA 5.x uses a different version scheme) + var apiLevel = TryOSVersionToApiLevel (frameworkVersion); + + foreach (var version in KnownVersions) + if (version.ApiLevel == apiLevel) + return version.CodeName; + return null; + } + + public static string TryFrameworkVersionToOSVersion (string frameworkVersion) + { + // match on API level, the framework version might not match what we have here (>= XA 5.x uses a different version scheme) + var apiLevel = TryOSVersionToApiLevel (frameworkVersion); + + foreach (AndroidVersion version in KnownVersions) + if (version.ApiLevel == apiLevel) + return version.OSVersion; + return null; + } + + [Obsolete("No longer used")] + public static bool IsKnownVersion (string version) + { + return TryOSVersionToApiLevel (version) > 0; + } + + public bool Equals (AndroidVersion other) + { + if (other == null) + return false; + + return ApiLevel == other.ApiLevel && + OSVersion == other.OSVersion && + CodeName == other.CodeName; + } + + public override bool Equals (object obj) + { + if (obj is null) + return false; + + if (ReferenceEquals (this, obj)) + return true; + + return Equals (obj as AndroidVersion); + } + + public static bool operator == (AndroidVersion left, AndroidVersion right) + { + if ((object) left == null || (object) right == null) + return ReferenceEquals (left, right); + + return left.Equals (right); + } + + public static bool operator != (AndroidVersion left, AndroidVersion right) + { + return !(left == right); + } + + public override int GetHashCode () + { + unchecked + { + int hash = 17; + hash = hash * 23 + ApiLevel.GetHashCode (); + hash = hash * 23 + OSVersion.GetHashCode (); + hash = hash * 23 + CodeName.GetHashCode (); + return hash; + } + } + + public static AndroidVersion[] KnownVersions = new[] { + new AndroidVersion (4, "1.6", "Donut", new Version (1, 6)), + new AndroidVersion (5, "2.0", "Eclair", new Version (2, 0)), + new AndroidVersion (6, "2.0.1", "Eclair", new Version (2, 0, 1)), + new AndroidVersion (7, "2.1", "Eclair", new Version (2, 1)), + new AndroidVersion (8, "2.2", "Froyo", new Version (2, 2)), + new AndroidVersion (10, "2.3", "Gingerbread", new Version (2, 3)), + new AndroidVersion (11, "3.0", "Honeycomb", new Version (3, 0)), + new AndroidVersion (12, "3.1", "Honeycomb", new Version (3, 1)), + new AndroidVersion (13, "3.2", "Honeycomb", new Version (3, 2)), + new AndroidVersion (14, "4.0", "Ice Cream Sandwich", new Version (4, 0)), + new AndroidVersion (15, "4.0.3", "Ice Cream Sandwich", new Version (4, 0, 3)), + new AndroidVersion (16, "4.1", "Jelly Bean", new Version (4, 1)), + new AndroidVersion (17, "4.2", "Jelly Bean", new Version (4, 2)), + new AndroidVersion (18, "4.3", "Jelly Bean", new Version (4, 3)), + new AndroidVersion (19, "4.4", "Kit Kat", new Version (4, 4)), + new AndroidVersion (20, "4.4.87", "Kit Kat + Wear support", new Version (4, 4, 87)), + new AndroidVersion (21, "5.0", "Lollipop", new Version (5, 0)), + new AndroidVersion (22, "5.1", "Lollipop", new Version (5, 1)), + new AndroidVersion (23, "6.0", "Marshmallow", new Version (6, 0)), + new AndroidVersion (24, "7.0", "Nougat", new Version (7, 0)), + new AndroidVersion (25, "7.1", "Nougat", new Version (7, 1)), + new AndroidVersion (26, "8.0", "Oreo", new Version (8, 0)), + new AndroidVersion (27, "8.1", "Oreo", new Version (8, 1)), + }; + } + + public static class AndroidVersionExtensions + { +#pragma warning disable CS0618 // Type or member is obsolete + + public static AndroidVersion ToLegacyVersion (this Xamarin.Android.Tools.AndroidVersion androidVersion) + { + if (!Version.TryParse (androidVersion.OSVersion, out Version version)) + return null; + + return new AndroidVersion ( + androidVersion.ApiLevel, + androidVersion.OSVersion, + androidVersion.CodeName, + version + ); + } + +#pragma warning restore CS0618 + + } +} diff --git a/src/Xamarin.AndroidTools/AssemblyInfo.cs b/src/Xamarin.AndroidTools/AssemblyInfo.cs new file mode 100644 index 00000000000..8f4867d5ff2 --- /dev/null +++ b/src/Xamarin.AndroidTools/AssemblyInfo.cs @@ -0,0 +1,32 @@ +// +// AssemblyInfo.cs +// +// Author: +// Michael Hutchinson +// +// Copyright (c) 2012 Xamarin Inc. (http://xamarin.com) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +using System.Reflection; +using System.Runtime.CompilerServices; + +[assembly:InternalsVisibleTo("Xamarin.AndroidTools.Tests, PublicKey=" + + "00240000048000009400000006020000002400005253413100040000010001008B63AA15D5222FA8E77F9DBFBE47AD82E74130B737743BC5" + + "2A7074AD85D2EB34F28C83EFD0C1812C6957BAED24836300DF569EA0E6A71EEDA71C5E88A657AB8F243F268DA584B2D4EF4FCD3245B6A370" + + "001A77DE21DE670E4678E29942F5628B153E88CD19504664FC663F9729DCA1CA2977875710EAF2CBD382DD6FD52BE9D3")] diff --git a/src/Xamarin.AndroidTools/Debugging/AmIntentCommandParser.cs b/src/Xamarin.AndroidTools/Debugging/AmIntentCommandParser.cs new file mode 100644 index 00000000000..104ed74674e --- /dev/null +++ b/src/Xamarin.AndroidTools/Debugging/AmIntentCommandParser.cs @@ -0,0 +1,303 @@ +// +// AmIntentCommandParser.cs +// +// Author: +// Greg Munn +// +// Copyright (c) 2016 Xamarin Inc +// + +using System; +using System.Collections.Generic; +using System.Linq; +using Mono.AndroidTools; +using Mono.AndroidTools.Util; + +namespace Xamarin.AndroidTools.Debugging +{ + public static class AmIntentCommandParser + { + public static AmIntentCommand Parse(string command, string packageName) + { + string[] args; + if (!ProcessArgumentBuilder.TryParse(command, out args)) + throw new ArgumentException(string.Format("Command '{0}' could not be parsed", command)); + + if (args.Length < 2) + throw new ArgumentException(string.Format("Command '{0}' does not have the correct number of arguments", command)); + + if (args[0] != "am") + throw new ArgumentException(string.Format("Command '{0}' does not start with `am`", command)); + + AmOptions options; + switch (args[1]) { + case "start": + options = new AmStartOptions(packageName); + + break; + case "startservice": + options = new AmStartServiceOptions(packageName); + + break; + case "broadcast": + options = new AmBroadcastOptions(packageName); + + break; + default: + throw new NotSupportedException(string.Format("Unsupported `am {0}` command", args[1])); + } + + var optSet = options.GetOptionSet(); + var optionArgs = args.Skip(2).ToArray(); + + var remainingArgs = optSet.Parse(optionArgs); + options.HandleRemainingArgs(remainingArgs); + + return options.Command; + } + + public static ExtraDataUri ParseDataUri(string v) + { + return new ExtraDataUri(v); + } + + public static ExtraComponentName ParseComponentName(string v) + { + return new ExtraComponentName(v); + } + + public static long ParseLong(string v) + { + return long.Parse(v); + } + + public static ExtraFloat ParseFloat(string v) + { + return new ExtraFloat(v); + } + + public static int[] ParseIntArray(string v) + { + if (string.IsNullOrEmpty(v)) + return new int[0]; + + var values = v.Split(new[] { "," }, StringSplitOptions.RemoveEmptyEntries); + var result = new int[values.Length]; + for (int i = 0; i < values.Length; i++) + { + result[i] = int.Parse(values[i]); + } + + return result; + } + + public static long[] ParseLongArray(string v) + { + if (string.IsNullOrEmpty(v)) + return new long[0]; + + var values = v.Split(new[] { "," }, StringSplitOptions.RemoveEmptyEntries); + var result = new long[values.Length]; + for (int i = 0; i < values.Length; i++) + { + result[i] = ParseLong(values[i]); + } + + return result; + } + + public static ExtraFloat[] ParseFloatArray(string v) + { + if (string.IsNullOrEmpty(v)) + return new ExtraFloat[0]; + + var values = v.Split(new[] { "," }, StringSplitOptions.RemoveEmptyEntries); + var result = new ExtraFloat[values.Length]; + for (int i = 0; i < values.Length; i++) + { + result[i] = ParseFloat(values[i]); + } + + return result; + } + + public static bool ParseExtraBool(string v) + { + if (string.Equals(v, "true", StringComparison.OrdinalIgnoreCase)) + { + return true; + } + + if (string.Equals(v, "false", StringComparison.OrdinalIgnoreCase)) + { + return false; + } + + if (string.Equals(v, "1", StringComparison.OrdinalIgnoreCase)) + { + return true; + } + + if (string.Equals(v, "0", StringComparison.OrdinalIgnoreCase)) + { + return false; + } + + throw new FormatException("coud not parse boolean value"); + } + + abstract class AmOptions + { + public AmIntentCommand Command { get; protected set; } + public abstract Mono.Options.OptionSet GetOptionSet(); + public virtual void HandleRemainingArgs(List args) + { + if (args.Count > 0) { + this.Command.Intent = args[0]; + } + } + + protected void AddIntentOptions(Mono.Options.OptionSet options) + { + // intent options + options.Add("n=", "Component", s => this.Command.Component = s); + options.Add("a=", "Action", s => this.Command.Action = s); + options.Add("d=", "Data Uri", s => this.Command.DataUri = s); + options.Add("c=", "Category", s => this.Command.Categories.Add(s)); + options.Add("t=", "Mime Type", s => this.Command.MimeType = s); + // strictly speaking we should also support -p here, but we don't because of conflicts with the use of + // packageName for fast dev property settings + + options.Add("selector", "Selector", s => this.Command.Selector = true); + + } + + protected void AddFlagsOptions(Mono.Options.OptionSet options) + { + options.Add("f=", (k) => { throw new NotSupportedException("-f is not supported, use individual flag arguments"); }); + + options.Add("grant-read-uri-permission", (k) => this.Command.Flags = this.Command.Flags | IntentFlag.GrantReadUriPermission); + options.Add("grant-write-uri-permission", (k) => this.Command.Flags = this.Command.Flags | IntentFlag.GrantWriteUriPermission); + options.Add("debug-log-resolution", (k) => this.Command.Flags = this.Command.Flags | IntentFlag.DebugLogResolution); + options.Add("exclude-stopped-packages", (k) => this.Command.Flags = this.Command.Flags | IntentFlag.ExcludeStoppedPackages); + options.Add("include-stopped-packages", (k) => this.Command.Flags = this.Command.Flags | IntentFlag.IncludeStoppedPackages); + options.Add("activity-brought-to-front", (k) => this.Command.Flags = this.Command.Flags | IntentFlag.ActivityBroughtToFront); + options.Add("activity-clear-top", (k) => this.Command.Flags = this.Command.Flags | IntentFlag.ActivityClearTop); + options.Add("activity-clear-when-task-reset", (k) => this.Command.Flags = this.Command.Flags | IntentFlag.ActivityClearWhenTaskReset); + options.Add("activity-exclude-from-recents", (k) => this.Command.Flags = this.Command.Flags | IntentFlag.ActivityExcludeFromRecents); + options.Add("activity-launched-from-history", (k) => this.Command.Flags = this.Command.Flags | IntentFlag.ActivityLaunchedFromHistory); + options.Add("activity-multiple-task", (k) => this.Command.Flags = this.Command.Flags | IntentFlag.ActvityMultipleTask); + options.Add("activity-no-animation", (k) => this.Command.Flags = this.Command.Flags | IntentFlag.ActivityNoAnimation); + options.Add("activity-no-history", (k) => this.Command.Flags = this.Command.Flags | IntentFlag.ActivityNoHistory); + options.Add("activity-no-user-action", (k) => this.Command.Flags = this.Command.Flags | IntentFlag.ActivityNoUserAction); + options.Add("activity-previous-is-top", (k) => this.Command.Flags = this.Command.Flags | IntentFlag.ActivityPreviousIsTop); + options.Add("activity-reorder-to-front", (k) => this.Command.Flags = this.Command.Flags | IntentFlag.ActivityReorderToFront); + options.Add("activity-reset-task-if-needed", (k) => this.Command.Flags = this.Command.Flags | IntentFlag.ActivityResetTaskIfNeeded); + options.Add("activity-single-top", (k) => this.Command.Flags = this.Command.Flags | IntentFlag.ActivitySingleTop); + options.Add("activity-clear-task", (k) => this.Command.Flags = this.Command.Flags | IntentFlag.ActivityClearTask); + options.Add("activity-task-on-home", (k) => this.Command.Flags = this.Command.Flags | IntentFlag.ActivityTaskOnHome); + options.Add("receiver-registered-only", (k) => this.Command.Flags = this.Command.Flags | IntentFlag.ReceiverRegisteredOnly); + options.Add("receiver-replace-pending", (k) => this.Command.Flags = this.Command.Flags | IntentFlag.ReceiverReplacePending); + } + + protected void AddExtraOptions(Mono.Options.OptionSet options) + { + options.Add("esn=", "Extra Null", (k) => this.Command.Extras.Add(k, null)); + options.Add("e|es=", "Extra String", (k, v) => this.Command.Extras.Add(k, v)); + options.Add("ez=", "Extra Bool", (k, v) => this.Command.Extras.Add(k, ParseExtraBool(v))); + options.Add("ei=", "Extra Int", (k, v) => this.Command.Extras.Add(k, int.Parse(v))); + options.Add("el=", "Extra Long", (k, v) => this.Command.Extras.Add(k, ParseLong(v))); + options.Add("ef=", "Extra Float", (k, v) => this.Command.Extras.Add(k, ParseFloat(v))); + options.Add("eu=", "Extra Uri", (k, v) => this.Command.Extras.Add(k, ParseDataUri(v))); + options.Add("ecn=", "Extra Component Name", (k, v) => this.Command.Extras.Add(k, ParseComponentName(v))); + + // extras that take an array + options.Add("eia=", "Extra Int Array", (k, v) => this.Command.Extras.Add(k, ParseIntArray(v))); + options.Add("ela=", "Extra Long Array", (k, v) => this.Command.Extras.Add(k, ParseLongArray(v))); + options.Add("efa=", "Extra Float Array", (k, v) => this.Command.Extras.Add(k, ParseFloatArray(v))); + } + } + + class AmStartOptions : AmOptions + { + readonly AmStartCommand command; + + public AmStartOptions(string packageName) + { + this.Command = this.command = new AmStartCommand(); + this.command.PackageName = packageName; + this.command.Extras = new Dictionary(); + this.command.Categories = new List(); + } + + public override Mono.Options.OptionSet GetOptionSet() + { + var options = new Mono.Options.OptionSet { + // start options + { "D", "Enable Debugging", s => this.command.EnableDebugging = true }, + { "W", "Wait For Launch", s => this.command.Wait = true }, + { "start-profiler=", "Profiler Output", s => this.command.ProfilerOutputPath = s }, + { "R=", "Repeat Launch", s => this.command.Repeat = int.Parse(s) }, + { "S", "Force Stop", s => this.command.ForceStop = true }, + { "opengl-trace", "Open GL Trace", s => this.command.EnableOpenGLTracing = true }, + { "user=", "User Id", s => this.command.User = s }, + }; + + AddIntentOptions(options); + AddExtraOptions(options); + AddFlagsOptions(options); + return options; + } + } + + class AmStartServiceOptions : AmOptions + { + readonly AmStartServiceCommand command; + + public AmStartServiceOptions(string packageName) + { + this.Command = this.command = new AmStartServiceCommand(); + this.command.PackageName = packageName; + this.command.Extras = new Dictionary(); + this.command.Categories = new List(); + } + + public override Mono.Options.OptionSet GetOptionSet() + { + var options = new Mono.Options.OptionSet { + { "user=", "User Id", s => this.command.User = s }, + }; + + AddIntentOptions(options); + AddExtraOptions(options); + AddFlagsOptions(options); + return options; + } + } + + class AmBroadcastOptions : AmOptions + { + readonly AmBroadcastCommand command; + + public AmBroadcastOptions(string packageName) + { + this.Command = this.command = new AmBroadcastCommand(); + this.command.PackageName = packageName; + this.command.Extras = new Dictionary(); + this.command.Categories = new List(); + } + + public override Mono.Options.OptionSet GetOptionSet() + { + var options = new Mono.Options.OptionSet { + { "user=", "User Id", s => this.command.User = s }, + }; + + AddIntentOptions(options); + AddExtraOptions(options); + AddFlagsOptions(options); + return options; + } + } + } +} \ No newline at end of file diff --git a/src/Xamarin.AndroidTools/Debugging/DebuggerOptions.cs b/src/Xamarin.AndroidTools/Debugging/DebuggerOptions.cs new file mode 100644 index 00000000000..7b1a5fa4ca5 --- /dev/null +++ b/src/Xamarin.AndroidTools/Debugging/DebuggerOptions.cs @@ -0,0 +1,47 @@ +// +// DebuggerOptions.cs +// +// Author: +// Greg Munn +// +// Copyright (c) 2016 Xamarin Inc +// + +using System; +using System.Net; + +namespace Xamarin.AndroidTools.Debugging +{ + /// + /// Debugger options that control how the android process connects to the debugger + /// + public sealed class DebuggerOptions + { + public DebuggerOptions() + { + } + + public DebuggerOptions(IPAddress address, int sdbPort, int stdoutPort, bool server) + { + this.Address = address; + this.SdbPort = sdbPort; + this.StdoutPort = stdoutPort; + this.Server = server; + } + + public DebuggerOptions(IPAddress address, int sdbPort, int stdoutPort, bool server, string jdwpHostName, int jdwpPort) : + this (address, sdbPort, stdoutPort, server) + { + this.JdwpHostName = jdwpHostName; + this.JdwpPort = jdwpPort; + } + + public IPAddress Address { get; set; } + public int SdbPort { get; set; } + public int StdoutPort { get; set; } + public bool Server { get; set; } + public TimeSpan Timeout { get; set; } = TimeSpan.FromSeconds (30); + public string JdwpHostName { get; set; } = "127.0.0.1"; + public int JdwpPort { get; set; } = 8100; + } +} diff --git a/src/Xamarin.AndroidTools/Debugging/DebuggingExtensions.cs b/src/Xamarin.AndroidTools/Debugging/DebuggingExtensions.cs new file mode 100644 index 00000000000..fea84e4156e --- /dev/null +++ b/src/Xamarin.AndroidTools/Debugging/DebuggingExtensions.cs @@ -0,0 +1,203 @@ +// +// DebuggingExtensions.cs +// +// Author: +// Greg Munn +// +// Copyright (c) 2016 Xamarin Inc +// +using System; +using System.Threading; +using System.Threading.Tasks; +using Mono.AndroidTools; +using System.Diagnostics; +using Xamarin.AndroidTools.Debugging.Java; + +namespace Xamarin.AndroidTools.Debugging +{ + /// + /// Device extension methods for initiating debugging + /// + public static class DebuggingExtensions + { + const int WAIT_BEFORE_RETRY_GET_PID = 250; + const int WAIT_FOR_DEBUGGER_TO_ATTACH_MS = 1400; + + /// + /// Starts the process debugging using the given execution configuration + /// + public async static Task StartWithDebuggingAsync(this IAndroidDevice device, ExecutionConfiguration configuration, CancellationToken token) + { + // TODO: refactor IAndroidDevice some more to remove casts + var androidDevice = (AndroidDevice)device; + + await androidDevice.ExecuteAndLogCommandAsync(configuration.BeforeRunCommand, configuration.LogWiter, token).ConfigureAwait(false); + + await device.SetDebugPropertiesAsync(configuration.PackageName, configuration.Debugger, token).ConfigureAwait(false); + + // add --include-stopped-packages if it's not present on broadcast commands because otherwise we won't start anything anyway + if (configuration.RunCommand is AmBroadcastCommand) { + ((AmBroadcastCommand)configuration.RunCommand).Flags |= IntentFlag.IncludeStoppedPackages; + } + + bool javaDebugging = false; + if (configuration.AllowJavaDebugging && configuration.RunCommand is AmStartCommand) { + var cmd = ((AmStartCommand)configuration.RunCommand); + if (androidDevice.IsWSA() || androidDevice.IsEmulator) // force -D for WSA and Emulators + cmd.EnableDebugging = true; + javaDebugging = cmd.EnableDebugging; + } + + // if the startCommand is null it is because there is no activity to start, in which case + // we will return a completed task and the user will have to manually start up the process + if (configuration.RunCommand == null) + return; + + if (configuration.LogWiter != null) { + configuration.LogWiter(configuration.RunCommand.ToString()); + } + + await androidDevice.ExecuteIntentCommandAsync(configuration.RunCommand, configuration.LogWiter, token).ConfigureAwait(false); + if (javaDebugging) { + try { + await androidDevice.ConnectJdwpAsync (configuration, token).ConfigureAwait(false); + } catch (Exception ex) { + if (configuration.LogWiter != null) + configuration.LogWiter ($"warning: Could not connect Jdwp. {ex}"); + } + } + } + + /// + /// Starts the process without debugging using the given execution configuration + /// + public async static Task StartWithoutDebuggingAsync(this IAndroidDevice device, ExecutionConfiguration configuration, CancellationToken token) + { + // TODO: refactor IAndroidDevice some more to remove casts + var androidDevice = (AndroidDevice)device; + + await androidDevice.ExecuteAndLogCommandAsync(configuration.BeforeRunCommand, configuration.LogWiter, token).ConfigureAwait(false); + + // reset the debug timeout in case the user tries to run after debugging within the 30 second time window + await androidDevice.SetProperty("debug.mono.extra", string.Empty, token).ConfigureAwait(false); + + // clear the fast dev property file + await androidDevice.SetFastDevPropertyFile(configuration.PackageName, "debug.mono.extra", string.Empty, token).ConfigureAwait(false); + token.ThrowIfCancellationRequested(); + + // add --include-stopped-packages if it's not present on broadcast commands because otherwise we won't start anything anyway + if (configuration.RunCommand is AmBroadcastCommand) + { + ((AmBroadcastCommand)configuration.RunCommand).Flags |= IntentFlag.IncludeStoppedPackages; + } + + if (configuration.LogWiter != null) + { + configuration.LogWiter(configuration.RunCommand.ToString()); + } + + await androidDevice.ExecuteIntentCommandAsync(configuration.RunCommand, configuration.LogWiter, token).ConfigureAwait(false); + } + + /// + /// Returns a Task which sets up the debug property and sets the fast dev property for the given package + /// + public async static Task SetDebugPropertiesAsync(this IAndroidDevice device, string packageName, DebuggerOptions options, CancellationToken token) + { + if (string.IsNullOrEmpty(packageName)) + throw new ArgumentException(nameof(packageName)); + + // TODO: refactor IAndroidDevice some more to remove casts + var androidDevice = (AndroidDevice)device; + + const int loglevel = 0; + + // Get the time the device thinks it is, and add options.Timeout seconds (defaults to 30) + long expire_date = (await androidDevice.GetDate(token).ConfigureAwait(false)) + (int)options.Timeout.TotalSeconds; + + string endpoint = options.StdoutPort > -1 + ? string.Format("{0}:{1}:{2}", options.Address, options.SdbPort, options.StdoutPort) + : string.Format("{0}:{1}", options.Address, options.SdbPort); + + // Set property to tell the device to launch in debug mode + string debugArg = string.Format("debug={0},timeout={1},loglevel={2},server={3}", endpoint, expire_date, loglevel, options.Server ? "y" : "n"); + + await androidDevice.SetProperty("debug.mono.extra", debugArg, token).ConfigureAwait(false); + + await androidDevice.SetFastDevPropertyFile(packageName, "debug.mono.extra", debugArg, token).ConfigureAwait(false); + } + + /// + /// Executes the command and writes the result to the log writer + /// + public static Task ExecuteAndLogCommandAsync(this IAndroidDevice device, string command, Action logWriter, CancellationToken token) + { + // TODO: refactor IAndroidDevice some more to remove casts + var androidDevice = (AndroidDevice)device; + return ExecuteAndLogCommandAsync(androidDevice, command, logWriter, token); + } + + /// + /// Executes the command and writes the result to the log writer + /// + public static async Task ExecuteAndLogCommandAsync(this AndroidDevice device, string command, Action logWriter, CancellationToken token) + { + if (!string.IsNullOrEmpty(command)) + { + if (logWriter != null) + { + logWriter(command); + } + + var cmdResult = await device.RunShellCommand(command, token).ConfigureAwait(false); + if (logWriter != null) + { + logWriter(cmdResult); + } + } + } + + public static bool IsWSA(this AndroidDevice androidDevice) + { + if (androidDevice == null) + return false; + + var vendor = androidDevice.Properties?.Get("ro.product.vendor.brand"); + var model = androidDevice.Properties?.Get("ro.product.vendor.model"); + + return !string.IsNullOrEmpty (vendor) && + !string.IsNullOrEmpty (model) && + vendor.Equals ("Windows", StringComparison.InvariantCultureIgnoreCase) && + model.Equals ("Subsystem for Android(TM)", StringComparison.InvariantCultureIgnoreCase); + } + + public static async Task ConnectJdwpAsync(this AndroidDevice androidDevice, ExecutionConfiguration config, CancellationToken token) + { + if (config.RunCommand != null && config.RunCommand is AmStartCommand amStartCommand && amStartCommand.EnableDebugging) + { + var packageName = (config.RunCommand as AmStartCommand).PackageName; + var pid = await androidDevice.GetProcessIDAsync(packageName, 5, WAIT_BEFORE_RETRY_GET_PID, token); + + if (pid <= 0) + { + throw new Exception("Process Not Found."); + } + + var jdwpClient = new JdwpClient(config.Debugger.JdwpHostName, config.Debugger.JdwpPort); + + await AdbServer.Default.ForwardPort(androidDevice, "tcp", jdwpClient.Port, "jdwp", pid, token); + try { + await jdwpClient.ConnectAsync (token); + + // Keep the Connection for 1300 milliseconds, otherwise the Android OS ignores the connection! + // https://github.com/aosp-mirror/platform_frameworks_base/blob/6b28a227400749f4f8ad1f56799370e7c2cab149/core/java/android/os/Debug.java#L101C50-L101C54 + await Task.Delay (WAIT_FOR_DEBUGGER_TO_ATTACH_MS); + + await jdwpClient.DisconnectAsync (); + } finally { + await AdbServer.Default.KillForward (androidDevice, "tcp", jdwpClient.Port, token); + } + } + } + } +} diff --git a/src/Xamarin.AndroidTools/Debugging/ExecutionConfiguration.cs b/src/Xamarin.AndroidTools/Debugging/ExecutionConfiguration.cs new file mode 100644 index 00000000000..728a7b8ade5 --- /dev/null +++ b/src/Xamarin.AndroidTools/Debugging/ExecutionConfiguration.cs @@ -0,0 +1,83 @@ +// +// ExecutionConfiguration.cs +// +// Author: +// Greg Munn +// +// Copyright (c) 2016 Xamarin Inc +// + +using System; +using Mono.AndroidTools; + +namespace Xamarin.AndroidTools.Debugging +{ + /// + /// Defines a configuration for running the application with the debugger + /// + public sealed class ExecutionConfiguration + { + public ExecutionConfiguration(string packageName, AmIntentCommand runCommand) + { + if (string.IsNullOrEmpty(packageName)) + throw new ArgumentException(nameof(packageName)); + + this.PackageName = packageName; + this.RunCommand = runCommand; + this.Debugger = new DebuggerOptions(); + } + + public ExecutionConfiguration(string packageName, string runCommand) + { + if (string.IsNullOrEmpty(packageName)) + throw new ArgumentException(nameof(packageName)); + + this.PackageName = packageName; + this.Debugger = new DebuggerOptions(); + + if (!string.IsNullOrEmpty(runCommand)) { + this.RunCommand = AmIntentCommandParser.Parse(runCommand, packageName); + } + } + + /// + /// Gets the name of the package that is being debugged. This is needed to set FastDev property files for + /// devices that fail to the debug properties corectly + /// + public string PackageName { get; private set; } + + /// + /// Gets the command that will be used to run the application + /// + public AmIntentCommand RunCommand { get; private set; } + + /// + /// Gets the debugger options that will instruct the runtime how to connect to the debugger + /// + public DebuggerOptions Debugger { get; private set; } + + /// + /// Gets or sets a value indicating whether Java debugging is allowed. Defaults to true, but will be available to be toggled off via -p:_AndroidAllowJavaDebugging=false. + /// + public bool AllowJavaDebugging { get; set; } = true; + + /// + /// Gets or sets the adb command to execute prior to executing the RunCommand + /// + public string BeforeRunCommand { get; set; } + + /// + /// Gets or sets the adb command to execute after the debugging session has ended + /// + public string AfterRunCommand { get; set; } + + /// + /// Gets or sets an action to write a string to the application output log + /// + /// + /// We want to be able to output the results of the run / before run / after run commands to a log that is + /// visisble to the user. This property should provide a method to do so. + /// + public Action LogWiter { get; set; } + } +} \ No newline at end of file diff --git a/src/Xamarin.AndroidTools/Debugging/Java/CommandPacket.cs b/src/Xamarin.AndroidTools/Debugging/Java/CommandPacket.cs new file mode 100644 index 00000000000..c5586a24e72 --- /dev/null +++ b/src/Xamarin.AndroidTools/Debugging/Java/CommandPacket.cs @@ -0,0 +1,46 @@ +using System; +using System.Buffers.Binary; + +namespace Xamarin.AndroidTools.Debugging.Java +{ + internal class CommandPacket : Packet + { + // 0-63 sets of commands sent to target VM + // 64-127 sets of commands sent to debugger + // 128-256 vendor defined commands and extensions + public byte CommandSet { get; set; } = 0x0; + + public byte Command { get; set; } = 0x0; + + public override ReadOnlyMemory ToMemory() + { + const uint headerLength = 11; + + var dataLength = Data.Length; + + Memory headerSpan = new byte[headerLength + dataLength]; + + var l = headerLength + ((uint)Math.Max(0, dataLength)); + + // Length + BinaryPrimitives.WriteUInt32BigEndian(headerSpan.Slice(0).Span, l); + + // Id + BinaryPrimitives.WriteInt32BigEndian(headerSpan.Slice(4).Span, Id); + + // Flags + headerSpan.Span[8] = Flags; + + // Command Set, Command + headerSpan.Span[9] = CommandSet; + headerSpan.Span[10] = Command; + + // Data body if there is one + if (dataLength > 0) + Data.CopyTo(headerSpan.Slice(11)); + + return headerSpan; + } + + } +} diff --git a/src/Xamarin.AndroidTools/Debugging/Java/JdwpClient.cs b/src/Xamarin.AndroidTools/Debugging/Java/JdwpClient.cs new file mode 100644 index 00000000000..5b57fa98146 --- /dev/null +++ b/src/Xamarin.AndroidTools/Debugging/Java/JdwpClient.cs @@ -0,0 +1,187 @@ +using System; +using System.Buffers.Binary; +using System.Collections.Generic; +using System.IO; +using System.Net.Sockets; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Mono.AndroidTools; + +namespace Xamarin.AndroidTools.Debugging.Java +{ + public class JdwpClient : IDisposable + { + const string handshake = "JDWP-Handshake"; + const int packetSize = 11; + private bool disposed = false; + + private TcpClient tcpClient; + private NetworkStream stream; + + public string HostName { get; } + + public int Port { get; } + + public JdwpClient(string hostname = "127.0.0.1", int port = 8100) + { + HostName = hostname; + Port = port; + } + + public async Task ConnectAsync(CancellationToken cancellationToken = default) + { + tcpClient = new TcpClient(); + + await tcpClient.ConnectAsync(HostName, Port); + stream = tcpClient.GetStream(); + + var data = Encoding.ASCII.GetBytes(handshake); + + await stream.WriteAsync(data, 0, data.Length); + + var buffer = new byte[handshake.Length]; + var replyBuffer = new byte[packetSize]; + + // Read handshake response + var read = await stream.ReadAsync(buffer, 0, buffer.Length, cancellationToken); + + var str = Encoding.ASCII.GetString(buffer, 0, read); + if (str.Equals(handshake)) + { + // Send version request command to kick things off + await SendAsync(new VersionCommandPacket(), cancellationToken); + + var replies = await ReadReply (cancellationToken); + // Read the Result but we do not need to process it. + AndroidLogger.LogDebug ($"VersionCommandPacket:"); + foreach (var reply in replies) { + str = Encoding.ASCII.GetString(reply.Data.ToArray (), 0, reply.Data.Length); + AndroidLogger.LogDebug ($"\t{str}"); + } + } + else + { + throw new InvalidDataException($"Debugger response did not match expected value: '{handshake}'"); + } + } + + async Task> ReadReply(CancellationToken cancellationToken = default) where T : ReplyPacket, new() + { + List packets = new List (); + do + { + if (stream != null) + { + byte[] headerData = new byte[11]; + + // Read the header data or bust + var read = await stream.ReadAsync (headerData, 0, headerData.Length, cancellationToken); + if (read != headerData.Length) { + break; + } + + // Get overall packet length from header + ReadOnlyMemory h = headerData; + var packetLength = BinaryPrimitives.ReadUInt32BigEndian(h.Slice (0, 4).Span); + // The remaining packet buffer is total packet length minus header length + byte[] packetData = new byte[packetLength - headerData.Length]; + + if (packetData.Length > 0) { + // Read the remainder of the packet into the second buffer + int datalen = packetData.Length; + while (datalen > 0) { + read = await stream.ReadAsync (packetData, 0, datalen, cancellationToken); + datalen -= read; + if (read == 0) + break; + } + if (datalen > 0) { + break; + } + } + var packet = new T (); + packet.FromMemory (headerData, packetData); + packets.Add (packet); + } + else + { + break; + } + } while (stream.DataAvailable); + return packets; + } + + [Obsolete ("Use DisconnectAsync instead", error:true)] + public void Disconnect() + { + } + + public Task DisconnectAsync() + { + if (stream != null) + { + try + { + stream.Dispose(); + } + catch + { + // nothing to do + } + finally { stream = null; } + } + + if (tcpClient != null) + { + try + { + tcpClient?.Close(); + } + catch { } + + try + { + tcpClient?.Dispose(); + } + catch + { + // nothing to do + } + finally { tcpClient = null; } + } + return Task.CompletedTask; + } + + async Task SendAsync(CommandPacket packet, CancellationToken cancellationToken = default) + { + if (stream != null) + { + var buffer = packet.ToMemory().ToArray(); + await stream.WriteAsync(buffer, 0, buffer.Length, cancellationToken); + } + } + + public void Dispose() + { + Dispose(disposing: true); + GC.SuppressFinalize(this); + } + + protected virtual void Dispose(bool disposing) + { + if(!this.disposed) + { + if(disposing) + { + // Dispose managed resources. + DisconnectAsync().Wait (); + } + + // Note disposing has been done. + disposed = true; + } + } + } + +} diff --git a/src/Xamarin.AndroidTools/Debugging/Java/Packet.cs b/src/Xamarin.AndroidTools/Debugging/Java/Packet.cs new file mode 100644 index 00000000000..8cd5164ce2e --- /dev/null +++ b/src/Xamarin.AndroidTools/Debugging/Java/Packet.cs @@ -0,0 +1,20 @@ +using System; +using System.Buffers.Binary; + +namespace Xamarin.AndroidTools.Debugging.Java +{ + internal abstract class Packet + { + static int id = 100; + + public int Id { get; set; } = id++; + + public byte Flags { get; set; } = 0x00; + + public ReadOnlyMemory Data { get; set; } = new byte[0]; + + public bool IsReply => (Flags & 0x80) == 0x80; + + public abstract ReadOnlyMemory ToMemory(); + } +} diff --git a/src/Xamarin.AndroidTools/Debugging/Java/ReplyPacket.cs b/src/Xamarin.AndroidTools/Debugging/Java/ReplyPacket.cs new file mode 100644 index 00000000000..1c899122337 --- /dev/null +++ b/src/Xamarin.AndroidTools/Debugging/Java/ReplyPacket.cs @@ -0,0 +1,53 @@ +using System; +using System.Buffers.Binary; +using System.Text; + +namespace Xamarin.AndroidTools.Debugging.Java +{ + internal class ReplyPacket : Packet + { + public ReplyPacket() + { + Flags &= 0x80; + } + + public short ErrorCode { get; set; } = 0; + + public override ReadOnlyMemory ToMemory() + { + const uint headerLength = 11; + + var dataLength = Data.Length; + + Memory headerSpan = new byte[headerLength + dataLength]; + + var l = headerLength + ((uint)Math.Max(0, dataLength)); + + // Length + BinaryPrimitives.WriteUInt32BigEndian(headerSpan.Slice(0).Span, l); + + // Id + BinaryPrimitives.WriteInt32BigEndian(headerSpan.Slice(4).Span, Id); + + // Flags + headerSpan.Span[8] = Flags; + + // Error code + BinaryPrimitives.WriteInt16BigEndian(headerSpan.Slice(9, 2).Span, ErrorCode); + + // Data body if there is one + if (dataLength > 0) + Data.Span.CopyTo(headerSpan.Slice(11, dataLength).Span); + + return headerSpan; + } + + public virtual void FromMemory(ReadOnlyMemory header, ReadOnlyMemory data) + { + Id = BinaryPrimitives.ReadInt32BigEndian(header.Slice(4, 4).Span); + Flags = header.Span[8]; + ErrorCode = BinaryPrimitives.ReadInt16BigEndian(header.Slice(9, 2).Span); + Data = data; + } + } +} \ No newline at end of file diff --git a/src/Xamarin.AndroidTools/Debugging/Java/VersionCommandPacket.cs b/src/Xamarin.AndroidTools/Debugging/Java/VersionCommandPacket.cs new file mode 100644 index 00000000000..acd3e2fb8b6 --- /dev/null +++ b/src/Xamarin.AndroidTools/Debugging/Java/VersionCommandPacket.cs @@ -0,0 +1,12 @@ + +namespace Xamarin.AndroidTools.Debugging.Java +{ + internal class VersionCommandPacket : CommandPacket + { + public VersionCommandPacket() + { + CommandSet = 1; + Command = 1; + } + } +} diff --git a/src/Xamarin.AndroidTools/Debugging/MonoDroidProcessMonitor.cs b/src/Xamarin.AndroidTools/Debugging/MonoDroidProcessMonitor.cs new file mode 100644 index 00000000000..a18cd104207 --- /dev/null +++ b/src/Xamarin.AndroidTools/Debugging/MonoDroidProcessMonitor.cs @@ -0,0 +1,375 @@ +// +// MonoDroidProcessMonitor.cs +// +// Author: +// Greg Munn +// +// Copyright (c) 2015 Xamarin Inc +// + +using System; +using System.Threading; +using System.Threading.Tasks; +using Mono.AndroidTools; + +namespace Xamarin.AndroidTools.Debugging +{ + /// + /// Monitors a running MonoDroid process during a debugging session + /// + /// something will launch the process, + /// this will monitor the device for the processes that match the given package name + /// once we find a process that matches we watch for that process to exit + /// + /// - onCompleted => end debugger session + /// + /// monitors logcat and outputs stdout and error messages + /// can optionally kill the process when disposed + /// + /// how to test? + /// - launch task + /// - get process task + /// - get logcat task + /// - kill process + /// - need to mock / augment device, maybe make the methods virtual on device + /// - or have an IDevice and we can pass a mock in instead + /// + /// + /// + public sealed class MonoDroidProcessMonitor : IDisposable + { + const int UNASSIGNED_PID = -1; + public static int RefreshPidInterval = 1000; + + // Common system tags that we may want to ignore + readonly static string [] excludedLogTags = { + "dalvikvm", + "ActivityThread", + "mkestner", + "MonoDroid-Debugger" + }; + + readonly ManualResetEvent endHandle = new ManualResetEvent (false); + readonly object lockObj = new object (); + readonly CancellationTokenSource cancellationSource; + readonly IAndroidDevice device; + readonly string packageName; + readonly Action onStdOut; + readonly Action onStdError; + readonly Func killProcessOnExit; + readonly Action onCompleted; + + /// + /// Func to return a task that will reset the debugger timeout, we use this to extend the debugger timeout to give the user + /// time to launch their app in the case where there is no launchable activity + /// + readonly Func resetTimeout; + + Task getPidTask; + Task loggingTask; + volatile int pid = UNASSIGNED_PID; + + public MonoDroidProcessMonitor (IAndroidDevice device, string packageName, Action stdout, Action stderr, + CancellationTokenSource cancellationSource, Func killProcessOnExit, Func resetTimeout, Action onCompleted) + { + if (device == null) + throw new ArgumentNullException ("device"); + if (packageName == null) + throw new ArgumentNullException ("packageName"); + if (stdout == null) + throw new ArgumentNullException ("stdout"); + if (stderr == null) + throw new ArgumentNullException ("stderr"); + if (cancellationSource == null) + throw new ArgumentNullException ("cancellationSource"); + + this.cancellationSource = cancellationSource; + this.device = device; + this.packageName = packageName; + this.onStdOut = stdout; + this.onStdError = stderr; + this.killProcessOnExit = killProcessOnExit; + this.resetTimeout = resetTimeout; + this.onCompleted = onCompleted; + } + + public bool IsStarted { get; private set; } + + public bool IsCompleted { get; private set; } + + /// + /// Starts the monitoring process. If processLaunchTask is non-null, waits for the launch task to complete before monitoring + /// and completes the monitor if the launch failed + /// + public void Start (Task processLaunchTask = null) + { + if (this.IsStarted) + throw new InvalidOperationException ("Already Started"); + + this.IsStarted = true; + this.StartMonitoring (processLaunchTask); + } + + public void Dispose () + { + if (!cancellationSource.IsCancellationRequested) + cancellationSource.Cancel (); + cancellationSource.Dispose (); + } + + public void Cancel () + { + lock (lockObj) { + if (IsCompleted) + return; + + // Make sure our tracking operations are finished first + cancellationSource.Cancel (); + + // Try to kill the activity if we were able to actually get its pid + if (pid != UNASSIGNED_PID && killProcessOnExit != null) { + var killTask = killProcessOnExit (device, packageName); + killTask.ContinueWith (t => { + if (t.IsFaulted) + AndroidLogger.LogError ("Failed to kill application", t.Exception.Flatten ().InnerException); + }); + } + } + + SetCompleted (); + } + + /// + /// Waits for the monitor to be completed, usually when the process has exited + /// + public void WaitForCompleted () + { + lock (lockObj) { + if (IsCompleted) + return; + } + + endHandle.WaitOne (); + } + + /// + /// Waits for the monitor to be completed, usually when the process has exited + /// + public void WaitForCompleted (int timeout) + { + lock (lockObj) { + if (IsCompleted) + return; + } + + endHandle.WaitOne (timeout); + } + + /// + /// Starts the monitoring process. If processLaunchTask is null or completed, starts tracking the process on the device + /// + void StartMonitoring (Task processLaunchTask) + { + if (processLaunchTask == null) { + StartTrackingProcess (); + return; + } + + processLaunchTask.ContinueWith (t => { + if (t.IsFaulted) { + var ex = t.Exception.Flatten ().InnerException; + onStdOut (string.Format ("\n Failed to launch app: {0}\n", ex.Message)); + AndroidLogger.LogError ("Failed to launch app", ex); + SetCompleted (); + } else if (cancellationSource.IsCancellationRequested || t.IsCanceled) { + SetCompleted (); + } else { + StartTrackingProcess (); + } + }, cancellationSource.Token, TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default); + } + + /// + /// Starts tracking the process on the device. We determine the process to track by the package name. + /// + void StartTrackingProcess () + { + if (cancellationSource.IsCancellationRequested) + return; + + try { + getPidTask = device.GetProcessId (packageName, cancellationSource.Token); + if (getPidTask == null) + throw new InvalidOperationException ("device.GetProcessId returned null, cannot track process"); + } catch (Exception ex) { + AndroidLogger.LogError ("Failure to get ProcessId task", ex); + SetCompleted (); + return; + } + + getPidTask.ContinueWith (RefreshPid); + } + + /// + /// Refreshs the pid of the process on the device, if the process has not yet started calls StartTrackingProcess again after a timeout. + /// + void RefreshPid (Task processIdTask) + { + if (cancellationSource.IsCancellationRequested) + return; + + if (processIdTask.IsFaulted) { + AndroidLogger.LogError ("Failed to get PID", processIdTask.Exception.Flatten ().InnerException); + SetCompleted (); + return; + } + if (processIdTask.IsCanceled) { + return; + } + + int resultPid = processIdTask.Result; + if (pid == UNASSIGNED_PID) { + // Ignore if the activity is still starting, and thus doesn't show up in 'ps' + if (resultPid > 0) { + pid = resultPid; + StartLogTracking (); // track log *after* getting the pid + } + } else { + if (resultPid == 0 || pid != resultPid) { + SetCompleted (); + return; + } + } + + //carry on polling the PID, so we can detect when the app exits + StartTrackingAfterTimeout (); + } + + static Task Delay(double milliseconds, CancellationToken token) + { + var tcs = new TaskCompletionSource(); + System.Timers.Timer timer = new System.Timers.Timer(); + timer.Elapsed+= (obj, args) => tcs.TrySetResult (true); + timer.Interval = milliseconds; + timer.AutoReset = false; + timer.Start(); + return tcs.Task; + } + + void StartTrackingAfterTimeout () + { + if (cancellationSource.IsCancellationRequested) + return; + + // TODO: use a Task.Delay once we can use .NET 4.5 + var delay = Delay (RefreshPidInterval, cancellationSource.Token); + delay.ContinueWith ((d) => { + StartTrackingProcess (); + + // if we are debugging an app that has no start up activity, like a watchface app, then + // periodically reset the debug timeout so that the user has time to start up the watchface + if (pid == UNASSIGNED_PID) { + ResetDebugerTimeout (); + } + }); + } + + /// + /// Starts monitoring logcat and outputs stdout and stderror + /// + void StartLogTracking () + { + try { + loggingTask = device.GetLogCat (ProcessLogLine, cancellationSource.Token, excludedLogTags); + if (loggingTask == null) + throw new InvalidOperationException ("device.GetLogCat returned null, cannot monitor logcat"); + } catch (Exception ex) { + AndroidLogger.LogError ("Failure to get LogCat task", ex); + SetCompleted (); + return; + } + + loggingTask.ContinueWith (t => { + if (t.IsFaulted) { + AndroidLogger.LogError ("Logcat ended unexpectedly", t.Exception.Flatten ().InnerException); + SetCompleted (); + } + }, cancellationSource.Token, TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default); + } + + /// + /// Resets the debugger output, if the process has not started then bump the debugger timeout to give additional time for it to start + /// This is used when the user has to manually start up the process (eg. watchface apps) + /// + void ResetDebugerTimeout () + { + if (cancellationSource.IsCancellationRequested) + return; + + if (resetTimeout != null) { + var timeoutTask = resetTimeout (); + try { + timeoutTask.Wait (cancellationSource.Token); + } + catch (AggregateException ex) { + if (ex.InnerException is OperationCanceledException) { + // expected, nothing to do + } else + throw; + } + catch (OperationCanceledException) { + // expected, nothing to do + } + catch (Exception ex) { + AndroidLogger.LogError ("Failed to reset timeout for debugger", ex); + } + } + } + + /// + /// Processes a line from logcat + /// + void ProcessLogLine (AndroidLogCatEntry entry) + { + // Disable the time check for now, as we need to use device-only dates + // We may implement a date retrieval later if needed + //if (pid != this.pid || time < startTime) + if (entry.Pid != this.pid) + return; + + switch (entry.Tag) { + case "mono-stdout": + case "stdout": + onStdOut (entry.Message + "\n"); + break; + case "mono-stderr": + case "stderr": + onStdError (entry.Message + "\n"); + break; + default: + onStdOut (string.Format ("[{0}] {1}\n", entry.Tag, entry.Message)); + break; + } + } + + void SetCompleted () + { + lock (lockObj) { + if (IsCompleted) + return; + + endHandle.Set (); + IsCompleted = true; + } + + try { + if (onCompleted != null) + onCompleted (); + + cancellationSource.Cancel (); + } catch (Exception ex) { + AndroidLogger.LogError ("Unhandled error completing MonoDroidProcess", ex); + } + } + } +} \ No newline at end of file diff --git a/src/Xamarin.AndroidTools/Devices/AndroidDeviceExtensions.cs b/src/Xamarin.AndroidTools/Devices/AndroidDeviceExtensions.cs new file mode 100644 index 00000000000..9a4118e3505 --- /dev/null +++ b/src/Xamarin.AndroidTools/Devices/AndroidDeviceExtensions.cs @@ -0,0 +1,825 @@ + +// AndroidDeviceExtensions.cs +// +// Authors: +// Jonathan Pobst +// +// Copyright 2011 Xamarin Inc. All rights reserved. +// + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net; +using System.Threading; +using System.Threading.Tasks; +using Mono.AndroidTools; +using Mono.AndroidTools.Adb; +using Xamarin.AndroidTools; +using Xamarin.AndroidTools.Debugging; + +// These are methods for Mono.AndroidTools.AndroidDevice, but +// are too MFA specific to go into Mono.AndroidTools. + + +public static class AndroidDeviceExtensions +{ + [Obsolete ("Use the async overload")] + public static void EnsureProperties (this AndroidDevice device) + { + EnsureProperties (device, CancellationToken.None).Wait (); + } + + public static Task EnsureProperties (this AndroidDevice device, CancellationToken cancellationToken) + { + if (device.Properties == null) { + return device.RefreshProperties (cancellationToken); + } + var tcs = new TaskCompletionSource (); + tcs.SetResult (null); + return tcs.Task; + } + + public static Task SendSeppukuBroadcast (this AndroidDevice device, string packageName) + { + return SendSeppukuBroadcast (device, packageName, CancellationToken.None); + } + + public static Task SendSeppukuBroadcast (this AndroidDevice device, string packageName, CancellationToken cancellationToken) + { + var action = "mono.android.intent.action.SEPPUKU"; + var category = string.Format ("mono.android.intent.category.SEPPUKU.{0}", packageName); + var name = packageName + "/mono.android.Seppuku"; + + return device.Broadcast (action, new[] { category }, null, name, null, cancellationToken); + } + + public static Task KillProcess (this AndroidDevice device, string packageName) + { + return KillProcess (device, packageName, CancellationToken.None); + } + + public static Task KillProcess (this AndroidDevice device, string packageName, CancellationToken cancellationToken) + { + if (device.Properties.BuildVersionSdk >= 14) { + return device.ForceStop (packageName, cancellationToken); + } + return SendSeppukuBroadcast (device, packageName, cancellationToken); + } + + public static Task KillProcessIfRunningAndWaitForExit (this AndroidDevice device, string packageName, CancellationToken token) + { + AndroidLogger.LogDebug ("KillProcessIfRunningAndWaitForExit", "Checking whether app {0} is running", packageName); + var tcs = new TaskCompletionSource (); + device.GetProcessId (packageName).ContinueWith (t => { + try { + if (t.IsFaulted) { + tcs.SetException (t.Exception); + } else if (token.IsCancellationRequested) { + tcs.SetCanceled (); + } else if (t.Result == 0) { + AndroidLogger.LogDebug ("KillProcessIfRunningAndWaitForExit", "App was not running, skipping kill"); + tcs.SetResult (false); + } else { + KillProcessAndWaitForExit (device, packageName, token).ContinueWith (t2 => { + if (t2.IsFaulted) { + tcs.SetException (t2.Exception); + } else if (token.IsCancellationRequested) { + tcs.SetCanceled (); + } else { + tcs.SetResult (true); + } + }, token, TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default); + } + } catch (Exception ex) { + tcs.TrySetException (ex); + } + }, token, TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default); + return tcs.Task; + } + + public static Task KillProcessAndWaitForExit (this AndroidDevice device, string packageName, CancellationToken token) + { + //if using 4.0+, we have a reliable hard kill, no need to seppuku and poll + if (device.Properties.BuildVersionSdk >= 14) { + return device.KillProcess (packageName, token); + } + + return KillProcessAndWaitForExitPreIcs (device, packageName, token); + } + + static Task KillProcessAndWaitForExitPreIcs (AndroidDevice device, string packageName, CancellationToken token) + { + var tcs = new TaskCompletionSource (); + KillProcess (device, packageName, token).ContinueWith (r => { + try { + if (r.IsFaulted) { + tcs.SetException (r.Exception); + } else if (token.IsCancellationRequested) { + tcs.SetCanceled (); + } else { + AndroidLogger.LogDebug ("KillProcessIfRunning", "Waiting for process to exit"); + RepeatTaskUntilTrue (() => device.GetProcessId (packageName, token), i => i == 0, token) + .ContinueWith (t2 => { + try { + if (t2.IsFaulted) { + tcs.SetException (t2.Exception); + } else if (token.IsCancellationRequested) { + tcs.SetCanceled (); + } else { + AndroidLogger.LogDebug ("KillProcessIfRunning", "Process exited"); + tcs.SetResult (true); + } + } catch (Exception ex) { + tcs.TrySetException (ex); + } + }, token, TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default); + } + } catch (Exception ex) { + tcs.TrySetException (ex); + } + }, token, TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default); + return tcs.Task; + } + + public static async Task GetProcessIDAsync(this AndroidDevice device, string packageName, int maxAttempts, int timeBetweenAttempts, CancellationToken token) + { + var retryCount = 1; + var pidOfResult = await device.GetProcessId(packageName, token); + AndroidLogger.LogDebug("GetProcessIDAsync", "PID of the application :: " + pidOfResult); + + while (pidOfResult <= 0 && retryCount <= maxAttempts) + { + await Task.Delay(timeBetweenAttempts); + pidOfResult = await device.GetProcessId(packageName, token); + AndroidLogger.LogDebug("GetProcessIDAsync", "Retrying " + retryCount + " time(s) to get PID of the application"); + retryCount++; + } + return pidOfResult; + } + + + static Task RepeatTaskUntilTrue (Func> createTask, Func checkResult, CancellationToken token) + { + var tcs = new TaskCompletionSource (); + Action> f = null; + f = t => { + try { + if (t.IsFaulted) { + tcs.SetException (t.Exception); + } else if (token.IsCancellationRequested) { + tcs.SetCanceled (); + } else if (checkResult (t.Result)) { + tcs.SetResult (null); + } else { + createTask ().ContinueWith (f, token, TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default); + } + } catch (Exception ex) { + tcs.TrySetException (ex); + } + }; + createTask ().ContinueWith (f, token, TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default); + return tcs.Task; + } + + [Obsolete ("Use PushAndInstallPackageAsync (PushAndInstallCommand command) instead.")] + public static Task PushAndInstallPackage (this AndroidDevice device, string apkFile, bool reinstall, AdbProgressReporter notifyProgress = null, CancellationToken token = new CancellationToken ()) + { + return PushAndInstallPackage (device, apkFile, null, reinstall, notifyProgress: notifyProgress, token: token); + } + + [Obsolete ("Use PushAndInstallPackageAsync (PushAndInstallCommand command) instead.")] + public static Task PushAndInstallPackage(this AndroidDevice device, string apkFile, string packageName, bool reinstall, AdbProgressReporter notifyProgress = null, CancellationToken token = new CancellationToken()) + { + var command = new PushAndInstallCommand () { + ApkFile = apkFile, + PackageName = packageName, + ReInstall = reinstall, + NotifyProgress = notifyProgress, + }; + return PushAndInstallPackageAsync (device, command, token: token); + } + + public static async Task PushAndInstallPackageAsync (this AndroidDevice device, PushAndInstallCommand command, CancellationToken token = new CancellationToken ()) + { + var remoteApkFile = AndroidDevice.GetRemoteTempApkPath (command.ApkFile); + + PmInstallCommand pmInstallCommand = new PmInstallCommand () { + RemoteApkFile = remoteApkFile, + User = command.User, + }; + + try { + // Copy the apk over to a temp location + await device.Push (command.ApkFile, remoteApkFile, command.NotifyProgress, token); + await device.EnsureProperties (token); + + // Tell Android to install it + var flags = device.Properties.BuildVersionSdk < 19 ? AdbInstallFlags.None : AdbInstallFlags.AllowDowngrade; + if (command.ReInstall) + flags |= AdbInstallFlags.Reinstall; + if (command.TestOnly) + flags |= AdbInstallFlags.TestOnly; + pmInstallCommand.Flags = flags; + await device.InstallPackage (pmInstallCommand, token); + } catch (PackageAlreadyExistsException) { + if (!string.IsNullOrEmpty (command.PackageName)) { + AndroidLogger.LogInfo ($"Looks like the package `{command.PackageName}` is already installed. Trying to uninstall it first."); + pmInstallCommand.Flags = AdbInstallFlags.None; + await device.UninstallPackage (command.PackageName, preserveData: true, cancellationToken: token); + await device.InstallPackage (pmInstallCommand, token); + } else { + throw; + } + } finally { + try { + // Delete the temp apk + await device.DeleteFile (remoteApkFile, false, token); + } catch (Exception ex) { + // Don't let this crash + AndroidLogger.LogInfo ($"Failed to delete package file: {ex.Message}"); + } + } + } + + [Obsolete ("Use StartWithDebuggingAsync")] + public static Task StartActivityWithDebugging (this AndroidDevice device, string package, string activity, + IPAddress address, int sdbPort, int stdoutPort, bool server) + { + return StartActivityWithDebugging (device, package, activity, address, sdbPort, stdoutPort, server, CancellationToken.None); + } + + [Obsolete("Use StartWithDebuggingAsync")] + public async static Task StartActivityWithDebugging (this AndroidDevice device, string package, string activity, + IPAddress address, int sdbPort, int stdoutPort, bool server, CancellationToken token) + { + var androidDevice = (IAndroidDevice)device; + + var debuggerOptions = new DebuggerOptions(address, sdbPort, stdoutPort, server); + + await androidDevice.SetDebugPropertiesAsync(package, debuggerOptions, token).ConfigureAwait(false); + + // if the startCommand is null it is because there is no activity to start, in which case + // we will return a completed task and the user will have to manually start up the process + if (string.IsNullOrEmpty(activity)) + return; + + var command = new AmStartCommand(package, activity); + command.Action = command.Action ?? "android.intent.action.MAIN"; + command.Categories = command.Categories ?? new[] { "android.intent.category.LAUNCHER" }; + await device.ExecuteIntentCommandAsync(command, null, token).ConfigureAwait(false); + } + + [Obsolete ("Use SetDebugPropertiesAsync")] + public static Task SetDebugProperties (this AndroidDevice device, IPAddress address, int sdbPort, int stdoutPort, + bool server, CancellationToken token) + { + return SetDebugProperties (device, null, address, sdbPort, stdoutPort, server, token); + } + + /// + /// Returns a Task which sets up the debug property + /// + [Obsolete ("Use SetDebugPropertiesAsync")] + public static Task SetDebugProperties (this AndroidDevice device, AmStartCommand startCommand, IPAddress address, int sdbPort, int stdoutPort, bool server, CancellationToken token) + { + const int loglevel = 0; + + // Get the time the device thinks it is, and add 30 seconds + return device.GetDate (token).ContinueWith (t => { + long expire_date = t.Result + 30; // 30 seconds + string endpoint = stdoutPort > -1 + ? string.Format ("{0}:{1}:{2}", address, sdbPort, stdoutPort) + : string.Format ("{0}:{1}", address, sdbPort); + + // Set property to tell the device to launch in debug mode + string debugArg = string.Format ( + "debug={0},timeout={1},loglevel={2},server={3}", + endpoint, expire_date, loglevel, server ? "y" : "n" + ); + + return device.SetProperty ("debug.mono.extra", debugArg, token).ContinueWith (r => { + return device.SetFastDevPropertyFile (startCommand?.PackageName, "debug.mono.extra", debugArg, token); + }, token, TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default).Unwrap (); + }, token, TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default).Unwrap (); + } + + public static Task StartActivityWithoutDebugging (this AndroidDevice device, string package, string activity) + { + return StartActivityWithoutDebugging (device, package, activity, CancellationToken.None); + } + + public static Task StartActivityWithoutDebugging (this AndroidDevice device, string package, string activity, CancellationToken token) + { + return StartActivityWithoutDebugging (device, new AmStartCommand (package, activity), token); + } + + public static Task StartActivityWithoutDebugging (this AndroidDevice device, AmStartCommand startCommand, CancellationToken token = default(CancellationToken)) + { + // In case the user is quick and the 30 second timeout is still valid, we'll go ahead and reset it + return device.SetProperty ("debug.mono.extra", string.Empty, token).ContinueWith (r => { + return device.SetFastDevPropertyFile (startCommand?.PackageName, "debug.mono.extra", string.Empty, token); + }).ContinueWith (t => { + if (t.IsFaulted) + throw t.Exception; + token.ThrowIfCancellationRequested (); + // Launch the activity + var command = new AmStartCommand (startCommand); + command.Action = command.Action ?? "android.intent.action.MAIN"; + command.Categories = command.Categories ?? new [] {"android.intent.category.LAUNCHER"}; + return device.ExecuteIntentCommandAsync (command, null, token); + }, token, TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default).Unwrap (); + } + + public static Task SetFastDevPropertyFile (this AndroidDevice device, string package, string property, string value, + CancellationToken token) + { + return device.RefreshProperties (token).ContinueWith (r => { + var propertyValue = device.Properties.Get (property); + if (string.IsNullOrEmpty (propertyValue) || propertyValue != value || string.IsNullOrEmpty (value)) { + return device.SetInternalPropertyFile (package, property, value, token); + } + return r; + }, token, TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default).Unwrap (); + } + + static Task TaskDelay (int delayMilliseconds) + { + var tcs = new TaskCompletionSource(); + var timer = new System.Timers.Timer { + Interval = delayMilliseconds, + AutoReset = false + }; + timer.Elapsed += (obj, args) => { + tcs.TrySetResult (true); + timer.Dispose (); + }; + timer.Start(); + return tcs.Task; + } + + static public Task StartActivityWithCommandSession (this AndroidDevice device, string package, string activity, IPAddress address, int port, CancellationToken token) + { + var tcs = new TaskCompletionSource (); + var connection = new AndroidConnectCommandSession(address, port); + + var launchTask = AdbServer.Default.ForwardPort (device, port, port, token) + .ContinueWith (t => { + t.Wait (); + return device.SetProperty ("debug.mono.connect", "port=" + port); + }, token, TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default).Unwrap () + .ContinueWith (t => { + t.Wait (); + return device.KillProcessIfRunningAndWaitForExit (package, token); + }, token, TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default).Unwrap () + .ContinueWith (t => { + t.Wait (); + return device.StartActivity ( + "android.intent.action.MAIN", new [] { "android.intent.category.LAUNCHER" }, + package, activity, false, token); + }, token, TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default).Unwrap (); + + // This just initializes the socket, but doesn't actually do any work yet + Action onConnect = null; + + Action cleanup = () => { + try { + connection.Dispose(); + } catch (Exception ex) { + AndroidLogger.LogError ("Failed to clean up Android command session", ex); + } + }; + + int retries = 10; + onConnect = t => { + //faulted, and out of retries or not an IOException + if (t.IsFaulted && (!(t.Exception.Flatten().InnerException is IOException) || retries < 0)) { + tcs.TrySetException(t.Exception); + cleanup(); + return; + } + //cancelled + if (t.IsCanceled || token.IsCancellationRequested) { + tcs.TrySetCanceled(); + cleanup(); + return; + } + //success + if (t.IsCompleted && !t.IsFaulted) { + tcs.TrySetResult (connection); + device.SetProperty ("debug.mono.connect", "", token).Wait (); + return; + } + + cleanup(); + + //retry after a small delay + retries--; + TaskDelay(200).ContinueWith (td => { + if (td.IsFaulted) { + tcs.TrySetException (td.Exception); + } else if (td.IsCanceled || token.IsCancellationRequested) { + tcs.TrySetCanceled (); + } else { + try { + connection = new AndroidConnectCommandSession(address, port); + Task.Factory.FromAsync (connection.BeginHandshake (), connection.EndHandshake).ContinueWith (onConnect); + } catch (Exception ex) { + tcs.TrySetException (ex); + } + } + }); + }; + + launchTask.ContinueWith (t => { + try { + t.Wait(); + token.Register (cleanup); + Task.Factory.FromAsync(connection.BeginHandshake (), connection.EndHandshake) + .ContinueWith (onConnect); + } catch (Exception ex) { + tcs.TrySetException (ex); + } + }); + + return tcs.Task; + } + + public static async Task InstallSharedRuntimeAsync (this AndroidDevice device, string runtimeFile, AdbProgressReporter progress, CancellationToken token) + { + // See if we were asked to cancel + token.ThrowIfCancellationRequested (); + + try { + await device.PushAndInstallPackageAsync (new PushAndInstallCommand () { + ApkFile = runtimeFile, + ReInstall = false, + NotifyProgress = progress + }, token); + } catch (Exception ex) { + var aex = ex as AggregateException; + if (aex != null) { + ex = aex.Flatten ().InnerException; + } + + // If the runtime already exists, ignore the error + // Sometimes android doesn't report it's installed when it is :/ + if (ex is PackageAlreadyExistsException) + return; + + throw; + } + } + + [Obsolete ("Use InstallSharedRuntimeAsync")] + public static void InstallSharedRuntime (this AndroidDevice device, string runtimeFile, AdbProgressReporter progress, CancellationToken token) + { + // See if we were asked to cancel + token.ThrowIfCancellationRequested (); + + try { + device.PushAndInstallPackageAsync (new PushAndInstallCommand () { + ApkFile = runtimeFile, + ReInstall = false, + NotifyProgress = progress, + }, token).Wait (); + } catch (Exception ex) { + var aex = ex as AggregateException; + if (aex != null) { + ex = aex.Flatten ().InnerException; + } + + // If the runtime already exists, ignore the error + // Sometimes android doesn't report it's installed when it is :/ + if (ex is PackageAlreadyExistsException) + return; + + throw; + } + } + + public static async Task InstallSharedPlatformAsync (this AndroidDevice device, string platformFile, int packageApiLevel, AdbProgressReporter progress, CancellationToken token) + { + // See if we were asked to cancel + token.ThrowIfCancellationRequested (); + + try { + await device.PushAndInstallPackageAsync (new PushAndInstallCommand () { + ApkFile = platformFile, + PackageName = GetPlatformPackageName (packageApiLevel), + ReInstall = false, + NotifyProgress = progress, + }, token); + } catch (Exception ex) { + var aex = ex as AggregateException; + if (aex != null) { + ex = aex.Flatten ().InnerException; + } + + // If the runtime already exists, ignore the error + // Sometimes android doesn't report it's installed when it is :/ + if (ex is PackageAlreadyExistsException) + return; + + throw; + } + } + + [Obsolete ("Use InstallSharedPlatformAsync")] + public static void InstallSharedPlatform (this AndroidDevice device, string platformFile, int packageApiLevel, AdbProgressReporter progress, CancellationToken token) + { + // See if we were asked to cancel + token.ThrowIfCancellationRequested (); + + try { + device.PushAndInstallPackageAsync (new PushAndInstallCommand () { + ApkFile = platformFile, + PackageName = GetPlatformPackageName (packageApiLevel), + ReInstall = false, + NotifyProgress = progress, + }, token).Wait (); + } catch (Exception ex) { + var aex = ex as AggregateException; + if (aex != null) { + ex = aex.Flatten ().InnerException; + } + + // If the runtime already exists, ignore the error + // Sometimes android doesn't report it's installed when it is :/ + if (ex is PackageAlreadyExistsException) + return; + + throw; + } + } + + public static AndroidDeploySession GetDeploySession (this AndroidDevice device, IList packages = null) + { + return new AndroidDeploySession (device, packages); + } + + public static async Task> GetPackagesAsync (this AndroidDevice device, int packageApiLevel, string packageName, bool provideFullDebugRuntime, CancellationToken cancellationToken, IProgressNotifier progressReporter) + { + // See what API level the device supports + await device.EnsureProperties (cancellationToken).ConfigureAwait(false); + + progressReporter.BeginStep ("Detecting installed packages"); + progressReporter.ReportMessage ("Detecting installed packages..."); + try { + string versions = await GetPackageVersionsAsync (device, packageApiLevel, packageName, cancellationToken).ConfigureAwait(false); + if (!string.IsNullOrWhiteSpace (versions)) + return GetInstalledPackages (versions); + + var p = await GetInstalledPackagesFromDatabaseAsync (device, true, cancellationToken).ConfigureAwait(false); + if (p != null) + return p; + + return await GetInstalledPackagesFromDatabaseAsync (device, false, cancellationToken).ConfigureAwait(false); + } finally { + progressReporter.EndStep (""); + } + } + + [Obsolete ("Use GetPackagesAsync")] + public static IList GetPackages (this AndroidDevice device, int packageApiLevel, string packageName, bool provideFullDebugRuntime, CancellationToken cancellationToken, IProgressNotifier progressReporter) + { + // See what API level the device supports + device.EnsureProperties (cancellationToken).Wait (); + + progressReporter.BeginStep ("Detecting installed packages"); + progressReporter.ReportMessage ("Detecting installed packages..."); + try { + string versions = GetPackageVersions (device, packageApiLevel, packageName, cancellationToken); + if (!string.IsNullOrWhiteSpace (versions)) + return GetInstalledPackages (versions); + + var p = GetInstalledPackagesFromDatabase (device, true, cancellationToken); + if (p != null) + return p; + + return GetInstalledPackagesFromDatabase (device, false, cancellationToken); + } finally { + progressReporter.EndStep (""); + } + } + + [Obsolete("Use GetInstalledPackagesFromDatabaseAsync")] + static IList GetInstalledPackagesFromDatabase (AndroidDevice device, bool requireVersions, CancellationToken cancellationToken) + { + return device.GetPackages (requireVersions, cancellationToken).Result; + } + + static Task> GetInstalledPackagesFromDatabaseAsync (AndroidDevice device, bool requireVersions, CancellationToken cancellationToken) + { + return device.GetPackages (requireVersions, cancellationToken); + } + + static string GetPlatformPackageName (int packageApiLevel) + { + return string.Format (AndroidPackageListExtensions.platformName, packageApiLevel); + } + + [Obsolete("Use GetPackageVersionsAsync")] + static string GetPackageVersions (AndroidDevice device, int packageApiLevel, string packageName, CancellationToken cancellationToken) + { + var action = "mono.android.intent.action.PACKAGE_VERSIONS"; + var platform = GetPlatformPackageName (packageApiLevel); + PlatformPackage.GetPlatformPackageVersion (packageApiLevel, ref platform); + var packages = string.Join (",", new[]{ + AndroidPackageListExtensions.runtimeName, + platform, + packageName, + }); + var extras = new Dictionary { + { "packages", packages }, + }; + return device.Broadcast (action, + null, + extras, + "Mono.Android.DebugRuntime/com.xamarin.mono.android.PackageVersions", + cancellationToken).Result; + } + + async static Task GetPackageVersionsAsync(AndroidDevice device, int packageApiLevel, string packageName, CancellationToken cancellationToken) + { + var action = "mono.android.intent.action.PACKAGE_VERSIONS"; + var platform = GetPlatformPackageName(packageApiLevel); + var packages = string.Join(",", new[]{ + AndroidPackageListExtensions.runtimeName, + platform, + packageName, + }); + var extras = new Dictionary { + { "packages", packages }, + }; + return await device.Broadcast(action, + null, + extras, + "Mono.Android.DebugRuntime/com.xamarin.mono.android.PackageVersions", + cancellationToken).ConfigureAwait(false); + } + + static IList GetInstalledPackages (string packageVersions) + { + return packageVersions.Split (new[]{','}, StringSplitOptions.RemoveEmptyEntries).Select (v => { + string[] p = v.Split ('='); + return new AndroidInstalledPackage (p [0], null, int.Parse (p [1])); + }).ToList (); + } + + internal static async Task InstallSharedRuntimeAsync (this AndroidDevice device, bool provideFullDebugRuntime, CancellationToken cancellationToken, IProgressNotifier progressReporter) + { + var arch = device.Properties.ProductCpuAbi; + progressReporter.ReportMessage ("Target device is " + arch + "."); + + var runtime_file = MonoDroidSdk.GetSharedRuntimePackage (provideFullDebugRuntime, arch); + var runtime_desc = runtime_file.EndsWith ("-debug.apk", StringComparison.Ordinal) ? "debug" : arch; + + // Install the runtime + var text = string.Format ("Installing the Mono shared runtime ({0} - {1})...", runtime_desc, MonoDroidSdk.SharedRuntimeVersion); + progressReporter.ReportMessage (text); + await device.InstallSharedRuntimeAsync (runtime_file, progressReporter.ReportProgress, cancellationToken); + } + + [Obsolete ("Use InstallSharedRuntimeAsync")] + internal static void InstallSharedRuntime (this AndroidDevice device, bool provideFullDebugRuntime, CancellationToken cancellationToken, IProgressNotifier progressReporter) + { + var arch = device.Properties.ProductCpuAbi; + progressReporter.ReportMessage ("Target device is " + arch + "."); + + var runtime_file = MonoDroidSdk.GetSharedRuntimePackage (provideFullDebugRuntime, arch); + var runtime_desc = runtime_file.EndsWith ("-debug.apk", StringComparison.Ordinal) ? "debug" : arch; + + // Install the runtime + var text = string.Format ("Installing the Mono shared runtime ({0} - {1})...", runtime_desc, MonoDroidSdk.SharedRuntimeVersion); + progressReporter.ReportMessage (text); + device.InstallSharedRuntime (runtime_file, progressReporter.ReportProgress, cancellationToken); + } + + const string PackageInstallLocationFormat = "data/{0}/files/.__override__"; + + [Obsolete ("Use GetPackageRemotePathAsync")] + public static string GetPackageRemotePath (this AndroidDevice device, string packageName, CancellationToken cancellationToken) + { + var x = device.RunShellCommand (cancellationToken, "pm", "path", packageName).Result; + + string[] packagePathInfo = x.Split (':'); + if (packagePathInfo.Length <= 1) { + throw new AndroidDeploymentException (AndroidDeploymentFailureReason.InternalError, + new InvalidOperationException ( + string.Format ("Could not determine the installation path for package {0}. " + + "`adb shell pm path {0}` returned '{1}'.", packageName, x))); + } + return packagePathInfo [1]; + } + + public static async Task GetPackageRemotePathAsync (this AndroidDevice device, string packageName, CancellationToken cancellationToken) + { + var x = await device.RunShellCommand (cancellationToken, "pm", "path", packageName); + + string[] packagePathInfo = x.Split (':'); + if (packagePathInfo.Length <= 1) { + throw new AndroidDeploymentException (AndroidDeploymentFailureReason.InternalError, + new InvalidOperationException ( + string.Format ("Could not determine the installation path for package {0}. " + + "`adb shell pm path {0}` returned '{1}'.", packageName, x))); + } + return packagePathInfo [1]; + } + + public static async Task GetFastDevRemotePathInternalAsync (this AndroidDevice device, string packageName, CancellationToken cancellationToken) + { + var internalPath = await device.RunShellCommand (cancellationToken, "run-as", packageName, "pwd"); + if (internalPath.IndexOf ("Permission denied", StringComparison.OrdinalIgnoreCase) >= 0) { + internalPath = await device.RunShellCommand (packageName, "readlink", "-f", "."); + } + if (internalPath.IndexOf ("run-as:", StringComparison.OrdinalIgnoreCase) >= 0 || + internalPath.IndexOf ("package not debuggable", StringComparison.OrdinalIgnoreCase) >= 0 || + internalPath.IndexOf ("unknown", StringComparison.OrdinalIgnoreCase) >= 0 || + internalPath.IndexOf ("Permission denied", StringComparison.OrdinalIgnoreCase) >= 0) { + return null; + } + return internalPath; + } + + [Obsolete ("Use GetFastDevRemotePathExternalAsync()")] + public static string GetFastDevRemotePathExternal (this AndroidDevice device, string packageName, CancellationToken cancellationToken) + { + return GetFastDevRemotePathExternalAsync (device, packageName, cancellationToken).Result; + } + + [Obsolete ("Use GetFastDevRemotePathInternalAsync. Shared Runtime is no longer supported.")] + public static async Task GetFastDevRemotePathExternalAsync (this AndroidDevice device, string packageName, CancellationToken cancellationToken) + { + // EMULATED_STORAGE_SOURCE=/mnt/shell/emulated + // EMULATED_STORAGE_TARGET=/storage/emulated + // EXTERNAL_STORAGE_DIRECTORY broadcast returns: + // * Primary user: "/mnt/shell/emulated/0" or "/storage/emulated/legacy" + // * Secondary user: "/storage/emulated/11" + + var source = device.RunShellCommand (cancellationToken, "echo", "-n", "${EMULATED_STORAGE_SOURCE}").Result; + var target = device.RunShellCommand (cancellationToken, "echo", "-n", "${EMULATED_STORAGE_TARGET}").Result; + var esd = await device.Broadcast ( + new AmBroadcastCommand { + Action = "mono.android.intent.action.EXTERNAL_STORAGE_DIRECTORY", + Component = "Mono.Android.DebugRuntime/com.xamarin.mono.android.ExternalStorageDirectory", + }, + cancellationToken); + + if (string.IsNullOrEmpty (esd)) { + esd = await device.RunShellCommand (cancellationToken, "echo", "-n", "${EXTERNAL_STORAGE}"); + } + if (!string.IsNullOrEmpty (source) && !string.IsNullOrEmpty (target) && esd.StartsWith (target, StringComparison.Ordinal)) { + esd = esd.Replace (target, source); + } + return string.Format ("{0}/Android/{1}", esd, string.Format (PackageInstallLocationFormat, packageName)); + } + + public class FastDevRemotePathInfo + { + public FastDevRemotePathInfo (string fastDevRemotePath, string packageRemotePath, bool external) + { + this.Root = fastDevRemotePath; + PackageRemotePath = packageRemotePath; + this.IsExternal = external; + } + + public string Root { get; set; } + public string PackageRemotePath { get; set; } + public bool IsExternal { get; set; } + } + + [Obsolete ("Use GetFastDevRemotePathAsync()")] + public static string GetFastDevRemotePath (this AndroidDevice device, string packageName, CancellationToken cancellationToken, out string packageRemotePath, out bool external) + { + var ret = GetFastDevRemotePathAsync (device, packageName, cancellationToken).Result; + packageRemotePath = ret.PackageRemotePath; + external = ret.IsExternal; + return ret.Root; + } + + public static async Task GetFastDevRemotePathAsync (this AndroidDevice device, string packageName, CancellationToken cancellationToken) + { + string packageRemotePath = await GetPackageRemotePathAsync (device, packageName, cancellationToken); + bool external = !packageRemotePath.StartsWith ("/data", StringComparison.Ordinal); + + var root = "/data/"; + if (external) { + var ex = await device.Broadcast ("mono.android.intent.action.EXTERNAL_STORAGE_DIRECTORY", null, cancellationToken); + if (!string.IsNullOrEmpty (ex)) + root = ex + "/Android/"; + } + + return new FastDevRemotePathInfo (root + string.Format (PackageInstallLocationFormat, packageName), packageRemotePath, external); + } +} + +public class PushAndInstallCommand { + public string ApkFile { get; set;} + public string User { get; set;} + public bool ReInstall { get; set; } + public string PackageName { get; set; } + public bool TestOnly { get; set; } = false; + public AdbProgressReporter NotifyProgress { get; set;} +} diff --git a/src/Xamarin.AndroidTools/Devices/AndroidPackageList.cs b/src/Xamarin.AndroidTools/Devices/AndroidPackageList.cs new file mode 100644 index 00000000000..a2c4febdf16 --- /dev/null +++ b/src/Xamarin.AndroidTools/Devices/AndroidPackageList.cs @@ -0,0 +1,93 @@ +// +// AndroidPackageList.cs +// +// Authors: +// Jonathan Pobst +// +// Copyright 2011 Xamarin Inc. All rights reserved. +// + +using System; +using System.Collections.Generic; +using System.Linq; +using Mono.AndroidTools; + +namespace Xamarin.AndroidTools +{ + [Obsolete ("Use AndroidPackageListExtensions")] + public class AndroidPackageList + { + string runtimeName = "Mono.Android.DebugRuntime"; + string oldRuntimeName = "com.novell.monodroid.runtimeservice"; + string platformName = "Mono.Android.Platform.ApiLevel_{0}"; + + public List Packages { get; private set; } + + public AndroidPackageList (List packages) + { + Packages = packages; + } + + public bool IsCurrentRuntimeInstalled (int runtimeVersion) + { + return Packages.Any (p => p.Name == runtimeName && p.Version == runtimeVersion); + } + + public bool IsUnknownRuntimeInstalled () + { + return Packages.Any (p => p.Name == runtimeName && p.Version == int.MaxValue); + } + + public List GetOldRuntimes (int runtimeVersion) + { + return Packages.Where (p => (p.Name == runtimeName && p.Version != runtimeVersion) || p.Name == oldRuntimeName).ToList (); + } + + public bool IsCurrentPlatformInstalled (string apiLevel, int runtimeVersion) + { + string name = string.Format (platformName, apiLevel); + + return Packages.Any (p => p.Name == name && p.Version == runtimeVersion); + } + + public bool IsUnknownPlatformInstalled (string apiLevel) + { + string name = string.Format (platformName, apiLevel); + + return Packages.Any (p => p.Name == name && p.Version == int.MaxValue); + } + + public bool AreCurrentRuntimeAndPlatformInstalled (string apiLevel, int runtimeVersion) + { + return IsCurrentRuntimeInstalled (runtimeVersion) && IsCurrentPlatformInstalled (apiLevel, runtimeVersion); + } + + // Hopefully they don't have multiple old + // platforms installed, but just in case... + public List GetOldPlatforms (string apiLevel, int runtimeVersion) + { + string name = string.Format (platformName, apiLevel); + + return Packages.Where (p => p.Name == name && p.Version != runtimeVersion).ToList (); + } + + public List GetOldRuntimesAndPlatforms (string apiLevel, int runtimeVersion) + { + var runtimes = GetOldRuntimes (runtimeVersion); + + runtimes.AddRange (GetOldPlatforms (apiLevel, runtimeVersion)); + + return runtimes; + } + + public bool ContainsPackage (string packageName) + { + return Packages.Any (p => p.Name == packageName); + } + + public AndroidInstalledPackage GetPackage (string packageName) + { + return Packages.Where (x => x.Name == packageName).SingleOrDefault (); + } + } +} \ No newline at end of file diff --git a/src/Xamarin.AndroidTools/Devices/AndroidPackageListExtensions.cs b/src/Xamarin.AndroidTools/Devices/AndroidPackageListExtensions.cs new file mode 100644 index 00000000000..c95a70c7ded --- /dev/null +++ b/src/Xamarin.AndroidTools/Devices/AndroidPackageListExtensions.cs @@ -0,0 +1,101 @@ +// +// AndroidPackageListExtensions.cs +// +// Author: +// Michael Hutchinson +// +// Copyright (c) 2012 Xamarin Inc. (http://xamarin.com) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +using System.Collections.Generic; +using System.Linq; +using Mono.AndroidTools; + +namespace Xamarin.AndroidTools +{ + public static class AndroidPackageListExtensions + { + internal static readonly string runtimeName = "Mono.Android.DebugRuntime"; + internal static readonly string oldRuntimeName = "com.novell.monodroid.runtimeservice"; + internal static readonly string platformName = "Mono.Android.Platform.ApiLevel_{0}"; + + public static bool IsCurrentRuntimeInstalled (this IList packages) + { + var version = MonoDroidSdk.SharedRuntimeVersion; + return packages.Any (p => p.Name == runtimeName && p.Version == version); + } + + public static bool IsUnknownRuntimeInstalled (this IList packages) + { + return packages.Any (p => p.Name == runtimeName && p.Version == int.MaxValue); + } + + public static IEnumerable GetOldRuntimes (this IList packages) + { + var version = MonoDroidSdk.SharedRuntimeVersion; + return packages.Where (p => (p.Name == runtimeName && p.Version != version) || p.Name == oldRuntimeName); + } + + public static bool IsCurrentPlatformInstalled (this IList packages, int apiLevel) + { + string name = string.Format (platformName, apiLevel); + var version = PlatformPackage.GetPlatformPackageVersion (apiLevel, ref name); + + return packages.Any (p => p.Name == name && p.Version == version); + } + + public static bool IsUnknownPlatformInstalled (this IList packages, int apiLevel) + { + string name = string.Format (platformName, apiLevel); + + return packages.Any (p => p.Name == name && p.Version == int.MaxValue); + } + + public static bool AreCurrentRuntimeAndPlatformInstalled (this IList packages, int apiLevel) + { + return packages.IsCurrentRuntimeInstalled () && packages.IsCurrentPlatformInstalled (apiLevel); + } + + // Hopefully they don't have multiple old + // platforms installed, but just in case... + public static List GetOldPlatforms (this IList packages, int apiLevel) + { + string name = string.Format (platformName, apiLevel); + int version = PlatformPackage.GetPlatformPackageVersion (apiLevel, ref name); + + return packages.Where (p => p.Name == name && p.Version != version).ToList (); + } + + public static IEnumerable GetOldRuntimesAndPlatforms (this IList packages, int apiLevel) + { + return packages.GetOldRuntimes ().Concat (packages.GetOldPlatforms (apiLevel)); + } + + public static bool ContainsPackage (this IList packages, string packageName) + { + return packages.Any (p => p.Name == packageName); + } + + public static AndroidInstalledPackage GetPackage (this IList packages, string packageName) + { + return packages.SingleOrDefault (x => x.Name == packageName); + } + } +} diff --git a/src/Xamarin.AndroidTools/IProgressNotifier.cs b/src/Xamarin.AndroidTools/IProgressNotifier.cs new file mode 100644 index 00000000000..073fe6dfe26 --- /dev/null +++ b/src/Xamarin.AndroidTools/IProgressNotifier.cs @@ -0,0 +1,24 @@ +// +// IProgressNotifier.cs +// +// Authors: +// Jonathan Pobst +// +// Copyright 2012 Xamarin Inc. All rights reserved. +// + +using System; + +namespace Xamarin.AndroidTools +{ + public interface IProgressNotifier + { + void BeginStep (string step); + void EndStep (string step); + + void ReportMessage (string message); + void ShowErrorDialog (string title, string message); + void ShowErrorDialog (string title, string message, Exception ex); + void ReportProgress (long copiedBytes, long totalBytes); + } +} diff --git a/src/Xamarin.AndroidTools/MonoDroidSdk.cs b/src/Xamarin.AndroidTools/MonoDroidSdk.cs new file mode 100644 index 00000000000..bd87a3c5972 --- /dev/null +++ b/src/Xamarin.AndroidTools/MonoDroidSdk.cs @@ -0,0 +1,376 @@ +// +// MonoDroidSdk.cs +// +// Author: +// Michael Hutchinson +// Andreia Gaita +// +// Copyright (c) 2012 Xamarin Inc +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using Mono.AndroidTools; +using System.Threading.Tasks; +using System.Diagnostics; +using System.Threading; + +using Xamarin.Android.Tools; + +namespace Xamarin.AndroidTools +{ + public class MonoDroidSdk + { + #region Obsolete + static MonoDroidSdk oldSdk; + + [Obsolete("Use static MonoDroidSdk members")] + public MonoDroidSdk (string mfaSdkPath = null, string mfaFrameworkPath = null) + { + } + + [Obsolete("Use static MonoDroidSdk members")] + public static MonoDroidSdk Sdk { + get { + if (oldSdk == null) + oldSdk = new MonoDroidSdk (); + return oldSdk; + } + } + + [Obsolete("Use MonoDroidSdk.SdkPath")] + public string MonoAndroidSdkPath { get { return MonoDroidSdk.SdkPath; } } + + [Obsolete("Use MonoDroidSdk.BinPath")] + public string MonoAndroidBinPath { get { return MonoDroidSdk.BinPath; } } + + [Obsolete("Use MonoDroidSdk.RuntimePath")] + public string MonoAndroidRuntimePath { get { return MonoDroidSdk.RuntimePath; } } + + [Obsolete("Use MonoDroidSdk.FrameworkPath")] + public string MonoAndroidFrameworkPath { get { return MonoDroidSdk.FrameworkPath; } } + + [Obsolete("Use MonoDroidSdk.GeneratorTool")] + public string GeneratorExe { + get { return Path.Combine (MonoAndroidBinPath, Generator); } + } + + [Obsolete ("Use MonoDroidSdk.SharedRuntimeVersion")] + public int GetCurrentSharedRuntimeVersion () + { + return MonoDroidSdk.SharedRuntimeVersion; + } + + [Obsolete ("Use MonoDroidSdk.SharedRuntimeVersion")] + public int CurrentRuntimeVersion { get { return MonoDroidSdk.SharedRuntimeVersion; } } + + [Obsolete("Do not use.")] + public static string Generator; + + [Obsolete ("Do not use.")] + public static string Javac; + + #endregion + + static MonoDroidSdkBase sdk; + + static MonoDroidSdk () + { + #pragma warning disable 618 + Javac = OS.IsWindows ? "javac.exe" : "javac"; + Generator = "generator.exe"; + #pragma warning restore 618 + } + + static MonoDroidSdkBase GetSdk () + { + if (sdk == null) { + Refresh (); + } + return sdk; + } + + public static void Refresh (string mfaSdkPath, string mfaFrameworkPath) + { + Refresh (runtimePath:mfaSdkPath, binPath:mfaSdkPath, bclPath:mfaFrameworkPath); + } + + public static void Refresh (string runtimePath = null, string binPath = null, string bclPath = null) + { + if (OS.IsWindows) { + sdk = new MonoDroidSdkWindows (); + } else { + sdk = new MonoDroidSdkUnix (); + } + + try { + sdk.Initialize (runtimePath, binPath, bclPath); + var v = LoadVersionInfo (); + if (v != null) { + VersionString = v.Item1; + Version = v.Item2; + if (!string.IsNullOrEmpty (v.Item3)) + VersionString += "." + v.Item3; + } + else { + AndroidLogger.LogWarning (Properties.Resources.XA5300_MonoDroidSdk_XA_Version); + VersionString = String.Empty; + Version = null; + sdk.Reset (); + } + } catch (Exception ex) { + AndroidLogger.LogError (Properties.Resources.XA5300_MonoDroidSdk_Refresh_Exception, ex.ToString ()); + } + } + + /// + /// Refreshes the SDK information if a different version of XA is installed + /// + public static void RefreshIfSdkChanged () + { + var v = LoadVersionInfo (); + var vs =v?.Item1; + if (v?.Item3 != null) { + vs += "." + v.Item3; + } + if (v != null && VersionString == vs && Version == v.Item2) { + return; + } + Refresh (); + } + + [Obsolete ("Do not use.")] + public static string AdbTool { + get { return AndroidSdk.AdbExe; } + } + + [Obsolete ("Do not use.")] + public static string SdkPath { get { return GetSdk ().SdkPath; } } + public static string BinPath { get { return GetSdk ().BinPath; } } + public static string IncludePath { get { return GetSdk ().IncludePath; } } + public static string RuntimePath { get { return GetSdk ().RuntimePath; } } + public static string FrameworkPath { get { return GetSdk ().BclPath; } } + public static string LibrariesPath { get { return GetSdk ().LibrariesPath; } } + public static int SharedRuntimeVersion { get { return GetSdk ().SharedRuntimeVersion; } } + public static bool IsInstalled { get { return !string.IsNullOrEmpty (GetSdk ().BinPath); } } + + public static AndroidVersions AndroidVersions { get { return GetSdk ().AndroidVersions; } } + + static readonly string[] sharedRuntimeAbis = new[] { "arm64-v8a", "armeabi-v7a", "x86", "x86_64" }; + + public static string[] SharedRuntimeAbis { + get { + return sharedRuntimeAbis; + } + } + static readonly string[] sharedRuntimeAndBundleAbis = new [] { "arm64-v8a", "armeabi-v7a", "x86" }; + + public static string[] SharedRuntimeAndBundleAbis { + get { + return sharedRuntimeAndBundleAbis; + } + } + + public static string DefaultAbi { + get { + return "armeabi-v7a"; + } + } + + [Obsolete ("Do not use.")] + public static string GeneratorToolExe { + get { + #pragma warning disable 618 + return Path.Combine (RuntimePath, "generator.exe"); + #pragma warning restore 618 + } + } + + [Obsolete ("Do not use.")] + public static string JavaDocToMDocExe { + get { return Path.Combine (RuntimePath, "javadoc-to-mdoc.exe"); } + } + + [Obsolete ("Do not use.")] + public static string MDocExe { + get { return Path.Combine (RuntimePath, "mdoc.exe"); } + } + + [Obsolete ("Please use PlatformPackage.GetPlatformPackagePath")] + public static string GetPlatformRuntimePackage (int apiLevel) + { + return Path.Combine (RuntimePath, "platforms", "android-" + apiLevel, "Mono.Android.Platform.apk"); + } + + [Obsolete ("Please use PlatformPackage.GetPlatformPackageVersion")] + public static int GetPlatformRuntimePackageVersion (int apiLevel) + { + string manifest = Path.Combine (RuntimePath, "platforms", "android-" + apiLevel, "Mono.Android.Platform.xml"); + + return MonoDroidSdkBase.GetManifestVersion (manifest); + } + + public static string GetSharedRuntimePackage (bool debug = true, string arch = "") + { + if (debug) { + arch = ""; + } + + string packageName; + switch (arch) { + case "armeabi-v7a": + case "x86": + packageName = string.Format ("Mono.Android.DebugRuntime-{0}.apk", arch); + break; + default: + packageName = "Mono.Android.DebugRuntime-debug.apk"; + break; + } + return Path.Combine (RuntimePath, packageName); + } + + /// + /// Gets all the api levels we are currently supporting (shipping assemblies for) + /// + /// + public static int[] SupportedApiLevels { + get { return GetSupportedApiLevels ().ToArray (); } + } + + static IEnumerable GetSupportedApiLevels () + { + foreach (var apiLevel in GetSdk ().GetSupportedApiLevels ()) { + int value; + if (int.TryParse (apiLevel, out value)) + yield return value; + } + } + + [Obsolete ("Do not use.")] + public static string GetPlatformNativeLibPath (string abi) + { + return GetSdk ().GetPlatformNativeLibPath (abi); + } + + [Obsolete ("Do not use.")] + public static string GetPlatformNativeLibPath (AndroidTargetArch arch) + { + return GetSdk ().GetPlatformNativeLibPath (arch); + } + + public static Version Version { get; private set; } + public static string VersionString { get; private set; } + + static Tuple LoadVersionInfo () + { + try { + var sdk = GetSdk (); + + string versionFile = sdk.FindVersionFile (); + if (versionFile == null) { +#pragma warning disable 0618 + AndroidLogger.LogInfo (null, $"Did not find Xamarin.Android at path {sdk.SdkPath}"); +#pragma warning restore 0618 + return null; + } + + var str = File.ReadAllText (versionFile).Trim (); + var version = ParseVersion (str); + if (version < new Version (4, 8)) { + AndroidLogger.LogInfo (null, "Xamarin.Android version {0} is too old", str); + return null; + } + + var versionRev = string.Empty; + var versionRevisionFile = Path.GetDirectoryName (versionFile); + versionRevisionFile = Path.Combine (versionRevisionFile, "Version.rev"); + if (File.Exists (versionRevisionFile)) { + versionRev = File.ReadAllText (versionRevisionFile).Trim (); + } + + AndroidLogger.LogInfo (null, "Found Xamarin.Android {0}.{1}", str, versionRev); + return Tuple.Create (str, version, versionRev); + } catch (Exception ex) { + AndroidLogger.LogError (Properties.Resources.XA5300_MonoDroidSdk_Refresh_Exception, ex.ToString ()); + return null; + } + } + + internal static Version ParseVersion (string versionString) + { + // More accepting than Version.Parse. Only care about first 3 parts. + var split = versionString.Trim ().Split ('.'); + int major = int.Parse (split [0]); + int minor = split.Length > 1 ? int.Parse (split [1]) : 0; + int build = split.Length > 2 ? int.Parse (split [2]) : 0; + return new Version (major, minor, build); + } + + public static bool SupportsSplitApk { + get { + return Version >= new Version (4, 14); + } + } + + public static bool SupportsAot { + get { + return Version >= new Version (5, 0, 99); + } + } + + public static bool SupportsMultiDex { + get { + return Version >= new Version (5, 0, 99); + } + } + + public static bool SupportsProguard { + get { + return Version >= new Version (5, 0, 99); + } + } + + public static bool SupportsArm64 { + get { + return Version >= new Version (5, 0, 99); + } + } + + public static string GetApiLevelForFrameworkVersion (string framework) + { + return GetSdk ().GetApiLevelForFrameworkVersion (framework); + } + + public static string GetFrameworkVersionForApiLevel (string apiLevel) + { + return GetSdk ().GetFrameworkVersionForApiLevel (apiLevel); + } + + /// + /// Determines if the given apiLevel is supported by an installed Framework + /// + public static bool IsSupportedFrameworkLevel (string apiLevel) + { + return GetSdk ().IsSupportedFrameworkLevel (apiLevel); + } + } +} diff --git a/src/Xamarin.AndroidTools/OS.cs b/src/Xamarin.AndroidTools/OS.cs new file mode 100644 index 00000000000..d275e2842be --- /dev/null +++ b/src/Xamarin.AndroidTools/OS.cs @@ -0,0 +1,252 @@ +// +// OS.cs +// +// Author: +// Andreia Gaita +// +// Copyright (c) 2012 Xamarin Inc. (http://xamarin.com) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +using System; +using System.Runtime.InteropServices; +using System.IO; +using System.Text; + +namespace Xamarin.AndroidTools +{ + public static class OS + { + public readonly static bool IsWindows; + public readonly static bool IsMac; + + internal readonly static string ProgramFilesX86; + + static OS () + { + IsWindows = Path.DirectorySeparatorChar == '\\'; + IsMac = !IsWindows && IsRunningOnMac (); + + if (IsWindows) { + ProgramFilesX86 = GetProgramFilesX86 (); + } + } + + //From Managed.Windows.Forms/XplatUI + static bool IsRunningOnMac () + { + IntPtr buf = IntPtr.Zero; + try { + buf = Marshal.AllocHGlobal (8192); + // This is a hacktastic way of getting sysname from uname () + if (uname (buf) == 0) { + string os = System.Runtime.InteropServices.Marshal.PtrToStringAnsi (buf); + if (os == "Darwin") + return true; + } + } catch { + } finally { + if (buf != IntPtr.Zero) + System.Runtime.InteropServices.Marshal.FreeHGlobal (buf); + } + return false; + } + + [DllImport ("libc")] + static extern int uname (IntPtr buf); + + static string GetProgramFilesX86 () + { + //SpecialFolder.ProgramFilesX86 is broken on 32-bit WinXP + if (IntPtr.Size == 8) { + return Environment.GetFolderPath (Environment.SpecialFolder.ProgramFilesX86); + } else { + return Environment.GetFolderPath (Environment.SpecialFolder.ProgramFiles); + } + } + + internal static string GetXamarinAndroidCacheDir () + { + if (IsMac) { + var home = Environment.GetFolderPath (Environment.SpecialFolder.UserProfile); + return Path.Combine (home, "Library", "Caches", "Xamarin.Android"); + } else if (IsWindows) { + var localAppData = Environment.GetFolderPath (Environment.SpecialFolder.LocalApplicationData); + return Path.Combine (localAppData, "Xamarin.Android", "Cache"); + } else { + var home = Environment.GetFolderPath (Environment.SpecialFolder.UserProfile); + var xdgCacheHome = Environment.GetEnvironmentVariable ("XDG_CACHE_HOME"); + if (string.IsNullOrEmpty (xdgCacheHome)) { + xdgCacheHome = Path.Combine (home, ".cache"); + } + return Path.Combine (xdgCacheHome, "Xamarin.Android"); + } + } + } + + public static class KernelEx { + [DllImport ("kernel32.dll", CharSet = CharSet.Auto)] + static extern int GetLongPathName ( + [MarshalAs (UnmanagedType.LPTStr)] string path, + [MarshalAs (UnmanagedType.LPTStr)] StringBuilder longPath, + int longPathLength + ); + + public static string GetLongPathName (string path) + { + StringBuilder sb = new StringBuilder (255); + GetLongPathName (path, sb, sb.Capacity); + return sb.ToString (); + } + + [DllImport ("kernel32.dll", CharSet = CharSet.Auto)] + static extern int GetShortPathName ( + [MarshalAs (UnmanagedType.LPTStr)] string path, + [MarshalAs (UnmanagedType.LPTStr)] StringBuilder shortPath, + int shortPathLength + ); + + public static string GetShortPathName (string path) + { + StringBuilder sb = new StringBuilder (255); + GetShortPathName (path, sb, sb.Capacity); + return sb.ToString (); + } + } + + internal static class RegistryEx + { + const string ADVAPI = "advapi32.dll"; + + public static UIntPtr CurrentUser = (UIntPtr)0x80000001; + public static UIntPtr LocalMachine = (UIntPtr)0x80000002; + + [DllImport (ADVAPI, CharSet = CharSet.Unicode, SetLastError = true)] + static extern int RegOpenKeyEx (UIntPtr hKey, string subKey, uint reserved, uint sam, out UIntPtr phkResult); + + [DllImport (ADVAPI, CharSet = CharSet.Unicode, SetLastError = true)] + static extern int RegQueryValueExW (UIntPtr hKey, string lpValueName, int lpReserved, out uint lpType, + StringBuilder lpData, ref uint lpcbData); + + [DllImport (ADVAPI, CharSet = CharSet.Unicode, SetLastError = true)] + static extern int RegSetValueExW (UIntPtr hKey, string lpValueName, int lpReserved, + uint dwType, string data, uint cbData); + + [DllImport (ADVAPI, CharSet = CharSet.Unicode, SetLastError = true)] + static extern int RegSetValueExW (UIntPtr hKey, string lpValueName, int lpReserved, + uint dwType, IntPtr data, uint cbData); + + [DllImport (ADVAPI, CharSet = CharSet.Unicode, SetLastError = true)] + static extern int RegCreateKeyEx (UIntPtr hKey, string subKey, uint reserved, string @class, uint options, + uint samDesired, IntPtr lpSecurityAttributes, out UIntPtr phkResult, out Disposition lpdwDisposition); + + [DllImport ("advapi32.dll", SetLastError = true)] + static extern int RegCloseKey (UIntPtr hKey); + + public static string GetValueString (UIntPtr key, string subkey, string valueName, Wow64 wow64) + { + UIntPtr regKeyHandle; + uint sam = (uint)Rights.QueryValue + (uint)wow64; + if (RegOpenKeyEx (key, subkey, 0, sam, out regKeyHandle) != 0) + return null; + + try { + uint type; + var sb = new StringBuilder (2048); + uint cbData = (uint) sb.Capacity; + if (RegQueryValueExW (regKeyHandle, valueName, 0, out type, sb, ref cbData) == 0) { + return sb.ToString (); + } + return null; + } finally { + RegCloseKey (regKeyHandle); + } + } + + public static void SetValueString (UIntPtr key, string subkey, string valueName, string value, Wow64 wow64) + { + UIntPtr regKeyHandle; + uint sam = (uint)(Rights.CreateSubKey | Rights.SetValue) + (uint)wow64; + uint options = (uint) Options.NonVolatile; + Disposition disposition; + if (RegCreateKeyEx (key, subkey, 0, null, options, sam, IntPtr.Zero, out regKeyHandle, out disposition) != 0) { + throw new Exception ("Could not open or create key"); + } + + try { + uint type = (uint)ValueType.String; + uint lenBytesPlusNull = ((uint)value.Length + 1) * 2; + var result = RegSetValueExW (regKeyHandle, valueName, 0, type, value, lenBytesPlusNull); + if (result != 0) + throw new Exception (string.Format ("Error {0} setting registry '{1}{2}' with value '{3}'='{4}'", + result, key, subkey, valueName, value)); + } finally { + RegCloseKey (regKeyHandle); + } + } + + [Flags] + enum Rights : uint + { + None = 0, + QueryValue = 0x0001, + SetValue = 0x0002, + CreateSubKey = 0x0004, + EnumerateSubKey = 0x0008, + } + + enum Options + { + BackupRestore = 0x00000004, + CreateLink = 0x00000002, + NonVolatile = 0x00000000, + Volatile = 0x00000001, + } + + public enum Wow64 : uint + { + Key64 = 0x0100, + Key32 = 0x0200, + } + + enum ValueType : uint + { + None = 0, //REG_NONE + String = 1, //REG_SZ + UnexpandedString = 2, //REG_EXPAND_SZ + Binary = 3, //REG_BINARY + DWord = 4, //REG_DWORD + DWordLittleEndian = 4, //REG_DWORD_LITTLE_ENDIAN + DWordBigEndian = 5, //REG_DWORD_BIG_ENDIAN + Link = 6, //REG_LINK + MultiString = 7, //REG_MULTI_SZ + ResourceList = 8, //REG_RESOURCE_LIST + FullResourceDescriptor = 9, //REG_FULL_RESOURCE_DESCRIPTOR + ResourceRequirementsList = 10, //REG_RESOURCE_REQUIREMENTS_LIST + QWord = 11, //REG_QWORD + QWordLittleEndian = 11, //REG_QWORD_LITTLE_ENDIAN + } + + enum Disposition : uint + { + CreatedNewKey = 0x00000001, + OpenedExistingKey = 0x00000002, + } + } +} diff --git a/src/Xamarin.AndroidTools/PlatformPackage.cs b/src/Xamarin.AndroidTools/PlatformPackage.cs new file mode 100644 index 00000000000..fbbdeb3c4fc --- /dev/null +++ b/src/Xamarin.AndroidTools/PlatformPackage.cs @@ -0,0 +1,378 @@ +// +// PlatformPackage.cs +// +// Author: +// Jonathan Pryor +// +// Copyright (c) 2014 Xamarin Inc +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +using System; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using System.Xml.Linq; +using Xamarin.AndroidTools.PublicationUtilities; + +namespace Xamarin.AndroidTools { + + public static class PlatformPackage { + + [Obsolete ("Use GetPlatformPackageVersion(int, ref string)")] + public static int GetPlatformPackageVersion (int apiLevel) + { + string packageName = null; + return GetPlatformPackageVersion (apiLevel, ref packageName); + } + + public static int GetPlatformPackageVersion (int apiLevel, ref string packageName) + { + // If this throws an ArgumentNullException from Path.Combine(), it's because MonoDroidSdk.RuntimePath is null + // To fix, either: + // 1. Provide a `runtimePath` value to `MonoDroidSdk.Refresh()` before calling this method, or + // 2. Export the `$MONO_ANDROID_PATH` environment variable to an appropriate `runtimePath` value. + string manifest = Path.Combine (MonoDroidSdk.RuntimePath, "platforms", "android-" + apiLevel, "Mono.Android.Platform.xml"); + if (File.Exists (manifest)) + return MonoDroidSdkBase.GetManifestVersion (manifest); + + string frameworkVersion = MonoDroidSdk.GetFrameworkVersionForApiLevel (apiLevel.ToString ()); + + return GetVersionInfo (frameworkVersion); + } + + internal static Version ToVersion (string frameworkDir) + { + string version = Path.GetFileName (frameworkDir); + if (!version.StartsWith ("v", StringComparison.OrdinalIgnoreCase)) { + // wat? + return new Version (); + } + version = version.Substring (1); + Version v; + if (Version.TryParse (version, out v)) + return v; + return new Version (); + } + + static int GetVersionInfo (string frameworkVersion) + { + string bclDir = MonoDroidSdk.FrameworkPath; + string frameworksDir = Path.GetDirectoryName (bclDir); + + string platform = Path.Combine (frameworksDir, frameworkVersion, "Mono.Android.dll"); + var platformTime = File.GetLastWriteTimeUtc (platform); + var unixEpoch = new DateTime (1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); + return (int) (platformTime - unixEpoch).TotalSeconds; + } + + [Obsolete ("Use GetPlatformPackagePathAsync")] + public static string GetPlatformPackagePath (int apiLevel, string aaptPath, IProgressNotifier progressReporter, CancellationToken token) + { + var task = GetPlatformPackagePathAsync (apiLevel, aaptPath, progressReporter, token); + task.Wait (token); + return task.Result; + } + + public static async Task GetPlatformPackagePathAsync (int apiLevel, string aaptPath, IProgressNotifier progressReporter, CancellationToken token) + { + string path = Path.Combine (MonoDroidSdk.RuntimePath, "platforms", "android-" + apiLevel, "Mono.Android.Platform.apk"); + if (File.Exists (path)) + return path; + + string cacheDir = OS.GetXamarinAndroidCacheDir (); + string manifest = Path.Combine (cacheDir, string.Format ("Mono.Android.Platform.ApiLevel_{0}.xml", apiLevel)); + string apkName = string.Format ("Mono.Android.Platform.ApiLevel_{0}.apk", apiLevel); + + path = Path.Combine (cacheDir, apkName); + + string frameworkVersion = MonoDroidSdk.GetFrameworkVersionForApiLevel (apiLevel.ToString ()); + + int version = GetVersionInfo (frameworkVersion); + + if (File.Exists (manifest) && File.Exists (path)) { + int curVersion = MonoDroidSdkBase.GetManifestVersion (manifest); + if (version == curVersion) + return path; + } + + aaptPath = aaptPath ?? AndroidSdk.GetAaptPath (); + + if (aaptPath == null) + throw new ArgumentNullException ("aaptPath", "'aaptPath' is null and no pre-built Platform.apk exists!"); + if (!File.Exists (aaptPath)) + throw new ArgumentException ("Could not find `aapt` and no pre-built Platform.apk exists.", "aaptPath"); + + ReportBeginStep (progressReporter, "Creating " + path); + + string packageDir = Path.Combine (Path.GetTempPath (), Path.GetRandomFileName ()); + string resourceDir = Path.Combine (packageDir, "r"); + string tmanifest = Path.Combine (packageDir, "AndroidManifest.xml"); + string tpath = Path.Combine (packageDir, apkName); + + Directory.CreateDirectory (packageDir); + try { + CopyAssemblies (frameworkVersion, Path.Combine (resourceDir, "assemblies"), progressReporter, token); + CreateAndroidManifest (apiLevel, version, frameworkVersion, tmanifest, progressReporter, token); + string unaligned = Aapt (aaptPath, tmanifest, resourceDir, packageDir, progressReporter, token); + string unsigned = Path.Combine (packageDir, "unsigned.apk"); + Zipalign (unaligned, unsigned, progressReporter, token); + await ApkSigner (unsigned, tpath, progressReporter, token); + + Directory.CreateDirectory (cacheDir); + File.Copy (tpath, path, overwrite: true); + File.Copy (tmanifest, manifest, overwrite: true); + } finally { + ReportMessage (progressReporter, "Removing temporary directory: {0}", packageDir); + Directory.Delete (packageDir, recursive:true); + ReportEndStep (progressReporter, "Creating " + path); + } + return path; + } + + static void CopyAssemblies (string frameworkVersion, string resourceDir, IProgressNotifier progressReporter, CancellationToken token) + { + ReportBeginStep (progressReporter, "Copying platform assemblies..."); + Directory.CreateDirectory (resourceDir); + string bclDir = MonoDroidSdk.FrameworkPath; + string frameworksDir = Path.GetDirectoryName (bclDir); + string lastDir = null; + foreach (var frameworkDir in Directory.EnumerateDirectories (frameworksDir).OrderBy (ToVersion)) { + lastDir = frameworkDir; + string version = Path.GetFileName (frameworkDir); + if (version == Path.GetFileName (MonoDroidSdk.FrameworkPath)) { + // BCL assemblies aren't part of the Platform Package. + continue; + } + foreach (var pattern in new string [] { "*.dll*", "*.pdb" }) { + foreach (var assembly in Directory.EnumerateFiles (frameworkDir, pattern)) { + token.ThrowIfCancellationRequested (); + string file = Path.GetFileName (assembly); + if (file.StartsWith ("Mono.Android", StringComparison.OrdinalIgnoreCase) && + !file.StartsWith ("Mono.Android.Export", StringComparison.OrdinalIgnoreCase)) + continue; + ReportMessage (progressReporter, "Copying file: {0}", assembly); + File.Copy (assembly, Path.Combine (resourceDir, Path.GetFileName (assembly)), overwrite:true); + } + } + if (version == frameworkVersion) + break; + } + foreach (var lib in Directory.EnumerateFiles (lastDir, "Mono.Android.*")) { + if (Path.GetExtension (lib) == ".xml") + continue; + token.ThrowIfCancellationRequested (); + ReportMessage (progressReporter, "Copying file: {0}", lib); + File.Copy (lib, Path.Combine (resourceDir, Path.GetFileName (lib)), overwrite:true); + } + ReportEndStep (progressReporter, "Copying platform assemblies..."); + } + + static void CreateAndroidManifest (int apiLevel, int version, string frameworkVersion, string androidManifest, IProgressNotifier progressReporter, CancellationToken token) + { + var nsAndroid = XNamespace.Get ("http://schemas.android.com/apk/res/android"); + var doc = new XDocument ( + new XDeclaration ("1.0", "UTF-8", null), + new XElement ("manifest", + new XAttribute (XNamespace.Xmlns + "android", nsAndroid), + new XAttribute ("package", "Mono.Android.Platform.ApiLevel_" + apiLevel), + new XAttribute (nsAndroid + "installLocation", "auto"), + new XAttribute (nsAndroid + "versionCode", version), + new XAttribute (nsAndroid + "versionName", frameworkVersion), + new XElement ("uses-sdk", + new XAttribute (nsAndroid + "minSdkVersion", 4), + new XAttribute (nsAndroid + "targetSdkVersion", apiLevel)), + new XElement ("application", + new XAttribute (nsAndroid + "label", string.Format ("Xamarin.Android API-{0} Support", apiLevel)), + new XAttribute (nsAndroid + "hasCode", "false")))); + ReportMessage (progressReporter, "Creating: {0}", androidManifest); + var utf8 = new UTF8Encoding (encoderShouldEmitUTF8Identifier:false); + using (var o = new StreamWriter (androidManifest, append:false, encoding:utf8)) { + o.NewLine = "\n"; + doc.Save (o); + } + token.ThrowIfCancellationRequested (); + } + + static string Aapt (string aapt, string androidManifest, string resourceDir, string outDir, IProgressNotifier progressReporter, CancellationToken token) + { + // /opt/android/sdk/build-tools/18.0.0/aapt package -f -0 .dll -0 .mdb -M AndroidManifest.xml -I /opt/android/sdk/platforms/android-8/android.jar -F unsigned.apk -k r + string apk = Path.Combine (outDir, "unaligned.apk"); + var arguments = string.Format ("package -f -0 .dll -0 .mdb -M \"{0}\" -I \"{1}\" -F \"{2}\" -k \"{3}\"", + androidManifest, Path.Combine (AndroidSdk.GetLatestPlatformDirectory (), "android.jar"), apk, resourceDir); + var psi = new ProcessStartInfo (aapt, arguments); + ReportMessage (progressReporter, "Creating: {0}", apk); + Exec ("Aapt", psi, progressReporter, token); + return apk; + } + + static void Exec (string step, ProcessStartInfo psi, IProgressNotifier progressReporter, CancellationToken token) + { + ReportMessage (progressReporter, "Executing: {0} {1}", psi.FileName, psi.Arguments); + + TextWriter stdout = Console.Out; + TextWriter stderr = Console.Error; + bool disposeStdout = false; + if (progressReporter != null) { + stdout = new ProgressTextWriter (progressReporter, step); + stderr = stdout; + disposeStdout = true; + } + + stderr = new TeeTextWriter (stderr); + + try { + int r = ProcessUtils.StartProcess (psi, stdout, stderr, token).Result; + ReportMessage (progressReporter, "{0} exited with value: {1}", psi.FileName, r); + + if (r != 0) + throw new InvalidOperationException (string.Format ("'{0}' exited with code '{1}': {2}", psi.FileName, r, stderr.ToString ())); + } finally { + if (disposeStdout) + stdout.Dispose (); + } + } + + static Task ApkSigner (string unsigned, string signed, IProgressNotifier progressReporter, CancellationToken token) + { + // See monodroid/tools/msbuild/Tasks/GetAppSettingsDirectory.cs!Execute() + var keystore = Path.Combine ( + Environment.GetFolderPath (Environment.SpecialFolder.LocalApplicationData), + "Xamarin", + "Mono for Android", + "debug.keystore"); + ReportMessage (progressReporter, "Creating: {0}", signed); + var options = new AndroidSigningOptions { + KeyStore = keystore, + KeyAlias = "androiddebugkey", + KeyPass = "android", + StorePass = "android", + }; + return PackageSigningTasks.SignPackageWithApkSignerAsync (options, unsigned, signed, token, AndroidSdk.ApkSignerJar, + result => ReportMessage (progressReporter, result)); + } + + static void Zipalign (string unaligned, string packageFile, IProgressNotifier progressReporter, CancellationToken token) + { + // /opt/android/sdk/tools/zipalign 4 unaligned.apk Xamarin.Android.Platform.apk + var arguments = string.Format ("4 \"{0}\" \"{1}\"", + unaligned, packageFile); + var psi = new ProcessStartInfo (AndroidSdk.ZipAlignExe, arguments); + ReportMessage (progressReporter, "Creating: {0}", packageFile); + Exec ("zipalign", psi, progressReporter, token); + } + + internal static void ReportBeginStep (IProgressNotifier progressReporter, string step) + { + if (progressReporter != null) { + progressReporter.BeginStep (step); + progressReporter.ReportMessage (step); + } + } + + internal static void ReportEndStep (IProgressNotifier progressReporter, string step) + { + if (progressReporter != null) + progressReporter.EndStep (step); + } + + internal static void ReportMessage (IProgressNotifier progressReporter, string format, params object[] args) + { + if (progressReporter != null) { + progressReporter.ReportMessage (string.Format (format, args)); + } + } + } + + class ProgressTextWriter : TextWriter { + + public ProgressTextWriter (IProgressNotifier progressRepoter, string step) + { + ProgressReporter = progressRepoter; + Step = step; + + ProgressReporter.BeginStep (step); + } + + public IProgressNotifier ProgressReporter { get; private set; } + public string Step { get; private set; } + + public override Encoding Encoding { + get { return Encoding.Default; } + } + + StringBuilder message = new StringBuilder (); + + public override void Write (char value) + { + if (value == '\r' || value == '\n') { + if (message.Length > 0) + ProgressReporter.ReportMessage (message.ToString ()); + message.Clear (); + return; + } + message.Append (value); + } + + public override void Write (string value) + { + ProgressReporter.ReportMessage (value); + } + + protected override void Dispose (bool disposing) + { + if (Step == null) + return; + ProgressReporter.EndStep (Step); + Step = null; + base.Dispose (disposing); + } + } + + class TeeTextWriter : StringWriter { + + public TeeTextWriter (TextWriter output) + { + Output = output; + } + + public TextWriter Output { get; private set; } + + public override void Write (char value) + { + base.Write (value); + Output.Write (value); + } + + public override void Write (string value) + { + base.Write (value); + Output.Write (value); + } + + public override void Write (char[] buffer, int index, int count) + { + base.Write (buffer, index, count); + Output.Write (buffer, index, count); + } + } +} diff --git a/src/Xamarin.AndroidTools/ProcessUtils.cs b/src/Xamarin.AndroidTools/ProcessUtils.cs new file mode 100644 index 00000000000..be4005c1a03 --- /dev/null +++ b/src/Xamarin.AndroidTools/ProcessUtils.cs @@ -0,0 +1,137 @@ +// +// ProcessUtils.cs +// +// Author: +// Michael Hutchinson +// Jérémie Laval +// +// Copyright (c) 2012 Xamarin, Inc. + +using System; +using System.IO; +using System.Diagnostics; +using System.Threading; +using System.Threading.Tasks; +using Mono.AndroidTools.Util; + +namespace Xamarin.AndroidTools +{ + public static class ProcessUtils + { + public static async Task StartProcess (ProcessStartInfo psi, TextWriter stdout, TextWriter stderr, CancellationToken cancellationToken, Action onStarted = null) + { + cancellationToken.ThrowIfCancellationRequested (); + psi.CreateNoWindow = true; + psi.WindowStyle = ProcessWindowStyle.Hidden; + psi.UseShellExecute = false; + psi.RedirectStandardOutput |= stdout != null; + psi.RedirectStandardError |= stderr != null; + + var process = new Process { + StartInfo = psi, + EnableRaisingEvents = true, + }; + + Task output = Task.FromResult (true); + Task error = Task.FromResult (true); + Task exit = WaitForExitAsync (process); + using (process) { + process.Start (); + if (onStarted != null) + onStarted (process); + + // If the token is cancelled while we're running, kill the process. + // Otherwise once we finish the Task.WhenAll we can remove this registration + // as there is no longer any need to Kill the process. + // + // We wrap `stdout` and `stderr` in syncronized wrappers for safety in case they + // end up writing to the same buffer, or they are the same object. + using (cancellationToken.Register (() => KillProcess (process))) { + if (psi.RedirectStandardOutput) + output = ReadStreamAsync (process.StandardOutput, TextWriter.Synchronized (stdout)); + + if (psi.RedirectStandardError) + error = ReadStreamAsync (process.StandardError, TextWriter.Synchronized (stderr)); + + await Task.WhenAll (new [] { output, error, exit }).ConfigureAwait (false); + } + // If we invoke 'KillProcess' our output, error and exit tasks will all complete normally. + // To protected against passing the user incomplete data we have to call + // `cancellationToken.ThrowIfCancellationRequested ()` here. + cancellationToken.ThrowIfCancellationRequested (); + return process.ExitCode; + } + } + + static void KillProcess (Process p) + { + try { + p.Kill (); + } catch (InvalidOperationException) { + // If the process has already exited this could happen + } + } + + static Task WaitForExitAsync (Process process) + { + var exitDone = new TaskCompletionSource (); + process.Exited += (o, e) => exitDone.TrySetResult (true); + return exitDone.Task; + } + + static async Task ReadStreamAsync (StreamReader stream, TextWriter destination) + { + int read; + var buffer = new char [4096]; + while ((read = await stream.ReadAsync (buffer, 0, buffer.Length).ConfigureAwait (false)) > 0) + destination.Write (buffer, 0, read); + } + + /// + /// Executes an Android Sdk tool and returns a result. The result is based on a function of the command output. + /// + public static Task ExecuteToolAsync (string exe, ProcessArgumentBuilder args, Func result, CancellationToken token, Action onStarted = null) + { + var tcs = new TaskCompletionSource (); + + var log = new StringWriter (); + var error = new StringWriter (); + + var psi = new ProcessStartInfo (exe, args.ToString ()); + psi.CreateNoWindow = true; + psi.WindowStyle = ProcessWindowStyle.Hidden; + psi.RedirectStandardInput = onStarted != null; + psi.EnvironmentVariables ["JAVA_HOME"] = AndroidSdk.JavaSdkPath; + + var processTask = ProcessUtils.StartProcess (psi, log, error, token, onStarted); + var exeName = Path.GetFileName (exe); + + processTask.ContinueWith (t => { + var output = log.ToString (); + var errorOutput = error.ToString (); + log.Dispose (); + error.Dispose (); + + if (t.IsCanceled) { + tcs.TrySetCanceled (); + return; + } + + if (t.IsFaulted) { + tcs.TrySetException (t.Exception.Flatten ().InnerException); + return; + } + + if (t.Result == 0) { + tcs.TrySetResult (result != null ? result (output) : default (TResult)); + } else { + var errorMessage = !string.IsNullOrEmpty (errorOutput) ? errorOutput : output; + + tcs.TrySetException (new AndroidSdkToolException (string.IsNullOrEmpty (errorMessage) ? exeName + " returned non-zero exit code" : errorMessage, t.Result, errorMessage)); + } + }, TaskContinuationOptions.ExecuteSynchronously); + + return tcs.Task; + } + } +} diff --git a/src/Xamarin.AndroidTools/Properties/Resources.Designer.cs b/src/Xamarin.AndroidTools/Properties/Resources.Designer.cs new file mode 100644 index 00000000000..11f001eefb0 --- /dev/null +++ b/src/Xamarin.AndroidTools/Properties/Resources.Designer.cs @@ -0,0 +1,189 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Xamarin.AndroidTools.Properties { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Xamarin.AndroidTools.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to Alias cannot have characters that are not allowed in file names. + /// + internal static string CreateKeyError_AliasContainsInvalidCharacters { + get { + return ResourceManager.GetString("CreateKeyError_AliasContainsInvalidCharacters", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to There was an error importing the key. + /// + internal static string CreateKeyError_ErrorImportingKey { + get { + return ResourceManager.GetString("CreateKeyError_ErrorImportingKey", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to There was an error creating the key. + /// + internal static string CreateKeyError_GenericError { + get { + return ResourceManager.GetString("CreateKeyError_GenericError", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The keystore was not found. + /// + internal static string CreateKeyError_KeyStoreNotFound { + get { + return ResourceManager.GetString("CreateKeyError_KeyStoreNotFound", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Validity cannot be less than 1 year. + /// + internal static string CreateKeyError_MinimumOneYearValidity { + get { + return ResourceManager.GetString("CreateKeyError_MinimumOneYearValidity", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to KeyStoreManagement has not been initialized. + /// + internal static string CreateKeyError_NotInitialized { + get { + return ResourceManager.GetString("CreateKeyError_NotInitialized", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Unsupported Platform. + /// + internal static string CreateKeyError_UnsupportedPlatform { + get { + return ResourceManager.GetString("CreateKeyError_UnsupportedPlatform", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Unexpected error occurred trying to disconnect Jdwp client.. + /// + internal static string JdwpClientDisconnectError { + get { + return ResourceManager.GetString("JdwpClientDisconnectError", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The Android SDK directory could not be found. Check that the Android SDK Manager in Visual Studio shows a valid installation. To use a custom SDK path for a command line build, set the 'AndroidSdkDirectory' MSBuild property to the custom path.. + /// + internal static string XA5300_Android_SDK { + get { + return ResourceManager.GetString("XA5300_Android_SDK", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to An exception occurred while retrieving the properties of the selected Java SDK installation. Check that the selected Java SDK installation contains a compatible version of Java and that 'java -XshowSettings:properties -version' runs successfully for that installation. Exception: {0}. + /// + internal static string XA5300_AndroidSdk_JdkInfo { + get { + return ResourceManager.GetString("XA5300_AndroidSdk_JdkInfo", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to An exception occurred while finding the Android SDK or Java SDK. Exception: {0}. + /// + internal static string XA5300_AndroidSdk_Refresh_Exception { + get { + return ResourceManager.GetString("XA5300_AndroidSdk_Refresh_Exception", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The Java SDK directory could not be found. Ensure that the Android section of the Visual Studio options has a valid Java SDK directory configured. To use a custom SDK path for a command line build, set the 'JavaSdkDirectory' MSBuild property to the custom path.. + /// + internal static string XA5300_Java_SDK { + get { + return ResourceManager.GetString("XA5300_Java_SDK", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to An exception occurred while validating the .NET for Android installation. Using the Visual Studio installer to repair or reinstall the .NET MAUI component might resolve this problem. Exception: {0}. + /// + internal static string XA5300_MonoDroidSdk_Refresh_Exception { + get { + return ResourceManager.GetString("XA5300_MonoDroidSdk_Refresh_Exception", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Unable to determine the .NET for Android version.. + /// + internal static string XA5300_MonoDroidSdk_XA_Version { + get { + return ResourceManager.GetString("XA5300_MonoDroidSdk_XA_Version", resourceCulture); + } + } + } +} diff --git a/src/Xamarin.AndroidTools/Properties/Resources.cs.resx b/src/Xamarin.AndroidTools/Properties/Resources.cs.resx new file mode 100644 index 00000000000..15717cfd700 --- /dev/null +++ b/src/Xamarin.AndroidTools/Properties/Resources.cs.resx @@ -0,0 +1,168 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Alias nemůže obsahovat znaky, které nejsou v názvech souborů povolené. + + + Při importu klíče došlo k chybě. + + + Při vytváření klíče došlo k chybě. + + + Úložiště klíčů se nenašlo. + + + Platnost nemůže být kratší než 1 rok. + + + KeyStoreManagement se neinicializoval. + + + Nepodporovaná platforma + + + Při pokusu o odpojení klienta Jdwp došlo k neočekávané chybě. + + + Při načítání vlastností vybrané instalace sady Java SDK došlo k výjimce. Zkontrolujte, že vybraná instalace sady Java SDK obsahuje kompatibilní verzi Javy a že pro danou instalaci je možné úspěšně spustit java -XshowSettings:properties -version. Výjimka: {0} + The following terms should not be translated: java -XshowSettings:properties -version +{0} - The exception message of the associated exception + + + Při hledání sady Android SDK nebo Java SDK došlo k výjimce. Výjimka: {0} + {0} - The exception message of the associated exception + + + Nepovedlo se najít adresář sady Android SDK. Zkontrolujte, jestli Správce sady Android SDK v sadě Visual Studio zobrazuje platnou instalaci. Pokud chcete použít vlastní cestu sady SDK pro sestavení z příkazového řádku, nastavte vlastnost AndroidSdkDirectory nástroje MSBuild na danou vlastní cestu. + The following terms should not be translated: AndroidSdkDirectory + + + Nepovedlo se najít adresář sady Java SDK. Zajistěte, aby byl v možnostech sady Visual Studio v části Android nakonfigurovaný platný adresář sady Java SDK. Pokud chcete použít vlastní cestu sady SDK pro sestavení z příkazového řádku, nastavte vlastnost JavaSdkDirectory nástroje MSBuild na danou vlastní cestu. + The following terms should not be translated: JavaSdkDirectory + + + Při ověřování instalace rozhraní .NET pro Android došlo k výjimce. Problém možná půjde vyřešit opravou nebo přeinstalováním součásti .NET MAUI pomocí Instalačního programu pro Visual Studio. Výjimka: {0} + {0} - The exception message of the associated exception + + + Nepovedlo se určit verzi rozhraní .NET pro Android. + + \ No newline at end of file diff --git a/src/Xamarin.AndroidTools/Properties/Resources.de.resx b/src/Xamarin.AndroidTools/Properties/Resources.de.resx new file mode 100644 index 00000000000..f6fccaa5545 --- /dev/null +++ b/src/Xamarin.AndroidTools/Properties/Resources.de.resx @@ -0,0 +1,168 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Der Alias darf keine Zeichen enthalten, die in Dateinamen nicht zulässig sind. + + + Fehler beim Importieren des Schlüssels + + + Beim Erstellen des Schlüssels ist ein Fehler aufgetreten. + + + Der Keystore wurde nicht gefunden. + + + Die Gültigkeit darf nicht weniger als 1 Jahr betragen. + + + KeyStoreManagement wurde nicht initialisiert + + + Nicht unterstützte Plattform + + + Unerwarteter Fehler beim Trennen des Jdwp-Clients. + + + Ausnahme beim Abrufen der Eigenschaften der ausgewählten Java SDK-Installation. Stellen Sie sicher, dass die ausgewählte Java SDK-Installation eine kompatible Version von Java enthält und dass "java -XshowSettings:properties -version" für diese Installation erfolgreich ausgeführt wird. Ausnahme: {0} + The following terms should not be translated: java -XshowSettings:properties -version +{0} - The exception message of the associated exception + + + Ausnahme bei der Suche nach dem Android SDK oder Java SDK. Ausnahme: {0} + {0} - The exception message of the associated exception + + + Das Android SDK-Verzeichnis wurde nicht gefunden. Überprüfen Sie, ob der Android SDK-Manager in Visual Studio eine gültige Installation anzeigt. Legen Sie die MSBuild-Eigenschaft "AndroidSdkDirectory" auf den benutzerdefinierten Pfad fest, um einen benutzerdefinierten SDK-Pfad für einen Befehlszeilenbuild zu verwenden. + The following terms should not be translated: AndroidSdkDirectory + + + Das Java SDK-Verzeichnis wurde nicht gefunden. Überprüfen Sie, ob im Abschnitt "Android" der Visual Studio-Optionen ein gültiges Java SDK-Verzeichnis konfiguriert ist. Legen Sie die MSBuild-Eigenschaft "JavaSdkDirectory" auf den benutzerdefinierten Pfad fest, um einen benutzerdefinierten SDK-Pfad für einen Befehlszeilenbuild zu verwenden. + The following terms should not be translated: JavaSdkDirectory + + + Beim Überprüfen der .NET für Android-Installation ist eine Ausnahme aufgetreten. Dieses Problem kann möglicherweise behoben werden, wenn Sie das Visual Studio-Installationsprogramm verwenden, um die .NET MAUI Komponente zu reparieren oder neu zu installieren. Ausnahme: {0} + {0} - The exception message of the associated exception + + + Die .NET für Android-Version kann nicht ermittelt werden. + + \ No newline at end of file diff --git a/src/Xamarin.AndroidTools/Properties/Resources.es.resx b/src/Xamarin.AndroidTools/Properties/Resources.es.resx new file mode 100644 index 00000000000..a75fc6987e6 --- /dev/null +++ b/src/Xamarin.AndroidTools/Properties/Resources.es.resx @@ -0,0 +1,168 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + El alias no puede tener caracteres no permitidos en los nombres de archivo + + + Error al importar la clave + + + Error al crear la clave + + + No se encontró el almacén de claves + + + La validez no puede ser inferior a 1 año + + + KeyStoreManagement no se ha inicializado + + + Plataforma no admitida + + + Se produjo un error inesperado al intentar desconectar el cliente Jdwp. + + + Se produjo una excepción al recuperar las propiedades de la instalación del SDK de Java que se ha seleccionado. Compruebe que dicha instalación contenga una versión de Java compatible y que "java -XshowSettings:properties -version" se ejecute correctamente para esa instalación. Excepción: {0} + The following terms should not be translated: java -XshowSettings:properties -version +{0} - The exception message of the associated exception + + + Se produjo una excepción al buscar el SDK de Java o Android SDK. Excepción: {0} + {0} - The exception message of the associated exception + + + No se encontró el directorio de Android SDK. Compruebe que el Administrador de Android SDK en Visual Studio muestre una instalación válida. Para usar una ruta de acceso de SDK personalizada para una compilación de línea de comandos, establezca la propiedad "AndroidSdkDirectory" de MSBuild en la ruta de acceso personalizada. + The following terms should not be translated: AndroidSdkDirectory + + + No se encontró el directorio del SDK de Java. Asegúrese de que la sección de Android de las opciones de Visual Studio tenga configurado un directorio válido del SDK de Java. Para usar una ruta de acceso del SDK personalizada para una compilación de línea de comandos, establezca la propiedad "JavaSdkDirectory" de MSBuild en la ruta de acceso personalizada. + The following terms should not be translated: JavaSdkDirectory + + + Se produjo una excepción al validar la instalación de .NET para Android. El uso del instalador de Visual Studio para reparar o reinstalar el componente .NET MAUI podría resolver este problema. Excepción: {0} + {0} - The exception message of the associated exception + + + No se puede determinar la versión de .NET para Android. + + \ No newline at end of file diff --git a/src/Xamarin.AndroidTools/Properties/Resources.fr.resx b/src/Xamarin.AndroidTools/Properties/Resources.fr.resx new file mode 100644 index 00000000000..627e7e8c6b8 --- /dev/null +++ b/src/Xamarin.AndroidTools/Properties/Resources.fr.resx @@ -0,0 +1,168 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + L’alias ne peut pas contenir de caractères non autorisés dans les noms de fichiers + + + Désolé, une erreur s’est produite lors de l’importation du contrôle AI. + + + Désolé, une erreur s’est produite lors de la création de la clé + + + Le magasin de clés est introuvable + + + La validité ne peut pas être inférieure à 1 an + + + KeyStoreManagement n’a pas été initialisé + + + Plateforme non prise en charge + + + Une erreur inattendue s’est produite lors de la tentative de déconnexion du client Jdwp. + + + Une exception s'est produite au moment de la récupération des propriétés de l'installation du kit SDK Java sélectionné. Vérifiez que l'installation du kit SDK Java sélectionné contient une version compatible de Java et que 'java -XshowSettings:properties -version' s'exécute correctement pour cette installation. Exception : {0} + The following terms should not be translated: java -XshowSettings:properties -version +{0} - The exception message of the associated exception + + + Une exception s'est produite durant la recherche du kit Android SDK ou du kit SDK Java. Exception : {0} + {0} - The exception message of the associated exception + + + Le répertoire du kit Android SDK est introuvable. Vérifiez que le Gestionnaire SDK Android de Visual Studio montre que l'installation est valide. Pour utiliser un chemin de kit SDK personnalisé dans une build à partir de la ligne de commande, affectez le chemin personnalisé à la propriété MSBuild 'AndroidSdkDirectory'. + The following terms should not be translated: AndroidSdkDirectory + + + Le répertoire du kit SDK Java est introuvable. Vérifiez que la section Android des options de Visual Studio comporte des informations de configuration valides pour le répertoire du kit SDK Java. Pour utiliser un chemin de kit SDK personnalisé dans une build à partir de la ligne de commande, affectez le chemin personnalisé à la propriété MSBuild 'JavaSdkDirectory'. + The following terms should not be translated: JavaSdkDirectory + + + Une exception s’est produite lors de la validation de l’installation de .NET pour Android. Pour résoudre ce problème, vous pouvez utiliser le programme d’installation de Visual Studio pour réparer ou réinstaller le composant .NET MAUI. Exception : {0} + {0} - The exception message of the associated exception + + + Impossible de déterminer la version .NET pour Android. + + \ No newline at end of file diff --git a/src/Xamarin.AndroidTools/Properties/Resources.it.resx b/src/Xamarin.AndroidTools/Properties/Resources.it.resx new file mode 100644 index 00000000000..319defab38d --- /dev/null +++ b/src/Xamarin.AndroidTools/Properties/Resources.it.resx @@ -0,0 +1,168 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + L'alias non può contenere caratteri non consentiti nei nomi file + + + Si è verificato un errore durante l'importazione della chiave + + + Si è verificato un errore durante la creazione della chiave + + + L'archivio chiavi non è stato trovato + + + La validità non può essere inferiore a 1 anno + + + KeyStoreManagement non inizializzato + + + La piattaforma non è supportata + + + Si è verificato un errore imprevisto durante il tentativo di disconnettere il client Jdwp. + + + Si è verificata un'eccezione durante il recupero delle proprietà dell'installazione di Java SDK selezionata. Verificare che l'installazione di Java SDK selezionata contenga una versione compatibile di Java e che 'java -XshowSettings:properties -version' venga eseguito correttamente per tale installazione. Eccezione: {0} + The following terms should not be translated: java -XshowSettings:properties -version +{0} - The exception message of the associated exception + + + Si è verificata un'eccezione durante la ricerca di Android SDK o Java SDK. Eccezione: {0} + {0} - The exception message of the associated exception + + + Non è stato possibile trovare la directory di Android SDK. Verificare che in Gestione Android SDK in Visual Studio sia indicata un'installazione valida. Per usare un percorso SDK personalizzato per una compilazione avviata tramite riga di comando, impostare la proprietà 'AndroidSdkDirectory' di MSBuild sul percorso personalizzato. + The following terms should not be translated: AndroidSdkDirectory + + + Non è stato possibile trovare la directory di Java SDK. Assicurarsi che nella sezione Android nelle opzioni di Visual Studio sia stata configurata una directory valida per Java SDK. Per usare un percorso SDK personalizzato per una compilazione avviata tramite riga di comando, impostare la proprietà 'JavaSdkDirectory' di MSBuild sul percorso personalizzato. + The following terms should not be translated: JavaSdkDirectory + + + Si è verificata un'eccezione durante la convalida dell'installazione di .NET per Android. Per risolvere il problema, usare il programma di installazione Visual Studio per ripristinare o reinstallare il componente .NET MAUI. Eccezione: {0} + {0} - The exception message of the associated exception + + + Non è possibile determinare la versione di .NET per Android. + + \ No newline at end of file diff --git a/src/Xamarin.AndroidTools/Properties/Resources.ja.resx b/src/Xamarin.AndroidTools/Properties/Resources.ja.resx new file mode 100644 index 00000000000..33f6408bb9a --- /dev/null +++ b/src/Xamarin.AndroidTools/Properties/Resources.ja.resx @@ -0,0 +1,168 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + エイリアスに、ファイル名に使用できない文字を含めることはできません + + + キーのインポートでエラーが発生しました + + + キーの作成でエラーが発生しました + + + キーストアが見つかりませんでした + + + 有効期間を 1 年未満にすることはできません + + + KeyStoreManagement が初期化されていません + + + サポートされていないプラットフォーム + + + Jdwp クライアントを切断しようとして予期しないエラーが発生しました。 + + + 選択した Java SDK インストールのプロパティを取得中に例外が発生しました。選択した Java SDK インストールに互換性のある Java のバージョンが含まれており、そのインストールに対して 'java -XshowSettings:properties -version' が正常に実行されることをご確認ください。例外: {0} + The following terms should not be translated: java -XshowSettings:properties -version +{0} - The exception message of the associated exception + + + Android SDK または Java SDK の検索中に例外が発生しました。例外: {0} + {0} - The exception message of the associated exception + + + Android SDK ディレクトリが見つかりませんでした。Visual Studio の Android SDK マネージャーで正常にインストールされていることを確認します。コマンド ライン ビルドにカスタムの SDK パスを使用するには、'AndroidSdkDirectory' MSBuild プロパティをカスタム パスに設定します。 + The following terms should not be translated: AndroidSdkDirectory + + + Java SDK ディレクトリが見つかりませんでした。Visual Studio オプションの Android セクションで、Java SDK ディレクトリが適切に構成されていることを確認します。コマンド ライン ビルドにカスタムの SDK パスを使用するには、'JavaSdkDirectory' MSBuild プロパティをカスタム パスに設定します。 + The following terms should not be translated: JavaSdkDirectory + + + Android 用 .NET のインストールの検証中に例外が発生しました。Visual Studio インストーラーを使用して .NET MAUI コンポーネントを修復または再インストールすると、この問題が解決される可能性があります。例外: {0} + {0} - The exception message of the associated exception + + + Android 用 .NET のバージョンを判別できません。 + + \ No newline at end of file diff --git a/src/Xamarin.AndroidTools/Properties/Resources.ko.resx b/src/Xamarin.AndroidTools/Properties/Resources.ko.resx new file mode 100644 index 00000000000..995c1ffd8fc --- /dev/null +++ b/src/Xamarin.AndroidTools/Properties/Resources.ko.resx @@ -0,0 +1,168 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 별칭에는 파일 이름에 허용되지 않는 문자가 있을 수 없습니다. + + + 키를 가져오는 동안 오류가 발생했습니다. + + + 키를 만드는 동안 오류가 발생했습니다. + + + 키 저장소를 찾을 수 없습니다. + + + 유효 기간은 1년 미만일 수 없습니다. + + + KeyStoreManagement가 초기화되지 않았습니다. + + + 지원되지 않는 플랫폼 + + + Jdwp 클라이언트의 연결을 끊는 동안 예기치 않은 오류가 발생했습니다. + + + 선택한 Java SDK 설치의 속성을 검색하는 동안 예외가 발생했습니다. 선택한 Java SDK 설치에 호환되는 Java 버전이 포함되어 있는지와 'java -XshowSettings:properties -version'이 해당 설치에 대해 성공적으로 실행되는지를 확인하세요. 예외: {0} + The following terms should not be translated: java -XshowSettings:properties -version +{0} - The exception message of the associated exception + + + Android SDK 또는 Java SDK를 찾는 동안 예외가 발생했습니다. 예외: {0} + {0} - The exception message of the associated exception + + + Android SDK 디렉터리를 찾을 수 없습니다. Visual Studio의 Android SDK 관리자에서 유효한 설치를 표시하는지 확인합니다. 명령줄 빌드에 사용자 지정 SDK 경로를 사용하려면 'AndroidSdkDirectory' MSBuild 속성을 사용자 지정 경로로 설정합니다. + The following terms should not be translated: AndroidSdkDirectory + + + Java SDK 디렉터리를 찾을 수 없습니다. Visual Studio 옵션의 Android 섹션에 유효한 Java SDK 디렉터리가 구성되어 있는지 확인하세요. 명령줄 빌드에 사용자 지정 SDK 경로를 사용하려면 'JavaSdkDirectory' MSBuild 속성을 사용자 지정 경로로 설정합니다. + The following terms should not be translated: JavaSdkDirectory + + + Android용 .NET 설치의 유효성을 검사하는 동안 예외가 발생했습니다. Visual Studio 설치 관리자를 사용하여 .NET MAUI 구성 요소를 복구하거나 다시 설치하면 이 문제를 해결할 수 있습니다. 예외: {0} + {0} - The exception message of the associated exception + + + Android용 .NET 버전을 확인할 수 없습니다. + + \ No newline at end of file diff --git a/src/Xamarin.AndroidTools/Properties/Resources.pl.resx b/src/Xamarin.AndroidTools/Properties/Resources.pl.resx new file mode 100644 index 00000000000..c11af345b37 --- /dev/null +++ b/src/Xamarin.AndroidTools/Properties/Resources.pl.resx @@ -0,0 +1,168 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Alias nie może zawierać znaków niedozwolonych w nazwach plików + + + Wystąpił błąd podczas importowania klucza + + + Wystąpił błąd podczas tworzenia klucza + + + Nie znaleziono magazynu kluczy + + + Ważność nie może być mniejsza niż 1 rok + + + Nie zainicjowano elementu KeyStoreManagement + + + Nieobsługiwana platforma + + + Wystąpił nieoczekiwany błąd podczas próby rozłączenia klienta Jdwp. + + + Wystąpił wyjątek podczas pobierania właściwości wybranej instalacji zestawu Java SDK. Sprawdź, czy wybrana instalacja zestawu Java SDK zawiera zgodną wersję języka Java oraz czy polecenie „java -XshowSettings:properties -version” jest pomyślnie wykonywane dla tej instalacji. Wyjątek: {0} + The following terms should not be translated: java -XshowSettings:properties -version +{0} - The exception message of the associated exception + + + Wystąpił wyjątek podczas znajdowania zestawu Android SDK lub Java SDK. Wyjątek: {0} + {0} - The exception message of the associated exception + + + Nie odnaleziono katalogu zestawu SDK systemu Android. Sprawdź, czy jest wyświetlana prawidłowa instalacja w menedżerze zestawu SDK systemu Android w programie Visual Studio. Aby użyć niestandardowej ścieżki zestawu SDK dla kompilacji w wierszu polecenia, ustaw tę ścieżkę we właściwości programu MSBuild „AndroidSdkDirectory”. + The following terms should not be translated: AndroidSdkDirectory + + + Nie odnaleziono katalogu zestawu SDK języka Java. Sprawdź, czy jest skonfigurowany prawidłowy katalog zestawu SDK języka Java w sekcji systemu Android w opcjach programu Visual Studio. Aby użyć niestandardowej ścieżki zestawu SDK dla kompilacji w wierszu polecenia, ustaw tę ścieżkę we właściwości programu MSBuild „JavaSdkDirectory”. + The following terms should not be translated: JavaSdkDirectory + + + Wystąpił wyjątek podczas walidacji środowiska .NET dla instalacji systemu Android. Użycie instalatora programu Visual Studio do naprawy lub ponownej instalacji składnika .NET MAUI może rozwiązać ten problem. Wyjątek: {0} + {0} - The exception message of the associated exception + + + Nie można określić środowiska .NET dla wersji systemu Android. + + \ No newline at end of file diff --git a/src/Xamarin.AndroidTools/Properties/Resources.pt-BR.resx b/src/Xamarin.AndroidTools/Properties/Resources.pt-BR.resx new file mode 100644 index 00000000000..ae37622c328 --- /dev/null +++ b/src/Xamarin.AndroidTools/Properties/Resources.pt-BR.resx @@ -0,0 +1,168 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + O alias não pode ter caracteres que não são permitidos em nomes de arquivo + + + Ocorreu um erro ao importar a chave + + + Ocorreu um erro ao criar a chave + + + O repositório de chaves não foi encontrado + + + A validade não pode ser inferior a 1 ano + + + KeyStoreManagement não foi inicializado + + + Plataforma sem suporte + + + Ocorreu um erro inesperado ao tentar desconectar o cliente Jdwp. + + + Ocorreu uma exceção ao recuperar as propriedades da instalação do SDK do Java selecionada. Verifique se a instalação do SDK do Java selecionada contém uma versão compatível do Java e se 'java -XshowSettings:properties -version' é executado com êxito para essa instalação. Exceção: {0} + The following terms should not be translated: java -XshowSettings:properties -version +{0} - The exception message of the associated exception + + + Ocorreu uma exceção ao localizar o SDK do Android ou o SDK do Java. Exceção: {0} + {0} - The exception message of the associated exception + + + Não foi possível encontrar o diretório do SDK do Android. Verifique se o Gerenciador de SDK Android no Visual Studio mostra uma instalação válida. Para usar um caminho do SDK personalizado para um build de linha de comando, defina a propriedade 'AndroidSdkDirectory' do MSBuild para o caminho personalizado. + The following terms should not be translated: AndroidSdkDirectory + + + Não foi possível encontrar o diretório do SDK do Java. Verifique se a seção Android das opções do Visual Studio tem um diretório válido do SDK do Java configurado. Para usar um caminho do SDK personalizado para um build de linha de comando, defina a propriedade 'JavaSdkDirectory' do MSBuild para o caminho personalizado. + The following terms should not be translated: JavaSdkDirectory + + + Ocorreu uma exceção durante a validação da instalação do .NET para Android. Usar o instalador do Visual Studio para reparar ou reinstalar o componente .NET MAUI pode resolver esse problema. Exceção: {0} + {0} - The exception message of the associated exception + + + Não é possível determinar a versão do .NET para Android. + + \ No newline at end of file diff --git a/src/Xamarin.AndroidTools/Properties/Resources.resx b/src/Xamarin.AndroidTools/Properties/Resources.resx new file mode 100644 index 00000000000..07bed6ebf5f --- /dev/null +++ b/src/Xamarin.AndroidTools/Properties/Resources.resx @@ -0,0 +1,168 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Alias cannot have characters that are not allowed in file names + + + There was an error importing the key + + + There was an error creating the key + + + The keystore was not found + + + Validity cannot be less than 1 year + + + KeyStoreManagement has not been initialized + + + Unsupported Platform + + + Unexpected error occurred trying to disconnect Jdwp client. + + + An exception occurred while retrieving the properties of the selected Java SDK installation. Check that the selected Java SDK installation contains a compatible version of Java and that 'java -XshowSettings:properties -version' runs successfully for that installation. Exception: {0} + The following terms should not be translated: java -XshowSettings:properties -version +{0} - The exception message of the associated exception + + + An exception occurred while finding the Android SDK or Java SDK. Exception: {0} + {0} - The exception message of the associated exception + + + The Android SDK directory could not be found. Check that the Android SDK Manager in Visual Studio shows a valid installation. To use a custom SDK path for a command line build, set the 'AndroidSdkDirectory' MSBuild property to the custom path. + The following terms should not be translated: AndroidSdkDirectory + + + The Java SDK directory could not be found. Ensure that the Android section of the Visual Studio options has a valid Java SDK directory configured. To use a custom SDK path for a command line build, set the 'JavaSdkDirectory' MSBuild property to the custom path. + The following terms should not be translated: JavaSdkDirectory + + + An exception occurred while validating the .NET for Android installation. Using the Visual Studio installer to repair or reinstall the .NET MAUI component might resolve this problem. Exception: {0} + {0} - The exception message of the associated exception + + + Unable to determine the .NET for Android version. + + \ No newline at end of file diff --git a/src/Xamarin.AndroidTools/Properties/Resources.ru.resx b/src/Xamarin.AndroidTools/Properties/Resources.ru.resx new file mode 100644 index 00000000000..aeb03b5155c --- /dev/null +++ b/src/Xamarin.AndroidTools/Properties/Resources.ru.resx @@ -0,0 +1,168 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Псевдоним не может содержать символы, запрещенные в именах файлов + + + При импорте ключа произошла ошибка + + + При создании ключа произошла ошибка + + + Хранилище ключей не найдено + + + Срок действия не может быть меньше 1 года + + + KeyStoreManagement не инициализировано + + + Неподдерживаемая платформа + + + Произошла непредвиденная ошибка при попытке отключить клиент JDWP. + + + Возникло исключение при получении свойств выбранной установки пакета SDK для Java. Проверьте, что она содержит совместимую версию Java и для нее успешно выполняется команда "java -XshowSettings:properties -version". Исключение: {0} + The following terms should not be translated: java -XshowSettings:properties -version +{0} - The exception message of the associated exception + + + Возникло исключение при поиске пакета SDK для Android или Java. Исключение: {0} + {0} - The exception message of the associated exception + + + Не удалось найти каталог пакета SDK для Android. Убедитесь, что в Диспетчере SDK Android в Visual Studio отображается допустимая установка. Чтобы использовать настраиваемый путь к пакету SDK при сборке из командной строки, задайте этот путь в свойстве MSBuild "AndroidSdkDirectory". + The following terms should not be translated: AndroidSdkDirectory + + + Не удалось найти каталог пакета SDK для Java. Убедитесь, что в разделе "Android" в параметрах Visual Studio указан допустимый каталог пакета SDK для Java. Чтобы использовать настраиваемый путь к пакету SDK при сборке из командной строки, задайте этот путь в свойстве MSBuild "JavaSdkDirectory". + The following terms should not be translated: JavaSdkDirectory + + + Возникло исключение при проверке установки .NET для Android. Чтобы устранить эту проблему, попробуйте исправить или переустановить компонент .NET MAUI с помощью установщика Visual Studio. Исключение: {0} + {0} - The exception message of the associated exception + + + Не удается определить версию .NET для Android. + + \ No newline at end of file diff --git a/src/Xamarin.AndroidTools/Properties/Resources.tr.resx b/src/Xamarin.AndroidTools/Properties/Resources.tr.resx new file mode 100644 index 00000000000..2b77a693935 --- /dev/null +++ b/src/Xamarin.AndroidTools/Properties/Resources.tr.resx @@ -0,0 +1,168 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Diğer ad, dosya adlarında izin verilmeyen karakterler içeremez. + + + Anahtar içeri aktarılırken bir hata oluştu + + + Anahtar oluşturulurken bir hata oluştu + + + Anahtar deposu bulunamadı + + + Geçerlilik 1 yıldan az olamaz + + + KeyStoreManagement başlatılmadı + + + Desteklenmeyen Platform + + + Jdwp istemcisinin bağlantısı kesilirken beklenmeyen bir hata oluştu. + + + Seçili Java SDK yüklemesinin özellikleri alınırken özel durum oluştu. Seçili Java SDK yüklemesinin Java'nın uyumlu bir sürümünü içerdiğini ve bu sürümün 'java -XshowSettings:properties -version' öğesini bu yükleme için başarıyla çalıştırıp çalıştırmadığını denetleyin. Özel durum: {0} + The following terms should not be translated: java -XshowSettings:properties -version +{0} - The exception message of the associated exception + + + Android SDK veya Java SDK'sı bulunurken bir özel durum oluştu. Özel durum: {0} + {0} - The exception message of the associated exception + + + Android SDK dizini bulunamadı. Visual Studio Android SDK Yöneticisi'nde geçerli bir yükleme gösterilip gösterilmediğini denetleyin. Komut satırı derlemesi için özel bir SDK yolu kullanmak üzere 'AndroidSdkDirectory' MSBuild özelliğini özel yola ayarlayın. + The following terms should not be translated: AndroidSdkDirectory + + + Java SDK dizini bulunamadı. Visual Studio seçeneklerindeki Android bölümünde geçerli bir Java SDK dizininin yapılandırıldığından emin olun. Komut satırı derlemesi için özel bir SDK yolu kullanmak üzere 'JavaSdkDirectory' MSBuild özelliğini özel yola ayarlayın. + The following terms should not be translated: JavaSdkDirectory + + + Android için .NET yükleme işlemi doğrulanırken bir özel durum oluştu. .NET MAUI bileşenini onarmak veya yeniden yüklemek için Visual Studio yükleyicisini kullanmak bu sorunu çözebilir. Özel Durum: {0} + {0} - The exception message of the associated exception + + + Android için .NET sürümü belirlenemiyor. + + \ No newline at end of file diff --git a/src/Xamarin.AndroidTools/Properties/Resources.zh-Hans.resx b/src/Xamarin.AndroidTools/Properties/Resources.zh-Hans.resx new file mode 100644 index 00000000000..cc61f065f07 --- /dev/null +++ b/src/Xamarin.AndroidTools/Properties/Resources.zh-Hans.resx @@ -0,0 +1,168 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 别名不能包含文件名中不允许的字符 + + + 导入密钥时出错。 + + + 创建密钥时出错 + + + 找不到密钥存储 + + + 有效期不能少于 1 年 + + + 尚未初始化 KeyStoreManagement + + + 不受支持的平台 + + + 尝试断开 Jdwp 客户端的连接时出现了意外错误。 + + + 检索所选 Java SDK 安装的属性时出现异常。请检查所选 Java SDK 安装是否包含兼容的 Java 版本,以及是否为该安装成功运行了 "java -XshowSettings:properties -version"。异常: {0} + The following terms should not be translated: java -XshowSettings:properties -version +{0} - The exception message of the associated exception + + + 查找 Android SDK 或 Java SDK 时出现异常。异常: {0} + {0} - The exception message of the associated exception + + + 找不到 Android SDK 目录。请检查 Visual Studio 中的 Android SDK 管理器是否显示有效的安装。若要使用命令行生成的自定义 SDK 路径,请将 "AndroidSdkDirectory" MSBuild 属性设置为自定义路径。 + The following terms should not be translated: AndroidSdkDirectory + + + 找不到 Java SDK 目录。请确保 Visual Studio 选项的 Android 部分配置了有效的 Java SDK 目录。若要使用命令行生成的自定义 SDK 路径,请将 "JavaSdkDirectory" MSBuild 属性设置为自定义路径。 + The following terms should not be translated: JavaSdkDirectory + + + 验证 .NET for Android 安装时发生异常。使用 Visual Studio 安装程序修复或重新安装 .NET MAUI 组件可能会解决此问题。异常: {0} + {0} - The exception message of the associated exception + + + 无法确定适用于 Android 的 .NET 版本。 + + \ No newline at end of file diff --git a/src/Xamarin.AndroidTools/Properties/Resources.zh-Hant.resx b/src/Xamarin.AndroidTools/Properties/Resources.zh-Hant.resx new file mode 100644 index 00000000000..f6e3e1b26b1 --- /dev/null +++ b/src/Xamarin.AndroidTools/Properties/Resources.zh-Hant.resx @@ -0,0 +1,168 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 別名不能有檔名中不允許的字元 + + + 匯入金鑰時發生錯誤 + + + 建立金鑰時發生錯誤 + + + 找不到金鑰儲存區 + + + 有效期不得小於 1 年 + + + KeyStoreManagement 尚未初始化 + + + 不支援的平台 + + + 嘗試中斷 Jdwp 用戶端的連線時發生意外的錯誤。 + + + 擷取所選 JAVA SDK 安裝的屬性時發生例外狀況。請檢查選取的 JAVA SDK 安裝是否包含相容的 JAVA 版本,以及是否已成功為該安裝執行 'java -XshowSettings:properties -version'。例外狀況: {0} + The following terms should not be translated: java -XshowSettings:properties -version +{0} - The exception message of the associated exception + + + 尋找 Android SDK 或 JAVA SDK 時發生例外狀況。例外狀況: {0} + {0} - The exception message of the associated exception + + + 找不到 Android SDK 目錄。請檢查 Visual Studio 中 Android SDK 管理員是否顯示有效的安裝。若要使用自訂的 SDK 路徑建置命令列,請將 'AndroidSdkDirectory' MSBuild 屬性設定為自訂路徑。 + The following terms should not be translated: AndroidSdkDirectory + + + 找不到 Java SDK 目錄。請確認 Visual Studio 選項 Android 區段已設定有效的 Java SDK 目錄。若要使用自訂的 SDK 路徑建置命令列,請將 'JavaSdkDirectory' MSBuild 屬性設定為自訂路徑。 + The following terms should not be translated: JavaSdkDirectory + + + 驗證 Android 版 .NET 安裝時發生例外狀況。使用 Visual Studio 安裝程式修復或重新安裝 .NET MAUI 元件可能會解決此問題。例外狀況: {0} + {0} - The exception message of the associated exception + + + 無法判斷 Android 版 .NET 的版本。 + + \ No newline at end of file diff --git a/src/Xamarin.AndroidTools/PublicationUtilities/AndroidSdkToolException.cs b/src/Xamarin.AndroidTools/PublicationUtilities/AndroidSdkToolException.cs new file mode 100644 index 00000000000..d46abe98e22 --- /dev/null +++ b/src/Xamarin.AndroidTools/PublicationUtilities/AndroidSdkToolException.cs @@ -0,0 +1,31 @@ +// +// AndroidSdkToolException.cs +// +// Author: +// Greg Munn +// +// Copyright (c) 2015 Xamarin Inc +// + +using System; + +namespace Xamarin.AndroidTools +{ + public sealed class AndroidSdkToolException : Exception + { + public AndroidSdkToolException (string message, int exitCode) : base (message) + { + this.ExitCode = exitCode; + } + + public AndroidSdkToolException (string message, int exitCode, string toolErrorMessage) : base (message) + { + this.ExitCode = exitCode; + this.ToolErrorMessage = toolErrorMessage; + } + + public int ExitCode { get; private set; } + + public string ToolErrorMessage { get; private set; } + } +} diff --git a/src/Xamarin.AndroidTools/PublicationUtilities/KeyManagement.cs b/src/Xamarin.AndroidTools/PublicationUtilities/KeyManagement.cs new file mode 100644 index 00000000000..b2fcbc4e7b6 --- /dev/null +++ b/src/Xamarin.AndroidTools/PublicationUtilities/KeyManagement.cs @@ -0,0 +1,558 @@ +// +// KeyManagement.cs +// +// Author: +// Greg Munn +// +// Copyright (c) 2014 Xamarin Inc +// +using System; +using System.IO; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using System.Text; +using Mono.AndroidTools; +using System.Xml.Linq; +using System.Threading; +using Xamarin.AndroidTools.Properties; + +namespace Xamarin.AndroidTools.PublicationUtilities +{ + public static class KeyManagement + { + static string keystoreBaseDir; + + public enum Platform + { + Mac, + Windows + } + + static KeyManagement () + { + if (Mono.AndroidTools.Util.Platform.IsMac) { + Initialize (KeyManagement.Platform.Mac); + } else { + Initialize (KeyManagement.Platform.Windows); + } + } + + /// + /// Used by test fixtures to set an arbitrary keystore folder + /// + internal static void OverrideKeystoreBaseDirectory (string folder) + { + keystoreBaseDir = folder; + } + + static void Initialize (Platform platform) + { + switch (platform) { + case Platform.Mac: + var macHome = Environment.GetFolderPath (Environment.SpecialFolder.UserProfile); + keystoreBaseDir = Path.Combine (macHome, "Library", "Developer", "Xamarin", "Keystore"); + break; + case Platform.Windows: + var winHome = Environment.GetFolderPath (Environment.SpecialFolder.LocalApplicationData); + keystoreBaseDir = Path.Combine (winHome, "Xamarin", "Mono for Android", "Keystore"); + break; + default: + throw new InvalidOperationException (Resources.CreateKeyError_UnsupportedPlatform); + } + } + + public static void DeleteKey (KeystoreEntry key) + { + var dir = Path.GetDirectoryName (key.Keystore); + Directory.Delete (dir, true); + } + + public static Task GetKeyAsync(string keystore) + { + if (string.IsNullOrWhiteSpace(keystore) || !File.Exists(keystore)) + return Task.FromResult(default(KeystoreEntry)); + + CheckInitialized(); + + return Task.Run(() => + { + var alias = Path.GetFileNameWithoutExtension(keystore); + + var aliasInfo = GetAliasInfo(keystore); + if (string.IsNullOrEmpty(aliasInfo.Item2)) + { + return new KeystoreEntry(keystore, alias, aliasInfo.Item1, string.Empty); + } + else + { + return new KeystoreEntry(keystore, alias, aliasInfo.Item1, aliasInfo.Item2); + } + }); + } + + public static Task> ListManagedKeysAsync () + { + CheckInitialized (); + + // iterate over the .keystore files that are in the baedir + var tcs = new TaskCompletionSource> (); + var result = new List (); + int missingInfoCount = 0; + + if (!Directory.Exists (keystoreBaseDir)) { + tcs.SetResult (new List ()); + return tcs.Task; + } + + var keystores = Directory.GetFiles (keystoreBaseDir, "*.keystore", SearchOption.AllDirectories); + foreach (var key in keystores) { + var alias = Path.GetFileNameWithoutExtension (key); + + var aliasInfo = GetAliasInfo (key); + if (string.IsNullOrEmpty (aliasInfo.Item2)) { + // we don't have it, get from keytool and update... + // we lock on the result list to allow tasks to sync info + lock (result) { + missingInfoCount++; + } + + ExtractAliasInfoAsync (key, aliasInfo.Item1).ContinueWith (lt => { + // we have returned with a list of aliases in the key (we expect there should only be one and + // we can now update our creation date info + + lock (result) { + missingInfoCount--; + + if (!lt.IsFaulted) { + foreach (var entry in lt.Result) { + result.Add (entry); + WriteCreationDateInfo (entry.Keystore, entry.CreationDate, entry.ValidityInfo); + } + } + + if (missingInfoCount <= 0) { + tcs.SetResult (result); + } + } + }); + } else { + lock (result) + result.Add (new KeystoreEntry (key, alias, aliasInfo.Item1, aliasInfo.Item2)); + } + } + + lock (result) { + if (missingInfoCount <= 0) { + tcs.SetResult (result); + } + } + + return tcs.Task; + } + + /// + /// Lists the aliases that are in the given keystore. Throws if the password is incorrect. + /// + public static Task> ListKeystoreAliasesAsync (string store, string storePassword) + { + var listTask = PackageSigningTasks.ListKeyStoreAliasesAsync (store, storePassword, CancellationToken.None); + + return listTask.ContinueWith> (t => { + if (t.IsFaulted) { + // observe the error + AndroidLogger.LogError ("keytool", "ListKeytoreAliases - {0}", t.Exception); + + var toolEx = t.Exception.InnerException as AndroidSdkToolException; + if (toolEx != null) { + throw new InvalidOperationException (toolEx.ToolErrorMessage); + } + + throw t.Exception.InnerException; + } + + var aliasInfo = ExtractAliasInfo (t.Result); + return aliasInfo.Select (x => new KeystoreEntry (store, x.Item1, x.Item2, x.Item3)).ToList (); + }); + } + + /// + /// Special version of ListKeystoreAliasesAsync that assumes that there will only be one alias in the keystore because it is a managed keystore. + /// This is to ensure that no matter what happens when we go to get additional information from the keystore that we always return a KeyStoreEntry + /// + static Task> ExtractAliasInfoAsync (string store, DateTime currentCreationTimestamp) + { + var listTask = PackageSigningTasks.ListKeyStoreAliasesAsync (store, null, CancellationToken.None); + + return listTask.ContinueWith> (t => { + if (t.IsFaulted) { + // observe the error + AndroidLogger.LogError ("keytool", "ListKeytoreAliases - {0}", t.Exception); + + var aliasName = Path.GetFileNameWithoutExtension (store); + var entry = new KeystoreEntry (store, aliasName, currentCreationTimestamp, string.Empty); + return new [] { entry }.ToList (); + } + + var aliasInfo = ExtractAliasInfo (t.Result); + + if (aliasInfo.Count > 0) { + return aliasInfo.Select (x => new KeystoreEntry (store, x.Item1, x.Item2, x.Item3)).ToList (); + } + else { + var aliasName = Path.GetFileNameWithoutExtension (store); + var entry = new KeystoreEntry (store, aliasName, currentCreationTimestamp, string.Empty); + return new [] { entry }.ToList (); + } + }); + } + + /// + /// Gets the information about the alias. Returns the detail as is from keytool + /// + public static Task GetAliasDetailAsync (string store, string alias, string storePassword) + { + // we could just return the task you know..... + var listTask = PackageSigningTasks.ListKeyStoreAliasAsync (store, alias, storePassword, CancellationToken.None); + + return listTask.ContinueWith (t => { + if (t.IsFaulted) { + // observe the error + AndroidLogger.LogError ("keytool", "ListKeyStoreAlias - {0}", t.Exception); + + var toolEx = t.Exception.InnerException as AndroidSdkToolException; + if (toolEx != null) { + throw new InvalidOperationException (toolEx.ToolErrorMessage); + } + + throw t.Exception.InnerException; + } + + return t.Result; + }); + } + + /// + /// Creates a key with the given alias and returns the store in which the key was created. + /// + public static Task CreateKeyAsync (string alias, string password, string dname, int validity) + { + if (string.IsNullOrEmpty (alias)) + throw new ArgumentNullException ("alias"); + if (string.IsNullOrEmpty (password)) + throw new ArgumentNullException ("password"); + if (string.IsNullOrEmpty (dname)) + throw new ArgumentNullException ("dname"); + if (validity < 1) + throw new ArgumentException (Resources.CreateKeyError_MinimumOneYearValidity, "validity"); + if (Path.GetInvalidFileNameChars ().Intersect (alias).Any ()) + throw new InvalidDataException (Resources.CreateKeyError_AliasContainsInvalidCharacters); + + var options = new AndroidSigningOptions { + KeyAlias = alias, + KeyPass = password, + KeyStore = CreateStoreFilename (alias), + StorePass = password, + }; + + var keyTask = PackageSigningTasks.GenerateKeyPairAsync (options, dname, validity, CancellationToken.None); + + return keyTask.ContinueWith ((t) => { + if (t.IsFaulted) { + // observe the error + AndroidLogger.LogError ("keytool", "CreateKey - {0}", t.Exception); + + var toolEx = t.Exception.InnerException as AndroidSdkToolException; + if (toolEx != null) { + throw new InvalidOperationException (toolEx.ToolErrorMessage); + } + + throw t.Exception.InnerException; + } + + if (!t.Result) { + // observe the error + AndroidLogger.LogError ("keytool", "CreateKey failed"); + throw new InvalidOperationException (Resources.CreateKeyError_GenericError); + } + + WriteCreationDateInfo (options.KeyStore, DateTime.Today, string.Empty); + return options.KeyStore; + }); + } + + /// + /// Creates a key with the given alias and returns the store in which the key was created. + /// + public static Task CreateKeyAsync (string alias, string password, string commonName, string organizationUnit, string organization, string locality, string state, string country, int validity) + { + var dname = GetDNameFromValues (commonName, organizationUnit, organization, locality, state, country); + + return CreateKeyAsync (alias, password, dname, validity); + } + + /// + /// Imports a given key from a keystore and returns the path to the new store to which it was imported. + /// A new store is guaranteed to be created for the imported key. + /// + public static Task ImportKeyAsync (string keystore, string alias, string storePassword, string aliasPassword, DateTime creationDate) + { + var storeFilename = CreateStoreFilename (alias); + + if (!File.Exists (keystore)) + throw new FileNotFoundException (Resources.CreateKeyError_KeyStoreNotFound, "keystore"); + + var listTask = PackageSigningTasks.ImportKeyAsync (keystore, storePassword, alias, aliasPassword, storeFilename, CancellationToken.None); + + return listTask.ContinueWith ((t) => { + if (t.IsFaulted) { + // observe the error + AndroidLogger.LogError ("keytool", "ImportKey - {0}", t.Exception); + + var toolEx = t.Exception.InnerException as AndroidSdkToolException; + if (toolEx != null) { + throw new InvalidOperationException (toolEx.ToolErrorMessage); + } + + throw t.Exception.InnerException; + } + + if (!t.Result) { + // observe the error + AndroidLogger.LogError ("keytool", "ImportKey failed"); + throw new InvalidOperationException (Resources.CreateKeyError_ErrorImportingKey); + } + + WriteCreationDateInfo (storeFilename, creationDate, string.Empty); + return storeFilename; + }); + } + + public static string GetDNameFromValues (string [] values) + { + var sb = new StringBuilder (); + + for (int i = 0; i < values.Length; i++) { + string value = values [i]; + if (string.IsNullOrEmpty (value)) + continue; + + if (sb.Length > 0) + sb.Append (", "); + + switch (i) { + case 0: sb.Append ("CN="); + break; + case 1: sb.Append ("OU="); + break; + case 2: sb.Append ("O="); + break; + case 3: sb.Append ("L="); + break; + case 4: sb.Append ("S="); + break; + case 5: sb.Append ("C="); + break; + } + sb.Append (GetEscapedDnameValue (value)); + } + + return sb.ToString (); + } + + public static string GetDNameFromValues (string commonName, string organizationUnit, string organization, string locality, string state, string country) + { + return GetDNameFromValues (new [] { commonName, organizationUnit, organization, locality, state, country }); + } + + public static bool IsValidAlias (string alias) + { + if (string.IsNullOrEmpty (alias)) + return false; + + if (Path.GetInvalidFileNameChars ().Intersect (alias).Any ()) + return false; + + return true; + } + + public static string GetErrorText (AggregateException ex) + { + if (ex.InnerExceptions.Count == 1) { + var message = ex.InnerExceptions [0].Message; + + message = message.Replace ("keytool error: java.io.IOException:", string.Empty).Trim (); + + return message; + } + + return ex.Message; + } + + static string GetEscapedDnameValue (string value) + { + return value.Replace (@",", @"\,"); + } + + static string CreateStoreFilename (string alias) + { + CheckInitialized (); + + var aliasDir = CreateStoreDirectory (keystoreBaseDir, alias); + + return Path.Combine (aliasDir, alias + ".keystore"); + } + + static string CreateStoreDirectory (string baseDir, string alias) + { + string aliasDir; + int unique = 1; + string name; + + do { + if (unique > 1) + name = string.Format ("{0} - {1}", alias, unique); + else + name = alias; + + aliasDir = Path.Combine (baseDir, name); + unique++; + } while (Directory.Exists (aliasDir)); + + Directory.CreateDirectory (aliasDir); + return aliasDir; + } + + static Tuple GetAliasInfo (string keystoreFile) + { + var keystoreInfoFile = Path.ChangeExtension (keystoreFile, ".keyInfo"); + + if (File.Exists (keystoreInfoFile)) { + try { + var doc = XDocument.Load (keystoreInfoFile); + var creationDate = (DateTime)doc.Root.Element ("CreationDate"); + var validityInfo = (string)doc.Root.Element ("ValidityInfo"); + return new Tuple (creationDate, validityInfo); + } + catch { + return new Tuple (DateTime.MinValue, string.Empty); + } + } + + return new Tuple (DateTime.MinValue, string.Empty); + } + + static void WriteCreationDateInfo (string keystoreFile, DateTime creationDate, string validityInfo) + { + try { + var keystoreInfoFile = Path.ChangeExtension (keystoreFile, ".keyInfo"); + + var doc = new XDocument (); + doc.Add (new XElement ("KeyStore")); + doc.Root.Add (new XElement ("CreationDate", creationDate)); + doc.Root.Add (new XElement ("ValidityInfo", validityInfo)); + + doc.Save (keystoreInfoFile); + } + catch (Exception ex) { + AndroidLogger.LogError ("KeyStoreManagement - WriteCreationInfo", ex); + } + } + + /// + /// Extracts the alias names, creation dates and validity from the keytool -list -v command output + /// + public static List> ExtractAliasInfo (string listOutput) + { + /* list output looks a little like this + Keystore type: JKS + Keystore provider: SUN + + Your keystore contains 2 entries + + Alias name: Alias 1 + Creation date: Aug 14, 2014 + Entry type: PrivateKeyEntry + Certificate chain length: 1 + Certificate[1]: + Owner: CN=Me + Issuer: CN=Me + Serial number: 53ecc39d + Valid from: Thu Aug 14 10:11:41 EDT 2014 until: Sat Sep 13 10:11:41 EDT 2014 + Certificate fingerprints: + MD5: 48:21:C7:99:74:FC:43:41:9B:52:EA:98:78:86:49:AD + SHA1: 3C:7E:3F:FE:26:9B:7B:76:1E:CF:84:AF:0F:92:69:B5:07:B9:24:03 + Signature algorithm name: SHA1withRSA + Version: 3 + + + ******************************************* + ******************************************* + + and then repeats + + because of localisatin from the output, we will have to rely on non-localisable output to find the information we want + this will be the ": PrivateKeyEntry" and the "*******************************************"'s + */ + + const string privateKeyEntryTag = ": PrivateKeyEntry"; + const string starsTag = "*******************************************"; + + var result = new List> (); + + var lines = listOutput.Split (new [] { "\n" }, StringSplitOptions.RemoveEmptyEntries); + + string alias = null; + string creationDateText = null; + string validityText = null; + DateTime creationDate = DateTime.MinValue; + + for (int lineNumber = 0; lineNumber < lines.Length; lineNumber++) { + var line = lines [lineNumber]; + + if (line.IndexOf (privateKeyEntryTag, StringComparison.InvariantCultureIgnoreCase) != -1) { + // we are on a line containing the PrivateKeyEntry tag + // look behind to the alias name and creation date + if (lineNumber > 2) { + var aliasLine = lines [lineNumber - 2]; + var creationInfoLine = lines [lineNumber - 1]; + + var ix = aliasLine.IndexOf (":", StringComparison.InvariantCultureIgnoreCase); + alias = aliasLine.Substring (ix + 1).Trim (); + + ix = creationInfoLine.IndexOf (":", StringComparison.InvariantCultureIgnoreCase); + creationDateText = creationInfoLine.Substring (ix + 1).Trim (); + } + + if (lineNumber < lines.Length - 6) { + var validityLine = lines [lineNumber + 6]; + + var ix = validityLine.IndexOf (":", StringComparison.InvariantCultureIgnoreCase); + validityText = validityLine.Substring (ix + 1).Trim (); + } + + if (alias != null) { + if (!DateTime.TryParse (creationDateText, out creationDate)) + creationDate = DateTime.MinValue; + result.Add (new Tuple (alias, creationDate, validityText)); + } + } + + if (line.IndexOf (starsTag, StringComparison.InvariantCultureIgnoreCase) != -1) { + alias = null; + creationDateText = null; + validityText = null; + creationDate = DateTime.MinValue; + } + } + + return result; + } + + static void CheckInitialized () + { + if (keystoreBaseDir == null) + throw new InvalidOperationException (Resources.CreateKeyError_NotInitialized); + } + } +} diff --git a/src/Xamarin.AndroidTools/PublicationUtilities/KeystoreEntry.cs b/src/Xamarin.AndroidTools/PublicationUtilities/KeystoreEntry.cs new file mode 100644 index 00000000000..ef05f5ef26c --- /dev/null +++ b/src/Xamarin.AndroidTools/PublicationUtilities/KeystoreEntry.cs @@ -0,0 +1,32 @@ +// +// KeystoreEntry.cs +// +// Author: +// Greg Munn +// +// Copyright (c) 2014 Xamarin Inc +// + +using System; + +namespace Xamarin.AndroidTools.PublicationUtilities +{ + public sealed class KeystoreEntry + { + public KeystoreEntry (string keystore, string alias, DateTime creationDate, string validityInfo) + { + Keystore = keystore; + Alias = alias; + CreationDate = creationDate; + ValidityInfo = validityInfo; + } + + public string Keystore { get; private set; } + + public string Alias { get; private set; } + + public DateTime CreationDate { get; private set; } + + public string ValidityInfo { get; private set; } + } +} diff --git a/src/Xamarin.AndroidTools/PublicationUtilities/PackageSigningTasks.cs b/src/Xamarin.AndroidTools/PublicationUtilities/PackageSigningTasks.cs new file mode 100644 index 00000000000..27ec7c8e1ac --- /dev/null +++ b/src/Xamarin.AndroidTools/PublicationUtilities/PackageSigningTasks.cs @@ -0,0 +1,462 @@ +using System; +using System.Diagnostics; +using System.Threading; +using System.Threading.Tasks; +using Mono.AndroidTools.Util; +using System.Linq; + +namespace Xamarin.AndroidTools.PublicationUtilities +{ + public static class PackageSigningTasks + { + // simply returns true, assuming the process exit code is zero, then the output is irrelevant + private static Func defaultOutputParser = (s) => true; + + /// + /// Queries the given apk and determines if it is signed or not + /// + public static Task QueryPackageSignatureAsync (string apkFile, CancellationToken token) + { + return QueryPackageSignatureAsync (apkFile, token, AndroidSdk.JarsignerExe); + } + + /// + /// Queries the given apk and determines if it is signed or not + /// + public static Task QueryPackageSignatureAsync (string apkFile, CancellationToken token, string jarsigner) + { + // we need to check the output to see if the package signature was verified or not + Func outputParser = (output) => output.Contains ("verified"); + + var args = new ProcessArgumentBuilder (); + args.Add ("-verify"); + args.AddQuoted (apkFile); + + var toolTask = ProcessUtils.ExecuteToolAsync (jarsigner, args, outputParser, token); + return toolTask; + } + + /// + /// Signs the .APK asynchronously + /// + public static Task SignPackageAsync (AndroidSigningOptions options, string unsignedApk, string signedApk, CancellationToken token) + { + var apkSigner = AndroidSdk.ApkSignerJar; + if (apkSigner != null) { + // use apk signer instead if it exists + return SignPackageWithApkSignerAsync (options, unsignedApk, signedApk, token, apkSigner); + } + + return SignPackageAsync (options, unsignedApk, signedApk, token, AndroidSdk.JarsignerExe); + } + + /// + /// Signs the .APK asynchronously + /// + public static Task SignPackageAsync (AndroidSigningOptions options, string unsignedApk, string signedApk, CancellationToken token, string jarsigner) + { + // Create our Argument list + var args = new ProcessArgumentBuilder (); + args.Add ("-keystore"); + args.AddQuoted (options.KeyStore); + args.Add ("-storepass"); + args.AddQuoted (options.StorePass); + args.Add ("-keypass"); + args.AddQuoted (options.KeyPass); + + if (!string.IsNullOrEmpty (options.TsaUrl)) { + args.Add ("-tsa"); + args.AddQuoted (options.TsaUrl); + } + + args.Add ("-digestalg"); + + if(options.SigningAlgorithm == PackageSigningAlgorithm.SHA256withRSA) + args.Add("SHA-256"); + else + args.Add ("SHA1"); + + args.Add ("-sigalg"); + + switch (options.SigningAlgorithm) { + case PackageSigningAlgorithm.RSA: + args.Add ("md5withRSA"); + break; + case PackageSigningAlgorithm.DSA: + args.Add ("SHA1withDSA"); + break; + case PackageSigningAlgorithm.SHA256withRSA: + args.Add("SHA256withRSA"); + break; + default: + args.Add ("md5withRSA"); + break; + } + + args.Add ("-signedjar"); + args.AddQuoted (signedApk, unsignedApk, options.KeyAlias); + + var toolTask = ProcessUtils.ExecuteToolAsync (jarsigner, args, defaultOutputParser, token); + return toolTask; + } + + /// + /// Signs the .APK asynchronously + /// + public static Task SignPackageWithApkSignerAsync (AndroidSigningOptions options, string unsignedApk, string signedApk, CancellationToken token, string apksigner) => + SignPackageWithApkSignerAsync (options, unsignedApk, signedApk, token, apksigner, null); + + /// + /// Signs the .APK asynchronously with logging + /// + public static Task SignPackageWithApkSignerAsync (AndroidSigningOptions options, string unsignedApk, string signedApk, CancellationToken token, string apksigner, Action logMessage) + { + apksigner = apksigner ?? AndroidSdk.ApkSignerJar; + bool useJava = apksigner != null && apksigner.EndsWith (".jar", StringComparison.OrdinalIgnoreCase); + + // Create our Argument list + var fileName = useJava ? AndroidSdk.JavaExe : apksigner; + var args = new ProcessArgumentBuilder (); + if (useJava) { + args.Add ("-jar"); + args.AddQuoted (apksigner); + } + args.Add ("sign"); + args.Add ("--ks"); + args.AddQuoted (options.KeyStore); + args.Add ("--ks-key-alias"); + args.AddQuoted (options.KeyAlias); + args.Add ("--out"); + args.AddQuoted (signedApk); + args.Add ("--ks-pass"); + args.AddQuoted ("pass:" + options.StorePass); + args.Add ("--key-pass"); + args.AddQuoted ("pass:"+ options.KeyPass); + + if (options.MinSdkVersion != null) { + args.Add ("--min-sdk-version"); + args.Add (options.MinSdkVersion.ToString ()); + } + + //if (!string.IsNullOrEmpty (options.TsaUrl)) + //{ + // args.Add ("-tsa"); + // args.AddQuoted (options.TsaUrl); + //} + + //args.Add ("-digestalg"); + //args.Add ("SHA1"); + //args.Add ("-sigalg"); + + //switch (options.SigningAlgorithm) + //{ + //case PackageSigningAlgorithm.RSA: + // args.Add ("md5withRSA"); + // break; + //case PackageSigningAlgorithm.DSA: + // args.Add ("SHA1withDSA"); + // break; + //default: + // args.Add ("md5withRSA"); + // break; + //} + + args.AddQuoted (unsignedApk); + + logMessage?.Invoke ($"Executing: {fileName} {args}"); + return ProcessUtils.ExecuteToolAsync (fileName, args, result => { + if (!string.IsNullOrEmpty (result)) + logMessage?.Invoke ($"apksigner: {result}"); + return true; + }, token); + } + + /// + /// Attempts to determine the signing algorithm to use with the given keystore and alias + /// + public static Task DetermineSigningAlgorithm (string keystore, string alias, CancellationToken token) + { + return DetermineSigningAlgorithm (keystore, alias, token, AndroidSdk.KeyToolExe); + } + + /// + /// Attempts to determine the signing algorithm to use with the given keystore and alias + /// + public static Task DetermineSigningAlgorithm (string keystore, string alias, CancellationToken token, string keytool) + { + var args = new ProcessArgumentBuilder (); + args.Add ("-list"); + args.Add ("-v"); + args.Add ("-keystore"); + args.AddQuoted (keystore); + args.Add ("-alias"); + args.AddQuoted (alias); + // make sure the output is in english + args.Add ("-J\"-Duser.language=en-US\""); + + Action onStarted = (p) => { + // press enter when prompted to enter password + p.StandardInput.WriteLine (); + }; + + var aliasDetailsTask = ProcessUtils.ExecuteToolAsync (keytool, args, (s) => s, token, onStarted); + return aliasDetailsTask.ContinueWith (dt => { + var lines = dt.Result.Split (new [] { "\n" }, StringSplitOptions.RemoveEmptyEntries); + var algLine = lines.FirstOrDefault (l => l.Contains ("Signature algorithm name:")); + + if (!string.IsNullOrEmpty (algLine)) { + var parts = algLine.Trim().Split (new [] { "Signature algorithm name:" }, StringSplitOptions.RemoveEmptyEntries); + if (parts.Length > 0) { + return FromString (parts [0]); + } + } else { + // mmm, unexpected + Mono.AndroidTools.AndroidLogger.LogWarning (string.Format ("Could not determine signing algorithm: {0}", dt.Result)); + } + + return PackageSigningAlgorithm.Unsupported; + }, TaskContinuationOptions.NotOnFaulted | TaskContinuationOptions.NotOnCanceled ); + } + + static PackageSigningAlgorithm FromString (string alg) + { + Mono.AndroidTools.AndroidLogger.LogInfo (string.Format ("Converting signing algorithm from {0}", alg)); + + if (!string.IsNullOrEmpty (alg)) { + var parts = alg.Split (new [] { "with" }, StringSplitOptions.RemoveEmptyEntries); + if (parts.Length == 2) { + switch (parts [1]) { + case "DSA": + return PackageSigningAlgorithm.DSA; + case "RSA": + return PackageSigningAlgorithm.RSA; + } + } + } + + return PackageSigningAlgorithm.Unsupported; + } + + /// + /// Aligns the .APK asynchronously + /// + public static Task AlignPackageAsync (string srcApk, string destApk, CancellationToken token) + { + return AlignPackageAsync (srcApk, destApk, token, AndroidSdk.ZipAlignExe); + } + + /// + /// Aligns the .APK asynchronously + /// + public static Task AlignPackageAsync (string srcApk, string destApk, CancellationToken token, string zipAlignExe) + { + // Create our Argument list + var args = new ProcessArgumentBuilder (); + args.Add ("-f"); + args.Add ("4"); + args.AddQuoted (srcApk, destApk); + + var toolTask = ProcessUtils.ExecuteToolAsync (zipAlignExe, args, defaultOutputParser, token); + return toolTask; + } + + /// + /// Verifies the alignment of the .APK asynchronously + /// + public static Task VerifyPackageAlignmentAsync (string apkfileName, CancellationToken token) + { + return VerifyPackageAlignmentAsync (apkfileName, token, AndroidSdk.ZipAlignExe); + } + + /// + /// Verifies the alignment of the .APK asynchronously + /// + public static Task VerifyPackageAlignmentAsync (string apkfileName, CancellationToken token, string zipAlignExe) + { + // Create our Argument list + var args = new ProcessArgumentBuilder (); + args.Add ("-c"); + args.Add ("4"); + args.AddQuoted (apkfileName); + + var toolTask = ProcessUtils.ExecuteToolAsync (zipAlignExe, args, defaultOutputParser, token); + return toolTask; + } + + /// + /// Generates a key-pair asynchronously + /// + public static Task GenerateKeyPairAsync (AndroidSigningOptions options, string dname, int validity, CancellationToken token) + { + return GenerateKeyPairAsync (options, dname, validity, token, AndroidSdk.KeyToolExe); + } + + /// + /// Generates a key-pair asynchronously + /// + public static Task GenerateKeyPairAsync (AndroidSigningOptions options, string dname, int validity, CancellationToken token, string keytool) + { + // For compatibility with JDK > 1.8, which errors out on an empty `-dname` value + if (string.IsNullOrEmpty (dname)) + dname = "CN="; + + var dnameParameter = ProcessArgumentBuilder.Quote(dname); + + if (OS.IsWindows) + dnameParameter = dnameParameter.Replace(",", @"\,"); + + var args = new ProcessArgumentBuilder (); + args.Add ("-genkeypair"); + args.Add ("-alias"); + args.AddQuoted (options.KeyAlias); + args.Add ("-dname"); + args.Add (dnameParameter); + args.Add ("-storepass"); + args.AddQuoted (options.StorePass); + args.Add ("-keypass"); + args.AddQuoted (options.KeyPass); + args.Add ("-keystore"); + args.AddQuoted (options.KeyStore); + args.Add ("-keysize"); + args.Add ("2048"); + args.Add ("-keyalg"); + args.Add ("RSA"); + if (validity > 0) { + args.Add ("-validity"); + args.AddQuoted (validity.ToString ()); + } + + var toolTask = ProcessUtils.ExecuteToolAsync (keytool, args, defaultOutputParser, token); + return toolTask; + } + + /// + /// Verifies a key-pair asynchronously + /// + public static Task VerifyKeyPairAsync (AndroidSigningOptions options, CancellationToken token) + { + return VerifyKeyPairAsync (options, token, AndroidSdk.KeyToolExe); + } + + /// + /// Verifies a key-pair asynchronously. Note: options.AliasPass is not used, only the other values + /// + public static Task VerifyKeyPairAsync (AndroidSigningOptions options, CancellationToken token, string keytool) + { + var args = new ProcessArgumentBuilder (); + args.Add ("-list"); + args.AddQuoted ("-keystore", options.KeyStore); + args.AddQuoted ("-storepass", options.StorePass); + args.AddQuoted ("-alias", options.KeyAlias); + + var toolTask = ProcessUtils.ExecuteToolAsync (keytool, args, defaultOutputParser, token); + return toolTask; + } + + /// + /// Lists the aliases that are stored in the keystore, returns the raw output from keytool + /// + public static Task ListKeyStoreAliasesAsync (string keystore, string storePassword, CancellationToken token) + { + return ListKeyStoreAliasesAsync (keystore, storePassword, token, AndroidSdk.KeyToolExe); + } + + /// + /// Lists the aliases that are stored in the keystore, returns the raw output from keytool + /// + public static Task ListKeyStoreAliasesAsync (string keystore, string storePassword, CancellationToken token, string keytool) + { + var args = new ProcessArgumentBuilder (); + args.Add ("-list"); + args.Add ("-v"); + if (!string.IsNullOrEmpty (storePassword)) { + args.Add ("-storepass"); + args.AddQuoted (storePassword); + } + args.Add ("-keystore"); + args.AddQuoted (keystore); + + Action onStarted = (p) => { + // press enter when prompted to enter password + if (string.IsNullOrEmpty (storePassword)) + p.StandardInput.WriteLine (); + }; + + var toolTask = ProcessUtils.ExecuteToolAsync (keytool, args, (s) => s, token, onStarted); + return toolTask; + } + + /// + /// Performs a list of a specific alias within a keystore asynchronously. + /// Used to get information about a specific alias. + /// + public static Task ListKeyStoreAliasAsync (string keystore, string alias, string storePassword, CancellationToken token) + { + return ListKeyStoreAliasAsync (keystore, alias, storePassword, token, AndroidSdk.KeyToolExe); + } + + /// + /// Performs a list of a specific alias within a keystore asynchronously. + /// Used to get information about a specific alias + /// + public static Task ListKeyStoreAliasAsync (string keystore, string alias, string storePassword, CancellationToken token, string keytool) + { + var args = new ProcessArgumentBuilder (); + args.Add ("-list"); + args.Add ("-v"); + if (!string.IsNullOrEmpty (storePassword)) { + args.Add ("-storepass"); + args.AddQuoted (storePassword); + } + args.Add ("-keystore"); + args.AddQuoted (keystore); + args.Add ("-alias"); + args.AddQuoted (alias); + + Action onStarted = (p) => { + // press enter when prompted to enter password + if (string.IsNullOrEmpty (storePassword)) + p.StandardInput.WriteLine (); + }; + + var toolTask = ProcessUtils.ExecuteToolAsync (keytool, args, (s) => s, token, onStarted); + return toolTask; + } + + /// + /// Imports the key from sourceKeystore to destKeystore but uses the same password for the new store + /// + public static Task ImportKeyAsync (string sourceKeystore, string sourceStorePassword, string alias, string aliasPassword, string destKeystore, CancellationToken token) + { + return ImportKeyAsync (sourceKeystore, sourceStorePassword, alias, aliasPassword, destKeystore, token, AndroidSdk.KeyToolExe); + } + + /// + /// Imports the key from sourceKeystore to destKeystore but uses the same password for the new store + /// + public static Task ImportKeyAsync (string sourceKeystore, string sourceStorePassword, string alias, string aliasPassword, string destKeystore, CancellationToken token, string keytool) + { + var args = new ProcessArgumentBuilder (); + args.Add ("-importkeystore"); + args.Add ("-srckeystore"); + args.AddQuoted (sourceKeystore); + args.Add ("-destkeystore"); + args.AddQuoted (destKeystore); + + args.Add ("-srcstorepass"); + args.AddQuoted (sourceStorePassword); + args.Add ("-srcalias"); + args.AddQuoted (alias); + args.Add ("-srckeypass"); + args.AddQuoted (aliasPassword); + + args.Add ("-deststorepass"); + args.AddQuoted (aliasPassword); + args.Add ("-destkeypass"); + args.AddQuoted (aliasPassword); + + var toolTask = ProcessUtils.ExecuteToolAsync (keytool, args, defaultOutputParser, token); + return toolTask; + } + } +} diff --git a/src/Xamarin.AndroidTools/PublicationUtilities/PublishAndroidApplication.cs b/src/Xamarin.AndroidTools/PublicationUtilities/PublishAndroidApplication.cs new file mode 100644 index 00000000000..8c78741b9c8 --- /dev/null +++ b/src/Xamarin.AndroidTools/PublicationUtilities/PublishAndroidApplication.cs @@ -0,0 +1,95 @@ +using System; +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using Mono.AndroidTools; +using Xamarin.AndroidTools.PublicationUtilities; + +namespace Xamarin.AndroidTools +{ + /// + /// Legacy packaging tasks + /// + public static class PublishAndroidApplication + { + /// + /// Signs the .APK asynchronously but does not return a faulted task + /// + public static Task SignPackage (AndroidSigningOptions options, string unsignedApk, string signedApk, StringWriter output, CancellationToken token) + { + if (token.CanBeCanceled && token.IsCancellationRequested) + throw new OperationCanceledException (); + + return PackageSigningTasks.SignPackageAsync (options, unsignedApk, signedApk, token).ContinueWith (res => { + return HandleAsyncTaskAndReturnBool (res, "SignPackageAsync faulted", output); + }); + } + + /// + /// Aligns the .APK asynchronously but does not return a faulted task + /// + public static Task AlignPackage (string srcApk, string destApk, StringWriter output, CancellationToken token) + { + if (token.CanBeCanceled && token.IsCancellationRequested) + throw new OperationCanceledException (); + + return PackageSigningTasks.AlignPackageAsync (srcApk, destApk, token).ContinueWith (res => { + return HandleAsyncTaskAndReturnBool (res, "AlignPackageAsync faulted", output); + }); + } + + /// + /// Generates a key-pair asynchronously but does not return a faulted task + /// + public static Task GenerateKeyPair (AndroidSigningOptions options, string dname, int validity, StringWriter output, CancellationToken token) + { + if (token.CanBeCanceled && token.IsCancellationRequested) + throw new OperationCanceledException (); + + return PackageSigningTasks.GenerateKeyPairAsync (options, dname, validity, token).ContinueWith (res => { + return HandleAsyncTaskAndReturnBool (res, "GenerateKeyPairAsync faulted", output); + }); + } + + /// + /// Verifies a key-pair asynchronously but does not return a faulted task + /// + public static Task VerifyKeyPair (AndroidSigningOptions options, StringWriter output, CancellationToken token) + { + if (token.CanBeCanceled && token.IsCancellationRequested) + throw new OperationCanceledException (); + + if (output != null) + output.WriteLine ("Keystore verification failed:"); + + return PackageSigningTasks.VerifyKeyPairAsync (options, token).ContinueWith (res => { + return HandleAsyncTaskAndReturnBool (res, "VerifyKeyPairAsync faulted", output); + }); + } + + /// + /// Handles the task and returns true or false, logs any exceptions and returns false for cancelled tasks. + /// Preserves API semantics for the methods not suffixed with 'Async' + /// + private static bool HandleAsyncTaskAndReturnBool (Task task, string faultMesage, StringWriter output) + { + if (task.IsFaulted) { + // observe the exception + AndroidLogger.LogError (faultMesage, task.Exception); + if (output != null) { + var toolException = task.Exception.InnerException as AndroidSdkToolException; + if (toolException != null) { + output.Write (toolException.ToolErrorMessage); + } + } + return false; + } + + if (task.IsCanceled) { + return false; + } + + return task.Result; + } + } +} diff --git a/src/Xamarin.AndroidTools/Sdks/MonoDroidSdkBase.cs b/src/Xamarin.AndroidTools/Sdks/MonoDroidSdkBase.cs new file mode 100644 index 00000000000..52ecbe787c6 --- /dev/null +++ b/src/Xamarin.AndroidTools/Sdks/MonoDroidSdkBase.cs @@ -0,0 +1,308 @@ +// +// MonoDroidSdkBase.cs +// +// Author: +// Michael Hutchinson +// Andreia Gaita +// +// Copyright (c) 2012 Xamarin Inc. (http://xamarin.com) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +using System; +using System.IO; +using System.Linq; +using System.Xml; +using Mono.AndroidTools; +using System.Collections.Generic; + +using Xamarin.Android.Tools; + +namespace Xamarin.AndroidTools +{ + abstract class MonoDroidSdkBase + { + // I can never remember the difference between SdkPath and anything else... + [Obsolete ("Do not use.")] + public string SdkPath { get; private set; } + + // Contains mono, *-as, cross-arm, etc. + public string BinPath { get; private set; } + + // Not actually shipped... + public string IncludePath { get; private set; } + + // Contains Mono.Android.DebugRuntime-*.apk, platforms/*/*.apk. + public string RuntimePath { get; private set; } + + // Root directory for XA libraries, contains designer dependencies + public string LibrariesPath { get; private set; } + + // Contains mscorlib.dll + public string BclPath { get; private set; } + + public AndroidVersions AndroidVersions { get; private set; } + + public int SharedRuntimeVersion { get; private set; } + + // runtimePath: contains Mono.Android.DebugRuntime-*.apk + // binPath: contains aapt2, ndk/*-as, etc. + // bclPath: contains mscorlib.dll + public void Initialize (string runtimePath = null, string binPath = null, string bclPath = null) + { + runtimePath = GetValidPath ("$(MonoAndroidToolsDirectory)", runtimePath, ValidateRuntime, () => FindRuntime ()); + if (runtimePath != null) { + binPath = GetValidPath ("$(MonoAndroidBinDirectory)", binPath, ValidateBin, () => FindBin (runtimePath)); + bclPath = GetValidPath ("mscorlib.dll", bclPath, ValidateFramework, () => FindFramework (runtimePath)); + } else { + binPath = bclPath = null; + } + + if (runtimePath == null || binPath == null || bclPath == null) { + Reset (); + return; + } + + RuntimePath = runtimePath; +#pragma warning disable 0618 + SdkPath = GetSdkPath (runtimePath); +#pragma warning restore 0618 + BinPath = binPath; + BclPath = bclPath; + LibrariesPath = FindLibraries (runtimePath); + + IncludePath = FindInclude (runtimePath); + if (IncludePath != null && !Directory.Exists (IncludePath)) + IncludePath = null; + + SharedRuntimeVersion = GetCurrentSharedRuntimeVersion (runtimePath); + AndroidVersions = new AndroidVersions (new [] { BclPath }); + } + + static string GetValidPath (string description, string path, Func validator, Func defaultPath) + { + if (!string.IsNullOrEmpty (path)) { + if (Directory.Exists (path)) { + if (validator (path)) { + AndroidLogger.LogDebug ($"{description} path `{path}` is valid"); + return path; + } + AndroidLogger.LogInfo ($"{description} path `{path}` is not valid; skipping."); + } else { + AndroidLogger.LogDebug ($"{description} path `{path}` did not exist"); + } + } + path = defaultPath (); + if (path != null && validator (path)) { + AndroidLogger.LogDebug ($"{description} defaultPath `{path}` is valid"); + return path; + } + AndroidLogger.LogInfo ($"{description} defaultPath `{path}` is not valid; skipping."); + return null; + } + + public void Reset () + { +#pragma warning disable 0618 + SdkPath = BinPath = IncludePath = RuntimePath = BclPath = null; +#pragma warning restore 0618 + SharedRuntimeVersion = 0; + + AndroidVersions = null; + } + + string GetSdkPath (string runtimePath) + { + var sdkPaths = new[]{ + // runtimePath=$prefix/lib/mandroid + Path.GetFullPath (Path.Combine (runtimePath, "..", "..")), + // runtimePath=$prefix/lib/xamarin.android/xbuild/Xamarin/Android + // we prefer $prefix/Version* over $prefix/lib/xamarin.android/xbuild/Xamarin/Android/Version* + Path.GetFullPath (Path.Combine (runtimePath, "..", "..", "..", "..", "..")), + // runtimePath=$prefix/lib/xamarin.android/xbuild/Xamarin/Android + Path.GetFullPath (runtimePath), + // runtimePath=/Library/Frameworks/Xamarin.Android.framework/Libraries/xbuild/Xamarin/Android + // Can possibly happen if we hit the `finalSearchPaths` value in MonoDroidSdkUnix.FindRuntime() + // See https://bugzilla.xamarin.com/show_bug.cgi?id=58776 + Path.GetFullPath (Path.Combine (runtimePath, "..", "..", "..", "..", "Versions", "Current")), + }; + var versionFiles = new[]{ + "Version.txt", + "Version", + }; + foreach (var sdkPath in sdkPaths) { + foreach (var version in versionFiles) { + var path = Path.Combine (sdkPath, version); + bool exists = File.Exists (path); + AndroidLogger.LogDebug ($"{nameof (GetSdkPath)} `{path}` exists={exists} "); + if (exists) + return sdkPath; + } + } + return null; + } + + protected abstract string FindRuntime (); + protected abstract string FindFramework (string runtimePath); + + // Check for platform-specific `mandroid` name + protected abstract bool ValidateBin (string binPath); + + protected static bool ValidateRuntime (string loc) + { + return !string.IsNullOrWhiteSpace (loc) && + File.Exists (Path.Combine (loc, "Xamarin.AndroidTools.dll")); + } + + protected static bool ValidateFramework (string loc) + { + return loc != null && File.Exists (Path.Combine (loc, "mscorlib.dll")); + } + + public string FindVersionFile () + { +#pragma warning disable 0618 + if (string.IsNullOrEmpty (SdkPath)) + return null; +#pragma warning restore 0618 + foreach (var loc in GetVersionFileLocations ()) { + bool exists = File.Exists (loc); + AndroidLogger.LogDebug (null, $"FindVersionFile: {loc}, result={exists}"); + if (exists) { + return loc; + } + } + return null; + } + + protected virtual IEnumerable GetVersionFileLocations () + { +#pragma warning disable 0618 + yield return Path.Combine (SdkPath, "Version.txt"); + yield return Path.Combine (SdkPath, "Version"); +#pragma warning restore 0618 + } + + protected abstract string FindBin (string runtimePath); + + protected abstract string FindInclude (string runtimePath); + + protected abstract string FindLibraries (string runtimePath); + + [Obsolete ("Do not use.")] + public string GetPlatformNativeLibPath (string abi) + { + return FindPlatformNativeLibPath (SdkPath, abi); + } + + [Obsolete ("Do not use.")] + public string GetPlatformNativeLibPath (AndroidTargetArch arch) + { + return FindPlatformNativeLibPath (SdkPath, GetMonoDroidArchName (arch)); + } + + [Obsolete ("Do not use.")] + static string GetMonoDroidArchName (AndroidTargetArch arch) + { + switch (arch) { + case AndroidTargetArch.Arm: + return "armeabi"; + case AndroidTargetArch.Mips: + return "mips"; + case AndroidTargetArch.X86: + return "x86"; + } + return null; + } + + [Obsolete] + protected string FindPlatformNativeLibPath (string sdk, string arch) + { + return Path.Combine (sdk, "lib", arch); + } + + static XmlReaderSettings GetSafeReaderSettings () + { + //allow DTD but not try to resolve it from web + return new XmlReaderSettings { + CloseInput = true, + DtdProcessing = DtdProcessing.Ignore, + XmlResolver = null, + }; + } + + int GetCurrentSharedRuntimeVersion (string runtimePath) + { + string file = Path.Combine (runtimePath, "Mono.Android.DebugRuntime-debug.xml"); + + return GetManifestVersion (file); + } + + internal static int GetManifestVersion (string file) + { + // It seems that MfA 1.0 on Windows didn't include the xml files to get the runtime version. + if (!File.Exists (file)) + return int.MaxValue; + + try { + using (var r = XmlReader.Create (file, GetSafeReaderSettings())) { + if (r.MoveToContent () == XmlNodeType.Element && r.MoveToAttribute ("android:versionCode")) { + int value; + if (int.TryParse (r.Value, out value)) + return value; + AndroidLogger.LogInfo ($"Cannot parse runtime version code: ({r.Value})"); + } + } + } catch (Exception ex) { + AndroidLogger.LogError ("Error trying to find shared runtime version", ex); + } + return int.MaxValue; + } + + public IEnumerable GetSupportedApiLevels () + { + if (AndroidVersions != null) { + return AndroidVersions.InstalledBindingVersions.Select (v => v.Id); + } + return new string [0]; + } + + public string GetApiLevelForFrameworkVersion (string framework) + { + return AndroidVersions?.GetIdFromFrameworkVersion (framework); + } + + public string GetFrameworkVersionForApiLevel (string apiLevel) + { + // API level 9 was discontinued immediately for 10, in the rare case we get it just upgrade the number + if (apiLevel == "9") + apiLevel = "10"; + return AndroidVersions?.GetFrameworkVersionFromId (apiLevel); + } + + /// + /// Determines if the given apiLevel is supported by an installed Framework + /// + public bool IsSupportedFrameworkLevel (string apiLevel) + { + var id = AndroidVersions?.GetIdFromApiLevel (apiLevel); + return id != null && AndroidVersions.InstalledBindingVersions.Any (v => v.Id == id); + } + } +} diff --git a/src/Xamarin.AndroidTools/Sdks/MonoDroidSdkUnix.cs b/src/Xamarin.AndroidTools/Sdks/MonoDroidSdkUnix.cs new file mode 100644 index 00000000000..b3c8bda36af --- /dev/null +++ b/src/Xamarin.AndroidTools/Sdks/MonoDroidSdkUnix.cs @@ -0,0 +1,172 @@ +// +// MonoDroidSdkUnix.cs +// +// Author: +// Michael Hutchinson +// Andreia Gaita +// +// Copyright (c) 2012 Xamarin Inc. (http://xamarin.com) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +using System; +using System.IO; +using System.Linq; +using System.Collections.Generic; + +using Mono.AndroidTools; + +namespace Xamarin.AndroidTools +{ + class MonoDroidSdkUnix : MonoDroidSdkBase + { + readonly static string[] RuntimeToFrameworkPaths = new[]{ + // runtimePath=$prefix/lib/xamarin.android/xbuild/Xamarin/Android/ + Path.Combine ("..", "..", "..", "xbuild-frameworks", "MonoAndroid"), + // runtimePath=??? What is this entry for? + Path.Combine ("..", "..", "..", ".xamarin.android", "lib", "xbuild-frameworks", "MonoAndroid"), + // runtimePath=$prefix/lib/xbuild + Path.Combine ("..", "xbuild-frameworks", "MonoAndroid"), + // runtimePath=??? System-wide Unix install? + Path.Combine ("..", "mono", "xbuild-frameworks", "MonoAndroid"), + }; + + readonly static string[] SearchPaths = { + // macOS + "/Library/Frameworks/Xamarin.Android.framework/Versions/Current/lib/xamarin.android/xbuild/Xamarin/Android", + "/Library/Frameworks/Xamarin.Android.framework/Versions/Current/lib/mandroid", + "/Developer/MonoAndroid/usr/lib/mandroid", + // FlatPak + "/app/lib/mandroid", + "/app/lib/xamarin.android/xbuild/Xamarin/Android", + // Various non-existent Unix paths + "/opt/mono-android/lib/mandroid", + "/opt/mono-android/lib/xamarin.android/xbuild/Xamarin/Android", + "/usr/lib/xamarin.android/xbuild/Xamarin/Android", + }; + + protected override string FindRuntime () + { + string environmentPath = GetRuntimePathFromEnvironment (); + if (environmentPath != null) + return environmentPath; + + // check also in the users folder + var personal = Environment.GetFolderPath (Environment.SpecialFolder.UserProfile); + var initialSearchPaths = new[] { + Path.Combine (personal, @".xamarin.android/lib/mandroid"), + Path.Combine (personal, @".xamarin.android/lib/xamarin.android/xbuild/Xamarin/Android"), + }; + var finalSearchPaths = new[] { + Path.GetFullPath (Path.GetDirectoryName (GetType ().Assembly.Location)), + }; + + return initialSearchPaths + .Concat (SearchPaths) + .Concat (finalSearchPaths) + .FirstOrDefault (ValidateRuntime); + } + + string GetRuntimePathFromEnvironment () + { + string monoAndroidPath = Environment.GetEnvironmentVariable ("MONO_ANDROID_PATH"); + if (string.IsNullOrEmpty (monoAndroidPath)) + return null; + + if (!Directory.Exists (monoAndroidPath)) { + AndroidLogger.LogInfo (null, "$MONO_ANDROID_PATH points to `{0}`, but it does not exist.", monoAndroidPath); + return null; + } + + string msbuildDir = Path.Combine (monoAndroidPath, "lib", "xamarin.android", "xbuild", "Xamarin", "Android"); + if (Directory.Exists (msbuildDir) && ValidateRuntime (msbuildDir)) + return msbuildDir; + + string libMandroid = Path.Combine (monoAndroidPath, "lib", "mandroid"); + if (Directory.Exists (libMandroid) && ValidateRuntime (libMandroid)) + return libMandroid; + AndroidLogger.LogInfo (null, "$MONO_ANDROID_PATH points to `{0}`, but it is invalid.", monoAndroidPath); + return null; + } + + protected override bool ValidateBin (string binPath) + { + if (!Directory.Exists (binPath)) + return false; + if (File.Exists (Path.Combine (binPath, "aapt2"))) + return true; + return false; + } + + protected override string FindFramework (string runtimePath) + { + foreach (var relativePath in RuntimeToFrameworkPaths) { + var fullPath = Path.GetFullPath (Path.Combine (runtimePath, relativePath)); + if (Directory.Exists (fullPath)) { + if (ValidateFramework (fullPath)) + return fullPath; + + // check to see if full path is the folder that contains each framework version, eg contains folders of the form v1.0, v2.3 etc + var subdirs = Directory.GetDirectories (fullPath, "v*").OrderBy (x => x).ToArray (); + foreach (var subdir in subdirs) { + if (ValidateFramework (subdir)) + return subdir; + } + } + } + + return null; + } + + protected override string FindBin (string runtimePath) + { + string osName = OS.IsMac ? "Darwin" : "Linux"; + string binPath = Path.GetFullPath (Path.Combine (runtimePath, osName)); + if (File.Exists (Path.Combine (binPath, "aapt2"))) + return binPath; + + return null; + } + + protected override string FindInclude (string runtimePath) + { + string includeDir = Path.GetFullPath (Path.Combine (runtimePath, "..", "..", "include")); + if (Directory.Exists (includeDir)) + return includeDir; + return null; + } + + protected override string FindLibraries (string runtimePath) + { + var libPaths = new[]{ + // runtimePath=$prefix/lib/mandroid + Path.GetFullPath (Path.Combine (runtimePath, "..")), + // runtimePath=$prefix/lib/xamarin.android/xbuild/Xamarin/Android + Path.GetFullPath (Path.Combine (runtimePath, "lib", "host")), + }; + var requiredFile = "libmono-android.debug.dylib"; + foreach (var libPath in libPaths) { + var required = Path.Combine (libPath, requiredFile); + if (File.Exists (required)) + return libPath; + } + return null; + } + } +} diff --git a/src/Xamarin.AndroidTools/Sdks/MonoDroidSdkWindows.cs b/src/Xamarin.AndroidTools/Sdks/MonoDroidSdkWindows.cs new file mode 100644 index 00000000000..a91a622d6b2 --- /dev/null +++ b/src/Xamarin.AndroidTools/Sdks/MonoDroidSdkWindows.cs @@ -0,0 +1,114 @@ +// +// MonoDroidSdkWindows.cs +// +// Author: +// Michael Hutchinson +// Andreia Gaita +// +// Copyright (c) 2012 Xamarin Inc. (http://xamarin.com) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; + +namespace Xamarin.AndroidTools +{ + class MonoDroidSdkWindows : MonoDroidSdkBase + { + protected override string FindRuntime () + { + string monoAndroidPath = Environment.GetEnvironmentVariable ("MONO_ANDROID_PATH"); + if (!string.IsNullOrEmpty (monoAndroidPath)) { + if (Directory.Exists (monoAndroidPath) && ValidateRuntime (monoAndroidPath)) + return monoAndroidPath; + } + var assemblyLocation = Path.GetFullPath (Path.GetDirectoryName (GetType ().Assembly.Location)); + if (Directory.Exists (assemblyLocation) && ValidateRuntime (assemblyLocation)) + return assemblyLocation; + string xamarinSdk = Path.Combine (OS.ProgramFilesX86, "MSBuild", "Xamarin", "Android"); + return Directory.Exists (xamarinSdk) + ? xamarinSdk + : OS.ProgramFilesX86 + @"\MSBuild\Novell"; + } + + static readonly string[] RuntimeToFrameworkPaths = new []{ + // runtimePath=C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\MSBuild\Xamarin\Android + Path.Combine ("..", "..", "..", "Common7", "IDE", "ReferenceAssemblies", "Microsoft", "Framework","MonoAndroid"), + // runtimePath=??? I suspect this is leftover from VS 2015 + Path.Combine ("..", "..", "..", "Reference Assemblies", "Microsoft", "Framework", "MonoAndroid"), + // global reference assemblies directory + Path.Combine (OS.ProgramFilesX86, "Reference Assemblies", "Microsoft", "Framework", "MonoAndroid"), + // runtimePath=$prefix/lib/xamarin.android/xbuild/Xamarin/Android/, when using a local xamarin-android inverted build + Path.Combine ("..", "..", "..", "xbuild-frameworks", "MonoAndroid"), + }; + + protected override string FindFramework (string runtimePath) + { + foreach (var relativePath in RuntimeToFrameworkPaths) { + var fullPath = Path.GetFullPath (Path.Combine (runtimePath, relativePath)); + if (Directory.Exists (fullPath)) { + if (ValidateFramework (fullPath)) + return fullPath; + + // check to see if full path is the folder that contains each framework version, eg contains folders of the form v1.0, v2.3 etc + var subdirs = Directory.GetDirectories (fullPath, "v*").OrderBy (x => x).ToArray (); + foreach (var subdir in subdirs) { + if (ValidateFramework (subdir)) + return subdir; + } + } + } + + return null; + } + + protected override string FindBin (string runtimePath) + { + return runtimePath; + } + + protected override bool ValidateBin (string binPath) + { + if (!Directory.Exists (binPath)) + return false; + if (File.Exists (Path.Combine (binPath, "aapt2.exe"))) + return true; + return false; + } + + protected override string FindInclude (string runtimePath) + { + return Path.GetFullPath (Path.Combine (runtimePath, "include")); + } + + protected override string FindLibraries (string runtimePath) + { + return Path.GetFullPath (runtimePath); + } + + protected override IEnumerable GetVersionFileLocations () + { + yield return Path.GetFullPath (Path.Combine (RuntimePath, "Version")); + yield return Path.GetFullPath (Path.Combine (RuntimePath, "Version.txt")); + } + } +} diff --git a/src/Xamarin.AndroidTools/Sdks/RegistryEx.cs b/src/Xamarin.AndroidTools/Sdks/RegistryEx.cs new file mode 100644 index 00000000000..cb9f9c425cc --- /dev/null +++ b/src/Xamarin.AndroidTools/Sdks/RegistryEx.cs @@ -0,0 +1,135 @@ +// +// RegistryEx.cs +// +// Authors: +// Michael Hutchinson +// +// Copyright 2012 Xamarin Inc. All rights reserved. +// + +using System; +using System.Runtime.InteropServices; +using System.Text; + +namespace Xamarin.AndroidTools.Sdks +{ + internal static class RegistryEx + { + const string ADVAPI = "advapi32.dll"; + + public static UIntPtr CurrentUser = (UIntPtr)0x80000001; + public static UIntPtr LocalMachine = (UIntPtr)0x80000002; + + [DllImport (ADVAPI, CharSet = CharSet.Unicode, SetLastError = true)] + static extern int RegOpenKeyEx (UIntPtr hKey, string subKey, uint reserved, uint sam, out UIntPtr phkResult); + + [DllImport (ADVAPI, CharSet = CharSet.Unicode, SetLastError = true)] + static extern int RegQueryValueExW (UIntPtr hKey, string lpValueName, int lpReserved, out uint lpType, + StringBuilder lpData, ref uint lpcbData); + + [DllImport (ADVAPI, CharSet = CharSet.Unicode, SetLastError = true)] + static extern int RegSetValueExW (UIntPtr hKey, string lpValueName, int lpReserved, + uint dwType, string data, uint cbData); + + [DllImport (ADVAPI, CharSet = CharSet.Unicode, SetLastError = true)] + static extern int RegSetValueExW (UIntPtr hKey, string lpValueName, int lpReserved, + uint dwType, IntPtr data, uint cbData); + + [DllImport (ADVAPI, CharSet = CharSet.Unicode, SetLastError = true)] + static extern int RegCreateKeyEx (UIntPtr hKey, string subKey, uint reserved, string @class, uint options, + uint samDesired, IntPtr lpSecurityAttributes, out UIntPtr phkResult, out Disposition lpdwDisposition); + + [DllImport ("advapi32.dll", SetLastError = true)] + static extern int RegCloseKey (UIntPtr hKey); + + public static string GetValueString (UIntPtr key, string subkey, string valueName, Wow64 wow64) + { + UIntPtr regKeyHandle; + uint sam = (uint)Rights.QueryValue + (uint)wow64; + if (RegOpenKeyEx (key, subkey, 0, sam, out regKeyHandle) != 0) + return null; + + try { + uint type; + var sb = new StringBuilder (2048); + uint cbData = (uint)sb.Capacity; + if (RegQueryValueExW (regKeyHandle, valueName, 0, out type, sb, ref cbData) == 0) { + return sb.ToString (); + } + return null; + } finally { + RegCloseKey (regKeyHandle); + } + } + + public static void SetValueString (UIntPtr key, string subkey, string valueName, string value, Wow64 wow64) + { + UIntPtr regKeyHandle; + uint sam = (uint)(Rights.CreateSubKey | Rights.SetValue) + (uint)wow64; + uint options = (uint)Options.NonVolatile; + Disposition disposition; + if (RegCreateKeyEx (key, subkey, 0, null, options, sam, IntPtr.Zero, out regKeyHandle, out disposition) != 0) { + throw new Exception ("Could not open or create key"); + } + + try { + uint type = (uint)ValueType.String; + uint lenBytesPlusNull = ((uint)value.Length + 1) * 2; + var result = RegSetValueExW (regKeyHandle, valueName, 0, type, value, lenBytesPlusNull); + if (result != 0) + throw new Exception (string.Format ("Error {0} setting registry '{1}{2}' with value '{3}'='{4}'", + result, key, subkey, valueName, value)); + } finally { + RegCloseKey (regKeyHandle); + } + } + + [Flags] + enum Rights : uint + { + None = 0, + QueryValue = 0x0001, + SetValue = 0x0002, + CreateSubKey = 0x0004, + EnumerateSubKey = 0x0008, + } + + enum Options + { + BackupRestore = 0x00000004, + CreateLink = 0x00000002, + NonVolatile = 0x00000000, + Volatile = 0x00000001, + } + + public enum Wow64 : uint + { + Key64 = 0x0100, + Key32 = 0x0200, + } + + enum ValueType : uint + { + None = 0, //REG_NONE + String = 1, //REG_SZ + UnexpandedString = 2, //REG_EXPAND_SZ + Binary = 3, //REG_BINARY + DWord = 4, //REG_DWORD + DWordLittleEndian = 4, //REG_DWORD_LITTLE_ENDIAN + DWordBigEndian = 5, //REG_DWORD_BIG_ENDIAN + Link = 6, //REG_LINK + MultiString = 7, //REG_MULTI_SZ + ResourceList = 8, //REG_RESOURCE_LIST + FullResourceDescriptor = 9, //REG_FULL_RESOURCE_DESCRIPTOR + ResourceRequirementsList = 10, //REG_RESOURCE_REQUIREMENTS_LIST + QWord = 11, //REG_QWORD + QWordLittleEndian = 11, //REG_QWORD_LITTLE_ENDIAN + } + + enum Disposition : uint + { + CreatedNewKey = 0x00000001, + OpenedExistingKey = 0x00000002, + } + } +} diff --git a/src/Xamarin.AndroidTools/Sessions/AndroidCommandSession.cs b/src/Xamarin.AndroidTools/Sessions/AndroidCommandSession.cs new file mode 100644 index 00000000000..2e8191dc897 --- /dev/null +++ b/src/Xamarin.AndroidTools/Sessions/AndroidCommandSession.cs @@ -0,0 +1,320 @@ +// +// AndroidCommandConnection.cs +// +// Author: +// Rodrigo Moya +// Stephen Shaw +// +// Based on Xamarin.MacDev.IPhoneCommandConnection +// Author: +// Michael Hutchinson +// Rolf Bjarne Kvinge +// +// Copyright (c) 2011-2014 Xamarin Inc. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Net; +using System.Net.Sockets; +using System.Text; +using System.Threading; +using Mono.AndroidTools; +using Mono.AndroidTools.Util; + +namespace Xamarin.AndroidTools +{ + public abstract class AndroidCommandSession : IDisposable + { + readonly object commandSessionLock = new object (); + readonly List streams = new List (); + Stream reusableStream; + bool disposed; + bool connected_once; + + public IPAddress Address { + get; + protected set; + } + + public int Port { + get; + protected set; + } + + protected AndroidCommandSession (IPAddress ipAddress, int port = 0) + { + Address = ipAddress ?? IPAddress.Loopback; + Port = port; + } + + #region Internal command execution methods + + protected abstract IAsyncResult BeginConnectStream (AsyncCallback callback, object state); + protected abstract Stream EndConnectStream (IAsyncResult result); + + IAsyncResult BeginExecuteCommand (string command, bool consumeStream, AsyncCallback callback = null, object state = null) + { + var data = Encoding.UTF8.GetBytes (command); + if (data.Length > byte.MaxValue) + throw new ArgumentException (string.Format ("Command '{0}' has length {1}, which exceeds maximum length {2}", command, data.Length, byte.MaxValue), "command"); + + var buffer = new byte [data.Length + 1]; + buffer [0] = (byte) data.Length; + Array.Copy (data, 0, buffer, 1, data.Length); + + var ar = new CommandAsyncResult (callback, state) { + Buffer = buffer, + ConsumeStream = consumeStream, + }; + + //try to re-use an existing stream + lock (commandSessionLock) { + if (reusableStream != null) { + ar.Stream = reusableStream; + //if we're going to consume the stream, don't leave it around for others to re-use + if (consumeStream) { + reusableStream = null; + } + } + } + + if (ar.Stream != null) { + ExecuteCommand_BeginWriteCommand (ar); + } else { + BeginConnectStream (ExecuteCommand_ConnectedCommandStream, ar); + } + + return ar; + } + + void ExecuteCommand_ConnectedCommandStream (IAsyncResult ar) + { + var r = (CommandAsyncResult) ar.AsyncState; + try { + r.Stream = EndConnectStream (ar); + ExecuteCommand_BeginWriteCommand (r); + } catch (Exception ex) { + r.CompleteWithError (ex); + } + } + + static void ExecuteCommand_DiscardStream (Stream stream) + { + var discard = new byte [] { 7, (byte) 'd', (byte) 'i', (byte) 's', (byte) 'c', (byte) 'a', (byte) 'r', (byte) 'd' }; + stream.BeginWrite (discard, 0, discard.Length, ar => ((Stream)ar.AsyncState).Dispose (), stream); + } + + void ExecuteCommand_BeginWriteCommand (CommandAsyncResult r) + { + lock (commandSessionLock) + connected_once = true; + r.Stream.BeginWrite (r.Buffer, 0, r.Buffer.Length, ExecuteCommand_WroteCommand, r); + } + + void ExecuteCommand_WroteCommand (IAsyncResult ar) + { + var r = (CommandAsyncResult) ar.AsyncState; + try { + r.Stream.EndWrite (ar); + //if the stream can be re-used, keep it + if (!r.ConsumeStream) { + lock (commandSessionLock) { + if (reusableStream == null) { + reusableStream = r.Stream; + streams.Add (r.Stream); + r.Stream = null; + } + } + //if there was already a re-usable stream from another thread, discard this one + if (r.Stream != null && r.Stream != reusableStream) { + ExecuteCommand_DiscardStream (r.Stream); + } + } + r.Complete (); + } catch (Exception ex) { + r.CompleteWithError (ex); + } + } + + void CancelExecuteCommand (IAsyncResult asyncResult) + { + ((CommandAsyncResult)asyncResult).Cancel (); + } + + Stream EndExecuteCommand (IAsyncResult result) + { + var r = (CommandAsyncResult) result; + r.CheckError (); + return r.ConsumeStream? r.Stream : null; + } + + IAsyncResult BeginSendSkipDebugger (AsyncCallback callback = null, object state = null) + { + return BeginExecuteCommand ("start debugger: no", consumeStream: false, callback: callback, state: state); + } + + void WriteProfilerOutputToFile (Stream stream, string outputFile) + { + try { + using (var fs = new FileStream (outputFile, FileMode.OpenOrCreate, FileAccess.Write, FileShare.ReadWrite)) { + stream.CopyTo (fs); + } + } catch (Exception ex) { + AndroidLogger.LogWarning ("Exception in the profile-data reading thread: {0}", ex); + } + + Console.WriteLine ("Finished writer thread"); + } + + #endregion + + #region CommandAsyncResult + + class CommandAsyncResult : AggregateAsyncResult + { + public CommandAsyncResult (AsyncCallback callback, object state) : base (callback, state) + { + } + + public void Cancel () + { + CompleteWithError (new OperationCanceledException ()); + if (Stream != null) { + Stream.Dispose (); + Stream = null; + } + } + + public byte[] Buffer; + public bool ConsumeStream; + public Stream Stream; + } + + #endregion + + #region Public API + + public bool IsConnected { + get { + lock (commandSessionLock) { + return connected_once; + } + } + } + + public IAsyncResult BeginHandshake (AsyncCallback callback = null, object state = null) + { + var r = new AggregateAsyncResult (callback, state); + BeginExecuteCommand ("ping", false, Handshake_SentPing, r); + return r; + } + + void Handshake_SentPing (IAsyncResult ar) + { + var r = (AggregateAsyncResult) ar.AsyncState; + try { + EndExecuteCommand (ar); + //we know that there will be a reusable string if the ping succeeded + //and nothing should be touching the tream during our handshake + r.Arg1 = reusableStream; + r.Arg2 = new byte[5]; + r.Arg1.BeginReadFull (r.Arg2, 0, r.Arg2.Length, Handshake_ReadPong, r); + } catch (Exception ex) { + r.CompleteWithError (ex); + } + } + + static void Handshake_ReadPong (IAsyncResult ar) + { + var r = (AggregateAsyncResult) ar.AsyncState; + try { + r.Arg1.EndReadFull (ar); + var pong = Encoding.ASCII.GetString (r.Arg2); + if (pong != "pong\0") + throw new Exception ("Bad handshake: '" + pong + "'"); + r.Complete(); + } catch (Exception ex) { + r.CompleteWithError (ex); + } + } + + public void EndHandshake (IAsyncResult result) + { + var r = (AggregateAsyncResult) result; + r.CheckError (); + } + + public void StartLogProfiler (string outputFile, string profilerConfiguration, string heapshotMode, AsyncCallback callback = null, object state = null) + { + EndExecuteCommand (BeginSendSkipDebugger ()); + + var outputDir = Path.GetDirectoryName (outputFile); + Directory.CreateDirectory (outputDir); + + var stream = EndExecuteCommand (BeginExecuteCommand ("start profiler: " + profilerConfiguration, true, callback, state)); + new Thread (() => { + try { + WriteProfilerOutputToFile (stream, outputFile); + } finally { + stream.Dispose (); + } + }) { + IsBackground = true, + Name = "Profiler output writer" + }.Start (); + } + + public void Stop () + { + try { + // Don't try forever to send 'exit process', the user might never have + // started the app on the device. + var exit = true; + + lock (commandSessionLock) { + exit = connected_once; + } + + if (exit && !disposed) { + var ar = BeginExecuteCommand ("exit process", consumeStream: true); + ar.AsyncWaitHandle.WaitOne (100); + EndExecuteCommand (ar); + } + } catch (ObjectDisposedException) { + } catch (SocketException ex) { + Console.WriteLine ("Error while requesting the application to exit: " + ex.Message); + } finally { + Dispose (); // make sure everything is cleaned up + } + } + + #endregion + + #region IDisposable implementation + + ~AndroidCommandSession () + { + Dispose (false); + } + + public void Dispose () + { + if (disposed) + return; + + lock (commandSessionLock) { + if (disposed) + return; + disposed = true; + GC.SuppressFinalize (this); + } + + Dispose (true); + } + + protected abstract void Dispose (bool disposing); + + #endregion + } +} diff --git a/src/Xamarin.AndroidTools/Sessions/AndroidConnectCommandSession.cs b/src/Xamarin.AndroidTools/Sessions/AndroidConnectCommandSession.cs new file mode 100644 index 00000000000..8aebf97c323 --- /dev/null +++ b/src/Xamarin.AndroidTools/Sessions/AndroidConnectCommandSession.cs @@ -0,0 +1,65 @@ +// +// AndroidConnectCommandConnection.cs +// +// Author: +// Stephen Shaw +// +// Copyright (c) 2014 Xamarin Inc. (http://xamarin.com) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +using System; +using System.IO; +using System.Net; +using System.Net.Sockets; +using System.Threading; + +namespace Xamarin.AndroidTools +{ + class AndroidConnectCommandSession : AndroidCommandSession + { + Socket client; + + public AndroidConnectCommandSession (IPAddress ipAddress = null, int port = 0) : base (ipAddress, port) + { + client = new Socket (AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); + client.ExclusiveAddressUse = false; + } + + protected override IAsyncResult BeginConnectStream (AsyncCallback callback, object state) + { + return client.BeginConnect (new IPEndPoint (Address, Port), callback, state); + } + + protected override Stream EndConnectStream (IAsyncResult result) + { + client.EndConnect (result); + client.NoDelay = true; + return new NetworkStream (client, true); + } + + protected override void Dispose (bool disposing) + { + if (disposing && client != null) { + client.Dispose (); + client = null; + } + } + } +} diff --git a/src/Xamarin.AndroidTools/Sessions/AndroidDeploySession.cs b/src/Xamarin.AndroidTools/Sessions/AndroidDeploySession.cs new file mode 100644 index 00000000000..5a621424864 --- /dev/null +++ b/src/Xamarin.AndroidTools/Sessions/AndroidDeploySession.cs @@ -0,0 +1,1054 @@ +// +// AndroidDeploySession.cs +// +// Authors: +// Jonathan Pobst +// Michael Hutchinson +// +// Copyright 2012-2013 Xamarin Inc. All rights reserved. +// + +using System; +using System.IO; +using System.Threading; +using System.Collections.Generic; +using System.Linq; +using Mono.AndroidTools; +using Mono.AndroidTools.Adb; +using System.Text.RegularExpressions; +using System.Threading.Tasks; +using System.Diagnostics; + +namespace Xamarin.AndroidTools +{ + public class AndroidDeploySession + { + [Obsolete ("Use PackageApiLevel")] + public string PackageTargetApi { + get { return PackageApiLevel.ToString (); } + set { PackageApiLevel = int.Parse (value); } + } + + public int PackageApiLevel { get; set; } + public AndroidDevice Device { get; private set; } + public string PackageSupportedArchs { get; set; } + public IProgressNotifier ProgressReporter { get; set; } + + /// Whether the user wants unstripped binaries and BCL debug symbols + public bool ProvideFullDebugRuntime { get; set; } + + public bool UsesSharedRuntime { get; set; } + public bool PreserveUserData { get; set; } + + public bool ForcePackageInstall { get; set; } + public bool IsFastDev { get; set; } + public string PackageName { get; set; } + public string PackageFile { get; set; } + + public string AaptToolPath { get; set; } + + [Obsolete ("DeltaInstall support has been removed.", error: true)] + public bool AllowDeltaInstall { + get { throw new NotSupportedException ("DeltaInstall support has been removed."); } + set { throw new NotSupportedException ("DeltaInstall support has been removed."); } + } + + [Obsolete ("Activity execution at installation is no longer needed")] + public string Activity { get; set; } + + [Obsolete ("Use FastDevAssembliesProvider")] + public List Assemblies { get; set; } + + /// + /// Gets the name of the activity launched to create the FastDev assembly directory. + /// + [Obsolete ("Activity execution at installation is no longer needed")] + public Func FastDevActivityProvider { get; set; } + + /// + /// Gets the collection of assemblies to be deployed by FastDev. + /// + public Func> FastDevAssembliesProvider { get; set; } + + /// + /// Gets the collection of dexes to be deployed by FastDev. + /// + public Func> FastDevNativeLibrariesProvider { get; set; } + + /// + /// Gets the collection of dexes to be deployed by FastDev. + /// + public Func> FastDevDexesProvider { get; set; } + + /// + /// Gets the collection of packaged_resources to be deployed by FastDev. + /// + public Func> FastDevPackagedResourcesProvider { get; set; } + + /// + /// Gets the collection of resources to be deployed by FastDev. + /// + public Func> FastDevResourcesProvider { get; set; } + + /// + /// Gets the collection of typemap files to be deployed by FastDev. + /// + public Func> FastDevTypemapsProvider { get; set; } + + /// + /// If non-null, will block on this before deploying actual assemblies. Returns true if the package changed. + /// + public Task PackagingTask { get; set; } + + public bool External { get; private set; } + public string PackageRemotePath { get; private set; } + + private CancellationToken token; + private IList packages; + private string fastDevRemotePath; + + public AndroidDeploySession (AndroidDevice device, IList packages = null) + { + Device = device; + this.packages = packages; + + PreserveUserData = true; + } + + public async Task StartAsync (CancellationToken token) + { + await RunLoggedAsync (token).ConfigureAwait(false); + } + + async Task RunLoggedAsync (CancellationToken token) + { + try { + await RunAsync (token).ConfigureAwait(false); + } catch (Exception ex) { + // make sure we observe these exceptions otherwise they get logged as unhandled + // so do this before the cancellation check below + var aex = ex as AggregateException; + if (aex != null) { + ex = aex.Flatten ().InnerException; + } + + if (token.IsCancellationRequested) { + AndroidLogger.LogInfo ("Deployment cancelled"); + token.ThrowIfCancellationRequested (); + } + + if (ex is OperationCanceledException) + throw ex; + + AndroidLogger.LogError ("Deployment failed", ex); + if (ex is AndroidDeploymentException) { + throw; + } + + if (ex is DeviceNotFoundException || ex is DeviceDisconnectedException) { + throw new AndroidDeploymentException (AndroidDeploymentFailureReason.DeviceDisconnected, ex); + } + + throw new AndroidDeploymentException (AndroidDeploymentFailureReason.InternalError, ex); + } + } + + [Obsolete ("Use StartAsync")] + public bool Start (CancellationToken token) + { + try { + RunLoggedAsync (token).Wait (); + return true; + } catch (OperationCanceledException) { + // ignore + } catch (AndroidDeploymentException ex) { + string title, detail; + ex.GetNiceExplanation (out title, out detail); + ProgressReporter.ShowErrorDialog (title, detail, ex); + } + return false; + } + + async Task RunAsync (CancellationToken token) + { + this.token = token; + + await Device.EnsureProperties (token).ConfigureAwait(false); + + // Make sure the device arch is supported + if (!string.IsNullOrWhiteSpace (PackageSupportedArchs) && !Device.CanRunPackageArchitecture (PackageSupportedArchs)) { + throw new AndroidDeploymentException (AndroidDeploymentFailureReason.ArchitectureNotSupported, this); + } + + if (UsesSharedRuntime && !Device.CanRunPackageArchitecture (MonoDroidSdk.SharedRuntimeAbis)) { + throw new AndroidDeploymentException (AndroidDeploymentFailureReason.ArchitectureNotSupportedBySharedRuntime, this); + } + + string redirectStdio = Device.Properties.Get ("log.redirect-stdio"); + if (redirectStdio != null && string.Equals ("true", redirectStdio.Trim (), StringComparison.OrdinalIgnoreCase)) { + throw new AndroidDeploymentException (AndroidDeploymentFailureReason.StdioRedirectionEnabled); + } + + if (packages == null) { + packages = await Device.GetPackagesAsync (PackageApiLevel, PackageName, ProvideFullDebugRuntime, token, ProgressReporter); + } + + if (packages == null) { + throw new AndroidDeploymentException (AndroidDeploymentFailureReason.FailedToGetPackageList); + } + + if (UsesSharedRuntime) { + await EnsureCorrectSharedRuntimes (); + } + + if (!UsesSharedRuntime && IsFastDev) { + if (string.IsNullOrWhiteSpace (Device.Properties.MonoLog)) { + await Device.SetProperty ("debug.mono.log", "gc"); + } + } + + //packaging can be done in parallel with installing the shared runtime, but need to block on it + //before installing the app package + if (PackagingTask != null && PackagingTask.Status != TaskStatus.RanToCompletion) { + ProgressReporter.BeginStep ("Waiting for packaging to complete"); + try { + await PackagingTask; + } catch (Exception ex) { + if (ex is AndroidDeploymentException || ex is OperationCanceledException) + throw; + throw new AndroidDeploymentException (AndroidDeploymentFailureReason.PackagingFailed, ex); + } + ProgressReporter.EndStep (null); + } + + await InstallPackage (); + + if (IsFastDev) { + try { + await FastDevAsync (true); + } catch { + ShowProgressText ("Fast dev didn't succeed, trying another location...", token); + + await FastDevAsync (false); + } + } else { + var fastDevPath = await GetFastDevRemotePathAsync (true); + if (!string.IsNullOrEmpty (fastDevPath)) { + ShowProgressText ("Fast dev is disabled, removing fast dev directory: " + fastDevPath, token); + await Device.DeleteDirectory (fastDevPath, true, token); + } + fastDevPath = await GetFastDevRemotePathAsync (false); + if (!string.IsNullOrEmpty (fastDevPath)) { + ShowProgressText ("Fast dev is disabled, removing fast dev directory: " + fastDevPath, token); + await Device.DeleteDirectory (fastDevPath, true, token); + } + } + } + + async Task EnsureCorrectSharedRuntimes () + { + // See if there are any old runtimes we need to remove + await RemoveOldRuntimes (); + + // Install the shared runtime and platform + + try { + await CheckAndInstallSharedRuntimeAsync (); + } catch (Exception ex) { + if (ex is InsufficientSpaceException) { + throw new AndroidDeploymentException (AndroidDeploymentFailureReason.InsufficientSpaceForRuntime, ex); + } + throw; + } + + try { + await InstallSharedPlatformAsync (); + } catch (Exception ex) { + if (ex is InsufficientSpaceException) { + throw new AndroidDeploymentException (AndroidDeploymentFailureReason.InsufficientSpaceForPlatform, ex); + } + if (ex is SdkNotSupportedException) { + throw new AndroidDeploymentException (AndroidDeploymentFailureReason.SdkNotSupportedByDevice, ex); + } + throw; + } + } + + async Task RemoveOldRuntimes () + { + // See if we were asked to cancel + if (token.IsCancellationRequested) + return; + + // See if there are any old runtimes we need to remove + var old_runtimes = packages.GetOldRuntimesAndPlatforms (PackageApiLevel).ToList (); + if (old_runtimes.Count == 0) { + return; + } + + ProgressReporter.BeginStep ("Removing old runtimes"); + foreach (var runtime in old_runtimes) { + ShowProgressText (string.Format ("Removing old runtime: {0} [{1}]..", runtime.Name, runtime.Version), token); + await Device.UninstallPackage (runtime.Name, false, token); + packages.Remove (runtime); + } + ProgressReporter.EndStep (null); + } + + // Returns whether a new shared runtime was installed + private async Task CheckAndInstallSharedRuntimeAsync () + { + // See if we were asked to cancel + if (token.IsCancellationRequested) + return false; + + // See if the device already has the shared runtime + var needs_runtime = !packages.IsCurrentRuntimeInstalled (); + + // See if there is a runtime on the device that + // we cannot determine the version of + if (needs_runtime && packages.IsUnknownRuntimeInstalled ()) { + ProgressReporter.ShowErrorDialog ( + "Unknown Runtime", + "There is a shared runtime on the device whose version cannot be determined. " + + "A new runtime will not be deployed. If the runtime needs to be replaced, please manually " + + "remove it from the device."); + needs_runtime = false; + } + + // If needed, install the shared runtime + if (!needs_runtime) + return false; + + ProgressReporter.BeginStep ("Installing shared runtime"); + await Device.InstallSharedRuntimeAsync (ProvideFullDebugRuntime, token, ProgressReporter); + ProgressReporter.EndStep (null); + packages.Add (new AndroidInstalledPackage (AndroidPackageListExtensions.runtimeName, null, MonoDroidSdk.SharedRuntimeVersion)); + + return true; + } + + // Returns whether a new shared platform was installed + private async Task InstallSharedPlatformAsync () + { + // See if we were asked to cancel + if (token.IsCancellationRequested) + return false; + + // See if the device already has the shared platform + var needs_platform = !packages.IsCurrentPlatformInstalled (PackageApiLevel); + + // See if there is a runtime on the device that + // we cannot determine the version of + if (needs_platform && packages.IsUnknownPlatformInstalled (PackageApiLevel)) { + ProgressReporter.ShowErrorDialog ( + "Unknown Platform Runtime", + "There is a platform support runtime on the device whose version cannot be determined. " + + "A new platform support runtime will not be deployed. If the platform support runtime needs " + + "to be replaced, please manually remove it from the device."); + needs_platform = false; + } + + var platform_file = await PlatformPackage.GetPlatformPackagePathAsync (PackageApiLevel, AaptToolPath, ProgressReporter, token); + + var sharedRuntimePackage = string.Format (AndroidPackageListExtensions.platformName, PackageApiLevel); + var useXamarinPackage = sharedRuntimePackage; + var version = PlatformPackage.GetPlatformPackageVersion (PackageApiLevel, ref useXamarinPackage); + + // If needed, install the shared platform + if (needs_platform) { + ProgressReporter.BeginStep ("Installing platform framework"); + ShowProgressText (string.Format ("Installing the API {0} platform framework..", PackageApiLevel), token); + await Device.InstallSharedPlatformAsync (platform_file, PackageApiLevel, ProgressReporter.ReportProgress, token); + ProgressReporter.EndStep (null); + packages.Add (new AndroidInstalledPackage (sharedRuntimePackage, null, version)); + } + + if (useXamarinPackage.StartsWith ("Xamarin", StringComparison.OrdinalIgnoreCase)) { + ShowProgressText (string.Format ("Removing old {0} framework.", sharedRuntimePackage), token); + await Device.UninstallPackage (sharedRuntimePackage, false, token); + } + + return needs_platform; + } + + private void ShowProgressText (string text, CancellationToken token) + { + if (!token.IsCancellationRequested && ProgressReporter != null) + ProgressReporter.ReportMessage (text); + } + + async Task InstallPackage () + { + bool force = ForcePackageInstall || (PackagingTask != null && PackagingTask.Result); + + if (!force && packages.ContainsPackage (PackageName)) { + // If we didn't uninstall/reinstall, the app might already be running, + // which makes our attempts to start a new one to debug fail, so we + // are going to kill the already running copy. + AndroidLogger.LogDebug ("InstallPackage", "Checking whether app {0} is running", PackageName); + var pid = await Device.GetProcessId (PackageName, token); + if (pid == 0) { + AndroidLogger.LogDebug ("InstallPackage", "App was not running, skipping kill"); + return; + } + AndroidLogger.LogDebug ("InstallPackage", "Killing app"); + + ProgressReporter.BeginStep ("Terminating running application"); + ShowProgressText ("Terminating running application...", token); + await Device.KillProcessAndWaitForExit (PackageName, token); + ProgressReporter.EndStep (null); + return; + } + + token.ThrowIfCancellationRequested (); + + if (packages.ContainsPackage (PackageName)) { + ProgressReporter.BeginStep ("Removing previous version of application"); + ShowProgressText ("Removing previous version of application...", token); + + await Device.UninstallPackage (PackageName, PreserveUserData, token); + ProgressReporter.EndStep (null); + } + + token.ThrowIfCancellationRequested (); + + ProgressReporter.BeginStep ("Installing application on device"); + ShowProgressText ("Copying application to device...", token); + + try { + //TODO: check the package ABI by poking inside the apk, if PackageSupportedArchs was not set + await Device.PushAndInstallPackageAsync (new PushAndInstallCommand { + ApkFile = PackageFile, + PackageName = PackageName, + ReInstall = false, + NotifyProgress = ProgressReporter.ReportProgress, + }, token); + } catch (Exception exception) { + var ex = exception; + if (exception is AggregateException aex) { + ex = aex.Flatten ().InnerException; + } + if (ex is InsufficientSpaceException) { + throw new AndroidDeploymentException (AndroidDeploymentFailureReason.InsufficientSpaceForPackage, ex); + } + if (ex is SdkNotSupportedException) { + throw new AndroidDeploymentException (AndroidDeploymentFailureReason.SdkNotSupportedByDevice, ex); + } + if (ex is IncompatibleCpuAbiException) { + throw new AndroidDeploymentException (AndroidDeploymentFailureReason.ArchitectureNotSupported, this, ex); + } + if (!ShouldThrowIfPackageInstallFailed (ex as PackageAlreadyExistsException, token)) { + ProgressReporter.EndStep (null); + return; + } + throw; + } + packages.Add (new AndroidInstalledPackage (PackageName, null)); + ProgressReporter.EndStep (null); + } + + bool ShouldThrowIfPackageInstallFailed (PackageAlreadyExistsException e, CancellationToken token) + { + if (e == null) + return true; + + int s = (e.PackageFile ?? "").LastIndexOf ('/'); + string apkBasename = s >= 0 ? e.PackageFile.Substring (s+1) : e.PackageFile; + + // If the runtime already exists, ignore the error + // Sometimes android doesn't report it's installed when it is :/ + if (apkBasename != Path.GetFileName (PackageFile)) + return false; + + // Oops; things have gotten wedged (stale/interrupted install?) + // The file we tried to upload already exists on the device! + // Delete and try again. + ShowProgressText (string.Format ("Package '{0}' already exists. Retrying...", PackageName), token); + try { + // NOTE We NEED to delete the cache data too other wise the install will fail. + Device.DeleteFile (e.PackageFile, true, token).Wait (token); + } catch { + // Ebil, yes, but... + } + ShowProgressText (string.Format ("Forcing complete uninstall of '{0}'...", PackageName), token); + Device.UninstallPackage (PackageName, false, token).Wait (token); + ShowProgressText (string.Format ("Installing '{0}'...", PackageName), token); + Device.PushAndInstallPackageAsync (new PushAndInstallCommand { + ApkFile = PackageFile, + ReInstall = false, + NotifyProgress = ProgressReporter.ReportProgress + }, token).Wait (token); + return false; + } + + async Task FastDevAsync (bool useExternal) + { + var dest = await GetFastDevRemotePathAsync (useExternal); + if (dest == null) { + throw new AndroidDeploymentException (AndroidDeploymentFailureReason.FailedToDetermineFastDevPath); + } + + ShowProgressText ("Using fast dev path: " + dest, token); + + await InstallAssemblies (dest, token); + // do not call these before InstallAssemblies otherwise InstallAssemblies will clean up resources. + await InstallNativeLibraries (dest, token); + await InstallDexes (dest, token); + await InstallPackagedResources (dest, token); + await InstallTypemaps (dest, token); + } + + async Task GetFastDevRemotePathAsync (bool useExternal) + { + ShowProgressText ("Getting installation path...", token); + + if (!string.IsNullOrEmpty (fastDevRemotePath) && !string.IsNullOrEmpty (PackageRemotePath)) { + ShowProgressText ($"Using cached value for installation path: {fastDevRemotePath}", token); + ShowProgressText ($"Using cached value for package remote path: {PackageRemotePath}", token); + return fastDevRemotePath; + } + + if (useExternal) { + External = true; + PackageRemotePath = await Device.GetPackageRemotePathAsync (PackageName, token); + +#pragma warning disable CS0618 // Type or member is obsolete + fastDevRemotePath = await Device.GetFastDevRemotePathExternalAsync (PackageName, token); +#pragma warning restore CS0618 + + return fastDevRemotePath; + } + + var result = await Device.GetFastDevRemotePathAsync (PackageName, token); + + PackageRemotePath = result.PackageRemotePath; + External = result.IsExternal; + fastDevRemotePath = result.Root; + + return fastDevRemotePath; + } + + static readonly HashSet AssemblyExtensions = new HashSet(StringComparer.OrdinalIgnoreCase) { + ".dll", + ".mdb", + }; + + async Task InstallAssemblies (string destinationPath, CancellationToken token) + { + token.ThrowIfCancellationRequested (); + + #pragma warning disable 618 + IEnumerable assemblies = FastDevAssembliesProvider != null? FastDevAssembliesProvider () : Assemblies; + #pragma warning restore 618 + + var progress = ProgressReporter; + var device = Device; + + var action = ForcePackageInstall? AdbSyncAction.Copy : AdbSyncAction.CopyIfNewer; + + var cultureDirectories = new Dictionary (); + var asmOverrideDir = new AdbSyncDirectory (Path.GetFileName (destinationPath), action, true); + + foreach (var a in assemblies) { + string culture, name; + AdbSyncDirectory dir; + if (TryGetSatelliteCultureAndFileName (a, out culture, out name) && culture != null) { + if (!cultureDirectories.TryGetValue (culture, out dir)) { + cultureDirectories [culture] = dir = new AdbSyncDirectory (culture, action, true); + asmOverrideDir.Add (dir); + } + } else { + name = Path.GetFileName (a); + dir = asmOverrideDir; + } + dir.Add (new AdbSyncFile (a, name, action)); + } + + var files = await device.ListFilesAsync (destinationPath.Replace ('\\', '/'), token); + + foreach (var remoteFile in files.Where (x => AssemblyExtensions.Contains (Path.GetExtension (x)))) { + if (!assemblies.Any (x => Path.GetFileName (x) == Path.GetFileName (remoteFile))) { + asmOverrideDir.Add (AdbSyncItem.Delete (remoteFile)); + } + } + + ShowProgressText ("Synchronizing assemblies...", token); + ProgressReporter.BeginStep ("Synchronizing assemblies"); + + try { + if (destinationPath.StartsWith ("/data", StringComparison.Ordinal)) { + WaitForRemoteDirCreation (destinationPath, token); + } + + token.ThrowIfCancellationRequested (); + + var t = device.PushSyncItems (asmOverrideDir, + Path.GetDirectoryName (destinationPath).Replace ('\\', '/'), + new AdbSyncClient.PushOptions () { + DryRun = false, + RemoveUnknown = true, + CheckTimestamps = true, + NotifySync = (a) => AndroidLogger.LogDebug ("NotifySync", "{0} {1} {2} {3}", a.Kind, a.LocalPath, a.RemotePath, a.Size), + NotifyPhase = (b) => AndroidLogger.LogDebug ("NotifyPhase", "{0}", b), + NotifyProgress = progress.ReportProgress, + RemoveBeforeCopy = false, + }, token); + await t; + } catch (Exception ex) { + if (ex is DeviceNotFoundException + || ex is DeviceDisconnectedException + || ex is AndroidDeploymentException + || ex is OperationCanceledException) + throw; + + throw new AndroidDeploymentException (AndroidDeploymentFailureReason.FailedToSynchronizeFastDevAssemblies, ex); + } finally { + ProgressReporter.EndStep (null); + } + } + + + Task InstallNativeLibraries (string destinationPath, CancellationToken token) + { + // native lib overrides are copied to .__override__ like assemblies, so we follow that. + return InstallFastDevFiles (destinationPath, FastDevNativeLibrariesProvider, "native libs", new string [] { ".__override__", "lib" }, removeUnknown: true, token: token); + } + + Task InstallDexes (string destinationPath, CancellationToken token) + { + return InstallFastDevFiles (destinationPath, FastDevDexesProvider, "dexes", new string [] { ".__override__", "dexes" }, removeUnknown: true, token: token); + } + + Task InstallPackagedResources (string destinationPath, CancellationToken token) + { + return InstallFastDevFiles (destinationPath, FastDevPackagedResourcesProvider, "resources", new string [] { ".__override__", "packaged" }, removeUnknown: true, token: token); + } + + Task InstallTypemaps (string destinationPath, CancellationToken token) + { + return InstallFastDevFiles (destinationPath, FastDevTypemapsProvider, "typemaps", new string [] { ".__override__", "typemaps" }, removeUnknown: true, token: token); + } + + async Task InstallFastDevFiles (string destinationPath, Func> filesProvider, string fileKind, string [] syncDirs, bool removeUnknown, CancellationToken token) + { + if (filesProvider == null) { + ShowProgressText ($"Skipping {fileKind}, not configured...", token); + return; + } + + token.ThrowIfCancellationRequested (); + + var progress = ProgressReporter; + var device = Device; + + var action = ForcePackageInstall? AdbSyncAction.Copy : AdbSyncAction.CopyIfNewer; + + AdbSyncDirectory overrideDir = null, destinationDir = null; + foreach (var syncDir in syncDirs.Reverse ()) { + overrideDir = new AdbSyncDirectory (syncDir, action, removeUnknown, Enumerable.Repeat (overrideDir, overrideDir == null ? 0 : 1)); + destinationDir = destinationDir ?? overrideDir; + } + + var files = await device.ListFilesAsync (Path.Combine (destinationPath, destinationDir.Name).Replace ('\\', '/'), token); + + var localFiles = filesProvider (); + foreach (var remoteFile in files) { + if (string.IsNullOrEmpty (remoteFile)) + continue; + if (!localFiles.Any (x => Path.GetFileName (x) == Path.GetFileName (remoteFile))) + destinationDir.Add (AdbSyncItem.Delete (remoteFile)); + } + + foreach (var a in localFiles) { + AdbSyncDirectory dir; + string name = Path.GetFileName (a); + dir = destinationDir; + dir.Add (new AdbSyncFile (a, name, action)); + } + + ShowProgressText (string.Format ("Synchronizing {0}...", fileKind), token); + ProgressReporter.BeginStep (string.Format ("Synchronizing {0}", fileKind)); + + try { + if (destinationPath.StartsWith ("/data", StringComparison.Ordinal)) { + WaitForRemoteDirCreation (destinationPath, token); + } + + token.ThrowIfCancellationRequested (); + + var t = device.PushSyncItems (overrideDir, + Path.GetDirectoryName (destinationPath).Replace ('\\', '/'), + new AdbSyncClient.PushOptions () { + DryRun = false, + RemoveUnknown = true, + RemoveBeforeCopy = false, + CheckTimestamps = true, + NotifySync = (a) => AndroidLogger.LogDebug ("NotifySync", "{0} {1} {2} {3}", a.Kind, a.LocalPath, a.RemotePath, a.Size), + NotifyPhase = (b) => AndroidLogger.LogDebug ("NotifyPhase", "{0}", b), + NotifyProgress = progress.ReportProgress + }, token); + + await t; + } catch (Exception ex) { + if (ex is DeviceNotFoundException + || ex is DeviceDisconnectedException + || ex is AndroidDeploymentException + || ex is OperationCanceledException) + throw; + + throw new AndroidDeploymentException (AndroidDeploymentFailureReason.FailedToSynchronizeFastDevResources, ex); + } finally { + ProgressReporter.EndStep (null); + } + } + + void WaitForRemoteDirCreation (string destinationPath, CancellationToken token) + { + var device = Device; + var packageName = PackageName; + int count = 0; + var wait = new ManualResetEvent (false); + // the logic here is: + // check if the target directory exists + // if it isn't, don't try to push, start the app to get them created + // directories exist, push assemblies + // + // Note: checking if the directory exists before trying to push + // is important because an emulator or rooted device may allow the + // push to succeed, but it will create everything with root permissions. + // If the app then tries to do anything with the files in the private dir, + // things will fail. Directories must be created by monodroid for permissions + // to be set correctly. + // + // All this is important for devices running Android 3.0+. Before that, just + // killing the process was enough for monodroid to run and create the directories, + // so by the time we reach this point, the directory already exists. + + var fi = device.GetRemoteFileInfo (destinationPath, token).Result; + AndroidLogger.LogDebug ("Stat", "FileInfo for {0}: {1}", destinationPath, fi != null ? fi.GetSymbolicMode () : "NONE"); + + if (fi == null || fi.IsFileType (AdbFileMode.S_IFREG)) { + throw new AndroidDeploymentException (AndroidDeploymentFailureReason.FastDevFileConflict); + } + + if (fi.IsFileType (AdbFileMode.S_IFDIR)) { + return; + } + + // If the directory doesn't exist, we're going to try starting the app to create it. + // on Android 4.2+ broadcasts don't return until long after they actually hit the app, so + // we don't block on the broastcast. Instead, we poll for the directory creation and app termination. + device.SendSeppukuBroadcast (packageName, token); + do { + if (!fi.IsFileType (AdbFileMode.S_IFDIR)) { + fi = device.GetRemoteFileInfo (destinationPath, token).Result; + AndroidLogger.LogDebug ("Stat", "{0} FileInfo for {1} : {2}", count, destinationPath, fi.GetSymbolicMode ()); + } + + // If directory has been created, still need to make sure the process exited, + // so we don't leave stray suicidal processes. + if (fi.IsFileType (AdbFileMode.S_IFDIR)) { + + // If targeting Android 4.0+, we have a reliable hard kill. + if (device.Properties.BuildVersionSdk >= 14) { + device.KillProcess (packageName, token).Wait (); + return; + } + + // Else, just poll until it exits. + if (device.GetProcessId (packageName, token).Result == 0) { + return; + } + } + + token.ThrowIfCancellationRequested (); + + // The ResetEvent objects are handy for suspending operations for a bit without the + // overhead of a Thread.Sleep + wait.WaitOne (200); + } while (++count <= 5*10); // this ends up waiting in chunks for about 10 seconds at the most, to give monodroid time to run. + + throw new AndroidDeploymentException (AndroidDeploymentFailureReason.FastDevDirectoryCreationFailed); + } + + // culture match courtesy: http://stackoverflow.com/a/3962783/83444 + static readonly Regex SatelliteChecker = new Regex ( + Regex.Escape (Path.DirectorySeparatorChar.ToString ()) + + "(?[a-zA-Z]{1,8}(-[a-zA-Z0-9]{1,8})*)" + + Regex.Escape (Path.DirectorySeparatorChar.ToString ()) + + string.Format ("(?[^{0}]+.resources.dll)$", Regex.Escape (Path.DirectorySeparatorChar.ToString ()))); + + public static bool TryGetSatelliteCultureAndFileName (string assemblyPath, out string culture, out string fileName) + { + culture = fileName = null; + + var m = SatelliteChecker.Match (assemblyPath); + if (!m.Success) + return false; + + culture = m.Groups ["culture"].Value; + fileName = m.Groups ["file"].Value; + return true; + } + } + + public class AndroidDeployRuntimeSession + { + private CancellationToken token; + private IList packages; + + public IProgressNotifier ProgressReporter { get; set; } + + public int PackageApiLevel { get; set; } + public AndroidDevice Device { get; private set; } + + public bool UsesSharedRuntime { get; set; } + + /// Whether the user wants unstripped binaries and BCL debug symbols + public bool ProvideFullDebugRuntime { get; set; } + + public string AaptToolPath { get; set; } + + public string PackageSupportedArchs { get; set; } + + public string PackageName { get; set; } + + + public AndroidDeployRuntimeSession(AndroidDevice device, IList packages = null) + { + Device = device; + this.packages = packages; + } + + public async Task StartAsync(CancellationToken token) + { + try + { + await RunAsync(token).ConfigureAwait(false); + } + catch (Exception ex) + { + // make sure we observe these exceptions otherwise they get logged as unhandled + // so do this before the cancellation check below + var aex = ex as AggregateException; + if (aex != null) + { + ex = aex.Flatten().InnerException; + } + + if (token.IsCancellationRequested) + { + AndroidLogger.LogInfo("Deployment cancelled"); + token.ThrowIfCancellationRequested(); + } + + if (ex is OperationCanceledException) + throw ex; + + AndroidLogger.LogError("Deployment failed", ex); + if (ex is AndroidDeploymentException) + { + throw; + } + + if (ex is DeviceNotFoundException || ex is DeviceDisconnectedException) + { + throw new AndroidDeploymentException(AndroidDeploymentFailureReason.DeviceDisconnected, ex); + } + + throw new AndroidDeploymentException(AndroidDeploymentFailureReason.InternalError, ex); + } + } + + async Task RunAsync(CancellationToken token) + { + if (!UsesSharedRuntime) + { + return; + } + + this.token = token; + + await Device.EnsureProperties(token).ConfigureAwait(false); + + // Make sure the device arch is supported + if (!string.IsNullOrWhiteSpace(PackageSupportedArchs) && !Device.CanRunPackageArchitecture(PackageSupportedArchs)) + { + throw new AndroidDeploymentException(AndroidDeploymentFailureReason.ArchitectureNotSupported); + } + + if (UsesSharedRuntime && !Device.CanRunPackageArchitecture(MonoDroidSdk.SharedRuntimeAbis)) + { + throw new AndroidDeploymentException(AndroidDeploymentFailureReason.ArchitectureNotSupportedBySharedRuntime); + } + + string redirectStdio = Device.Properties.Get("log.redirect-stdio"); + if (redirectStdio != null && string.Equals("true", redirectStdio.Trim(), StringComparison.OrdinalIgnoreCase)) + { + throw new AndroidDeploymentException(AndroidDeploymentFailureReason.StdioRedirectionEnabled); + } + + if (packages == null) + { + packages = await Device.GetPackagesAsync(PackageApiLevel, PackageName, ProvideFullDebugRuntime, token, ProgressReporter); + } + + if (packages == null) + { + throw new AndroidDeploymentException(AndroidDeploymentFailureReason.FailedToGetPackageList); + } + + if (UsesSharedRuntime) + { + await EnsureCorrectSharedRuntimes().ConfigureAwait(false); + } + } + + async Task EnsureCorrectSharedRuntimes() + { + // See if there are any old runtimes we need to remove + await RemoveOldRuntimes().ConfigureAwait(false); + + // Install the shared runtime and platform + + try + { + await CheckAndInstallSharedRuntimeAsync().ConfigureAwait(false); + } + catch (Exception ex) + { + if (ex is InsufficientSpaceException) + { + throw new AndroidDeploymentException(AndroidDeploymentFailureReason.InsufficientSpaceForRuntime, ex); + } + throw; + } + + try + { + await InstallSharedPlatformAsync().ConfigureAwait(false); + } + catch (Exception ex) + { + if (ex is InsufficientSpaceException) + { + throw new AndroidDeploymentException(AndroidDeploymentFailureReason.InsufficientSpaceForPlatform, ex); + } + if (ex is SdkNotSupportedException) + { + throw new AndroidDeploymentException(AndroidDeploymentFailureReason.SdkNotSupportedByDevice, ex); + } + throw; + } + } + + async Task RemoveOldRuntimes() + { + // See if we were asked to cancel + if (token.IsCancellationRequested) + return; + + // See if there are any old runtimes we need to remove + var old_runtimes = packages.GetOldRuntimesAndPlatforms(PackageApiLevel).ToList(); + if (old_runtimes.Count == 0) + { + return; + } + + ProgressReporter.BeginStep("Removing old runtimes"); + foreach (var runtime in old_runtimes) + { + ShowProgressText(string.Format("Removing old runtime: {0} [{1}]..", runtime.Name, runtime.Version), token); + await Device.UninstallPackage(runtime.Name, false, token); + packages.Remove(runtime); + } + ProgressReporter.EndStep(null); + } + + // Returns whether a new shared runtime was installed + private async Task CheckAndInstallSharedRuntimeAsync() + { + // See if we were asked to cancel + if (token.IsCancellationRequested) + return false; + + // See if the device already has the shared runtime + var needs_runtime = !packages.IsCurrentRuntimeInstalled(); + + // See if there is a runtime on the device that + // we cannot determine the version of + if (needs_runtime && packages.IsUnknownRuntimeInstalled()) + { + ProgressReporter.ShowErrorDialog( + "Unknown Runtime", + "There is a shared runtime on the device whose version cannot be determined. " + + "A new runtime will not be deployed. If the runtime needs to be replaced, please manually " + + "remove it from the device."); + needs_runtime = false; + } + + // If needed, install the shared runtime + if (!needs_runtime) + return false; + + ProgressReporter.BeginStep("Installing shared runtime"); + await Device.InstallSharedRuntimeAsync(ProvideFullDebugRuntime, token, ProgressReporter); + ProgressReporter.EndStep(null); + + return true; + } + + // Returns whether a new shared platform was installed + private async Task InstallSharedPlatformAsync() + { + // See if we were asked to cancel + if (token.IsCancellationRequested) + return false; + + // See if the device already has the shared platform + var needs_platform = !packages.IsCurrentPlatformInstalled(PackageApiLevel); + + // See if there is a runtime on the device that + // we cannot determine the version of + if (needs_platform && packages.IsUnknownPlatformInstalled(PackageApiLevel)) + { + ProgressReporter.ShowErrorDialog( + "Unknown Platform Runtime", + "There is a platform support runtime on the device whose version cannot be determined. " + + "A new platform support runtime will not be deployed. If the platform support runtime needs " + + "to be replaced, please manually remove it from the device."); + needs_platform = false; + } + + var platform_file = await PlatformPackage.GetPlatformPackagePathAsync (PackageApiLevel, AaptToolPath, ProgressReporter, token); + + // If needed, install the shared platform + if (needs_platform) + { + ProgressReporter.BeginStep("Installing platform framework"); + ShowProgressText(string.Format("Installing the API {0} platform framework..", PackageApiLevel), token); + await Device.InstallSharedPlatformAsync(platform_file, PackageApiLevel, ProgressReporter.ReportProgress, token); + ProgressReporter.EndStep(null); + } + + var sharedRuntimePackage = string.Format(AndroidPackageListExtensions.platformName, PackageApiLevel); + var useXamarinPackage = sharedRuntimePackage; + PlatformPackage.GetPlatformPackageVersion(PackageApiLevel, ref useXamarinPackage); + if (useXamarinPackage.StartsWith("Xamarin", StringComparison.OrdinalIgnoreCase)) + { + ShowProgressText(string.Format("Removing old {0} framework.", sharedRuntimePackage), token); + await Device.UninstallPackage(sharedRuntimePackage, false, token).ConfigureAwait(false); + } + + return needs_platform; + } + + private void ShowProgressText(string text, CancellationToken token) + { + if (!token.IsCancellationRequested && ProgressReporter != null) + ProgressReporter.ReportMessage(text); + } + } + +} diff --git a/src/Xamarin.AndroidTools/Sessions/AndroidDeploymentException.cs b/src/Xamarin.AndroidTools/Sessions/AndroidDeploymentException.cs new file mode 100644 index 00000000000..8f6d06cc3bb --- /dev/null +++ b/src/Xamarin.AndroidTools/Sessions/AndroidDeploymentException.cs @@ -0,0 +1,175 @@ +// +// AndroidDeploymentException.cs +// +// Authors: +// Michael Hutchinson +// +// Copyright 2013 Xamarin Inc. All rights reserved. +// + +using System; + +namespace Xamarin.AndroidTools +{ + public class AndroidDeploymentException : Exception + { + AndroidDeploySession session; + + public AndroidDeploymentFailureReason Reason { + get; private set; + } + + public AndroidDeploymentException (AndroidDeploymentFailureReason reason) : base (reason.ToString ()) + { + Reason = reason; + } + + public AndroidDeploymentException (AndroidDeploymentFailureReason reason, AndroidDeploySession session) + : base (reason.ToString ()) + { + Reason = reason; + this.session = session; + } + + public AndroidDeploymentException (AndroidDeploymentFailureReason reason, Exception inner) + : base (reason.ToString (), InnerExceptionFromAggregate (inner)) + { + Reason = reason; + } + + public AndroidDeploymentException (AndroidDeploymentFailureReason reason, AndroidDeploySession session, Exception inner) + : base (reason.ToString (), InnerExceptionFromAggregate (inner)) + { + Reason = reason; + this.session = session; + } + + static Exception InnerExceptionFromAggregate (Exception ex) + { + if (ex is AggregateException) + return ((AggregateException)ex).Flatten ().InnerException; + return ex; + } + + public void GetNiceExplanation (out string title, out string detail) + { + switch (Reason) { + case AndroidDeploymentFailureReason.DeviceDisconnected: + title = "Device disconnected."; + detail = "The device was disconnected. Please reconnect it and try again."; + break; + case AndroidDeploymentFailureReason.ArchitectureNotSupported: + title = "Architecture not supported."; + detail = string.Format ( + "The package does not support the device architecture ({0}). You can change the supported " + + "architectures in the Android Build section of the Project Options.", + session.Device.SupportedArchitecturesFormatted () + ); + break; + case AndroidDeploymentFailureReason.FailedToGetPackageList: + title = "Package list not found."; + detail = "The package list could not be read from the device."; + break; + case AndroidDeploymentFailureReason.PackagingFailed: + title = "Packaging failed."; + detail = "Deployment was cancelled as the package failed to build."; + break; + case AndroidDeploymentFailureReason.InsufficientSpaceForRuntime: + title = "Insufficient space on device."; + detail = + "Deployment failed because there was insufficient space on the device to install the shared " + + "Mono runtime package. Please make space and try again."; + break; + case AndroidDeploymentFailureReason.InsufficientSpaceForPlatform: + title = "Insufficient space on device."; + detail = + "Deployment failed because there was insufficient space on the device to install the shared " + + "Mono platform package. Please make space and try again."; + break; + case AndroidDeploymentFailureReason.SdkNotSupportedByDevice: + title = "Minimum Android version not supported by device."; + detail = "Deployment failed because the device does not support the package's minimum Android version. " + + "You can change the minimum Android version in the Android Application section of the " + + "Project Options."; + break; + case AndroidDeploymentFailureReason.InsufficientSpaceForPackage: + title = "Insufficient space on device."; + detail = + "Deployment failed because there was insufficient space on the device to install the package. " + + "Please make space and try again."; + break; + case AndroidDeploymentFailureReason.FailedToDetermineFastDevPath: + title = "FastDev path not found."; + detail = "Deployment failed because the FastDev assembly installation path could not be determined."; + break; + case AndroidDeploymentFailureReason.FailedToSynchronizeFastDevAssemblies: + title = "Assembly synchronization error."; + detail = "Deployment failed due to an error in FastDev assembly synchronization."; + break; + case AndroidDeploymentFailureReason.FailedToSynchronizeFastDevResources: + title = "Resource synchronization error."; + detail = "Deployment failed due to an error in FastDev resource synchronization."; + break; + case AndroidDeploymentFailureReason.FastDevActivityNotFound: + title = "FastDev activity not found."; + detail = "Deployment failed because a FastDev activity was not found in the package."; + break; + case AndroidDeploymentFailureReason.FastDevFileConflict: + title = "FastDev file conflict."; + detail = + "Deployment failed because a FastDev assembly conflicted with an existing file. Try " + + "manually removing the application from the device before redeploying."; + break; + case AndroidDeploymentFailureReason.FastDevDirectoryCreationFailed: + title = "FastDev directory creation failed."; + detail = "Deployment failed because the FastDev assembly directory could not be created."; + break; + case AndroidDeploymentFailureReason.ArchitectureNotSupportedBySharedRuntime: + title = "Architecture not supported."; + detail = string.Format ( + "The shared runtime package does not support the device architecture ({0}).", + session.Device.SupportedArchitecturesFormatted () + ); + break; + case AndroidDeploymentFailureReason.StdioRedirectionEnabled: + title = "Android stdio redirection is enabled."; + detail = + "The log.redirect-stdio system property is set to 'true'. " + + "This is not supported; it must be set to 'false'. " + + "See: http://developer.android.com/tools/debugging/debugging-log.html#viewingStd"; + break; + default: + case AndroidDeploymentFailureReason.InternalError: + title = "Internal error."; + if (InnerException != null) { + detail = "Deployment failed because of an internal error: " + InnerException.Message; + } else { + detail = "Deployment failed because of an internal error."; + } + break; + } + } + } + + public enum AndroidDeploymentFailureReason + { + DeviceDisconnected, + InternalError, + ArchitectureNotSupported, + FailedToGetPackageList, + PackagingFailed, + InsufficientSpaceForRuntime, + InsufficientSpaceForPlatform, + SdkNotSupportedByDevice, + InsufficientSpaceForPackage, + FailedToDetermineFastDevPath, + FailedToSynchronizeFastDevAssemblies, + FailedToSynchronizeFastDevResources, + FastDevActivityNotFound, + FastDevFileConflict, + FastDevDirectoryCreationFailed, + ArchitectureNotSupportedBySharedRuntime, + StdioRedirectionEnabled, + RequiresUninstall, + } +} diff --git a/src/Xamarin.AndroidTools/Utilities/JdkProperties.cs b/src/Xamarin.AndroidTools/Utilities/JdkProperties.cs new file mode 100644 index 00000000000..f3abb0839a8 --- /dev/null +++ b/src/Xamarin.AndroidTools/Utilities/JdkProperties.cs @@ -0,0 +1,159 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Xamarin.AndroidTools +{ + public class JdkProperties + { + readonly static Dictionary propertiesCache = new Dictionary (); + + /// + /// Enables caching of the results of querying the jsdkPath. This is false by default for backwards compatibility of + /// behaviour. + /// + public static bool Cached = false; + + public static bool ValidateBeforeQuery = false; + + public string Vendor { get; set; } + + public string Version { get; set; } + + public static JdkProperties Get(string jsdkPath=null) + { + var javaPath = GetJavaPath (jsdkPath); + + lock (propertiesCache) { + // have we already queried this sdk before? + if (Cached && propertiesCache.ContainsKey(javaPath)) { + // yep, return this value + return propertiesCache [javaPath]; + } + + // no, let's query it then, but first lets see if this is the + // default macOS java that prompts the user to install java + if (ValidateBeforeQuery) { + if (!AnySystemJavasInstalled() && (javaPath == "/usr/bin/java" || javaPath == "java")) { + // there are no system javas detected, if jsdkPath looks like the default + // path, then do not attempt to query + return new JdkProperties (); + } + } + + var props = QueryProperties (javaPath); + + propertiesCache [javaPath] = props; + return props; + } + } + + /// + /// Resets and clears the cache of properties + /// + public static void Reset() + { + lock (propertiesCache) { + propertiesCache.Clear (); + } + } + + static bool AnySystemJavasInstalled() + { + if (Mono.AndroidTools.Util.Platform.IsMac) { + string path = Path.Combine (Path.DirectorySeparatorChar + "System", "Library", "Java", "JavaVirtualMachines"); + if (!Directory.Exists (path)) { + return false; + } + + string [] dirs = Directory.GetDirectories (path); + if (dirs == null || dirs.Length == 0) { + return false; + } + } + + return true; + } + + static JdkProperties QueryProperties (string javaPath) + { + var props = new JdkProperties (); + + var processStartInfo = new ProcessStartInfo (javaPath, "-XshowSettings:properties -version"); + processStartInfo.UseShellExecute = false; + processStartInfo.RedirectStandardInput = false; + processStartInfo.RedirectStandardOutput = true; + processStartInfo.RedirectStandardError = true; + processStartInfo.CreateNoWindow = true; + processStartInfo.WindowStyle = ProcessWindowStyle.Hidden; + + var p = new Process () { + StartInfo = processStartInfo, + }; + + p.OutputDataReceived += (sender, e) => + { + ProcessData (props, e.Data); + }; ; + + p.ErrorDataReceived += (sender, e) => + { + ProcessData (props, e.Data); + }; + + using (p) { + p.Start (); + p.BeginOutputReadLine (); + p.BeginErrorReadLine (); + p.WaitForExit (); + } + + return props; + } + + static void ProcessData(JdkProperties props, string data) + { + try + { + int eq = data?.IndexOf ("= ", StringComparison.Ordinal) ?? 0; + var propName = eq > 0 + ? data.Substring (0, eq).Trim () + : data; + if (string.Equals ("java.vm.vendor", propName, StringComparison.Ordinal)) + props.Vendor = GetValue (data); + else if (string.Equals ("java.version", propName, StringComparison.Ordinal) ) + props.Version = GetValue (data); + } catch { + // data was discarded + } + } + + static string GetValue(string data) + { + var start = data.IndexOf("= ") + 2; + var end = data.Length; + return data.Substring(start, end - start).Trim(); + } + + static string GetJavaPath(string javaSdkPath) + { + string java = "java"; + + if (!string.IsNullOrEmpty(javaSdkPath)) + { + java = Path.Combine(javaSdkPath, "bin"); + java = Path.Combine(java, "java"); + if (!File.Exists(java)) + java += ".exe"; + if (!File.Exists(java)) + java = "java"; + } + + return java; + } + } +} diff --git a/src/Xamarin.AndroidTools/Utilities/SdkBuildProperties.cs b/src/Xamarin.AndroidTools/Utilities/SdkBuildProperties.cs new file mode 100644 index 00000000000..94a2262b4b6 --- /dev/null +++ b/src/Xamarin.AndroidTools/Utilities/SdkBuildProperties.cs @@ -0,0 +1,51 @@ +// +// SdkBuildProperties.cs +// +// Author: +// Greg Munn +// +// Copyright (c) 2015 Xamarin Inc +// +using System; +using System.Collections.Generic; +using System.Linq; +using System.IO; +using Mono.AndroidTools; + +namespace Xamarin.AndroidTools.Utilities +{ + /// + /// Simple utility functions to read properties from files like 'source.properties' and 'build.prop' + /// + static class SdkBuildProperties + { + public static IEnumerable LoadProperties (string propertyFile) + { + if (File.Exists (propertyFile)) { + var allText = File.ReadAllText(propertyFile).Replace("\r", string.Empty); + return allText.Split (new [] { "\n" }, StringSplitOptions.RemoveEmptyEntries); + } + + return null; + } + + public static string GetPropertyValue(this IEnumerable properties, string propertyName) + { + if (properties == null) + return null; + + var prop = properties.FirstOrDefault (p => p.StartsWith (propertyName, StringComparison.InvariantCultureIgnoreCase)); + if (!string.IsNullOrEmpty (prop)) { + var propValues = prop.Split (new [] { propertyName }, StringSplitOptions.RemoveEmptyEntries); + if (propValues.Length > 0) { + return propValues [0]; + } + + return string.Empty; + } + + return null; + } + } +} + diff --git a/src/Xamarin.AndroidTools/Utils.cs b/src/Xamarin.AndroidTools/Utils.cs new file mode 100644 index 00000000000..d9125cfe4a9 --- /dev/null +++ b/src/Xamarin.AndroidTools/Utils.cs @@ -0,0 +1,111 @@ +// +// Utils.cs +// +// Author: +// Andreia Gaita +// +// Copyright (c) 2012 Xamarin Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +using System; + +namespace Xamarin.AndroidTools +{ + [Obsolete] + public static class Utils + { + + /// + /// Converts a framework version (2.2 or v2.2) to an Android API Level (8) + /// + [Obsolete("Use AndroidVersion.TryOSVersionToApiLevel")] + public static int FrameworkVersionToApiLevel (string version) + { + version = version.TrimStart ('v'); + + switch (version) { + case "1.6": + return 4; + case "2.0": + return 5; + case "2.0.1": + return 6; + case "2.1": + return 7; + case "2.2": + return 8; + case "2.3": + return 10; + case "3.0": + return 11; + case "3.1": + return 12; + case "3.2": + return 13; + case "4.0": + return 14; + case "4.0.3": + return 15; + case "4.1": + return 16; + case "4.2": + return 17; + case "4.3": + return 18; + case "4.4": + return 19; + case "4.4.87": + return 20; + case "5.0": + return 21; + } + + return -1; + } + + /// + /// Gets all the api levels we are currently supporting (shipping assemblies for) + /// + /// + [Obsolete("Use MonoDroidSdk.SupportedApiLevels")] + public static int[] SupportedApiLevels { + get { return MonoDroidSdk.SupportedApiLevels; } + } + + /// + /// Gets the path to Google's Android SDK + /// + [Obsolete("Use AndroidSdk.AndroidSdkPath")] + public static string GetAndroidSdkPath () + { + return AndroidSdk.AndroidSdkPath; + } + + /// + /// Sets the user specified path to Google's Android SDK + /// + /// + [Obsolete("Use AndroidSdk.SetPreferredAndroidSdkPath")] + public static void SetAndroidSdkPath (string path) + { + AndroidSdk.SetPreferredAndroidSdkPath (path); + } + } +} diff --git a/src/Xamarin.AndroidTools/Xamarin.AndroidTools.csproj b/src/Xamarin.AndroidTools/Xamarin.AndroidTools.csproj new file mode 100644 index 00000000000..5fcd5b72484 --- /dev/null +++ b/src/Xamarin.AndroidTools/Xamarin.AndroidTools.csproj @@ -0,0 +1,65 @@ + + + + $(NoWarn);CA1305 + + + 12 + netstandard2.0 + false + $(VendorPrefix)Xamarin.AndroidTools$(VendorSuffix) + true + Xamarin.AndroidTools.snk + $(MicrosoftAndroidSdkOutDir) + + + + + + + + + + + runtime; build; native; contentfiles; analyzers + all + + + + + + Microsoft400 + + + Microsoft400 + + + + + + + TargetFramework=netstandard2.0 + + + + + Utilities\AggregateAsyncResult.cs + + + Utilities\StreamExtensions.cs + + + + + True + True + Resources.resx + + + + + ResXFileCodeGenerator + Resources.Designer.cs + + + diff --git a/src/Xamarin.AndroidTools/Xamarin.AndroidTools.snk b/src/Xamarin.AndroidTools/Xamarin.AndroidTools.snk new file mode 100644 index 00000000000..07beae72ad0 Binary files /dev/null and b/src/Xamarin.AndroidTools/Xamarin.AndroidTools.snk differ diff --git a/src/Xamarin.Installer.AndroidSDK/CodeGenerator.targets b/src/Xamarin.Installer.AndroidSDK/CodeGenerator.targets new file mode 100644 index 00000000000..9bd54768669 --- /dev/null +++ b/src/Xamarin.Installer.AndroidSDK/CodeGenerator.targets @@ -0,0 +1,37 @@ + + + + $(MSBuildThisFileDirectory)..\Xamarin.Installer.Build.Tasks\Xamarin.Installer.Common.props + + + + + $(IntermediateOutputPath)RequiredComponentVersions.cs + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Xamarin.Installer.AndroidSDK/Feeds/AndroidManifestFeed_d18.0.xml b/src/Xamarin.Installer.AndroidSDK/Feeds/AndroidManifestFeed_d18.0.xml new file mode 100644 index 00000000000..118358a4b61 --- /dev/null +++ b/src/Xamarin.Installer.AndroidSDK/Feeds/AndroidManifestFeed_d18.0.xml @@ -0,0 +1,3620 @@ + + + + + https://aka.ms/download-jdk/microsoft-jdk-21.0.9-windows-aarch64.zip + https://aka.ms/download-jdk/microsoft-jdk-21.0.9-windows-x64.zip + https://aka.ms/download-jdk/microsoft-jdk-21.0.9-macOS-aarch64.tar.gz + https://aka.ms/download-jdk/microsoft-jdk-21.0.9-macOS-x64.tar.gz + https://aka.ms/download-jdk/microsoft-jdk-21.0.9-linux-aarch64.tar.gz + https://aka.ms/download-jdk/microsoft-jdk-21.0.9-linux-x64.tar.gz + + + + + https://aka.ms/download-jdk/microsoft-jdk-21.0.8-windows-aarch64.zip + https://aka.ms/download-jdk/microsoft-jdk-21.0.8-windows-x64.zip + https://aka.ms/download-jdk/microsoft-jdk-21.0.8-macOS-aarch64.tar.gz + https://aka.ms/download-jdk/microsoft-jdk-21.0.8-macOS-x64.tar.gz + https://aka.ms/download-jdk/microsoft-jdk-21.0.8-linux-aarch64.tar.gz + https://aka.ms/download-jdk/microsoft-jdk-21.0.8-linux-x64.tar.gz + + + + + https://aka.ms/download-jdk/microsoft-jdk-21.0.6-windows-aarch64.zip + https://aka.ms/download-jdk/microsoft-jdk-21.0.6-windows-x64.zip + https://aka.ms/download-jdk/microsoft-jdk-21.0.6-macOS-aarch64.tar.gz + https://aka.ms/download-jdk/microsoft-jdk-21.0.6-macOS-x64.tar.gz + https://aka.ms/download-jdk/microsoft-jdk-21.0.6-linux-aarch64.tar.gz + https://aka.ms/download-jdk/microsoft-jdk-21.0.6-linux-x64.tar.gz + + + + + https://aka.ms/download-jdk/microsoft-jdk-17.0.14-windows-aarch64.zip + https://aka.ms/download-jdk/microsoft-jdk-17.0.14-windows-x64.zip + https://aka.ms/download-jdk/microsoft-jdk-17.0.14-macOS-aarch64.tar.gz + https://aka.ms/download-jdk/microsoft-jdk-17.0.14-macOS-x64.tar.gz + https://aka.ms/download-jdk/microsoft-jdk-17.0.14-linux-aarch64.tar.gz + https://aka.ms/download-jdk/microsoft-jdk-17.0.14-linux-x64.tar.gz + + + + + https://aka.ms/download-jdk/microsoft-jdk-17.0.13-windows-aarch64.zip + https://aka.ms/download-jdk/microsoft-jdk-17.0.13-windows-x64.zip + https://aka.ms/download-jdk/microsoft-jdk-17.0.13-macOS-aarch64.tar.gz + https://aka.ms/download-jdk/microsoft-jdk-17.0.13-macOS-x64.tar.gz + https://aka.ms/download-jdk/microsoft-jdk-17.0.13-linux-aarch64.tar.gz + https://aka.ms/download-jdk/microsoft-jdk-17.0.13-linux-x64.tar.gz + + + + + https://aka.ms/download-jdk/microsoft-jdk-17.0.12-windows-aarch64.zip + https://aka.ms/download-jdk/microsoft-jdk-17.0.12-windows-x64.zip + https://aka.ms/download-jdk/microsoft-jdk-17.0.12-macOS-aarch64.tar.gz + https://aka.ms/download-jdk/microsoft-jdk-17.0.12-macOS-x64.tar.gz + https://aka.ms/download-jdk/microsoft-jdk-17.0.12-linux-aarch64.tar.gz + https://aka.ms/download-jdk/microsoft-jdk-17.0.12-linux-x64.tar.gz + + + + + https://aka.ms/download-jdk/microsoft-jdk-17.0.8.1-windows-aarch64.zip + https://aka.ms/download-jdk/microsoft-jdk-17.0.8.1-windows-x64.zip + https://aka.ms/download-jdk/microsoft-jdk-17.0.8.1-macOS-aarch64.tar.gz + https://aka.ms/download-jdk/microsoft-jdk-17.0.8.1-macOS-x64.tar.gz + https://aka.ms/download-jdk/microsoft-jdk-17.0.8.1-linux-aarch64.tar.gz + https://aka.ms/download-jdk/microsoft-jdk-17.0.8.1-linux-x64.tar.gz + + + + + https://aka.ms/download-jdk/microsoft-jdk-17.0.8-windows-aarch64.zip + https://aka.ms/download-jdk/microsoft-jdk-17.0.8-windows-x64.zip + https://aka.ms/download-jdk/microsoft-jdk-17.0.8-macOS-aarch64.tar.gz + https://aka.ms/download-jdk/microsoft-jdk-17.0.8-macOS-x64.tar.gz + https://aka.ms/download-jdk/microsoft-jdk-17.0.8-linux-aarch64.tar.gz + https://aka.ms/download-jdk/microsoft-jdk-17.0.8-linux-x64.tar.gz + + + + + https://aka.ms/download-jdk/microsoft-jdk-11.0.19-windows-aarch64.zip + https://aka.ms/download-jdk/microsoft-jdk-11.0.19-windows-x64.zip + https://aka.ms/download-jdk/microsoft-jdk-11.0.19-macOS-aarch64.tar.gz + https://aka.ms/download-jdk/microsoft-jdk-11.0.19-macOS-x64.tar.gz + https://aka.ms/download-jdk/microsoft-jdk-11.0.19-linux-aarch64.tar.gz + https://aka.ms/download-jdk/microsoft-jdk-11.0.19-linux-x64.tar.gz + + + + + https://dl.google.com/android/repository/platform-tools_r36.0.0-linux.zip + https://dl.google.com/android/repository/platform-tools_r36.0.0-darwin.zip + https://dl.google.com/android/repository/platform-tools_r36.0.0-win.zip + + + + + https://dl.google.com/android/repository/platform-tools_r35.0.2-linux.zip + https://dl.google.com/android/repository/platform-tools_r35.0.2-darwin.zip + https://dl.google.com/android/repository/platform-tools_r35.0.2-win.zip + + + + + https://dl.google.com/android/repository/platform-tools_r35.0.1-linux.zip + https://dl.google.com/android/repository/platform-tools_r35.0.1-darwin.zip + https://dl.google.com/android/repository/platform-tools_r35.0.1-win.zip + + + + + https://dl.google.com/android/repository/platform-tools_r34.0.5-darwin.zip + https://dl.google.com/android/repository/platform-tools_r34.0.5-linux.zip + https://dl.google.com/android/repository/platform-tools_r34.0.5-windows.zip + + + + + https://dl.google.com/android/repository/platform-tools_r34.0.4-darwin.zip + https://dl.google.com/android/repository/platform-tools_r34.0.4-linux.zip + https://dl.google.com/android/repository/platform-tools_r34.0.4-windows.zip + + + + + https://dl.google.com/android/repository/platform-tools_r34.0.3-darwin.zip + https://dl.google.com/android/repository/platform-tools_r34.0.3-linux.zip + https://dl.google.com/android/repository/platform-tools_r34.0.3-windows.zip + + + + + https://dl.google.com/android/repository/platform-tools_r34.0.1-darwin.zip + https://dl.google.com/android/repository/platform-tools_r34.0.1-linux.zip + https://dl.google.com/android/repository/platform-tools_r34.0.1-windows.zip + + + + + https://dl.google.com/android/repository/platform-tools_r33.0.3-darwin.zip + https://dl.google.com/android/repository/platform-tools_r33.0.3-linux.zip + https://dl.google.com/android/repository/platform-tools_r33.0.3-windows.zip + + + + + https://dl.google.com/android/repository/platform-tools_r33.0.2-darwin.zip + https://dl.google.com/android/repository/platform-tools_r33.0.2-linux.zip + https://dl.google.com/android/repository/platform-tools_r33.0.2-windows.zip + + + + + https://dl.google.com/android/repository/commandlinetools-linux-13114758_latest.zip + https://dl.google.com/android/repository/commandlinetools-mac-13114758_latest.zip + https://dl.google.com/android/repository/commandlinetools-win-13114758_latest.zip + + + + + https://dl.google.com/android/repository/commandlinetools-linux-12700392_latest.zip + https://dl.google.com/android/repository/commandlinetools-mac-12700392_latest.zip + https://dl.google.com/android/repository/commandlinetools-win-12700392_latest.zip + + + + + https://dl.google.com/android/repository/commandlinetools-linux-11076708_latest.zip + https://dl.google.com/android/repository/commandlinetools-mac-11076708_latest.zip + https://dl.google.com/android/repository/commandlinetools-win-11076708_latest.zip + + + + + https://dl.google.com/android/repository/commandlinetools-linux-10406996_latest.zip + https://dl.google.com/android/repository/commandlinetools-mac-10406996_latest.zip + https://dl.google.com/android/repository/commandlinetools-win-10406996_latest.zip + + + + + https://dl.google.com/android/repository/commandlinetools-linux-9862592_latest.zip + https://dl.google.com/android/repository/commandlinetools-mac-9862592_latest.zip + https://dl.google.com/android/repository/commandlinetools-win-9862592_latest.zip + + + + + https://dl.google.com/android/repository/commandlinetools-linux-9477386_latest.zip + https://dl.google.com/android/repository/commandlinetools-mac-9477386_latest.zip + https://dl.google.com/android/repository/commandlinetools-win-9477386_latest.zip + + + + + https://dl.google.com/android/repository/commandlinetools-linux-9123335_latest.zip + https://dl.google.com/android/repository/commandlinetools-mac-9123335_latest.zip + https://dl.google.com/android/repository/commandlinetools-win-9123335_latest.zip + + + + + https://dl.google.com/android/repository/commandlinetools-linux-8512546_latest.zip + https://dl.google.com/android/repository/commandlinetools-mac-8512546_latest.zip + https://dl.google.com/android/repository/commandlinetools-win-8512546_latest.zip + + + + + https://dl.google.com/android/repository/commandlinetools-linux-8092744_latest.zip + https://dl.google.com/android/repository/commandlinetools-mac-8092744_latest.zip + https://dl.google.com/android/repository/commandlinetools-win-8092744_latest.zip + + + + + https://dl.google.com/android/repository/commandlinetools-linux-7583922_latest.zip + https://dl.google.com/android/repository/commandlinetools-mac-7583922_latest.zip + https://dl.google.com/android/repository/commandlinetools-win-7583922_latest.zip + + + + + https://dl.google.com/android/repository/build-tools_r36.1_linux.zip + https://dl.google.com/android/repository/build-tools_r36.1_windows.zip + https://dl.google.com/android/repository/build-tools_r36.1_macosx.zip + + + + + https://dl.google.com/android/repository/build-tools_r36_linux.zip + https://dl.google.com/android/repository/build-tools_r36_windows.zip + https://dl.google.com/android/repository/build-tools_r36_macosx.zip + + + + + https://dl.google.com/android/repository/build-tools_r35.0.1_linux.zip + https://dl.google.com/android/repository/build-tools_r35.0.1_windows.zip + https://dl.google.com/android/repository/build-tools_r35.0.1_macosx.zip + + + + + https://dl.google.com/android/repository/build-tools_r35_linux.zip + https://dl.google.com/android/repository/build-tools_r35_windows.zip + https://dl.google.com/android/repository/build-tools_r35_macosx.zip + + + + + https://dl.google.com/android/repository/build-tools_r34-linux.zip + https://dl.google.com/android/repository/build-tools_r34-macosx.zip + https://dl.google.com/android/repository/build-tools_r34-windows.zip + + + + + https://dl.google.com/android/repository/build-tools_r33.0.3-linux.zip + https://dl.google.com/android/repository/build-tools_r33.0.3-macosx.zip + https://dl.google.com/android/repository/build-tools_r33.0.3-windows.zip + + + + + https://dl.google.com/android/repository/build-tools_r33.0.2-linux.zip + https://dl.google.com/android/repository/build-tools_r33.0.2-macosx.zip + https://dl.google.com/android/repository/build-tools_r33.0.2-windows.zip + + + + + https://dl.google.com/android/repository/build-tools_r33.0.1-linux.zip + https://dl.google.com/android/repository/build-tools_r33.0.1-macosx.zip + https://dl.google.com/android/repository/build-tools_r33.0.1-windows.zip + + + + + https://dl.google.com/android/repository/build-tools_r33-linux.zip + https://dl.google.com/android/repository/build-tools_r33-macosx.zip + https://dl.google.com/android/repository/build-tools_r33-windows.zip + + + + + https://dl.google.com/android/repository/210b77e4bc623bd4cdda4dae790048f227972bd2.build-tools_r32-windows.zip + https://dl.google.com/android/repository/5219cc671e844de73762e969ace287c29d2e14cd.build-tools_r32-macosx.zip + https://dl.google.com/android/repository/build-tools_r32-linux.zip + + + + + https://dl.google.com/android/repository/09489e417c0a266f2862ddd82b4ac29a1b7af55e.build-tools_r31-windows.zip + https://dl.google.com/android/repository/build-tools_r31-linux.zip + https://dl.google.com/android/repository/d32e21a8aa8492ef8b86a489f601da425842b5da.build-tools_r31-macosx.zip + + + + + https://dl.google.com/android/repository/91936d4ee3ccc839f0addd53c9ebf087b1e39251.build-tools_r30.0.3-windows.zip + https://dl.google.com/android/repository/build-tools_r30.0.3-linux.zip + https://dl.google.com/android/repository/f6d24b187cc6bd534c6c37604205171784ac5621.build-tools_r30.0.3-macosx.zip + + + + + https://dl.google.com/android/repository/5a6ceea22103d8dec989aefcef309949c0c42f1d.build-tools_r30.0.2-macosx.zip + https://dl.google.com/android/repository/build-tools_r30.0.2-linux.zip + https://dl.google.com/android/repository/efbaa277338195608aa4e3dbd43927e97f60218c.build-tools_r30.0.2-windows.zip + + + + + https://dl.google.com/android/repository/emulator-linux_x64-14214601.zip + https://dl.google.com/android/repository/emulator-darwin_x64-14214601.zip + https://dl.google.com/android/repository/emulator-darwin_aarch64-14214601.zip + https://dl.google.com/android/repository/emulator-windows_x64-14214601.zip + + + + + https://dl.google.com/android/repository/emulator-linux_x64-13402964.zip + https://dl.google.com/android/repository/emulator-darwin_x64-13402964.zip + https://dl.google.com/android/repository/emulator-darwin_aarch64-13402964.zip + https://dl.google.com/android/repository/emulator-windows_x64-13402964.zip + + + + + https://dl.google.com/android/repository/android-ndk-r28c-linux.zip + https://dl.google.com/android/repository/android-ndk-r28c-darwin.zip + https://dl.google.com/android/repository/android-ndk-r28c-windows.zip + + + + + https://dl.google.com/android/repository/android-ndk-r28b-linux.zip + https://dl.google.com/android/repository/android-ndk-r28b-darwin.zip + https://dl.google.com/android/repository/android-ndk-r28b-windows.zip + + + + + https://dl.google.com/android/repository/android-ndk-r28-linux.zip + https://dl.google.com/android/repository/android-ndk-r28-darwin.zip + https://dl.google.com/android/repository/android-ndk-r28-windows.zip + + + + + https://dl.google.com/android/repository/android-ndk-r28-beta3-linux.zip + https://dl.google.com/android/repository/android-ndk-r28-beta3-darwin.zip + https://dl.google.com/android/repository/android-ndk-r28-beta3-windows.zip + + + + + https://dl.google.com/android/repository/android-ndk-r28-beta2-linux.zip + https://dl.google.com/android/repository/android-ndk-r28-beta2-darwin.zip + https://dl.google.com/android/repository/android-ndk-r28-beta2-windows.zip + + + + + https://dl.google.com/android/repository/android-ndk-r28-beta1-linux.zip + https://dl.google.com/android/repository/android-ndk-r28-beta1-darwin.zip + https://dl.google.com/android/repository/android-ndk-r28-beta1-windows.zip + + + + + https://dl.google.com/android/repository/android-ndk-r27d-linux.zip + https://dl.google.com/android/repository/android-ndk-r27d-darwin.zip + https://dl.google.com/android/repository/android-ndk-r27d-windows.zip + + + + + https://dl.google.com/android/repository/android-ndk-r27c-linux.zip + https://dl.google.com/android/repository/android-ndk-r27c-darwin.zip + https://dl.google.com/android/repository/android-ndk-r27c-windows.zip + + + + + https://dl.google.com/android/repository/android-ndk-r27b-linux.zip + https://dl.google.com/android/repository/android-ndk-r27b-darwin.zip + https://dl.google.com/android/repository/android-ndk-r27b-windows.zip + + + + + https://dl.google.com/android/repository/android-ndk-r27-linux.zip + https://dl.google.com/android/repository/android-ndk-r27-darwin.zip + https://dl.google.com/android/repository/android-ndk-r27-windows.zip + + + + + https://dl.google.com/android/repository/android-ndk-r27-beta2-linux.zip + https://dl.google.com/android/repository/android-ndk-r27-beta2-darwin.zip + https://dl.google.com/android/repository/android-ndk-r27-beta2-windows.zip + + + + + https://dl.google.com/android/repository/android-ndk-r27-beta1-linux.zip + https://dl.google.com/android/repository/android-ndk-r27-beta1-darwin.zip + https://dl.google.com/android/repository/android-ndk-r27-beta1-windows.zip + + + + + https://dl.google.com/android/repository/android-ndk-r26d-linux.zip + https://dl.google.com/android/repository/android-ndk-r26d-darwin.zip + https://dl.google.com/android/repository/android-ndk-r26d-windows.zip + + + + + https://dl.google.com/android/repository/android-ndk-r26c-darwin.zip + https://dl.google.com/android/repository/android-ndk-r26c-linux.zip + https://dl.google.com/android/repository/android-ndk-r26c-windows.zip + + + + + https://dl.google.com/android/repository/android-ndk-r26b-darwin.zip + https://dl.google.com/android/repository/android-ndk-r26b-linux.zip + https://dl.google.com/android/repository/android-ndk-r26b-windows.zip + + + + + https://dl.google.com/android/repository/android-ndk-r26-darwin.zip + https://dl.google.com/android/repository/android-ndk-r26-linux.zip + https://dl.google.com/android/repository/android-ndk-r26-windows.zip + + + + + https://dl.google.com/android/repository/android-ndk-r25c-darwin.zip + https://dl.google.com/android/repository/android-ndk-r25c-linux.zip + https://dl.google.com/android/repository/android-ndk-r25c-windows.zip + + + + + https://dl.google.com/android/repository/android-ndk-r25b-darwin.zip + https://dl.google.com/android/repository/android-ndk-r25b-linux.zip + https://dl.google.com/android/repository/android-ndk-r25b-windows.zip + + + + + https://dl.google.com/android/repository/android-ndk-r25-darwin.zip + https://dl.google.com/android/repository/android-ndk-r25-linux.zip + https://dl.google.com/android/repository/android-ndk-r25-windows.zip + + + + + https://dl.google.com/android/repository/android-ndk-r24-darwin.zip + https://dl.google.com/android/repository/android-ndk-r24-linux.zip + https://dl.google.com/android/repository/android-ndk-r24-windows.zip + + + + + https://dl.google.com/android/repository/android-ndk-r23c-darwin.zip + https://dl.google.com/android/repository/android-ndk-r23c-linux.zip + https://dl.google.com/android/repository/android-ndk-r23c-windows.zip + + + + + https://dl.google.com/android/repository/android-ndk-r23b-darwin.zip + https://dl.google.com/android/repository/android-ndk-r23b-linux.zip + https://dl.google.com/android/repository/android-ndk-r23b-windows.zip + + + + + https://dl.google.com/android/repository/android-ndk-r23-darwin.zip + https://dl.google.com/android/repository/android-ndk-r23-linux.zip + https://dl.google.com/android/repository/android-ndk-r23-windows.zip + + + + + https://dl.google.com/android/repository/android-ndk-r22b-darwin-aarch64.zip + https://dl.google.com/android/repository/android-ndk-r22b-darwin-x86_64.zip + https://dl.google.com/android/repository/android-ndk-r22b-linux-x86_64.zip + https://dl.google.com/android/repository/android-ndk-r22b-windows-x86_64.zip + + + + + https://dl.google.com/android/repository/android-ndk-r22b-darwin-aarch64.zip + https://dl.google.com/android/repository/android-ndk-r22b-darwin-x86_64.zip + https://dl.google.com/android/repository/android-ndk-r22b-linux-x86_64.zip + https://dl.google.com/android/repository/android-ndk-r22b-windows-x86_64.zip + + + + + https://dl.google.com/android/repository/android-ndk-r22-darwin-aarch64.zip + https://dl.google.com/android/repository/android-ndk-r22-darwin-x86_64.zip + https://dl.google.com/android/repository/android-ndk-r22-linux-x86_64.zip + https://dl.google.com/android/repository/android-ndk-r22-windows-x86_64.zip + + + + + https://dl.google.com/android/repository/android-ndk-r22-darwin-aarch64.zip + https://dl.google.com/android/repository/android-ndk-r22-darwin-x86_64.zip + https://dl.google.com/android/repository/android-ndk-r22-linux-x86_64.zip + https://dl.google.com/android/repository/android-ndk-r22-windows-x86_64.zip + + + + + https://dl.google.com/android/repository/android-ndk-r21e-darwin-aarch64.zip + https://dl.google.com/android/repository/android-ndk-r21e-darwin-x86_64.zip + https://dl.google.com/android/repository/android-ndk-r21e-linux-x86_64.zip + https://dl.google.com/android/repository/android-ndk-r21e-windows-x86_64.zip + + + + + https://dl.google.com/android/repository/android-ndk-r21e-darwin-aarch64.zip + https://dl.google.com/android/repository/android-ndk-r21e-darwin-x86_64.zip + https://dl.google.com/android/repository/android-ndk-r21e-linux-x86_64.zip + https://dl.google.com/android/repository/android-ndk-r21e-windows-x86_64.zip + + + + + https://dl.google.com/android/repository/android-ndk-r21d-darwin-aarch64.zip + https://dl.google.com/android/repository/android-ndk-r21d-darwin-x86_64.zip + https://dl.google.com/android/repository/android-ndk-r21d-linux-x86_64.zip + https://dl.google.com/android/repository/android-ndk-r21d-windows-x86_64.zip + + + + + https://dl.google.com/android/repository/android-ndk-r21d-darwin-aarch64.zip + https://dl.google.com/android/repository/android-ndk-r21d-darwin-x86_64.zip + https://dl.google.com/android/repository/android-ndk-r21d-linux-x86_64.zip + https://dl.google.com/android/repository/android-ndk-r21d-windows-x86_64.zip + + + + + https://dl.google.com/android/repository/android-ndk-r21c-darwin-aarch64.zip + https://dl.google.com/android/repository/android-ndk-r21c-darwin-x86_64.zip + https://dl.google.com/android/repository/android-ndk-r21c-linux-x86_64.zip + https://dl.google.com/android/repository/android-ndk-r21c-windows-x86_64.zip + + + + + https://dl.google.com/android/repository/android-ndk-r21c-darwin-aarch64.zip + https://dl.google.com/android/repository/android-ndk-r21c-darwin-x86_64.zip + https://dl.google.com/android/repository/android-ndk-r21c-linux-x86_64.zip + https://dl.google.com/android/repository/android-ndk-r21c-windows-x86_64.zip + + + + + https://dl.google.com/android/repository/android-ndk-r21b-darwin-aarch64.zip + https://dl.google.com/android/repository/android-ndk-r21b-darwin-x86_64.zip + https://dl.google.com/android/repository/android-ndk-r21b-linux-x86_64.zip + https://dl.google.com/android/repository/android-ndk-r21b-windows-x86_64.zip + + + + + https://dl.google.com/android/repository/android-ndk-r21b-darwin-aarch64.zip + https://dl.google.com/android/repository/android-ndk-r21b-darwin-x86_64.zip + https://dl.google.com/android/repository/android-ndk-r21b-linux-x86_64.zip + https://dl.google.com/android/repository/android-ndk-r21b-windows-x86_64.zip + + + + + https://dl.google.com/android/repository/android-ndk-r21-darwin-x86_64.zip + https://dl.google.com/android/repository/android-ndk-r21-linux-x86_64.zip + https://dl.google.com/android/repository/android-ndk-r21-windows-x86_64.zip + + + + + https://dl.google.com/android/repository/android-ndk-r21-darwin-x86_64.zip + https://dl.google.com/android/repository/android-ndk-r21-linux-x86_64.zip + https://dl.google.com/android/repository/android-ndk-r21-windows-x86_64.zip + + + + + https://dl.google.com/android/repository/android-ndk-r20b-darwin-aarch64.zip + https://dl.google.com/android/repository/android-ndk-r20b-darwin-x86_64.zip + https://dl.google.com/android/repository/android-ndk-r20b-linux-x86_64.zip + https://dl.google.com/android/repository/android-ndk-r20b-windows-x86.zip + https://dl.google.com/android/repository/android-ndk-r20b-windows-x86_64.zip + + + + + https://dl.google.com/android/repository/android-ndk-r20b-darwin-aarch64.zip + https://dl.google.com/android/repository/android-ndk-r20b-darwin-x86_64.zip + https://dl.google.com/android/repository/android-ndk-r20b-linux-x86_64.zip + https://dl.google.com/android/repository/android-ndk-r20b-windows-x86.zip + https://dl.google.com/android/repository/android-ndk-r20b-windows-x86_64.zip + + + + + https://dl.google.com/android/repository/android-ndk-r20-darwin-aarch64.zip + https://dl.google.com/android/repository/android-ndk-r20-darwin-x86_64.zip + https://dl.google.com/android/repository/android-ndk-r20-linux-x86_64.zip + https://dl.google.com/android/repository/android-ndk-r20-windows-x86.zip + https://dl.google.com/android/repository/android-ndk-r20-windows-x86_64.zip + + + + + https://dl.google.com/android/repository/android-ndk-r20-darwin-aarch64.zip + https://dl.google.com/android/repository/android-ndk-r20-darwin-x86_64.zip + https://dl.google.com/android/repository/android-ndk-r20-linux-x86_64.zip + https://dl.google.com/android/repository/android-ndk-r20-windows-x86.zip + https://dl.google.com/android/repository/android-ndk-r20-windows-x86_64.zip + + + + + https://dl.google.com/android/repository/android-ndk-r19c-darwin-aarch64.zip + https://dl.google.com/android/repository/android-ndk-r19c-darwin-x86_64.zip + https://dl.google.com/android/repository/android-ndk-r19c-linux-x86_64.zip + https://dl.google.com/android/repository/android-ndk-r19c-windows-x86.zip + https://dl.google.com/android/repository/android-ndk-r19c-windows-x86_64.zip + + + + + https://dl.google.com/android/repository/android-ndk-r19c-darwin-aarch64.zip + https://dl.google.com/android/repository/android-ndk-r19c-darwin-x86_64.zip + https://dl.google.com/android/repository/android-ndk-r19c-linux-x86_64.zip + https://dl.google.com/android/repository/android-ndk-r19c-windows-x86.zip + https://dl.google.com/android/repository/android-ndk-r19c-windows-x86_64.zip + + + + + https://dl.google.com/android/repository/android-ndk-r19-darwin-aarch64.zip + https://dl.google.com/android/repository/android-ndk-r19-darwin-x86_64.zip + https://dl.google.com/android/repository/android-ndk-r19-linux-x86_64.zip + https://dl.google.com/android/repository/android-ndk-r19-windows-x86.zip + https://dl.google.com/android/repository/android-ndk-r19-windows-x86_64.zip + + + + + https://dl.google.com/android/repository/android-ndk-r19-darwin-aarch64.zip + https://dl.google.com/android/repository/android-ndk-r19-darwin-x86_64.zip + https://dl.google.com/android/repository/android-ndk-r19-linux-x86_64.zip + https://dl.google.com/android/repository/android-ndk-r19-windows-x86.zip + https://dl.google.com/android/repository/android-ndk-r19-windows-x86_64.zip + + + + + https://dl.google.com/android/repository/android-ndk-r18b-darwin-aarch64.zip + https://dl.google.com/android/repository/android-ndk-r18b-darwin-x86_64.zip + https://dl.google.com/android/repository/android-ndk-r18b-linux-x86_64.zip + https://dl.google.com/android/repository/android-ndk-r18b-windows-x86.zip + https://dl.google.com/android/repository/android-ndk-r18b-windows-x86_64.zip + + + + + https://dl.google.com/android/repository/android-ndk-r18b-darwin-aarch64.zip + https://dl.google.com/android/repository/android-ndk-r18b-darwin-x86_64.zip + https://dl.google.com/android/repository/android-ndk-r18b-linux-x86_64.zip + https://dl.google.com/android/repository/android-ndk-r18b-windows-x86.zip + https://dl.google.com/android/repository/android-ndk-r18b-windows-x86_64.zip + + + + + https://dl.google.com/android/repository/android-ndk-r17c-darwin-aarch64.zip + https://dl.google.com/android/repository/android-ndk-r17c-darwin-x86_64.zip + https://dl.google.com/android/repository/android-ndk-r17c-linux-x86_64.zip + https://dl.google.com/android/repository/android-ndk-r17c-windows-x86.zip + https://dl.google.com/android/repository/android-ndk-r17c-windows-x86_64.zip + + + + + https://dl.google.com/android/repository/android-ndk-r17c-darwin-aarch64.zip + https://dl.google.com/android/repository/android-ndk-r17c-darwin-x86_64.zip + https://dl.google.com/android/repository/android-ndk-r17c-linux-x86_64.zip + https://dl.google.com/android/repository/android-ndk-r17c-windows-x86.zip + https://dl.google.com/android/repository/android-ndk-r17c-windows-x86_64.zip + + + + + https://dl.google.com/android/repository/android-ndk-r16b-darwin-aarch64.zip + https://dl.google.com/android/repository/android-ndk-r16b-darwin-x86_64.zip + https://dl.google.com/android/repository/android-ndk-r16b-linux-x86_64.zip + https://dl.google.com/android/repository/android-ndk-r16b-windows-x86.zip + https://dl.google.com/android/repository/android-ndk-r16b-windows-x86_64.zip + + + + + https://dl.google.com/android/repository/android-ndk-r16b-darwin-aarch64.zip + https://dl.google.com/android/repository/android-ndk-r16b-darwin-x86_64.zip + https://dl.google.com/android/repository/android-ndk-r16b-linux-x86_64.zip + https://dl.google.com/android/repository/android-ndk-r16b-windows-x86.zip + https://dl.google.com/android/repository/android-ndk-r16b-windows-x86_64.zip + + + + + https://dl.google.com/android/repository/android-ndk-r14b-linux-x86_64.zip + https://dl.google.com/android/repository/android-ndk-r14b-darwin-x86_64.zip + https://dl.google.com/android/repository/android-ndk-r14b-windows-x86.zip + https://dl.google.com/android/repository/android-ndk-r14b-windows-x86_64.zip + + + + + https://dl.google.com/android/repository/platform-37.0_r01.zip + + + + + https://dl.google.com/android/repository/platform-36.1_r01.zip + + + + + https://dl.google.com/android/repository/platform-36_r02.zip + + + + + https://dl.google.com/android/repository/platform-35_r02.zip + + + + + https://dl.google.com/android/repository/platform-34-ext7_r03.zip + + + + + https://dl.google.com/android/repository/platform-34-ext7_r02.zip + + + + + https://dl.google.com/android/repository/platform-33-ext3_r03.zip + + + + + https://dl.google.com/android/repository/platform-32_r01.zip + + + + + https://dl.google.com/android/repository/platform-31_r01.zip + + + + + https://dl.google.com/android/repository/platform-30_r03.zip + + + + + https://dl.google.com/android/repository/platform-29_r05.zip + + + + + https://dl.google.com/android/repository/platform-28_r06.zip + + + + + https://dl.google.com/android/repository/platform-27_r03.zip + + + + + https://dl.google.com/android/repository/platform-26_r02.zip + + + + + https://dl.google.com/android/repository/platform-25_r03.zip + + + + + https://dl.google.com/android/repository/platform-24_r02.zip + + + + + https://dl.google.com/android/repository/platform-23_r03.zip + + + + + https://dl.google.com/android/repository/android-22_r02.zip + + + + + https://dl.google.com/android/repository/android-21_r02.zip + + + + + https://dl.google.com/android/repository/sys-img/android/arm64-v8a-35_r02.zip + + + + + + + + https://dl.google.com/android/repository/sys-img/android/x86_64-35_r02.zip + + + + + + + + https://dl.google.com/android/repository/sys-img/android/armeabi-v7a-21_r04.zip + + + + + https://dl.google.com/android/repository/sys-img/android/armeabi-v7a-22_r02.zip + + + + + https://dl.google.com/android/repository/sys-img/android/armeabi-v7a-23_r06.zip + + + + + https://dl.google.com/android/repository/sys-img/android/armeabi-v7a-24_r07.zip + + + + + https://dl.google.com/android/repository/sys-img/android/arm64-v8a-21_r04.zip + + + + + https://dl.google.com/android/repository/sys-img/android/arm64-v8a-22_r02.zip + + + + + https://dl.google.com/android/repository/sys-img/android/arm64-v8a-23_r07.zip + + + + + https://dl.google.com/android/repository/sys-img/android/arm64-v8a-24_r09.zip + + + + + https://dl.google.com/android/repository/sys-img/android/arm64-v8a-25_r02.zip + + + + + https://dl.google.com/android/repository/sys-img/android/arm64-v8a-26_r02.zip + + + + + + + + https://dl.google.com/android/repository/sys-img/android/arm64-v8a-27_r02.zip + + + + + + + + https://dl.google.com/android/repository/sys-img/android/arm64-v8a-28_r02.zip + + + + + + + + https://dl.google.com/android/repository/sys-img/android/arm64-v8a-29_r08.zip + + + + + https://dl.google.com/android/repository/sys-img/android/arm64-v8a-30_r02.zip + + + + + https://dl.google.com/android/repository/sys-img/android/arm64-v8a-31_r03.zip + + + + + + + + https://dl.google.com/android/repository/sys-img/android/arm64-v8a-32_r02.zip + + + + + + + + https://dl.google.com/android/repository/sys-img/android/arm64-v8a-33_r02.zip + + + + + + + + https://dl.google.com/android/repository/sys-img/android/arm64-v8a-34_r04.zip + + + + + + + + https://dl.google.com/android/repository/sys-img/android/x86-21_r05.zip + + + + + https://dl.google.com/android/repository/sys-img/android/x86-22_r06.zip + + + + + https://dl.google.com/android/repository/sys-img/android/x86-23_r10.zip + + + + + https://dl.google.com/android/repository/sys-img/android/x86-24_r08.zip + + + + + https://dl.google.com/android/repository/sys-img/android/x86-25_r01.zip + + + + + https://dl.google.com/android/repository/sys-img/android/x86-26_r01.zip + + + + + https://dl.google.com/android/repository/sys-img/android/x86-27_r01.zip + + + + + https://dl.google.com/android/repository/sys-img/android/x86-28_r04.zip + + + + + https://dl.google.com/android/repository/sys-img/android/x86-29_r08-windows.zip + + + + + + + + https://dl.google.com/android/repository/sys-img/android/x86-29_r08-darwin.zip + https://dl.google.com/android/repository/sys-img/android/x86-29_r08-linux.zip + + + + + + + + https://dl.google.com/android/repository/sys-img/android/x86_64-21_r05.zip + + + + + https://dl.google.com/android/repository/sys-img/android/x86_64-22_r06.zip + + + + + https://dl.google.com/android/repository/sys-img/android/x86_64-23_r10.zip + + + + + https://dl.google.com/android/repository/sys-img/android/x86_64-24_r08.zip + + + + + https://dl.google.com/android/repository/sys-img/android/x86_64-25_r01.zip + + + + + https://dl.google.com/android/repository/sys-img/android/x86_64-26_r01.zip + + + + + https://dl.google.com/android/repository/sys-img/android/x86_64-27_r01.zip + + + + + https://dl.google.com/android/repository/sys-img/android/x86_64-28_r04.zip + + + + + https://dl.google.com/android/repository/sys-img/android/x86_64-29_r08-windows.zip + + + + + + + + https://dl.google.com/android/repository/sys-img/android/x86_64-29_r08-darwin.zip + https://dl.google.com/android/repository/sys-img/android/x86_64-29_r08-linux.zip + + + + + + + + https://dl.google.com/android/repository/sys-img/android/x86_64-30_r11.zip + + + + + + + + https://dl.google.com/android/repository/sys-img/android/x86_64-31_r05.zip + + + + + + + + https://dl.google.com/android/repository/sys-img/android/x86_64-32_r02.zip + + + + + + + + https://dl.google.com/android/repository/sys-img/android/x86_64-33_r02.zip + + + + + + + + https://dl.google.com/android/repository/sys-img/android/x86_64-34_r04.zip + + + + + + + + https://dl.google.com/android/repository/sys-img/android-automotive/x86_64-33_r04.zip + + + + + + + + https://dl.google.com/android/repository/sys-img/android-automotive/arm64-v8a-33_r04.zip + + + + + + + + https://dl.google.com/android/repository/sys-img/android-automotive-distantdisplay/arm64-v8a-33_r01.zip + + + + + + + + https://dl.google.com/android/repository/sys-img/android-automotive-distantdisplay/x86_64-33_r01.zip + + + + + + + + https://dl.google.com/android/repository/sys-img/android-wear/arm64-v8a-34_r01.zip + + + + + https://dl.google.com/android/repository/sys-img/android-wear/x86_64-34_r01.zip + + + + + https://dl.google.com/android/repository/sys-img/android-wear/armeabi-v7a-25_r03.zip + + + + + https://dl.google.com/android/repository/sys-img/android-wear/x86-25_r03.zip + + + + + https://dl.google.com/android/repository/sys-img/android-wear/x86-26_r04.zip + + + + + https://dl.google.com/android/repository/sys-img/android-wear/x86-28_r09.zip + + + + + https://dl.google.com/android/repository/sys-img/android-wear/x86-30_r12.zip + + + + + https://dl.google.com/android/repository/sys-img/android-wear/arm64-v8a-30_r12.zip + + + + + https://dl.google.com/android/repository/sys-img/android-wear/x86_64-33_r04.zip + + + + + https://dl.google.com/android/repository/sys-img/android-wear/arm64-v8a-33_r04.zip + + + + + https://dl.google.com/android/repository/sys-img/android-wear-cn/x86-30_r12.zip + + + + + https://dl.google.com/android/repository/sys-img/android-wear-cn/arm64-v8a-30_r12.zip + + + + + https://dl.google.com/android/repository/sys-img/android-tv/x86-21_r03.zip + + + + + https://dl.google.com/android/repository/sys-img/android-tv/armeabi-v7a-21_r03.zip + + + + + https://dl.google.com/android/repository/sys-img/android-tv/x86-22_r03.zip + + + + + https://dl.google.com/android/repository/sys-img/android-tv/x86-23_r21.zip + + + + + https://dl.google.com/android/repository/sys-img/android-tv/armeabi-v7a-23_r12.zip + + + + + https://dl.google.com/android/repository/sys-img/android-tv/x86-24_r22.zip + + + + + https://dl.google.com/android/repository/sys-img/android-tv/x86-25_r16.zip + + + + + https://dl.google.com/android/repository/sys-img/android-tv/x86-26_r14.zip + + + + + + + + https://dl.google.com/android/repository/sys-img/android-tv/x86-27_r09.zip + + + + + https://dl.google.com/android/repository/sys-img/android-tv/x86-28_r10.zip + + + + + https://dl.google.com/android/repository/sys-img/android-tv/x86-29_r03.zip + + + + + + + + https://dl.google.com/android/repository/sys-img/android-tv/x86-30_r04.zip + + + + + + + + https://dl.google.com/android/repository/sys-img/android-tv/x86-31_r04.zip + + + + + + + + https://dl.google.com/android/repository/sys-img/android-tv/arm64-v8a-31_r04.zip + + + + + + + + https://dl.google.com/android/repository/sys-img/android-tv/x86-33_r05.zip + + + + + + + + https://dl.google.com/android/repository/sys-img/android-tv/arm64-v8a-33_r05.zip + + + + + + + + https://dl.google.com/android/repository/sys-img/android-tv/x86-34_r03.zip + + + + + + + + https://dl.google.com/android/repository/sys-img/android-tv/arm64-v8a-34_r03.zip + + + + + + + + https://dl.google.com/android/repository/sys-img/android-desktop/arm64-v8a-34_r01.zip + + + + + + + + https://dl.google.com/android/repository/sys-img/android-desktop/x86_64-34_r01.zip + + + + + + + + https://dl.google.com/android/repository/sys-img/android-desktop/x86_64-32_r05.zip + + + + + + + + https://dl.google.com/android/repository/sys-img/android-desktop/arm64-v8a-32_r05.zip + + + + + + + + https://dl.google.com/android/repository/sys-img/android-desktop/x86_64-33_r04.zip + + + + + + + + https://dl.google.com/android/repository/sys-img/android-desktop/arm64-v8a-33_r04.zip + + + + + + + + https://dl.google.com/android/repository/sys-img/aosp_atd/arm64-v8a-35_r01.zip + + + + + + + + https://dl.google.com/android/repository/sys-img/aosp_atd/x86_64-35_r01.zip + + + + + + + + https://dl.google.com/android/repository/sys-img/aosp_atd/x86-30_r01.zip + + + + + + + + https://dl.google.com/android/repository/sys-img/aosp_atd/x86_64-30_r02.zip + + + + + + + + https://dl.google.com/android/repository/sys-img/aosp_atd/x86_64-31_r01.zip + + + + + + + + https://dl.google.com/android/repository/sys-img/aosp_atd/x86_64-32_r01.zip + + + + + + + + https://dl.google.com/android/repository/sys-img/aosp_atd/x86_64-33_r02.zip + + + + + + + + https://dl.google.com/android/repository/sys-img/aosp_atd/x86_64-34_r02.zip + + + + + + + + https://dl.google.com/android/repository/sys-img/aosp_atd/arm64-v8a-30_r02.zip + + + + + + + + https://dl.google.com/android/repository/sys-img/aosp_atd/arm64-v8a-31_r01.zip + + + + + + + + https://dl.google.com/android/repository/sys-img/aosp_atd/arm64-v8a-32_r01.zip + + + + + + + + https://dl.google.com/android/repository/sys-img/aosp_atd/arm64-v8a-33_r02.zip + + + + + + + + https://dl.google.com/android/repository/sys-img/aosp_atd/arm64-v8a-34_r02.zip + + + + + + + + https://dl.google.com/android/repository/sys-img/google_atd/arm64-v8a-35_r01.zip + + + + + + + + https://dl.google.com/android/repository/sys-img/google_atd/x86_64-35_r01.zip + + + + + + + + https://dl.google.com/android/repository/sys-img/google_atd/arm64-v8a-34_r01.zip + + + + + + + + https://dl.google.com/android/repository/sys-img/google_atd/x86_64-34_r01.zip + + + + + + + + https://dl.google.com/android/repository/sys-img/google_atd/x86-30_r01.zip + + + + + + + + https://dl.google.com/android/repository/sys-img/google_atd/x86_64-30_r02.zip + + + + + + + + https://dl.google.com/android/repository/sys-img/google_atd/x86_64-31_r01.zip + + + + + + + + https://dl.google.com/android/repository/sys-img/google_atd/x86_64-32_r01.zip + + + + + + + + https://dl.google.com/android/repository/sys-img/google_atd/x86_64-33_r02.zip + + + + + + + + https://dl.google.com/android/repository/sys-img/google_atd/arm64-v8a-30_r02.zip + + + + + + + + https://dl.google.com/android/repository/sys-img/google_atd/arm64-v8a-31_r01.zip + + + + + + + + https://dl.google.com/android/repository/sys-img/google_atd/arm64-v8a-32_r01.zip + + + + + + + + https://dl.google.com/android/repository/sys-img/google_atd/arm64-v8a-33_r02.zip + + + + + + + + https://dl.google.com/android/repository/sys-img/google-tv/x86-30_r04.zip + + + + + + + + https://dl.google.com/android/repository/sys-img/google-tv/x86-31_r04.zip + + + + + + + + https://dl.google.com/android/repository/sys-img/google-tv/arm64-v8a-31_r04.zip + + + + + + + + https://dl.google.com/android/repository/sys-img/google-tv/x86-33_r05.zip + + + + + + + + https://dl.google.com/android/repository/sys-img/google-tv/arm64-v8a-33_r05.zip + + + + + + + + https://dl.google.com/android/repository/sys-img/google-tv/x86-34_r03.zip + + + + + + + + https://dl.google.com/android/repository/sys-img/google-tv/arm64-v8a-34_r03.zip + + + + + + + + https://dl.google.com/android/repository/sys-img/google_apis/arm64-v8a-ps16k-35_r05.zip + + + + + + + + https://dl.google.com/android/repository/sys-img/google_apis/x86_64-ps16k-35_r05.zip + + + + + + + + https://dl.google.com/android/repository/sys-img/google_apis/arm64-v8a-35_r08.zip + + + + + + + + https://dl.google.com/android/repository/sys-img/google_apis/x86_64-35_r08.zip + + + + + + + + https://dl.google.com/android/repository/sys-img/google_apis/arm64-v8a-ps16k-36.1_r02.zip + + + + + + + + https://dl.google.com/android/repository/sys-img/google_apis/x86_64-ps16k-36.1_r02.zip + + + + + + + + https://dl.google.com/android/repository/sys-img/google_apis/arm64-v8a-36.1_r03.zip + + + + + + + + https://dl.google.com/android/repository/sys-img/google_apis/x86_64-36.1_r03.zip + + + + + + + + https://dl.google.com/android/repository/sys-img/google_apis/arm64-v8a-36_r06.zip + + + + + + + + https://dl.google.com/android/repository/sys-img/google_apis/x86_64-36_r06.zip + + + + + + + + https://dl.google.com/android/repository/sys-img/google_apis/arm64-v8a-ps16k-36_r06.zip + + + + + + + + https://dl.google.com/android/repository/sys-img/google_apis/x86_64-ps16k-36_r06.zip + + + + + + + + https://dl.google.com/android/repository/sys-img/google_apis/x86-21_r32.zip + + + + + https://dl.google.com/android/repository/sys-img/google_apis/x86_64-21_r32.zip + + + + + https://dl.google.com/android/repository/sys-img/google_apis/armeabi-v7a-21_r32.zip + + + + + https://dl.google.com/android/repository/sys-img/google_apis/x86-22_r26.zip + + + + + https://dl.google.com/android/repository/sys-img/google_apis/armeabi-v7a-22_r26.zip + + + + + https://dl.google.com/android/repository/sys-img/google_apis/x86_64-22_r26.zip + + + + + https://dl.google.com/android/repository/sys-img/google_apis/x86-23_r33.zip + + + + + https://dl.google.com/android/repository/sys-img/google_apis/x86_64-23_r33.zip + + + + + https://dl.google.com/android/repository/sys-img/google_apis/armeabi-v7a-23_r33.zip + + + + + https://dl.google.com/android/repository/sys-img/google_apis/x86-24_r27.zip + + + + + https://dl.google.com/android/repository/sys-img/google_apis/x86_64-24_r27.zip + + + + + https://dl.google.com/android/repository/sys-img/google_apis/armeabi-v7a-24_r07.zip + + + + + https://dl.google.com/android/repository/sys-img/google_apis/arm64-v8a-24_r27.zip + + + + + https://dl.google.com/android/repository/sys-img/google_apis/x86-25_r18.zip + + + + + https://dl.google.com/android/repository/sys-img/google_apis/x86_64-25_r18.zip + + + + + https://dl.google.com/android/repository/sys-img/google_apis/armeabi-v7a-25_r18.zip + + + + + https://dl.google.com/android/repository/sys-img/google_apis/arm64-v8a-21_r32.zip + + + + + https://dl.google.com/android/repository/sys-img/google_apis/arm64-v8a-22_r26.zip + + + + + https://dl.google.com/android/repository/sys-img/google_apis/arm64-v8a-23_r33.zip + + + + + https://dl.google.com/android/repository/sys-img/google_apis/arm64-v8a-24_r29.zip + + + + + https://dl.google.com/android/repository/sys-img/google_apis/arm64-v8a-25_r20.zip + + + + + https://dl.google.com/android/repository/sys-img/google_apis/x86-26_r16.zip + + + + + + + + https://dl.google.com/android/repository/sys-img/google_apis/x86_64-26_r16.zip + + + + + + + + https://dl.google.com/android/repository/sys-img/google_apis/x86-27_r11.zip + + + + + + + + https://dl.google.com/android/repository/sys-img/google_apis/x86-28_r12.zip + + + + + + + + https://dl.google.com/android/repository/sys-img/google_apis/x86_64-28_r11.zip + + + + + + + + https://dl.google.com/android/repository/sys-img/google_apis/x86-29_r12.zip + + + + + + + + https://dl.google.com/android/repository/sys-img/google_apis/x86_64-29_r12.zip + + + + + + + + https://dl.google.com/android/repository/sys-img/google_apis/x86-30_r16.zip + + + + + + + + https://dl.google.com/android/repository/sys-img/google_apis/x86_64-30_r16.zip + + + + + + + + https://dl.google.com/android/repository/sys-img/google_apis/x86_64-31_r14.zip + + + + + + + + https://dl.google.com/android/repository/sys-img/google_apis/x86_64-32_r08.zip + + + + + + + + https://dl.google.com/android/repository/sys-img/google_apis/x86_64-33_r17.zip + + + + + + + + https://dl.google.com/android/repository/sys-img/google_apis/x86_64-34_r14.zip + + + + + + + + https://dl.google.com/android/repository/sys-img/google_apis/arm64-v8a-28_r02.zip + + + + + + + + https://dl.google.com/android/repository/sys-img/google_apis/arm64-v8a-26_r03.zip + + + + + + + + https://dl.google.com/android/repository/sys-img/google_apis/arm64-v8a-27_r03.zip + + + + + + + + https://dl.google.com/android/repository/sys-img/google_apis/arm64-v8a-29_r13.zip + + + + + + + + https://dl.google.com/android/repository/sys-img/google_apis/arm64-v8a-30_r16.zip + + + + + + + + https://dl.google.com/android/repository/sys-img/google_apis/arm64-v8a-31_r11.zip + + + + + + + + https://dl.google.com/android/repository/sys-img/google_apis/arm64-v8a-32_r08.zip + + + + + + + + https://dl.google.com/android/repository/sys-img/google_apis/arm64-v8a-33_r17.zip + + + + + + + + https://dl.google.com/android/repository/sys-img/google_apis/arm64-v8a-34_r14.zip + + + + + + + + https://dl.google.com/android/repository/sys-img/google_apis_playstore/arm64-v8a-playstore-ps16k-35_r05.zip + + + + + + + + https://dl.google.com/android/repository/sys-img/google_apis_playstore/x86_64-playstore-ps16k-35_r05.zip + + + + + + + + https://dl.google.com/android/repository/sys-img/google_apis_playstore/arm64-v8a-35_r09.zip + + + + + + + + https://dl.google.com/android/repository/sys-img/google_apis_playstore/x86_64-35_r09.zip + + + + + + + + https://dl.google.com/android/repository/sys-img/google_apis_playstore/arm64-v8a-playstore-ps16k-36.1_r02.zip + + + + + + + + https://dl.google.com/android/repository/sys-img/google_apis_playstore/x86_64-playstore-ps16k-36.1_r02.zip + + + + + + + + https://dl.google.com/android/repository/sys-img/google_apis_playstore/arm64-v8a-36.1_r03.zip + + + + + + + + https://dl.google.com/android/repository/sys-img/google_apis_playstore/x86_64-36.1_r03.zip + + + + + + + + https://dl.google.com/android/repository/sys-img/google_apis_playstore/arm64-v8a-36_r06.zip + + + + + + + + https://dl.google.com/android/repository/sys-img/google_apis_playstore/x86_64-36_r06.zip + + + + + + + + https://dl.google.com/android/repository/sys-img/google_apis_playstore/arm64-v8a-playstore-ps16k-36_r06.zip + + + + + + + + https://dl.google.com/android/repository/sys-img/google_apis_playstore/x86_64-playstore-ps16k-36_r06.zip + + + + + + + + https://dl.google.com/android/repository/sys-img/google_apis_playstore/x86-24_r19.zip + + + + + https://dl.google.com/android/repository/sys-img/google_apis_playstore/x86-25_r09.zip + + + + + https://dl.google.com/android/repository/sys-img/google_apis_playstore/x86-26_r07.zip + + + + + + + + https://dl.google.com/android/repository/sys-img/google_apis_playstore/x86-27_r03.zip + + + + + + + + https://dl.google.com/android/repository/sys-img/google_apis_playstore/x86-28_r08.zip + + + + + + + + https://dl.google.com/android/repository/sys-img/google_apis_playstore/x86-28_r09.zip + + + + + + + + https://dl.google.com/android/repository/sys-img/google_apis_playstore/x86_64-28_r08.zip + + + + + + + + https://dl.google.com/android/repository/sys-img/google_apis_playstore/x86-29_r08-windows.zip + + + + + + + + https://dl.google.com/android/repository/sys-img/google_apis_playstore/x86-29_r08-darwin.zip + https://dl.google.com/android/repository/sys-img/google_apis_playstore/x86-29_r08-linux.zip + + + + + + + + https://dl.google.com/android/repository/sys-img/google_apis_playstore/x86_64-29_r08-windows.zip + + + + + + + + https://dl.google.com/android/repository/sys-img/google_apis_playstore/x86_64-29_r08-darwin.zip + https://dl.google.com/android/repository/sys-img/google_apis_playstore/x86_64-29_r08-linux.zip + + + + + + + + https://dl.google.com/android/repository/sys-img/google_apis_playstore/x86-30_r09-windows.zip + + + + + + + + https://dl.google.com/android/repository/sys-img/google_apis_playstore/x86-30_r09-darwin.zip + https://dl.google.com/android/repository/sys-img/google_apis_playstore/x86-30_r09-linux.zip + + + + + + + + https://dl.google.com/android/repository/sys-img/google_apis_playstore/x86_64-30_r10-windows.zip + + + + + + + + https://dl.google.com/android/repository/sys-img/google_apis_playstore/x86_64-30_r10-darwin.zip + https://dl.google.com/android/repository/sys-img/google_apis_playstore/x86_64-30_r10-linux.zip + + + + + + + + https://dl.google.com/android/repository/sys-img/google_apis_playstore/x86_64-31_r09.zip + + + + + + + + https://dl.google.com/android/repository/sys-img/google_apis_playstore/x86_64-32_r04-windows.zip + + + + + + + + https://dl.google.com/android/repository/sys-img/google_apis_playstore/x86_64-32_r04-darwin.zip + https://dl.google.com/android/repository/sys-img/google_apis_playstore/x86_64-32_r04-linux.zip + + + + + + + + https://dl.google.com/android/repository/sys-img/google_apis_playstore/x86_64-33_r09.zip + + + + + + + + https://dl.google.com/android/repository/sys-img/google_apis_playstore/x86_64-34_r14.zip + + + + + + + + https://dl.google.com/android/repository/sys-img/google_apis_playstore/arm64-v8a-28_r02.zip + + + + + + + + https://dl.google.com/android/repository/sys-img/google_apis_playstore/arm64-v8a-29_r09-darwin.zip + https://dl.google.com/android/repository/sys-img/google_apis_playstore/arm64-v8a-29_r09-linux.zip + + + + + + + + https://dl.google.com/android/repository/sys-img/google_apis_playstore/arm64-v8a-30_r10-darwin.zip + https://dl.google.com/android/repository/sys-img/google_apis_playstore/arm64-v8a-30_r10-linux.zip + + + + + + + + https://dl.google.com/android/repository/sys-img/google_apis_playstore/arm64-v8a-31_r09-darwin.zip + https://dl.google.com/android/repository/sys-img/google_apis_playstore/arm64-v8a-31_r09-linux.zip + + + + + + + + https://dl.google.com/android/repository/sys-img/google_apis_playstore/arm64-v8a-32_r04-darwin.zip + https://dl.google.com/android/repository/sys-img/google_apis_playstore/arm64-v8a-32_r04-linux.zip + + + + + + + + https://dl.google.com/android/repository/sys-img/google_apis_playstore/arm64-v8a-33_r09.zip + + + + + + + + https://dl.google.com/android/repository/sys-img/google_apis_playstore/arm64-v8a-34_r14.zip + + + + + + + + https://dl.google.com/android/repository/google_apis-19_r20.zip + + + + + + + + + + https://dl.google.com/android/repository/google_apis-24_r1.zip + + + + + + + + + + https://dl.google.com/android/repository/google_apis-25_r1.zip + + + + + + + + + + https://dl.google.com/android/repository/google_apis-3-r03.zip + + + + + + + + https://dl.google.com/android/repository/google_apis-4_r02.zip + + + + + + + + https://dl.google.com/android/repository/google_apis-5_r01.zip + + + + + + + + https://dl.google.com/android/repository/google_apis-6_r01.zip + + + + + + + + https://dl.google.com/android/repository/google_apis-7_r01.zip + + + + + + + + https://dl.google.com/android/repository/google_apis-8_r02.zip + + + + + + + + https://dl.google.com/android/repository/google_apis-9_r02.zip + + + + + + + + https://dl.google.com/android/repository/google_apis-10_r02.zip + + + + + + + + + https://dl.google.com/android/repository/google_apis-11_r01.zip + + + + + + + + https://dl.google.com/android/repository/google_apis-12_r01.zip + + + + + + + + + https://dl.google.com/android/repository/google_apis-13_r01.zip + + + + + + + + + https://dl.google.com/android/repository/google_apis-14_r02.zip + + + + + + + + + https://dl.google.com/android/repository/google_apis-15_r03.zip + + + + + + + + + + https://dl.google.com/android/repository/google_apis-16_r04.zip + + + + + + + + + + https://dl.google.com/android/repository/google_apis-17_r04.zip + + + + + + + + + + https://dl.google.com/android/repository/google_apis-18_r04.zip + + + + + + + + + + https://dl.google.com/android/repository/google_apis-21_r01.zip + + + + + + + + + + https://dl.google.com/android/repository/google_apis-22_r01.zip + + + + + + + + + + https://dl.google.com/android/repository/google_apis-23_r01.zip + + + + + + + + + + https://dl.google.com/android/repository/google_tv-12_r02.zip + + + + + https://dl.google.com/android/repository/google_tv-13_r01.zip + + + + + https://dl.google.com/android/repository/glass/google-gdk.zip + + + + + + + + https://dl.google.com/android/repository/android_m2repository_r47.zip + + + + + https://dl.google.com/android/repository/google_m2repository_gms_v11_3_rc05_wear_2_0_5.zip + + + + + https://dl.google.com/android/repository/market_licensing-r02.zip + + + + + https://dl.google.com/android/repository/market_apk_expansion-r03.zip + + + + + https://dl.google.com/android/repository/google_play_services_3265130_r12.zip + + + + + https://dl.google.com/android/repository/google_play_services_v16_1_rc09.zip + + + + + https://dl.google.com/android/repository/usb_driver_r13-windows.zip + + + + + https://dl.google.com/android/repository/GoogleAdMobAdsSdkAndroid-6.4.1.zip + + + + + https://dl.google.com/android/repository/GoogleAnalyticsAndroid_2.0beta5.zip + + + + + https://dl.google.com/android/repository/webdriver_r02.zip + + + + + https://dl.google.com/android/repository/gcm_r03.zip + + + + + https://dl.google.com/android/repository/simulator_r01.zip + + + + + https://dl.google.com/android/repository/iasdk-1.9.0-1566514721.zip + + + + + https://dl.google.com/android/repository/aehd-windows_v2.2.zip + + + + + https://dl.google.com/android/repository/gvm-windows_v2_0_0.zip + + + + + https://dl.google.com/android/repository/extras/intel/haxm-windows_v7_6_5.zip + + + + Terms and Conditions + +This is the Android Software Development Kit License Agreement + +1. Introduction + +1.1 The Android Software Development Kit (referred to in the License Agreement as the "SDK" and specifically including the Android system files, packaged APIs, and Google APIs add-ons) is licensed to you subject to the terms of the License Agreement. The License Agreement forms a legally binding contract between you and Google in relation to your use of the SDK. + +1.2 "Android" means the Android software stack for devices, as made available under the Android Open Source Project, which is located at the following URL: http://source.android.com/, as updated from time to time. + +1.3 A "compatible implementation" means any Android device that (i) complies with the Android Compatibility Definition document, which can be found at the Android compatibility website (http://source.android.com/compatibility) and which may be updated from time to time; and (ii) successfully passes the Android Compatibility Test Suite (CTS). + +1.4 "Google" means Google Inc., a Delaware corporation with principal place of business at 1600 Amphitheatre Parkway, Mountain View, CA 94043, United States. + + +2. Accepting the License Agreement + +2.1 In order to use the SDK, you must first agree to the License Agreement. You may not use the SDK if you do not accept the License Agreement. + +2.2 By clicking to accept, you hereby agree to the terms of the License Agreement. + +2.3 You may not use the SDK and may not accept the License Agreement if you are a person barred from receiving the SDK under the laws of the United States or other countries, including the country in which you are resident or from which you use the SDK. + +2.4 If you are agreeing to be bound by the License Agreement on behalf of your employer or other entity, you represent and warrant that you have full legal authority to bind your employer or such entity to the License Agreement. If you do not have the requisite authority, you may not accept the License Agreement or use the SDK on behalf of your employer or other entity. + + +3. SDK License from Google + +3.1 Subject to the terms of the License Agreement, Google grants you a limited, worldwide, royalty-free, non-assignable, non-exclusive, and non-sublicensable license to use the SDK solely to develop applications for compatible implementations of Android. + +3.2 You may not use this SDK to develop applications for other platforms (including non-compatible implementations of Android) or to develop another SDK. You are of course free to develop applications for other platforms, including non-compatible implementations of Android, provided that this SDK is not used for that purpose. + +3.3 You agree that Google or third parties own all legal right, title and interest in and to the SDK, including any Intellectual Property Rights that subsist in the SDK. "Intellectual Property Rights" means any and all rights under patent law, copyright law, trade secret law, trademark law, and any and all other proprietary rights. Google reserves all rights not expressly granted to you. + +3.4 You may not use the SDK for any purpose not expressly permitted by the License Agreement. Except to the extent required by applicable third party licenses, you may not copy (except for backup purposes), modify, adapt, redistribute, decompile, reverse engineer, disassemble, or create derivative works of the SDK or any part of the SDK. + +3.5 Use, reproduction and distribution of components of the SDK licensed under an open source software license are governed solely by the terms of that open source software license and not the License Agreement. + +3.6 You agree that the form and nature of the SDK that Google provides may change without prior notice to you and that future versions of the SDK may be incompatible with applications developed on previous versions of the SDK. You agree that Google may stop (permanently or temporarily) providing the SDK (or any features within the SDK) to you or to users generally at Google's sole discretion, without prior notice to you. + +3.7 Nothing in the License Agreement gives you a right to use any of Google's trade names, trademarks, service marks, logos, domain names, or other distinctive brand features. + +3.8 You agree that you will not remove, obscure, or alter any proprietary rights notices (including copyright and trademark notices) that may be affixed to or contained within the SDK. + + +4. Use of the SDK by You + +4.1 Google agrees that it obtains no right, title or interest from you (or your licensors) under the License Agreement in or to any software applications that you develop using the SDK, including any intellectual property rights that subsist in those applications. + +4.2 You agree to use the SDK and write applications only for purposes that are permitted by (a) the License Agreement and (b) any applicable law, regulation or generally accepted practices or guidelines in the relevant jurisdictions (including any laws regarding the export of data or software to and from the United States or other relevant countries). + +4.3 You agree that if you use the SDK to develop applications for general public users, you will protect the privacy and legal rights of those users. If the users provide you with user names, passwords, or other login information or personal information, you must make the users aware that the information will be available to your application, and you must provide legally adequate privacy notice and protection for those users. If your application stores personal or sensitive information provided by users, it must do so securely. If the user provides your application with Google Account information, your application may only use that information to access the user's Google Account when, and for the limited purposes for which, the user has given you permission to do so. + +4.4 You agree that you will not engage in any activity with the SDK, including the development or distribution of an application, that interferes with, disrupts, damages, or accesses in an unauthorized manner the servers, networks, or other properties or services of any third party including, but not limited to, Google or any mobile communications carrier. + +4.5 You agree that you are solely responsible for (and that Google has no responsibility to you or to any third party for) any data, content, or resources that you create, transmit or display through Android and/or applications for Android, and for the consequences of your actions (including any loss or damage which Google may suffer) by doing so. + +4.6 You agree that you are solely responsible for (and that Google has no responsibility to you or to any third party for) any breach of your obligations under the License Agreement, any applicable third party contract or Terms of Service, or any applicable law or regulation, and for the consequences (including any loss or damage which Google or any third party may suffer) of any such breach. + +5. Your Developer Credentials + +5.1 You agree that you are responsible for maintaining the confidentiality of any developer credentials that may be issued to you by Google or which you may choose yourself and that you will be solely responsible for all applications that are developed under your developer credentials. + +6. Privacy and Information + +6.1 In order to continually innovate and improve the SDK, Google may collect certain usage statistics from the software including but not limited to a unique identifier, associated IP address, version number of the software, and information on which tools and/or services in the SDK are being used and how they are being used. Before any of this information is collected, the SDK will notify you and seek your consent. If you withhold consent, the information will not be collected. + +6.2 The data collected is examined in the aggregate to improve the SDK and is maintained in accordance with Google's Privacy Policy. + + +7. Third Party Applications + +7.1 If you use the SDK to run applications developed by a third party or that access data, content or resources provided by a third party, you agree that Google is not responsible for those applications, data, content, or resources. You understand that all data, content or resources which you may access through such third party applications are the sole responsibility of the person from which they originated and that Google is not liable for any loss or damage that you may experience as a result of the use or access of any of those third party applications, data, content, or resources. + +7.2 You should be aware the data, content, and resources presented to you through such a third party application may be protected by intellectual property rights which are owned by the providers (or by other persons or companies on their behalf). You may not modify, rent, lease, loan, sell, distribute or create derivative works based on these data, content, or resources (either in whole or in part) unless you have been specifically given permission to do so by the relevant owners. + +7.3 You acknowledge that your use of such third party applications, data, content, or resources may be subject to separate terms between you and the relevant third party. In that case, the License Agreement does not affect your legal relationship with these third parties. + + +8. Using Android APIs + +8.1 Google Data APIs + +8.1.1 If you use any API to retrieve data from Google, you acknowledge that the data may be protected by intellectual property rights which are owned by Google or those parties that provide the data (or by other persons or companies on their behalf). Your use of any such API may be subject to additional Terms of Service. You may not modify, rent, lease, loan, sell, distribute or create derivative works based on this data (either in whole or in part) unless allowed by the relevant Terms of Service. + +8.1.2 If you use any API to retrieve a user's data from Google, you acknowledge and agree that you shall retrieve data only with the user's explicit consent and only when, and for the limited purposes for which, the user has given you permission to do so. If you use the Android Recognition Service API, documented at the following URL: https://developer.android.com/reference/android/speech/RecognitionService, as updated from time to time, you acknowledge that the use of the API is subject to the Data Processing Addendum for Products where Google is a Data Processor, which is located at the following URL: https://privacy.google.com/businesses/gdprprocessorterms/, as updated from time to time. By clicking to accept, you hereby agree to the terms of the Data Processing Addendum for Products where Google is a Data Processor. + + +9. Terminating the License Agreement + +9.1 The License Agreement will continue to apply until terminated by either you or Google as set out below. + +9.2 If you want to terminate the License Agreement, you may do so by ceasing your use of the SDK and any relevant developer credentials. + +9.3 Google may at any time, terminate the License Agreement with you if: +(A) you have breached any provision of the License Agreement; or +(B) Google is required to do so by law; or +(C) the partner with whom Google offered certain parts of SDK (such as APIs) to you has terminated its relationship with Google or ceased to offer certain parts of the SDK to you; or +(D) Google decides to no longer provide the SDK or certain parts of the SDK to users in the country in which you are resident or from which you use the service, or the provision of the SDK or certain SDK services to you by Google is, in Google's sole discretion, no longer commercially viable. + +9.4 When the License Agreement comes to an end, all of the legal rights, obligations and liabilities that you and Google have benefited from, been subject to (or which have accrued over time whilst the License Agreement has been in force) or which are expressed to continue indefinitely, shall be unaffected by this cessation, and the provisions of paragraph 14.7 shall continue to apply to such rights, obligations and liabilities indefinitely. + + +10. DISCLAIMER OF WARRANTIES + +10.1 YOU EXPRESSLY UNDERSTAND AND AGREE THAT YOUR USE OF THE SDK IS AT YOUR SOLE RISK AND THAT THE SDK IS PROVIDED "AS IS" AND "AS AVAILABLE" WITHOUT WARRANTY OF ANY KIND FROM GOOGLE. + +10.2 YOUR USE OF THE SDK AND ANY MATERIAL DOWNLOADED OR OTHERWISE OBTAINED THROUGH THE USE OF THE SDK IS AT YOUR OWN DISCRETION AND RISK AND YOU ARE SOLELY RESPONSIBLE FOR ANY DAMAGE TO YOUR COMPUTER SYSTEM OR OTHER DEVICE OR LOSS OF DATA THAT RESULTS FROM SUCH USE. + +10.3 GOOGLE FURTHER EXPRESSLY DISCLAIMS ALL WARRANTIES AND CONDITIONS OF ANY KIND, WHETHER EXPRESS OR IMPLIED, INCLUDING, BUT NOT LIMITED TO THE IMPLIED WARRANTIES AND CONDITIONS OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. + + +11. LIMITATION OF LIABILITY + +11.1 YOU EXPRESSLY UNDERSTAND AND AGREE THAT GOOGLE, ITS SUBSIDIARIES AND AFFILIATES, AND ITS LICENSORS SHALL NOT BE LIABLE TO YOU UNDER ANY THEORY OF LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, CONSEQUENTIAL OR EXEMPLARY DAMAGES THAT MAY BE INCURRED BY YOU, INCLUDING ANY LOSS OF DATA, WHETHER OR NOT GOOGLE OR ITS REPRESENTATIVES HAVE BEEN ADVISED OF OR SHOULD HAVE BEEN AWARE OF THE POSSIBILITY OF ANY SUCH LOSSES ARISING. + + +12. Indemnification + +12.1 To the maximum extent permitted by law, you agree to defend, indemnify and hold harmless Google, its affiliates and their respective directors, officers, employees and agents from and against any and all claims, actions, suits or proceedings, as well as any and all losses, liabilities, damages, costs and expenses (including reasonable attorneys fees) arising out of or accruing from (a) your use of the SDK, (b) any application you develop on the SDK that infringes any copyright, trademark, trade secret, trade dress, patent or other intellectual property right of any person or defames any person or violates their rights of publicity or privacy, and (c) any non-compliance by you with the License Agreement. + + +13. Changes to the License Agreement + +13.1 Google may make changes to the License Agreement as it distributes new versions of the SDK. When these changes are made, Google will make a new version of the License Agreement available on the website where the SDK is made available. + + +14. General Legal Terms + +14.1 The License Agreement constitutes the whole legal agreement between you and Google and governs your use of the SDK (excluding any services which Google may provide to you under a separate written agreement), and completely replaces any prior agreements between you and Google in relation to the SDK. + +14.2 You agree that if Google does not exercise or enforce any legal right or remedy which is contained in the License Agreement (or which Google has the benefit of under any applicable law), this will not be taken to be a formal waiver of Google's rights and that those rights or remedies will still be available to Google. + +14.3 If any court of law, having the jurisdiction to decide on this matter, rules that any provision of the License Agreement is invalid, then that provision will be removed from the License Agreement without affecting the rest of the License Agreement. The remaining provisions of the License Agreement will continue to be valid and enforceable. + +14.4 You acknowledge and agree that each member of the group of companies of which Google is the parent shall be third party beneficiaries to the License Agreement and that such other companies shall be entitled to directly enforce, and rely upon, any provision of the License Agreement that confers a benefit on (or rights in favor of) them. Other than this, no other person or company shall be third party beneficiaries to the License Agreement. + +14.5 EXPORT RESTRICTIONS. THE SDK IS SUBJECT TO UNITED STATES EXPORT LAWS AND REGULATIONS. YOU MUST COMPLY WITH ALL DOMESTIC AND INTERNATIONAL EXPORT LAWS AND REGULATIONS THAT APPLY TO THE SDK. THESE LAWS INCLUDE RESTRICTIONS ON DESTINATIONS, END USERS AND END USE. + +14.6 The rights granted in the License Agreement may not be assigned or transferred by either you or Google without the prior written approval of the other party. Neither you nor Google shall be permitted to delegate their responsibilities or obligations under the License Agreement without the prior written approval of the other party. + +14.7 The License Agreement, and your relationship with Google under the License Agreement, shall be governed by the laws of the State of California without regard to its conflict of laws provisions. You and Google agree to submit to the exclusive jurisdiction of the courts located within the county of Santa Clara, California to resolve any legal matter arising from the License Agreement. Notwithstanding this, you agree that Google shall still be allowed to apply for injunctive remedies (or an equivalent type of urgent legal relief) in any jurisdiction. + + +January 16, 2019 + + To get started with the Android SDK Preview, you must agree to the following terms and conditions. +As described below, please note that this is a preview version of the Android SDK, subject to change, that you use at your own risk. The Android SDK Preview is not a stable release, and may contain errors and defects that can result in serious damage to your computer systems, devices and data. + +This is the Android SDK Preview License Agreement (the "License Agreement"). + +1. Introduction + +1.1 The Android SDK Preview (referred to in the License Agreement as the “Preview” and specifically including the Android system files, packaged APIs, and Preview library files, if and when they are made available) is licensed to you subject to the terms of the License Agreement. The License Agreement forms a legally binding contract between you and Google in relation to your use of the Preview. + +1.2 "Android" means the Android software stack for devices, as made available under the Android Open Source Project, which is located at the following URL: http://source.android.com/, as updated from time to time. + +1.3 "Google" means Google Inc., a Delaware corporation with principal place of business at 1600 Amphitheatre Parkway, Mountain View, CA 94043, United States. + +2. Accepting the License Agreement + +2.1 In order to use the Preview, you must first agree to the License Agreement. You may not use the Preview if you do not accept the License Agreement. + +2.2 By clicking to accept and/or using the Preview, you hereby agree to the terms of the License Agreement. + +2.3 You may not use the Preview and may not accept the License Agreement if you are a person barred from receiving the Preview under the laws of the United States or other countries including the country in which you are resident or from which you use the Preview. + +2.4 If you will use the Preview internally within your company or organization you agree to be bound by the License Agreement on behalf of your employer or other entity, and you represent and warrant that you have full legal authority to bind your employer or such entity to the License Agreement. If you do not have the requisite authority, you may not accept the License Agreement or use the Preview on behalf of your employer or other entity. + +3. Preview License from Google + +3.1 Subject to the terms of the License Agreement, Google grants you a royalty-free, non-assignable, non-exclusive, non-sublicensable, limited, revocable license to use the Preview, personally or internally within your company or organization, solely to develop applications to run on the Android platform. + +3.2 You agree that Google or third parties owns all legal right, title and interest in and to the Preview, including any Intellectual Property Rights that subsist in the Preview. "Intellectual Property Rights" means any and all rights under patent law, copyright law, trade secret law, trademark law, and any and all other proprietary rights. Google reserves all rights not expressly granted to you. + +3.3 You may not use the Preview for any purpose not expressly permitted by the License Agreement. Except to the extent required by applicable third party licenses, you may not: (a) copy (except for backup purposes), modify, adapt, redistribute, decompile, reverse engineer, disassemble, or create derivative works of the Preview or any part of the Preview; or (b) load any part of the Preview onto a mobile handset or any other hardware device except a personal computer, combine any part of the Preview with other software, or distribute any software or device incorporating a part of the Preview. + +3.4 You agree that you will not take any actions that may cause or result in the fragmentation of Android, including but not limited to distributing, participating in the creation of, or promoting in any way a software development kit derived from the Preview. + +3.5 Use, reproduction and distribution of components of the Preview licensed under an open source software license are governed solely by the terms of that open source software license and not the License Agreement. You agree to remain a licensee in good standing in regard to such open source software licenses under all the rights granted and to refrain from any actions that may terminate, suspend, or breach such rights. + +3.6 You agree that the form and nature of the Preview that Google provides may change without prior notice to you and that future versions of the Preview may be incompatible with applications developed on previous versions of the Preview. You agree that Google may stop (permanently or temporarily) providing the Preview (or any features within the Preview) to you or to users generally at Google's sole discretion, without prior notice to you. + +3.7 Nothing in the License Agreement gives you a right to use any of Google's trade names, trademarks, service marks, logos, domain names, or other distinctive brand features. + +3.8 You agree that you will not remove, obscure, or alter any proprietary rights notices (including copyright and trademark notices) that may be affixed to or contained within the Preview. + +4. Use of the Preview by You + +4.1 Google agrees that nothing in the License Agreement gives Google any right, title or interest from you (or your licensors) under the License Agreement in or to any software applications that you develop using the Preview, including any intellectual property rights that subsist in those applications. + +4.2 You agree to use the Preview and write applications only for purposes that are permitted by (a) the License Agreement, and (b) any applicable law, regulation or generally accepted practices or guidelines in the relevant jurisdictions (including any laws regarding the export of data or software to and from the United States or other relevant countries). + +4.3 You agree that if you use the Preview to develop applications, you will protect the privacy and legal rights of users. If users provide you with user names, passwords, or other login information or personal information, you must make the users aware that the information will be available to your application, and you must provide legally adequate privacy notice and protection for those users. If your application stores personal or sensitive information provided by users, it must do so securely. If users provide you with Google Account information, your application may only use that information to access the user's Google Account when, and for the limited purposes for which, each user has given you permission to do so. + +4.4 You agree that you will not engage in any activity with the Preview, including the development or distribution of an application, that interferes with, disrupts, damages, or accesses in an unauthorized manner the servers, networks, or other properties or services of Google or any third party. + +4.5 You agree that you are solely responsible for (and that Google has no responsibility to you or to any third party for) any data, content, or resources that you create, transmit or display through Android and/or applications for Android, and for the consequences of your actions (including any loss or damage which Google may suffer) by doing so. + +4.6 You agree that you are solely responsible for (and that Google has no responsibility to you or to any third party for) any breach of your obligations under the License Agreement, any applicable third party contract or Terms of Service, or any applicable law or regulation, and for the consequences (including any loss or damage which Google or any third party may suffer) of any such breach. + +4.7 The Preview is in development, and your testing and feedback are an important part of the development process. By using the Preview, you acknowledge that implementation of some features are still under development and that you should not rely on the Preview having the full functionality of a stable release. You agree not to publicly distribute or ship any application using this Preview as this Preview will no longer be supported after the official Android SDK is released. + +5. Your Developer Credentials + +5.1 You agree that you are responsible for maintaining the confidentiality of any developer credentials that may be issued to you by Google or which you may choose yourself and that you will be solely responsible for all applications that are developed under your developer credentials. + +6. Privacy and Information + +6.1 In order to continually innovate and improve the Preview, Google may collect certain usage statistics from the software including but not limited to a unique identifier, associated IP address, version number of the software, and information on which tools and/or services in the Preview are being used and how they are being used. Before any of this information is collected, the Preview will notify you and seek your consent. If you withhold consent, the information will not be collected. + +6.2 The data collected is examined in the aggregate to improve the Preview and is maintained in accordance with Google's Privacy Policy located at http://www.google.com/policies/privacy/. + +7. Third Party Applications + +7.1 If you use the Preview to run applications developed by a third party or that access data, content or resources provided by a third party, you agree that Google is not responsible for those applications, data, content, or resources. You understand that all data, content or resources which you may access through such third party applications are the sole responsibility of the person from which they originated and that Google is not liable for any loss or damage that you may experience as a result of the use or access of any of those third party applications, data, content, or resources. + +7.2 You should be aware the data, content, and resources presented to you through such a third party application may be protected by intellectual property rights which are owned by the providers (or by other persons or companies on their behalf). You may not modify, rent, lease, loan, sell, distribute or create derivative works based on these data, content, or resources (either in whole or in part) unless you have been specifically given permission to do so by the relevant owners. + +7.3 You acknowledge that your use of such third party applications, data, content, or resources may be subject to separate terms between you and the relevant third party. + +8. Using Google APIs + +8.1 Google APIs + +8.1.1 If you use any API to retrieve data from Google, you acknowledge that the data may be protected by intellectual property rights which are owned by Google or those parties that provide the data (or by other persons or companies on their behalf). Your use of any such API may be subject to additional Terms of Service. You may not modify, rent, lease, loan, sell, distribute or create derivative works based on this data (either in whole or in part) unless allowed by the relevant Terms of Service. + +8.1.2 If you use any API to retrieve a user's data from Google, you acknowledge and agree that you shall retrieve data only with the user's explicit consent and only when, and for the limited purposes for which, the user has given you permission to do so. + +9. Terminating the License Agreement + +9.1 the License Agreement will continue to apply until terminated by either you or Google as set out below. + +9.2 If you want to terminate the License Agreement, you may do so by ceasing your use of the Preview and any relevant developer credentials. + +9.3 Google may at any time, terminate the License Agreement, with or without cause, upon notice to you. + +9.4 The License Agreement will automatically terminate without notice or other action upon the earlier of: +(A) when Google ceases to provide the Preview or certain parts of the Preview to users in the country in which you are resident or from which you use the service; and +(B) Google issues a final release version of the Android SDK. + +9.5 When the License Agreement is terminated, the license granted to you in the License Agreement will terminate, you will immediately cease all use of the Preview, and the provisions of paragraphs 10, 11, 12 and 14 shall survive indefinitely. + +10. DISCLAIMERS + +10.1 YOU EXPRESSLY UNDERSTAND AND AGREE THAT YOUR USE OF THE PREVIEW IS AT YOUR SOLE RISK AND THAT THE PREVIEW IS PROVIDED "AS IS" AND "AS AVAILABLE" WITHOUT WARRANTY OF ANY KIND FROM GOOGLE. + +10.2 YOUR USE OF THE PREVIEW AND ANY MATERIAL DOWNLOADED OR OTHERWISE OBTAINED THROUGH THE USE OF THE PREVIEW IS AT YOUR OWN DISCRETION AND RISK AND YOU ARE SOLELY RESPONSIBLE FOR ANY DAMAGE TO YOUR COMPUTER SYSTEM OR OTHER DEVICE OR LOSS OF DATA THAT RESULTS FROM SUCH USE. WITHOUT LIMITING THE FOREGOING, YOU UNDERSTAND THAT THE PREVIEW IS NOT A STABLE RELEASE AND MAY CONTAIN ERRORS, DEFECTS AND SECURITY VULNERABILITIES THAT CAN RESULT IN SIGNIFICANT DAMAGE, INCLUDING THE COMPLETE, IRRECOVERABLE LOSS OF USE OF YOUR COMPUTER SYSTEM OR OTHER DEVICE. + +10.3 GOOGLE FURTHER EXPRESSLY DISCLAIMS ALL WARRANTIES AND CONDITIONS OF ANY KIND, WHETHER EXPRESS OR IMPLIED, INCLUDING, BUT NOT LIMITED TO THE IMPLIED WARRANTIES AND CONDITIONS OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. + +11. LIMITATION OF LIABILITY + +11.1 YOU EXPRESSLY UNDERSTAND AND AGREE THAT GOOGLE, ITS SUBSIDIARIES AND AFFILIATES, AND ITS LICENSORS SHALL NOT BE LIABLE TO YOU UNDER ANY THEORY OF LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, CONSEQUENTIAL OR EXEMPLARY DAMAGES THAT MAY BE INCURRED BY YOU, INCLUDING ANY LOSS OF DATA, WHETHER OR NOT GOOGLE OR ITS REPRESENTATIVES HAVE BEEN ADVISED OF OR SHOULD HAVE BEEN AWARE OF THE POSSIBILITY OF ANY SUCH LOSSES ARISING. + +12. Indemnification + +12.1 To the maximum extent permitted by law, you agree to defend, indemnify and hold harmless Google, its affiliates and their respective directors, officers, employees and agents from and against any and all claims, actions, suits or proceedings, as well as any and all losses, liabilities, damages, costs and expenses (including reasonable attorneys’ fees) arising out of or accruing from (a) your use of the Preview, (b) any application you develop on the Preview that infringes any Intellectual Property Rights of any person or defames any person or violates their rights of publicity or privacy, and (c) any non-compliance by you of the License Agreement. + +13. Changes to the License Agreement + +13.1 Google may make changes to the License Agreement as it distributes new versions of the Preview. When these changes are made, Google will make a new version of the License Agreement available on the website where the Preview is made available. + +14. General Legal Terms + +14.1 the License Agreement constitutes the whole legal agreement between you and Google and governs your use of the Preview (excluding any services which Google may provide to you under a separate written agreement), and completely replaces any prior agreements between you and Google in relation to the Preview. + +14.2 You agree that if Google does not exercise or enforce any legal right or remedy which is contained in the License Agreement (or which Google has the benefit of under any applicable law), this will not be taken to be a formal waiver of Google's rights and that those rights or remedies will still be available to Google. + +14.3 If any court of law, having the jurisdiction to decide on this matter, rules that any provision of the License Agreement is invalid, then that provision will be removed from the License Agreement without affecting the rest of the License Agreement. The remaining provisions of the License Agreement will continue to be valid and enforceable. + +14.4 You acknowledge and agree that each member of the group of companies of which Google is the parent shall be third party beneficiaries to the License Agreement and that such other companies shall be entitled to directly enforce, and rely upon, any provision of the License Agreement that confers a benefit on (or rights in favor of) them. Other than this, no other person or company shall be third party beneficiaries to the License Agreement. + +14.5 EXPORT RESTRICTIONS. THE PREVIEW IS SUBJECT TO UNITED STATES EXPORT LAWS AND REGULATIONS. YOU MUST COMPLY WITH ALL DOMESTIC AND INTERNATIONAL EXPORT LAWS AND REGULATIONS THAT APPLY TO THE PREVIEW. THESE LAWS INCLUDE RESTRICTIONS ON DESTINATIONS, END USERS AND END USE. + +14.6 The License Agreement may not be assigned or transferred by you without the prior written approval of Google, and any attempted assignment without such approval will be void. You shall not delegate your responsibilities or obligations under the License Agreement without the prior written approval of Google. + +14.7 The License Agreement, and your relationship with Google under the License Agreement, shall be governed by the laws of the State of California without regard to its conflict of laws provisions. You and Google agree to submit to the exclusive jurisdiction of the courts located within the county of Santa Clara, California to resolve any legal matter arising from the License Agreement. Notwithstanding this, you agree that Google shall still be allowed to apply for injunctive remedies (or an equivalent type of urgent legal relief) in any jurisdiction. + +June 2014. + + Intel Corporation Internal Evaluation License Agreement for x86 Android* System Images for Android Software Development Kit (SDK) +This Internal Evaluation License Agreement (this "Agreement") is entered into by and between Intel and you (as an individual developer or a legal entity -- identified below as Recipient). Intel shall provide the Evaluation Software to Recipient as described in accordance with the Internal Evaluation License Terms and Conditions. + +Definitions. +These terms shall have the following meanings: + +"Intel" or "INTEL" +Intel Corporation +With an Address of: +2200 Mission College Blvd. +Santa Clara, CA 95052 +Office of the General Counsel +Mail Stop: RNB-4-51 +Attn: Software and Services Group Legal + +"Evaluation Software" +The x86 Android* emulator system images for Android Software Development Kit (SDK), as provided by Intel. + +INTERNAL EVALUATION LICENSE TERMS AND CONDITIONS + +1. DEFINITIONS. + +1.1 Additional Defined Terms. "Agreement", "Evaluation Software", "Intel", "Non-disclosure Agreement", "Recipient", and "Effective Date" shall have the meanings ascribed to them on the signature page(s) of this Agreement. + +1.2 Evaluation Materials means, collectively, the Evaluation Software (in source and/or object code form) and documentation (including, without limitation, any design documents, specifications and other related materials) related to the Evaluation Software. + +1.3 "Open Source Software" means any software that requires as a condition of use, modification and/or distribution of such software that such software or other software incorporated into, derived from or distributed with such software (a) be disclosed or distributed in source code form; or (b) be licensed by the user to third parties for the purpose of making and/or distributing derivative works; or (c) be redistributable at no charge. Open Source Software includes, without limitation, software licensed or distributed under any of the following licenses or distribution models, or licenses or distribution models substantially similar to any of the following: (a) GNU’s General Public License (GPL) or Lesser/Library GPL (LGPL), (b) the Artistic License (e.g., PERL), (c) the Mozilla Public License, (d) the Netscape Public License, (e) the Sun Community Source License (SCSL), (f) the Sun Industry Source License (SISL), (g) the Apache Software license and (h) the Common Public License (CPL). + +1.4 "Pre-Release Materials" means "alpha" or "beta" designated pre-release features, which may not be fully functional, which Intel may substantially modify in producing any production version of the Evaluation Materials and/or is still under development by Intel and/or Intel’s suppliers. + +2. PURPOSE. Intel desires to provide the Evaluation Materials to Recipient solely for Recipient's internal evaluation of the Evaluation Software and other Intel products, to evaluate the desirability of cooperating with Intel in developing products based on the Evaluation Software and/or to advise Intel as to possible modifications to the Evaluation Software. Recipient may not disclose, distribute or make commercial use of the Evaluation Materials or any modifications to the Evaluation Materials. +THE EVALUATION MATERIALS ARE PROVIDED FOR EVALUATION PURPOSES ONLY AND MAY NOT BE DISTRIBUTED BY RECIPIENT OR INCORPORATED INTO RECIPIENT’S PRODUCTS OR SOFTWARE. PLEASE CONTACT AN INTEL SALES REPRESENTATIVE TO LEARN ABOUT THE AVAILABILITY AND COST OF A COMMERICAL VERSION OF THE EVALUATION SOFTWARE. + +3. TITLE. Title to the Evaluation Materials remains with Intel or its suppliers. Recipient shall not mortgage, pledge or encumber the Evaluation Materials in any way. Recipient shall return all Evaluation Materials, keeping no copies, upon termination or expiration of this Agreement. + +4. LICENSE. Intel grants Recipient a royalty-free, personal, nontransferable, nonexclusive license under its copyrights to use the Evaluation Software only for the purposes described in paragraph 2 above. Unless otherwise communicated in writing by Intel to Recipient, to the extent the Evaluation Software is provided in more than one delivery or release (each, a "Release") the license grant in this Section 4 and the Evaluation Period shall apply to each Release. Recipient may not make modifications to the Evaluation Software. Recipient shall not disassemble, reverse-engineer, or decompile any software not provided to Recipient in source code form. +EXCEPT AS PROVIDED HEREIN, NO OTHER LICENSE, EXPRESS OR IMPLIED, BY ESTOPPEL OR OTHERWISE, TO ANY OTHER INTELLECTUAL PROPERTY RIGHTS IS GRANTED TO THE RECIPIENT. + +5. NO OBLIGATION. Recipient shall have no duty to purchase or license any product from Intel. Intel and its suppliers shall have no obligation to provide support for, or develop a non-evaluation version of, the Evaluation Software or to license any version of it. + +6. MODIFICATIONS. This Agreement does NOT obligate Recipient to provide Intel with comments or suggestions regarding Evaluation Materials. However, should Recipient provide Intel with comments or suggestions for the modification, correction, improvement or enhancement of (a) the Evaluation Materials or (b) Intel products or processes which may embody the Evaluation Materials, Recipient grants to Intel a non-exclusive, irrevocable, worldwide, royalty-free license, with the right to sublicense Intel’s licensees and customers, under Recipient intellectual property rights, the rights to use and disclose such comments and suggestions in any manner Intel chooses and to display, perform, copy, make, have made, use, sell, offer to sell, import, and otherwise dispose of Intel’s and its sublicensee’s products embodying such comments and suggestions in any manner and via any media Intel chooses, without reference to the source. + +7. WARRANTY DISCLAIMER. INTEL AND ITS SUPPLIERS MAKE NO WARRANTIES WITH RESPECT TO EVALUATION MATERIALS, EITHER EXPRESS OR IMPLIED, INCLUDING ANY IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, OR ANY IMPLIED WARRANTY OF NONINFRINGEMENT. THE EVALUATION MATERIALS ARE PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND. + +8. LIMITATION OF LIABILITY. INTEL AND ITS SUPPLIERS SHALL NOT BE LIABLE FOR ANY PROPERTY DAMAGE, PERSONAL INJURY, LOSS OF PROFITS, INTERRUPTION OF BUSINESS OR ANY SPECIAL, CONSEQUENTIAL OR INCIDENTAL DAMAGES, HOWEVER CAUSED, WHETHER FOR BREACH OF WARRANTY, CONTRACT, STRICT LIABILITY OR OTHERWISE. INTEL AND ITS SUPPLIERS DISCLAIM ALL LIABILITY, INCLUDING LIABILITY FOR INFRINGEMENT OF ANY INTELLECTUAL PROPERTY RIGHTS RELATING TO THE EVALUATION MATERIALS. + +9. EXPIRATION. Intel may terminate this Agreement immediately after a breach by Recipient. + +10. GENERAL. + +10.1 Controlling Law. Any claims arising under or relating to this Agreement shall be governed by the internal substantive laws of the State of Delaware or federal courts located in Delaware, without regard to principles of conflict of laws. Each party hereby agrees to jurisdiction and venue in the courts of the State of Delaware for all disputes and litigation arising under or relating to this Agreement. The parties agree that the United Nations Convention on Contracts for the International Sale of Goods is specifically excluded from application to this Agreement. The parties consent to the personal jurisdiction of the above courts. + +10.2 Remedies. Recipient acknowledges that any disclosure, commercialization, or public use of the Evaluation Materials would cause irreparable injury to Intel and consents to the grant of an injunction by any court of competent jurisdiction in the event of a threatened breach. + +10.3 Assignment. Recipient may not delegate, assign or transfer this Agreement, the license granted or any of Recipient’s rights or duties hereunder, expressly, by implication, by operation of law, by way of merger (regardless of whether Recipient is the surviving entity) or acquisition, or otherwise and any attempt to do so, without Intel’s express prior written consent, shall be null and void. Intel may assign this Agreement, and its rights and obligations hereunder, in its sole discretion. + +10.4 Entire Agreement. This Agreement constitutes the entire agreement between Recipient and Intel and supersedes in their entirety any and all oral or written agreements previously existing between Recipient and Intel with respect to the subject matter hereof. This Agreement supersedes any and all "click-to-accept" or shrink-wrapped licenses, in hard-copy or electronic form, embedded in or included with the Evaluation Materials. This Agreement may only be amended or supplemented by a writing that refers explicitly to this Agreement and that is signed by duly authorized representatives of Recipient and Intel. Without limiting the foregoing, terms and conditions on any purchase orders or similar materials submitted by Recipient to Intel, and any terms contained in Intel’s standard acknowledgment form that are in conflict with these terms, shall be of no force or effect. + +10.5 Severability. In the event that any provision of this Agreement shall be unenforceable or invalid under any applicable law or be so held by applicable court decision, such unenforceability or invalidity shall not render this Agreement unenforceable or invalid as a whole, and, in such event, such provision shall be changed and interpreted so as to best accomplish the objectives of such unenforceable or invalid provision within the limits of applicable law or applicable court decisions. + +10.6 Export Regulations / Export Control. Recipient shall not export, either directly or indirectly, any product, service or technical data or system incorporating the Evaluation Materials without first obtaining any required license or other approval from the U.S. Department of Commerce or any other agency or department of the United States Government. In the event any product is exported from the United States or re-exported from a foreign destination by Recipient, Recipient shall ensure that the distribution and export/re-export or import of the product is in compliance with all laws, regulations, orders, or other restrictions of the U.S. Export Administration Regulations and the appropriate foreign government. Recipient agrees that neither it nor any of its subsidiaries will export/re-export any technical data, process, product, or service, directly or indirectly, to any country for which the United States government or any agency thereof or the foreign government from where it is shipping requires an export license, or other governmental approval, without first obtaining such license or approval. Recipient also agrees to implement measures to ensure that foreign national employees are authorized to receive any information controlled by U.S. export control laws. An export is "deemed" to take place when information is released to a foreign national wherever located. + +10.7 Special Terms for Pre-Release Materials. If so indicated in the description of the Evaluation Software, the Evaluation Software may contain Pre-Release Materials. Recipient hereby understands, acknowledges and agrees that: (i) Pre-Release Materials may not be fully tested and may contain bugs or errors; (ii) Pre-Release materials are not suitable for commercial release in their current state; (iii) regulatory approvals for Pre-Release Materials (such as UL or FCC) have not been obtained, and Pre-Release Materials may therefore not be certified for use in certain countries or environments and (iv) Intel can provide no assurance that it will ever produce or make generally available a production version of the Pre-Release Materials . Intel is not under any obligation to develop and/or release or offer for sale or license a final product based upon the Pre-Release Materials and may unilaterally elect to abandon the Pre-Release Materials or any such development platform at any time and without any obligation or liability whatsoever to Recipient or any other person. + +10.8 Open Source Software. In the event Open Source software is included with Evaluation Software, such Open Source software is licensed pursuant to the applicable Open Source software license agreement identified in the Open Source software comments in the applicable source code file(s) and/or file header provided with Evaluation Software. Additional detail may be provided (where applicable) in the accompanying on-line documentation. With respect to the Open Source software, nothing in this Agreement limits any rights under, or grants rights that supersede, the terms of any applicable Open Source software license agreement. +ANY PRE-RELEASE MATERIALS ARE NON-QUALIFIED AND, AS SUCH, ARE PROVIDED POSSIBLY WITH FAULTS + + + MIPS Technologies, Inc. (“MIPS”) Internal Evaluation License Agreement for MIPS Android™ System Images for Android Software Development Kit (SDK): +This Internal Evaluation License Agreement (this "Agreement") is entered into by and between MIPS and you (as an individual developer or a legal entity -- identified below as “Recipient”). MIPS shall make the Evaluation Software available to Recipient as described in accordance with the terms and conditions set forth below. + +By clicking on the “Accept” button, downloading, installing, or otherwise using the Evaluation Materials (defined below), you agree to be bound by the terms of this Agreement effective as of the date you click “Accept” (the “Effective Date”), and if doing so on behalf of an entity, you represent that you are authorized to bind the entity to the terms and conditions of this Agreement. If you do not agree to be bound by the terms and conditions of this Agreement, do not download, install, or use the Evaluation Materials. + +1. DEFINITIONS. These terms shall have the following meanings: + +1.1 “MIPS” shall mean MIPS Technologies, Inc., a Delaware corporation having a principal place of business at: 955 East Arques Ave., Sunnyvale, CA 94085 + +1.2 “Evaluation Software” shall mean MIPS Android™ emulator system images for Android Software Development Kit (SDK), as made available to Recipient. + +1.3 “Evaluation Materials" means, collectively, the Evaluation Software (in source and/or object code form) and documentation (including, without limitation, any design documents, specifications, reference manuals, and other related materials) related to the Evaluation Software as made available to Recipient. + +1.4 “Open Source Software” means any software that requires (as a condition of use, modification and/or distribution of such software) that such software or other software incorporated into, derived from or distributed with such software (a) be disclosed or distributed in source code form; or (b) be licensed by the user to third parties for the purpose of making and/or distributing derivative works; or (c) be redistributable at no charge. Open Source Software includes, without limitation, software licensed or distributed under any of the following licenses or distribution models, or licenses or distribution models substantially similar to any of the following: (a) GNU’s General Public License (GPL) or Lesser/Library GPL (LGPL), (b) the Artistic License (e.g., PERL), (c) the Mozilla Public License, (d) the Netscape Public License, (e) the Sun Community Source License (SCSL), (f) the Sun Industry Source License (SISL), (g) the Apache Software license and (h) the Common Public License (CPL). + +1.5 “Pre-Release Materials” means “alpha” or “beta” designated pre-release features, which may not be fully functional, which MIPS may substantially modify in producing any production version of the Evaluation Materials, and/or which is still under development by MIPS and/or MIPS’ suppliers. + +2. PURPOSE. MIPS desires to make the Evaluation Materials available to Recipient solely for Recipient's internal evaluation of the Evaluation Software to evaluate the desirability of cooperating with MIPS in developing products that are compatible with the Evaluation Software and/or to advise MIPS as to possible modifications to the Evaluation Software. Recipient may not disclose, distribute, modify (except to facilitate the above-mentioned internal evaluation), or make commercial use of the Evaluation Materials or any modifications of the Evaluation Materials. + +THE EVALUATION MATERIALS ARE PROVIDED FOR EVALUATION PURPOSES ONLY AND MAY NOT BE MODIFIED (EXCEPT TO FACILITATE THE INTERNAL EVALUATION) OR DISTRIBUTED BY RECIPIENT OR INCORPORATED INTO RECIPIENT’S PRODUCTS OR SOFTWARE. PLEASE CONTACT A MIPS SALES REPRESENTATIVE TO LEARN ABOUT THE AVAILABILITY AND COST OF A COMMERCIAL VERSION OF THE EVALUATION SOFTWARE. + +3. TITLE. Title to the Evaluation Materials remains with MIPS or its suppliers. Recipient shall not mortgage, pledge or encumber the Evaluation Materials in any way. Recipient shall return all Evaluation Materials, keeping no copies, upon termination or expiration of this Agreement. + +4. LICENSE. MIPS grants Recipient a royalty-free, personal, nontransferable, nonexclusive license under its copyrights to use the Evaluation Software only for the purposes described in paragraph 2 above and only for a period beginning on the Effective Date and extending to the first anniversary of the Effective Date (the “Evaluation Period”). Unless otherwise communicated in writing by MIPS to Recipient, to the extent the Evaluation Software is provided in more than one delivery or release (each, a “Release”) the license grant in this Section 4 and the Evaluation Period shall apply to each Release, in which case the Evaluation Period shall begin on the date that the Release is made generally available and continue to the first anniversary of such date. Recipient may not make modifications to the Evaluation Software. Recipient shall not disassemble, reverse-engineer, or decompile any software that is not provided to Recipient in source code form. + + +EXCEPT AS PROVIDED HEREIN, NO OTHER LICENSE, EXPRESS OR IMPLIED, BY ESTOPPEL OR OTHERWISE, TO ANY OTHER MIPS INTELLECTUAL PROPERTY RIGHTS IS GRANTED TO THE RECIPIENT. OTHER THAN AS EXPLICITLY SET FORTH IN PARAGRAPH 2 ABOVE, NO RIGHT TO COPY, TO REPRODUCE, TO MODIFY, OR TO CREATE DERIVATIVE WORKS OF, THE EVALUATION MATERIALS IS GRANTED HEREIN. + +5. NO OBLIGATION. Recipient shall have no duty to purchase or license any product from MIPS. MIPS and its suppliers shall have no obligation to provide support for, or develop a non-evaluation version of, the Evaluation Software or to license any version of it. + +6. MODIFICATIONS. This Agreement does not obligate Recipient to provide MIPS with comments or suggestions regarding Evaluation Materials. However, should Recipient provide MIPS with comments or suggestions for the modification, correction, improvement or enhancement of (a) the Evaluation Materials or (b) MIPS products or processes which may embody the Evaluation Materials, then Recipient agrees to grant and hereby grants to MIPS a non-exclusive, irrevocable, worldwide, fully paid-up, royalty-free license, with the right to sublicense MIPS’ licensees and customers, under Recipient’s Intellectual property rights, to use and disclose such comments and suggestions in any manner MIPS chooses and to display, perform, copy, make, have made, use, sell, offer to sell, import, and otherwise dispose of MIPS’ and its sublicensee’s products embodying such comments and suggestions in any manner and via any media MIPS chooses, without reference to the source. + +7. WARRANTY DISCLAIMER. MIPS AND ITS SUPPLIERS MAKE NO WARRANTIES WITH RESPECT TO EVALUATION MATERIALS, EITHER EXPRESS OR IMPLIED, INCLUDING ANY IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, OR ANY IMPLIED WARRANTY OF NONINFRINGEMENT WITH RESPECT TO THIRD PARTY INTELLECTUAL PROPERTY. RECIPIENT ACKNOWLEDGES AND AGREES THAT THE EVALUATION MATERIALS ARE PROVIDED “AS IS,” WITHOUT WARRANTY OF ANY KIND. + +8. LIMITATION OF LIABILITY. MIPS AND ITS SUPPLIERS SHALL NOT BE LIABLE FOR ANY PROPERTY DAMAGE, PERSONAL INJURY, LOSS OF PROFITS, INTERRUPTION OF BUSINESS OR FOR ANY DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL OR INCIDENTAL DAMAGES, HOWEVER CAUSED OR ALLEGED, WHETHER FOR BREACH OF WARRANTY, CONTRACT, STRICT LIABILITY OR OTHERWISE, INCLUDING WITHOUT LIMITATION, UNDER TORT OR OTHER LEGAL THEORY. MIPS AND ITS SUPPLIERS DISCLAIM ANY AND ALL LIABILITY, INCLUDING LIABILITY FOR INFRINGEMENT OF ANY INTELLECTUAL PROPERTY RIGHTS OF ANY KIND RELATING TO THE EVALUATION MATERIALS. + +9. EXPIRATION. MIPS may terminate this Agreement immediately after a breach by Recipient or otherwise at MIPS’ reasonable discretion and upon five (5) business days’ notice to Recipient. + +10. GENERAL. + +10.1 Controlling Law. This Agreement shall be governed by California law excluding its choice of law rules. With the exception of MIPS’ rights to enforce its intellectual property rights and any confidentiality obligations under this Agreement or any licenses distributed with the Evaluation Materials, all disputes and any claims arising under or relating to this Agreement shall be subject to the exclusive jurisdiction and venue of the state and federal courts located in Santa Clara County, California. Each party hereby agrees to jurisdiction and venue in the courts set forth in the preceding sentence. The parties agree that the United Nations Convention on Contracts for the International Sale of Goods is specifically excluded from application to this Agreement. The parties consent to the personal jurisdiction of the above courts. + +10.2 Remedies. Recipient acknowledges and agrees that any breach of confidentiality obligations under this Agreement or any licenses distributed with the Evaluation Materials, as well as any disclosure, commercialization, or public use of the Evaluation Materials, would cause irreparable injury to MIPS, and therefore Recipient agrees to consent to, and hereby consents to, the grant of an injunction by any court of competent jurisdiction in the event of an actual or threatened breach. + +10.3 Assignment. Recipient may not delegate, assign or transfer this Agreement, the license granted or any of Recipient’s rights, obligations, or duties hereunder, expressly, by implication, by operation of law, by way of merger (regardless of whether Recipient is the surviving entity) or acquisition, or otherwise and any attempt to do so, without MIPS’ express prior written consent, shall be ineffective, null and void. MIPS may freely assign this Agreement, and its rights and obligations hereunder, in its sole discretion. + +10.4 Entire Agreement. This Agreement constitutes the entire agreement between Recipient and MIPS and supersedes in their entirety any and all oral or written agreements previously existing between Recipient and MIPS with respect to the subject matter hereof. This Agreement may only be amended or supplemented by a writing that refers explicitly to this Agreement and that is signed or otherwise accepted by duly authorized representatives of Recipient and MIPS. + +10.5 Severability. In the event that any provision of this Agreement is finally adjudicated to be unenforceable or invalid under any applicable law, such unenforceability or invalidity shall not render this Agreement unenforceable or invalid as a whole, and, in such event, such unenforceable or invalid provision shall be interpreted so as to best accomplish the objectives of such provision within the limits of applicable law or applicable court decisions. + +10.6 Export Regulations / Export Control. Recipient shall not export, either directly or indirectly, any product, service or technical data or system incorporating the Evaluation Materials without first obtaining any required license or other necessary approval from the U.S. Department of Commerce or any other governing agency or department of the United States Government. In the event any product is exported from the United States or re-exported from a foreign destination by Recipient, Recipient shall ensure that the distribution and export/re-export or import of the product is in compliance with all applicable laws, regulations, orders, or other restrictions of the U.S. Export Administration Regulations and the appropriate foreign government. Recipient agrees that neither it nor any of its subsidiaries will export/re-export any technical data, process, product, or service, directly or indirectly, to any country for which the United States government or any agency thereof or the foreign government from where it is shipping requires an export license, or other governmental approval, without first obtaining such license or approval. Recipient also agrees to implement measures to ensure that foreign national employees are authorized to receive any information controlled by U.S. export control laws. An export is "deemed" to take place when information is released to a foreign national wherever located. + +10.7 Special Terms for Pre-Release Materials. If so indicated in the description of the Evaluation Software, the Evaluation Software may contain Pre-Release Materials. Recipient hereby understands, acknowledges and agrees that: (i) Pre-Release Materials may not be fully tested and may contain bugs or errors; (ii) Pre-Release materials are not suitable for commercial release in their current state; (iii) regulatory approvals for Pre-Release Materials (such as UL or FCC) have not been obtained, and Pre-Release Materials may therefore not be certified for use in certain countries or environments or may not be suitable for certain applications and (iv) MIPS can provide no assurance that it will ever produce or make generally available a production version of the Pre-Release Materials . MIPS is not under any obligation to develop and/or release or offer for sale or license a final product based upon the Pre-Release Materials and may unilaterally elect to abandon the Pre-Release Materials or any such development platform at any time and without any obligation or liability whatsoever to Recipient or any other person. + +ANY PRE-RELEASE MATERIALS ARE NON-QUALIFIED AND, AS SUCH, ARE PROVIDED “AS IS” AND “AS AVAILABLE”, POSSIBLY WITH FAULTS, AND WITHOUT REPRESENTATION OR WARRANTY OF ANY KIND. + +10.8 Open Source Software. In the event Open Source software is included with Evaluation Software, such Open Source software is licensed pursuant to the applicable Open Source software license agreement identified in the Open Source software comments in the applicable source code file(s) and/or file header as indicated in the Evaluation Software. Additional detail may be available (where applicable) in the accompanying on-line documentation. With respect to the Open Source software, nothing in this Agreement limits any rights under, or grants rights that supersede, the terms of any applicable Open Source software license agreement. + + Terms and Conditions + +This is the Google TV Add-on for the Android Software Development Kit License Agreement. + +1. Introduction + +1.1 The Google TV Add-on for the Android Software Development Kit (referred to in this License Agreement as the "Google TV Add-on" and specifically including the Android system files, packaged APIs, and Google APIs add-ons) is licensed to you subject to the terms of this License Agreement. This License Agreement forms a legally binding contract between you and Google in relation to your use of the Google TV Add-on. + +1.2 "Google" means Google Inc., a Delaware corporation with principal place of business at 1600 Amphitheatre Parkway, Mountain View, CA 94043, United States. + +2. Accepting this License Agreement + +2.1 In order to use the Google TV Add-on, you must first agree to this License Agreement. You may not use the Google TV Add-on if you do not accept this License Agreement. + +2.2 You can accept this License Agreement by: + +(A) clicking to accept or agree to this License Agreement, where this option is made available to you; or + +(B) by actually using the Google TV Add-on. In this case, you agree that use of the Google TV Add-on constitutes acceptance of the License Agreement from that point onwards. + +2.3 You may not use the Google TV Add-on and may not accept the Licensing Agreement if you are a person barred from receiving the Google TV Add-on under the laws of the United States or other countries including the country in which you are resident or from which you use the Google TV Add-on. + +2.4 If you are agreeing to be bound by this License Agreement on behalf of your employer or other entity, you represent and warrant that you have full legal authority to bind your employer or such entity to this License Agreement. If you do not have the requisite authority, you may not accept the Licensing Agreement or use the Google TV Add-on on behalf of your employer or other entity. + +3. Google TV Add-on License from Google + +3.1 Subject to the terms of this License Agreement, Google grants you a limited, worldwide, royalty-free, non- assignable and non-exclusive license to use the Google TV Add-on solely to develop applications to run on the Google TV platform. + +3.2 You agree that Google or third parties own all legal right, title and interest in and to the Google TV Add-on, including any Intellectual Property Rights that subsist in the Google TV Add-on. "Intellectual Property Rights" means any and all rights under patent law, copyright law, trade secret law, trademark law, and any and all other proprietary rights. Google reserves all rights not expressly granted to you. + +3.3 Except to the extent required by applicable third party licenses, you may not copy (except for backup purposes), modify, adapt, redistribute, decompile, reverse engineer, disassemble, or create derivative works of the Google TV Add-on or any part of the Google TV Add-on. Except to the extent required by applicable third party licenses, you may not load any part of the Google TV Add-on onto a mobile handset, television, or any other hardware device except a personal computer, combine any part of the Google TV Add-on with other software, or distribute any software or device incorporating a part of the Google TV Add-on. + +3.4 Use, reproduction and distribution of components of the Google TV Add-on licensed under an open source software license are governed solely by the terms of that open source software license and not this License Agreement. + +3.5 You agree that the form and nature of the Google TV Add-on that Google provides may change without prior notice to you and that future versions of the Google TV Add-on may be incompatible with applications developed on previous versions of the Google TV Add-on. You agree that Google may stop (permanently or temporarily) providing the Google TV Add-on (or any features within the Google TV Add-on) to you or to users generally at Google's sole discretion, without prior notice to you. + +3.6 Nothing in this License Agreement gives you a right to use any of Google's or it’s licensors’ trade names, trademarks, service marks, logos, domain names, or other distinctive brand features. + +3.7 You agree that you will not remove, obscure, or alter any proprietary rights notices (including copyright and trademark notices) that may be affixed to or contained within the Google TV Add-on. + +4. Use of the Google TV Add-on by You + +4.1 Google agrees that it obtains no right, title or interest from you (or your licensors) under this License Agreement in or to any software applications that you develop using the Google TV Add-on, including any intellectual property rights that subsist in those applications. + +4.2 You agree to use the Google TV Add-on and write applications only for purposes that are permitted by (a) this License Agreement and (b) any applicable law, regulation or generally accepted practices or guidelines in the relevant jurisdictions (including any laws regarding the export of data or software to and from the United States or other relevant countries). + +4.3 You agree that if you use the Google TV Add-on to develop applications for general public users, you will protect the privacy and legal rights of those users. If the users provide you with user names, passwords, or other login information or personal information, your must make the users aware that the information will be available to your application, and you must provide legally adequate privacy notice and protection for those users. If your application stores personal or sensitive information provided by users, it must do so securely. If the user provides your application with Google Account information, your application may only use that information to access the user's Google Account when, and for the limited purposes for which, the user has given you explicit permission to do so. + +4.4 You agree that you will not engage in any activity with the Google TV Add-on, including the development or distribution of an application, that interferes with, disrupts, damages, or accesses in an unauthorized manner the servers, networks, or other properties or services of any third party including, but not limited to, Google, Multichannel Video Program Distributors or any mobile communications carrier. + +4.5 You agree that you are solely responsible for (and that Google has no responsibility to you or to any third party for) any data, content, or resources that you create, transmit or display through the Google TV platform and/or applications for the Google TV platform, and for the consequences of your actions (including any loss or damage which Google may suffer) by doing so. + +4.6 You agree that you are solely responsible for (and that Google has no responsibility to you or to any third party for) any breach of your obligations under this License Agreement, any applicable third party contract or Terms of Service, or any applicable law or regulation, and for the consequences (including any loss or damage which Google or any third party may suffer) of any such breach. + +5. Your Developer Credentials + +5.1 You agree that you are responsible for maintaining the confidentiality of any developer credentials that may be issued to you by Google or which you may choose yourself and that you will be solely responsible for all applications that are developed under your developer credentials. + +6. Privacy and Information + +6.1 In order to continually innovate and improve the Google TV Add-on, Google may collect certain usage statistics from the software including but not limited to a unique identifier, associated IP address, version number of the software, and information on which tools and/or services in the Google TV Add-on are being used and how they are being used. Before any of this information is collected, the Google TV Add-on will notify you and seek your consent. If you withhold consent, the information will not be collected. + +6.2 The data collected is examined in the aggregate to improve the Google TV Add-on and is maintained in accordance with Google's Privacy Policy. + +7. Third Party Applications for the Google TV Platform + +7.1 If you use the Google TV Add-on to run applications developed by a third party or that access data, content or resources provided by a third party, you agree that Google is not responsible for those applications, data, content, or resources. You understand that all data, content or resources which you may access through such third party applications are the sole responsibility of the person from which they originated and that Google is not liable for any loss or damage that you may experience as a result of the use or access of any of those third party applications, data, content, or resources. + +7.2 You should be aware the data, content, and resources presented to you through such a third party application may be protected by intellectual property rights which are owned by the providers (or by other persons or companies on their behalf). You may not modify, rent, lease, loan, sell, distribute or create derivative works based on these data, content, or resources (either in whole or in part) unless you have been specifically given permission to do so by the relevant owners. + +7.3 You acknowledge that your use of such third party applications, data, content, or resources may be subject to separate terms between you and the relevant third party. In that case, this License Agreement does not affect your legal relationship with these third parties. + +8. Using Google TV APIs + +8.1 If you use any Google TV API to retrieve data from Google, you acknowledge that the data (“Google TV API Content”) may be protected by intellectual property rights which are owned by Google or those parties that provide the data (or by other persons or companies on their behalf). Your use of any such API may be subject to additional Terms of Service. You may not modify, rent, lease, loan, sell, distribute or create derivative works based on this data (either in whole or in part) unless allowed by the relevant Terms of Service. Some portions of the Google TV API Content are licensed to Google by third parties, including but not limited to Tribune Media Services + +8.2 If you use any API to retrieve a user's data from Google, you acknowledge and agree that you shall retrieve data only with the user's explicit consent and only when, and for the limited purposes for which, the user has given you permission to do so. + +8.3 Except as explicitly permitted in Section 3 (Google TV Add-on License from Google), you must: + +(a) not modify nor format the Google TV API Content except to the extent reasonably and technically necessary to optimize the display such Google TV API Content in your application; + +(b) not edit the Google TV API Content in a manner that renders the Google TV API Content inaccurate of alters its inherent meaning (provided that displaying excerpts will not violate the foregoing); or + +(c) not create any commercial audience measurement tool or service using the Google TV API Content + +9. Terminating this License Agreement + +9.1 This License Agreement will continue to apply until terminated by either you or Google as set out below. + +9.2 If you want to terminate this License Agreement, you may do so by ceasing your use of the Google TV Add-on and any relevant developer credentials. + +9.3 Google may at any time, terminate this License Agreement with you if: + +(A) you have breached any provision of this License Agreement; or + +(B) Google is required to do so by law; or + +(C) the partner with whom Google offered certain parts of Google TV Add-on (such as APIs) to you has terminated its relationship with Google or ceased to offer certain parts of the Google TV Add-on to you; or + +(D) Google decides to no longer providing the Google TV Add-on or certain parts of the Google TV Add-on to users in the country in which you are resident or from which you use the service, or the provision of the Google TV Add-on or certain Google TV Add-on services to you by Google is, in Google's sole discretion, no longer commercially viable. + +9.4 When this License Agreement comes to an end, all of the legal rights, obligations and liabilities that you and Google have benefited from, been subject to (or which have accrued over time whilst this License Agreement has been in force) or which are expressed to continue indefinitely, shall be unaffected by this cessation, and the provisions of paragraph 14.7 shall continue to apply to such rights, obligations and liabilities indefinitely. + +10. DISCLAIMER OF WARRANTIES + +10.1 YOU EXPRESSLY UNDERSTAND AND AGREE THAT YOUR USE OF THE GOOGLE TV ADD-ON IS AT YOUR SOLE RISK AND THAT THE GOOGLE TV ADD-ON IS PROVIDED "AS IS" AND "AS AVAILABLE" WITHOUT WARRANTY OF ANY KIND FROM GOOGLE. + +10.2 YOUR USE OF THE GOOGLE TV ADD-ON AND ANY MATERIAL DOWNLOADED OR OTHERWISE OBTAINED THROUGH THE USE OF THE GOOGLE TV ADD-ON IS AT YOUR OWN DISCRETION AND RISK AND YOU ARE SOLELY RESPONSIBLE FOR ANY DAMAGE TO YOUR COMPUTER SYSTEM OR OTHER DEVICE OR LOSS OF DATA THAT RESULTS FROM SUCH USE. + +10.3 GOOGLE FURTHER EXPRESSLY DISCLAIMS ALL WARRANTIES AND CONDITIONS OF ANY KIND, WHETHER EXPRESS OR IMPLIED, INCLUDING, BUT NOT LIMITED TO THE IMPLIED WARRANTIES AND CONDITIONS OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. + +11. LIMITATION OF LIABILITY + +11.1 YOU EXPRESSLY UNDERSTAND AND AGREE THAT GOOGLE, ITS SUBSIDIARIES AND AFFILIATES, AND ITS LICENSORS SHALL NOT BE LIABLE TO YOU UNDER ANY THEORY OF LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL CONSEQUENTIAL OR EXEMPLARY DAMAGES THAT MAY BE INCURRED BY YOU, INCLUDING ANY LOSS OF DATA, WHETHER OR NOT GOOGLE OR ITS REPRESENTATIVES HAVE BEEN ADVISED OF OR SHOULD HAVE BEEN AWARE OF THE POSSIBILITY OF ANY SUCH LOSSES ARISING. + +12. Indemnification + +12.1 To the maximum extent permitted by law, you agree to defend, indemnify and hold harmless Google, its affiliates and their respective directors, officers, employees and agents from and against any and all claims, actions, suits or proceedings, as well as any and all losses, liabilities, damages, costs and expenses (including reasonable attorneys fees) arising out of or accruing from (a) your use of the Google TV Add-on, (b) any application you develop on the Google TV Add-on that infringes any copyright, trademark, trade secret, trade dress, patent or other intellectual property right of any person or defames any person or violates their rights of publicity or privacy, and (c) any non-compliance by you with this License Agreement. + +13. Changes to the License Agreement + +13.1 Google may make changes to the License Agreement as it distributes new versions of the Google TV Add-on. + +14. General Legal Terms + +14.1 This License Agreement constitute the whole legal agreement between you and Google and govern your use of the Google TV Add-on (excluding any services which Google may provide to you under a separate written agreement), and completely replace any prior agreements between you and Google in relation to the Google TV Add-on. + +14.2 You agree that if Google does not exercise or enforce any legal right or remedy which is contained in this License Agreement (or which Google has the benefit of under any applicable law), this will not be taken to be a formal waiver of Google's rights and that those rights or remedies will still be available to Google. + +14.3 If any court of law, having the jurisdiction to decide on this matter, rules that any provision of this License Agreement is invalid, then that provision will be removed from this License Agreement without affecting the rest of this License Agreement. The remaining provisions of this License Agreement will continue to be valid and enforceable. + +14.4 You acknowledge and agree that Google’s API data licensors and each member of the group of companies of which Google is the parent shall be third party beneficiaries to this License Agreement and that such other companies shall be entitled to directly enforce, and rely upon, any provision of this License Agreement that confers a benefit on (or rights in favor of) them. Other than this, no other person or company shall be third party beneficiaries to this License Agreement. + +14.5 EXPORT RESTRICTIONS. THE GOOGLE TV ADD-ON IS SUBJECT TO UNITED STATES EXPORT LAWS AND REGULATIONS. YOU MUST COMPLY WITH ALL DOMESTIC AND INTERNATIONAL EXPORT LAWS AND REGULATIONS THAT APPLY TO THE GOOGLE TV ADD-ON. THESE LAWS INCLUDE RESTRICTIONS ON DESTINATIONS, END USERS AND END USE. + +14.6 The rights granted in this License Agreement may not be assigned or transferred by either you or Google without the prior written approval of the other party. Neither you nor Google shall be permitted to delegate their responsibilities or obligations under this License Agreement without the prior written approval of the other party. + +14.7 This License Agreement, and your relationship with Google under this License Agreement, shall be governed by the laws of the State of California without regard to its conflict of laws provisions. You and Google agree to submit to the exclusive jurisdiction of the courts located within the county of Santa Clara, California to resolve any legal matter arising from this License Agreement. Notwithstanding this, you agree that Google shall still be allowed to apply for injunctive remedies (or an equivalent type of urgent legal relief) in any jurisdiction. + + +August 15, 2011 + + To get started with the Android XR Emulator System Image SDK, you must agree to the following terms and conditions. As described below, please note that this is a preview, emulated version of the Android XR OS, subject to change, that you use at your own risk. + +This is the Android XR Emulator System Image SDK License Agreement + +1. Introduction + +1.1 The Android Android XR Emulator System Image SDK (referred to in the License Agreement as the "SDK" and specifically including the Android system files,, packaged APIs, library files (if and when they are made available), and Google applications and APIs add-ons) is licensed to you subject to the terms of the License Agreement. The License Agreement forms a legally binding contract between you and Google in relation to your use of the SDK. + +1.2 "Android" means the Android software stack for devices, as made available under the Android Open Source Project, which is located at the following URL: https://source.android.com/, as updated from time to time. + +1.3 "Google" means Google LLC, organized under the laws of the State of Delaware, USA, and operating under the laws of the USA with principal place of business at 1600 Amphitheatre Parkway, Mountain View, CA 94043, USA. + +2. Accepting this License Agreement + +2.1 In order to use the SDK, you must first agree to the License Agreement. You may not use the SDK if you do not accept the License Agreement. + +2.2 By clicking to accept and/or using this SDK, you hereby agree to the terms of the License Agreement. + +2.3 You may not use the SDK and may not accept the License Agreement if you are a person barred from receiving the SDK under the laws of the United States or other countries, including the country in which you are resident or from which you use the SDK. + +2.4 If you are agreeing to be bound by the License Agreement on behalf of your employer or other entity, you represent and warrant that you have full legal authority to bind your employer or such entity to the License Agreement. If you do not have the requisite authority, you may not accept the License Agreement or use the SDK on behalf of your employer or other entity. + +3. SDK License from Google + +3.1 Subject to the terms of the License Agreement, Google grants you a limited, worldwide, royalty-free, non-assignable, non-exclusive, and non-sublicensable license to use the SDK solely to develop applications for Android XR. + +3.2 You may not use this SDK to develop applications for other platforms or to develop another SDK. You are of course free to develop applications for other platforms provided that this SDK is not used for that purpose. + +3.3 You agree that Google or third parties own all legal right, title and interest in and to the SDK, including any Intellectual Property Rights that subsist in the SDK. "Intellectual Property Rights" means any and all rights under patent law, copyright law, trade secret law, trademark law, and any and all other proprietary rights. Google reserves all rights not expressly granted to you. + +3.4 You may not use the SDK for any purpose not expressly permitted by the License Agreement. Except to the extent required by applicable third party licenses, you may not (a) copy (except for backup purposes), modify, adapt, redistribute, decompile, reverse engineer, disassemble, or create derivative works of the SDK or any part of the SDK; or (b) load any part of the SDK onto a mobile handset or any other hardware device except a personal computer, combine any part of the SDK with other software, or distribute any software or device incorporating a part of the SDK. + +3.5 Use, reproduction and distribution of components of the SDK licensed under an open source software license are governed solely by the terms of that open source software license and not the License Agreement. You agree to remain a licensee in good standing in regard to such open source software licenses under all the rights granted and to refrain from any actions that may terminate, suspend, or breach such rights. + +3.6 You agree that the form and nature of the SDK that Google provides may change without prior notice to you and that future versions of the SDK may be incompatible with applications developed on previous versions of the SDK. You agree that Google may stop (permanently or temporarily) providing the SDK (or any features within the SDK) to you or to users generally at Google's sole discretion, without prior notice to you. + +3.7 Nothing in the License Agreement gives you a right to use any of Google's trade names, trademarks, service marks, logos, domain names, or other distinctive brand features. + +3.8 You agree that you will not remove, obscure, or alter any proprietary rights notices (including copyright and trademark notices) that may be affixed to or contained within the SDK. + +4. Use of the SDK by You + +4.1 Google agrees that it obtains no right, title or interest from you (or your licensors) under the License Agreement in or to any software applications that you develop using the SDK, including any intellectual property rights that subsist in those applications. + +4.2 You agree to use the SDK and write applications only for purposes that are permitted by (a) the License Agreement and (b) any applicable law, regulation or generally accepted practices or guidelines in the relevant jurisdictions (including any laws regarding the export of data or software to and from the United States or other relevant countries). + +4.3 You agree that if you use the SDK to develop applications for general public users, you will protect the privacy and legal rights of those users. If the users provide you with user names, passwords, or other login information or personal information, you must make the users aware that the information will be available to your application, and you must provide legally adequate privacy notice and protection for those users. If your application stores personal or sensitive information provided by users, it must do so securely. If the user provides your application with Google Account information, your application may only use that information to access the user's Google Account when, and for the limited purposes for which, the user has given you permission to do so. + +4.4 You agree that you will not engage in any activity with the SDK, including the development or distribution of an application, that interferes with, disrupts, damages, or accesses in an unauthorized manner the servers, networks, or other properties or services of any third party including, but not limited to, Google or any mobile communications carrier. + +4.5 You agree that you are solely responsible for (and that Google has no responsibility to you or to any third party for) any data, content, or resources that you create, transmit or display through Android and/or applications for Android, and for the consequences of your actions (including any loss or damage which Google may suffer) by doing so. + +4.6 You agree that you are solely responsible for (and that Google has no responsibility to you or to any third party for) any breach of your obligations under the License Agreement, any applicable third party contract or Terms of Service, or any applicable law or regulation, and for the consequences (including any loss or damage which Google or any third party may suffer) of any such breach. + +4.7 The SDK is in development, and your testing and feedback are an important part of the development process. By using the SDK, you acknowledge that implementation of some features are still under development and that you should not rely on the SDK having the full functionality of a stable release. + +5. Your Developer Credentials + +5.1 You agree that you are responsible for maintaining the confidentiality of any developer credentials that may be issued to you by Google or which you may choose yourself and that you will be solely responsible for all applications that are developed under your developer credentials. + +6. Privacy and Information + +6.1 In order to continually innovate and improve the SDK, Google may collect certain usage statistics from the software including but not limited to a unique identifier, associated IP address, version number of the software, and information on which tools and/or services in the SDK are being used and how they are being used. Before any of this information is collected, the SDK will notify you and seek your consent. If you withhold consent, the information will not be collected. + +6.2 The data collected is examined in the aggregate to improve the SDK and is maintained in accordance with Google's Privacy Policy, which is located at the following URL: https://policies.google.com/privacy + +6.3 Anonymized and aggregated sets of the data may be shared with Google partners to improve the SDK. + +7. Third Party Applications + +7.1 If you use the SDK to run applications developed by a third party or that access data, content or resources provided by a third party, you agree that Google is not responsible for those applications, data, content, or resources. You understand that all data, content or resources which you may access through such third party applications are the sole responsibility of the person from which they originated and that Google is not liable for any loss or damage that you may experience as a result of the use or access of any of those third party applications, data, content, or resources. + +7.2 You should be aware that the data, content, and resources presented to you through such a third party application may be protected by intellectual property rights which are owned by the providers (or by other persons or companies on their behalf). You may not modify, rent, lease, loan, sell, distribute or create derivative works based on these data, content, or resources (either in whole or in part) unless you have been specifically given permission to do so by the relevant owners. + +7.3 You acknowledge that your use of such third party applications, data, content, or resources may be subject to separate terms between you and the relevant third party. In that case, the License Agreement does not affect your legal relationship with these third parties. + +8. Using Android APIs + +8.1 Google Data APIs + +8.1.1 If you use any API to retrieve data from Google, you acknowledge that the data may be protected by intellectual property rights which are owned by Google or those parties that provide the data (or by other persons or companies on their behalf). Your use of any such API may be subject to additional Terms of Service. You may not modify, rent, lease, loan, sell, distribute or create derivative works based on this data (either in whole or in part) unless allowed by the relevant Terms of Service. + +8.1.2 If you use any API to retrieve a user's data from Google, you acknowledge and agree that you shall retrieve data only with the user's explicit consent and only when, and for the limited purposes for which, the user has given you permission to do so. If you use the Android Recognition Service API, documented at the following URL: https://developer.android.com/reference/android/speech/RecognitionService, as updated from time to time, you acknowledge that the use of the API is subject to the Data Processing Addendum for Products where Google is a Data Processor, which is located at the following URL: https://privacy.google.com/businesses/gdprprocessorterms/, as updated from time to time. By clicking to accept, you hereby agree to the terms of the Data Processing Addendum for Products where Google is a Data Processor. + +9. Terminating this License Agreement + +9.1 The License Agreement will continue to apply until terminated by either you or Google as set out below. + +9.2 If you want to terminate the License Agreement, you may do so by ceasing your use of the SDK and any relevant developer credentials. + +9.3 Google may at any time, terminate the License Agreement with you if: +(A) you have breached any provision of the License Agreement; or +(B) Google is required to do so by law; or +(C) the partner with whom Google offered certain parts of SDK (such as APIs) to you has terminated its relationship with Google or ceased to offer certain parts of the SDK to you; or +(D) Google decides to no longer provide the SDK or certain parts of the SDK to users in the country in which you are resident or from which you use the service, or the provision of the SDK or certain SDK services to you by Google is, in Google's sole discretion, no longer commercially viable. + +9.4 When the License Agreement comes to an end, all of the legal rights, obligations and liabilities that you and Google have benefited from, been subject to (or which have accrued over time whilst the License Agreement has been in force) or which are expressed to continue indefinitely, shall be unaffected by this cessation, and the provisions of paragraph 14.7 shall continue to apply to such rights, obligations and liabilities indefinitely. + +10. DISCLAIMER OF WARRANTIES + +10.1 YOU EXPRESSLY UNDERSTAND AND AGREE THAT YOUR USE OF THE SDK IS AT YOUR SOLE RISK AND THAT THE SDK IS PROVIDED "AS IS" AND "AS AVAILABLE" WITHOUT WARRANTY OF ANY KIND FROM GOOGLE. + +10.2 YOUR USE OF THE SDK AND ANY MATERIAL DOWNLOADED OR OTHERWISE OBTAINED THROUGH THE USE OF THE SDK IS AT YOUR OWN DISCRETION AND RISK AND YOU ARE SOLELY RESPONSIBLE FOR ANY DAMAGE TO YOUR COMPUTER SYSTEM OR OTHER DEVICE OR LOSS OF DATA THAT RESULTS FROM SUCH USE. + +10.3 GOOGLE FURTHER EXPRESSLY DISCLAIMS ALL WARRANTIES AND CONDITIONS OF ANY KIND, WHETHER EXPRESS OR IMPLIED, INCLUDING, BUT NOT LIMITED TO THE IMPLIED WARRANTIES AND CONDITIONS OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. + +11. LIMITATION OF LIABILITY + +11.1 YOU EXPRESSLY UNDERSTAND AND AGREE THAT GOOGLE, ITS SUBSIDIARIES AND AFFILIATES, AND ITS LICENSORS SHALL NOT BE LIABLE TO YOU UNDER ANY THEORY OF LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, CONSEQUENTIAL OR EXEMPLARY DAMAGES THAT MAY BE INCURRED BY YOU, INCLUDING ANY LOSS OF DATA, WHETHER OR NOT GOOGLE OR ITS REPRESENTATIVES HAVE BEEN ADVISED OF OR SHOULD HAVE BEEN AWARE OF THE POSSIBILITY OF ANY SUCH LOSSES ARISING. + +12. Indemnification + +12.1 To the maximum extent permitted by law, you agree to defend, indemnify and hold harmless Google, its affiliates and their respective directors, officers, employees and agents from and against any and all claims, actions, suits or proceedings, as well as any and all losses, liabilities, damages, costs and expenses (including reasonable attorneys fees) arising out of or accruing from (a) your use of the SDK, (b) any application you develop on the SDK that infringes any copyright, trademark, trade secret, trade dress, patent or other intellectual property right of any person or defames any person or violates their rights of publicity or privacy, and (c) any non-compliance by you with the License Agreement. + +13. Changes to the License Agreement + +13.1 Google may make changes to the License Agreement as it distributes new versions of the SDK. When these changes are made, Google will make a new version of the License Agreement available on the website where the SDK is made available. + +14. General Legal Terms + +14.1 The License Agreement constitutes the whole legal agreement between you and Google and governs your use of the SDK (excluding any services which Google may provide to you under a separate written agreement), and completely replaces any prior agreements between you and Google in relation to the SDK. + +14.2 You agree that if Google does not exercise or enforce any legal right or remedy which is contained in the License Agreement (or which Google has the benefit of under any applicable law), this will not be taken to be a formal waiver of Google's rights and that those rights or remedies will still be available to Google. + +14.3 If any court of law, having the jurisdiction to decide on this matter, rules that any provision of the License Agreement is invalid, then that provision will be removed from the License Agreement without affecting the rest of the License Agreement. The remaining provisions of the License Agreement will continue to be valid and enforceable. + +14.4 You acknowledge and agree that each member of the group of companies of which Google is the parent shall be third party beneficiaries to the License Agreement and that such other companies shall be entitled to directly enforce, and rely upon, any provision of the License Agreement that confers a benefit on (or rights in favor of) them. Other than this, no other person or company shall be third party beneficiaries to the License Agreement. + +14.5 EXPORT RESTRICTIONS. THE SDK IS SUBJECT TO UNITED STATES EXPORT LAWS AND REGULATIONS. YOU MUST COMPLY WITH ALL DOMESTIC AND INTERNATIONAL EXPORT LAWS AND REGULATIONS THAT APPLY TO THE SDK. THESE LAWS INCLUDE RESTRICTIONS ON DESTINATIONS, END USERS AND END USE. + +14.6 The rights granted in the License Agreement may not be assigned or transferred by either you or Google without the prior written approval of the other party. Neither you nor Google shall be permitted to delegate their responsibilities or obligations under the License Agreement without the prior written approval of the other party. + +14.7 The License Agreement, and your relationship with Google under the License Agreement, shall be governed by the laws of the State of California without regard to its conflict of laws provisions. You and Google agree to submit to the exclusive jurisdiction of the courts located within the county of Santa Clara, California to resolve any legal matter arising from the License Agreement. Notwithstanding this, you agree that Google shall still be allowed to apply for injunctive remedies (or an equivalent type of urgent legal relief) in any jurisdiction. + + Terms and Conditions + +This is the Android Software Development Kit License Agreement + +1. Introduction + +1.1 The Android Software Development Kit (referred to in the License Agreement as the "SDK" and specifically including the Android system files, packaged APIs, and Google APIs add-ons) is licensed to you subject to the terms of the License Agreement. The License Agreement forms a legally binding contract between you and Google in relation to your use of the SDK. + +1.2 "Android" means the Android software stack for devices, as made available under the Android Open Source Project, which is located at the following URL: http://source.android.com/, as updated from time to time. + +1.3 A "compatible implementation" means any Android device that (i) complies with the Android Compatibility Definition document, which can be found at the Android compatibility website (http://source.android.com/compatibility) and which may be updated from time to time; and (ii) successfully passes the Android Compatibility Test Suite (CTS). + +1.4 "Google" means Google Inc., a Delaware corporation with principal place of business at 1600 Amphitheatre Parkway, Mountain View, CA 94043, United States. + + +2. Accepting the License Agreement + +2.1 In order to use the SDK, you must first agree to the License Agreement. You may not use the SDK if you do not accept the License Agreement. + +2.2 By clicking to accept, you hereby agree to the terms of the License Agreement. + +2.3 You may not use the SDK and may not accept the License Agreement if you are a person barred from receiving the SDK under the laws of the United States or other countries, including the country in which you are resident or from which you use the SDK. + +2.4 If you are agreeing to be bound by the License Agreement on behalf of your employer or other entity, you represent and warrant that you have full legal authority to bind your employer or such entity to the License Agreement. If you do not have the requisite authority, you may not accept the License Agreement or use the SDK on behalf of your employer or other entity. + + +3. SDK License from Google + +3.1 Subject to the terms of the License Agreement, Google grants you a limited, worldwide, royalty-free, non-assignable, non-exclusive, and non-sublicensable license to use the SDK solely to develop applications for compatible implementations of Android. + +3.2 You may not use this SDK to develop applications for other platforms (including non-compatible implementations of Android) or to develop another SDK. You are of course free to develop applications for other platforms, including non-compatible implementations of Android, provided that this SDK is not used for that purpose. + +3.3 You agree that Google or third parties own all legal right, title and interest in and to the SDK, including any Intellectual Property Rights that subsist in the SDK. "Intellectual Property Rights" means any and all rights under patent law, copyright law, trade secret law, trademark law, and any and all other proprietary rights. Google reserves all rights not expressly granted to you. + +3.4 You may not use the SDK for any purpose not expressly permitted by the License Agreement. Except to the extent required by applicable third party licenses, you may not copy (except for backup purposes), modify, adapt, redistribute, decompile, reverse engineer, disassemble, or create derivative works of the SDK or any part of the SDK. + +3.5 Use, reproduction and distribution of components of the SDK licensed under an open source software license are governed solely by the terms of that open source software license and not the License Agreement. + +3.6 You agree that the form and nature of the SDK that Google provides may change without prior notice to you and that future versions of the SDK may be incompatible with applications developed on previous versions of the SDK. You agree that Google may stop (permanently or temporarily) providing the SDK (or any features within the SDK) to you or to users generally at Google's sole discretion, without prior notice to you. + +3.7 Nothing in the License Agreement gives you a right to use any of Google's trade names, trademarks, service marks, logos, domain names, or other distinctive brand features. + +3.8 You agree that you will not remove, obscure, or alter any proprietary rights notices (including copyright and trademark notices) that may be affixed to or contained within the SDK. + + +4. Use of the SDK by You + +4.1 Google agrees that it obtains no right, title or interest from you (or your licensors) under the License Agreement in or to any software applications that you develop using the SDK, including any intellectual property rights that subsist in those applications. + +4.2 You agree to use the SDK and write applications only for purposes that are permitted by (a) the License Agreement and (b) any applicable law, regulation or generally accepted practices or guidelines in the relevant jurisdictions (including any laws regarding the export of data or software to and from the United States or other relevant countries). + +4.3 You agree that if you use the SDK to develop applications for general public users, you will protect the privacy and legal rights of those users. If the users provide you with user names, passwords, or other login information or personal information, you must make the users aware that the information will be available to your application, and you must provide legally adequate privacy notice and protection for those users. If your application stores personal or sensitive information provided by users, it must do so securely. If the user provides your application with Google Account information, your application may only use that information to access the user's Google Account when, and for the limited purposes for which, the user has given you permission to do so. + +4.4 You agree that you will not engage in any activity with the SDK, including the development or distribution of an application, that interferes with, disrupts, damages, or accesses in an unauthorized manner the servers, networks, or other properties or services of any third party including, but not limited to, Google or any mobile communications carrier. + +4.5 You agree that you are solely responsible for (and that Google has no responsibility to you or to any third party for) any data, content, or resources that you create, transmit or display through Android and/or applications for Android, and for the consequences of your actions (including any loss or damage which Google may suffer) by doing so. + +4.6 You agree that you are solely responsible for (and that Google has no responsibility to you or to any third party for) any breach of your obligations under the License Agreement, any applicable third party contract or Terms of Service, or any applicable law or regulation, and for the consequences (including any loss or damage which Google or any third party may suffer) of any such breach. + +4.7 This software enables the execution of intellectual property owned by Arm Limited. You agree that your use of the software, that allows execution of ARM Instruction Set Architecture (“ISA”) compliant executables for application development and debug only on x86 desktop, laptop, customer on-premise servers, and customer-procured cloud-based environments. + +5. Your Developer Credentials + +5.1 You agree that you are responsible for maintaining the confidentiality of any developer credentials that may be issued to you by Google or which you may choose yourself and that you will be solely responsible for all applications that are developed under your developer credentials. + +6. Privacy and Information + +6.1 In order to continually innovate and improve the SDK, Google may collect certain usage statistics from the software including but not limited to a unique identifier, associated IP address, version number of the software, and information on which tools and/or services in the SDK are being used and how they are being used. Before any of this information is collected, the SDK will notify you and seek your consent. If you withhold consent, the information will not be collected. + +6.2 The data collected is examined in the aggregate to improve the SDK and is maintained in accordance with Google's Privacy Policy. + + +7. Third Party Applications + +7.1 If you use the SDK to run applications developed by a third party or that access data, content or resources provided by a third party, you agree that Google is not responsible for those applications, data, content, or resources. You understand that all data, content or resources which you may access through such third party applications are the sole responsibility of the person from which they originated and that Google is not liable for any loss or damage that you may experience as a result of the use or access of any of those third party applications, data, content, or resources. + +7.2 You should be aware the data, content, and resources presented to you through such a third party application may be protected by intellectual property rights which are owned by the providers (or by other persons or companies on their behalf). You may not modify, rent, lease, loan, sell, distribute or create derivative works based on these data, content, or resources (either in whole or in part) unless you have been specifically given permission to do so by the relevant owners. + +7.3 You acknowledge that your use of such third party applications, data, content, or resources may be subject to separate terms between you and the relevant third party. In that case, the License Agreement does not affect your legal relationship with these third parties. + + +8. Using Android APIs + +8.1 Google Data APIs + +8.1.1 If you use any API to retrieve data from Google, you acknowledge that the data may be protected by intellectual property rights which are owned by Google or those parties that provide the data (or by other persons or companies on their behalf). Your use of any such API may be subject to additional Terms of Service. You may not modify, rent, lease, loan, sell, distribute or create derivative works based on this data (either in whole or in part) unless allowed by the relevant Terms of Service. + +8.1.2 If you use any API to retrieve a user's data from Google, you acknowledge and agree that you shall retrieve data only with the user's explicit consent and only when, and for the limited purposes for which, the user has given you permission to do so. If you use the Android Recognition Service API, documented at the following URL: https://developer.android.com/reference/android/speech/RecognitionService, as updated from time to time, you acknowledge that the use of the API is subject to the Data Processing Addendum for Products where Google is a Data Processor, which is located at the following URL: https://privacy.google.com/businesses/gdprprocessorterms/, as updated from time to time. By clicking to accept, you hereby agree to the terms of the Data Processing Addendum for Products where Google is a Data Processor. + + +9. Terminating the License Agreement + +9.1 The License Agreement will continue to apply until terminated by either you or Google as set out below. + +9.2 If you want to terminate the License Agreement, you may do so by ceasing your use of the SDK and any relevant developer credentials. + +9.3 Google may at any time, terminate the License Agreement with you if: +(A) you have breached any provision of the License Agreement; or +(B) Google is required to do so by law; or +(C) the partner with whom Google offered certain parts of SDK (such as APIs) to you has terminated its relationship with Google or ceased to offer certain parts of the SDK to you; or +(D) Google decides to no longer provide the SDK or certain parts of the SDK to users in the country in which you are resident or from which you use the service, or the provision of the SDK or certain SDK services to you by Google is, in Google's sole discretion, no longer commercially viable. + +9.4 When the License Agreement comes to an end, all of the legal rights, obligations and liabilities that you and Google have benefited from, been subject to (or which have accrued over time whilst the License Agreement has been in force) or which are expressed to continue indefinitely, shall be unaffected by this cessation, and the provisions of paragraph 14.7 shall continue to apply to such rights, obligations and liabilities indefinitely. + + +10. DISCLAIMER OF WARRANTIES + +10.1 YOU EXPRESSLY UNDERSTAND AND AGREE THAT YOUR USE OF THE SDK IS AT YOUR SOLE RISK AND THAT THE SDK IS PROVIDED "AS IS" AND "AS AVAILABLE" WITHOUT WARRANTY OF ANY KIND FROM GOOGLE. + +10.2 YOUR USE OF THE SDK AND ANY MATERIAL DOWNLOADED OR OTHERWISE OBTAINED THROUGH THE USE OF THE SDK IS AT YOUR OWN DISCRETION AND RISK AND YOU ARE SOLELY RESPONSIBLE FOR ANY DAMAGE TO YOUR COMPUTER SYSTEM OR OTHER DEVICE OR LOSS OF DATA THAT RESULTS FROM SUCH USE. + +10.3 GOOGLE FURTHER EXPRESSLY DISCLAIMS ALL WARRANTIES AND CONDITIONS OF ANY KIND, WHETHER EXPRESS OR IMPLIED, INCLUDING, BUT NOT LIMITED TO THE IMPLIED WARRANTIES AND CONDITIONS OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. + + +11. LIMITATION OF LIABILITY + +11.1 YOU EXPRESSLY UNDERSTAND AND AGREE THAT GOOGLE, ITS SUBSIDIARIES AND AFFILIATES, AND ITS LICENSORS SHALL NOT BE LIABLE TO YOU UNDER ANY THEORY OF LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, CONSEQUENTIAL OR EXEMPLARY DAMAGES THAT MAY BE INCURRED BY YOU, INCLUDING ANY LOSS OF DATA, WHETHER OR NOT GOOGLE OR ITS REPRESENTATIVES HAVE BEEN ADVISED OF OR SHOULD HAVE BEEN AWARE OF THE POSSIBILITY OF ANY SUCH LOSSES ARISING. + + +12. Indemnification + +12.1 To the maximum extent permitted by law, you agree to defend, indemnify and hold harmless Google, its affiliates and their respective directors, officers, employees and agents from and against any and all claims, actions, suits or proceedings, as well as any and all losses, liabilities, damages, costs and expenses (including reasonable attorneys fees) arising out of or accruing from (a) your use of the SDK, (b) any application you develop on the SDK that infringes any copyright, trademark, trade secret, trade dress, patent or other intellectual property right of any person or defames any person or violates their rights of publicity or privacy, and (c) any non-compliance by you with the License Agreement. + + +13. Changes to the License Agreement + +13.1 Google may make changes to the License Agreement as it distributes new versions of the SDK. When these changes are made, Google will make a new version of the License Agreement available on the website where the SDK is made available. + + +14. General Legal Terms + +14.1 The License Agreement constitutes the whole legal agreement between you and Google and governs your use of the SDK (excluding any services which Google may provide to you under a separate written agreement), and completely replaces any prior agreements between you and Google in relation to the SDK. + +14.2 You agree that if Google does not exercise or enforce any legal right or remedy which is contained in the License Agreement (or which Google has the benefit of under any applicable law), this will not be taken to be a formal waiver of Google's rights and that those rights or remedies will still be available to Google. + +14.3 If any court of law, having the jurisdiction to decide on this matter, rules that any provision of the License Agreement is invalid, then that provision will be removed from the License Agreement without affecting the rest of the License Agreement. The remaining provisions of the License Agreement will continue to be valid and enforceable. + +14.4 You acknowledge and agree that each member of the group of companies of which Google is the parent shall be third party beneficiaries to the License Agreement and that such other companies shall be entitled to directly enforce, and rely upon, any provision of the License Agreement that confers a benefit on (or rights in favor of) them. Other than this, no other person or company shall be third party beneficiaries to the License Agreement. + +14.5 EXPORT RESTRICTIONS. THE SDK IS SUBJECT TO UNITED STATES EXPORT LAWS AND REGULATIONS. YOU MUST COMPLY WITH ALL DOMESTIC AND INTERNATIONAL EXPORT LAWS AND REGULATIONS THAT APPLY TO THE SDK. THESE LAWS INCLUDE RESTRICTIONS ON DESTINATIONS, END USERS AND END USE. + +14.6 The rights granted in the License Agreement may not be assigned or transferred by either you or Google without the prior written approval of the other party. Neither you nor Google shall be permitted to delegate their responsibilities or obligations under the License Agreement without the prior written approval of the other party. + +14.7 The License Agreement, and your relationship with Google under the License Agreement, shall be governed by the laws of the State of California without regard to its conflict of laws provisions. You and Google agree to submit to the exclusive jurisdiction of the courts located within the county of Santa Clara, California to resolve any legal matter arising from the License Agreement. Notwithstanding this, you agree that Google shall still be allowed to apply for injunctive remedies (or an equivalent type of urgent legal relief) in any jurisdiction. + + +January 16, 2019 + + This is a Developer Preview of the GDK that is subject to change. + +Terms and Conditions + +This is the Glass Development Kit License Agreement. + +1. Introduction + +1.1 The Glass Development Kit (referred to in this License Agreement as the "GDK" and specifically including the Android system files, packaged APIs, and GDK library files, if and when they are made available) is licensed to you subject to the terms of this License Agreement. This License Agreement forms a legally binding contract between you and Google in relation to your use of the GDK. + +1.2 "Glass" means Glass devices and the Glass software stack for use on Glass devices. + + +1.3 "Android" means the Android software stack for devices, as made available under the Android Open Source Project, which is located at the following URL: http://source.android.com/, as updated from time to time. + +1.4 "Google" means Google Inc., a Delaware corporation with principal place of business at 1600 Amphitheatre Parkway, Mountain View, CA 94043, United States. + +2. Accepting this License Agreement + +2.1 In order to use the GDK, you must first agree to this License Agreement. You may not use the GDK if you do not accept this License Agreement. + +2.2 By clicking to accept, you hereby agree to the terms of this License Agreement. + +2.3 You may not use the GDK and may not accept the License Agreement if you are a person barred from receiving the GDK under the laws of the United States or other countries including the country in which you are resident or from which you use the GDK. + +2.4 If you are agreeing to be bound by this License Agreement on behalf of your employer or other entity, you represent and warrant that you have full legal authority to bind your employer or such entity to this License Agreement. If you do not have the requisite authority, you may not accept the License Agreement or use the GDK on behalf of your employer or other entity. + +3. GDK License from Google + +3.1 Subject to the terms of this License Agreement, Google grants you a limited, worldwide, royalty-free, non-assignable and non-exclusive license to use the GDK solely to develop applications to run on the Glass platform for Glass devices. + +3.2 You agree that Google or third parties own all legal right, title and interest in and to the GDK, including any Intellectual Property Rights that subsist in the GDK. "Intellectual Property Rights" means any and all rights under patent law, copyright law, trade secret law, trademark law, and any and all other proprietary rights. Google reserves all rights not expressly granted to you. + +3.3 You may not use the GDK for any purpose not expressly permitted by this License Agreement. Except to the extent required by applicable third party licenses, you may not: (a) copy (except for backup purposes), modify, adapt, redistribute, decompile, reverse engineer, disassemble, or create derivative works of the GDK or any part of the GDK; or (b) load any part of the GDK onto a mobile handset or wearable computing device or any other hardware device except a Glass device personal computer, combine any part of the GDK with other software, or distribute any software or device incorporating a part of the GDK. + +3.4 You agree that you will not take any actions that may cause or result in the fragmentation of Glass, including but not limited to distributing, participating in the creation of, or promoting in any way a software development kit derived from the GDK. + +3.5 Use, reproduction and distribution of components of the GDK licensed under an open source software license are governed solely by the terms of that open source software license and not this License Agreement. + +3.6 You agree that the form and nature of the GDK that Google provides may change without prior notice to you and that future versions of the GDK may be incompatible with applications developed on previous versions of the GDK. You agree that Google may stop (permanently or temporarily) providing the GDK (or any features within the GDK) to you or to users generally at Google's sole discretion, without prior notice to you. + +3.7 Nothing in this License Agreement gives you a right to use any of Google's trade names, trademarks, service marks, logos, domain names, or other distinctive brand features. + +3.8 You agree that you will not remove, obscure, or alter any proprietary rights notices (including copyright and trademark notices) that may be affixed to or contained within the GDK. + + +3.9 Your use of any Android system files, packaged APIs, or other components of the GDK which are part of the Android Software Development Kit is subject to the terms of the Android Software Development Kit License Agreement located at http://developer.android.com/sdk/terms.html. These terms are hereby incorporated by reference into this License Agreement. + +4. Use of the GDK by You + +4.1 Google agrees that it obtains no right, title or interest from you (or your licensors) under this License Agreement in or to any software applications that you develop using the GDK, including any intellectual property rights that subsist in those applications. + +4.2 You agree to use the GDK and write applications only for purposes that are permitted by (a) this License Agreement, (b) the Glass Platform Developer Policies (located at https://developers.google.com/glass/policies, and hereby incorporated into this License Agreement by reference), and (c) any applicable law, regulation or generally accepted practices or guidelines in the relevant jurisdictions (including any laws regarding the export of data or software to and from the United States or other relevant countries). + +4.3 You agree that if you use the GDK to develop applications for general public users, you will protect the privacy and legal rights of those users. If the users provide you with user names, passwords, or other login information or personal information, you must make the users aware that the information will be available to your application, and you must provide legally adequate privacy notice and protection for those users. If your application stores personal or sensitive information provided by users, it must do so securely. If the user provides your application with Google Account information, your application may only use that information to access the user's Google Account when, and for the limited purposes for which, the user has given you permission to do so. + +4.4 You agree that you will not engage in any activity with the GDK, including the development or distribution of an application, that interferes with, disrupts, damages, or accesses in an unauthorized manner the servers, networks, or other properties or services of any third party including, but not limited to, Google. + +4.5 You agree that you are solely responsible for (and that Google has no responsibility to you or to any third party for) any data, content, or resources that you create, transmit or display through Glass and/or applications for Glass, and for the consequences of your actions (including any loss or damage which Google may suffer) by doing so. + +4.6 You agree that you are solely responsible for (and that Google has no responsibility to you or to any third party for) any breach of your obligations under this License Agreement, any applicable third party contract or Terms of Service, or any applicable law or regulation, and for the consequences (including any loss or damage which Google or any third party may suffer) of any such breach. + + +4.7 The GDK is in development, and your testing and feedback are an important part of the development process. By using the GDK, you acknowledge that implementation of some features are still under development and that you should not rely on the GDK, Glass devices, Glass system software, Google Mirror API, or Glass services having the full functionality of a stable release. + +5. Your Developer Credentials + +5.1 You agree that you are responsible for maintaining the confidentiality of any developer credentials that may be issued to you by Google or which you may choose yourself and that you will be solely responsible for all applications that are developed under your developer credentials. + +6. Privacy and Information + + +6.1 In order to continually innovate and improve the GDK, Google may collect certain usage statistics from the software including but not limited to a unique identifier, associated IP address, version number of the software, and information on which tools and/or services in the GDK are being used and how they are being used. Before any of this information is collected, the GDK will notify you and seek your consent. If you withhold consent, the information will not be collected. + +6.2 The data collected is examined in the aggregate to improve the GDK and is maintained in accordance with Google's Privacy Policy. + +7. Third Party Applications + +7.1 If you use the GDK to run applications developed by a third party or that access data, content or resources provided by a third party, you agree that Google is not responsible for those applications, data, content, or resources. You understand that all data, content or resources which you may access through such third party applications are the sole responsibility of the person from which they originated and that Google is not liable for any loss or damage that you may experience as a result of the use or access of any of those third party applications, data, content, or resources. + +7.2 You should be aware the data, content, and resources presented to you through such a third party application may be protected by intellectual property rights which are owned by the providers (or by other persons or companies on their behalf). You may not modify, rent, lease, loan, sell, distribute or create derivative works based on these data, content, or resources (either in whole or in part) unless you have been specifically given permission to do so by the relevant owners. + +7.3 You acknowledge that your use of such third party applications, data, content, or resources may be subject to separate terms between you and the relevant third party. In that case, this License Agreement does not affect your legal relationship with these third parties. + +8. Using Google APIs + +8.1 Google APIs + +8.1.1 If you use any API to retrieve data from Google, you acknowledge that the data may be protected by intellectual property rights which are owned by Google or those parties that provide the data (or by other persons or companies on their behalf). Your use of any such API may be subject to additional Terms of Service. You may not modify, rent, lease, loan, sell, distribute or create derivative works based on this data (either in whole or in part) unless allowed by the relevant Terms of Service. + +8.1.2 If you use any API to retrieve a user's data from Google, you acknowledge and agree that you shall retrieve data only with the user's explicit consent and only when, and for the limited purposes for which, the user has given you permission to do so. + +9. Terminating this License Agreement + +9.1 This License Agreement will continue to apply until terminated by either you or Google as set out below. + +9.2 If you want to terminate this License Agreement, you may do so by ceasing your use of the GDK and any relevant developer credentials. + +9.3 Google may at any time, terminate this License Agreement with you if: +(A) you have breached any provision of this License Agreement; or +(B) Google is required to do so by law; or +(C) the partner with whom Google offered certain parts of GDK (such as APIs) to you has terminated its relationship with Google or ceased to offer certain parts of the GDK to you; or +(D) Google decides to no longer provide the GDK or certain parts of the GDK to users in the country in which you are resident or from which you use the service, or the provision of the GDK or certain GDK services to you by Google is, in Google's sole discretion, no longer commercially viable. + +9.4 When this License Agreement comes to an end, all of the legal rights, obligations and liabilities that you and Google have benefited from, been subject to (or which have accrued over time whilst this License Agreement has been in force) or which are expressed to continue indefinitely, shall be unaffected by this cessation, and the provisions of paragraph 14.7 shall continue to apply to such rights, obligations and liabilities indefinitely. + +10. DISCLAIMER OF WARRANTIES + +10.1 YOU EXPRESSLY UNDERSTAND AND AGREE THAT YOUR USE OF THE GDK IS AT YOUR SOLE RISK AND THAT THE GDK IS PROVIDED "AS IS" AND "AS AVAILABLE" WITHOUT WARRANTY OF ANY KIND FROM GOOGLE. + +10.2 YOUR USE OF THE GDK AND ANY MATERIAL DOWNLOADED OR OTHERWISE OBTAINED THROUGH THE USE OF THE GDK IS AT YOUR OWN DISCRETION AND RISK AND YOU ARE SOLELY RESPONSIBLE FOR ANY DAMAGE TO YOUR COMPUTER SYSTEM OR OTHER DEVICE OR LOSS OF DATA THAT RESULTS FROM SUCH USE. + +10.3 GOOGLE FURTHER EXPRESSLY DISCLAIMS ALL WARRANTIES AND CONDITIONS OF ANY KIND, WHETHER EXPRESS OR IMPLIED, INCLUDING, BUT NOT LIMITED TO THE IMPLIED WARRANTIES AND CONDITIONS OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. + +11. LIMITATION OF LIABILITY + +11.1 YOU EXPRESSLY UNDERSTAND AND AGREE THAT GOOGLE, ITS SUBSIDIARIES AND AFFILIATES, AND ITS LICENSORS SHALL NOT BE LIABLE TO YOU UNDER ANY THEORY OF LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, CONSEQUENTIAL OR EXEMPLARY DAMAGES THAT MAY BE INCURRED BY YOU, INCLUDING ANY LOSS OF DATA, WHETHER OR NOT GOOGLE OR ITS REPRESENTATIVES HAVE BEEN ADVISED OF OR SHOULD HAVE BEEN AWARE OF THE POSSIBILITY OF ANY SUCH LOSSES ARISING. + +12. Indemnification + +12.1 To the maximum extent permitted by law, you agree to defend, indemnify and hold harmless Google, its affiliates and their respective directors, officers, employees and agents from and against any and all claims, actions, suits or proceedings, as well as any and all losses, liabilities, damages, costs and expenses (including reasonable attorneys fees) arising out of or accruing from (a) your use of the GDK, (b) any application you develop on the GDK that infringes any copyright, trademark, trade secret, trade dress, patent or other intellectual property right of any person or defames any person or violates their rights of publicity or privacy, and (c) any non-compliance by you with this License Agreement. + +13. Changes to the License Agreement + +13.1 Google may make changes to the License Agreement as it distributes new versions of the GDK. When these changes are made, Google will make a new version of the License Agreement available on the website where the GDK is made available. + +14. General Legal Terms + +14.1 This License Agreement constitutes the whole legal agreement between you and Google and governs your use of the GDK (excluding any services which Google may provide to you under a separate written agreement), and completely replaces any prior agreements between you and Google in relation to the GDK. + +14.2 You agree that if Google does not exercise or enforce any legal right or remedy which is contained in this License Agreement (or which Google has the benefit of under any applicable law), this will not be taken to be a formal waiver of Google's rights and that those rights or remedies will still be available to Google. + +14.3 If any court of law, having the jurisdiction to decide on this matter, rules that any provision of this License Agreement is invalid, then that provision will be removed from this License Agreement without affecting the rest of this License Agreement. The remaining provisions of this License Agreement will continue to be valid and enforceable. + +14.4 You acknowledge and agree that each member of the group of companies of which Google is the parent shall be third party beneficiaries to this License Agreement and that such other companies shall be entitled to directly enforce, and rely upon, any provision of this License Agreement that confers a benefit on (or rights in favor of) them. Other than this, no other person or company shall be third party beneficiaries to this License Agreement. + +14.5 EXPORT RESTRICTIONS. THE GDK IS SUBJECT TO UNITED STATES EXPORT LAWS AND REGULATIONS. YOU MUST COMPLY WITH ALL DOMESTIC AND INTERNATIONAL EXPORT LAWS AND REGULATIONS THAT APPLY TO THE GDK. THESE LAWS INCLUDE RESTRICTIONS ON DESTINATIONS, END USERS AND END USE. + +14.6 The rights granted in this License Agreement may not be assigned or transferred by either you or Google without the prior written approval of the other party. Neither you nor Google shall be permitted to delegate their responsibilities or obligations under this License Agreement without the prior written approval of the other party. + +14.7 This License Agreement, and your relationship with Google under this License Agreement, shall be governed by the laws of the State of California without regard to its conflict of laws provisions. You and Google agree to submit to the exclusive jurisdiction of the courts located within the county of Santa Clara, California to resolve any legal matter arising from this License Agreement. Notwithstanding this, you agree that Google shall still be allowed to apply for injunctive remedies (or an equivalent type of urgent legal relief) in any jurisdiction. + +November 19, 2013 + + Intel (R) Hardware Accelerated Execution Manager +End-User License Agreement + +Copyright (c) 2012 Intel Corporation. +All rights reserved. + +Redistribution. Redistribution and use in binary form, without modification, are permitted provided that the following conditions are met: + +1.Redistributions must reproduce the above copyright notice and the following disclaimer in the documentation and/or other materials provided with the distribution. + +2.Neither the name of Intel Corporation nor the names of its suppliers may be used to endorse or promote products derived from this software without specific prior written permission. + +3.No reverse engineering, de-compilation, or disassembly of this software is permitted. Limited patent license. Intel Corporation grants a world-wide, royalty-free, non-exclusive license under patents it now or hereafter owns or controls to make, have made, use, import, offer to sell and sell ("Utilize") this software, but solely to the extent that any such patent is necessary to Utilize the software alone. The patent license shall not apply to any combinations which include this software. No hardware per se is licensed hereunder. + +DISCLAIMER. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + + \ No newline at end of file diff --git a/src/Xamarin.Installer.AndroidSDK/GlobalSuppressions.cs b/src/Xamarin.Installer.AndroidSDK/GlobalSuppressions.cs new file mode 100644 index 00000000000..86f6851b742 --- /dev/null +++ b/src/Xamarin.Installer.AndroidSDK/GlobalSuppressions.cs @@ -0,0 +1,8 @@ + +// This file is used by Code Analysis to maintain SuppressMessage +// attributes that are applied to this project. +// Project-level suppressions either have no target or are given +// a specific target and scoped to a namespace, type, member, etc. + +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage ("Style", "IDE0018:Inline variable declaration", Justification = "", Scope = "member", Target = "~M:Xamarin.Installer.AndroidSDK.LocalSDK.LocalSDKRepository.CreateId``1(Kajabity.Tools.Java.JavaProperties,System.String,System.String)~``0")] + diff --git a/src/Xamarin.Installer.AndroidSDK/Properties/AssemblyInfo.cs b/src/Xamarin.Installer.AndroidSDK/Properties/AssemblyInfo.cs new file mode 100644 index 00000000000..7222449168a --- /dev/null +++ b/src/Xamarin.Installer.AndroidSDK/Properties/AssemblyInfo.cs @@ -0,0 +1,7 @@ +using System.Reflection; +using System.Runtime.InteropServices; + +[assembly: AssemblyTitle("Xamarin Android SDK Installer")] +[assembly: AssemblyDescription("Xamarin Android SDK Installer")] +[assembly: AssemblyProduct("Xamarin Android SDK Manager")] +[assembly: DefaultDllImportSearchPaths(DllImportSearchPath.SafeDirectories)] diff --git a/src/Xamarin.Installer.AndroidSDK/Properties/Resources.Designer.cs b/src/Xamarin.Installer.AndroidSDK/Properties/Resources.Designer.cs new file mode 100644 index 00000000000..c6dfd39f781 --- /dev/null +++ b/src/Xamarin.Installer.AndroidSDK/Properties/Resources.Designer.cs @@ -0,0 +1,126 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Xamarin.Installer.AndroidSDK.Properties { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Xamarin.Installer.AndroidSDK.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to Accept Android licenses failed: Android SDK not found. + /// + internal static string AccepLicensesFailedAndroidSDKNotFound { + get { + return ResourceManager.GetString("AccepLicensesFailedAndroidSDKNotFound", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Accept Android licenses failed: cmdline-tools not found. + /// + internal static string AccepLicensesFailedCMDLineToolsNotFound { + get { + return ResourceManager.GetString("AccepLicensesFailedCMDLineToolsNotFound", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Accept Android licenses failed: Java SDK not found. + /// + internal static string AccepLicensesFailedJavaSDKNotFound { + get { + return ResourceManager.GetString("AccepLicensesFailedJavaSDKNotFound", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Component installation failed. + /// + internal static string ComponentInstallationFailed { + get { + return ResourceManager.GetString("ComponentInstallationFailed", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Failure to load component properties file. + /// + internal static string FailedToLoadSourceProperties { + get { + return ResourceManager.GetString("FailedToLoadSourceProperties", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to An error occurred trying to accept Android licenses. + /// + internal static string GoogleAccepLicensesFailed { + get { + return ResourceManager.GetString("GoogleAccepLicensesFailed", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to No suitable component archive found.. + /// + internal static string NoSuitableArchiveFound { + get { + return ResourceManager.GetString("NoSuitableArchiveFound", resourceCulture); + } + } + } +} diff --git a/src/Xamarin.Installer.AndroidSDK/Properties/Resources.cs.resx b/src/Xamarin.Installer.AndroidSDK/Properties/Resources.cs.resx new file mode 100644 index 00000000000..b01eed2459a --- /dev/null +++ b/src/Xamarin.Installer.AndroidSDK/Properties/Resources.cs.resx @@ -0,0 +1,141 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Instalace komponenty se nezdařila. + + + Nepovedlo se načíst soubor vlastností komponenty. + + + Nenašel se žádný vhodný archiv komponent. + + + Při pokusu o přijetí licencí pro Android došlo k chybě. + + + Nepovedlo se přijmout licence na Android: nenalezena sada Android SDK + + + Nepovedlo se přijmout licence na Android: nenalezena sada Java SDK + + + Nepovedlo se přijmout licence pro Android: nenalezeny nástroje příkazového řádku + + \ No newline at end of file diff --git a/src/Xamarin.Installer.AndroidSDK/Properties/Resources.de.resx b/src/Xamarin.Installer.AndroidSDK/Properties/Resources.de.resx new file mode 100644 index 00000000000..4c3daa050aa --- /dev/null +++ b/src/Xamarin.Installer.AndroidSDK/Properties/Resources.de.resx @@ -0,0 +1,141 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Fehler beim Installieren der Komponente. + + + Fehler beim Laden der Datei mit den Komponenteneigenschaften. + + + Es wurde kein geeignetes Komponentenarchiv gefunden. + + + Fehler beim Akzeptieren von Android-Lizenzen + + + Fehler beim Akzeptieren von Android-Lizenzen: Android SDK nicht gefunden + + + Fehler beim Akzeptieren von Android-Lizenzen: Java SDK nicht gefunden + + + Fehler beim Akzeptieren von Android-Lizenzen: cmdline-Tools nicht gefunden + + \ No newline at end of file diff --git a/src/Xamarin.Installer.AndroidSDK/Properties/Resources.es.resx b/src/Xamarin.Installer.AndroidSDK/Properties/Resources.es.resx new file mode 100644 index 00000000000..cbd83a51b89 --- /dev/null +++ b/src/Xamarin.Installer.AndroidSDK/Properties/Resources.es.resx @@ -0,0 +1,141 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Fallo en la instalación de componentes + + + Error al cargar el archivo de propiedades del componente + + + No se encontró ningún archivo de componentes adecuado. + + + Error al intentar aceptar licencias de Android + + + Error al aceptar licencias de Android: no se encontró el SDK de Android + + + Error al aceptar licencias de Android: no se encontró el SDK de Java + + + Error al aceptar licencias de Android: cmdline-tools no encontrado + + \ No newline at end of file diff --git a/src/Xamarin.Installer.AndroidSDK/Properties/Resources.fr.resx b/src/Xamarin.Installer.AndroidSDK/Properties/Resources.fr.resx new file mode 100644 index 00000000000..58ab878a959 --- /dev/null +++ b/src/Xamarin.Installer.AndroidSDK/Properties/Resources.fr.resx @@ -0,0 +1,141 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Échec de l'installation du composant. + + + Échec du chargement du fichier des propriétés du composant + + + Aucune archive de composant appropriée n’a été trouvée. + + + Désolé, une erreur s’est produite lors de la tentative d’acceptation des licences Android. + + + Échec de l’acceptation des licences Android : Android SDK introuvable + + + Échec de l’acceptation des licences Android : SDK Java introuvable + + + Échec de l’acceptation des licences Android : outils de ligne de commande introuvables + + \ No newline at end of file diff --git a/src/Xamarin.Installer.AndroidSDK/Properties/Resources.it.resx b/src/Xamarin.Installer.AndroidSDK/Properties/Resources.it.resx new file mode 100644 index 00000000000..455c9a46ba6 --- /dev/null +++ b/src/Xamarin.Installer.AndroidSDK/Properties/Resources.it.resx @@ -0,0 +1,141 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + L'installazione del componente non è riuscita + + + Si è verificato un errore durante il caricamento del file delle proprietà del componente + + + Non sono stati trovati archivi di componenti appropriati. + + + Si è verificato un errore durante il tentativo di accettare le licenze di Android + + + L'accettazione delle licenze Android non è riuscita: Android SDK non trovato + + + L'accettazione delle licenze Android non è riuscita: Java SDK non trovato + + + Accettazione delle licenze Android non riuscita: cmdline-tools non trovato + + \ No newline at end of file diff --git a/src/Xamarin.Installer.AndroidSDK/Properties/Resources.ja.resx b/src/Xamarin.Installer.AndroidSDK/Properties/Resources.ja.resx new file mode 100644 index 00000000000..15ff2df1c16 --- /dev/null +++ b/src/Xamarin.Installer.AndroidSDK/Properties/Resources.ja.resx @@ -0,0 +1,141 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + コンポーネントをインストールできませんでした。 + + + コンポーネント プロパティ ファイルの読み込みに失敗しました + + + 適切なコンポーネント アーカイブが見つかりませんでした。 + + + Android ライセンスを承諾しようとしてエラーが発生しました。 + + + Android ライセンスの承諾に失敗しました: Android SDK が見つかりません + + + Android ライセンスの承諾に失敗しました: Java SDK が見つかりません + + + Android ライセンスの承諾に失敗しました: cmdline-tools が見つかりません + + \ No newline at end of file diff --git a/src/Xamarin.Installer.AndroidSDK/Properties/Resources.ko.resx b/src/Xamarin.Installer.AndroidSDK/Properties/Resources.ko.resx new file mode 100644 index 00000000000..d6f2995c361 --- /dev/null +++ b/src/Xamarin.Installer.AndroidSDK/Properties/Resources.ko.resx @@ -0,0 +1,141 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 구성 요소 설치 실패 + + + 구성 요소 속성 파일 로드 실패 + + + 적절한 구성 요소 보관함을 찾을 수 없습니다. + + + Android 라이선스를 수락하는 동안 오류 발생 + + + Android 라이선스 수락 실패: Android SDK를 찾을 수 없음 + + + Android 라이선스 수락 실패: Java SDK를 찾을 수 없음 + + + Android 라이선스 수락 실패: cmdline-tools를 찾을 수 없음 + + \ No newline at end of file diff --git a/src/Xamarin.Installer.AndroidSDK/Properties/Resources.pl.resx b/src/Xamarin.Installer.AndroidSDK/Properties/Resources.pl.resx new file mode 100644 index 00000000000..89745f6389e --- /dev/null +++ b/src/Xamarin.Installer.AndroidSDK/Properties/Resources.pl.resx @@ -0,0 +1,141 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Instalacja składników nie powiodła się + + + Nie można załadować pliku właściwości składników + + + Nie znaleziono odpowiedniego archiwum składników. + + + Wystąpił błąd podczas próby zaakceptowania licencji dla systemu Android + + + Zaakceptuj licencje dla systemu Android nie powiodło się: nie znaleziono zestawu Android SDK dla języka Java + + + Zaakceptuj licencje dla systemu Android nie powiodło się: nie znaleziono zestawu SDK dla języka Java + + + Zaakceptuj licencje dla systemu Android nie powiodło się: nie znaleziono narzędzi cmdline + + \ No newline at end of file diff --git a/src/Xamarin.Installer.AndroidSDK/Properties/Resources.pt-BR.resx b/src/Xamarin.Installer.AndroidSDK/Properties/Resources.pt-BR.resx new file mode 100644 index 00000000000..ec57b754bc4 --- /dev/null +++ b/src/Xamarin.Installer.AndroidSDK/Properties/Resources.pt-BR.resx @@ -0,0 +1,141 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + A instalação do componente falhou + + + Falha ao carregar o arquivo de propriedades do componente + + + Nenhum arquivo de componente adequado encontrado. + + + Ocorreu um erro ao tentar aceitar licenças do Android + + + Falha ao aceitar licenças do Android: SDK do Android não encontrado + + + Falha ao aceitar licenças do Android: SDK do Java não encontrado + + + Falha ao aceitar licenças do Android: cmdline-tools não encontrado + + \ No newline at end of file diff --git a/src/Xamarin.Installer.AndroidSDK/Properties/Resources.resx b/src/Xamarin.Installer.AndroidSDK/Properties/Resources.resx new file mode 100644 index 00000000000..cbed730e388 --- /dev/null +++ b/src/Xamarin.Installer.AndroidSDK/Properties/Resources.resx @@ -0,0 +1,141 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Component installation failed + + + Failure to load component properties file + + + No suitable component archive found. + + + An error occurred trying to accept Android licenses + + + Accept Android licenses failed: Android SDK not found + + + Accept Android licenses failed: Java SDK not found + + + Accept Android licenses failed: cmdline-tools not found + + \ No newline at end of file diff --git a/src/Xamarin.Installer.AndroidSDK/Properties/Resources.ru.resx b/src/Xamarin.Installer.AndroidSDK/Properties/Resources.ru.resx new file mode 100644 index 00000000000..64e260b60a8 --- /dev/null +++ b/src/Xamarin.Installer.AndroidSDK/Properties/Resources.ru.resx @@ -0,0 +1,141 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Сбой установки компонента + + + Не удалось загрузить файл свойств компонента + + + Подходящий архив компонентов не найден. + + + Произошла ошибка при попытке принять лицензии Android + + + Не удалось принять лицензии Android: пакет SDK для Android не найден + + + Не удалось принять лицензии Android: пакет SDK для Java не найден + + + Не удалось принять лицензии Android: инструменты командной строки не найдены + + \ No newline at end of file diff --git a/src/Xamarin.Installer.AndroidSDK/Properties/Resources.tr.resx b/src/Xamarin.Installer.AndroidSDK/Properties/Resources.tr.resx new file mode 100644 index 00000000000..d8e1e647c10 --- /dev/null +++ b/src/Xamarin.Installer.AndroidSDK/Properties/Resources.tr.resx @@ -0,0 +1,141 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Bileşen yükleme işlemi başarısız oldu + + + Bileşen özellikleri dosyası yüklenemedi + + + Uygun bileşen arşivi bulunamadı. + + + Android lisansları kabul edilmeye çalışılırken hata oluştu + + + Android lisansları kabul edilemedi: Android SDK bulunamadı + + + Android lisansları kabul edilemedi: Java SDK bulunamadı + + + Android lisansları kabul edilemedi: cmdline-tools bulunamadı + + \ No newline at end of file diff --git a/src/Xamarin.Installer.AndroidSDK/Properties/Resources.zh-Hans.resx b/src/Xamarin.Installer.AndroidSDK/Properties/Resources.zh-Hans.resx new file mode 100644 index 00000000000..b8003c18edd --- /dev/null +++ b/src/Xamarin.Installer.AndroidSDK/Properties/Resources.zh-Hans.resx @@ -0,0 +1,141 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 组件安装失败 + + + 无法加载组件属性文件 + + + 找不到合适的组件存档。 + + + 尝试接受 Android 许可证时出错。 + + + 接受 Android 许可证失败: 找不到 Android SDK + + + 接受 Android 许可证失败: 找不到 Java SDK + + + 接受 Android 许可证失败: 找不到 cmdline-tools + + \ No newline at end of file diff --git a/src/Xamarin.Installer.AndroidSDK/Properties/Resources.zh-Hant.resx b/src/Xamarin.Installer.AndroidSDK/Properties/Resources.zh-Hant.resx new file mode 100644 index 00000000000..9bd9bbf05f5 --- /dev/null +++ b/src/Xamarin.Installer.AndroidSDK/Properties/Resources.zh-Hant.resx @@ -0,0 +1,141 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 元件安裝失敗 + + + 無法載入元件內容檔案 + + + 找不到適合的元件保存。 + + + 嘗試接受 Android 授權時發生錯誤 + + + 接受 Android 授權失敗: 找不到 Android SDK + + + 接受 Android 授權失敗: 找不到 JAVA SDK + + + 接受 Android 授權失敗: 找不到 Cmdline-tools + + \ No newline at end of file diff --git a/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK.csproj b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK.csproj new file mode 100644 index 00000000000..78baa0560b1 --- /dev/null +++ b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK.csproj @@ -0,0 +1,99 @@ + + + + $(NoWarn);CA1305 + + + netstandard2.0 + 12 + false + WINDOWS;$(DefineConstants) + false + $(MicrosoftAndroidSdkOutDir) + + + bin\Debug\Xamarin.Installer.AndroidSDK.xml + + + bin\Release\Xamarin.Installer.AndroidSDK.xml + + + + + true + $(CodeSigningFile) + + + Xamarin.Installer.AndroidSDK + Assembly for Xamarin.Installer.AndroidSDK + + + + + + Properties\AssemblyInfoTemplate.cs + + + True + True + Resources.resx + + + + + ResXFileCodeGenerator + Resources.Designer.cs + + + + + + + + + + + + + runtime + All + + + runtime + All + + + 1.0.0 + + + + + + + + + + + + + + $(MSBuildThisFileDirectory)..\..\build-tools\android-platform-support\version.txt + + + + + + + + + + + Microsoft400 + + + Microsoft400 + + + + + diff --git a/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/AndroidAddonDependencyInfo.cs b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/AndroidAddonDependencyInfo.cs new file mode 100644 index 00000000000..e83e57b20fa --- /dev/null +++ b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/AndroidAddonDependencyInfo.cs @@ -0,0 +1,14 @@ +using System; +namespace Xamarin.Installer.AndroidSDK +{ + class AndroidAddonDependencyInfo + { + public string ApiLevel { get; set; } + public string NameId { get; set; } + public string NameDisplay { get; set; } + public string VendorId { get; set; } + public string VendorDisplay { get; set; } + public bool Unsure { get; set; } + } +} + diff --git a/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/AndroidComponentType.cs b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/AndroidComponentType.cs new file mode 100644 index 00000000000..bbb5fbfb33f --- /dev/null +++ b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/AndroidComponentType.cs @@ -0,0 +1,51 @@ +using System; + +namespace Xamarin.Installer.AndroidSDK +{ + /// + /// Android component type. This serves as an easier way to find what type an instance of + /// represents. + /// + public enum AndroidComponentType + { + /// + /// Component is an addon + /// + Addon, + + /// + /// Component is an extra addition to the SDK + /// + Extra, + + /// + /// A generic component + /// + Generic, + + /// + /// A Maven component + /// + Maven, + + /// + /// An API level (platform) component + /// + Platform, + + /// + /// A source component + /// + Source, + + /// + /// A system image component + /// + SystemImage, + + /// + /// Unknown type of the component + /// + Unknown = 1000 + } +} diff --git a/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/AndroidExtensions.cs b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/AndroidExtensions.cs new file mode 100644 index 00000000000..e2ac6ababf0 --- /dev/null +++ b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/AndroidExtensions.cs @@ -0,0 +1,68 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +using Kajabity.Tools.Java; +using Xamarin.Installer.Common; + +namespace Xamarin.Installer.AndroidSDK +{ + static class AndroidExtensions + { + public static bool GetPkgRevision (this JavaProperties props, out string original, out AndroidRevision parsed, string packageName = null) + { + original = null; + parsed = null; + + packageName = packageName.SafeTrim (); + if (!String.IsNullOrEmpty (packageName)) + packageName = String.Format ("'{0}' ", packageName); + + if (props == null) { + Logger.Debug ("No properties object, cannot obtain Android package {0}revision", packageName); + return false; + } + + string rev; + if (!props.GetProperty("Pkg.Revision", out rev) || String.IsNullOrEmpty (rev)) { + Logger.Debug ("Android package {0}has no revision defined", packageName); + return false; + } + + original = rev; + var arev = new AndroidRevision (rev, false); + parsed = arev.IsValid ? arev : null; + + return parsed != null; + } + + public static Version TryParseAsAndroidVersion (this string av, bool returnDefaultZeros = false) + { + if (String.IsNullOrEmpty (av)) + goto returnDefault; + + if (av.IndexOf ('.') < 0) { + int v; + + if (!Int32.TryParse (av, out v)) { + Logger.Info ("Version is not an integer: {0}", av); + goto returnDefault; + } + + return new Version (v, 0).CloneFillWithZeros (); + } + + Version ver; + if (!Version.TryParse (av, out ver)) { + Logger.Info ("Package revision is not a valid version: {0}", av); + goto returnDefault; + } + + return ver.CloneFillWithZeros (); + + returnDefault: + return returnDefaultZeros ? new Version (0, 0, 0, 0) : null; + } + } +} \ No newline at end of file diff --git a/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/AndroidManifestType.cs b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/AndroidManifestType.cs new file mode 100644 index 00000000000..7eb6a76b3b0 --- /dev/null +++ b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/AndroidManifestType.cs @@ -0,0 +1,36 @@ +// +// Author: +// Marek Habersack +// +// Copyright (c) 2017, Microsoft, Inc +// +// All rights reserved. +// +using System; + +namespace Xamarin.Installer.AndroidSDK +{ + /// + /// Android manifest type. + /// + public enum AndroidManifestType + { + /// + /// New (as of 2016) Google repository manifest format (e.g. http://dl-ssl.google.com/android/repository/repository2-1.xml) + /// + GoogleV2, + + /// + /// Xamarin manifest format (https://aka.ms/AndroidManifestFeed/d17-12) + /// + Xamarin, + + /// + /// Doesn't access the Internet at all. Instead it looks for local repositories and attempts to read information about + /// all the installed components. The returned information can (and will) be incomplete - information about channels is + /// unavailable for locally installed packages, as well as download URLs of the archives. Care must be taken when + /// accessing properties so that no null reference exceptions are thrown. + /// + Local, + } +} diff --git a/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/AndroidRevision.cs b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/AndroidRevision.cs new file mode 100644 index 00000000000..a639b6cfe4f --- /dev/null +++ b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/AndroidRevision.cs @@ -0,0 +1,354 @@ +// +// Authors: +// Marek Habersack +// +// Copyright (c) 2017, Microsoft, Inc +// +// All rights reserved. +// +using System; +using System.Text; + +using Xamarin.Installer.Common; + +namespace Xamarin.Installer.AndroidSDK +{ + /// + /// Represents Android component revision. Only the component is guaranteed to be present. + /// + public sealed class AndroidRevision : IEquatable , IComparable + { + /// + /// Gets the major revision number + /// + /// Major revision number + public int Major { get; private set; } + + /// + /// Gets the minor revision number. Value is valid if it's equal or higer than zero. + /// + /// Minor revision number + public int Minor { get; private set; } + + /// + /// Gets the micro revision number. Value is valid if it's equal or higer than zero. + /// + /// Micro revision number + public int Micro { get; private set; } + + /// + /// Gets the minor preview number. Value is valid if it's equal or higer than zero. + /// + /// Preview revision number + public int Preview { get; private set; } + + /// + /// Indicates whether or not this revision instance represents a valid revision + /// + /// true if valid; otherwise, false. + public bool IsValid => Major > 0; + + /// + /// If set to true, indicates that should consider comparing only + /// the and properties. + /// + /// true to compare only major and minor components; otherwise, false. + public bool ConsiderOnlyMajorMinor { get; set; } + + /// + /// Initializes a new instance of the class. + /// + /// Major revision number (required, must be higher than or equal to zero) + /// Minor revision number (optional) + /// Micro revision number (optional) + /// Preview revision number (optional) + /// If set to true throw an exception if revision is invalid. + public AndroidRevision (int major, int minor = -1, int micro = -1, int preview = -1, bool throwIfInvalid = true) + { + Init (major, minor, micro, preview, throwIfInvalid); + } + + /// + /// Initializes a new instance of the class. Attempts to + /// parse the revision string passed in the parameter. + /// + /// Revision string to parse + /// If set to true throw an exception if revision is invalid. + public AndroidRevision (string rev, bool throwIfInvalid = true) + { + if (String.IsNullOrEmpty (rev)) + goto setDefault; + + if (rev.IndexOf ('.') < 0) { + int v; + + if (!Int32.TryParse (rev, out v)) { + Logger.Info ($"Version is not an integer: {rev}"); + goto setDefault; + } + + Init (v, throwIfInvalid: throwIfInvalid); + return; + } + + Version ver; + if (!Version.TryParse (rev, out ver)) { + Logger.Info ($"Package revision is not a valid version: {rev}"); + goto setDefault; + } + + Init (ver.Major, ver.Minor, ver.Build, ver.Revision, throwIfInvalid); + return; + + setDefault: + Init (0, throwIfInvalid: throwIfInvalid); + } + + public AndroidRevision (AndroidRevision other, bool throwIfInvalid = true) + { + if (other == null) + throw new ArgumentNullException (nameof (other)); + Init (other.Major, other.Minor, other.Micro, other.Preview, throwIfInvalid); + } + + void Init (int major, int minor = -1, int micro = -1, int preview = -1, bool throwIfInvalid = true) + { + if (major < 0 && throwIfInvalid) + throw new ArgumentOutOfRangeException(nameof(major), "Must not be less than zero"); + + Major = EnsureValidValue (major); + Minor = EnsureValidValue (minor); + Micro = EnsureValidValue (micro); + Preview = EnsureValidValue (preview); + } + + int EnsureValidValue (int value) + { + if (value < 0) + return -1; + return value; + } + + /// + /// Returns a that represents the current in the standard + /// dotted format. + /// + /// A that represents the current . + public override string ToString () + { + var sb = new StringBuilder (); + sb.Append (Major); + AppendValue (Minor, sb); + AppendValue (Micro, sb); + AppendValue (Preview, sb); + + return sb.ToString (); + } + + void AppendValue (int value, StringBuilder sb) + { + if (value < 0) + return; + sb.Append ('.'); + sb.Append (value); + } + + /// + /// Determines whether the specified is equal to the + /// current . + /// + /// The to compare with the current . + /// true if the specified is equal to the current + /// ; otherwise, false. + public bool Equals (AndroidRevision other) + { + if (other == null) + return false; + + if (ReferenceEquals (this, other)) + return true; + + if (Major != other.Major) + return false; + + if (Minor != other.Minor) + return false; + + if (ConsiderOnlyMajorMinor) + return true; + + if (Micro != other.Micro) + return false; + + return Preview == other.Preview; + } + + public bool EqualsValid(AndroidRevision other) + { + if (other == null) + return false; + + if (ReferenceEquals(this, other)) + return true; + + if (!IsValid || !other.IsValid) + return false; + + if (Major != other.Major) + return false; + + if (Minor == -1 || other.Minor == -1) + return true; + if (Minor != other.Minor) + return false; + + if (Micro == -1 || other.Micro == -1) + return true; + if (Micro != other.Micro) + return false; + + if (Preview == -1 || other.Preview == -1) + return true; + return Preview == other.Preview; + } + + /// + /// Determines whether the specified is equal to the current . + /// + /// The to compare with the current . + /// true if the specified is equal to the current + /// ; otherwise, false. + public override bool Equals (object obj) + { + return Equals (obj as AndroidRevision); + } + + /// + /// Serves as a hash function for a object. + /// + /// A hash code for this instance that is suitable for use in hashing algorithms and data structures such as a hash table. + public override int GetHashCode () + { + int hashCode = Major.GetHashCode (); + hashCode = hashCode.XorWith (Minor); + hashCode = hashCode.XorWith (Micro); + return hashCode.XorWith (Preview); + } + + /// + /// Compares this instance of to another one. + /// + /// + /// + /// + /// 1if this instance is bigger than + /// + /// + /// 0if this instance is equal to + /// + /// + /// -1if this instance is smaller than + /// + /// "> + /// Other. + public int CompareTo (AndroidRevision other) + { + if (other == null) + return 1; + + if (Major != other.Major) + return Major > other.Major ? 1 : -1; + + if (Minor != other.Minor) + return Minor > other.Minor ? 1 : -1; + + if (Micro != other.Micro) + return Micro > other.Micro ? 1 : -1; + + if (Preview != other.Preview) + return Preview > other.Preview ? 1 : -1; + + return 0; + } + + /// + /// Determines whether a specified instance of is equal to + /// another specified . + /// + /// The first to compare. + /// The second to compare. + /// true if rev1 and rev2 are equal; otherwise, false. + public static bool operator == (AndroidRevision rev1, AndroidRevision rev2) + { + if (Object.ReferenceEquals (rev1, null)) { + return Object.ReferenceEquals (rev2, null); + } + + return rev1.Equals (rev2); + } + + /// + /// Determines whether a specified instance of is not equal + /// to another specified . + /// + /// The first to compare. + /// The second to compare. + /// true if rev1 and rev2 are not equal; otherwise, false. + public static bool operator != (AndroidRevision rev1, AndroidRevision rev2) + { + return !(rev1 == rev2); + } + + /// + /// Determines whether one specified is lower than another + /// specfied . + /// + /// The first to compare. + /// The second to compare. + /// true if rev1 is lower than rev2; otherwise, false. + public static bool operator < (AndroidRevision rev1, AndroidRevision rev2) + { + if (rev1 == null) + throw new ArgumentNullException (nameof (rev1)); + return rev1.CompareTo (rev2) < 0; + } + + /// + /// Determines whether one specified is lower than or equal + /// to another specfied . + /// + /// The first to compare. + /// The second to compare. + /// true if rev1 is lower than or equal to rev2; otherwise, false. + public static bool operator <= (AndroidRevision rev1, AndroidRevision rev2) + { + if (rev1 == null) + throw new ArgumentNullException (nameof (rev1)); + return rev1.CompareTo (rev2) <= 0; + } + + /// + /// Determines whether one specified is greater than + /// another specfied . + /// + /// The first to compare. + /// The second to compare. + /// true if rev1 is greater than rev2; otherwise, false. + public static bool operator > (AndroidRevision rev1, AndroidRevision rev2) + { + return rev2 < rev1; + } + + /// + /// Determines whether one specified is greater than or + /// equal to another specfied . + /// + /// The first to compare. + /// The second to compare. + /// true if rev1 is greater than or equal to rev2; otherwise, false. + public static bool operator >= (AndroidRevision rev1, AndroidRevision rev2) + { + return rev2 <= rev1; + } + } +} diff --git a/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/AndroidSDKComponentInstallationResult.cs b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/AndroidSDKComponentInstallationResult.cs new file mode 100644 index 00000000000..70696d21450 --- /dev/null +++ b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/AndroidSDKComponentInstallationResult.cs @@ -0,0 +1,75 @@ +using System; + +namespace Xamarin.Installer.AndroidSDK +{ + public class AndroidSDKComponentInstallationResult + { + public enum States + { + None, + UserCancelled, + DownloadError, + InstallationError, + UninstallationError, + DownloadedSuccessfully, + InstalledSuccessfully, + UninstalledSuccessfully, + Success, + + LicensesNotAccepted, + LicensesAccepted, + LicensesNotRequired, + LicensesAcceptanceFailed + } + + public bool Success { get; set; } + public States State { get; set; } + + public Exception Exception { get; set; } + + public AndroidSDKComponentInstallationResult (bool success, Exception exception = null) + : this (success ? States.Success : States.None, exception, success) + { + } + + public AndroidSDKComponentInstallationResult (States state, Exception exception = null, bool? success = null) + { + State = state; + Exception = exception; + Success = success.HasValue ? success.Value : state.IsSuccessful (); + } + + /// + /// Use instead if you need to handle null values + /// + public override string ToString () + { + return this.ToReadableString (); + } + } + + public static class AndroidSDKComponentInstallationResultExtensions + { + public static bool IsSuccessful(this AndroidSDKComponentInstallationResult.States state) + { + return state == AndroidSDKComponentInstallationResult.States.Success + || state == AndroidSDKComponentInstallationResult.States.DownloadedSuccessfully + || state == AndroidSDKComponentInstallationResult.States.InstalledSuccessfully + || state == AndroidSDKComponentInstallationResult.States.UninstalledSuccessfully + || state == AndroidSDKComponentInstallationResult.States.LicensesAccepted + || state == AndroidSDKComponentInstallationResult.States.LicensesNotRequired; + } + + /// + /// This method supports null input values + /// + public static string ToReadableString (this AndroidSDKComponentInstallationResult result) + { + return result == null ? "undefined" : + (result.Success ? "success" + : ( + "fail" + (!String.IsNullOrEmpty (result.Exception?.Message) ? $": \"{result.Exception.Message}\"" : String.Empty) + )) + $" ({result.State})"; + } + } +} diff --git a/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/AndroidSDKContext.cs b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/AndroidSDKContext.cs new file mode 100644 index 00000000000..c2be0dce8ae --- /dev/null +++ b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/AndroidSDKContext.cs @@ -0,0 +1,106 @@ +using System; +using System.IO; +using System.Runtime.InteropServices; + +using Xamarin.Installer.Common; + +namespace Xamarin.Installer.AndroidSDK +{ + public class AndroidSDKContext + { + static readonly object instanceCreationLock = new object (); + + public static readonly AndroidSDKContext Instance; + + public AndroidSDKPlatform Platform { get; set; } + + public string HostArch { get; set; } + + public string BaseTemporaryDirectory { get; set; } + + public string UserName { + get { + if (CommonUtilities.Helpers == null) + throw new InvalidOperationException ("Internal error: CommonUtilities.Helpers must be initialized first"); + if (String.IsNullOrEmpty (CommonUtilities.Helpers.UserName)) + throw new InvalidOperationException ("Internal error: CommonUtilities.Helpers.UserName must have a valid value"); + return CommonUtilities.Helpers.UserName; + } + } + + // Just a convenience to make initialization more straightforward + public string ProductName { + get { return CommonUtilities.ProductName; } + set { CommonUtilities.ProductName = value; } + } + + static AndroidSDKContext () + { + lock (instanceCreationLock) { + if (Instance != null) + return; + + Instance = new AndroidSDKContext { + BaseTemporaryDirectory = Path.Combine(Path.GetTempPath(), "Xamarin"), + Platform = DeterminePlatform(), + HostArch = DetermineHostArch() + }; + } + } + + static AndroidSDKPlatform DeterminePlatform () + { + switch (Environment.OSVersion.Platform) { + case PlatformID.MacOSX: + return AndroidSDKPlatform.Mac; + + // We only care about distinction between OSX and Linux, since that's the only + // difference that matters in the Android SDK repository context + case PlatformID.Unix: + return IsRunningOnMac () ? AndroidSDKPlatform.Mac : AndroidSDKPlatform.Linux; + + default: + return AndroidSDKPlatform.Windows; // another simplification that'll do + } + } + + static string DetermineHostArch() + { + switch (RuntimeInformation.OSArchitecture) + { + case Architecture.X86: + return "x86"; + case Architecture.X64: + return "x64"; + case Architecture.Arm64: + return "aarch64"; + default: + return ""; + } + } + + [DllImport ("libc")] + static extern int uname (IntPtr buf); + + //From Managed.Windows.Forms/XplatUI + static bool IsRunningOnMac () + { + IntPtr buf = IntPtr.Zero; + try { + buf = Marshal.AllocHGlobal (8192); + // This is a hacktastic way of getting sysname from uname () + if (uname (buf) == 0) { + string os = Marshal.PtrToStringAnsi (buf); + if (os == "Darwin") + return true; + } + } catch { + // ignore + } finally { + if (buf != IntPtr.Zero) + Marshal.FreeHGlobal (buf); + } + return false; + } + } +} diff --git a/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/AndroidSDKInstaller.cs b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/AndroidSDKInstaller.cs new file mode 100644 index 00000000000..a41e12d46fc --- /dev/null +++ b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/AndroidSDKInstaller.cs @@ -0,0 +1,779 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Xml.Linq; +using Kajabity.Tools.Java; + +using Xamarin.AndroidTools; +using Xamarin.Installer.AndroidSDK.Common; +using Xamarin.Installer.AndroidSDK.Xamarin; +using Xamarin.Installer.AndroidSDK.GoogleV2; +using Xamarin.Installer.AndroidSDK.LocalSDK; +using Xamarin.Installer.Common; +using Xamarin.Installer.AndroidSDK.Manager; +using System.Threading.Tasks; +using System.Threading; + +namespace Xamarin.Installer.AndroidSDK +{ + /// + /// Android SDK installer class is the class to be used by the client software to perform all the + /// operations of updating or installing the SDK. + /// + public class AndroidSDKInstaller : IParserErrorHandler + { + List discoveredInstances; + StringComparer filesystemComparer; + StringComparison filesystemComparison; + Uri _manifestURL; + Uri _googleAddonsListURL; + Uri _googleRepositoryBaseURL; + bool _useManifestCaching; + + /// + /// Initializes a new instance of the class. The caller is + /// required to pass an instance of class that implements the interface. + /// + /// Instance of a class implementing the IHelpers interface + /// AndroidManifestType.Xamarin if the installer is to use the + /// Xamarin Android SDK manifest, AndroidManifestType.GoogleV1 or AndroidManifestType.GoogleV2 + /// to use the official Google Android SDK manifest + /// URL of the repository manifest (optional) + /// Google repository only: URL of the addon manifest (optional) + /// Google repository only: base URL of the repository + /// If true, load local manifest when offline + public AndroidSDKInstaller (IHelpers helpers, AndroidManifestType manifestType = AndroidManifestType.Xamarin, Uri manifestURL = null, Uri googleAddonsListURL = null, Uri googleRepositoryBaseURL = null, bool useManifestCaching = false, ILicensesStorage licensesStorage = null) + { + CommonUtilities.Helpers = helpers ?? new Helper(); + AndroidManifestType = manifestType; + + _manifestURL = manifestURL; + _googleAddonsListURL = googleAddonsListURL; + _googleRepositoryBaseURL = googleRepositoryBaseURL; + _useManifestCaching = useManifestCaching; + + LicensesStorage = licensesStorage ?? new AndroidLicensesStorage(); + + CreateRepository (manifestType, manifestURL, googleAddonsListURL, googleRepositoryBaseURL, useManifestCaching); + + filesystemComparer = helpers.IsCaseSensitiveFileSystem ? StringComparer.Ordinal : StringComparer.OrdinalIgnoreCase; + filesystemComparison = helpers.IsCaseSensitiveFileSystem ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase; + } + + public Repository Repository { get; private set; } + + public ILicensesStorage LicensesStorage { get; private set; } + + /// + /// Raised whenever the installer wants to show a message to the user. Used only by the Install* + /// family of methods. + /// + public event EventHandler InstallationProgress; + + /// + /// Gets the repository manifest URL as detected and used by the current installer instance. + /// + /// The repository manifest URL + public Uri RepositoryManifestURL { + get { return Repository?.ManifestURL; } + } + + /// + /// After calling this list will contain all the instances of Android SDK + /// detected on the system. Note that if there are no instances found a default one will be created + /// and stored in this list. + /// + /// The discovered Android SDK instances. + public IList DiscoveredSdkInstances { + get { return discoveredInstances; } + } + + /// + /// Gets list of all the channels retrieved from the various repository manifests. Currently all of the + /// Android SDK manifests defined the same 4 channels () but it is possible that + /// there might be differences between manifests in the future. + /// + /// The channels. + public IList Channels { + get { return Repository?.Channels?.Values?.ToList (); } + } + + /// + /// Sets or gets the Android manifest type for this instance (see ) + /// + public AndroidManifestType AndroidManifestType { get; } + + /// + /// Gets the repository state. If true, it is using local manifest, + /// and should be updated as soon as connection is established. + /// + public bool IsOffline => Repository.IsOffline; + + void CreateRepository (AndroidManifestType manifestType, Uri manifestURL, Uri googleAddonsListURL, Uri googleRepositoryBaseURL, bool useManifestCaching) + { + switch (manifestType) { + case AndroidManifestType.Xamarin: + Repository = new XamarinRepository (manifestURL, useManifestCaching); + break; + + case AndroidManifestType.GoogleV2: + Repository = new GoogleV2Repository (this, manifestURL, googleAddonsListURL, googleRepositoryBaseURL, useManifestCaching); + break; + + case AndroidManifestType.Local: + Repository = new LocalSDKRepository (this); + break; + + default: + throw new InvalidOperationException ($"Unsupported manifest type {manifestType}"); + } + } + + /// + /// Given a full filesystem path attempts to find and return a corresponding instance. + /// + /// The instance corresponding to the path. + /// Full path for which to find a corresponding SDK instance. If null, default path will be used. + public AndroidSdkInstance FindInstance (string forPath) + { + forPath = forPath ?? AndroidSdk.AndroidSdkPath; + + if (String.IsNullOrEmpty (forPath)) + throw new ArgumentException ("must not be an empty string", nameof (forPath)); + + string path = Path.GetFullPath (forPath); + if (discoveredInstances == null || discoveredInstances.Count == 0) { + return null; + } + + return discoveredInstances.FirstOrDefault (sdk => String.Compare (sdk.Path, path, filesystemComparison) == 0); + } + + /// + /// + /// Refreshes the state of all the components given a particular Android SDK . By default the method + /// doesn't re-download or re-parse the manifest(s) but instead it performs detection of each of the components in the + /// instance, thus updating their state. If, however, is set to true the repository manifests, + /// if any, will be fetched and reparsed. + /// + /// + /// If is true then the may have its set of components changed - new + /// ones can be added and old ones can be changed (only their metadata will change, the component instance won't change). + /// + /// + /// + /// If is true then the method will start a full detection cycle. This means that the + /// will be modified - all of its components will be replaced with fresh instances. This is to ensure that + /// the metadata in the components is 100% accurate. By default full detection is not performed. + /// + /// + /// Android SDK instance + /// Perform update from the local filesystem, if true, update from remote manifests otherwise + /// If true, perform full detection of all the components after refreshing the instance + public void RefreshState (AndroidSdkInstance instance, bool localOnly = true, bool performFullDetection = false) + { + if (instance == null) + throw new ArgumentNullException (nameof (instance)); + if (!localOnly) + { + try + { + Repository.Parse(); + } + catch (Exception e) + { + LogParserError("AndroidSDK.RefreshState", e); + return; + } + } + Repository.Refresh (instance); + + if (performFullDetection) + DetectSdkComponents (instance); + } + + /// + /// Downloads the current Android SDK repository manifest () and uses it to disover/detect + /// all instances of Android SDK on the system. It uses the system-dependent default location as well as the locations stored + /// in . Additionally, it can include extra paths passed in + /// the list. If no Android SDK instance is detected on the system a default one will be created. + /// All the discovered instances are returned by the property. + /// + /// If is set to true (the default), the discovered instances replace the ones currently stored in + /// the list returned by the property. Otherwise the newly discovered instances are appended + /// to the list. + /// + /// The method returns true if detection was successful, false to indicate a "soft" error (one which lets the caller + /// retry the operation, most likely a networking issue) and it can throw exceptions for "hard" errors. + /// + /// Custom Android SDK locations (optional). + /// Perform detection from scratch if set to true (optional) + /// Handler to be called whenever any component in the SDK instance changes its state + public bool Discover (List sdkLocations = null, bool fromScratch = true, EventHandler instanceComponentStatusChangeHandler = null) + { + if (fromScratch || !Repository.Parsed) { + if (fromScratch) + CreateRepository (AndroidManifestType, _manifestURL, _googleAddonsListURL, _googleRepositoryBaseURL, _useManifestCaching); + try + { + Repository.Parse(); + } + catch (Exception e) + { + LogParserError("AndroidSDK.Discover", e); + return false; + } + } + + var sdkInstances = new Dictionary (filesystemComparer); + if (!fromScratch && discoveredInstances != null) { + discoveredInstances.ForEach (instance => { + if (instance == null || sdkInstances.ContainsKey (instance.Path)) + return; + sdkInstances [instance.Path] = instance; + }); + } + + var sdkPaths = new List (); + +#pragma warning disable CS0612 // Type or member is obsolete + if (AndroidSdk.AllAndroidSdkPaths != null) + sdkPaths.AddRange (AndroidSdk.AllAndroidSdkPaths); +#pragma warning restore CS0612 // Type or member is obsolete + + if (sdkLocations != null) + sdkPaths.AddRange (sdkLocations); + + + sdkPaths = sdkPaths.Distinct ().ToList (); + + string defaultPath = AndroidSdk.AndroidSdkPath ?? sdkPaths.FirstOrDefault (); + foreach (string path in sdkPaths) { + if (String.IsNullOrEmpty (path)) + continue; + string asdkPath = Path.GetFullPath (path); + if (sdkInstances.ContainsKey (asdkPath)) + continue; + + var sdkInstance = new AndroidSdkInstance { + Path = asdkPath, + IsDefault = String.Compare (path, defaultPath, filesystemComparison) == 0 + }; + if (instanceComponentStatusChangeHandler != null) { + sdkInstance.ComponentStatusChanged -= instanceComponentStatusChangeHandler; + sdkInstance.ComponentStatusChanged += instanceComponentStatusChangeHandler; + } + + DetectSdkComponents (sdkInstance); + + sdkInstances [asdkPath] = sdkInstance; + } + + if (fromScratch || discoveredInstances == null) + discoveredInstances = sdkInstances.Values.ToList (); + else { + sdkInstances.Values.ToList ().ForEach (instance => { + if (discoveredInstances.Contains (instance)) + return; + discoveredInstances.Add (instance); + }); + } + if (discoveredInstances.Count == 1) + discoveredInstances [0].Selected = true; + else if (discoveredInstances.Any () && !discoveredInstances.Any (item => item.Selected)) { + // Make sure we have a selected sdk in any case as the interaction is optional + // Select default location if its valid, select sdk with latest version otherwise + var defaultInstance = discoveredInstances.FirstOrDefault (item => item.IsDefault); + if (defaultInstance != null && (defaultInstance.Version.Major > 0 || !discoveredInstances.Any (item => item.Version.Major > 0))) + defaultInstance.Selected = true; + else + discoveredInstances.OrderByDescending (item => item.Version).First ().Selected = true; + } + + return true; + } + + /// + /// Gets the license with the ID specified in . The ID usually comes from + /// + /// + /// The license identified by or an empty string if license with this ID is not found. + /// License identifier. Required, must not be null + public string GetLicense (string licenseId) + { + if (licenseId == null) + throw new ArgumentNullException (nameof (licenseId)); + + return Repository.GetLicense (licenseId)?.Text; + } + + /// + /// Returns a list of licenses for all the components passed in the + /// parameter or, if it's null or empty, for all the outdated components in the specified + /// . + /// + /// List of licenses or null if no licenses were available + /// Android SDK instance. + /// List of components to install (optional) + public IList GetLicenses (AndroidSdkInstance instance, IList componentsToInstall = null) + { + if (instance == null) + throw new ArgumentNullException (nameof (instance)); + + IList < IAndroidComponent > components = null; + if (componentsToInstall == null || componentsToInstall.Count == 0) + components = instance.Components.AllOutdated (); + + if (components == null || components.Count == 0) + return null; + + var ret = new List (); + foreach (IAndroidComponent component in components) { + License license = component?.License; + if (license == null || ret.Contains (license)) + continue; + ret.Add (license); + } + + return ret.Count > 0 ? ret : null; + } + + /// + /// Gets a list of download items for all of the components passed in the parameter. After a + /// successful download the calling party must set to the on-disk path of the + /// downloaded file thus marking the instance as installable. + /// + /// List of archives to download + /// List of components to get download items for + public IList GetDownloadItems (IList components) + { + if (components == null || components.Count == 0) + return null; + + var ret = new List (); + foreach (IAndroidComponent component in components.Where (c => c != null && c.Archives != null && c.Archives.Count > 0)) { + ret.AddRange (component.Archives.Where (a => a != null && a.Url != null).Select (a => ResetForDownload (a))); + } + return ret; + + Archive ResetForDownload (Archive archive) + { + archive.DownloadedFilePath = null; + return archive; + } + } + + /// + /// Gets a list of download items for all the outdated or missing components in the passed . After a + /// successful download the calling party must set to the on-disk path of the + /// downloaded file thus marking the instance as installable. The method considers + /// before + /// + /// List of archives to download + /// Android SDK instance + /// If true, include missing components + public IList GetDownloadItems (AndroidSdkInstance instance, bool includeMissing = false) + { + if (instance == null) + return null; + IList components = instance.ComponentsForInstallation ?? instance.Components; + return GetDownloadItems (includeMissing ? components?.AllOutdatedOrMissing () : components?.AllOutdated ()); + } + + void OnInstallationProgress (bool isInitialEvent, float progress, string format, params object[] args) + { + if (InstallationProgress == null) + return; + + string message; + if (format == null) + message = String.Empty; + else if (args == null || args.Length == 0) + message = format; + else + message = String.Format (format, args); + InstallationProgress (this, new InstallationProgressEventArgs { Message = message, Progress = progress, IsInitialEvent = isInitialEvent }); + } + + /// + /// + /// Gets the set of components available for installation given the particular . + /// If is null or empty, the outdated components from the + /// will be considered for inclusion in the set. If the + /// parameter is null or empty, only the default channel (stable) packages will be considered. + /// + /// + /// Each component's dependencies will be added to the resulting set. It is possible that certain components + /// (for instance build tools or emulators) will be found in the returned set more than once because they match + /// the criteria (e.g. all the included versions are higher than the one detected on the system) or because they + /// are dependent upon by one or more of the other components. + /// + /// + /// Caller must decide which packages to eventually install if there are duplicates in the returned set. + /// + /// + /// List of components that are candidates for installation in the given SDK instance + /// Android SDK instance + /// Components to install. + /// Allowed channels. + public IList GetInstallationSet (AndroidSdkInstance instance, IList componentsToInstall = null, HashSet allowedChannels = null) + { + if (instance == null) + throw new ArgumentNullException (nameof (instance)); + + if (componentsToInstall == null || componentsToInstall.Count == 0) + componentsToInstall = instance.Components.AllOutdated (); + + if (componentsToInstall.Count == 0) + return null; + + if (allowedChannels == null || allowedChannels.Count == 0) { + allowedChannels = new HashSet { + Repository.DefaultChannel + }; + } + + var components = new List (); + foreach (IAndroidComponent c in componentsToInstall) { + AddComponentToSet (instance, c, components, allowedChannels); + } + + return components; + } + + /// + /// Performs installation of all the components in the specified . The + /// parameter must not be null or empty and it must contain all the components the hosting application needs to install - no dependency + /// checking is performed by this method. It is advised to call the method prior to calling this one. + /// Before installing anything the method checks all the archives in all the passed components to make sure they are installable. + /// If any invalid archive is found no installation is done. This is to prevent partially successful installations, potentially breaking the SDK + /// instance. If is set to true and an invalid archive is found then the method + /// will throw an exception, otherwise it will silently return without performing installation. All the issues are logged. + /// + /// Android SDK instance. + /// Components to install + /// Throw an exception if any component with invalid archives is found, if set to true + public void Install (AndroidSdkInstance instance, IList componentsToInstall, bool throwIfInvalidComponentsFound = true, + IProgressMonitor monitor = null) + { + if (instance == null) + throw new ArgumentNullException (nameof (instance)); + + if (componentsToInstall == null) + throw new ArgumentNullException (nameof (componentsToInstall)); + + //throw new Exception("A test install inner exception."); + + if (componentsToInstall.Count == 0) + return; + + // This is a separate step so that we can report all the issues at the same time as well as prevent + // installing of any components if even a single archive isn't valid. + bool haveArchiveIssues = false; + foreach (IAndroidComponent c in componentsToInstall) { + // If it happens simply ignore the component silently + if (c == null || c.Archives == null || c.Archives.Count == 0) + continue; + + bool haveInvalidArchives = false; + foreach (Archive a in c.Archives) { + if (a == null || !a.WasDownloaded || a.IsInstallable ()) + continue; + haveInvalidArchives = true; + } + + if (haveInvalidArchives) + Logger.Warning ($"Android SDK component '{c.DetailedDescription}' reports issues with downloaded archive(s)"); + } + + if (haveArchiveIssues) { + Logger.Error ($"Certain Android SDK components encountered issues with the downloaded archives"); + if (throwIfInvalidComponentsFound) + throw new InvalidOperationException ("Found issues with one or more Android SDK Components. Unable to continue."); + return; + } + + foreach (IAndroidComponent c in componentsToInstall) + InstallComponent (c, instance, monitor); + } + + /// + /// Removes the components listed in from the specified . + /// + /// Android SDK instance. + /// Components to remove + public void Remove (AndroidSdkInstance instance, IList componentsToRemove) + { + if (instance == null) + throw new ArgumentNullException (nameof (instance)); + + if (componentsToRemove == null) + throw new ArgumentNullException (nameof (componentsToRemove)); + + //throw new Exception("Uninstall inner test exception."); + + if (componentsToRemove.Count == 0) + return; + + foreach (IAndroidComponent c in componentsToRemove) { + if (c == null) + continue; + + Repository.Remove (c, instance.Path); + } + } + + void AddComponentToSet (AndroidSdkInstance instance, IAndroidComponent component, List componentSet, HashSet allowedChannels) + { + if (component == null) { + Logger.Debug ("Skipping null component"); + return; + } + if (!component.ForceInstallation && component.Present && !component.NeedsUpdate) { + Logger.Debug ($"Skipping component '{component.DetailedDescription}' as it is already present and up-to-date"); + return; + } + if (allowedChannels != null && !allowedChannels.Contains (component.Channel)) { + var channels = string.Join (", ", allowedChannels.Select (c => c.Name)); + Logger.Debug ($"Skipping component '{component.DetailedDescription}' in channel '{component.Channel?.Name}' as it is not in the allowed channels ({channels})"); + return; + } + if (componentSet.Contains (component)) { + Logger.Debug ($"Skipping component '{component.DetailedDescription}' as it is already in the set"); + return; + } + + Logger.Debug ($"Adding component '{component.DetailedDescription}'"); + componentSet.Add (component); + + string hostOS = AndroidUtilities.GetPlatformOS (); + if (component.Dependencies != null) { + foreach (Dependency dep in component.Dependencies) { + if (dep == null || String.IsNullOrEmpty (dep.Path)) + continue; + Logger.Debug ($"{component.DetailedDescription} depends on: '{dep.Path}'{GetMinimumRevisionString (dep)}"); + IList deps = instance.Components.AllWithPath (dep.Path, dep.MinRevision); + if (deps == null || deps.Count == 0) { + Logger.Debug ($"{component.DetailedDescription}: dependency package '{dep.Path}' not found"); + continue; + } + string instances = GetPluralForm (deps.Count, "instance", "instances"); + Logger.Debug ($"{component.DetailedDescription}: found {deps.Count} {instances} of '{dep.Path}'"); + foreach (IAndroidComponent dc in deps) { + if (dc == component) + continue; + AddComponentToSet (instance, dc, componentSet, allowedChannels); + } + } + } + } + + string GetPluralForm (int count, string singular, string plural) + { + return count == 1 ? singular : plural; + } + + string GetMinimumRevisionString (Dependency dep) + { + if (dep?.MinRevision == null) + return ", any revision"; + return $", minimum revision {dep.MinRevision}"; + } + + void InstallComponent (IAndroidComponent c, AndroidSdkInstance instance, IProgressMonitor monitor = null) + { + if (c == null || (!c.ForceInstallation && c.Present && !c.NeedsUpdate) || (c.Archives == null || c.Archives.Count == 0)) + return; + var component = c as BasePackage; + if (component == null) + throw new InvalidOperationException ($"Internal error: unexpected component type {c.GetType ()}"); + + int installSubComponentIndex = 0; + bool isMonitorWithTotalProgress; + if (monitor is MonitorWithTotalProgress wrapperMonitor) + isMonitorWithTotalProgress = wrapperMonitor.IsMonitorWithTotalProgress; + else if (monitor is IProgressMonitorWithTotalProgress) { + monitor = new MonitorWithTotalProgress (monitor, 1); + isMonitorWithTotalProgress = true; + } else + isMonitorWithTotalProgress = false; + + if (isMonitorWithTotalProgress && ((MonitorWithTotalProgress) monitor).IsMonitorWithTotalProgress) { + ((MonitorWithTotalProgress) monitor).State = MonitorWithTotalProgress.States.Install; + ((MonitorWithTotalProgress) monitor).SubComponentsCount = component.Archives.Count; + } + + // In theory there should be just a single archive in each component, but we never know really... + foreach (Archive archive in component.Archives) { + // We can skip the checksum verification, it was done earlier + // Don't check platform if the component is system-image + if (!archive.IsInstallable (false, checkPlatform: !(component.Info is AndroidComponentInfoSystemImage))) + continue; + + string progressMsgFormat = CommonUtilities.Helpers.GetString ("Installing {0}"); + string componentName = component.DetailedDescription; + + InstallationProgressEventArgs.InstallationProgressActionDelegate progressCallback = + delegate (float progress) { + //Logger.Debug($"[InstallationProgressActionDelegate] will report progress: {progress}"); + OnInstallationProgress (false, progress, progressMsgFormat, componentName); + monitor?.ReportProgress ((long) progress); + }; + + OnInstallationProgress (true, 0f, progressMsgFormat, componentName); + + monitor?.BeginStep (String.Format (progressMsgFormat, componentName), 100); + + if (isMonitorWithTotalProgress && ((MonitorWithTotalProgress) monitor).IsMonitorWithTotalProgress) { + ((MonitorWithTotalProgress) monitor).SubComponentIndex = installSubComponentIndex++; + } + + Repository.Install (component, archive.DownloadedFilePath, instance.Path, progressCallback); + monitor?.EndStep (new AndroidSDKComponentInstallationResult (AndroidSDKComponentInstallationResult.States.InstalledSuccessfully)); + } + } + + void DetectSdkComponents (AndroidSdkInstance sdkInstance) + { + if (String.IsNullOrEmpty (sdkInstance.Path)) + throw new InvalidOperationException ("Internal error: Android SDK instance without associated path"); + + Logger.Debug ($"Detecting Android SDK in '{sdkInstance.Path}'"); + Repository.Detect (sdkInstance); + + var sourcePropsFileName = "source.properties"; + string path = Path.Combine (sdkInstance.Path, Constants.ComponentPaths.Tools, Constants.RequiredComponentVersions.RequiredCommandLineToolsVersion, sourcePropsFileName); + if (!File.Exists (path)) { + path = Path.Combine (sdkInstance.Path, Constants.ComponentPaths.ToolsObsolete, sourcePropsFileName); + } + + JavaProperties props = AndroidUtilities.ReadAndroidProperties (path); + + if (!props.GetPkgRevision (out string origRev, out AndroidRevision instv)) { + sdkInstance.Version = new AndroidRevision (0, 0, 0, 0); + sdkInstance.VersionString = "r0"; + Logger.Debug ("Could not retrieve Pkg.Revision from {0}, r0 assumed", path); + return; + } + sdkInstance.VersionString = "r" + origRev; + sdkInstance.Version = instv; + + // The paths currently overlap with the contents of the tools package, but this can change hence the + // code duplication + List sdkExistenceCheckPaths; + switch (AndroidSDKContext.Instance.Platform) { + case AndroidSDKPlatform.Linux: + case AndroidSDKPlatform.Mac: + sdkExistenceCheckPaths = new List { + Path.Combine (Constants.ComponentPaths.Tools, Constants.RequiredComponentVersions.RequiredCommandLineToolsVersion, "bin", "avdmanager"), + Path.Combine (Constants.ComponentPaths.Tools, Constants.RequiredComponentVersions.RequiredCommandLineToolsVersion, "bin", "lint"), + Path.Combine (Constants.ComponentPaths.Tools, Constants.RequiredComponentVersions.RequiredCommandLineToolsVersion, "bin", "sdkmanager"), + }; + break; + + case AndroidSDKPlatform.Windows: + sdkExistenceCheckPaths = new List { + Path.Combine (Constants.ComponentPaths.Tools, Constants.RequiredComponentVersions.RequiredCommandLineToolsVersion, "bin", "avdmanager.bat"), + Path.Combine (Constants.ComponentPaths.Tools, Constants.RequiredComponentVersions.RequiredCommandLineToolsVersion, "bin", "lint.bat"), + Path.Combine (Constants.ComponentPaths.Tools, Constants.RequiredComponentVersions.RequiredCommandLineToolsVersion, "bin", "sdkmanager.bat"), + }; + break; + + default: + throw new InvalidOperationException ("Unsupported platform"); + } + + bool someFound = AndroidUtilities.CheckWheterFilesExist (sdkInstance.Path, "Android SDK Core", sdkExistenceCheckPaths, out double percentFound); + if (!someFound || percentFound < 100.0) { + if (someFound) + Logger.Info ("'Android SDK Core' has less than 100% of its files on disk. Update is necessary."); + } + } + + void IParserErrorHandler.OnRecoverableError (ParserErrorLevel recommendedLevel, Uri documentUrl, XElement element, string message) + { + switch (recommendedLevel) { + case ParserErrorLevel.Debug: + Logger.Debug (message); + break; + + case ParserErrorLevel.Info: + Logger.Info (message); + break; + + case ParserErrorLevel.Error: + case ParserErrorLevel.Warning: + Logger.Warning (message); + break; + + default: + goto case ParserErrorLevel.Info; + } + } + + void IParserErrorHandler.OnFatalError (Uri documentUrl, XElement element, string message) + { + Logger.Error (message); + } + + static void LogParserError(string whereItHappened, Exception e) + { + Logger.Error($"[{whereItHappened}]: Failure parsing Android manifest: {e.Message}"); + Logger.Error($"[{whereItHappened}]: This is usually due to bad XML in one or more of the SDK manifests."); + } + + /// + /// Returns if the license was accepted in this Android SDK + /// + /// Android SDK instance. + /// license to check + /// true if the license is accepted, false otherwise + public bool IsLicenseAccepted(AndroidSdkInstance instance, License license) + { + if (instance == null || license == null) + { + return false; + } + + return this.LicensesStorage.IsLicenseAccepted(instance.Path, license); + } + + /// + /// Accept licenses + /// + /// Android SDK instance. + /// list of licenses to accept + /// cancellation token + /// log file path + public Task AcceptLicensesAsync(AndroidSdkInstance instance, IEnumerable licenses, CancellationToken token, string javaSdkPath = null, string logPath = null, bool throwsErrorIfValidationFailed = false) + { + if (instance != null) { + var androidSdkPath = instance.Path; + var javaPath = string.IsNullOrEmpty(javaSdkPath) ? AndroidSdk.JavaSdkPath : javaSdkPath; + + // try use the preferred cmdline-tools + var cmdlineToolsRoot = Path.Combine(androidSdkPath, Constants.ComponentPaths.Tools); + var cmdLineToolsPath = Path.Combine(cmdlineToolsRoot, Constants.RequiredComponentVersions.RequiredCommandLineToolsVersion); + var cmdLineToolsPropertiesFilePath = Path.Combine(cmdLineToolsPath, "source.properties"); + if (!File.Exists(cmdLineToolsPropertiesFilePath)) + { + // try use the latest cmdline-tools installed + if (Directory.Exists(cmdlineToolsRoot)) + { + cmdLineToolsPath = Directory.EnumerateDirectories(cmdlineToolsRoot).OrderBy(d => Path.GetFileName(d)).LastOrDefault(); + } + else + { + cmdLineToolsPath = string.Empty; + } + } + + return LicensesStorage.AcceptLicensesAsync(androidSdkPath, javaPath, cmdLineToolsPath, licenses, token, logPath, throwsErrorIfValidationFailed); + } + + return Task.CompletedTask; + } + } +} + diff --git a/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/AndroidSDKPlatform.cs b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/AndroidSDKPlatform.cs new file mode 100644 index 00000000000..70ed0748ce8 --- /dev/null +++ b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/AndroidSDKPlatform.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Xamarin.Installer.AndroidSDK +{ + public enum AndroidSDKPlatform + { + Linux, + Mac, + Windows, + Any, + Unknown + } +} diff --git a/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/AndroidSdkInstance.cs b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/AndroidSdkInstance.cs new file mode 100644 index 00000000000..c6a503d4b8e --- /dev/null +++ b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/AndroidSdkInstance.cs @@ -0,0 +1,149 @@ +using System; +using System.Collections.Generic; +using Xamarin.Installer.AndroidSDK.Common; + +namespace Xamarin.Installer.AndroidSDK +{ + /// + /// Describes a single Android SDK instance on the user's machine + /// + public sealed class AndroidSdkInstance + { + ulong? downloadSize; + IList components; + + /// + /// Occurs when component status changes. + /// + public event EventHandler ComponentStatusChanged; + + /// + /// List of all the components that can be or are installed in this SDK instance. + /// The caller needs to determine whether or not they desire all of them to be actually installed. + /// + /// List of all SDK components for this instance + public IList Components { + get { return components; } + internal set { + components = value; + HookComponentEvents (components); + } + } + + /// + /// A convenience property to store only components selected for installation as opposed to + /// which has all the components loaded from the manifest. This + /// property is used by the unified installer. + /// + /// The components for installation. + public IList ComponentsForInstallation { get; set; } + + /// + /// Indicates whether this instance corresponds to the default Android SDK location + /// + /// true if this instance corresponds to the default Android SDK location; otherwise, false. + public bool IsDefault { get; internal set; } + + /// + /// Indicates whether this instance corresponds to the official Android SDK location. Official location is, in fact, found + /// only on Windows when the .exe Android SDK installer was used. + /// + /// true if this instance is official; otherwise, false. + public bool IsOfficial { get; internal set; } + + /// + /// Indicates whether this instance corresponds to a location specified by the user in the Unified Installer. + /// + /// true if this instance is user location; otherwise, false. + public bool IsUserLocation { get; set; } + + /// + /// Path to this instance's directory on disk. Note that the directory might not exist yet. + /// + /// Instance directory path. + public string Path { get; internal set; } + + /// + /// Indicates whether this instance has been selected as the one to install to. Used by the unified installer. + /// + /// true if selected; otherwise, false. + public bool Selected { get; set; } + + /// + /// Android SDK version in string (unparsed) form. It may include additional information in additon to the actual + /// revision/version number. + /// + /// The version string. + public string VersionString { get; internal set; } + + /// + /// Parsed Android SDK version. This is parsed from and includes only the numeric part. + /// + /// The version. + public AndroidRevision Version { get; internal set; } + + /// + /// Gets a value indicating whether this needs any updates. + /// + /// true if needs update; otherwise, false. + public bool NeedsUpdate => Components != null && Components.AnyOutdated (); + + internal AndroidSdkInstance () + {} + + /// + /// Computes the total download size of all components + /// passed in the parameter. If is false + /// (the default) a cached value will be returned for all calls following the first one. If + /// is null or empty, all the outdated components in this instance will be included in calculation. + /// + /// The total download size. + /// Components to install (optional) + /// Use cached value of the download size if true. + public ulong GetDownloadSize (IList componentsToInstall = null, bool refresh = false) + { + if (!refresh && downloadSize.HasValue) + return downloadSize.Value; + + if (componentsToInstall == null || componentsToInstall.Count == 0) + componentsToInstall = Components.AllOutdated (); + + downloadSize = 0; + if (componentsToInstall.Count == 0) + return 0; + + ulong ret = 0; + foreach (IAndroidComponent component in componentsToInstall) { + if (component == null) + continue; + foreach (Archive archive in component.Archives) { + if (archive == null) + continue; + ret += archive.Size; + } + }; + + downloadSize = ret; + return ret; + } + + void HookComponentEvents (IList components) + { + if (components == null || components.Count == 0) + return; + + foreach (IAndroidComponent c in components) { + c.StatusChanged -= OnStatusChanged; + c.StatusChanged += OnStatusChanged; + } + } + + void OnStatusChanged (object sender, AndroidComponentStatusChangeEventArgs args) + { + if (ComponentStatusChanged == null) + return; + + ComponentStatusChanged (this, args); + } + } +} diff --git a/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/AndroidSystemImageAbi.cs b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/AndroidSystemImageAbi.cs new file mode 100644 index 00000000000..16b8b641501 --- /dev/null +++ b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/AndroidSystemImageAbi.cs @@ -0,0 +1,41 @@ +using System; + +namespace Xamarin.Installer.AndroidSDK +{ + /// + /// Android SDK System Image ABI + /// + public enum AndroidSystemImageAbi + { + /// + /// System Image for the x86 architecture + /// + X86, + + /// + /// System Image for the x86_64 architecture + /// + X86_64, + + /// + /// System Image for the MIPS architecture + /// + Mips, + + /// + /// System Image for the arm-v7a architecture + /// + ARMV7a, + + /// + /// System Image for the arm64-v8a architecture + /// + ARM64V8a, + + /// + /// Represents any ABI + /// + Any = 1000 + } +} + diff --git a/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/AndroidUtilities.cs b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/AndroidUtilities.cs new file mode 100644 index 00000000000..3ed967cbc9d --- /dev/null +++ b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/AndroidUtilities.cs @@ -0,0 +1,412 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Text.RegularExpressions; +using System.Xml; + +using Kajabity.Tools.Java; +using Xamarin.Installer.AndroidSDK.Properties; +using Xamarin.Installer.Common; +using Xamarin.Installer.AndroidSDK.Common; +using System.Text; + +namespace Xamarin.Installer.AndroidSDK +{ + /// + /// A collection of Android SDK utilities + /// + public static class AndroidUtilities + { + const string PLACEHOLDER_DELIMITER = "|"; + + static readonly Regex ndkNameRegex = new Regex (@"android-ndk-r(?\d+)(?\w*)-\w*", RegexOptions.Compiled); + + /// + /// Parses a string with embedded placeholders to render a full directory path. Placeholders are + /// placed between pipe characters and their names come from members of the + /// enumeration. + /// + /// pattern to parse + /// + public static string ParseDirectoryWithSpecialFolderPlaceholders (string dirPattern) + { + dirPattern = dirPattern.SafeTrim (); + if (String.IsNullOrEmpty (dirPattern)) + return String.Empty; + + string ret = dirPattern; + + // A dumb way, but it's all we need + foreach (string name in Enum.GetNames (typeof (Environment.SpecialFolder))) { + string ph = PLACEHOLDER_DELIMITER + name + PLACEHOLDER_DELIMITER; + // We only replace the placeholder at the start of the value, since that's the only place where it makes sense + if (ret.IndexOf (ph, StringComparison.Ordinal) != 0) + continue; + ret = ret.Replace (ph, Environment.GetFolderPath ((Environment.SpecialFolder)Enum.Parse (typeof (Environment.SpecialFolder), name))); + } + + return ret; + } + + /// + /// Reads Java properties from the sepcified file + /// + /// path to the properties file + /// collection of properties, an instance of the class. + public static JavaProperties ReadAndroidProperties (string sourcePropertiesPath) + { + if (String.IsNullOrEmpty (sourcePropertiesPath) || !File.Exists (sourcePropertiesPath)) + return new JavaProperties (); + + Encoding.RegisterProvider (CodePagesEncodingProvider.Instance); + var ret = new JavaProperties (); + try { + using (var fs = File.Open (sourcePropertiesPath, FileMode.Open, FileAccess.Read, FileShare.Read)) { + ret.Load (fs); + } + } catch (Exception ex) { + Logger.Exception ("Failed to read Android properties from {0}", ex, sourcePropertiesPath); + throw new InvalidOperationException (Resources.FailedToLoadSourceProperties, ex); + } + + return ret; + } + + /// + /// Installs (unpacks) the specified Android SDK ZIP archive () in the directory + /// specified in the parameter. You can pass more path segments following the + /// parameter, they will be used to construct the final path rooted at + /// + /// name of the archive/component, for informational purposes + /// path to the ZIP archive + /// top directory in which to install the archive + /// optional path segments to append to to form the archive installation path + /// + public static bool InstallArchive (string name, string archivePath, string targetBasePath, params string[] targetPaths) + { + return InstallArchive (name, archivePath, false, targetBasePath, targetPaths); + } + + /// + /// Installs (unpacks) the specified Android SDK ZIP archive () in the directory + /// specified in the parameter. You can pass more path segments following the + /// parameter, they will be used to construct the final path rooted at + /// If is set to true, the archive is unzipped without first creating a backup of the possibly existing + /// target directory. This is used to install the main (distribution) archive of the Android SDK. + /// + /// name of the archive/component, for informational purposes + /// path to the ZIP archive + /// archive is the main/root archive of the Android SDK + /// top directory in which to install the archive + /// optional path segments to append to to form the archive installation path + /// + public static bool InstallArchive (string name, string archivePath, bool mainArchive, string targetBasePath, params string[] targetPaths) + { + string destDir; + return InstallArchive (out destDir, name, archivePath, mainArchive, false, targetBasePath, targetPaths); + } + + /// + /// Installs (unpacks) the specified Android SDK ZIP archive () in the directory + /// specified in the parameter. You can pass more path segments following the + /// parameter, they will be used to construct the final path rooted at + /// If is set to true, the archive is unzipped without first creating a backup of the possibly existing + /// target directory. This is used to install the main (distribution) archive of the Android SDK. + /// If is false then the method copies only the contents of the toplevel directory of the archive, instead of the + /// entire contents of the archive. This is used with Android SDK archives who contain a single root directory and their target directory on disk is named differently. + /// + /// set to the destination directory on exit + /// name of the archive/component, for informational purposes + /// path to the ZIP archive + /// archive is the main/root archive of the Android SDK + /// include the archive's top-level directory in the copy if true, copy just its contents otherwise + /// top directory in which to install the archive + /// optional path segments to append to to form the archive installation path + /// true if copy was successful, false otherwise + public static bool InstallArchive (out string destinationDirectory, string name, string archivePath, bool mainArchive, bool includeZipRootInCopy, string targetBasePath, params string[] targetPaths) + { + destinationDirectory = null; + Logger.Info ("Installing Android archive '{0}'", name); + if (String.IsNullOrEmpty ("archivePath")) + throw new ArgumentNullException ("archivePath"); + if (String.IsNullOrEmpty (targetBasePath)) + throw new ArgumentNullException ("targetBasePath"); + + var rnd = new Random (); + string componentUnzippedPath = null; + + // A small race here but I think we can accept the risks :) + string temporaryPath = Path.Combine (Path.GetTempPath (), Path.GetRandomFileName ()); + try { + componentUnzippedPath = CommonUtilities.Helpers.Unzip (temporaryPath, archivePath, AndroidSDKContext.Instance.UserName).RemoveTrailingDirectorySeparator (); + } catch (Exception ex) { + Logger.Exception (String.Format ("Exception caught while unzipping '{0}' in '{1}'", archivePath, temporaryPath), ex); + componentUnzippedPath = null; + } + + if (String.IsNullOrEmpty (componentUnzippedPath)) { + Logger.SetOperationStatus (OperationStatus.Failure); + Logger.Error ("Attempt to unzip Android archive '{0}' found in {1} failed.", name, archivePath); + return false; + } + + Logger.Debug ("Android archive '{0}' unpacked in directory '{1}'", name, componentUnzippedPath); + destinationDirectory = CombinePaths (targetBasePath, targetPaths); + if (includeZipRootInCopy) + destinationDirectory = Path.Combine (destinationDirectory, Path.GetFileName (componentUnzippedPath)); + Logger.Debug ("Archive's destination path: {0}", destinationDirectory); + string oldDestinationPath = null; + + if (!mainArchive && Directory.Exists (destinationDirectory)) { + oldDestinationPath = destinationDirectory + ".old" + rnd.Next (Int32.MaxValue); + Logger.Info ("Moving old directory '{0}' to '{1}'", destinationDirectory, oldDestinationPath); + CommonUtilities.MoveDirectory (destinationDirectory, oldDestinationPath); + } + + bool ret = true; + try { + int idx = destinationDirectory.LastIndexOf (Path.DirectorySeparatorChar); + string leadingPath; + if (idx > 1) + leadingPath = destinationDirectory.Substring (0, idx); + else + leadingPath = destinationDirectory; + + if (!Directory.Exists (leadingPath)) { + Logger.Info ("Creating Android component's parent path '{0}'", leadingPath); + Directory.CreateDirectory (leadingPath); + } + Logger.Info ("Moving component to its destination path."); + + CommonUtilities.MoveDirectory (componentUnzippedPath, destinationDirectory, overwrite: true); + } catch (Exception e) { + ret = false; + Logger.Exception ("Failure to move component to its destination path. Exception was thrown.", e); + if (!String.IsNullOrEmpty (oldDestinationPath) && Directory.Exists (oldDestinationPath)) { + Logger.Error ("Attempting to clean up."); + Logger.Error ("Trying to remove directory '{0}'", destinationDirectory); + CommonUtilities.DeleteDirectoryRecursively (destinationDirectory); + + try { + CommonUtilities.MoveDirectory (oldDestinationPath, destinationDirectory); + } catch (Exception ex) { + // ignore + Logger.Exception ("Failed to rename directory '{0}' to '{1}'", ex, oldDestinationPath, destinationDirectory); + } + } + } + + bool logError = false; + Exception rex = null; + try { + CommonUtilities.DeleteDirectoryRecursively (temporaryPath); + if (!String.IsNullOrEmpty (oldDestinationPath)) + logError = !CommonUtilities.DeleteDirectoryRecursively (oldDestinationPath); + } catch (Exception e) { + logError = true; + ret = false; + rex = e; + // ignore + } + if (logError) { + string message = String.Format ("Failed to remove old destination directory '{0}'. User will have to remove it manually.", oldDestinationPath); + if (rex != null) + Logger.Exception (message, rex); + else + Logger.Error (message); + } + + return ret; + } + + internal static string CombinePaths (string targetBasePath, params string[] targetPaths) + { + if (targetPaths == null || targetPaths.Length == 0) + return targetBasePath; + else { + var allPaths = new List (); + allPaths.Add (targetBasePath); + allPaths.AddRange (targetPaths); + return Path.Combine (allPaths.ToArray ()); + } + } + + internal static bool ParsePlatformVersion (string source, out Version version, out string versionTag) + { + version = null; + versionTag = String.Empty; + + source = (source ?? String.Empty).Trim (); + if (String.IsNullOrEmpty (source)) + return false; + + try { + if (source.Length >= 3) { // The minimum valid version is X.Y + version = Version.Parse (source); + return true; + } + } catch (FormatException) { + // ignore - google introduced version numbers with letters in them, we deal with it below + } + + string validVersion = null; + for (int i = 0; i < source.Length; i++) { + char ch = source[i]; + if (ch == '.' || Char.IsDigit (ch)) + continue; + + validVersion = source.Substring (0, i); + versionTag = source.Substring (i); + } + + if (!String.IsNullOrEmpty (validVersion)) + version = Version.Parse (validVersion); + + return true; + } + + public static string GetPlatformOS () + { + return PlatformToString (AndroidSDKContext.Instance.Platform); + } + + public static string PlatformToString (AndroidSDKPlatform platform) + { + switch (platform) { + case AndroidSDKPlatform.Linux: + return "linux"; + + case AndroidSDKPlatform.Mac: + return "macosx"; + + case AndroidSDKPlatform.Windows: + return "windows"; + + case AndroidSDKPlatform.Unknown: + case AndroidSDKPlatform.Any: + return String.Empty; + + default: + throw new InvalidOperationException ($"Unknown SDK platform: {platform}"); + } + } + + internal static AndroidSDKPlatform GetPlatformFromOS (string osName) + { + osName = osName?.Trim (); + if (String.IsNullOrEmpty (osName)) // Some archives aren't OS-specific + return AndroidSDKPlatform.Any; + + if (String.Compare (osName, "linux", StringComparison.OrdinalIgnoreCase) == 0) + return AndroidSDKPlatform.Linux; + + if (String.Compare (osName, "macosx", StringComparison.OrdinalIgnoreCase) == 0) + return AndroidSDKPlatform.Mac; + + if (String.Compare (osName, "windows", StringComparison.OrdinalIgnoreCase) == 0) + return AndroidSDKPlatform.Windows; + + return AndroidSDKPlatform.Unknown; + } + + internal static bool CheckWheterFilesExist (string root, string ownerName, List paths, out double percentFound) + { + percentFound = 0; + int found = 0; + if (paths == null || paths.Count == 0) + return false; + + foreach (string p in paths) { + string path = p.SafeTrim (); + if (String.IsNullOrEmpty (path)) + continue; + path = Path.Combine (root, path); + if (File.Exists (path)) + found++; + else + Logger.Info ("File '{0}' not found for '{1}'", path, ownerName); + } + + if (found == 0) + return false; + + percentFound = ((double)found * 100.0) / (double)paths.Count; + + return true; + } + + internal static AndroidSystemImageAbi StringToAbi (string abi) + { + abi = abi.SafeTrim (); + if (String.IsNullOrEmpty (abi)) + return AndroidComponentInfoSystemImage.DefaultAbi; + + if (String.Compare ("arm64-v8a", abi, StringComparison.OrdinalIgnoreCase) == 0) + return AndroidSystemImageAbi.ARM64V8a; + + if (String.Compare ("armeabi-v7a", abi, StringComparison.OrdinalIgnoreCase) == 0) + return AndroidSystemImageAbi.ARMV7a; + + if (String.Compare ("mips", abi, StringComparison.OrdinalIgnoreCase) == 0) + return AndroidSystemImageAbi.Mips; + + if (String.Compare ("x86", abi, StringComparison.OrdinalIgnoreCase) == 0) + return AndroidSystemImageAbi.X86; + + if (String.Compare ("x86_64", abi, StringComparison.OrdinalIgnoreCase) == 0) + return AndroidSystemImageAbi.X86_64; + + throw new ArgumentOutOfRangeException (String.Format ("Unknown ABI '{0}'", abi)); + } + + /// + /// Utility method to work around Google's decision not to include NDK version in the Android SDK repository manifest v11. Parsing is + /// performed with the assumption that the NDK archive name follows a certain convention that matches the + /// android-ndk-r(?<ver>\d+)(?<tag>\w*)-\w* regex. + /// + /// manifest XML element for the NDK node + /// namespace manager initialized for the repository manifest + /// Base URL to use when the NDK element has only relative URLs + /// receives the NDK release, if parse is successful (just the number) + /// receives the NDK release tag, if parse is successful (just the trailing non-digit characters) + public static void ParseNDKVersionFromGoogleManifest (XmlElement element, XmlNamespaceManager nsmgr, Uri baseURL, out string release, out string releaseTag) + { + // Conveniently, google don't include NDK version in the manifest, why bother? We'll need to try to parse + // the URL for name... + XmlNodeList urls = element.SelectNodes ("sdk:archives/sdk:archive/sdk:url", nsmgr); + if (urls == null || urls.Count == 0) + throw new InvalidOperationException ("Android SDK manifest has NDK element without URLs, unable to determine NDK version"); + + Uri url = null; + foreach (XmlNode n in urls) { + string uri; + var urlElement = n as XmlElement; + if (n == null) + continue; + uri = urlElement.InnerXml.SafeTrim (); + if (String.IsNullOrEmpty (uri)) + continue; + if (!Uri.TryCreate (uri, UriKind.RelativeOrAbsolute, out url)) + continue; + break; + } + + if (url == null) + throw new InvalidOperationException ("Android SDK manifest has NDK element without valid URLs, unable to determine NDK version"); + + if (!url.IsAbsoluteUri) + url = new Uri (baseURL, url); + + string ndkName = Path.GetFileName (url.LocalPath).SafeTrim (); + if (String.IsNullOrEmpty (ndkName)) + throw new InvalidOperationException ("Android SDK has NDK element without valid archive name in the URL, unable to determine NDK version"); + + Match match = ndkNameRegex.Match (ndkName); + if (!match.Success) + throw new InvalidOperationException ("Android SDK has NDK element with URLs in unsupported format, unable to determine NDK version"); + + release = match.Groups["ver"].Value; + releaseTag = match.Groups["tag"].Value; + } + } +} diff --git a/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/Common/AndroidComponentInfo.cs b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/Common/AndroidComponentInfo.cs new file mode 100644 index 00000000000..0ee9807b119 --- /dev/null +++ b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/Common/AndroidComponentInfo.cs @@ -0,0 +1,156 @@ +// +// Authors: +// Marek Habersack +// +// Copyright (c) 2017, Microsoft Corp. (http://microsoft.com) +// +// All rights reserved. +// +using System; +using System.Xml; + +namespace Xamarin.Installer.AndroidSDK.Common +{ + /// + /// Base class for all the types containing detailed information about specific component groups/kinds. + /// + public abstract class AndroidComponentInfo : IEquatable + { + /// + /// Gets the component type name. The name corresponds to the Google SDK remote package type, not the .NET one. + /// + /// Type name + public string Type { get; } + + /// + /// Gets the printable detailed description of the package type. + /// + /// Detailed type description + public string DetailedDescription => GetDetailedDescription (); + + /// + /// Initializes a new instance of the class. + /// + /// Type name (required to be not null and not empty). + protected AndroidComponentInfo (string type) + { + if (String.IsNullOrEmpty (type)) + throw new ArgumentException ("must not be null or empty", nameof (type)); + + Type = type; + } + + /// + /// Builds and returns the detailed description of the component type. + /// + /// Detailed type description + protected abstract string GetDetailedDescription (); + + internal void WritePackageXmlInfo (XmlWriter writer, Repository repository) + { + writer.WriteStartElement ("type-details"); + writer.WriteAttributeString ("xmlns", "xsi", null, repository.GetNamespaceUri ("xsi")); + writer.WriteAttributeString ("xsi", "type", null, Type); + + WritePackageXmlInfoDetails (writer, repository); + + writer.WriteEndElement (); // type-details + } + + /// + /// Writes XML representation of the package vendor data. + /// + /// XML writer + /// Vendor data + protected void WritePackageXmlVendor (XmlWriter writer, PackageVendor vendor) + { + writer.WriteStartElement ("vendor"); + writer.WriteStartElement ("id"); + writer.WriteString (vendor?.ID ?? String.Empty); + writer.WriteEndElement (); // id + + writer.WriteStartElement ("display"); + writer.WriteString (vendor?.Display ?? String.Empty); + writer.WriteEndElement (); // display + writer.WriteEndElement (); // vendor + } + + /// + /// Writes XML representation of the package tag data. + /// + /// XML Writer + /// Tag data + protected void WritePackageXmlTag (XmlWriter writer, PackageTag tag) + { + writer.WriteStartElement ("tag"); + writer.WriteStartElement ("id"); + writer.WriteString (tag?.ID ?? String.Empty); + writer.WriteEndElement (); // id + + writer.WriteStartElement ("display"); + writer.WriteString (tag?.Display ?? String.Empty); + writer.WriteEndElement (); // display + writer.WriteEndElement (); // tag + } + + internal virtual void WritePackageXmlInfoDetails (XmlWriter writer, Repository repository) + { + } + + /// + /// Determines whether the specified is equal + /// to the current . + /// + /// The to compare with the current . + /// true if the specified is equal to + /// the current ; otherwise, false. + public bool Equals (AndroidComponentInfo other) + { + if (other == null) + return false; + + if (ReferenceEquals (this, other)) + return true; + + return String.Compare (Type, other.Type, StringComparison.Ordinal) == 0; + } + + /// + /// Determines whether the specified is equal to the current . + /// + /// The to compare with the current . + /// true if the specified is equal to the current + /// ; otherwise, false. + public override bool Equals (object obj) + { + return Equals (obj as AndroidComponentInfo); + } + + /// + /// Serves as a hash function for a object. + /// + /// A hash code for this instance that is suitable for use in hashing algorithms and data structures such as a hash table. + public override int GetHashCode () + { + int hashCode = base.GetHashCode (); + if (Type == null) + return hashCode; + + return hashCode.XorWith (Type.GetHashCode ()); + } + + /// + /// A helper to compare two strings. + /// + /// true, if strings are equal, false otherwise. + /// first string + /// second string + protected bool AreEqual (string one, string two) + { + if (one == null) + return two == null; + + return String.Compare (one, two, StringComparison.Ordinal) == 0; + } + } +} diff --git a/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/Common/AndroidComponentInfoAddon.cs b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/Common/AndroidComponentInfoAddon.cs new file mode 100644 index 00000000000..4279feef629 --- /dev/null +++ b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/Common/AndroidComponentInfoAddon.cs @@ -0,0 +1,179 @@ +// +// Authors: +// Marek Habersack +// +// Copyright (c) 2017, Microsoft Corp. (http://microsoft.com) +// +// All rights reserved. +// +using System; +using System.Collections.Generic; +using System.Xml; + +namespace Xamarin.Installer.AndroidSDK.Common +{ + /// + /// Information about the addon Android component + /// + public class AndroidComponentInfoAddon : AndroidComponentInfo, IAndroidApiLevel, IEquatable + { + string detailedDescription; + + /// + /// Gets the API level. + /// + /// The API level. + public string ApiLevel { get; } + + /// + /// Gets the platform source code name. + /// + /// Platfor code name + public string CodeName { get; } + + /// + /// Gets the tag associated with the system image + /// + /// Package tag + public PackageTag Tag { get; } + + /// + /// Gets system image vendor information + /// + /// System image vendor + public PackageVendor Vendor { get; } + + /// + /// List of libraries shipped with this addon + /// + /// Libraries shipped with this addon + public IList Libraries { get; } + + internal AndroidComponentInfoAddon(string type, string apiLevel, string codeName, PackageTag tag, PackageVendor vendor, IList libraries) : base (type) + { + if (String.IsNullOrEmpty (apiLevel)) + throw new ArgumentException ("must not be null or empty", nameof (apiLevel)); + + ApiLevel = apiLevel; + CodeName = codeName; + Tag = tag; + Vendor = vendor; + Libraries = libraries; + } + + internal override void WritePackageXmlInfoDetails (XmlWriter writer, Repository repository) + { + writer.WriteStartElement ("api-level"); + writer.WriteString (ApiLevel ?? String.Empty); + writer.WriteEndElement (); // api-level + + writer.WriteStartElement ("codename"); + writer.WriteString (CodeName ?? String.Empty); + writer.WriteEndElement (); // codename + + WritePackageXmlVendor (writer, Vendor); + WritePackageXmlTag (writer, Tag); + + writer.WriteStartElement ("libraries"); + if (Libraries != null && Libraries.Count > 0) { + foreach (PackageLibrary lib in Libraries) { + if (lib == null) + continue; + writer.WriteStartElement ("library"); + writer.WriteAttributeString ("localJarPath", lib?.LocalJarPath ?? String.Empty); + writer.WriteAttributeString ("name", lib?.Name ?? String.Empty); + writer.WriteStartElement ("description"); + writer.WriteString (lib?.Description ?? String.Empty); + writer.WriteEndElement (); // description + writer.WriteEndElement (); // library + } + } + writer.WriteEndElement (); // libraries + } + + /// + /// Gets the detailed description of the component + /// + /// Description + protected override string GetDetailedDescription () + { + if (detailedDescription != null) + return detailedDescription; + + var desc = new List (); + if (!String.IsNullOrEmpty (ApiLevel)) + desc.Add ($"API {ApiLevel}"); + if (!String.IsNullOrEmpty (CodeName)) + desc.Add ($"\"{CodeName}\""); + if (!String.IsNullOrEmpty (Vendor?.Display)) + desc.Add ($"({Vendor.Display})"); + + if (desc.Count > 0) + detailedDescription = $"Addon: {String.Join (" ", desc)}"; + else + detailedDescription = String.Empty; + return detailedDescription; + } + + /// + /// Determines whether the specified is + /// equal to the current . + /// + /// The to compare with the current . + /// true if the specified is equal + /// to the current ; otherwise, false. + public bool Equals (AndroidComponentInfoAddon other) + { + if (!Equals ((AndroidComponentInfo)other)) + return false; + + if (!AreEqual (ApiLevel, other.ApiLevel)) + return false; + + if (Tag != other.Tag) + return false; + + if (Vendor != other.Vendor) + return false; + + if (!AreEqual (CodeName, other.CodeName)) + return false; + + return Libraries.AreEqual (other.Libraries); + } + + /// + /// Determines whether the specified is equal to the current . + /// + /// The to compare with the current . + /// true if the specified is equal to the current + /// ; otherwise, false. + public override bool Equals (object obj) + { + return Equals (obj as AndroidComponentInfoAddon); + } + + /// + /// Serves as a hash function for a object. + /// + /// A hash code for this instance that is suitable for use in hashing algorithms and data structures such as a hash table. + public override int GetHashCode () + { + int hashCode = base.GetHashCode (); + + if (ApiLevel != null) + hashCode = hashCode.XorWith (ApiLevel.GetHashCode ()); + + if (CodeName != null) + hashCode = hashCode.XorWith (CodeName.GetHashCode ()); + + if (Tag != null) + hashCode = hashCode.XorWith (Tag.GetHashCode ()); + + if (Vendor != null) + hashCode = hashCode.XorWith (Vendor.GetHashCode ()); + + return hashCode; + } + } +} diff --git a/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/Common/AndroidComponentInfoExtra.cs b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/Common/AndroidComponentInfoExtra.cs new file mode 100644 index 00000000000..3e225819f12 --- /dev/null +++ b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/Common/AndroidComponentInfoExtra.cs @@ -0,0 +1,100 @@ +// +// Authors: +// Marek Habersack +// +// Copyright (c) 2017, Microsoft Corp. (http://microsoft.com) +// +// All rights reserved. +// +using System; +using System.Collections.Generic; +using System.Xml; + +namespace Xamarin.Installer.AndroidSDK.Common +{ + /// + /// Information about the extra Android component + /// + public class AndroidComponentInfoExtra : AndroidComponentInfo, IEquatable + { + string detailedDescription; + + /// + /// Gets system image vendor information + /// + /// System image vendor + public PackageVendor Vendor { get; } + + internal AndroidComponentInfoExtra (string type, PackageVendor vendor) : base (type) + { + Vendor = vendor; + } + + internal override void WritePackageXmlInfoDetails (XmlWriter writer, Repository repository) + { + WritePackageXmlVendor (writer, Vendor); + } + + /// + /// Gets the detailed description of the component + /// + /// Description + protected override string GetDetailedDescription () + { + if (detailedDescription != null) + return detailedDescription; + + var desc = new List (); + if (!String.IsNullOrEmpty (Vendor?.Display)) + desc.Add ($"({Vendor.Display})"); + + if (desc.Count > 0) + detailedDescription = $"Extra: {String.Join (" ", desc)}"; + else + detailedDescription = String.Empty; + return detailedDescription; + } + + /// + /// Determines whether the specified is + /// equal to the current . + /// + /// The to compare with the current . + /// true if the specified is equal + /// to the current ; otherwise, false. + public bool Equals (AndroidComponentInfoExtra other) + { + if (!Equals ((AndroidComponentInfo)other)) + return false; + + if (Vendor != other.Vendor) + return false; + return true; + } + + /// + /// Determines whether the specified is equal to the current . + /// + /// The to compare with the current . + /// true if the specified is equal to the current + /// ; otherwise, false. + public override bool Equals (object obj) + { + return Equals (obj as AndroidComponentInfoExtra); + } + + /// + /// Serves as a hash function for a object. + /// + /// A hash code for this instance that is suitable for use in hashing algorithms and data structures such as a hash table. + public override int GetHashCode () + { + int hashCode = base.GetHashCode (); + + if (Vendor != null) + hashCode = hashCode.XorWith (Vendor.GetHashCode ()); + + return hashCode; + } + } +} diff --git a/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/Common/AndroidComponentInfoGeneric.cs b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/Common/AndroidComponentInfoGeneric.cs new file mode 100644 index 00000000000..71e3debcda3 --- /dev/null +++ b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/Common/AndroidComponentInfoGeneric.cs @@ -0,0 +1,56 @@ +// +// Authors: +// Marek Habersack +// +// Copyright (c) 2017, Microsoft Corp. (http://microsoft.com) +// +// All rights reserved. +// +using System; + +namespace Xamarin.Installer.AndroidSDK.Common +{ + /// + /// Information about a generic component. + /// +#pragma warning disable CS0659 // Type overrides Object.Equals(object o) but does not override Object.GetHashCode() + public class AndroidComponentInfoGeneric : AndroidComponentInfo, IEquatable +#pragma warning restore CS0659 // Type overrides Object.Equals(object o) but does not override Object.GetHashCode() + { + public AndroidComponentInfoGeneric (string type) : base (type) + {} + + /// + /// Builds and returns the detailed description of the component type. . + /// This override returns an empty string. + /// + /// Detailed type description + protected override string GetDetailedDescription () + { + return String.Empty; + } + + /// + /// Determines whether the specified is + /// equal to the current . + /// + /// The to compare with the current . + /// true if the specified is + /// equal to the current ; otherwise, false. + public bool Equals (AndroidComponentInfoGeneric other) + { + return Equals ((AndroidComponentInfo)other); + } + + /// + /// Determines whether the specified is equal to the current . + /// + /// The to compare with the current . + /// true if the specified is equal to the current + /// ; otherwise, false. + public override bool Equals (object obj) + { + return Equals (obj as AndroidComponentInfoGeneric); + } + } +} diff --git a/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/Common/AndroidComponentInfoMaven.cs b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/Common/AndroidComponentInfoMaven.cs new file mode 100644 index 00000000000..66e114dc684 --- /dev/null +++ b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/Common/AndroidComponentInfoMaven.cs @@ -0,0 +1,100 @@ +// +// Authors: +// Marek Habersack +// +// Copyright (c) 2017, Microsoft Corp. (http://microsoft.com) +// +// All rights reserved. +// +using System; +using System.Collections.Generic; +using System.Xml; + +namespace Xamarin.Installer.AndroidSDK.Common +{ + /// + /// Information about the Maven Android component + /// + public class AndroidComponentInfoMaven : AndroidComponentInfo, IEquatable + { + string detailedDescription; + + /// + /// Gets system image vendor information + /// + /// System image vendor + public PackageVendor Vendor { get; } + + internal AndroidComponentInfoMaven (string type, PackageVendor vendor) : base (type) + { + Vendor = vendor; + } + + internal override void WritePackageXmlInfoDetails (XmlWriter writer, Repository repository) + { + WritePackageXmlVendor (writer, Vendor); + } + + /// + /// Gets the detailed description of the component + /// + /// Description + protected override string GetDetailedDescription () + { + if (detailedDescription != null) + return detailedDescription; + + var desc = new List (); + if (!String.IsNullOrEmpty (Vendor?.Display)) + desc.Add ($"({Vendor.Display})"); + + if (desc.Count > 0) + detailedDescription = $"Maven: {String.Join (" ", desc)}"; + else + detailedDescription = String.Empty; + return detailedDescription; + } + + /// + /// Determines whether the specified is + /// equal to the current . + /// + /// The to compare with the current . + /// true if the specified is equal + /// to the current ; otherwise, false. + public bool Equals (AndroidComponentInfoMaven other) + { + if (!Equals ((AndroidComponentInfo)other)) + return false; + + if (Vendor != other.Vendor) + return false; + return true; + } + + /// + /// Determines whether the specified is equal to the current . + /// + /// The to compare with the current . + /// true if the specified is equal to the current + /// ; otherwise, false. + public override bool Equals (object obj) + { + return Equals (obj as AndroidComponentInfoMaven); + } + + /// + /// Serves as a hash function for a object. + /// + /// A hash code for this instance that is suitable for use in hashing algorithms and data structures such as a hash table. + public override int GetHashCode () + { + int hashCode = base.GetHashCode (); + + if (Vendor != null) + hashCode = hashCode.XorWith (Vendor.GetHashCode ()); + + return hashCode; + } + } +} diff --git a/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/Common/AndroidComponentInfoPlatform.cs b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/Common/AndroidComponentInfoPlatform.cs new file mode 100644 index 00000000000..c97d8c889fe --- /dev/null +++ b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/Common/AndroidComponentInfoPlatform.cs @@ -0,0 +1,162 @@ +// +// Authors: +// Marek Habersack +// +// Copyright (c) 2017, Microsoft Corp. (http://microsoft.com) +// +// All rights reserved. +// +using System; +using System.Collections.Generic; +using System.Xml; + +namespace Xamarin.Installer.AndroidSDK.Common +{ + /// + /// Information about the API level (platform) component + /// + public class AndroidComponentInfoPlatform : AndroidComponentInfo, IAndroidApiLevel,IEquatable + { + string detailedDescription; + + /// + /// Gets the API level + /// + /// The API level. + public string ApiLevel { get; } + + /// + /// Gets the platform code name. + /// + /// Platfor code name + public string CodeName { get; } + + /// + /// Gets the layout library API version + /// + /// Layout library API version + public string LayoutLibApi { get; } + + /// + /// Description of the API level. + /// + /// Platform description. + public string Description { get; internal set; } + + /// + /// Is the Platform API level a preview? + /// + /// if the Platform is not API stable. + public bool Preview { get; internal set; } + + internal AndroidComponentInfoPlatform (string type, string apiLevel, string codeName, string layoutLibApi) : base (type) + { + if (String.IsNullOrEmpty (apiLevel)) + throw new ArgumentException ("must not be null or empty", nameof (apiLevel)); + if (String.IsNullOrEmpty (layoutLibApi)) + throw new ArgumentException ("must not be null or empty", nameof (layoutLibApi)); + + ApiLevel = apiLevel; + CodeName = codeName; + LayoutLibApi = layoutLibApi; + } + + internal override void WritePackageXmlInfoDetails (XmlWriter writer, Repository repository) + { + writer.WriteStartElement ("api-level"); + writer.WriteString (ApiLevel ?? String.Empty); + writer.WriteEndElement (); // api-level + + writer.WriteStartElement ("codename"); + writer.WriteString (CodeName ?? String.Empty); + writer.WriteEndElement (); // codename + + writer.WriteStartElement ("preview"); + writer.WriteString (Preview.ToString ()); + writer.WriteEndElement (); // preview + + writer.WriteStartElement ("layoutlib"); + writer.WriteAttributeString ("api", ApiLevel ?? String.Empty); + writer.WriteEndElement (); // layoutlib + } + + /// + /// Gets the detailed description of the component + /// + /// Description + protected override string GetDetailedDescription () + { + if (detailedDescription != null) + return detailedDescription; + + var desc = new List (); + if (!String.IsNullOrEmpty (ApiLevel)) + desc.Add ($"API {ApiLevel}"); + if (!String.IsNullOrEmpty (CodeName)) + desc.Add ($"\"{CodeName}\""); + + if (desc.Count > 0) + detailedDescription = $"Platform: {String.Join (" ", desc)}"; + else + detailedDescription = String.Empty; + return detailedDescription; + } + + /// + /// Determines whether the specified is + /// equal to the current . + /// + /// The to compare with the current . + /// true if the specified is + /// equal to the current ; otherwise, false. + public bool Equals (AndroidComponentInfoPlatform other) + { + if (!Equals ((AndroidComponentInfo)other)) + return false; + + if (!AreEqual (ApiLevel, other.ApiLevel)) + return false; + + if (!AreEqual (CodeName, other.CodeName)) + return false; + + if (Preview != other.Preview) + return false; + + return AreEqual (LayoutLibApi, other.LayoutLibApi); + } + + /// + /// Determines whether the specified is equal to the current . + /// + /// The to compare with the current . + /// true if the specified is equal to the current + /// ; otherwise, false. + public override bool Equals (object obj) + { + return Equals (obj as AndroidComponentInfoPlatform); + } + + /// + /// Serves as a hash function for a object. + /// + /// A hash code for this instance that is suitable for use in hashing algorithms and data structures such as a hash table. + public override int GetHashCode () + { + int hashCode = base.GetHashCode (); + + if (ApiLevel != null) + hashCode = hashCode.XorWith (ApiLevel.GetHashCode ()); + + if (CodeName != null) + hashCode = hashCode.XorWith (CodeName.GetHashCode ()); + + hashCode = hashCode.XorWith (Preview.GetHashCode ()); + + if (LayoutLibApi != null) + hashCode = hashCode.XorWith (LayoutLibApi.GetHashCode ()); + + return hashCode; + } + } +} diff --git a/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/Common/AndroidComponentInfoSource.cs b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/Common/AndroidComponentInfoSource.cs new file mode 100644 index 00000000000..6fa54158b01 --- /dev/null +++ b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/Common/AndroidComponentInfoSource.cs @@ -0,0 +1,122 @@ +// +// Authors: +// Marek Habersack +// +// Copyright (c) 2017, Microsoft Corp. (http://microsoft.com) +// +// All rights reserved. +// +using System; +using System.Collections.Generic; +using System.Xml; + +namespace Xamarin.Installer.AndroidSDK.Common +{ + /// + /// Information about the platform source code Android component + /// + public class AndroidComponentInfoSource : AndroidComponentInfo, IAndroidApiLevel, IEquatable + { + string detailedDescription; + + /// + /// Gets the API level + /// + /// The API level. + public string ApiLevel { get; } + + /// + /// Gets the platform source code name. + /// + /// Platfor code name + public string CodeName { get; } + + internal AndroidComponentInfoSource (string type, string apiLevel, string codeName) : base (type) + { + if (String.IsNullOrEmpty (apiLevel)) + throw new ArgumentException ("must not be null or empty", nameof (apiLevel)); + + ApiLevel = apiLevel; + CodeName = codeName; + } + + internal override void WritePackageXmlInfoDetails (XmlWriter writer, Repository repository) + { + writer.WriteStartElement ("api-level"); + writer.WriteString (ApiLevel ?? String.Empty); + writer.WriteEndElement (); // api-level + + writer.WriteStartElement ("codename"); + writer.WriteString (CodeName ?? String.Empty); + writer.WriteEndElement (); // codename + } + + /// + /// Gets the detailed description of the component + /// + /// Description + protected override string GetDetailedDescription () + { + if (detailedDescription != null) + return detailedDescription; + + var desc = new List (); + if (!String.IsNullOrEmpty (ApiLevel)) + desc.Add ($"API {ApiLevel}"); + if (!String.IsNullOrEmpty (CodeName)) + desc.Add ($"\"{CodeName}\""); + + if (desc.Count > 0) + detailedDescription = $"Platform Source: {String.Join (" ", desc)}"; + else + detailedDescription = String.Empty; + return detailedDescription; + } + + /// + /// Determines whether the specified is + /// equal to the current . + /// + /// The to compare with the current . + /// true if the specified is equal + /// to the current ; otherwise, false. + public bool Equals (AndroidComponentInfoSource other) + { + if (!Equals ((AndroidComponentInfo)other)) + return false; + + if (!AreEqual (ApiLevel, other.ApiLevel)) + return false; + + return AreEqual (CodeName, other.CodeName); + } + + /// + /// Determines whether the specified is equal to the current . + /// + /// The to compare with the current . + /// true if the specified is equal to the current + /// ; otherwise, false. + public override bool Equals (object obj) + { + return Equals (obj as AndroidComponentInfoSource); + } + + /// + /// Serves as a hash function for a object. + /// + /// A hash code for this instance that is suitable for use in hashing algorithms and data structures such as a hash table. + public override int GetHashCode () + { + int hashCode = base.GetHashCode (); + + if (ApiLevel != null) + hashCode = hashCode.XorWith (ApiLevel.GetHashCode ()); + + if (CodeName != null) + hashCode = hashCode.XorWith (CodeName.GetHashCode ()); + + return hashCode; + } + } +} diff --git a/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/Common/AndroidComponentInfoSystemImage.cs b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/Common/AndroidComponentInfoSystemImage.cs new file mode 100644 index 00000000000..9b593e82640 --- /dev/null +++ b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/Common/AndroidComponentInfoSystemImage.cs @@ -0,0 +1,187 @@ +// +// Authors: +// Marek Habersack +// +// Copyright (c) 2017, Microsoft Corp. (http://microsoft.com) +// +// All rights reserved. +// +using System; +using System.Collections.Generic; +using System.Xml; + +namespace Xamarin.Installer.AndroidSDK.Common +{ + /// + /// Information about the system image Android component + /// + public class AndroidComponentInfoSystemImage : AndroidComponentInfo, IAndroidApiLevel, IEquatable + { + /// + /// The default architecture + /// + public static readonly AndroidSystemImageAbi DefaultAbi = AndroidSystemImageAbi.X86; + + string detailedDescription; + + /// + /// System image ABI + /// + /// System image ABI + public AndroidSystemImageAbi Abi { get; } + + /// + /// Gets the textual version of the system image ABI + /// + /// Name of the ABI + public string AbiName { get; } + + /// + /// Gets the API level. + /// + /// The API level. + public string ApiLevel { get; } + + /// + /// Gets the platform source code name. + /// + /// Platfor code name + public string CodeName { get; } + + /// + /// Gets the tag associated with the system image + /// + /// Package tag + public PackageTag Tag { get; } + + /// + /// Gets system image vendor information + /// + /// System image vendor + public PackageVendor Vendor { get; } + + internal AndroidComponentInfoSystemImage (string type, AndroidSystemImageAbi abi, string abiName, string apiLevel, string codeName, PackageTag tag, PackageVendor vendor) : base (type) + { + if (String.IsNullOrEmpty (apiLevel)) + throw new ArgumentException ("must not be null or empty", nameof (apiLevel)); + + Abi = abi; + AbiName = abiName; + ApiLevel = apiLevel; + CodeName = codeName; + Tag = tag; + Vendor = vendor; + } + + internal override void WritePackageXmlInfoDetails (XmlWriter writer, Repository repository) + { + writer.WriteStartElement ("api-level"); + writer.WriteString (ApiLevel ?? String.Empty); + writer.WriteEndElement (); // api-level + + writer.WriteStartElement ("codename"); + writer.WriteString (CodeName ?? String.Empty); + writer.WriteEndElement (); // codename + + WritePackageXmlTag (writer, Tag); + WritePackageXmlVendor (writer, Vendor); + + writer.WriteStartElement ("abi"); + writer.WriteString (GetAbiManifestString ()); + writer.WriteEndElement (); // abi + } + + public string GetAbiManifestString () + { + return String.IsNullOrEmpty (AbiName) ? Abi.ToString ().ToLowerInvariant () : AbiName.ToLowerInvariant (); + } + + /// + /// Gets the detailed description of the component + /// + /// Description + protected override string GetDetailedDescription () + { + if (detailedDescription != null) + return detailedDescription; + + var desc = new List (); + desc.Add ($"{Abi}"); + if (!String.IsNullOrEmpty (ApiLevel)) + desc.Add ($"API {ApiLevel}"); + if (!String.IsNullOrEmpty (CodeName)) + desc.Add ($"\"{CodeName}\""); + if (!String.IsNullOrEmpty (Vendor?.Display)) + desc.Add ($"({Vendor.Display})"); + + detailedDescription = $"System Image: {String.Join (" ", desc)}"; + return detailedDescription; + } + + /// + /// Determines whether the specified + /// is equal to the current . + /// + /// The to compare with the current . + /// true if the specified is + /// equal to the current ; + /// otherwise, false. + public bool Equals (AndroidComponentInfoSystemImage other) + { + if (!Equals ((AndroidComponentInfo)other)) + return false; + + if (Abi != other.Abi) + return false; + + if (!AreEqual (ApiLevel, other.ApiLevel)) + return false; + + if (Tag != other.Tag) + return false; + + if (Vendor != other.Vendor) + return false; + + if (!AreEqual (CodeName, other.CodeName)) + return false; + + return true; + } + + /// + /// Determines whether the specified is equal to the current . + /// + /// The to compare with the current . + /// true if the specified is equal to the current + /// ; otherwise, false. + public override bool Equals (object obj) + { + return Equals (obj as AndroidComponentInfoSystemImage); + } + + /// + /// Serves as a hash function for a + /// object. + /// + /// A hash code for this instance that is suitable for use in hashing algorithms and data structures such as a hash table. + public override int GetHashCode () + { + int hashCode = base.GetHashCode (); + + if (ApiLevel != null) + hashCode = hashCode.XorWith (ApiLevel.GetHashCode ()); + + if (CodeName != null) + hashCode = hashCode.XorWith (CodeName.GetHashCode ()); + + if (Tag != null) + hashCode = hashCode.XorWith (Tag.GetHashCode ()); + + if (Vendor != null) + hashCode = hashCode.XorWith (Vendor.GetHashCode ()); + + return hashCode; + } + } +} diff --git a/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/Common/AndroidComponentStatus.cs b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/Common/AndroidComponentStatus.cs new file mode 100644 index 00000000000..0df9722be07 --- /dev/null +++ b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/Common/AndroidComponentStatus.cs @@ -0,0 +1,74 @@ +// +// Authors: +// Marek Habersack +// +// Copyright (c) 2017, Microsoft, Inc (http://microsoft.com) +// +// All rights reserved. +// +using System; + +namespace Xamarin.Installer.AndroidSDK.Common +{ + /// + /// Android component status. , + /// + /// + public enum AndroidComponentStatus + { + /// + /// Component detection started + /// + DetectionStarted, + + /// + /// Component detection completed + /// + DetectionEnded, + + /// + /// Component installation started + /// + InstallationStarted, + + /// + /// Component installation ended + /// + InstallationEnded, + + /// + /// Component archive unpacking started + /// + UnpackingStarted, + + /// + /// Component archive unpacking ended + /// + UnpackingEnded, + + /// + /// Component removal started + /// + RemovalStarted, + + /// + /// Component removal ended + /// + RemovalEnded, + + /// + /// Component verification started + /// + VerificationStarted, + + /// + /// Component verification started + /// + VerificationEnded, + + /// + /// Component metadata (presence, version etc) changed + /// + MetadataUpdated + } +} diff --git a/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/Common/AndroidComponentStatusChangeEventArgs.cs b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/Common/AndroidComponentStatusChangeEventArgs.cs new file mode 100644 index 00000000000..d2891ecff56 --- /dev/null +++ b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/Common/AndroidComponentStatusChangeEventArgs.cs @@ -0,0 +1,36 @@ +// +// Authors: +// Marek Habersack +// +// Copyright (c) 2017, Microsoft, Inc (http://microsoft.com) +// +// All rights reserved. +// +using System; + +namespace Xamarin.Installer.AndroidSDK.Common +{ + /// + /// Android component status change event arguments. + /// + public class AndroidComponentStatusChangeEventArgs : EventArgs + { + /// + /// Gets the new component status. + /// + /// The status. + public AndroidComponentStatus Status { get; } + + /// + /// Gets the component whose status changed + /// + /// The component. + public IAndroidComponent Component { get; } + + internal AndroidComponentStatusChangeEventArgs (AndroidComponentStatus status, IAndroidComponent component) + { + Component = component ?? throw new ArgumentNullException (nameof (component)); + Status = status; + } + } +} diff --git a/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/Common/AndroidLicensesStorage.cs b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/Common/AndroidLicensesStorage.cs new file mode 100644 index 00000000000..76418c5d64e --- /dev/null +++ b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/Common/AndroidLicensesStorage.cs @@ -0,0 +1,456 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.ComponentModel; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Security.Cryptography; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Xamarin.Installer.AndroidSDK.Properties; +using Xamarin.Installer.Common; + +namespace Xamarin.Installer.AndroidSDK.Common +{ + /// + /// AndroidLicensesStorage class has all the protocol to check if a License was accepted + /// + public class AndroidLicensesStorage : ILicensesStorage + { + /// + /// Returns the path where licenses acceptance are stored + /// + /// Android Sdk path + /// Directory path where the licenses acceptance are stored + protected virtual string GetLicensesPath(string androidSdkPath) + { + return string.IsNullOrWhiteSpace(androidSdkPath) || !Directory.Exists(androidSdkPath) ? null : Path.Combine(androidSdkPath, "licenses"); + } + + /// + /// Returns if the license was accepted in this Android SDK + /// + /// Android Sdk path + /// license to check + /// true if the license is accepted, false otherwise + public bool IsLicenseAccepted(string androidSdkPath, License license) + { + if (license == null || string.IsNullOrEmpty(license.ID)) + return false; + + var localLicensesPath = GetLicensesPath(androidSdkPath); + if(string.IsNullOrEmpty(localLicensesPath)) + return false; + + var licensePath = Path.Combine(localLicensesPath, license.ID); + if (!File.Exists(licensePath)) + return false; + + var storedLicenseHash = GetLicenseHash(licensePath); + return !string.IsNullOrEmpty(storedLicenseHash); + } + + protected virtual string GetLicenseHash(License license) + { + if (string.IsNullOrEmpty(license?.Text)) + { + return null; + } + + var licenseText = license.Text; +#pragma warning disable CA5350 + using (var hashAlgorithm = SHA1.Create()) // CodeQL [SM02196] Used to verify checksums provided by an external party. + { + var bytes = Encoding.UTF8.GetBytes(licenseText); + var hash = string.Concat(hashAlgorithm.ComputeHash(bytes).Select(x => x.ToString("x2"))); + return hash; + } +#pragma warning restore CA5350 + } + + protected virtual string GetLicenseHash(string licensePath) + { + return string.IsNullOrWhiteSpace(licensePath) || !File.Exists(licensePath) ? null : File.ReadAllText(licensePath); + } + + /// + /// Accept licenses using local storage + /// + /// Android Sdk path + /// Java Sdk path + /// specific cmdLine-tools path to use + /// list of licenses to accept + /// cancellation token + /// log file path + public Task AcceptLicensesAsync( + string androidSdkPath, + string javaSdkPath, + string cmdLineToolsPath, + IEnumerable licenses, + CancellationToken token, + string logPath = null, + bool throwsErrorIfValidationFailed = false) + { + return Task.Run(async () => + { + var progress = new AggregateProgressSink(logPath); + + try + { + var error = ValidateAcceptLicensesArguments(androidSdkPath, javaSdkPath, cmdLineToolsPath, licenses, progress); + if (!string.IsNullOrEmpty(error)) + { + if (throwsErrorIfValidationFailed) + { + throw new NotSupportedException(error); + } + else + { + return; + } + } + + var licenseIDs = licenses.Select(x => x.ID).Distinct(); + progress.Report($"Licenses to be accepted: {string.Join("|", licenseIDs)}"); + + // run sdkmanager from cmdline-tools to accept the licenses + await WriteGoogleLicensesAsync(androidSdkPath, javaSdkPath, cmdLineToolsPath, licenses, token, progress); + + // it is required because the hash can be different than the generated by google + // Xamarin feed licenses text can be not in sync with latest google feed licenses text + // Xamarin feed is generated/updated on each VS release, + // and VS releases are not in sync with the cadence of updates of Google feed + WriteVSLicenses(androidSdkPath, licenses, token, progress); + } + finally + { + progress.Dispose(); + } + }, token); + } + + string ValidateAcceptLicensesArguments( + string androidSdkPath, + string javaSdkPath, + string cmdLineToolsPath, + IEnumerable licenses, + AggregateProgressSink progress) + { + if (!licenses.Any()) + { + progress?.Report("Android licenses acceptance discarded, no licenses to accept"); + return string.Empty; + } + + if (string.IsNullOrEmpty(androidSdkPath) || !Directory.Exists(androidSdkPath)) + { + progress?.Report(Resources.AccepLicensesFailedAndroidSDKNotFound); + return Resources.AccepLicensesFailedAndroidSDKNotFound; + } + + if (string.IsNullOrEmpty(javaSdkPath) || !Directory.Exists(javaSdkPath)) + { + progress?.Report(Resources.AccepLicensesFailedJavaSDKNotFound); + return Resources.AccepLicensesFailedJavaSDKNotFound; + } + + if (string.IsNullOrEmpty(cmdLineToolsPath)) + { + progress?.Report(Resources.AccepLicensesFailedCMDLineToolsNotFound); + return Resources.AccepLicensesFailedCMDLineToolsNotFound; + } + + var cmdLineToolsPropertiesFilePath = Path.Combine(cmdLineToolsPath, "source.properties"); + if (!File.Exists(cmdLineToolsPropertiesFilePath)) + { + progress?.Report(Resources.AccepLicensesFailedCMDLineToolsNotFound); + return Resources.AccepLicensesFailedCMDLineToolsNotFound; + } + + return string.Empty; + } + + protected void WriteVSLicenses( + string androidSdkPath, + IEnumerable licenses, + CancellationToken token, + AggregateProgressSink progress) + { + if (!licenses.Any()) + return; + + // creates local licenses storage folder + if (token.IsCancellationRequested) + return; + + progress.Report("Running local accept licenses process"); + + try + { + var licensesPath = GetLicensesPath(androidSdkPath); + if (!Directory.Exists(licensesPath)) + Directory.CreateDirectory(licensesPath); + + // save licenses + var licensesToAccept = licenses.Where(license => !string.IsNullOrEmpty(license.ID)).GroupBy(license => license); + foreach (var l in licensesToAccept) + { + if (token.IsCancellationRequested) + return; + + var androidLicense = l.Key; + + var licensePath = Path.Combine(licensesPath, androidLicense.ID); + var licenseHash = GetLicenseHash(androidLicense); + + WriteLicenseHash(licensePath, licenseHash); + } + + progress.Report("Running local accept licenses process completed"); + } + catch (Exception ex) + { + progress.Report($"Running local accept licenses process failed: {ex.Message}"); + } + } + + protected virtual void WriteLicenseHash(string licensePath, string licenseHash) + { + // same format as generated by google sdkmanager licenses command + var sb = new StringWriter(); + + if (File.Exists(licensePath)) + { + sb.WriteLine(""); // enter for previous line + } + + sb.WriteLine(""); // force empty line + sb.WriteLine(licenseHash); // hash + + File.AppendAllText(licensePath, sb.ToString()); + } + + protected async Task WriteGoogleLicensesAsync( + string androidSdkPath, + string javaSdkPath, + string cmdLineToolsPath, + IEnumerable licenses, + CancellationToken token, + AggregateProgressSink progress) + { + progress.Report("Running sdkmanager --licenses --verbose"); + + var standardOutput = progress.ForOutputs; + var standardError = progress.ForErrors; + + var sdkManagerApp = string.Empty; + switch (AndroidSDKContext.Instance.Platform) + { + case AndroidSDKPlatform.Linux: + case AndroidSDKPlatform.Mac: + sdkManagerApp = "sdkmanager"; + break; + case AndroidSDKPlatform.Windows: + sdkManagerApp = "sdkmanager.bat"; + break; + + default: + throw new InvalidOperationException("Unsupported platform"); + } + + var processStartInfo = new ProcessStartInfo(fileName: Path.Combine(cmdLineToolsPath, "bin", sdkManagerApp), "--licenses --verbose"); + processStartInfo.WorkingDirectory = Path.Combine(cmdLineToolsPath, "bin"); + processStartInfo.WindowStyle = ProcessWindowStyle.Hidden; + processStartInfo.UseShellExecute = false; + processStartInfo.RedirectStandardError = true; + processStartInfo.RedirectStandardInput = true; + processStartInfo.RedirectStandardOutput = true; + processStartInfo.CreateNoWindow = true; + + if (!string.IsNullOrEmpty(javaSdkPath)) + { + // Use short path (8.3 format) for JAVA_HOME on Windows to avoid batch script parsing issues + // when paths contain parentheses like "Program Files (x86)" which break CMD.exe IF/FOR blocks. + // See: https://dev.azure.com/devdiv/DevDiv/_workitems/edit/2632833 + var javaHomePath = AndroidSDKContext.Instance.Platform == AndroidSDKPlatform.Windows + ? CommonUtilities.GetShortPath(javaSdkPath) + : javaSdkPath; + processStartInfo.EnvironmentVariables["JAVA_HOME"] = javaHomePath; + } + + var process = new Process(); + process.StartInfo = processStartInfo; + + bool matchLicenseId = false; + var loadingLocalRepository = true; + + var inputAnswers = new ConcurrentQueue(); + inputAnswers.Enqueue("Y"); // to accept the review "Review licenses that have not been accepted (y/N)?" + + if (processStartInfo.RedirectStandardOutput) + { + process.OutputDataReceived += (sender, e) => + { + if (e.Data != null) + { + standardOutput?.Report(e.Data); + + if (e.Data.Contains("(y/N)?")) + { + // when the message "Review licenses that have not been accepted (y/N)?" is reported the loading phase and display all licenses text ends + loadingLocalRepository = false; + } + + // try to match the license id from message: "License {id}:" + if (loadingLocalRepository && licenses.Any(x => e.Data.Contains($"{x.ID}:"))) + { + matchLicenseId = true; + } + + if (loadingLocalRepository && e.Data.Trim().Equals("Accepted")) + { + // reset the match when an accepted license is detected + matchLicenseId = false; + } + + if (loadingLocalRepository && e.Data.Trim().Contains("Not yet accepted")) + { + // enqueue a Y/N answer + if (matchLicenseId) + { + matchLicenseId = false; + inputAnswers.Enqueue("Y"); + } + else + { + inputAnswers.Enqueue("N"); + } + } + } + }; + } + + if (processStartInfo.RedirectStandardError) + { + process.ErrorDataReceived += (sender, e) => + { + standardError?.Report(e.Data); + }; + } + + process.Start(); + + if (processStartInfo.RedirectStandardOutput) + process.BeginOutputReadLine(); + + if (processStartInfo.RedirectStandardError) + process.BeginErrorReadLine(); + + process.StandardInput.AutoFlush = true; + + while (!process.HasExited) + { + if (token.IsCancellationRequested) + { + process.Kill(); + } + else + { + try + { + if (inputAnswers.Any() && inputAnswers.TryDequeue(out var inputAnswer)) + { + await process.StandardInput.WriteLineAsync(inputAnswer); + } + } + catch (Exception e) + { + progress.Report(e.Message); + } + } + } + + if (!token.IsCancellationRequested && process.ExitCode > 0) + { + var errorMessage = $""" + {Resources.GoogleAccepLicensesFailed}. + Exit code: {process.ExitCode}. + Android SDK: '{androidSdkPath}'. + Java SDK: '{javaSdkPath}'. + Command-line tools: '{cmdLineToolsPath}'. + """; + + if (!string.IsNullOrEmpty(standardError.Errors)) + { + errorMessage += $" Errors: {standardError.Errors}"; + } + + progress.Report(errorMessage); + throw new Exception(errorMessage); + } + } + + protected class AggregateProgressSink : IProgress, IDisposable + { + private StreamWriter writer; + + public AggregateProgressSink(string logPath) + { + this.ForOutputs = new OutputProgressSink(this); + this.ForErrors = new ErrorProgressSink(this); + this.writer = string.IsNullOrEmpty(logPath) ? null : new StreamWriter(logPath); + } + + public OutputProgressSink ForOutputs { get; private set; } + + public ErrorProgressSink ForErrors { get; private set; } + + public void Report(string value) + { + this.writer?.WriteLine(value); + } + + public void Dispose() + { + this.writer?.Close(); + this.writer = null; + } + } + + protected class OutputProgressSink : IProgress + { + private IProgress fileSink; + + public OutputProgressSink(IProgress fileSink) + { + this.fileSink = fileSink; + } + + public void Report(string value) + { + this.fileSink.Report(value); + } + } + + protected class ErrorProgressSink : IProgress + { + private readonly StringBuilder allErrors = new StringBuilder(); + private readonly IProgress fileSink; + + public ErrorProgressSink(IProgress fileSink) + { + this.fileSink = fileSink; + } + + public string Errors => this.allErrors.ToString(); + + public void Report(string value) + { + this.fileSink.Report(value); + this.allErrors.AppendLine(value); + } + } + } +} diff --git a/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/Common/Archive.cs b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/Common/Archive.cs new file mode 100644 index 00000000000..29ec9a9ebbd --- /dev/null +++ b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/Common/Archive.cs @@ -0,0 +1,272 @@ +// +// Authors: +// Marek Habersack +// +// Copyright (c) 2017, Microsoft, Inc +// +// All rights reserved. +// +using System; +using System.Collections.Generic; +using System.IO; +using System.Security.Cryptography; + +using Xamarin.Installer.Common; + +namespace Xamarin.Installer.AndroidSDK.Common +{ + /// + /// Describes a single package archive + /// + public class Archive : ArchiveBase, IEquatable + { + /// + /// Gets the collection of patches for this archive. This library currently does not support applying these + /// patches, instead it always downloads the full archive from the specified + /// + /// The list of patches (if any) + public IList Patches { get; set; } + + /// + /// Gets a value indicating whether this was downloaded. + /// true will be returned only if is set and refers to an existing file. + /// Checksum is not verified when reading this property. + /// + /// true if archive was downloaded; otherwise, false. + public bool WasDownloaded => !String.IsNullOrEmpty(GetDownloadPath(false)); + + /// + /// Gets or sets the downloaded file path. The installer assembly resets this property to null when returning archive + /// instance from but otherwise it is responsibilty of the client code + /// to set the property to a valid path of a downloaded archive. + /// + /// The downloaded file path. + public string DownloadedFilePath { get; set; } + + /// + /// Gets the Android component that owns this Archive, if any + /// + /// The owner component. + public IAndroidComponent Owner { get; internal set; } + + public Archive(string hostOS) : base(hostOS) + { + } + + /// + /// Verifies whether the downloaded archive is indeed found on disk and whether it passes checksum verification + /// (if is true). + /// + /// true, if downloaded archive is valid, false otherwise. + /// If set to true verify the archive checksum. + public bool IsDownloadValid(bool verifyChecksum = true) + { + string filePath = GetDownloadPath(); + if (String.IsNullOrEmpty(filePath)) + return false; + + if (!verifyChecksum) + return true; + + if (!VerifyChecksum(filePath, ChecksumType, Checksum, Url)) + { + Logger.Warning($"Downloaded file '{DownloadedFilePath}' fails checksum verification for Android SDK archive '{Url}'"); + return false; + } + + return true; + } + + /// + /// Verifies against using the algorithm + /// named by . The algorithm string is normalized (trimmed, + /// lowercased, with a single hyphen stripped) so values such as "sha-1" / "SHA1" / "sha-256" + /// are all accepted. A null or empty algorithm defaults to SHA-1 to match the historical + /// manifest behavior. Unknown algorithms or hash-length mismatches log a warning and return + /// false rather than silently falling back. + /// + internal static bool VerifyChecksum(string filePath, string algorithm, string hash, Uri url) + { + string normalized = (algorithm ?? string.Empty).Trim().ToLowerInvariant().Replace("-", string.Empty); + string trimmedHash = hash?.Trim(); + + if (string.IsNullOrEmpty(normalized) || normalized == "sha1") + { + if (!string.IsNullOrEmpty(trimmedHash) && trimmedHash.Length != 40) + { + Logger.Warning($"Checksum for Android SDK archive '{url}' is declared as SHA-1 but its length ({trimmedHash.Length}) is not 40 hex characters"); + return false; + } + return CheckSHA1(filePath, trimmedHash); + } + + if (normalized == "sha256") + { + if (!string.IsNullOrEmpty(trimmedHash) && trimmedHash.Length != 64) + { + Logger.Warning($"Checksum for Android SDK archive '{url}' is declared as SHA-256 but its length ({trimmedHash.Length}) is not 64 hex characters"); + return false; + } + return CheckSHA256(filePath, trimmedHash); + } + + Logger.Warning($"Unknown checksum algorithm '{algorithm}' declared for Android SDK archive '{url}'; refusing to verify"); + return false; + } + + /// + /// Checks whether the downloaded archive is valid (verifying the checksum if is true) and + /// whether it can be installed on the current system. + /// + /// true, if the archive is installable on the current system, false otherwise. + /// If set to true verify checksum. + /// If set to true verify archive host os. + public bool IsInstallable(bool verifyChecksum = true, bool checkPlatform = true) + { + return (!checkPlatform || IsValidForSystem()) && IsDownloadValid(verifyChecksum); + } + + /// + /// Checks whether archive's platform and host-arch are matching the current system. + /// + /// + public override bool IsValidForSystem() + { + if (!IsPlatformValid()) + return false; + + if (IsHostArchValid()) + return true; + + // custom logic: windows emulator only have x64 archives + return Platform == AndroidSDKPlatform.Windows && + (string.IsNullOrEmpty(Owner?.FileSystemPath) || (Owner.FileSystemPath.StartsWith("emulator") && HostArch.StartsWith("x"))); + } + + string GetDownloadPath(bool logMissing = true) + { + string filePath = DownloadedFilePath?.Trim(); + if (String.IsNullOrEmpty(filePath)) + return null; + + if (!File.Exists(filePath)) + { + if (logMissing) + Logger.Info($"Downloaded file '{DownloadedFilePath}' not found for Android SDK archive '{Url}'"); + return null; + } + + return filePath; + } + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Security", "CA5350:Do Not Use Weak Cryptographic Algorithms", Justification = "Used to verify checksums provided by an external party.")] + static bool CheckSHA1(string filePath, string sha1hash) + { + sha1hash = sha1hash?.Trim(); + if (String.IsNullOrEmpty(sha1hash) || sha1hash.Length != 40) + return false; + + using (var sha1 = SHA1.Create())// CodeQL [SM02196] Receiving external information: Used to verify checksums provided by an external party + { + using (var fs = File.OpenRead(filePath)) + { + byte[] hash = sha1.ComputeHash(fs); + string shash = BitConverter.ToString(hash).Replace("-", ""); + return String.Compare(shash, sha1hash, StringComparison.OrdinalIgnoreCase) == 0; + } + } + } + + static bool CheckSHA256(string filePath, string sha256hash) + { + sha256hash = sha256hash?.Trim(); + if (String.IsNullOrEmpty(sha256hash) || sha256hash.Length != 64) + return false; + + using (var sha256 = SHA256.Create()) + { + using (var fs = File.OpenRead(filePath)) + { + byte[] hash = sha256.ComputeHash(fs); + string shash = BitConverter.ToString(hash).Replace("-", ""); + return String.Compare(shash, sha256hash, StringComparison.OrdinalIgnoreCase) == 0; + } + } + } + + /// + /// Determines whether the specified is equal to the current . + /// + /// The to compare with the current . + /// true if the specified is equal to the current + /// ; otherwise, false. + public bool Equals(Archive other) + { + if (!base.Equals(other)) + return false; + + if (Patches == null) + { + if (other.Patches != null) + return false; + } + else + { + if (other.Patches == null) + return false; + if (!ComparePatches(other.Patches)) + return false; + } + + return true; + } + + bool ComparePatches(IList otherPatches) + { + if (Patches.Count != otherPatches.Count) + return false; + + for (int i = 0; i < otherPatches.Count; i++) + { + ArchivePatch localPatch = Patches[i]; + ArchivePatch otherPatch = otherPatches[i]; + + if (localPatch == null) + { + if (otherPatch != null) + return false; + continue; + } + + if (otherPatch == null) + return false; + + if (!localPatch.Equals(otherPatch)) + return false; + } + + return true; + } + + /// + /// Determines whether the specified is equal to the current . + /// + /// The to compare with the current . + /// true if the specified is equal to the current + /// ; otherwise, false. + public override bool Equals(Object obj) + { + return Equals(obj as Archive); + } + + /// + /// Serves as a hash function for a object. + /// + /// A hash code for this instance that is suitable for use in hashing algorithms and data structures such as a hash table. + public override int GetHashCode() + { + int hashCode = base.GetHashCode(); + return hashCode.XorWith(Patches?.GetHashCode()); + } + } +} diff --git a/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/Common/ArchiveBase.cs b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/Common/ArchiveBase.cs new file mode 100644 index 00000000000..2d65771b3a1 --- /dev/null +++ b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/Common/ArchiveBase.cs @@ -0,0 +1,135 @@ +using System; + +namespace Xamarin.Installer.AndroidSDK.Common +{ + /// + /// Describes a single package archive + /// + public abstract class ArchiveBase : IArchive + { + public ArchiveBase(string hostOS) + { + HostOS = hostOS; + Platform = AndroidUtilities.GetPlatformFromOS(hostOS); + } + + /// + /// Gets the archive size + /// + /// Archive size + public ulong Size { get; set; } + + /// + /// Gets the checksum of the archive. The algorithm is identified by ; + /// when is null or empty the checksum is assumed to be SHA-1. + /// + /// Checksum digest as a hex string + public string Checksum { get; set; } + + /// + /// Gets the checksum algorithm declared by the manifest for , + /// for example sha1 or sha256. A null or empty value means SHA-1 (the historical default). + /// + /// Checksum algorithm name + public string ChecksumType { get; set; } + + /// + /// Gets the archive URL + /// + /// Archive URL + public Uri Url { get; set; } + + /// + /// Gets the target (host) operating system of the archive (if any) + /// + /// Host operating system + public string HostOS { get; } + + /// + /// Mapping of into one of the enumeration members + /// + /// Archive platform. + public AndroidSDKPlatform Platform { get; } + + /// + /// Native word size of the host operating system. + /// + /// Host operating system word size + public uint HostBits { get; set; } + + /// + /// CPU Architecture of the host operating system. + /// + /// Host operating CPU architecture + public string HostArch { get; set; } + + /// + /// Checks whether archive's platform and host-arch are matching the current system. + /// + /// + public abstract bool IsValidForSystem(); + + /// + /// Checks whether archive's platform is matching the current system. + /// + /// + public virtual bool IsPlatformValid() + { + return (Platform == AndroidSDKPlatform.Any || Platform == AndroidSDKContext.Instance.Platform); + } + + /// + /// Checks whether archive's host-arch is matching the current system. + /// + /// + public virtual bool IsHostArchValid() + { + return string.IsNullOrEmpty(HostArch) || HostArch == AndroidSDKContext.Instance.HostArch; + } + + /// + /// Determines whether the specified is equal to the current . + /// + /// The to compare with the current . + /// true if the specified is equal to the current + /// ; otherwise, false. + public bool Equals(IArchive other) + { + if (other == null) + return false; + + if (ReferenceEquals(this, other)) + return true; + + if (String.Compare(Checksum, other.Checksum, StringComparison.Ordinal) != 0) + return false; + + if (HostBits != other.HostBits) + return false; + + if (String.Compare(HostOS, other.HostOS, StringComparison.Ordinal) != 0) + return false; + + if (Size != other.Size) + return false; + + if (Url != other.Url) + return false; + + return true; + } + + /// + /// Serves as a hash function for a object. + /// + /// A hash code for this instance that is suitable for use in hashing algorithms and data structures such as a hash table. + public override int GetHashCode() + { + int hashCode = base.GetHashCode(); + hashCode = hashCode.XorWith(HostBits.GetHashCode()); + hashCode = hashCode.XorWith(HostOS?.GetHashCode()); + hashCode = hashCode.XorWith(Size.GetHashCode()); + return hashCode.XorWith(Url?.GetHashCode()); + } + } +} diff --git a/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/Common/ArchivePatch.cs b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/Common/ArchivePatch.cs new file mode 100644 index 00000000000..fc014f181a5 --- /dev/null +++ b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/Common/ArchivePatch.cs @@ -0,0 +1,112 @@ +// +// Authors: +// Marek Habersack +// +// Copyright (c) 2017, Microsoft, Inc +// +// All rights reserved. +// +using System; + +namespace Xamarin.Installer.AndroidSDK.Common +{ + /// + /// Represents a single archive patch instance. + /// + public sealed class ArchivePatch : IEquatable + { + /// + /// Gets the revision of the parent archive this patch must be applied to. + /// + /// Base archive revision + public AndroidRevision BasedOn { get; internal set; } + + /// + /// Gets the patch download size + /// + /// The size. + public ulong Size { get; internal set; } + + /// + /// Gets the checksum of the patch. The algorithm is identified by ; + /// when is null or empty the checksum is assumed to be SHA-1. + /// + /// Checksum digest as a hex string + public string Checksum { get; internal set; } + + /// + /// Gets the checksum algorithm declared by the manifest for , + /// for example sha1 or sha256. A null or empty value means SHA-1. + /// + /// Checksum algorithm name + public string ChecksumType { get; internal set; } + + /// + /// Gets the patch URL + /// + /// Archive URL + public Uri Url { get; internal set; } + + internal ArchivePatch () + { + } + + /// + /// Determines whether the specified is equal to the + /// current . + /// + /// The to compare with the current . + /// true if the specified is equal to the + /// current ; otherwise, false. + public bool Equals (ArchivePatch other) + { + if (other == null) + return false; + + if (ReferenceEquals (this, other)) + return true; + + if (Size != other.Size) + return false; + + if (Url != other.Url) + return false; + + if (String.Compare (Checksum, other.Checksum, StringComparison.Ordinal) != 0) + return false; + + if (BasedOn == null) { + if (other.BasedOn != null) + return false; + } else if (BasedOn != other.BasedOn) + return false; + + return true; + } + + /// + /// Determines whether the specified is equal to the current . + /// + /// The to compare with the current . + /// true if the specified is equal to the current + /// ; otherwise, false. + public override bool Equals (object obj) + { + return Equals (obj as ArchivePatch); + } + + /// + /// Serves as a hash function for a object. + /// + /// A hash code for this instance that is suitable for use in hashing algorithms and data structures such as a hash table. + public override int GetHashCode () + { + int hashCode = base.GetHashCode (); + + hashCode = hashCode.XorWith (Size.GetHashCode ()); + hashCode = hashCode.XorWith (Url?.GetHashCode ()); + hashCode = hashCode.XorWith (Checksum?.GetHashCode ()); + return hashCode.XorWith (BasedOn?.GetHashCode ()); + } + } +} diff --git a/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/Common/BasePackage.cs b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/Common/BasePackage.cs new file mode 100644 index 00000000000..c1629449939 --- /dev/null +++ b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/Common/BasePackage.cs @@ -0,0 +1,912 @@ +// +// Authors: +// Marek Habersack +// +// Copyright (c) 2018, Microsoft Corp. (https://microsoft.com) +// +// All rights reserved. +// +using Kajabity.Tools.Java; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading; +using System.Xml; +using System.Xml.Linq; +using Xamarin.Installer.Common; +using IOPath = System.IO.Path; + +namespace Xamarin.Installer.AndroidSDK.Common +{ + public abstract class BasePackage : IEquatable, IAndroidComponent, IAndroidComponentInternal + { + protected Repository Repository { get; } + protected IList OriginalArchives { get; } + + public event EventHandler StatusChanged; + + public abstract AndroidComponentType ComponentType { get; } + public Guid UniqueID { get; } = Guid.NewGuid(); + public bool IsEssential { get; set; } + public bool IgnoreFailure { get; set; } + public bool Present { get; set; } + public bool NeedsUpdate { get; set; } + public string Path { get; set; } + public string FileSystemPath { get; set; } + public bool Obsolete { get; set; } + public bool Preview => GetPreview(); + public AndroidComponentInfo Info { get; set; } + public AndroidRevision Revision { get; set; } + public AndroidRevision InstalledRevision { get; set; } + public string DisplayName { get; set; } + public string DetailedDescription => GetDetailedDescription(); + public IList Dependencies { get; set; } + public abstract Channel Channel { get; } + public IList Archives { get; protected set; } + public License License => Repository.GetLicense(LicenseID); + public string LicenseID { get; set; } + public Uri ManifestURL { get; } + public IXmlLineInfo Location { get; } + public bool ForceInstallation { get; set; } + + public BasePackage(Repository repository, Uri manifestURL, IXmlLineInfo location, IList archives) + { + Repository = repository ?? throw new ArgumentNullException(nameof(repository)); + ManifestURL = manifestURL ?? throw new ArgumentNullException(nameof(manifestURL)); + Location = location; + OriginalArchives = archives; + if (archives == null || archives.Count == 0) + return; + + foreach (Archive archive in archives) + { + if (archive == null) + continue; + archive.Owner = this; + } + } + + public IEnumerable SupportedPlatforms + { + get + { + //todo OriginalArchives? + foreach (var archive in Archives) + yield return archive.Platform; + } + } + + public bool IsPlatformSpecific + { + get + { + return !Archives.Any(a => a.Platform == AndroidSDKPlatform.Any || a.Platform == AndroidSDKPlatform.Unknown); + } + } + + /// + /// NOTE: Using in ManifestGenerator only. + /// + /// + public void ExcludePlatform(AndroidSDKPlatform platform) + { + Archives = Archives.Where(a => a.Platform != platform).ToList(); + Dependencies = Dependencies.Where(d => d.Platform != platform).ToList(); + } + + protected void PopulatePlatformArchives(bool includeAllArchives) + { + var alist = new List(); + AndroidSDKPlatform platform = AndroidSDKContext.Instance.Platform; + + foreach (Archive archive in OriginalArchives) + { + if (archive == null || (!includeAllArchives && !archive.IsValidForSystem())) + continue; + alist.Add(archive); + } + + + bool IsBitsValid(uint bits) => ((bits == 64) == CommonUtilities.Helpers.Is64BitOS); + // Remove all archives with HostBits not matching the OS Bits + // Apply only if there is any matching in the list + if (!includeAllArchives && alist.Count > 1 && alist.Any(a => IsBitsValid(a.HostBits))) + { + alist.RemoveAll(a => !IsBitsValid(a.HostBits)); + } + + Archives = alist.AsReadOnly(); + } + + protected virtual string GetDetailedDescription() + { + string desc = $"{DisplayName} r{Revision}"; + if (!String.IsNullOrEmpty(Info?.DetailedDescription)) + return $"{desc} [{Info?.DetailedDescription}]"; + return desc; + } + + protected void OnStatusChange(AndroidComponentStatus status) + { + if (StatusChanged == null) + return; + + StatusChanged(this, new AndroidComponentStatusChangeEventArgs(status, this)); + } + + public void Remove(string androidSDKRoot) + { + OnStatusChange(AndroidComponentStatus.RemovalStarted); + try + { + PackageMetadata metadata = CreateMetadata(this); + + DoRemove(androidSDKRoot, retries: 5, delayInSeconds: 5); + + PerformDetection(androidSDKRoot, true); + RefreshMetadata(this, false); + } + finally + { + OnStatusChange(AndroidComponentStatus.RemovalEnded); + } + } + + void DoRemove(string androidSDKRoot, int retries, int delayInSeconds) + { + if (String.IsNullOrEmpty(androidSDKRoot)) + throw new ArgumentException("must not be null or empty", nameof(androidSDKRoot)); + + var ex = default(Exception); + var success = false; + while (!success && retries > 0) + { + try + { + DoRemove(androidSDKRoot); + success = true; + } + catch (UnauthorizedAccessException uae) + { + ex = uae; + retries = retries - 1; + Thread.Sleep(TimeSpan.FromSeconds(delayInSeconds)); + delayInSeconds = delayInSeconds + 3; + } + } + + if (!success && ex != null) + throw ex; + } + + void DoRemove(string androidSDKRoot) + { + if (String.IsNullOrEmpty(androidSDKRoot)) + throw new ArgumentException("must not be null or empty", nameof(androidSDKRoot)); + + string packagePath = IOPath.Combine(androidSDKRoot, FileSystemPath); + if (File.Exists(packagePath)) + File.Delete(packagePath); + else if (Directory.Exists(packagePath)) + Directory.Delete(packagePath, true); + else + return; + + DeleteDirTreeIfEmpty( + new DirectoryInfo(GetRealDirectoryName(androidSDKRoot)), + new DirectoryInfo(GetRealDirectoryName(IOPath.GetDirectoryName(packagePath))) + ); + } + + public abstract void RefreshMetadata(IAndroidComponent component, bool ignoreInstalledState = true); + protected abstract PackageMetadata CreateMetadata(BasePackage component); + + public void PerformDetection(string androidSDKRoot, bool isRefresh = false) + { + if (String.IsNullOrEmpty(androidSDKRoot)) + throw new ArgumentException("must not be null or empty", nameof(androidSDKRoot)); + + PackageMetadata metadata = isRefresh ? CreateMetadata(this) : null; + string description = DetailedDescription; + Present = false; + InstalledRevision = null; + ForceInstallation = false; + OnStatusChange(AndroidComponentStatus.DetectionStarted); + try + { + NeedsUpdate = Detect(androidSDKRoot, description); + } + catch (Exception ex) + { + LogInfo($"Component {description} detection failed with exception. Component will be marked as outdated."); + LogInfo(ex.ToString()); + NeedsUpdate = true; + } + finally + { + OnStatusChange(AndroidComponentStatus.DetectionEnded); + if (MetadataChanged(metadata)) + OnStatusChange(AndroidComponentStatus.MetadataUpdated); + } + } + + // Returns `true` if the package is absent or is older than our version + bool Detect(string androidSDKRoot, string description) + { + Present = false; + string fullPath = IOPath.Combine(androidSDKRoot, FileSystemPath); + if (!Directory.Exists(fullPath)) + { + LogDebug($"Component {description} not present on the system"); + return true; + } + LogDebug($"Detecting component {description} in directory '{fullPath}'"); + + // Treat the package as present only if we have any file with version information + AndroidRevision instv = null; + string manifestFile = IOPath.Combine(fullPath, "package.xml"); + if (File.Exists(manifestFile)) + { + Present = true; + instv = GetPackageRevision(manifestFile); + } + + if (instv == null) + { + manifestFile = IOPath.Combine(fullPath, "source.properties"); + if (File.Exists(manifestFile)) + { + Present = true; + JavaProperties props = AndroidUtilities.ReadAndroidProperties(manifestFile); + string rev; + props.GetPkgRevision(out rev, out instv, DisplayName); + } + } + + // https://bugzilla.xamarin.com/show_bug.cgi?id=59714 AndroidComponentInfoSystemImage.Abi reads Default for a x86 image + // Try to fix system image' package.xml -> abi as it might be wrong + if (ComponentType == AndroidComponentType.SystemImage && Present) + { + AndroidComponentInfoSystemImage info = (AndroidComponentInfoSystemImage)Info; + if (info != null + && (info.Abi == AndroidSystemImageAbi.X86 + || info.Abi == AndroidSystemImageAbi.ARMV7a + || info.Abi == AndroidSystemImageAbi.ARM64V8a)) + { + manifestFile = IOPath.Combine(fullPath, "package.xml"); + if (File.Exists(manifestFile)) + { + string abi = GetAbi(manifestFile); + + // Rewrite the entire manifest if the abi value differs + if (abi != null && abi != info.GetAbiManifestString()) + { + LogWarning($"Found invalid abi \"{abi}\" (expected value: \"{info.GetAbiManifestString()}\") in \"{manifestFile}\", regenerating package.xml..."); + try + { + GeneratePackageXml(manifestFile); + } + catch (Exception ex) + { + if (ex is IOException || ex is UnauthorizedAccessException) + { + LogError($"Could not regenerate package.xml for \"{manifestFile}\" as the process might not have administrative privileges. You will need to fix the manifest manually.\nHere is the error details: {ex}"); + // Marking component as not installed + Present = false; + InstalledRevision = null; + return true; + } + throw; + } + } + } + } + } + + if (instv == null) + { + LogInfo($" Version information not found for component {description}"); + return true; + } + + LogDebug($" Found revision {instv} on the system"); + InstalledRevision = instv; + + return Revision > InstalledRevision; + } + + protected AndroidRevision GetPackageRevision(string manifestFile) + { + try + { + XDocument doc = XDocument.Load(manifestFile); + XElement revision = doc.Descendants("revision")?.FirstOrDefault(); + if (revision == null) + return null; + + // We should use RevisionParser but due to its requirements (parser context) it's rather + // unwieldy, hence the code duplication here + return new AndroidRevision( + ParseAsInt(revision.Element("major")), + ParseAsInt(revision.Element("minor")), + ParseAsInt(revision.Element("micro")), + ParseAsInt(revision.Element("preview")) + ); + } + catch (Exception ex) + { + LogDebug($"Exception caught trying to parse {manifestFile} for package version information."); + LogDebug(ex.ToString()); + return null; + } + } + + protected string GetAbi(string manifestFile) + { + try + { + XDocument doc = XDocument.Load(manifestFile); + XElement abi = doc.Descendants("abi")?.FirstOrDefault(); + if (abi == null) + return null; + + return abi.Value; + } + catch (Exception ex) + { + LogDebug($"Exception caught trying to parse {manifestFile} for abi information."); + LogDebug(ex.ToString()); + return null; + } + } + + protected int ParseAsInt(XElement e) + { + string v = e?.Value?.Trim(); + if (String.IsNullOrEmpty(v)) + return -1; + + int ret; + if (!Int32.TryParse(v, out ret)) + return -1; + + return ret; + } + + protected string GetRealDirectoryName(string path) + { + if (!Directory.Exists(path)) + return path; + + string cwd = Environment.CurrentDirectory; + try + { + Directory.SetCurrentDirectory(path); + return Environment.CurrentDirectory; + } + finally + { + Directory.SetCurrentDirectory(cwd); + } + } + + protected void DeleteDirTreeIfEmpty(DirectoryInfo stopAt, DirectoryInfo di) + { + if (String.Compare(stopAt.FullName, di.FullName, StringComparison.Ordinal) == 0) + return; + + if (di.EnumerateFileSystemInfos().Any()) + return; + + DirectoryInfo parent = di.Parent; + di.Delete(); + DeleteDirTreeIfEmpty(stopAt, parent); + } + + protected virtual bool MetadataChanged(PackageMetadata metadata, bool update = false, bool ignoreInstalledState = false) + { + if (metadata == null) + return false; + + bool haveChanges = false; + if (!metadata.Archives.AreEqual(Archives)) + { + if (update) + { + Archives = new List(metadata.Archives).AsReadOnly(); + haveChanges = true; + } + else + return true; + } + + if (!metadata.Dependencies.AreEqual(Dependencies)) + { + if (update) + { + Dependencies = metadata.Dependencies != null ? new List(metadata.Dependencies).AsReadOnly() : null; + haveChanges = true; + } + else + return true; + } + + if (String.Compare(metadata.DisplayName, DisplayName, StringComparison.Ordinal) != 0) + { + if (update) + { + DisplayName = metadata.DisplayName; + haveChanges = true; + } + else + return true; + } + + if (String.Compare(metadata.FileSystemPath, FileSystemPath, StringComparison.Ordinal) != 0) + { + if (update) + { + FileSystemPath = metadata.FileSystemPath; + haveChanges = true; + } + else + return true; + } + + if (!AreInfosEqual(Info, metadata.Info)) + { + if (update) + { + Info = metadata.Info; + haveChanges = true; + } + else + return true; + } + + if (!ignoreInstalledState && metadata.InstalledRevision != InstalledRevision) + { + if (update) + { + InstalledRevision = metadata.InstalledRevision; + haveChanges = true; + } + else + return true; + } + + if (!ignoreInstalledState && metadata.NeedsUpdate != NeedsUpdate) + { + if (update) + { + NeedsUpdate = metadata.NeedsUpdate; + haveChanges = true; + } + else + return true; + } + + if (metadata.Obsolete != Obsolete) + { + if (update) + { + Obsolete = Obsolete; + haveChanges = true; + } + else + return true; + } + + if (String.Compare(metadata.Path, Path, StringComparison.OrdinalIgnoreCase) != 0) + { + if (update) + { + Path = metadata.Path; + haveChanges = true; + } + else + return true; + } + + if (!ignoreInstalledState && metadata.Present != Present) + { + if (update) + { + Present = metadata.Present; + haveChanges = true; + } + else + return true; + } + + if (metadata.Revision != Revision) + { + if (update) + { + Revision = metadata.Revision; + haveChanges = true; + } + else + return true; + } + + return haveChanges; + } + + public void Install(string archivePath, string androidSDKRoot, + InstallationProgressEventArgs.InstallationProgressActionDelegate progressCallback = null) + { + OnStatusChange(AndroidComponentStatus.InstallationStarted); + try + { + DoInstall(archivePath, androidSDKRoot, progressCallback); + } + finally + { + OnStatusChange(AndroidComponentStatus.InstallationEnded); + } + } + + void DoInstall(string archivePath, string androidSDKRoot, + InstallationProgressEventArgs.InstallationProgressActionDelegate progressCallback = null) + { + if (String.IsNullOrEmpty(archivePath)) + throw new ArgumentException("must not be null or empty", nameof(archivePath)); + if (String.IsNullOrEmpty(androidSDKRoot)) + throw new ArgumentException("must not be null or empty", nameof(androidSDKRoot)); + + string targetDirectory = IOPath.Combine(androidSDKRoot, FileSystemPath); + OnStatusChange(AndroidComponentStatus.UnpackingStarted); + + bool success = false; + try + { + success = InstallArchive(DetailedDescription, archivePath, targetDirectory, progressCallback); + } + catch (Exception ex) + { + LogError($"Archive '{archivePath}' installation failed with error: {ex}"); + throw; + } + finally + { + OnStatusChange(AndroidComponentStatus.UnpackingEnded); + } + + if (success) + GeneratePackageXml(IOPath.Combine(targetDirectory, "package.xml")); + else + throw new InvalidOperationException("Archive installation failed."); + } + + bool InstallArchive(string description, string archivePath, string targetDirectory, + InstallationProgressEventArgs.InstallationProgressActionDelegate progressCallback = null) + { + //return false; // this is a test + //throw new Exception("A test InstallArchive exception."); + var rnd = new Random(); + string componentUnzippedPath; + + int moveSplitPercents = DirectorySizeMonitoringTimer.CalculateMoveProgressSplit(archivePath); + int unzipSplitPercents = 100 - moveSplitPercents; + + // LogDebug ($"unzipSplitPercents: {unzipSplitPercents} | moveSplitPercents: {moveSplitPercents}"); + + // A small race here but I think we can accept the risks :) + string temporaryPath = IOPath.Combine(IOPath.GetTempPath(), IOPath.GetRandomFileName()); + try + { + componentUnzippedPath = CommonUtilities.Helpers.Unzip(temporaryPath, archivePath, AndroidSDKContext.Instance.UserName, (progress) => + { + try + { + progressCallback?.Invoke((float)DirectorySizeMonitoringTimer.Remap(progress, 0, 100, 0, unzipSplitPercents)); + } + catch (Exception ex) + { + Logger.Error($"[InstallArchive] progress callback exception: {ex}"); + } + }).RemoveTrailingDirectorySeparator(); + } + catch (Exception ex) + { + LogError($"Exception caught while unzipping '{archivePath}' in '{temporaryPath}'. {ex}"); + throw; + } + + LogDebug($"Android component '{description}' unpacked in directory '{componentUnzippedPath}'"); + LogDebug($"Android component destination path: {targetDirectory}"); + string oldDestinationPath = null; + + if (Directory.Exists(targetDirectory)) + { + oldDestinationPath = targetDirectory + ".old" + rnd.Next(Int32.MaxValue); + LogInfo($"Moving old directory '{targetDirectory}' to '{oldDestinationPath}'"); + + // We'll be on the same filesystem/volume, so we can just use this instead of recursive copy+delete + Directory.Move(targetDirectory, oldDestinationPath); + } + + bool ret = true; + Exception exMoveDirectory = null; + try + { + int idx = targetDirectory.LastIndexOf(IOPath.DirectorySeparatorChar); + string leadingPath; + if (idx > 1) + leadingPath = targetDirectory.Substring(0, idx); + else + leadingPath = targetDirectory; + + if (!Directory.Exists(leadingPath)) + { + LogInfo($"Creating Android component's parent path '{leadingPath}'"); + Directory.CreateDirectory(leadingPath); + } + LogInfo("Moving Android component to its destination path."); + + using (var monitoringTimer = new DirectorySizeMonitoringTimer(targetDirectory, componentUnzippedPath, (progress) => + { + try + { + progressCallback?.Invoke(progress); + } + catch (Exception ex) + { + Logger.Error($"[InstallArchive] progress callback exception: {ex}"); + } + }, remapFrom: unzipSplitPercents, remapTo: 100f)) + { + CommonUtilities.MoveDirectory(componentUnzippedPath, targetDirectory, overwrite: true); + } + } + catch (Exception e) + { + exMoveDirectory = e; + ret = false; + LogError($"Failure to move Android component to its destination path. {e}"); + if (!String.IsNullOrEmpty(oldDestinationPath) && Directory.Exists(oldDestinationPath)) + { + LogError("Attempting to clean up."); + LogError($"Trying to remove directory '{targetDirectory}'"); + CommonUtilities.DeleteDirectoryRecursively(targetDirectory); + + try + { + CommonUtilities.MoveDirectory(oldDestinationPath, targetDirectory); + } + catch (Exception ex) + { + // ignore + LogError($"Failed to rename directory '{oldDestinationPath}' to '{targetDirectory}'. {ex}"); + } + } + } + + bool logError = false; + Exception rex = null; + try + { + CommonUtilities.DeleteDirectoryRecursively(temporaryPath); + if (!String.IsNullOrEmpty(oldDestinationPath)) + logError = !CommonUtilities.DeleteDirectoryRecursively(oldDestinationPath); + } + catch (Exception e) + { + logError = true; + ret = false; + rex = e; + // ignore + } + if (logError) + { + string message = $"Failed to remove old destination directory '{oldDestinationPath}'. User will have to remove it manually."; + if (rex != null) + LogError($"{message}. {rex}"); + else + LogError(message); + } + + if (exMoveDirectory != null) + throw exMoveDirectory; + + return ret; + } + + protected void GeneratePackageXml(string path) + { + LogDebug($"Generating {path}"); + + var settings = new XmlWriterSettings + { + CloseOutput = true, + Encoding = Encoding.UTF8, + Indent = false, + NamespaceHandling = NamespaceHandling.OmitDuplicates, + NewLineHandling = NewLineHandling.None, + OmitXmlDeclaration = false + }; + + using (var writer = XmlWriter.Create(path, settings)) + { + writer.WriteStartDocument(true); + // TODO: check if the namespace exists + writer.WriteStartElement("sdk", "repository", Repository.GetNamespaceUri("sdk")); + if (Repository.Namespaces != null) + { + foreach (var kvp in Repository.Namespaces) + { + if (String.Compare("xsi", kvp.Key, StringComparison.Ordinal) == 0) + continue; + writer.WriteAttributeString("xmlns", kvp.Key, null, kvp.Value.NamespaceName); + } + } + + writer.WriteStartElement("license"); + writer.WriteAttributeString("id", LicenseID ?? String.Empty); + writer.WriteAttributeString("type", License?.Type ?? String.Empty); + writer.WriteString(License?.Text ?? String.Empty); + writer.WriteEndElement(); // license + + writer.WriteStartElement("localPackage"); + writer.WriteAttributeString("path", Path); + writer.WriteAttributeString("obsolete", Obsolete ? "true" : "false"); + + Info?.WritePackageXmlInfo(writer, Repository); + + writer.WriteStartElement("revision"); + if (Revision != null) + { + WriteRevisionPart(writer, "major", Revision.Major); + WriteRevisionPart(writer, "minor", Revision.Minor); + WriteRevisionPart(writer, "micro", Revision.Micro); + WriteRevisionPart(writer, "preview", Revision.Preview); + } + writer.WriteEndElement(); // revision + + writer.WriteStartElement("display-name"); + writer.WriteString(DisplayName); + writer.WriteEndElement(); // display-name + + writer.WriteStartElement("uses-license"); + writer.WriteAttributeString("ref", LicenseID ?? String.Empty); + writer.WriteEndElement(); // uses-license + + if (Dependencies != null && Dependencies.Count > 0) + { + writer.WriteStartElement("dependencies"); + foreach (Dependency dep in Dependencies) + { + if (dep == null) + continue; + writer.WriteStartElement("dependency"); + writer.WriteAttributeString("path", dep.Path ?? String.Empty); + writer.WriteEndElement(); //dependency + } + writer.WriteEndElement(); // dependencies + } + + writer.WriteEndElement(); // localPackage + + writer.WriteEndElement(); // repository + writer.WriteEndDocument(); + } + } + + void WriteRevisionPart(XmlWriter writer, string name, int value) + { + if (value < 0) + return; + + writer.WriteStartElement(name); + writer.WriteValue(value); + writer.WriteEndElement(); + } + + public virtual bool MatchesTo(IAndroidComponent other) + { + if (!(other is BasePackage)) + return false; + + if (ReferenceEquals(this, other)) + return true; + + if (IsPlatformSpecific && !SupportedPlatforms.SequenceEqual((other as BasePackage).SupportedPlatforms)) + return false; + + return String.Compare(Path, other.Path, StringComparison.Ordinal) == 0 && + Revision == other.Revision && + Channel == other.Channel; + } + + public virtual bool BasicMetadataEquals(BasePackage other) + { + if (other == null) + return false; + + if (ReferenceEquals(this, other)) + return true; + + // We compare only the properties that actually matter + if (String.Compare(Path, other.Path, StringComparison.Ordinal) != 0) + return false; + + if (!AreInfosEqual(Info, other.Info)) + return false; + + if (Obsolete != other.Obsolete) + return false; + + if (!Revision.Equals(other.Revision)) + return false; + + return true; + } + + bool AreInfosEqual(AndroidComponentInfo info1, AndroidComponentInfo info2) + { + // If only one of them is null return false + if ((info1 == null) != (info2 == null)) + return false; + + if (info1 != null && !info1.Equals(info2)) + return false; + + return true; + } + + // It's very simple in this case, we're not interested in deep comparison + // of the two instances, but rather whether they are uniquely identified - + // that is they have a different path+version combination. + public virtual bool Equals(BasePackage other) + { + if (!BasicMetadataEquals(other)) + return false; + + if (UniqueID != other.UniqueID) + return false; + + if (!Dependencies.AreEqual(other.Dependencies)) + return false; + + if (!Archives.AreEqual(other.Archives)) + return false; + + return true; + } + + public override bool Equals(object obj) + { + return Equals(obj as BasePackage); + } + + public override int GetHashCode() + { + int hashCode = base.GetHashCode(); + + hashCode = hashCode.XorWith(UniqueID.GetHashCode()); + hashCode = hashCode.XorWith(Path?.GetHashCode()); + hashCode = hashCode.XorWith(Info?.GetHashCode()); + hashCode = hashCode.XorWith(Obsolete.GetHashCode()); + hashCode = hashCode.XorWith(Revision?.GetHashCode()); + hashCode = hashCode.XorWith(Dependencies?.GetHashCode()); + return hashCode.XorWith(Archives?.GetHashCode()); + } + + protected abstract void LogError(string message); + protected abstract void LogWarning(string message); + protected abstract void LogInfo(string message); + protected abstract void LogDebug(string message); + + private bool GetPreview() + { + if (Info is AndroidComponentInfoPlatform platform) + { + return platform.Preview; + } + return false; + } + } +} diff --git a/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/Common/Channel.cs b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/Common/Channel.cs new file mode 100644 index 00000000000..132690fad68 --- /dev/null +++ b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/Common/Channel.cs @@ -0,0 +1,88 @@ +// +// Authors: +// Marek Habersack +// +// Copyright (c) 2017, Microsoft, Inc +// +// All rights reserved. +// +using System; + +using Xamarin.Installer.AndroidSDK.Common; + +namespace Xamarin.Installer.AndroidSDK.Common +{ + /// + /// Describes a single Android SDK channel. Google currently define 4 channels: + /// + /// + /// stable + /// + /// + /// beta + /// + /// + /// dev + /// + /// + /// canary + /// + /// + /// Note however that the list is defined directly in the Android SDK manifest document and may be + /// changed by Google at any time. + /// + public sealed class Channel : ItemWithID, IEquatable + { + /// + /// Gets the channel name + /// + /// Channel name + public string Name { get; } + + /// + /// Initializes a new instance of the class. + /// + /// Identifier. + /// Name. + public Channel (string id, string name) : base (id) + { + if (String.IsNullOrEmpty (name)) + throw new ArgumentException ("Must not be null or empty", nameof (name)); + Name = name; + } + + /// + /// Determines whether the specified is equal to the current . + /// + /// The to compare with the current . + /// true if the specified is equal to the current + /// ; otherwise, false. + public bool Equals (Channel other) + { + if (other == null) + return false; + + return Equals (other as ItemWithID); + } + + /// + /// Determines whether the specified is equal to the current . + /// + /// The to compare with the current . + /// true if the specified is equal to the current + /// ; otherwise, false. + public override bool Equals (object obj) + { + return Equals (obj as Channel); + } + + /// + /// Serves as a hash function for a object. + /// + /// A hash code for this instance that is suitable for use in hashing algorithms and data structures such as a hash table. + public override int GetHashCode () + { + return base.GetHashCode (); + } + } +} diff --git a/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/Common/ConfigManager.cs b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/Common/ConfigManager.cs new file mode 100644 index 00000000000..0390ba89a65 --- /dev/null +++ b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/Common/ConfigManager.cs @@ -0,0 +1,118 @@ +using System; +using System.IO; +using System.Collections.Generic; +using System.Xml; +using System.Xml.Linq; +using Xamarin.Installer.Common; + +namespace Xamarin.Installer.AndroidSDK.Manager +{ + public class ConfigManager + { + const string AppName = "AndroidSDKManager"; + const string PropertiesRootNode = "SDKManagerProperties"; + const string PropertyNode = "Property"; + + Dictionary properties; + + /// + /// SDK Manager configuration folder path + /// + /// + public static string ConfigFolder { + get { + string configFolder; + if (Platform.IsMac) { + var home = Environment.GetFolderPath (Environment.SpecialFolder.UserProfile); + configFolder = Path.Combine (home, "Library", "Preferences"); + } else { + configFolder = Environment.GetFolderPath (Environment.SpecialFolder.ApplicationData); + if (Platform.IsWindows) + configFolder = Path.Combine (configFolder, "Config"); + } + return Path.Combine (configFolder, AppName); + } + } + + string filePath; + string FilePath { + get { + if (filePath == null) + filePath = Path.Combine (ConfigFolder, "properties.xml"); + + return filePath; + } + } + + internal ConfigManager () + { + LoadProperties (); + } + + internal void SetProperty (string key, string value) + { + if (properties.ContainsKey (key)) { + if (String.Compare (properties [key], value, StringComparison.Ordinal) == 0) + return; + properties.Remove (key); + } + + properties.Add (key, value); + } + + internal string GetProperty (string key, string defaultValue) + { + bool success = properties.TryGetValue (key, out string value); + return success ? value : defaultValue; + } + + void LoadProperties () + { + properties = new Dictionary (StringComparer.Ordinal); + if (!File.Exists (FilePath)) + return; + + try + { + var element = XElement.Load (FilePath); + foreach (var node in element.Descendants (PropertyNode)) { + var key = node.Attribute ("key").Value; + var value = node.Attribute ("value").Value; + if (String.IsNullOrEmpty (key)) + continue; + + properties.Add (key, value); + } + } + catch (XmlException ex) + { + // https://devdiv.visualstudio.com/DevDiv/_workitems/edit/723620 + // Overwrite the broken config with an empty xml (we write default property values there later) + Logger.Warning ($"Failed to read the config, resetting it.\nError details: {ex}"); + SaveProperties (); + } + } + + + internal void SaveProperties () + { + var filePath = FilePath; + if (!File.Exists (filePath)) { + var dir = Path.GetDirectoryName (filePath); + if (!Directory.Exists (dir)) + Directory.CreateDirectory (dir); + } + XmlWriter xmlWriter = XmlWriter.Create(FilePath); + + xmlWriter.WriteStartDocument(); + xmlWriter.WriteStartElement(PropertiesRootNode); + foreach (var keyValue in properties) { + xmlWriter.WriteStartElement(PropertyNode); + xmlWriter.WriteAttributeString ("key", keyValue.Key); + xmlWriter.WriteAttributeString ("value", keyValue.Value); + } + xmlWriter.WriteEndElement(); + xmlWriter.Close(); + } + } +} \ No newline at end of file diff --git a/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/Common/Constants.cs b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/Common/Constants.cs new file mode 100644 index 00000000000..8a280aa0731 --- /dev/null +++ b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/Common/Constants.cs @@ -0,0 +1,46 @@ +// Authors: +// Marek Habersack +// +// Copyright (c) 2017, Microsoft, Inc +// +// All rights reserved. +// +using System; + +namespace Xamarin.Installer.AndroidSDK.Common +{ +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member + public static partial class Constants + { + public static class ComponentPaths + { + public const string BuildTools = "build-tools;"; + public const string CMake = "cmake;"; + public const string Emulator = "emulator"; + public const string LLDB = "lldb;"; + public const string NDK = "ndk-bundle"; + public const string PlatformTools = "platform-tools"; + public const string ToolsObsolete = "tools"; + public const string Tools = "cmdline-tools"; + public const string ToolsLatest = "cmdline-tools;latest"; + + public const string Platforms = "platforms"; + + public const string HypervisorDriver = "extras;google;Android_Emulator_Hypervisor_Driver"; + } + + public static partial class RequiredComponentVersions + { + // This class has properties which are auto generated by the GenerateRequiredComponentVersions MSBuild Target + + // this defaults are used by the IDEs to complete the required components on the auto-sdk process + // values are in sync with the GetAndroidDependencies target result + public static readonly string[] RequiredComponents = { + $"build-tools/{RequiredBuildToolsVersion}@{RequiredBuildToolsVersion}", + $"platform-tools@{RequiredPlatformToolsVersion}", + $"cmdline-tools/{RequiredCommandLineToolsVersion}@{RequiredCommandLineToolsVersion}", + }; + } + } +#pragma warning restore CS1591 // Missing XML comment for publicly visible type or member +} diff --git a/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/Common/CustomVSAndroidLicensesStorage.cs b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/Common/CustomVSAndroidLicensesStorage.cs new file mode 100644 index 00000000000..1f31a937aae --- /dev/null +++ b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/Common/CustomVSAndroidLicensesStorage.cs @@ -0,0 +1,49 @@ +using System; +using System.IO; +using System.Linq; +using System.Security.Cryptography; +using System.Text; + +namespace Xamarin.Installer.AndroidSDK.Common +{ + public class CustomVSAndroidLicensesStorage : AndroidLicensesStorage + { + protected override string GetLicensesPath(string androidSdkPath) + { + if (string.IsNullOrWhiteSpace(androidSdkPath) || !Directory.Exists(androidSdkPath)) + { + return null; + } + + var androidSdkId = GetLicensesLocalStorageIdFor(androidSdkPath); + var winHome = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData); + return Path.Combine(winHome, "Xamarin", "Mono for Android", "Licenses", androidSdkId); + } + + string GetLicensesLocalStorageIdFor(string androidSdkPath) + { + using (var hashAlgorithm = SHA256.Create()) + { + var bytes = Encoding.UTF8.GetBytes(androidSdkPath.ToLowerInvariant()); + var hash = string.Concat(hashAlgorithm.ComputeHash(bytes).Select(x => x.ToString("x2"))); + return hash; + } + } + + protected override string GetLicenseHash(License license) + { + if (string.IsNullOrEmpty(license?.Text)) + { + return null; + } + + var content = string.IsNullOrEmpty(license.Text) ? license.ID : license.Text; + using (var hashAlgorithm = SHA256.Create()) + { + var bytes = Encoding.UTF8.GetBytes(content.ToLowerInvariant()); + var hash = string.Concat(hashAlgorithm.ComputeHash(bytes).Select(x => x.ToString("x2"))); + return hash; + } + } + } +} diff --git a/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/Common/Dependency.cs b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/Common/Dependency.cs new file mode 100644 index 00000000000..5a425edd2ba --- /dev/null +++ b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/Common/Dependency.cs @@ -0,0 +1,102 @@ +// +// Authors: +// Marek Habersack +// +// Copyright (c) 2017, Microsoft, Inc +// +// All rights reserved. +// +using System; + +namespace Xamarin.Installer.AndroidSDK.Common +{ + /// + /// Describes a single package dependency + /// + public sealed class Dependency : IEquatable + { + /// + /// Manifest path to the dependency + /// + /// Dependency path + public string Path { get; } + + /// + /// Gets the minimum revision of the dependency package + /// + /// The minimum revision. + public AndroidRevision MinRevision { get; } + + public AndroidSDKPlatform Platform { get; set; } = AndroidSDKPlatform.Unknown; + public bool IsPlatformSpecific => Platform != AndroidSDKPlatform.Any && Platform != AndroidSDKPlatform.Unknown; + + internal Dependency (string path, AndroidRevision minRevision = null) + { + if (String.IsNullOrEmpty (path)) + throw new ArgumentException ("must not be null or empty", nameof (path)); + + Path = path; + MinRevision = minRevision; + } + + internal Dependency (Dependency other) + { + if (other == null) + throw new ArgumentNullException (nameof (other)); + + Path = other.Path; + if (other.MinRevision != null) + MinRevision = new AndroidRevision (other.MinRevision); + Platform = other.Platform; + } + + /// + /// Determines whether the specified is equal to the + /// current . + /// + /// The to compare with the current . + /// true if the specified is equal to the current + /// ; otherwise, false. + public bool Equals (Dependency other) + { + if (other == null) + return false; + + if (ReferenceEquals (this, other)) + return true; + + if (String.Compare (Path, other.Path, StringComparison.Ordinal) != 0) + return false; + + if (MinRevision == null) { + if (other.MinRevision != null) + return false; + } else if (!MinRevision.Equals (other.MinRevision)) + return false; + + return Platform == other.Platform; + } + + /// + /// Determines whether the specified is equal to the current . + /// + /// The to compare with the current . + /// true if the specified is equal to the current + /// ; otherwise, false. + public override bool Equals (object obj) + { + return Equals (obj as Dependency); + } + + /// + /// Serves as a hash function for a object. + /// + /// A hash code for this instance that is suitable for use in hashing algorithms and data structures such as a hash table. + public override int GetHashCode () + { + int hashCode = base.GetHashCode (); + hashCode = hashCode.XorWith (Path?.GetHashCode ()); + return hashCode.XorWith (MinRevision?.GetHashCode ()); + } + } +} diff --git a/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/Common/Helper.cs b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/Common/Helper.cs new file mode 100644 index 00000000000..749e4556007 --- /dev/null +++ b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/Common/Helper.cs @@ -0,0 +1,470 @@ +// +// Helper.cs +// +// Author: +// Vsevolod Kukol +// +// Copyright (c) 2017, Microsoft, Inc +// + +using System; +using System.IO; +using System.Net; +using System.Net.Http; +using ICSharpCode.SharpZipLib.Zip; +using Xamarin.Installer.Common; +using System.Collections.Concurrent; +using System.Threading.Tasks; +using Xamarin.Installer.AndroidSDK.Common; +using System.Threading; +using System.Linq; +using Polly; +using Polly.Retry; + +#if !WINDOWS +using Mono.Unix.Native; +#endif + +using System.Runtime.InteropServices; + +namespace Xamarin.Installer.AndroidSDK.Manager +{ + public class Helper : IHelpers + { + public static readonly TimeSpan HttpTimeout = TimeSpan.FromSeconds (Platform.IsWindows ? 60 : 10); + static string homeDirectory; + ConfigManager config = new ConfigManager (); + + public string CacheFolder { get; set; } + + static ConcurrentDictionary> stringDownloadCache = new ConcurrentDictionary> (); + + public Helper (string cacheFolder = null) + { + CacheFolder = cacheFolder; + } + + public static string HomeDirectory { + get + { + if (string.IsNullOrEmpty(homeDirectory)) { + homeDirectory = Environment.GetEnvironmentVariable("HOME"); + if (String.IsNullOrEmpty(homeDirectory) || !Directory.Exists(homeDirectory)) { + homeDirectory = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile); + } + } + return homeDirectory; + } + } + + string IHelpers.HomeDirectory { + get { + return HomeDirectory; + } + } + + public bool IsArm64 + { + get { + return RuntimeInformation.ProcessArchitecture == Architecture.Arm64; + } + } + + public bool Is64BitOS { + get { + return Environment.Is64BitOperatingSystem; + } + } + + public bool IsCaseSensitiveFileSystem { + get { return true; } // TODO + } + + public string UserName { + get { + if (Platform.IsWindows) + return Environment.UserName; + return Environment.GetEnvironmentVariable ("USER"); + } + } + + public bool DownloadToString (Uri url, out string output) + { +#if DEBUG + // DEBUG: support setting XAMARIN_ANDROID_MANIFEST_URL=file:///… for local manifest authoring. + if (url.Scheme == "file") + { + output = File.ReadAllText(url.LocalPath); + return true; + } +#else + // Release: only allow https://, plus the in-box file:// fallback manifest that + // ships alongside this assembly (XamarinRepository.GetFallbackManifestUrl). + if (url.Scheme == "file") { + var assemblyDir = Path.GetDirectoryName (typeof (Helper).Assembly.Location); + var requestedDir = Path.GetDirectoryName (url.LocalPath); + if (!string.IsNullOrEmpty (assemblyDir) && + string.Equals (assemblyDir, requestedDir, StringComparison.OrdinalIgnoreCase)) { + output = File.ReadAllText (url.LocalPath); + return true; + } + Logger.Warning ($"Ignoring file:// manifest URL '{url}': file:// is only honored for the in-box fallback manifest in shipped builds."); + output = null; + return false; + } + if (url.Scheme != "https") { + Logger.Warning ($"Ignoring manifest URL '{url}': only https:// URLs are honored in shipped builds."); + output = null; + return false; + } +#endif + + // FIXME: returning false on failure doesn't make Installer.Discover return false. + // hence we need a hard throw on failure to detect it on higher levels + //try { + + using (var client = HttpClientProvider.CreateHttpClient (url)) { + client.Timeout = HttpTimeout; + var req = new HttpRequestMessage (HttpMethod.Get, url); + + if (stringDownloadCache.TryGetValue(url, out Tuple cache)) { + req.Headers.IfModifiedSince = cache.Item1; + output = cache.Item2; + } + + //try { + using (var res = client.SendAsync (req, HttpCompletionOption.ResponseHeadersRead).Result) { + if (res.StatusCode == HttpStatusCode.NotModified) { + output = cache.Item2; + return true; + } + if (res.StatusCode != HttpStatusCode.OK) { + Logger.Warning ($"Unable to download manifest xml from {url}. Status Code: {res.StatusCode} - {(int)res.StatusCode}"); + output = null; + return false; + } + output = res.Content.ReadAsStringAsync ().Result; + DateTimeOffset? lastModified = res.Content.Headers.LastModified; + if (lastModified.HasValue) + stringDownloadCache[url] = Tuple.Create (lastModified.Value, output); + return true; + } + //} catch (Exception ex) { + // Logger.Exception ($"Downloading '{url}' failed", ex); + // output = null; + // return false; + //} + } + } + + public string GetPathForDownloadId (Guid id) + { + return Path.Combine (Path.GetTempPath (), "xamarin-android-sdk", id.ToString () + ".zip"); + } + + public ulong GetUrlContentLength (Uri url) + { + using (var client = HttpClientProvider.CreateHttpClient (url)) { + client.Timeout = HttpTimeout; + var request = new HttpRequestMessage(HttpMethod.Head, url); + var resp = client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead).Result; + if (resp.IsSuccessStatusCode) + return (ulong)(resp.Content.Headers.ContentLength ?? 0); + return 0; + } + } + + public bool IsSpecialFile (string filePath) + { + return false; + } + + public void CopySpecialFile (string source, string target) + { + throw new NotImplementedException (); + } + + public class CustomTooLongPathException : Exception + { + public Exception OriginalException { get; set; } + } + + public string Unzip (string baseDirectory, string archivePath, string fileOwnerName = null) + { + return Unzip (baseDirectory, archivePath, fileOwnerName, progressCallback: null); + } + + public string Unzip (string baseDirectory, string archivePath, string fileOwnerName = null, + InstallationProgressEventArgs.InstallationProgressActionDelegate progressCallback = null) + { + if (string.IsNullOrEmpty (baseDirectory)) + throw new ArgumentNullException (nameof (baseDirectory), "No base directory given, unable to unzip."); + if (string.IsNullOrEmpty (archivePath)) + throw new ArgumentNullException (nameof (archivePath), "No ZIP archive specified."); + Logger.Info ($"Unzipping file '{archivePath}' to directory '{baseDirectory}'"); + + if (!Directory.Exists (baseDirectory)) { + try { + Directory.CreateDirectory (baseDirectory); + // TODO: set directory owner and permissions + } catch (Exception ex) { + Logger.Exception ($"Failed to create base directory '{baseDirectory}' to unzip archive.", ex); + return null; + } + } + + string lastDestPath = String.Empty; + ZipFile zip = null; + + try + { + zip = new ZipFile (archivePath); + + float minProgressDelta = 5f; // percents + float lastProgress = 0f; + float progress; + + ulong totalSize = DirectorySizeMonitoringTimer.CalculateTotalSize (zip); + ulong currentSize = 0, gradualSize; + try { + progressCallback?.Invoke (0f); + } catch (Exception ex) { + Logger.Error ($"[Unzip] progress callback exception: {ex}"); + } + + string fullBaseDirectory = Path.GetFullPath (baseDirectory).TrimEnd (Path.DirectorySeparatorChar) + Path.DirectorySeparatorChar; + + foreach (ZipEntry entry in zip) { + string destPath = Path.Combine (baseDirectory, entry.Name); + lastDestPath = destPath; + + if (!Path.GetFullPath (destPath).StartsWith (fullBaseDirectory, StringComparison.OrdinalIgnoreCase)) { + throw new Exception ($"Archive file '{archivePath}' has parent traversal in paths"); + } + + if (entry.IsDirectory) { + Directory.CreateDirectory (destPath); + SafeSetLastWriteTime (destPath, entry.DateTime); + continue; + } + + Directory.CreateDirectory (Path.GetDirectoryName (destPath)); + + byte [] buf = new byte [4096]; + int n = 0; + gradualSize = 0; + ulong iteration = 0; + using (var fs = new FileStream (destPath, FileMode.Create, FileAccess.Write, FileShare.None)) { + using (var inZip = zip.GetInputStream (entry)) { + while ((n = inZip.Read (buf, 0, buf.Length)) > 0) { + fs.Write (buf, 0, n); + gradualSize += (ulong) n; + iteration++; + + if (iteration % 1000 == 0 && progressCallback != null) { + progress = (currentSize + gradualSize) * 100f / totalSize; + if (progress - lastProgress > minProgressDelta) { + try { + progressCallback?.Invoke (progress); + } catch (Exception ex) { + Logger.Error ($"[Unzip] progress callback exception: {ex}"); + } + lastProgress = progress; + } + } + } + } + currentSize += (ulong) fs.Length; + } + SafeSetLastWriteTime (destPath, entry.DateTime); + SetFileAttributes (destPath, entry.IsDOSEntry, entry.ExternalFileAttributes); + // TODO: set owner + + progress = currentSize * 100f / totalSize; + if (progress - lastProgress > minProgressDelta) { + try { + progressCallback?.Invoke (progress); + } catch (Exception ex) { + Logger.Error ($"[Unzip] progress callback exception: {ex}"); + } + lastProgress = progress; + } + } + + try { + progressCallback?.Invoke (100f); + } catch (Exception ex) { + Logger.Error ($"[Unzip] progress callback exception: {ex}"); + } + } catch (DirectoryNotFoundException ex) { + Logger.Error ($"Unzip failed with DirectoryNotFoundException error: {ex}"); + if (lastDestPath.Length >= 240) { + Logger.Error ($"Try enabling long paths support to resolve this issue: https://blogs.msdn.microsoft.com/jeremykuhne/2016/07/30/net-4-6-2-and-long-paths-on-windows-10/"); + throw new CustomTooLongPathException { OriginalException = ex }; + } + throw; + } finally { + zip?.Close (); + } + + string topDirectory = string.Empty; + string [] entries = Directory.GetDirectories (baseDirectory); + + if (entries.Length == 1) + topDirectory = entries [0]; + + string ret = Path.GetFullPath (Path.Combine (baseDirectory, topDirectory)); + Logger.Info ($"Archive unzipped to '{ret}'"); + + return ret; + } + + static void SafeSetLastWriteTime (string path, DateTime stamp) + { + try { + if (Directory.Exists (path)) + Directory.SetLastWriteTime (path, stamp); + else if (File.Exists (path)) + File.SetLastWriteTime (path, stamp); + } catch { + // Just ignoring this exception, time stamp is not THAT important... + } + } + + static void SetFileAttributes (string filePath, bool dosAttributes, int zipAttributes) + { + if (String.IsNullOrEmpty(filePath) || !File.Exists(filePath)) + return; + if (dosAttributes && zipAttributes != -1) { + var fileAttributes = (FileAttributes)zipAttributes; + fileAttributes &= (FileAttributes.Archive | FileAttributes.Normal | FileAttributes.ReadOnly | FileAttributes.Hidden); + File.SetAttributes(filePath, fileAttributes); + } else + File.SetAttributes(filePath, FileAttributes.Normal); + + #if !WINDOWS + if (Platform.IsWindows) + return; + + zipAttributes = (zipAttributes >> 16); + + if (zipAttributes == -1) + return; + + FilePermissions permissions = unchecked((FilePermissions)zipAttributes); + if (permissions == 0) { + // 644 octal + permissions = FilePermissions.S_IRUSR | + FilePermissions.S_IWUSR | + FilePermissions.S_IRGRP | + FilePermissions.S_IROTH; + } else if ((permissions & FilePermissions.S_IRUSR) == 0) + permissions |= FilePermissions.S_IRUSR; // owner must be able to read the file + + if (Syscall.chmod(filePath, permissions) == -1) + throw new InvalidOperationException (String.Format("Failed to set file attributes for '{0}'. {1}", + filePath, Stdlib.strerror(Stdlib.GetLastError()))); + #endif + } + + public bool URLExists (Uri url) + { + throw new NotImplementedException (); + } + + public string GetRegistryKeyValue (string subKeyPath, string keyName, bool check64Node) + { + throw new NotImplementedException (); + } + + public string GetPluralString (string s, string p, int n) + { + return n == 1 ? s : p; + } + + public string GetString (string s) + { + return s; + } + + public string GetProperty (string key, string defaultValue = "") + { + return config.GetProperty (key, defaultValue); + } + + public void SetProperty (string key, string value) + { + config.SetProperty (key, value); + config.SaveProperties (); + } + + public async Task CheckIfNetworkIsAvailableAsync () + { + var policyResult = await ResiliencyPolicies.AsyncRetryIfTimeout (3) + .ExecuteAndCaptureAsync (() => CheckIfNetworkAvailable ()); + if (policyResult.Outcome == OutcomeType.Successful) + return policyResult.Result; + + return false; + } + + private Uri GetUrlToCheckIfNetworkAvailable() + { + var customURL = Environment.GetEnvironmentVariable("ANDROID_SDK_INSTALLER_CHECK_NETWORK_URL"); + if (!string.IsNullOrEmpty(customURL) && Uri.TryCreate(customURL, UriKind.Absolute, out Uri url)) + { + return url; + } else + { + return new Uri("https://dotnet.microsoft.com/"); + } + } + + async Task CheckIfNetworkAvailable () + { + try { + var uri = GetUrlToCheckIfNetworkAvailable(); + using (var client = HttpClientProvider.CreateHttpClient (uri)) { + client.Timeout = HttpTimeout; + var request = new HttpRequestMessage (HttpMethod.Get, uri); + using (var response = await client.SendAsync (request, HttpCompletionOption.ResponseHeadersRead)) + return response.IsSuccessStatusCode; + } + } catch (TimeoutException) { + throw; + } catch (TaskCanceledException) { + throw; + } catch (HttpRequestException) { + return false; + } catch (AggregateException ex) { + foreach (var innerEx in ex.Flatten ().InnerExceptions) + if (!(innerEx is HttpRequestException)) + Logger.Warning ($"An exception occurred while checking network availability.\n{innerEx}"); + + if (ex.Flatten ().InnerException is TaskCanceledException) + throw ex.Flatten ().InnerException; + + return false; + } catch (Exception ex) { + Logger.Warning ($"An exception occurred while checking network availability.\n{ex}"); + return false; + } + } + + public static class ResiliencyPolicies + { + public static AsyncRetryPolicy AsyncRetryIfTimeout (int numberOfRetries, Action actionToPerformOnRetry = null) + { + return Policy + .Handle () + .Or() + .WaitAndRetryAsync (numberOfRetries, + (x) => TimeSpan.FromSeconds (1), + (exception, timespan) => { + Logger.Warning ($"[Resiliency.Polly.Policies]: Delegate has thrown {exception}, retrying..."); + actionToPerformOnRetry?.Invoke (); + }); + } + } + } +} diff --git a/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/Common/HttpClientProvider.cs b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/Common/HttpClientProvider.cs new file mode 100644 index 00000000000..bd8aae936b7 --- /dev/null +++ b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/Common/HttpClientProvider.cs @@ -0,0 +1,73 @@ +using System; +using System.Net; +using System.Net.Http; +using System.Net.Security; +using System.Security.Cryptography.X509Certificates; + +namespace Xamarin.Installer.AndroidSDK.Manager +{ + /// + /// Creates a HttpClient. + /// + public static class HttpClientProvider + { + static Func httpClientFactory; + + /// + /// Allows the default HttpClient creation to be overridden. + /// + /// Factory method used to create the HttpClient. + public static void SetHttpClientFactory (Func factory) + { + httpClientFactory = factory; + } + + /// + /// Creates a new HttpClient. + /// + /// The HttpClient. + /// The request url. + public static HttpClient CreateHttpClient (Uri uri, CookieContainer cookieContainer = null, DecompressionMethods automaticDecompression = DecompressionMethods.None) + { + if (httpClientFactory != null) + return httpClientFactory.Invoke (uri); + + var handler = new HttpClientHandler + { + AutomaticDecompression = automaticDecompression, + CheckCertificateRevocationList = true, + ServerCertificateCustomValidationCallback = SslValidationCallback + }; + + if (cookieContainer != null) + { + handler.CookieContainer = cookieContainer; + } + + if (WebRequest.DefaultWebProxy != null) + handler.Proxy = WebRequest.DefaultWebProxy; + + var client = new HttpClient(handler); + return client; + } + + static bool SslValidationCallback(HttpRequestMessage request, X509Certificate2 certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors) + { + if (sslPolicyErrors == SslPolicyErrors.None) + return true; + + if (sslPolicyErrors != SslPolicyErrors.RemoteCertificateChainErrors) + return false; + + // If all we have is "Revocation Unknown" errors, then accept the certificate. Works around Enterprise proxy issues + // such as the one described in https://devdiv.visualstudio.com/DevDiv/_workitems/edit/2057013 + foreach (var status in chain.ChainStatus) + { + if (status.Status != X509ChainStatusFlags.NoError && status.Status != X509ChainStatusFlags.RevocationStatusUnknown) + return false; + } + + return true; + } + } +} \ No newline at end of file diff --git a/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/Common/IAndroidApiLevel.cs b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/Common/IAndroidApiLevel.cs new file mode 100644 index 00000000000..c90169182dd --- /dev/null +++ b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/Common/IAndroidApiLevel.cs @@ -0,0 +1,24 @@ +// +// Authors: +// Marek Habersack +// +// Copyright (c) 2017, Microsoft Corp. (http://microsoft.com) +// +// All rights reserved. +// +using System; + +namespace Xamarin.Installer.AndroidSDK.Common +{ + /// + /// Helper interface representing a component's API level + /// + public interface IAndroidApiLevel + { + /// + /// Gets the API level. + /// + /// The API level. + string ApiLevel { get; } + } +} diff --git a/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/Common/IAndroidArchive.cs b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/Common/IAndroidArchive.cs new file mode 100644 index 00000000000..23892f3df40 --- /dev/null +++ b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/Common/IAndroidArchive.cs @@ -0,0 +1,15 @@ +using System; + +namespace Xamarin.Installer.AndroidSDK.Common +{ + interface IAndroidArchive + { + string OS { get; } + uint OSBits { get; } + string Arch { get; } + uint Size { get; } + string Checksum { get; } + string ChecksumType { get; } + Uri Url { get; } + } +} diff --git a/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/Common/IAndroidComponent.cs b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/Common/IAndroidComponent.cs new file mode 100644 index 00000000000..57a2e84ce88 --- /dev/null +++ b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/Common/IAndroidComponent.cs @@ -0,0 +1,165 @@ +// +// Authors: +// Marek Habersack +// +// Copyright (c) 2017, Microsoft, Inc +// +// All rights reserved. +// +using System; +using System.Collections.Generic; + +namespace Xamarin.Installer.AndroidSDK.Common +{ + /// + /// Interface fully describing a single Android component + /// + public interface IAndroidComponent + { + /// + /// Occurs when status changes. + /// + event EventHandler StatusChanged; + + /// + /// true if this component is required for an instance of Android SDK to work properly + /// + bool IsEssential { get; } + + /// + /// Gets a value indicating whether this component ignores installation failures. + /// + /// true if installation failure is ignored; otherwise, false. + bool IgnoreFailure { get; } + + /// + /// Gets or sets a value indicating whether the component is present in the given Android SDK instance on disk. + /// + /// true if present; otherwise, false. + bool Present { get; } + + /// + /// Gets or sets a value indicating whether this component has to be + /// updated. It will be set to true also of is false + /// + /// true if needs update; otherwise, false. + bool NeedsUpdate { get; } + + /// + /// Type of the component. This is a map from the textual type name in the repository manifest into an + /// enumeration for easier searching and component identification + /// + /// The type of the component. + AndroidComponentType ComponentType { get; } + + /// + /// Gets the unique identifier of this particular instance. The ID is not persistent, it is valid only within + /// a single installer session and used to retrieve download information from the hosting application. + /// + /// + /// The unique identifier. + Guid UniqueID { get; } + + /// + /// Gets package path as found in the SDK repository manifest. Note that paths aren't necessarily unique, there + /// may exist several packages with the same path but different version. + /// + /// The manifest path. + string Path { get; } + + /// + /// Gets the file system path where the package is (or will be) found, relative to the given Android SDK root + /// directory. This is, essentially, value of the property converted to the proper path + /// representation for the current operating system. + /// + /// The file system path. + string FileSystemPath { get; } + + /// + /// Gets a value indicating whether this is obsolete. + /// + /// true if obsolete; otherwise, false. + bool Obsolete { get; } + + /// + /// Type-specific information about this component + /// + /// Component type-specific information + AndroidComponentInfo Info { get; } + + /// + /// Gets the available component revision. + /// + /// Component revision. + AndroidRevision Revision { get; } + + /// + /// Gets the revision of the component as found in the given Android SDK root, if any. + /// + /// Revision of the installed component + AndroidRevision InstalledRevision { get; } + + /// + /// Gets the display name of the component + /// + /// Component display name + string DisplayName { get; } + + /// + /// Gets the detailed description of the component + /// + /// Detailed description of the component + string DetailedDescription { get; } + + /// + /// Gets the component dependencies, if any. + /// + /// Component dependencies + IList Dependencies { get; } + + /// + /// Gets the component channel. + /// + /// Component channel + Channel Channel { get; } + + /// + /// Gets the component archives. Only archives valid for the current operating system will be found here. + /// + /// Component archives + IList Archives { get; } + + /// + /// Gets the component license. + /// + /// Component license + License License { get; } + + /// + /// Gets the URL of the manifest from which the component description was loaded. + /// + /// The manifest URL. + Uri ManifestURL { get; } + + /// + /// Hosting application can set this to true if it wants the installer to ignore value of the + /// property and force (re)installation of the component. This property must be set before the component is passed to the + /// method. + /// + /// true to force installation; otherwise, false. + bool ForceInstallation { get; set; } + + /// + /// ID of the license the component uses + /// + string LicenseID { get; set; } + + /// + /// Compares properties related to Path and Revision, + /// and returns True, if they are same for the matching components. + /// + /// Component to compare with + /// + bool MatchesTo (IAndroidComponent other); + } +} diff --git a/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/Common/IAndroidComponentDescriptor.cs b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/Common/IAndroidComponentDescriptor.cs new file mode 100644 index 00000000000..4f82fef7d7d --- /dev/null +++ b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/Common/IAndroidComponentDescriptor.cs @@ -0,0 +1,29 @@ +using System; +using System.Collections.Generic; +using System.Reflection; +using System.Text; +using System.Xml; + +using Xamarin.Installer.Common; + +namespace Xamarin.Installer.AndroidSDK.Common +{ + interface IAndroidComponentDescriptor + { + Uri BaseURL { get; } + AndroidRevision Revision { get; } + string PreviewRevision { get; } + string Description { get; } + Uri DescriptionUrl { get; } + string UsesLicense { get; } + bool IsObsolete { get; } + bool IsPreview { get; } + IList AddonsInfo { get; } + + string Name { get; } + bool IsEssential { get; } + + IAndroidComponent CreateComponent(AndroidSDKInstaller installer); + IAndroidArchive GetArchive(string os, string arch, uint osbits, bool quiet = false); + } +} diff --git a/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/Common/IAndroidComponentInternal.cs b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/Common/IAndroidComponentInternal.cs new file mode 100644 index 00000000000..6a28f594672 --- /dev/null +++ b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/Common/IAndroidComponentInternal.cs @@ -0,0 +1,20 @@ +// +// Authors: +// Marek Habersack +// +// Copyright (c) 2018, Microsoft, Inc +// +// All rights reserved. +// +using System; +using System.Collections.Generic; + +namespace Xamarin.Installer.AndroidSDK.Common +{ + interface IAndroidComponentInternal + { + void Remove (string androidSDKRoot); + void PerformDetection (string androidSDKRoot, bool isRefresh = false); + void RefreshMetadata (IAndroidComponent component, bool ignoreInstalledState = true); + } +} diff --git a/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/Common/IArchive.cs b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/Common/IArchive.cs new file mode 100644 index 00000000000..f05530d9c1f --- /dev/null +++ b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/Common/IArchive.cs @@ -0,0 +1,53 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Xamarin.Installer.AndroidSDK.Common +{ + public interface IArchive + { + /// + /// Gets the archive size + /// + /// Archive size + ulong Size { get; } + + /// + /// Gets the SHA1 checksum of the archive + /// + /// SHA1 checksum + string Checksum { get; } + + /// + /// Gets the archive URL + /// + /// Archive URL + Uri Url { get; } + + /// + /// Gets the target (host) operating system of the archive (if any) + /// + /// Host operating system + string HostOS { get; } + + /// + /// Mapping of into one of the enumeration members + /// + /// Archive platform. + AndroidSDKPlatform Platform { get; } + + /// + /// Native word size of the host operating system. + /// + /// Host operating system word size + uint HostBits { get; } + + /// + /// CPU Architecture of the host operating system. + /// + /// Host operating CPU architecture + string HostArch { get; } + } +} diff --git a/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/Common/IJdkComponent.cs b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/Common/IJdkComponent.cs new file mode 100644 index 00000000000..0a04fb4ba7a --- /dev/null +++ b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/Common/IJdkComponent.cs @@ -0,0 +1,30 @@ +using System; +using System.Collections.Generic; + +namespace Xamarin.Installer.AndroidSDK.Common +{ + public interface IJdkComponent + { + Guid UniqueID { get; } + + string DisplayName { get; } + + bool Obsolete { get; } + + bool Preview { get; } + + string LicenseID { get; } + + PackageVendor Vendor { get; } + + AndroidRevision Revision { get; } + + /// + /// Gets the component archives + /// + /// Component archives + IList Archives { get; } + + IEnumerable GetValidArchivesForSystem(); + } +} diff --git a/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/Common/ILicensesStorage.cs b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/Common/ILicensesStorage.cs new file mode 100644 index 00000000000..de4ece78fc7 --- /dev/null +++ b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/Common/ILicensesStorage.cs @@ -0,0 +1,36 @@ +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace Xamarin.Installer.AndroidSDK.Common +{ + public interface ILicensesStorage + { + /// + /// Returns if the license was accepted in this Android SDK + /// + /// Android Sdk path + /// license to check + /// true if the license is accepted, false otherwise + bool IsLicenseAccepted(string androidSdkPath, License license); + + /// + /// Accept licenses + /// + /// Android Sdk path + /// Java Sdk path + /// specific cmdLine-tools path to use + /// list of licenses to accept + /// cancellation token + /// log file path + /// throw exception with arguments validation error + Task AcceptLicensesAsync( + string androidSdkPath, + string javaSdkPath, + string cmdLineToolsPath, + IEnumerable licenses, + CancellationToken token, + string logPath = null, + bool throwsErrorIfValidationFailed = false); + } +} diff --git a/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/Common/ItemWithID.cs b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/Common/ItemWithID.cs new file mode 100644 index 00000000000..8fc7a286ddd --- /dev/null +++ b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/Common/ItemWithID.cs @@ -0,0 +1,59 @@ +// +// Authors: +// Marek Habersack +// +// Copyright (c) 2017, Marek Habersack +// +// All rights reserved. +// +using System; + +namespace Xamarin.Installer.AndroidSDK.Common +{ + /// + /// Base class for all types with a string identifier + /// + public abstract class ItemWithID : IEquatable + { + /// + /// Gets the identifier string. + /// + /// The identifier string + public string ID { get; } + + /// + /// Initializes a new instance of the class. + /// + /// Identifier (required) + protected ItemWithID (string id) + { + if (String.IsNullOrEmpty (id)) + throw new ArgumentException ("Must not be null or empty", nameof (id)); + ID = id; + } + + public bool Equals (ItemWithID other) + { + if (other == null) + return false; + + if (ReferenceEquals (this, other)) + return true; + + if (String.Compare (ID, other.ID, StringComparison.Ordinal) != 0) + return false; + + return true; + } + + public override bool Equals (object obj) + { + return Equals (obj as ItemWithID); + } + + public override int GetHashCode () + { + return ID?.GetHashCode () ?? 0; + } + } +} diff --git a/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/Common/ItemWithIDAndDisplay.cs b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/Common/ItemWithIDAndDisplay.cs new file mode 100644 index 00000000000..5f3a4eedf72 --- /dev/null +++ b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/Common/ItemWithIDAndDisplay.cs @@ -0,0 +1,34 @@ +// +// Authors: +// Marek Habersack +// +// Copyright (c) 2017, Microsoft, Inc (http://microsoft.com) +// +// All rights reserved. +// +using System; + +namespace Xamarin.Installer.AndroidSDK.Common +{ + /// + /// Abstract base class for all the clases that have some sort of identifier and a display name. + /// + public abstract class ItemWithIDAndDisplay : ItemWithID + { + /// + /// Gets the display name of the item + /// + /// Item display name + public string Display { get; } + + /// + /// Initializes a new instance of the class. + /// + /// Identifier. + /// Display. + protected ItemWithIDAndDisplay (string id, string display) : base (id) + { + Display = display ?? String.Empty; + } + } +} diff --git a/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/Common/JdkArchive.cs b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/Common/JdkArchive.cs new file mode 100644 index 00000000000..1ab925ed3b5 --- /dev/null +++ b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/Common/JdkArchive.cs @@ -0,0 +1,33 @@ +using System; + +namespace Xamarin.Installer.AndroidSDK.Common +{ + public class JdkArchive : Archive, IEquatable + { + public JdkArchive(string hostOS) : base(hostOS) + { + } + + public string PayloadFileName { get; set; } + + public override bool IsValidForSystem() + { + return IsPlatformValid() && IsHostArchValid(); + } + + public override bool Equals(Object obj) + { + return Equals(obj as JdkArchive); + } + + public bool Equals(JdkArchive other) + { + return base.Equals(other); + } + + public override int GetHashCode() + { + return this.HostOS.GetHashCode(); + } + } +} diff --git a/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/Common/JdkPackage.cs b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/Common/JdkPackage.cs new file mode 100644 index 00000000000..100f374ea9f --- /dev/null +++ b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/Common/JdkPackage.cs @@ -0,0 +1,41 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Xamarin.Installer.AndroidSDK.Common +{ + public class JdkPackage : IJdkComponent + { + public JdkPackage(string displayName, bool obsolete, bool preview, string licenseId, PackageVendor vendor, AndroidRevision revision, List archives) + { + DisplayName = displayName; + Obsolete = obsolete; + Preview = preview; + Vendor = vendor; + Revision = revision; + Archives = archives; + LicenseID = licenseId; + } + + public Guid UniqueID { get; } = Guid.NewGuid(); + + public string DisplayName { get; } + + public bool Obsolete { get; } + + public bool Preview { get; } + + public string LicenseID { get; } + + public PackageVendor Vendor { get; } + + public AndroidRevision Revision { get; } + + public IList Archives { get; } + + public IEnumerable GetValidArchivesForSystem() + { + return Archives.Where(x => x.IsValidForSystem()); + } + } +} diff --git a/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/Common/License.cs b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/Common/License.cs new file mode 100644 index 00000000000..86e626e0e7e --- /dev/null +++ b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/Common/License.cs @@ -0,0 +1,86 @@ +// +// Authors: +// Marek Habersack +// +// Copyright (c) 2017, Microsoft, Inc +// +// All rights reserved. +// +using System; + +namespace Xamarin.Installer.AndroidSDK.Common +{ + /// + /// Represents component License + /// + public sealed class License : ItemWithID, IEquatable + { + /// + /// Content type. Currently only text is defined. + /// + /// Content type. + public string Type { get; } + + /// + /// Gets text of the license. + /// + /// License text + public string Text { get; } + + internal License (string id, string type, string text) : base (id) + { + Type = type; + Text = text; + } + + /// + /// Determines whether the specified is equal to the current . + /// + /// The to compare with the current . + /// true if the specified is equal to the current + /// ; otherwise, false. + public bool Equals (License other) + { + if (other == null) + return false; + + if (ReferenceEquals (this, other)) + return true; + + if (String.Compare (ID, other.ID, StringComparison.Ordinal) != 0) + return false; + + if (String.Compare (Type, other.Type, StringComparison.Ordinal) != 0) + return false; + + if (String.Compare (Text, other.Text, StringComparison.Ordinal) != 0) + return false; + + return true; + } + + /// + /// Determines whether the specified is equal to the current . + /// + /// The to compare with the current . + /// true if the specified is equal to the current + /// ; otherwise, false. + public override bool Equals (object obj) + { + return Equals (obj as License); + } + + /// + /// Serves as a hash function for a object. + /// + /// A hash code for this instance that is suitable for use in hashing algorithms and data structures such as a hash table. + public override int GetHashCode () + { + int hashCode = 0; + + hashCode = hashCode.XorWith (ID?.GetHashCode ()); + hashCode = hashCode.XorWith (Type?.GetHashCode ()); + return hashCode.XorWith (Text?.GetHashCode ()); + } + } +} diff --git a/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/Common/PackageLibrary.cs b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/Common/PackageLibrary.cs new file mode 100644 index 00000000000..e1d9cfcb590 --- /dev/null +++ b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/Common/PackageLibrary.cs @@ -0,0 +1,85 @@ +// +// Authors: +// Marek Habersack +// +// Copyright (c) 2017, Microsoft, Inc +// +// All rights reserved. +// +using System; + +namespace Xamarin.Installer.AndroidSDK.Common +{ + /// + /// Describes a single library contained in a package + /// + public sealed class PackageLibrary : IEquatable + { + /// + /// Gets the local path of the library JAR archive. + /// + /// Path to the JAR archive + public string LocalJarPath { get; internal set; } + + /// + /// Gets the library name + /// + /// Library name + public string Name { get; internal set; } + + /// + /// Gets the library description. + /// + /// The description. + public string Description { get; internal set; } + + /// + /// Determines whether the specified is equal to the + /// current . + /// + /// The to compare with the current . + /// true if the specified is equal to the + /// current ; otherwise, false. + public bool Equals (PackageLibrary other) + { + if (other == null) + return false; + + if (!ReferenceEquals (this, other)) + return false; + + return String.Compare (LocalJarPath, other.LocalJarPath, StringComparison.Ordinal) == 0; + } + + /// + /// Determines whether the specified is equal to the current . + /// + /// The to compare with the current . + /// true if the specified is equal to the current + /// ; otherwise, false. + public override bool Equals (object obj) + { + return Equals (obj as PackageLibrary); + } + + /// + /// Serves as a hash function for a object. + /// + /// A hash code for this instance that is suitable for use in hashing algorithms and data structures such as a hash table. + public override int GetHashCode () + { + int hashCode = base.GetHashCode (); + + if (LocalJarPath != null) + hashCode = hashCode.XorWith (LocalJarPath.GetHashCode ()); + + if (Name != null) + hashCode = hashCode.XorWith (Name.GetHashCode ()); + + if (Description != null) + hashCode = hashCode.XorWith (Description.GetHashCode ()); + + return hashCode; + } + } +} diff --git a/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/Common/PackageMetadata.cs b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/Common/PackageMetadata.cs new file mode 100644 index 00000000000..7ab34e8dee6 --- /dev/null +++ b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/Common/PackageMetadata.cs @@ -0,0 +1,57 @@ +// +// Authors: +// Marek Habersack +// +// Copyright (c) 2018, Microsoft Corp. (https://microsoft.com) +// +// All rights reserved. +// +using System; +using System.Collections.Generic; + +namespace Xamarin.Installer.AndroidSDK.Common +{ + public class PackageMetadata + { + public bool Present { get; set; } + public bool NeedsUpdate { get; set; } + public string Path { get; set; } + public string FileSystemPath { get; set; } + public bool Obsolete { get; set; } + public bool Preview => GetPreview (); + public AndroidComponentInfo Info { get; set; } + public AndroidRevision Revision { get; set; } + public AndroidRevision InstalledRevision { get; set; } + public string DisplayName { get; set; } + public IList Dependencies { get; set; } + public IList Archives { get; set; } + public string LicenseID { get; set; } + + public PackageMetadata (IAndroidComponent component) + { + if (component == null) + throw new ArgumentNullException (nameof (component)); + + Present = component.Present; + NeedsUpdate = component.NeedsUpdate; + Path = component.Path; + FileSystemPath = component.FileSystemPath; + Obsolete = component.Obsolete; + Info = component.Info; + Revision = component.Revision; + InstalledRevision = component.InstalledRevision; + DisplayName = component.DisplayName; + Dependencies = component.Dependencies != null ? new List (component.Dependencies) : null; + Archives = component.Archives != null ? new List (component.Archives) : null; + LicenseID = component.LicenseID; + } + + bool GetPreview () + { + if (Info is AndroidComponentInfoPlatform platform) { + return platform.Preview; + } + return false; + } + } +} diff --git a/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/Common/PackageTag.cs b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/Common/PackageTag.cs new file mode 100644 index 00000000000..00e2b89e673 --- /dev/null +++ b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/Common/PackageTag.cs @@ -0,0 +1,69 @@ +// +// Authors: +// Marek Habersack +// +// Copyright (c) 2017, Microsoft, Inc +// +// All rights reserved. +// +using System; + +namespace Xamarin.Installer.AndroidSDK.Common +{ + /// + /// Describes a package tag. + /// + public sealed class PackageTag : ItemWithIDAndDisplay, IEquatable + { + public PackageTag (string id, string display) : base (id, display) + {} + + /// + /// Determines whether the specified is equal to the + /// current . + /// + /// The to compare with the current . + /// true if the specified is equal to the current + /// ; otherwise, false. + public bool Equals (PackageTag other) + { + if (other == null) + return false; + + if (!ReferenceEquals (this, other)) + return false; + + if (String.Compare (ID, other.ID, StringComparison.Ordinal) != 0) + return false; + + return String.Compare (Display, other.Display, StringComparison.Ordinal) == 0; + } + + /// + /// Determines whether the specified is equal to the current . + /// + /// The to compare with the current . + /// true if the specified is equal to the current + /// ; otherwise, false. + public override bool Equals (object obj) + { + return Equals (obj as PackageTag); + } + + /// + /// Serves as a hash function for a object. + /// + /// A hash code for this instance that is suitable for use in hashing algorithms and data structures such as a hash table. + public override int GetHashCode () + { + int hashCode = base.GetHashCode (); + + if (ID != null) + hashCode = hashCode.XorWith (ID.GetHashCode ()); + if (Display != null) + hashCode = hashCode.XorWith (Display.GetHashCode ()); + + return hashCode; + } + } +} diff --git a/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/Common/PackageVendor.cs b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/Common/PackageVendor.cs new file mode 100644 index 00000000000..75c55d11ad2 --- /dev/null +++ b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/Common/PackageVendor.cs @@ -0,0 +1,70 @@ +// +// Authors: +// Marek Habersack +// +// Copyright (c) 2017, Microsoft, Inc +// +// All rights reserved. +// +using System; + +namespace Xamarin.Installer.AndroidSDK.Common +{ + /// + /// Describes a package vendor + /// + public sealed class PackageVendor : ItemWithIDAndDisplay, IEquatable + { + public PackageVendor (string id, string display) : base (id, display) + { + } + + /// + /// Determines whether the specified is equal to the + /// current . + /// + /// The to compare with the current . + /// true if the specified is equal to the + /// current ; otherwise, false. + public bool Equals (PackageVendor other) + { + if (other == null) + return false; + + if (!ReferenceEquals (this, other)) + return false; + + if (String.Compare (ID, other.ID, StringComparison.Ordinal) != 0) + return false; + + return String.Compare (Display, other.Display, StringComparison.Ordinal) == 0; + } + + /// + /// Determines whether the specified is equal to the current . + /// + /// The to compare with the current . + /// true if the specified is equal to the current + /// ; otherwise, false. + public override bool Equals (object obj) + { + return Equals (obj as PackageVendor); + } + + /// + /// Serves as a hash function for a object. + /// + /// A hash code for this instance that is suitable for use in hashing algorithms and data structures such as a hash table. + public override int GetHashCode () + { + int hashCode = base.GetHashCode (); + + if (ID != null) + hashCode = hashCode.XorWith (ID.GetHashCode ()); + if (Display != null) + hashCode = hashCode.XorWith (Display.GetHashCode ()); + + return hashCode; + } + } +} diff --git a/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/Common/Platform.cs b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/Common/Platform.cs new file mode 100644 index 00000000000..7134c3f6b61 --- /dev/null +++ b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/Common/Platform.cs @@ -0,0 +1,54 @@ +// +// Platform.cs +// +// Author: +// Vsevolod Kukol +// +// Copyright (c) 2017, Microsoft, Inc +// + +using System; +using System.IO; +using System.Runtime.InteropServices; + +namespace Xamarin.Installer.AndroidSDK.Manager +{ + public static class Platform + { + public readonly static bool IsWindows; + public readonly static bool IsMac; + public readonly static bool IsLinux; + public static Version OSVersion { get; private set; } + + static Platform () + { + IsWindows = Path.DirectorySeparatorChar == '\\'; + IsMac = !IsWindows && IsRunningOnMac (); + IsLinux = !IsMac && !IsWindows; + OSVersion = Environment.OSVersion.Version; + } + + [DllImport ("libc")] + static extern int uname (IntPtr buf); + + //From Managed.Windows.Forms/XplatUI + static bool IsRunningOnMac () + { + IntPtr buf = IntPtr.Zero; + try { + buf = Marshal.AllocHGlobal (8192); + // This is a hacktastic way of getting sysname from uname () + if (uname (buf) == 0) { + string os = Marshal.PtrToStringAnsi (buf); + if (os == "Darwin") + return true; + } + } catch { + } finally { + if (buf != IntPtr.Zero) + Marshal.FreeHGlobal (buf); + } + return false; + } + } +} diff --git a/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/DirectorySizeMonitoringTimer.cs b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/DirectorySizeMonitoringTimer.cs new file mode 100644 index 00000000000..21d4e131638 --- /dev/null +++ b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/DirectorySizeMonitoringTimer.cs @@ -0,0 +1,152 @@ +using ICSharpCode.SharpZipLib.Zip; +using Mono.AndroidTools.Util; +using System; +using System.Threading; +using System.Threading.Tasks; +using Xamarin.AndroidTools; +using Xamarin.Installer.Common; +using Timer = System.Timers.Timer; + +namespace Xamarin.Installer.AndroidSDK +{ + public class DirectorySizeMonitoringTimer : IDisposable + { + const int MIN_MOVE_PROGRESS_SPLIT = 10, MAX_MOVE_PROGRESS_SPLIT = 50; + const int SMALL_NUMBER_OF_FILES = 1, BIG_NUMBER_OF_FILES = 10000; + + Timer timer; + float maxProgress, targetMaxProgress; + Action progressUpdateAction; + bool disposed; + + public DirectorySizeMonitoringTimer (string targetDirectoryPath, string dirToCompareWith, Action progressUpdateAction, + float remapFrom = 0f, float remapTo = 100f, double checkInterval = 500d) + { + this.progressUpdateAction = progressUpdateAction; + maxProgress = -1f; + targetMaxProgress = remapTo; + + progressUpdateAction?.Invoke (remapFrom); + + if (!Platform.IsMac) + return; + + ulong? totalExpectedSize = null; + + timer = new Timer { Interval = checkInterval, AutoReset = false }; + timer.Elapsed += async (_, __) => { + if (disposed) + return; + + if (!totalExpectedSize.HasValue) + totalExpectedSize = await GetDirectorySizeAsync (dirToCompareWith); + + if (disposed) + return; + + var size = await GetDirectorySizeAsync (targetDirectoryPath); + if (size > totalExpectedSize.Value) + size = totalExpectedSize.Value; + + if (disposed) + return; + + float progress = (float) Remap (size * 100f / totalExpectedSize.Value, 0f, 100f, remapFrom, remapTo); + if (progress > maxProgress) { + // Logger.Debug ($"[DirectorySizeMonitoringTimer] size: {size} / totalExpectedSize: {totalExpectedSize} --> {progress}"); + progressUpdateAction.Invoke (progress); + maxProgress = progress; + } + + if (!disposed) + timer.Start(); + }; + timer.Start (); + } + + public void Dispose () + { + disposed = true; + //Logger.Debug ($"DirectorySizeMonitoringTimer disposing"); + timer?.Stop (); + timer?.Dispose (); + + if (maxProgress < targetMaxProgress) + progressUpdateAction?.Invoke (targetMaxProgress); + + progressUpdateAction = null; + } + + async Task GetDirectorySizeAsync (string directory) + { + // TODO: DirectoryInfo.GetFiles is throwing IO exceptions because of simultaneous access - find another way + // maybe this: https://docs.microsoft.com/en-us/sysinternals/downloads/du - update: its EULA disallows their binaries distribution + if (!Platform.IsMac) + return 0; + + var result = 0uL; + var duCommand = "/usr/bin/du"; + var args = new ProcessArgumentBuilder (); + args.Add ($"-sk \"{directory}\""); + string sizeOutput = null; + try { + sizeOutput = await ProcessUtils.ExecuteToolAsync(duCommand, args, output => output, CancellationToken.None); + } catch (Exception ex) { + if (ex?.Message?.Contains ("No such file or directory") == true) { + Logger.Debug ($"[DirectorySizeMonitoringTimer] WARN! Dir \"{directory}\" was removed before \"/usr/bin/du\" could calculate its size"); + } else { + Logger.Warning ($"[DirectorySizeMonitoringTimer] ProcessUtils.ExecuteToolAsync failed to execute {duCommand}.\n{ex}"); + } + } + + if (!String.IsNullOrEmpty (sizeOutput)) { + sizeOutput = sizeOutput.Replace (directory, string.Empty).Trim (); + if (ulong.TryParse (sizeOutput, out ulong parsed)) { + // "k" gives size in KBytes + result = parsed * 1024; + } + } + return result; + } + + public static double Remap (double value, double from1, double to1, double from2, double to2) + { + var mapping = (value - from1) / (to1 - from1) * (to2 - from2) + from2; + if (mapping < from2) + mapping = from2; + if (mapping > to2) + mapping = to2; + return mapping; + } + + public static ulong CalculateTotalSize (ZipFile zipFile) + { + ulong totalSize = 0; + foreach (ZipEntry entry in zipFile) + totalSize += (ulong) entry.Size; + Logger.Debug ($"[DirectorySizeMonitoringTimer] ZipFile totalSize: {totalSize}"); + + return totalSize; + } + + /// + /// Move operation will take different time depending on number of files in an archive + /// so we split the 100% installation progress according these values (unzip + move = 100%) + /// + /// + /// Amount of percents for the Move part of the installation + public static int CalculateMoveProgressSplit (string archivePath) + { + ZipFile zip = null; + long filesCount; + try { + zip = new ZipFile (archivePath); + filesCount = zip.Count; + } finally { + zip?.Close (); + } + + return (int) Remap (filesCount, SMALL_NUMBER_OF_FILES, BIG_NUMBER_OF_FILES, MIN_MOVE_PROGRESS_SPLIT, MAX_MOVE_PROGRESS_SPLIT); + } + } +} diff --git a/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/Extensions.IAndroidComponent.cs b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/Extensions.IAndroidComponent.cs new file mode 100644 index 00000000000..4c9f17c80c2 --- /dev/null +++ b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/Extensions.IAndroidComponent.cs @@ -0,0 +1,26 @@ +using System; +using Xamarin.Installer.AndroidSDK.Common; +using Xamarin.Installer.AndroidSDK.GoogleV2; +using Xamarin.Installer.AndroidSDK.Xamarin; + +namespace Xamarin.Installer.AndroidSDK +{ + partial class Extensions + { + internal static IAndroidComponent Clone (this IAndroidComponent component) + { + if (component == null) + return null; + + var rp = component as RemotePackage; + if (rp != null) + return RemotePackage.Clone (rp); + + var xp = component as XamarinPackage; + if (xp != null) + return XamarinPackage.Clone (xp); + + throw new InvalidOperationException ($"Unknown component type {component.GetType ()}"); + } + } +} diff --git a/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/Extensions.IListOfIAndroidComponent.cs b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/Extensions.IListOfIAndroidComponent.cs new file mode 100644 index 00000000000..d9beadcfe0e --- /dev/null +++ b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/Extensions.IListOfIAndroidComponent.cs @@ -0,0 +1,266 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +using Xamarin.Installer.AndroidSDK.Common; + +namespace Xamarin.Installer.AndroidSDK +{ + /// + /// Extensions to make it easier to select components from a full component set. + /// + public static class PublicExtensions + { + /// + /// Checks whether any components in the passed set are outdated. + /// + /// true, if outdated components were found, false otherwise. + /// Components. + public static bool AnyOutdated (this IList components) + { + return components?.Any (c => c != null && c.NeedsUpdate) ?? false; + } + + /// + /// Checks whether any components in the set aren't preset on the system + /// + /// true, if there are any missing components, false otherwise. + /// Components. + public static bool AnyNotInstalled (this IList components) + { + return components?.Any (c => c != null && !c.Present) ?? false; + } + + /// + /// Returns a list of all outdated or missing components from the passed set/ + /// + /// The outdated or missing components. + /// Components. + /// An optional delegate which decides whether or not to include the component passed to it + public static IList AllOutdatedOrMissing (this IList components, Func shouldInclude = null) + { + var ret = new List (); + ret.AddRange (components.AllOutdated (shouldInclude)); + ret.AddRange (components.AllNotInstalled (shouldInclude: shouldInclude)); + + return ret; + } + + /// + /// Gets all outdated components from the passed set. The returned list contains outdated components from all + /// channels. + /// + /// List of all the outdated components + /// Aource list of components + /// An optional delegate which decides whether or not to include the component passed to it + public static IList AllOutdated (this IList components, Func shouldInclude = null) + { + return components?.Where (c => c != null && c.Present && c.NeedsUpdate && (shouldInclude == null || shouldInclude (c)))?.ToList (); + } + + /// + /// Gets all installed components from the passed set. The returned list contains components from all channels + /// + /// List of all installed components + /// Source list of components + /// If set to true include outdated components. + /// An optional delegate which decides whether or not to include the component passed to it + public static IList AllInstalled (this IList components, bool includeOutdated = true, Func shouldInclude = null) + { + return components?.Where (c => c != null && c.Present && (includeOutdated || !c.NeedsUpdate) && (shouldInclude == null || shouldInclude(c)))?.ToList (); + } + + /// + /// Gets all components that aren't installed from the passed set. The returned list contains components from all channels + /// + /// List of or components that aren't installed + /// Source list of components + /// If set to true include obsolete components. + /// An optional delegate which decides whether or not to include the component passed to it + public static IList AllNotInstalled (this IList components, bool includeObsolete = false, Func shouldInclude = null) + { + return components?.Where (c => c != null && !c.Present && (includeObsolete || !c.Obsolete) && (shouldInclude == null || shouldInclude(c)))?.ToList (); + } + + /// + /// Gets all the components of the specified type from the passed set. The returned list contains components from all channels + /// + /// List of components of the specified type + /// Source list of components + /// Component type + /// An optional delegate which decides whether or not to include the component passed to it + public static IList AllOfType (this IList components, AndroidComponentType type, Func shouldInclude = null) + { + return GetAllOfType (components, type, shouldInclude)?.ToList (); + } + + /// + /// Gets all the platform components from the passed set. + /// + /// List of platform components + /// Source list of components + /// An optional delegate which decides whether or not to include the component passed to it + public static IList AllPlatforms (this IList components, Func shouldInclude = null) + { + return GetAllOfType (components, AndroidComponentType.Platform, shouldInclude)?.ToList (); + } + + /// + /// Gets all the system image components from the passed set. The returned list contains components from all channels + /// + /// List of system image components. + /// Source list of components + /// Select only components that use this ABI + /// An optional delegate which decides whether or not to include the component passed to it + public static IList AllSystemImages (this IList components, AndroidSystemImageAbi forABI = AndroidSystemImageAbi.Any, Func shouldInclude = null) + { + return GetAllOfType (components, AndroidComponentType.SystemImage, shouldInclude)?.Where (c => forABI == AndroidSystemImageAbi.Any || (c.Info as AndroidComponentInfoSystemImage).Abi == forABI)?.ToList (); + } + + /// + /// Gets all the components that use the specified API level (platform). The returned list contains components from all channels + /// + /// List of components using the specified API level + /// Source list of components + /// API level to match. + /// An optional delegate which decides whether or not to include the component passed to it + public static IList AllApiLevel (this IList components, string apiLevel = null, Func shouldInclude = null) + { + return GetAllApiLevel (components, apiLevel, shouldInclude)?.ToList (); + } + + /// + /// Gets all the components with a matching path. The returned list contains components from all channels + /// + /// List of components with a matching path + /// Source list of components + /// Path. + /// Minimum revision of the components. + /// An optional delegate which decides whether or not to include the component passed to it + /// If true it will check whether the component path starts with the specified one. + public static IList AllWithPath (this IListcomponents, string path, AndroidRevision minimumRevision = null, Func shouldInclude = null, bool matchPathStartOnly = false) + { + if (String.IsNullOrEmpty (path)) + return null; + + return components?.Where (c => c != null && + !String.IsNullOrEmpty (c.Path) && + PathMatches (c) && + (minimumRevision == null || c.Revision >= minimumRevision) && + (shouldInclude == null || shouldInclude (c)))?.ToList (); + + bool PathMatches (IAndroidComponent c) + { + if (c == null || c.Path == null) + return false; + + if (matchPathStartOnly) + return c.Path.StartsWith (path, StringComparison.Ordinal); + return String.Compare (c.Path, path, StringComparison.Ordinal) == 0; + } + } + + /// + /// Get all of the Build Tools components. The returned list contains components from all channels + /// + /// List of the Build Tools components + /// Source list of components + /// Minimum revision of the components. + /// An optional delegate which decides whether or not to include the component passed to it + public static IList AllBuildTools (this IList components, AndroidRevision minimumRevision = null, Func shouldInclude = null) + { + return components.AllWithPath (Constants.ComponentPaths.BuildTools, minimumRevision, shouldInclude, true); + } + + /// + /// Get all of the Tools components. The returned list contains components from all channels + /// + /// List of the Tools components + /// Source list of components + /// Minimum revision of the components. + /// An optional delegate which decides whether or not to include the component passed to it + public static IList AllTools (this IList components, AndroidRevision minimumRevision = null, Func shouldInclude = null) + { + return components.AllWithPath (Constants.ComponentPaths.Tools, minimumRevision, shouldInclude, true); + } + + /// + /// Get all of the Platform Tools components. The returned list contains components from all channels + /// + /// List of the Platform Tools components + /// Source list of components + /// Minimum revision of the components. + /// An optional delegate which decides whether or not to include the component passed to it + public static IList AllPlatformTools(this IList components, AndroidRevision minimumRevision = null, Func shouldInclude = null) + { + return components.AllWithPath (Constants.ComponentPaths.PlatformTools, minimumRevision, shouldInclude); + } + + /// + /// Get all of the Emulator components. The returned list contains components from all channels + /// + /// List of the Emulator components + /// Source list of components + /// Minimum revision of the components. + /// An optional delegate which decides whether or not to include the component passed to it + public static IList AllEmulators (this IList components, AndroidRevision minimumRevision = null, Func shouldInclude = null) + { + return components.AllWithPath (Constants.ComponentPaths.Emulator, minimumRevision, shouldInclude); + } + + /// + /// Get all of the CMake components. The returned list contains components from all channels + /// + /// List of the CMake components + /// Source list of components + /// Minimum revision of the components. + /// An optional delegate which decides whether or not to include the component passed to it + public static IList AllCMake (this IList components, AndroidRevision minimumRevision = null, Func shouldInclude = null) + { + return components.AllWithPath (Constants.ComponentPaths.CMake, minimumRevision, shouldInclude, true); + } + + /// + /// Get all of the LLDB components. The returned list contains components from all channels + /// + /// List of the LLDB components + /// Source list of components + /// Minimum revision of the components. + /// An optional delegate which decides whether or not to include the component passed to it + public static IList AllLLDB (this IList components, AndroidRevision minimumRevision = null, Func shouldInclude = null) + { + return components.AllWithPath (Constants.ComponentPaths.LLDB, minimumRevision, shouldInclude, true); + } + + /// + /// Get all of the NDK components. The returned list contains components from all channels + /// + /// List of the Emulator components + /// Source list of components + /// Minimum revision of the components. + /// An optional delegate which decides whether or not to include the component passed to it + public static IList AllNDK (this IList components, AndroidRevision minimumRevision = null, Func shouldInclude = null) + { + return components.AllWithPath (Constants.ComponentPaths.NDK, minimumRevision, shouldInclude); + } + + public static IList AllAddons (this IList components, AndroidRevision minimumRevision = null, Func shouldInclude = null) + { + return GetAllOfType (components, AndroidComponentType.Addon, shouldInclude)?.ToList (); + } + + static IEnumerable GetAllApiLevel (IList components, string apiLevel, Func shouldInclude) + { + bool haveApiLevel = !String.IsNullOrEmpty (apiLevel); + return components?.OfType ()?. + Where (c => c != null && (!haveApiLevel || String.Compare(c.ApiLevel, apiLevel, StringComparison.OrdinalIgnoreCase) == 0))?. + Select (c => c as IAndroidComponent). + Where (c => shouldInclude == null || shouldInclude(c)); + } + + static IEnumerable GetAllOfType (IList components, AndroidComponentType type, Func shouldInclude) + { + return components?.Where (c => c != null && c.ComponentType == type && (shouldInclude == null || shouldInclude(c))); + } + } +} diff --git a/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/Extensions.IListOfT.cs b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/Extensions.IListOfT.cs new file mode 100644 index 00000000000..46af9a6d574 --- /dev/null +++ b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/Extensions.IListOfT.cs @@ -0,0 +1,35 @@ +// +// Authors: +// Marek Habersack +// +// Copyright (c) 2017, Marek Habersack +// +// All rights reserved. +// +using System; +using System.Collections.Generic; + +namespace Xamarin.Installer.AndroidSDK +{ + static partial class Extensions + { + public static bool AreEqual (this IList one, IList two) + { + if (one == null) + return two == null; + if (two == null) + return false; + + if (one.Count != two.Count) + return false; + + // Order of elements shouldn't matter I think + foreach (T item in one) { + if (!two.Contains (item)) + return false; + } + + return true; + } + } +} diff --git a/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/Extensions.IXmlLineInfo.cs b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/Extensions.IXmlLineInfo.cs new file mode 100644 index 00000000000..bfa08892c0d --- /dev/null +++ b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/Extensions.IXmlLineInfo.cs @@ -0,0 +1,26 @@ +// +// Authors: +// Marek Habersack +// +// Copyright (c) 2017, Marek Habersack +// +// All rights reserved. +// +using System; +using System.Xml; + +namespace Xamarin.Installer.AndroidSDK +{ + static partial class Extensions + { + public static string AsString (this IXmlLineInfo info, string openBracket = "[", string closeBracket = "]") + { + if (info == null || (info.LineNumber < 0 && info.LinePosition < 0)) + return String.Empty; + string open = openBracket ?? String.Empty; + string close = closeBracket ?? String.Empty; + + return $"{open}{info.LineNumber}:{info.LinePosition}{close}"; + } + } +} diff --git a/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/Extensions.Int32.cs b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/Extensions.Int32.cs new file mode 100644 index 00000000000..c1b79af8572 --- /dev/null +++ b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/Extensions.Int32.cs @@ -0,0 +1,29 @@ +// +// Authors: +// Marek Habersack +// +// Copyright (c) 2017, Marek Habersack +// +// All rights reserved. +// +using System; + +namespace Xamarin.Installer.AndroidSDK +{ + static partial class Extensions + { + public static int XorWith (this int value, int? maybeValue) + { + if (!maybeValue.HasValue) + return value; + + return XorWith (value, maybeValue.Value); + } + + public static int XorWith (this int value, int otherValue) + { + return value ^ otherValue; + + } + } +} diff --git a/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/Extensions.JavaProperties.cs b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/Extensions.JavaProperties.cs new file mode 100644 index 00000000000..b248e3e821d --- /dev/null +++ b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/Extensions.JavaProperties.cs @@ -0,0 +1,38 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +using Kajabity.Tools.Java; + +namespace Xamarin.Installer.AndroidSDK +{ + static partial class Extensions + { + public static bool GetProperty (this JavaProperties props, string name, out T result) + { + result = default (T); + if (props == null) + return false; + string value = props.GetProperty (name); + if (value != null) + value = value.Trim (); + if (String.IsNullOrEmpty (value)) + return false; + + if (typeof (T) == typeof (string)) { + result = (T)((object)value); + return true; + } + + try { + result = (T)Convert.ChangeType (value, typeof (T)); + } catch { + // ignore + return false; + } + return true; + } + } +} diff --git a/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/Extensions.String.cs b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/Extensions.String.cs new file mode 100644 index 00000000000..cfc4713a904 --- /dev/null +++ b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/Extensions.String.cs @@ -0,0 +1,73 @@ +// +// Authors: +// Marek Habersack +// +// Copyright (c) 2017, Marek Habersack +// +// All rights reserved. +// +using System; + +namespace Xamarin.Installer.AndroidSDK +{ + static partial class Extensions + { + public static uint AsUInt (this string s, bool throwOnError = false) + { + if (String.IsNullOrEmpty (s)) + return 0; + + uint ret; + if (UInt32.TryParse (s, out ret)) + return ret; + + if (throwOnError) + throw new InvalidOperationException ($"Unable to convert string '{s}' to an unsigned integer"); + return 0; + } + + public static ulong AsULong (this string s, bool throwOnError = false) + { + if (String.IsNullOrEmpty (s)) + return 0; + + ulong ret; + if (UInt64.TryParse (s, out ret)) + return ret; + + if (throwOnError) + throw new InvalidOperationException ($"Unable to convert string '{s}' to an unsigned long integer"); + return 0; + } + + public static bool AsBool (this string s, bool throwOnError = false) + { + if (String.IsNullOrEmpty (s)) + return false; + + bool ret; + if (Boolean.TryParse (s, out ret)) + return ret; + + if (throwOnError) + throw new InvalidOperationException ($"Unable to convert string '{s}' to boolean"); + + return false; + } + + public static Uri AsUri (this string s, bool throwOnError = false) + { + if (String.IsNullOrEmpty (s)) + return null; + + Uri ret; + if (Uri.TryCreate (s, UriKind.RelativeOrAbsolute, out ret)) + return ret; + + if (throwOnError) + throw new InvalidOperationException ($"Unable to parse string '{s}' as a valid URI"); + + return null; + } + } +} diff --git a/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/Extensions.XElement.cs b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/Extensions.XElement.cs new file mode 100644 index 00000000000..ff64a9e22d8 --- /dev/null +++ b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/Extensions.XElement.cs @@ -0,0 +1,109 @@ +// +// Authors: +// Marek Habersack +// +// Copyright (c) 2017, Microsoft Corp. (http://microsoft.com) +// +// All rights reserved. +// +using System; +using System.Collections.Generic; +using System.Linq; +using System.Xml; +using System.Xml.Linq; + +using Xamarin.Installer.Common; + +namespace Xamarin.Installer.AndroidSDK +{ + public static partial class Extensions + { + public static void GetLineInfo (this XElement e, out int line, out int column) + { + IXmlLineInfo linfo = e.GetLineInfo (); + if (linfo == null || !linfo.HasLineInfo ()) { + line = column = -1; + return; + } + line = linfo.LineNumber; + column = linfo.LinePosition; + } + + public static IXmlLineInfo GetLineInfo (this XElement e) + { + return e as IXmlLineInfo; + } + + public static string GetLocation (this XElement e, Uri documentUrl = null) + { + if (e == null) + return String.Empty; + + if (documentUrl == null) + return e.GetLineInfo ().AsString ("(", ")"); + + return $"({documentUrl} {e.GetLineInfo ().AsString ()}"; + } + + public static string GetChildElementValue (this XElement element, string childName) + { + if (element == null) + return String.Empty; + + XElement child = element.Elements (childName)?.FirstOrDefault (); + return child?.Value ?? String.Empty; + } + + public static string GetAttributeValue (this XElement e, string name, bool required = true, Uri documentUrl = null) + { + XAttribute ret = e.GetAttribute (name); + if (ret == null) { + if (required) + throw new InvalidOperationException ($"Required attribute '{name}' missing for element {e?.Name} at {e.GetLocation (documentUrl)}"); + return String.Empty; + } + + return ret.Value; + } + + public static XAttribute GetAttribute (this XElement e, string name) + { + return e?.Attribute (name); + } + + public static XAttribute GetAttribute (this XElement e, XNamespace ns, string name) + { + return e?.Attribute (ns + name); + } + + public static string GetElementValue (this XElement e) + { + return e?.Value?.Trim (); + } + + public static void GetNamespaces (this XElement root, Uri docURL, ref Dictionary namespaces) + { + if (root == null || !root.HasAttributes) + return; + + foreach (XAttribute attr in root.Attributes ()) + GetNamespace (root, attr, docURL, ref namespaces); + } + + static void GetNamespace (XElement element, XAttribute attr, Uri docURL, ref Dictionary namespaces) + { + if (!attr.IsNamespaceDeclaration) + return; + + if (namespaces == null) + namespaces = new Dictionary (StringComparer.Ordinal); + + if (namespaces.ContainsKey (attr.Name.LocalName)) { + if (namespaces[attr.Name.LocalName] != attr.Value) + Logger.Warning ($"Element '{element.Name.GetFullName ()}' has a duplicate attribute '{attr.Name.GetFullName ()}' in document '{docURL}'. New value will override the old one"); + namespaces[attr.Name.LocalName] = attr.Value; + } else + namespaces.Add (attr.Name.LocalName, attr.Value); + } + } +} diff --git a/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/Extensions.XName.cs b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/Extensions.XName.cs new file mode 100644 index 00000000000..742145b65b2 --- /dev/null +++ b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/Extensions.XName.cs @@ -0,0 +1,27 @@ +// +// Authors: +// Marek Habersack +// +// Copyright (c) 2017, Microsoft Corp. (http://microsoft.com) +// +// All rights reserved. +// +using System; +using System.Xml.Linq; + +namespace Xamarin.Installer.AndroidSDK +{ + static partial class Extensions + { + public static string GetFullName (this XName name) + { + if (name == null) + return String.Empty; + + if (!String.IsNullOrEmpty (name.NamespaceName)) + return $"{name.NamespaceName}:{name.LocalName}"; + + return name.LocalName; + } + } +} diff --git a/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/GoogleV2/AddonSite.cs b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/GoogleV2/AddonSite.cs new file mode 100644 index 00000000000..3827dfa4bf0 --- /dev/null +++ b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/GoogleV2/AddonSite.cs @@ -0,0 +1,27 @@ +// +// Authors: +// Marek Habersack +// +// Copyright (c) 2017, Marek Habersack +// +// All rights reserved. +// +using System; + +namespace Xamarin.Installer.AndroidSDK.GoogleV2 +{ + public class AddonSite + { + public AddonSiteType Type { get; } + public string DisplayName { get; } + public Uri Url { get; } + + public AddonSite (AddonSiteType type, string displayName, Uri url) + { + if (url == null) + throw new ArgumentNullException (nameof (url)); + DisplayName = displayName ?? "Unnamed"; + Url = url; + } + } +} diff --git a/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/GoogleV2/AddonSiteType.cs b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/GoogleV2/AddonSiteType.cs new file mode 100644 index 00000000000..f135a8cc1b2 --- /dev/null +++ b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/GoogleV2/AddonSiteType.cs @@ -0,0 +1,19 @@ +// +// Authors: +// Marek Habersack +// +// Copyright (c) 2017, Marek Habersack +// +// All rights reserved. +// +using System; + +namespace Xamarin.Installer.AndroidSDK.GoogleV2 +{ + public enum AddonSiteType + { + Unknown, + SystemImage, + Addon + } +} diff --git a/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/GoogleV2/Extensions.IParserErrorHandler.cs b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/GoogleV2/Extensions.IParserErrorHandler.cs new file mode 100644 index 00000000000..8b708d2256f --- /dev/null +++ b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/GoogleV2/Extensions.IParserErrorHandler.cs @@ -0,0 +1,135 @@ +// +// Authors: +// Marek Habersack +// +// Copyright (c) 2017, Marek Habersack +// +// All rights reserved. +// +using System; +using System.Xml.Linq; + +namespace Xamarin.Installer.AndroidSDK.GoogleV2 +{ + public static partial class Extensions + { + public static void Fatal (this IParserErrorHandler handler, string message) + { + handler.Fatal (null, null, message); + } + + public static void Fatal (this IParserErrorHandler handler, Uri url, string message) + { + handler.Fatal (url, null, message); + } + + public static void Fatal (this IParserErrorHandler handler, XElement element, string message, bool addLocationInfo = true) + { + handler.Fatal (null, element, message); + } + + public static void Fatal (this IParserErrorHandler handler, Uri documentUrl, XElement element, string message, bool addLocationInfo = true) + { + if (handler == null) + return; + + handler.OnFatalError (documentUrl, element, $"{message}{RenderLocation (documentUrl, element, addLocationInfo)}"); + } + + public static void Error (this IParserErrorHandler handler, string message) + { + handler.Error (null, null, message); + } + + public static void Error (this IParserErrorHandler handler, Uri url, string message) + { + handler.Error (url, null, message); + } + + public static void Error (this IParserErrorHandler handler, XElement element, string message, bool addLocationInfo = true) + { + handler.Error (null, element, message, addLocationInfo); + } + + public static void Error (this IParserErrorHandler handler, Uri documentUrl, XElement element, string message, bool addLocationInfo = true) + { + ReportRecoverableError (handler, ParserErrorLevel.Error, documentUrl, element, message, addLocationInfo); + } + + public static void Warning (this IParserErrorHandler handler, string message) + { + handler.Warning (null, null, message); + } + + public static void Warning (this IParserErrorHandler handler, Uri url, string message) + { + handler.Warning (url, null, message); + } + + public static void Warning (this IParserErrorHandler handler, XElement element, string message, bool addLocationInfo = true) + { + handler.Warning (null, element, message, addLocationInfo); + } + + public static void Warning (this IParserErrorHandler handler, Uri documentUrl, XElement element, string message, bool addLocationInfo = true) + { + ReportRecoverableError (handler, ParserErrorLevel.Warning, documentUrl, element, message, addLocationInfo); + } + + public static void Info (this IParserErrorHandler handler, string message) + { + handler.Info (null, null, message); + } + + public static void Info (this IParserErrorHandler handler, Uri url, string message) + { + handler.Info (url, null, message); + } + + public static void Info (this IParserErrorHandler handler, XElement element, string message, bool addLocationInfo = true) + { + handler.Info (null, element, message, addLocationInfo); + } + + public static void Info (this IParserErrorHandler handler, Uri documentUrl, XElement element, string message, bool addLocationInfo = true) + { + ReportRecoverableError (handler, ParserErrorLevel.Info, documentUrl, element, message, addLocationInfo); + } + + public static void Debug (this IParserErrorHandler handler, string message) + { + handler.Debug (null, null, message); + } + + public static void Debug (this IParserErrorHandler handler, Uri url, string message) + { + handler.Debug (url, null, message); + } + + public static void Debug (this IParserErrorHandler handler, XElement element, string message, bool addLocationInfo = true) + { + handler.Debug (null, element, message, addLocationInfo); + } + + public static void Debug (this IParserErrorHandler handler, Uri documentUrl, XElement element, string message, bool addLocationInfo = true) + { + ReportRecoverableError (handler, ParserErrorLevel.Debug, documentUrl, element, message, addLocationInfo); + } + + static void ReportRecoverableError (IParserErrorHandler handler, ParserErrorLevel level, Uri documentUrl, XElement element, string message, bool addLocationInfo = true) + { + if (handler == null) + return; + + handler.OnRecoverableError (level, documentUrl, element, $"{message}{RenderLocation (documentUrl, element, addLocationInfo)}"); + } + + static string RenderLocation (Uri documentUrl, XElement element, bool addLocationInfo) + { + if (!addLocationInfo) + return String.Empty; + + return $" {element.GetLocation (documentUrl)}"; + } + } +} diff --git a/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/GoogleV2/GoogleV2Repository.cs b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/GoogleV2/GoogleV2Repository.cs new file mode 100644 index 00000000000..808f6275b1d --- /dev/null +++ b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/GoogleV2/GoogleV2Repository.cs @@ -0,0 +1,314 @@ +// +// Authors: +// Marek Habersack +// +// Copyright (c) 2017, Microsoft Corp. (http://microsoft.com) +// +// All rights reserved. +// +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using System.Xml.Linq; + +using Xamarin.Installer.AndroidSDK.Common; +using Xamarin.Installer.AndroidSDK.GoogleV2.Parsing; +using Xamarin.Installer.Common; + +namespace Xamarin.Installer.AndroidSDK.GoogleV2 +{ + public class GoogleV2Repository : Repository + { + static readonly Uri DefaultRepositoryBaseURL = new Uri ("https://dl.google.com/android/repository/"); + static readonly Uri DefaultManifestURL = new Uri (DefaultRepositoryBaseURL, $"repository2-3.xml"); + static readonly Uri DefaultAddonsListURL = new Uri (DefaultRepositoryBaseURL, $"addons_list-5.xml"); + static readonly Regex MANIFEST_FILE_NAME_SANITIZER_REGEX = new Regex ("[-,\\s]+"); + + ParserContext parserContext; + + List repoParts; + + IParserErrorHandler errorHandler; + + public Uri RepositoryBaseURL { get; private set; } + public Uri AddonsListURL { get; private set; } + + public GoogleV2Repository (IParserErrorHandler errorHandler, Uri manifestURL = null, Uri addonsListURL = null, Uri repositoryBaseURL = null, bool cacheManifest = false) + : base ("Google Repository V2", manifestURL ?? DefaultManifestURL) + { + this.errorHandler = errorHandler ?? throw new ArgumentNullException (nameof (errorHandler)); + if (repositoryBaseURL != null) + RepositoryBaseURL = repositoryBaseURL; + else if (manifestURL == null) + RepositoryBaseURL = DefaultRepositoryBaseURL; + else + RepositoryBaseURL = Helpers.GetBaseURL (manifestURL); + + AddonsListURL = addonsListURL ?? DefaultAddonsListURL; + + if (cacheManifest) + ManifestCacher = LocalManifestProvider.CreateGoogleManifestProvider (); + } + + public override void Parse () + { + string manifest = LoadManifest (); + if (string.IsNullOrEmpty (manifest)) { + Logger.Warning ($"Android manifest should not be empty! Manifest URL: {ManifestURL}"); + // todo: should we throw here? + return; + } + + string addonManifest = null; + bool haveAddons = false; + try { + haveAddons = CommonUtilities.Helpers.DownloadToString (AddonsListURL, out addonManifest); + + if (haveAddons && addonManifest != null) + ManifestCacher?.SaveManifest (addonManifest, ManifestNameToFileName (AddonsListURL.Segments.Last ())); + } catch (Exception ex) { + Logger.Error ($"Failed to download addons manifest {AddonsListURL}. Ex: {ex}"); + } + + if (addonManifest == null) { + Logger.Info ("Trying to load cached addon manifest..."); + + addonManifest = ManifestCacher?.GetManifest (ManifestNameToFileName (AddonsListURL.Segments.Last ())); + if (addonManifest != null) + haveAddons = true; + } + try { + parserContext = new ParserContext ( + errorHandler, + RepositoryBaseURL, + ManifestURL, new MemoryStream (Encoding.UTF8.GetBytes (manifest)), + AddonsListURL, haveAddons ? new MemoryStream (Encoding.UTF8.GetBytes (addonManifest)) : null + ); + ParseInternal (); + DefaultChannel = GetChannel ("channel-0"); + Parsed = true; + } finally { + parserContext?.Dispose (); + parserContext = null; + } + } + + void ParseInternal () + { + Dictionary namespaces = null; + repoParts = new List (); + + Components?.Clear (); + if (!ParseRepository (parserContext.RepositoryManifest, ManifestURL, ref namespaces)) { + Logger.Error ("Unable to parse repository {0} at URL `{1}`.", parserContext.RepositoryManifest, ManifestURL); + return; + } + if (parserContext.AddonsListManifest != null) + ParseAddons (parserContext.AddonsListManifest, parserContext.AddonsListManifestURL, ref namespaces); + + Namespaces = namespaces; + parserContext.ErrorHandler.Debug ("Namespaces:"); + foreach (var kvp in namespaces) { + parserContext.ErrorHandler.Debug ($" {kvp.Key}:{kvp.Value}"); + } + + Dictionary licenses = null; + Dictionary channels = null; + int duplicateChannelIdCounter = 0; + int duplicateLicenseIdCounter = 0; + + foreach (RepositoryParser rp in repoParts) { + if (rp == null) + continue; + + MergeItems (rp.Channels, ref channels, ref duplicateChannelIdCounter, (string newID, Channel ch) => { + UpdateIDs (rp.Packages, ch, (RemotePackage p) => p.ChannelID, (RemotePackage p) => p.ChannelID = newID); + }); + + MergeItems (rp.Licenses, ref licenses, ref duplicateLicenseIdCounter, (string newID, License lic) => { + UpdateIDs (rp.Packages, lic, (RemotePackage p) => p.LicenseID, (RemotePackage p) => p.LicenseID = newID); + }); + + AddComponents (rp); + } + + if (Components != null) + parserContext.ErrorHandler.Debug ($"{Name}: found {Components.Count} packages loaded from {repoParts.Count} repositories"); + + if (licenses != null && licenses.Count > 0) { + parserContext.ErrorHandler.Debug ($"{Name}: found {licenses.Count} unique licenses"); + CopyDictionary (licenses, Licenses); + licenses.Clear (); + licenses = null; + } + + if (channels != null && channels.Count > 0) { + parserContext.ErrorHandler.Debug ($"{Name}: found {channels.Count} unique channels"); + CopyDictionary (channels, Channels); + channels.Clear (); + channels = null; + } + } + + void AddComponents (RepositoryParser rp) + { + if (rp.Packages == null || rp.Packages.Count == 0) + return; + + foreach (RemotePackage package in rp.Packages) { + if (package == null) + continue; + + Components.Add (package); + } + } + + void UpdateIDs (List packages, T item, Func getID, Action setID) where T : ItemWithID + { + if (packages == null || packages.Count == 0) + return; + + foreach (RemotePackage rp in packages) { + if (rp == null || String.Compare (getID (rp), item.ID, StringComparison.Ordinal) != 0) + continue; + setID (rp); + } + } + + void MergeItems (Dictionary sourceDict, ref Dictionary items, ref int duplicateIdCounter, Action updateID) where T : ItemWithID + { + if (sourceDict == null || sourceDict.Count == 0) + return; + + foreach (KeyValuePair kvp in sourceDict) { + if (kvp.Value == null) + continue; + MergeItemWithID (kvp.Value, ref duplicateIdCounter, ref items, updateID); + } + } + + void MergeItemWithID (T item, ref int duplicateIdCounter, ref Dictionary items, Action updateID) where T : ItemWithID + { + if (item == null) + return; + + if (items == null) + items = new Dictionary (StringComparer.Ordinal); + + if (items.ContainsValue (item)) { + if (items.ContainsKey (item.ID)) + return; + + // We have an identical item but with a different dictionary key, we must update the ID + KeyValuePair kvp = items.FirstOrDefault ((KeyValuePair i) => item == i.Value); + if (kvp.Value == null) + return; + updateID (kvp.Key, kvp.Value); + return; + } + + string itemID; + if (items.ContainsKey (item.ID)) { + // We have an item with identical ID but different contents + duplicateIdCounter++; + itemID = $"{item.ID}_{duplicateIdCounter}"; + } else + itemID = item.ID; + items.Add (itemID, item); + } + + bool ParseRepository (Stream manifest, Uri url, ref Dictionary namespaces) + { + XDocument doc = LoadManifest (parserContext, manifest, url, ref namespaces); + if (doc == null) { + return false; + } + + var repoParser = new RepositoryParser (this, parserContext, doc.Root, namespaces); + repoParser.Parse (); + repoParts.Add (repoParser); + return true; + } + + void ParseAddons (Stream manifest, Uri url, ref Dictionary namespaces) + { + XDocument doc = LoadManifest (parserContext, manifest, url, ref namespaces); + if (doc == null) { + return; + } + var addonListParser = new AddonList3Parser (parserContext, doc.Root, namespaces); + addonListParser.Parse (); + + if (addonListParser.Sites == null || addonListParser.Sites.Count == 0) { + parserContext.ErrorHandler.Error (parserContext.CurrentManifestURL, doc.Root, $"No addon or system image sites found at URL '{url}'"); + return; + } + + foreach (AddonSite site in addonListParser.Sites) { + string addonManifest = null; + bool gotAddonManifest = false; + + try { + gotAddonManifest = CommonUtilities.Helpers.DownloadToString (site.Url, out addonManifest); + if (gotAddonManifest && addonManifest != null) + ManifestCacher?.SaveManifest (addonManifest, ManifestNameToFileName (site.DisplayName + '_' + AddonsListURL.Segments.Last ())); + } catch (Exception ex) { + Logger.Error ($"Failed to load local addon manifest: {ex}"); + } + + if (addonManifest == null) { + Logger.Info ("Trying to load cached sites manifest..."); + addonManifest = ManifestCacher?.GetManifest (ManifestNameToFileName (site.DisplayName + '_' + AddonsListURL.Segments.Last ())); + if (addonManifest != null) + gotAddonManifest = true; + } + + if (!gotAddonManifest && addonManifest == null) + continue; + + using (Stream addonManifestContents = new MemoryStream (Encoding.UTF8.GetBytes (addonManifest))) { + if (!ParseRepository (addonManifestContents, site.Url, ref namespaces)) { + Logger.Error ("Unable to parse addon manifest contents"); + addonManifestContents.Position = 0; + Logger.Debug ("{0}", new StreamReader(addonManifestContents).ReadToEnd ()); + } + } + } + } + + string ManifestNameToFileName (string name) + { + return "google_" + MANIFEST_FILE_NAME_SANITIZER_REGEX.Replace (name, "_").ToLower(); + } + + XDocument LoadManifest (ParserContext context, Stream manifest, Uri url, ref Dictionary namespaces) + { + try { + context.CurrentManifestURL = null; + XDocument doc = ParseManifest (GetManifestNameForUrl (url), manifest, LoadOptions.SetLineInfo); + doc.Root.GetNamespaces (url, ref namespaces); + + context.CurrentManifestURL = url; + return doc; + } + catch (Exception) { + // Error message logged by ParseManifest() + return null; + } + } + + string GetManifestNameForUrl (Uri url) + { + var segments = url.Segments; + var result = "GoogleAndroidManifest"; + if (segments.Length > 0) { + var lastUrlSegment = segments[segments.Length - 1]; + return result += $"-{lastUrlSegment.Replace (".xml", "")}"; + } + return result; + } + } +} diff --git a/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/GoogleV2/IParserErrorHandler.cs b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/GoogleV2/IParserErrorHandler.cs new file mode 100644 index 00000000000..879f4606c99 --- /dev/null +++ b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/GoogleV2/IParserErrorHandler.cs @@ -0,0 +1,19 @@ +// +// Authors: +// Marek Habersack +// +// Copyright (c) 2017, Microsoft Corp. (http://microsoft.com) +// +// All rights reserved. +// +using System; +using System.Xml.Linq; + +namespace Xamarin.Installer.AndroidSDK.GoogleV2 +{ + public interface IParserErrorHandler + { + void OnRecoverableError (ParserErrorLevel recommendedLevel, Uri url, XElement element, string message); + void OnFatalError (Uri url, XElement element, string message); + } +} diff --git a/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/GoogleV2/Parsing/AddonList3Parser.cs b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/GoogleV2/Parsing/AddonList3Parser.cs new file mode 100644 index 00000000000..f44c5ad583a --- /dev/null +++ b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/GoogleV2/Parsing/AddonList3Parser.cs @@ -0,0 +1,58 @@ +// +// Authors: +// Marek Habersack +// +// Copyright (c) 2017, Marek Habersack +// +// All rights reserved. +// +using System; +using System.Collections.Generic; +using System.Xml.Linq; + +namespace Xamarin.Installer.AndroidSDK.GoogleV2.Parsing +{ + public class AddonList3Parser : ElementParser + { + List sites; + + public IList Sites { get; private set; } + + public AddonList3Parser (ParserContext parserContext, XElement element, Dictionary namespaces = null) : base (parserContext, element, namespaces) + { + IgnoreNamespaceAttributes = true; + } + + protected override Dictionary> GetKnownAttributes () + { + return null; + } + + protected override Dictionary> GetKnownChildElements () + { + return new Dictionary> (StringComparer.Ordinal) { + {"site", ParseChildElement_Site} + }; + } + + protected override void Parsed () + { + base.Parsed (); + Sites = sites; + } + + void ParseChildElement_Site (XElement element) + { + var asp = new AddonSiteParser (Context, element, Namespaces); + asp.Parse (); + + if (asp.Type == AddonSiteType.Unknown) + return; + + if (sites == null) + sites = new List (); + + sites.Add (new AddonSite (asp.Type, asp.DisplayName, asp.Url)); + } + } +} diff --git a/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/GoogleV2/Parsing/AddonSiteParser.cs b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/GoogleV2/Parsing/AddonSiteParser.cs new file mode 100644 index 00000000000..9f0cb9e9688 --- /dev/null +++ b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/GoogleV2/Parsing/AddonSiteParser.cs @@ -0,0 +1,90 @@ +// +// Authors: +// Marek Habersack +// +// Copyright (c) 2017, Marek Habersack +// +// All rights reserved. +// +using System; +using System.Collections.Generic; +using System.Xml.Linq; + +namespace Xamarin.Installer.AndroidSDK.GoogleV2.Parsing +{ + class AddonSiteParser : ElementParser + { + static readonly Dictionary siteTypeMap = new Dictionary (StringComparer.Ordinal) { + {"sdk:sysImgSiteType", AddonSiteType.SystemImage}, + {"sdk:addonSiteType", AddonSiteType.Addon} + }; + + public AddonSiteType Type { get; private set; } = AddonSiteType.Unknown; + public string DisplayName { get; private set; } = "Unnamed"; + public Uri Url { get; private set; } + + public AddonSiteParser (ParserContext parserContext, XElement element, Dictionary namespaces = null) : base (parserContext, element, namespaces) + { + } + + protected override Dictionary> GetKnownAttributes () + { + return new Dictionary> (StringComparer.Ordinal) { + {GetAttributeName ("xsi", "type"), ParseAttribute_Type} + }; + } + + protected override Dictionary> GetKnownChildElements () + { + return new Dictionary> (StringComparer.Ordinal) { + {"displayName", ParseChildElement_DisplayName}, + {"url", ParseChildElement_Url} + }; + } + + void ParseChildElement_DisplayName (XElement element) + { + DisplayName = element.Value ?? "Unnamed"; + } + + void ParseChildElement_Url (XElement element) + { + Uri url; + + if (!Uri.TryCreate (element.Value?.Trim (), UriKind.RelativeOrAbsolute, out url)) { + ErrorHandler.Error (Context.CurrentManifestURL, Element, $"The '{element.Name.LocalName}' element contains invalid URL: '{element.Value}'"); + return; + } + + Url = EnsureAbsoluteUrl (url); + } + + void ParseAttribute_Type (XAttribute attr) + { + AddonSiteType type; + if (!siteTypeMap.TryGetValue (attr.Value, out type)) { + ErrorHandler.Warning (Context.CurrentManifestURL, Element, $"'xsi:type' attribute with value '{attr.Value}' does not map to a known site type"); + return; + } + + Type = type; + } + + protected override void Parsed () + { + base.Parsed (); + + if (Type == AddonSiteType.Unknown) { + ErrorHandler.Warning (Context.CurrentManifestURL, Element, $"Unknown site type for element '{Element.Name.GetFullName ()}', site will be ignored"); + Url = null; + DisplayName = null; + return; + } + + if (Url == null) { + Type = AddonSiteType.Unknown; + ErrorHandler.Error (Context.CurrentManifestURL, Element, $"Element '{Element.Name.GetFullName ()}' has a valid type but no URL, site will be ignored"); + } + } + } +} diff --git a/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/GoogleV2/Parsing/ArchiveMetadataExtractor.cs b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/GoogleV2/Parsing/ArchiveMetadataExtractor.cs new file mode 100644 index 00000000000..73dbcf1df74 --- /dev/null +++ b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/GoogleV2/Parsing/ArchiveMetadataExtractor.cs @@ -0,0 +1,55 @@ +// +// Authors: +// Marek Habersack +// +// Copyright (c) 2017, Microsoft Corp. (http://microsoft.com) +// +// All rights reserved. +// +using System; +using System.Collections.Generic; +using System.Linq; +using System.Xml.Linq; + +namespace Xamarin.Installer.AndroidSDK.GoogleV2.Parsing +{ + class ArchiveMetadataExtractor + { + public ulong Size { get; } + public string Checksum { get; } + public string ChecksumType { get; } + public Uri Url { get; } + + public ArchiveMetadataExtractor (XElement element, ParserContext parserContext) + { + if (element == null) + throw new ArgumentNullException (nameof (element)); + + if (parserContext == null) + throw new ArgumentNullException (nameof (parserContext)); + + ulong s; + string v = element.GetChildElementValue ("size"); + if (!String.IsNullOrEmpty (v) && UInt64.TryParse (v, out s)) + Size = s; + else + Size = 0; + + XElement checksumElement = element.Elements ("checksum")?.FirstOrDefault (); + if (checksumElement != null) { + Checksum = checksumElement.Value; + ChecksumType = checksumElement.Attribute ("type")?.Value; + } + + Uri url; + v = element.GetChildElementValue ("url"); + if (String.IsNullOrEmpty (v) || !Uri.TryCreate (v, UriKind.RelativeOrAbsolute, out url)) + return; + + if (url.IsAbsoluteUri) + Url = url; + else + Url = new Uri (parserContext.CurrentManifestBaseURL, url); + } + } +} diff --git a/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/GoogleV2/Parsing/ArchiveParser.cs b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/GoogleV2/Parsing/ArchiveParser.cs new file mode 100644 index 00000000000..d713ca1be99 --- /dev/null +++ b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/GoogleV2/Parsing/ArchiveParser.cs @@ -0,0 +1,95 @@ +// +// Authors: +// Marek Habersack +// +// Copyright (c) 2017, Microsoft Corp. (http://microsoft.com) +// +// All rights reserved. +// +using System; +using System.Collections.Generic; +using System.Xml.Linq; + +using Xamarin.Installer.AndroidSDK.Common; + +namespace Xamarin.Installer.AndroidSDK.GoogleV2.Parsing +{ + class ArchiveParser : ElementParser + { + ulong size; + string checksum; + string checksumType; + Uri url; + string hostOS; + string hostArch; + uint hostBits; + IList patches; + + public Archive Archive { get; private set; } + + public ArchiveParser (ParserContext parserContext, XElement element, Dictionary namespaces = null) : base (parserContext, element, namespaces) + { + } + + protected override Dictionary> GetKnownAttributes () + { + return null; + } + + protected override Dictionary> GetKnownChildElements () + { + return new Dictionary> (StringComparer.Ordinal) { + {"complete", ParseChildElement_Complete}, + {"host-os", (XElement element) => hostOS = element.Value}, + {"host-bits", (XElement element) => hostBits = element.Value.AsUInt ()}, + {"host-arch", (XElement element) => { + hostArch = element.GetElementValue(); + if (hostBits != 0) { + hostBits = HostArchToBits(hostArch); + } + }}, + {"patches", ParseChildElement_Patches} + }; + } + + static uint HostArchToBits (string hostArch) + { + switch (hostArch) { + case "aarch64": return 64; + case "x64": return 64; + case "x86": return 32; + } + throw new NotSupportedException ($"Unsupported architecture {hostArch}"); + } + + void ParseChildElement_Patches (XElement element) + { + var ap = new ArchivePatchesParser (Context, element, Namespaces); + ap.Parse (); + patches = ap.Patches; + } + + void ParseChildElement_Complete (XElement element) + { + var ame = new ArchiveMetadataExtractor (element, Context); + size = ame.Size; + checksum = ame.Checksum; + checksumType = ame.ChecksumType; + url = ame.Url; + } + + protected override void Parsed () + { + base.Parsed (); + Archive = new Archive (hostOS) { + Size = size, + Checksum = checksum, + ChecksumType = checksumType, + Url = url, + HostBits = hostBits, + HostArch = hostArch, + Patches = patches + }; + } + } +} diff --git a/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/GoogleV2/Parsing/ArchivePatchParser.cs b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/GoogleV2/Parsing/ArchivePatchParser.cs new file mode 100644 index 00000000000..e5e3691475a --- /dev/null +++ b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/GoogleV2/Parsing/ArchivePatchParser.cs @@ -0,0 +1,65 @@ +// +// Authors: +// Marek Habersack +// +// Copyright (c) 2017, Marek Habersack +// +// All rights reserved. +// +using System; +using System.Collections.Generic; +using System.Xml.Linq; + +using Xamarin.Installer.AndroidSDK.Common; + +namespace Xamarin.Installer.AndroidSDK.GoogleV2.Parsing +{ + class ArchivePatchParser : ElementParser + { + AndroidRevision basedOn; + + public ArchivePatch Patch { get; private set; } + + public ArchivePatchParser (ParserContext parserContext, XElement element, Dictionary namespaces = null) : base (parserContext, element, namespaces) + { + } + + protected override Dictionary> GetKnownAttributes () + { + return null; + } + + protected override Dictionary> GetKnownChildElements () + { + return new Dictionary> (StringComparer.Ordinal) { + {"based-on", ParseChildElement_BasedOn}, + + // The three elements below are special treatment, done in Parsed() + {"size", (XElement) => {}}, + {"checksum", (XElement) => {}}, + {"url", (XElement) => {}}, + }; + } + + void ParseChildElement_BasedOn (XElement element) + { + var rp = new RevisionParser (Context, element, Namespaces); + rp.Parse (); + basedOn = rp.Revision; + } + + protected override void Parsed () + { + base.Parsed (); + + var ame = new ArchiveMetadataExtractor (Element, Context); + Patch = new ArchivePatch { + BasedOn = basedOn, + Checksum = ame.Checksum, + ChecksumType = ame.ChecksumType, + Size = ame.Size, + Url = ame.Url + }; + } + } +} diff --git a/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/GoogleV2/Parsing/ArchivePatchesParser.cs b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/GoogleV2/Parsing/ArchivePatchesParser.cs new file mode 100644 index 00000000000..37612d1fbd8 --- /dev/null +++ b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/GoogleV2/Parsing/ArchivePatchesParser.cs @@ -0,0 +1,58 @@ +// +// Authors: +// Marek Habersack +// +// Copyright (c) 2017, Marek Habersack +// +// All rights reserved. +// +using System; +using System.Collections.Generic; +using System.Linq; +using System.Xml.Linq; + +using Xamarin.Installer.AndroidSDK.Common; + +namespace Xamarin.Installer.AndroidSDK.GoogleV2.Parsing +{ + class ArchivePatchesParser : ElementParser + { + List patches; + + public IList Patches { get; private set; } + + public ArchivePatchesParser (ParserContext parserContext, XElement element, Dictionary namespaces = null) : base (parserContext, element, namespaces) + { + } + + protected override Dictionary> GetKnownAttributes () + { + return null; + } + + protected override Dictionary> GetKnownChildElements () + { + return new Dictionary> (StringComparer.Ordinal) { + {"patch", ParseChildElement_Patch} + }; + } + + void ParseChildElement_Patch (XElement element) + { + var ap = new ArchivePatchParser (Context, element, Namespaces); + ap.Parse (); + if (ap.Patch == null) + return; + + if (patches == null) + patches = new List (); + patches.Add (ap.Patch); + } + + protected override void Parsed () + { + base.Parsed (); + Patches = patches?.AsReadOnly (); + } + } +} diff --git a/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/GoogleV2/Parsing/ArchivesParser.cs b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/GoogleV2/Parsing/ArchivesParser.cs new file mode 100644 index 00000000000..e66562a9deb --- /dev/null +++ b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/GoogleV2/Parsing/ArchivesParser.cs @@ -0,0 +1,58 @@ +// +// Authors: +// Marek Habersack +// +// Copyright (c) 2017, Microsoft Corp. (http://microsoft.com) +// +// All rights reserved. +// +using System; +using System.Collections.Generic; +using System.Linq; +using System.Xml.Linq; + +using Xamarin.Installer.AndroidSDK.Common; + +namespace Xamarin.Installer.AndroidSDK.GoogleV2.Parsing +{ + class ArchivesParser : ElementParser + { + List archives; + + public IList Archives { get; private set; } + + public ArchivesParser (ParserContext parserContext, XElement element, Dictionary namespaces = null) : base (parserContext, element, namespaces) + { + } + + protected override Dictionary> GetKnownAttributes () + { + return null; + } + + protected override Dictionary> GetKnownChildElements () + { + return new Dictionary> (StringComparer.Ordinal) { + {"archive", ParseChildElement_Archive} + }; + } + + void ParseChildElement_Archive (XElement element) + { + var ap = new ArchiveParser (Context, element, Namespaces); + ap.Parse (); + if (ap.Archive == null) + return; + + if (archives == null) + archives = new List (); + archives.Add (ap.Archive); + } + + protected override void Parsed () + { + base.Parsed (); + Archives = archives?.AsReadOnly (); + } + } +} diff --git a/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/GoogleV2/Parsing/DependenciesParser.cs b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/GoogleV2/Parsing/DependenciesParser.cs new file mode 100644 index 00000000000..97ab2f29ed5 --- /dev/null +++ b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/GoogleV2/Parsing/DependenciesParser.cs @@ -0,0 +1,59 @@ +// +// Authors: +// Marek Habersack +// +// Copyright (c) 2017, Microsoft Corp. (http://microsoft.com) +// +// All rights reserved. +// +using System; +using System.Collections.Generic; +using System.Linq; +using System.Xml.Linq; + +using Xamarin.Installer.AndroidSDK.Common; + +namespace Xamarin.Installer.AndroidSDK.GoogleV2.Parsing +{ + class DependenciesParser : ElementParser + { + List dependencies; + + public IList Dependencies { get; private set; } + + public DependenciesParser (ParserContext parserContext, XElement element, Dictionary namespaces = null) : base (parserContext, element, namespaces) + { + } + + protected override Dictionary> GetKnownAttributes () + { + return null; + } + + protected override Dictionary> GetKnownChildElements () + { + return new Dictionary> (StringComparer.Ordinal) { + {"dependency", ParseChildElement_Dependency} + }; + } + + void ParseChildElement_Dependency (XElement element) + { + var dep = new DependencyParser (Context, element, Namespaces); + dep.Parse (); + + if (dep.Dependency == null) + return; + + if (dependencies == null) + dependencies = new List (); + dependencies.Add (dep.Dependency); + } + + protected override void Parsed () + { + base.Parsed (); + Dependencies = dependencies?.AsReadOnly (); + } + } +} diff --git a/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/GoogleV2/Parsing/DependencyParser.cs b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/GoogleV2/Parsing/DependencyParser.cs new file mode 100644 index 00000000000..e958931f3bc --- /dev/null +++ b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/GoogleV2/Parsing/DependencyParser.cs @@ -0,0 +1,62 @@ +// +// Authors: +// Marek Habersack +// +// Copyright (c) 2017, Microsoft Corp. (http://microsoft.com) +// +// All rights reserved. +// +using System; +using System.Collections.Generic; +using System.Xml.Linq; + +using Xamarin.Installer.AndroidSDK.Common; + +namespace Xamarin.Installer.AndroidSDK.GoogleV2.Parsing +{ + class DependencyParser : ElementParser + { + string path; + AndroidRevision minRevision; + + public Dependency Dependency { get; private set; } + + public DependencyParser (ParserContext parserContext, XElement element, Dictionary namespaces = null) : base (parserContext, element, namespaces) + { + } + + protected override Dictionary> GetKnownAttributes () + { + return new Dictionary> (StringComparer.Ordinal) { + {"path", ParseAttribute_Path} + }; + } + + void ParseAttribute_Path (XAttribute attr) + { + path = attr.Value; + } + + protected override Dictionary> GetKnownChildElements () + { + return new Dictionary> (StringComparer.Ordinal) { + {"min-revision", ParseChildElement_MinRevision} + }; + } + + void ParseChildElement_MinRevision (XElement element) + { + var rp = new RevisionParser (Context, element, Namespaces); + rp.Parse (); + minRevision = rp.Revision; + } + + protected override void Parsed () + { + base.Parsed (); + if (String.IsNullOrEmpty (path)) + return; + Dependency = new Dependency (path, minRevision); + } + } +} diff --git a/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/GoogleV2/Parsing/ElementParser.cs b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/GoogleV2/Parsing/ElementParser.cs new file mode 100644 index 00000000000..e253716f853 --- /dev/null +++ b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/GoogleV2/Parsing/ElementParser.cs @@ -0,0 +1,133 @@ +// +// Authors: +// Marek Habersack +// +// Copyright (c) 2017, Microsoft Corp. (http://microsoft.com) +// +// All rights reserved. +// +using System; +using System.Collections.Generic; +using System.Xml; +using System.Xml.Linq; + +namespace Xamarin.Installer.AndroidSDK.GoogleV2.Parsing +{ + public abstract class ElementParser + { + protected XElement Element { get; } + protected ParserContext Context { get; } + protected Dictionary Namespaces { get; } + protected IParserErrorHandler ErrorHandler { get; } + protected bool IgnoreNamespaceAttributes { get; set; } + + protected ElementParser (ParserContext parserContext, XElement element, Dictionary namespaces) + { + Context = parserContext ?? throw new ArgumentNullException (nameof (parserContext)); + ErrorHandler = parserContext.ErrorHandler; + Element = element ?? throw new ArgumentNullException (nameof (element)); + Namespaces = namespaces; + } + + public void Parse () + { + Dictionary> knownAttributes = GetKnownAttributes (); + Dictionary> knownElements = GetKnownChildElements (); + + if (Element.HasAttributes) { + if (knownAttributes == null) { + if (!IgnoreNamespaceAttributes || !AllAttributesAreNamespaceDeclarations (Element)) + ErrorHandler.Warning (Context.CurrentManifestURL, Element, $"Element '{Element.Name.GetFullName ()}' with parser {GetType().Name} has attributes but this version of the library does not support any of them"); + } else { + Action handler; + foreach (XAttribute attr in Element.Attributes ()) { + if (IgnoreNamespaceAttributes && attr.IsNamespaceDeclaration) + continue; + + if (!knownAttributes.TryGetValue (attr.Name.GetFullName (), out handler)) + handler = null; + + if (handler == null) { + ErrorHandler.Warning (Context.CurrentManifestURL, Element, $"Element '{Element.Name.GetFullName ()}' with parser {GetType().Name} has attribute '{attr.Name.GetFullName ()}' but this version of the library does not support it"); + continue; + } + + handler (attr); + } + } + } + + if (Element.HasElements) { + if (knownElements == null) { + ErrorHandler.Warning (Context.CurrentManifestURL, Element, $"Element '{Element.Name.GetFullName ()}' with parser {GetType().Name} has child elements but this version of the library does not support any children in this element"); + } else { + Action handler; + foreach (XElement child in Element.Elements ()) { + if (!knownElements.TryGetValue (child.Name.GetFullName (), out handler)) + handler = null; + + if (handler == null) { + ErrorHandler.Error (Context.CurrentManifestURL, Element, $"Element '{Element.Name.GetFullName ()}' with parser {GetType().Name} has a child element '{child.Name.GetFullName ()}' but this version of the library does not support it"); + continue; + } + + handler (child); + } + } + } + + Parsed (); + } + + protected virtual void Parsed () + { + } + + bool AllAttributesAreNamespaceDeclarations (XElement element) + { + foreach (XAttribute attr in element.Attributes ()) { + if (!attr.IsNamespaceDeclaration) + return false; + } + + return true; + } + + protected string GetAttributeName (string namespaceName, string attributeName, bool warnIfMissing = true) + { + XNamespace xsins = GetNamespace (namespaceName); + if (xsins == null && warnIfMissing) + ErrorHandler.Warning (Context.CurrentManifestURL, Element, $"Required '{namespaceName}' namespace not declared, parsing results might be invalid"); + + return xsins == null ? $"{namespaceName}:{attributeName}" : (xsins + attributeName).GetFullName (); + } + + protected XNamespace GetNamespace (string name) + { + return GetNamespace (name, Namespaces); + } + + public static XNamespace GetNamespace (string name, Dictionary namespaces) + { + if (String.IsNullOrEmpty (name) || namespaces == null) + return null; + + XNamespace ns; + if (!namespaces.TryGetValue (name, out ns)) + return null; + + return ns; + } + + protected Uri EnsureAbsoluteUrl (Uri url) + { + if (url.IsAbsoluteUri) + return url; + + return new Uri (Context.BaseURL, url); + } + + protected abstract Dictionary> GetKnownChildElements (); + protected abstract Dictionary> GetKnownAttributes (); + } +} diff --git a/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/GoogleV2/Parsing/LibrariesParser.cs b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/GoogleV2/Parsing/LibrariesParser.cs new file mode 100644 index 00000000000..5f6d6761ac6 --- /dev/null +++ b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/GoogleV2/Parsing/LibrariesParser.cs @@ -0,0 +1,58 @@ +// +// Authors: +// Marek Habersack +// +// Copyright (c) 2017, Microsoft Corp. (http://microsoft.com) +// +// All rights reserved. +// +using System; +using System.Collections.Generic; +using System.Linq; +using System.Xml.Linq; + +using Xamarin.Installer.AndroidSDK.Common; + +namespace Xamarin.Installer.AndroidSDK.GoogleV2.Parsing +{ + class LibrariesParser : ElementParser + { + List libraries; + + public IList Libraries { get; private set; } + + public LibrariesParser (ParserContext parserContext, XElement element, Dictionary namespaces = null) : base (parserContext, element, namespaces) + { + } + + protected override Dictionary> GetKnownAttributes () + { + return null; + } + + protected override Dictionary> GetKnownChildElements () + { + return new Dictionary> (StringComparer.Ordinal) { + {"library", ParseChildElement_Archive} + }; + } + + void ParseChildElement_Archive (XElement element) + { + var lp = new LibraryParser (Context, element, Namespaces); + lp.Parse (); + if (lp.Library == null) + return; + + if (libraries == null) + libraries = new List (); + libraries.Add (lp.Library); + } + + protected override void Parsed () + { + base.Parsed (); + Libraries = libraries?.AsReadOnly (); + } + } +} diff --git a/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/GoogleV2/Parsing/LibraryParser.cs b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/GoogleV2/Parsing/LibraryParser.cs new file mode 100644 index 00000000000..f3c23bfa675 --- /dev/null +++ b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/GoogleV2/Parsing/LibraryParser.cs @@ -0,0 +1,54 @@ +// +// Authors: +// Marek Habersack +// +// Copyright (c) 2017, Microsoft Corp. (http://microsoft.com) +// +// All rights reserved. +// +using System; +using System.Collections.Generic; +using System.Xml.Linq; + +using Xamarin.Installer.AndroidSDK.Common; + +namespace Xamarin.Installer.AndroidSDK.GoogleV2.Parsing +{ + class LibraryParser : ElementParser + { + string localJarPath; + string name; + string description; + + public PackageLibrary Library { get; private set; } + + public LibraryParser (ParserContext parserContext, XElement element, Dictionary namespaces = null) : base (parserContext, element, namespaces) + { + } + + protected override Dictionary> GetKnownAttributes () + { + return new Dictionary> (StringComparer.Ordinal) { + {"localJarPath", (XAttribute attribute) => localJarPath = attribute.Value ?? String.Empty }, + {"name", (XAttribute attribute) => name = attribute.Value ?? String.Empty } + }; + } + + protected override Dictionary> GetKnownChildElements () + { + return new Dictionary> (StringComparer.Ordinal) { + {"description", (XElement element) => description = element.Value ?? String.Empty }, + }; + } + + protected override void Parsed () + { + base.Parsed (); + Library = new PackageLibrary { + LocalJarPath = localJarPath, + Name = name, + Description = description + }; + } + } +} diff --git a/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/GoogleV2/Parsing/LicenseParser.cs b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/GoogleV2/Parsing/LicenseParser.cs new file mode 100644 index 00000000000..f6243e7ad70 --- /dev/null +++ b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/GoogleV2/Parsing/LicenseParser.cs @@ -0,0 +1,61 @@ +// +// Authors: +// Marek Habersack +// +// Copyright (c) 2017, Microsoft, Inc (http://microsoft.com) +// +// All rights reserved. +// +using System; +using System.Collections.Generic; +using System.Xml.Linq; + +using Xamarin.Installer.AndroidSDK.Common; + +namespace Xamarin.Installer.AndroidSDK.GoogleV2.Parsing +{ + class LicenseParser : ElementParser + { + string id; + string type; + + public License License { get; private set; } + + public LicenseParser (ParserContext parserContext, XElement element, Dictionary namespaces = null) : base (parserContext, element, namespaces) + { + } + + protected override Dictionary> GetKnownAttributes () + { + return new Dictionary> (StringComparer.Ordinal) { + {"id", ParseAttribute_Id}, + {"type", ParseAttribute_Type} + }; + } + + protected override Dictionary> GetKnownChildElements () + { + return null; + } + + protected override void Parsed () + { + base.Parsed (); + if (String.IsNullOrEmpty (id)) { + ErrorHandler.Error (Context.CurrentManifestURL, Element, $"License element is missing the required 'id' attribute"); + return; + } + License = new License (id, type, Element.Value ?? String.Empty); + } + + void ParseAttribute_Id (XAttribute attr) + { + id = attr?.Value ?? String.Empty; + } + + void ParseAttribute_Type (XAttribute attr) + { + type = attr?.Value ?? String.Empty; + } + } +} diff --git a/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/GoogleV2/Parsing/PackageTagParser.cs b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/GoogleV2/Parsing/PackageTagParser.cs new file mode 100644 index 00000000000..7fcaf2cadcf --- /dev/null +++ b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/GoogleV2/Parsing/PackageTagParser.cs @@ -0,0 +1,53 @@ +// +// Authors: +// Marek Habersack +// +// Copyright (c) 2017, Marek Habersack +// +// All rights reserved. +// +using System; +using System.Collections.Generic; +using System.Xml.Linq; + +using Xamarin.Installer.AndroidSDK.Common; + +namespace Xamarin.Installer.AndroidSDK.GoogleV2.Parsing +{ + class PackageTagParser : ElementParser + { + string id; + string display; + + public PackageTag Tag { get; private set; } + + public PackageTagParser (ParserContext parserContext, XElement element, Dictionary namespaces = null) : base (parserContext, element, namespaces) + { + } + + protected override Dictionary> GetKnownAttributes () + { + return null; + } + + protected override Dictionary> GetKnownChildElements () + { + return new Dictionary> (StringComparer.Ordinal) { + {"id", (XElement e) => id = e.Value?.Trim ()}, + {"display", (XElement e) => display = e.Value?.Trim ()} + }; + } + + protected override void Parsed () + { + base.Parsed (); + + if (String.IsNullOrEmpty (id)) { + ErrorHandler.Error (Context.CurrentManifestURL, Element, $"Missing child element 'id' on element {Element.Name}"); + return; + } + + Tag = new PackageTag (id, display); + } + } +} diff --git a/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/GoogleV2/Parsing/PackageVendorParser.cs b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/GoogleV2/Parsing/PackageVendorParser.cs new file mode 100644 index 00000000000..b8217b80295 --- /dev/null +++ b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/GoogleV2/Parsing/PackageVendorParser.cs @@ -0,0 +1,52 @@ +// +// Authors: +// Marek Habersack +// +// Copyright (c) 2017, Marek Habersack +// +// All rights reserved. +// +using System; +using System.Collections.Generic; +using System.Xml.Linq; + +using Xamarin.Installer.AndroidSDK.Common; + +namespace Xamarin.Installer.AndroidSDK.GoogleV2.Parsing +{ + class PackageVendorParser : ElementParser + { + string id; + string display; + + public PackageVendor Vendor { get; private set; } + + public PackageVendorParser (ParserContext parserContext, XElement element, Dictionary namespaces = null) : base (parserContext, element, namespaces) + {} + + protected override Dictionary> GetKnownAttributes () + { + return null; + } + + protected override Dictionary> GetKnownChildElements () + { + return new Dictionary> (StringComparer.Ordinal) { + {"id", (XElement e) => id = e.Value?.Trim ()}, + {"display", (XElement e) => display = e.Value?.Trim ()} + }; + } + + protected override void Parsed () + { + base.Parsed (); + + if (String.IsNullOrEmpty (id)) { + ErrorHandler.Error (Context.CurrentManifestURL, Element, $"Missing child element 'id' on element {Element.Name}"); + return; + } + + Vendor = new PackageVendor (id, display); + } + } +} diff --git a/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/GoogleV2/Parsing/ParserContext.cs b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/GoogleV2/Parsing/ParserContext.cs new file mode 100644 index 00000000000..605fe0224f2 --- /dev/null +++ b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/GoogleV2/Parsing/ParserContext.cs @@ -0,0 +1,82 @@ +// +// Authors: +// Marek Habersack +// +// Copyright (c) 2017, Microsoft Corp. (http://microsoft.com) +// +// All rights reserved. +// +using System; +using System.Collections.Generic; +using System.IO; + +using Xamarin.Installer.AndroidSDK.Common; + +namespace Xamarin.Installer.AndroidSDK.GoogleV2.Parsing +{ + public class ParserContext : IDisposable + { + Uri currentManifestURL; + Uri currentManifestBaseURL; + + public IParserErrorHandler ErrorHandler { get; } + public Uri BaseURL { get; } + public Uri RepositoryManifestURL { get; } + public Uri AddonsListManifestURL { get; } + public Stream RepositoryManifest { get; } + public Stream AddonsListManifest { get; } + + public Uri CurrentManifestBaseURL => GetCurrentManifestBaseURL (); + + public Uri CurrentManifestURL { + get { return currentManifestURL; } + set { + currentManifestURL = value; + currentManifestBaseURL = null; + } + } + + + public IDictionary Licenses { get; private set; } + public IDictionary Channels { get; private set; } + + /// + /// Parse Context + /// + /// + /// + /// + /// + /// + /// If null, addons are not available + public ParserContext (IParserErrorHandler errorHandler, Uri baseURL, Uri repositoryManifestURL, Stream repositoryManifest, Uri addonsListManifestURL, Stream addonsListManifest = null) + { + ErrorHandler = errorHandler ?? throw new ArgumentNullException (nameof (errorHandler)); + BaseURL = baseURL ?? throw new ArgumentNullException (nameof (baseURL)); + RepositoryManifestURL = repositoryManifestURL ?? throw new ArgumentNullException (nameof (repositoryManifestURL)); + RepositoryManifest = repositoryManifest ?? throw new ArgumentNullException (nameof (repositoryManifest)); + AddonsListManifestURL = addonsListManifestURL ?? throw new ArgumentNullException (nameof (addonsListManifestURL)); + AddonsListManifest = addonsListManifest; + } + + public ParserContext (IParserErrorHandler errorHandler, Uri repositoryManifestURL) + { + ErrorHandler = errorHandler ?? throw new ArgumentNullException (nameof (errorHandler)); + RepositoryManifestURL = repositoryManifestURL ?? throw new ArgumentNullException (nameof (repositoryManifestURL)); + } + + public void Dispose () + { + RepositoryManifest?.Dispose (); + AddonsListManifest?.Dispose (); + } + + Uri GetCurrentManifestBaseURL () + { + if (currentManifestBaseURL != null) + return currentManifestBaseURL; + currentManifestBaseURL = currentManifestURL == null ? null : Helpers.GetBaseURL (currentManifestURL); + return currentManifestBaseURL; + } + } +} diff --git a/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/GoogleV2/Parsing/RemotePackageParser.cs b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/GoogleV2/Parsing/RemotePackageParser.cs new file mode 100644 index 00000000000..7d27744db8f --- /dev/null +++ b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/GoogleV2/Parsing/RemotePackageParser.cs @@ -0,0 +1,158 @@ +// +// Authors: +// Marek Habersack +// +// Copyright (c) 2017, Microsoft Corp. (http://microsoft.com) +// +// All rights reserved. +// +using System; +using System.Collections.Generic; +using System.Xml.Linq; + +using Xamarin.Installer.AndroidSDK.Common; + +using IOPath = System.IO.Path; + +namespace Xamarin.Installer.AndroidSDK.GoogleV2.Parsing +{ + class RemotePackageParser : ElementParser + { + Repository repository; + + protected string Path { get; private set; } = String.Empty; + protected string FileSystemPath { get; private set; } + protected bool Obsolete { get; set; } + protected AndroidComponentInfo Info { get; private set; } + protected AndroidRevision Revision { get; private set; } + protected string DisplayName { get; private set; } = String.Empty; + protected IList Dependencies { get; private set; } + protected string ChannelID { get; private set; } + protected IList Archives { get; private set; } + protected string LicenseID { get; private set; } + + public RemotePackage Package { get; private set; } + + internal bool IncludeAllArchives { get; set; } // For Xamarin manifest generator this is 'true' + + public RemotePackageParser (Repository repository, ParserContext parserContext, XElement element, Dictionary namespaces = null) : base (parserContext, element, namespaces) + { + this.repository = repository ?? throw new ArgumentNullException (nameof (repository)); + } + + protected override Dictionary> GetKnownChildElements () + { + return new Dictionary> (StringComparer.Ordinal) { + {"type-details", ParseChildElement_TypeDetails}, + {"revision", ParseChildElement_Revision}, + {"display-name", ParseChildElement_DisplayName}, + {"uses-license", ParseChildElement_UsesLicense}, + {"channelRef", ParseChildElement_ChannelRef}, + {"dependencies", ParseChildElement_Dependencies}, + {"archives", ParseChildElement_Archives}, + }; + } + + protected override void Parsed () + { + base.Parsed (); + + Package = new RemotePackage (repository, Context.CurrentManifestURL, Element.GetLineInfo (), ErrorHandler, Archives) { + ChannelID = ChannelID, + Dependencies = Dependencies, + DisplayName = DisplayName, + FileSystemPath = FileSystemPath, + LicenseID = LicenseID, + Obsolete = Obsolete, + Path = Path, + Revision = Revision, + Info = Info, + IncludeAllArchives = IncludeAllArchives + }; + Package.Verify (); + } + + void ParseChildElement_Archives (XElement element) + { + var ap = new ArchivesParser (Context, element, Namespaces); + ap.Parse (); + Archives = ap.Archives; + } + + void ParseChildElement_Dependencies (XElement element) + { + var dp = new DependenciesParser (Context, element, Namespaces); + dp.Parse (); + Dependencies = dp.Dependencies; + } + + void ParseChildElement_ChannelRef (XElement element) + { + ChannelID = GetRefAttributeValue (element); + } + + void ParseChildElement_UsesLicense (XElement element) + { + LicenseID = GetRefAttributeValue (element); + } + + string GetRefAttributeValue (XElement element) + { + return element.Attribute ("ref")?.Value ?? String.Empty; + } + + void ParseChildElement_DisplayName (XElement element) + { + DisplayName = element?.Value ?? String.Empty; + } + + void ParseChildElement_Revision (XElement element) + { + var rp = new RevisionParser (Context, element, Namespaces); + rp.Parse (); + if (rp.Revision == null) { + ErrorHandler.Warning (Context.CurrentManifestURL, Element, "Package is missing a valid 'revision' child element"); + Revision = null; + } else + Revision = rp.Revision; + } + + void ParseChildElement_TypeDetails (XElement element) + { + if (Info != null) + ErrorHandler.Info (Context.CurrentManifestURL, element, "'type-details' element present more than once within this package. Will override old values."); + + Info = null; + TypeDetailsParser td = TypeDetailsParserFactory.CreateInstance (Context, element, Namespaces); + if (td == null) { + ErrorHandler.Error (Context.CurrentManifestURL, Element, "Package is missing the 'type-details' child element"); + return; + } + td.Parse (); + + Info = td.Info; + } + + protected override Dictionary> GetKnownAttributes () + { + return new Dictionary> (StringComparer.Ordinal) { + {"path", ParseAttribute_Path}, + {"obsolete", ParseAttribute_Obsolete}, + }; + } + + void ParseAttribute_Obsolete (XAttribute attr) + { + string value = attr.Value; + if (String.IsNullOrEmpty (value)) + return; + Obsolete = String.Compare ("true", value, StringComparison.OrdinalIgnoreCase) == 0; + } + + void ParseAttribute_Path (XAttribute attr) + { + Path = attr.Value ?? String.Empty; + FileSystemPath = Path.Replace (';', IOPath.DirectorySeparatorChar); + } + } +} diff --git a/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/GoogleV2/Parsing/RepositoryParser.cs b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/GoogleV2/Parsing/RepositoryParser.cs new file mode 100644 index 00000000000..785765c500b --- /dev/null +++ b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/GoogleV2/Parsing/RepositoryParser.cs @@ -0,0 +1,127 @@ +// +// Authors: +// Marek Habersack +// +// Copyright (c) 2017, Microsoft Corp. (http://microsoft.com) +// +// All rights reserved. +// +using System; +using System.Collections.Generic; +using System.Xml.Linq; + +using Xamarin.Installer.AndroidSDK.Common; + +namespace Xamarin.Installer.AndroidSDK.GoogleV2.Parsing +{ + public class RepositoryParser : ElementParser + { + Dictionary licenses; + Dictionary channels; + List packages; + GoogleV2Repository repository; + + public Dictionary Licenses => licenses; + public Dictionary Channels => channels; + public List Packages => packages; + public Uri RepositoryURL { get; } + + public bool IncludeAllArchives { get; set; } // For Xamarin manifest generator this is 'true' + + public RepositoryParser (GoogleV2Repository repository, ParserContext parserContext, XElement element, Dictionary namespaces = null) : base (parserContext, element, namespaces) + { + this.repository = repository ?? throw new ArgumentNullException (nameof (repository)); + IgnoreNamespaceAttributes = true; + RepositoryURL = parserContext.CurrentManifestURL; + } + + protected override Dictionary> GetKnownAttributes () + { + return null; + } + + protected override Dictionary> GetKnownChildElements () + { + return new Dictionary> (StringComparer.Ordinal) { + {"license", ParseChildElement_License}, + {"channel", ParseChildElement_Channel}, + {"remotePackage", ParseChildElement_RemotePackage}, + }; + } + + void ParseChildElement_RemotePackage (XElement element) + { + var rp = new RemotePackageParser (repository, Context, element, Namespaces) { + IncludeAllArchives = IncludeAllArchives + }; + rp.Parse (); + AddPackage (rp.Package); + } + + void ParseChildElement_Channel (XElement element) + { + string id = element.Attribute ("id")?.Value; + if (String.IsNullOrEmpty (id)) { + ErrorHandler.Error (Context.CurrentManifestURL, element, $"Channel element misses the required 'id' attribute"); + return; + } + + string value = element.Value ?? id; + AddChannel (id, value); + } + + void ParseChildElement_License (XElement element) + { + var lp = new LicenseParser (Context, element, Namespaces); + lp.Parse (); + AddLicense (lp.License); + } + + void AddPackage (RemotePackage package) + { + if (packages == null) + packages = new List (); + packages.Add (package); + } + + void AddChannel (string id, string text) + { + AddDictionaryItem ( + new Channel (id, text), + id, + ref channels, + $"Duplicate channel with id '{id}' found. Will replace the old value" + ); + } + + void AddLicense (License license) + { + if (license == null) + return; + + AddDictionaryItem ( + license, + license.ID, + ref licenses, + $"Duplicate license with id '{license.ID}' found. Will replace the old value" + ); + } + + void AddDictionaryItem (T item, string key, ref Dictionary dict, string duplicateMessage, Func getDuplicateLocation = null) + { + if (dict == null) + dict = new Dictionary (StringComparer.Ordinal); + + if (dict.ContainsKey (key)) { + ErrorHandler.Warning (Context.CurrentManifestURL, Element, duplicateMessage); + if (getDuplicateLocation != null) { + string location = getDuplicateLocation (dict[key]); + if (!String.IsNullOrEmpty (location)) + ErrorHandler.Warning ($"Location of the previous value: {location}"); + } + dict[key] = item; + } else + dict.Add (key, item); + } + } +} diff --git a/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/GoogleV2/Parsing/RevisionParser.cs b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/GoogleV2/Parsing/RevisionParser.cs new file mode 100644 index 00000000000..baf0e3d5fd7 --- /dev/null +++ b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/GoogleV2/Parsing/RevisionParser.cs @@ -0,0 +1,69 @@ +// +// Authors: +// Marek Habersack +// +// Copyright (c) 2017, Microsoft Corp. (http://microsoft.com) +// +// All rights reserved. +// +using System; +using System.Collections.Generic; +using System.Xml.Linq; + +namespace Xamarin.Installer.AndroidSDK.GoogleV2.Parsing +{ + class RevisionParser : ElementParser + { + int major; + int minor; + int micro; + int preview; + + public AndroidRevision Revision { get; private set; } + + public RevisionParser (ParserContext parserContext, XElement element, Dictionary namespaces = null) : base (parserContext, element, namespaces) + { + major = minor = micro = preview = -1; + } + + protected override void Parsed () + { + base.Parsed (); + if (major < 0) { + ErrorHandler.Error (Context.CurrentManifestURL, Element, $"Required 'major' version component is either missing or has invalid value ({major})"); + Revision = null; + } else + Revision = new AndroidRevision (major, minor, micro, preview); + } + + protected override Dictionary> GetKnownChildElements () + { + return new Dictionary> (StringComparer.Ordinal) { + {"major", (XElement e) => major = ParseValue (e)}, + {"minor", (XElement e) => minor = ParseValue (e)}, + {"micro", (XElement e) => micro = ParseValue (e)}, + {"preview", (XElement e) => preview = ParseValue (e)}, + }; + } + + int ParseValue (XElement element) + { + string v = element?.Value?.Trim (); + if (String.IsNullOrEmpty (v)) + return -1; + + int ret; + if (!Int32.TryParse (v, out ret)) { + ErrorHandler.Error (Context.CurrentManifestURL, element, $"Invalid value of element '{element.Name}'. Expected integer larger than 0"); + return -1; + } + + return ret; + } + + protected override Dictionary> GetKnownAttributes () + { + return null; + } + } +} diff --git a/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/GoogleV2/Parsing/TypeDetailsAddOnParser.cs b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/GoogleV2/Parsing/TypeDetailsAddOnParser.cs new file mode 100644 index 00000000000..95d0ac0a7c6 --- /dev/null +++ b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/GoogleV2/Parsing/TypeDetailsAddOnParser.cs @@ -0,0 +1,61 @@ +// +// Authors: +// Marek Habersack +// +// Copyright (c) 2017, Microsoft, Inc +// +// All rights reserved. +// +using System; +using System.Collections.Generic; +using System.Xml.Linq; + +using Xamarin.Installer.AndroidSDK.Common; + +namespace Xamarin.Installer.AndroidSDK.GoogleV2.Parsing +{ + class TypeDetailsAddOnParser : TypeDetailsParser + { + public string ApiLevel { get; private set; } + public string CodeName { get; private set; } + public PackageVendor Vendor { get; private set; } + public PackageTag Tag { get; private set; } + public IList Libraries { get; private set; } + + public TypeDetailsAddOnParser (ParserContext parserContext, XElement element, Dictionary namespaces = null) : base (parserContext, element, namespaces) + {} + + protected override Dictionary> GetKnownChildElements () + { + Dictionary> dict = EnsureDictionary (base.GetKnownChildElements ()); + dict["api-level"] = (XElement e) => ApiLevel = e.GetElementValue (); + dict["codename"] = (XElement e) => CodeName = e.GetElementValue (); + dict["libraries"] = ParseChildElement_Libraries; + dict["tag"] = (XElement e) => Tag = ParseChildElement_Tag (e); + dict["vendor"] = (XElement e) => Vendor = ParseChildElement_Vendor (e); + + // https://dl.google.com/android/repository/addon2-3.xml [605:4] + dict["base-extension"] = (XElement e) => {}; // ignore + return dict; + } + + void ParseChildElement_Libraries (XElement element) + { + var lp = new LibrariesParser (Context, element, Namespaces); + lp.Parse (); + if (lp.Libraries == null || lp.Libraries.Count == 0) + return; + Libraries = lp.Libraries; + } + + protected override AndroidComponentInfo CreateComponentInfo () + { + return new AndroidComponentInfoAddon (Type, ApiLevel, CodeName, Tag, Vendor, Libraries); + } + + protected override bool Validate () + { + return base.Validate () && ValidateApiLevel (ApiLevel); + } + } +} diff --git a/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/GoogleV2/Parsing/TypeDetailsExtraParser.cs b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/GoogleV2/Parsing/TypeDetailsExtraParser.cs new file mode 100644 index 00000000000..aaf9ccd4ecb --- /dev/null +++ b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/GoogleV2/Parsing/TypeDetailsExtraParser.cs @@ -0,0 +1,36 @@ +// +// Authors: +// Marek Habersack +// +// Copyright (c) 2017, Microsoft, Inc +// +// All rights reserved. +// +using System; +using System.Collections.Generic; +using System.Xml.Linq; + +using Xamarin.Installer.AndroidSDK.Common; + +namespace Xamarin.Installer.AndroidSDK.GoogleV2.Parsing +{ + class TypeDetailsExtraParser : TypeDetailsParser + { + public PackageVendor Vendor { get; set; } + + public TypeDetailsExtraParser (ParserContext parserContext, XElement element, Dictionary namespaces = null) : base (parserContext, element, namespaces) + {} + + protected override Dictionary> GetKnownChildElements () + { + Dictionary> dict = EnsureDictionary (base.GetKnownChildElements ()); + dict["vendor"] = (XElement e) => Vendor = ParseChildElement_Vendor (e); + return dict; + } + + protected override AndroidComponentInfo CreateComponentInfo () + { + return new AndroidComponentInfoExtra (Type, Vendor); + } + } +} diff --git a/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/GoogleV2/Parsing/TypeDetailsGenericParser.cs b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/GoogleV2/Parsing/TypeDetailsGenericParser.cs new file mode 100644 index 00000000000..fd0e9b4eba5 --- /dev/null +++ b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/GoogleV2/Parsing/TypeDetailsGenericParser.cs @@ -0,0 +1,27 @@ +// +// Authors: +// Marek Habersack +// +// Copyright (c) 2017, Marek Habersack +// +// All rights reserved. +// +using System; +using System.Collections.Generic; +using System.Xml.Linq; + +using Xamarin.Installer.AndroidSDK.Common; + +namespace Xamarin.Installer.AndroidSDK.GoogleV2.Parsing +{ + class TypeDetailsGenericParser : TypeDetailsParser + { + public TypeDetailsGenericParser (ParserContext parserContext, XElement element, Dictionary namespaces = null) : base (parserContext, element, namespaces) + { } + + protected override AndroidComponentInfo CreateComponentInfo () + { + return new AndroidComponentInfoGeneric (Type); + } + } +} diff --git a/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/GoogleV2/Parsing/TypeDetailsMavenParser.cs b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/GoogleV2/Parsing/TypeDetailsMavenParser.cs new file mode 100644 index 00000000000..95be22fe32a --- /dev/null +++ b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/GoogleV2/Parsing/TypeDetailsMavenParser.cs @@ -0,0 +1,36 @@ +// +// Authors: +// Marek Habersack +// +// Copyright (c) 2017, Marek Habersack +// +// All rights reserved. +// +using System; +using System.Collections.Generic; +using System.Xml.Linq; + +using Xamarin.Installer.AndroidSDK.Common; + +namespace Xamarin.Installer.AndroidSDK.GoogleV2.Parsing +{ + class TypeDetailsMavenParser : TypeDetailsParser + { + public PackageVendor Vendor { get; set; } + + public TypeDetailsMavenParser (ParserContext parserContext, XElement element, Dictionary namespaces = null) : base (parserContext, element, namespaces) + { } + + protected override Dictionary> GetKnownChildElements () + { + Dictionary> dict = EnsureDictionary (base.GetKnownChildElements ()); + dict["vendor"] = (XElement e) => Vendor = ParseChildElement_Vendor (e); + return dict; + } + + protected override AndroidComponentInfo CreateComponentInfo () + { + return new AndroidComponentInfoMaven (Type, Vendor); + } + } +} diff --git a/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/GoogleV2/Parsing/TypeDetailsParser.cs b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/GoogleV2/Parsing/TypeDetailsParser.cs new file mode 100644 index 00000000000..463f9a52da9 --- /dev/null +++ b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/GoogleV2/Parsing/TypeDetailsParser.cs @@ -0,0 +1,86 @@ +// +// Authors: +// Marek Habersack +// +// Copyright (c) 2017, Microsoft, Inc +// +// All rights reserved. +// +using System; +using System.Collections.Generic; +using System.Xml.Linq; + +using Xamarin.Installer.AndroidSDK.Common; + +namespace Xamarin.Installer.AndroidSDK.GoogleV2.Parsing +{ + abstract class TypeDetailsParser : ElementParser + { + protected string Type { get; private set; } + public AndroidComponentInfo Info { get; private set; } + + protected TypeDetailsParser (ParserContext parserContext, XElement element, Dictionary namespaces = null) : base (parserContext, element, namespaces) + { + IgnoreNamespaceAttributes = true; // for package.xml + } + + protected override Dictionary> GetKnownChildElements () + { + return null; + } + + protected override Dictionary> GetKnownAttributes () + { + return new Dictionary> (StringComparer.Ordinal) { + {GetAttributeName ("xsi", "type"), (XAttribute e) => Type = e.Value} + }; + } + + protected Dictionary> EnsureDictionary (Dictionary> dict) + { + if (dict == null) + return new Dictionary> (StringComparer.Ordinal); + return dict; + } + + protected override void Parsed () + { + base.Parsed (); + Info = Validate () ? CreateComponentInfo () : null; + } + + protected virtual bool Validate () + { + if (String.IsNullOrEmpty (Type)) // Not critical when missing, just warn + ErrorHandler.Warning (Context.CurrentManifestURL, Element, $"Type not specified on element {Element.Name}"); + + return true; + } + + protected bool ValidateApiLevel (string apiLevel) + { + if (String.IsNullOrEmpty (apiLevel)) { + ErrorHandler.Error (Context.CurrentManifestURL, Element, $"Missing 'api-level' child element in element {Element.Name}"); + return false; + } + + return true; + } + + protected PackageTag ParseChildElement_Tag (XElement element) + { + var ptp = new PackageTagParser (Context, element, Namespaces); + ptp.Parse (); + return ptp.Tag; + } + + protected PackageVendor ParseChildElement_Vendor (XElement element) + { + var vp = new PackageVendorParser (Context, element, Namespaces); + vp.Parse (); + return vp.Vendor; + } + + protected abstract AndroidComponentInfo CreateComponentInfo (); + } +} diff --git a/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/GoogleV2/Parsing/TypeDetailsParserFactory.cs b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/GoogleV2/Parsing/TypeDetailsParserFactory.cs new file mode 100644 index 00000000000..2f182e90cf2 --- /dev/null +++ b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/GoogleV2/Parsing/TypeDetailsParserFactory.cs @@ -0,0 +1,133 @@ +// +// Authors: +// Marek Habersack +// +// Copyright (c) 2017, Microsoft Corp. (http://microsoft.com) +// +// All rights reserved. +// +using System; +using System.Collections.Generic; +using System.Xml.Linq; + +namespace Xamarin.Installer.AndroidSDK.GoogleV2.Parsing +{ + class TypeDetailsParserFactory + { + delegate TypeDetailsParser TypeCreator (ParserContext parserContext, XElement element, Dictionary namespaces); + + static TypeCreator genericCreator = (ParserContext parserContext, XElement element, Dictionary namespaces) => new TypeDetailsGenericParser (parserContext, element, namespaces); + static TypeCreator platfomCreator = (ParserContext parserContext, XElement element, Dictionary namespaces) => new TypeDetailsPlatformParser (parserContext, element, namespaces); + static TypeCreator sourceCreator = (ParserContext parserContext, XElement element, Dictionary namespaces) => new TypeDetailsSourceParser (parserContext, element, namespaces); + static TypeCreator systemImageCreator = (ParserContext parserContext, XElement element, Dictionary namespaces) => new TypeDetailsSystemImageParser (parserContext, element, namespaces); + static TypeCreator addonCreator = (ParserContext parserContext, XElement element, Dictionary namespaces) => new TypeDetailsAddOnParser (parserContext, element, namespaces); + static TypeCreator extraCreator = (ParserContext parserContext, XElement element, Dictionary namespaces) => new TypeDetailsExtraParser (parserContext, element, namespaces); + static TypeCreator mavenCreator = (ParserContext parserContext, XElement element, Dictionary namespaces) => new TypeDetailsMavenParser (parserContext, element, namespaces); + + // The map looks a bit convoluted but it is necessary in order to support type details lookups from different sources. + // The package type is namespaced and uses different namespace names (but the same URIs) in the online repository and + // package.xml on disk. The namespace-less version is just a fallback. + static readonly Dictionary typeDetailsMap = new Dictionary { + // Generic + {"genericDetailsType", genericCreator}, + {"generic:genericDetailsType", genericCreator}, + {"http://schemas.android.com/repository/android/generic/01:genericDetailsType", genericCreator}, + + // Platform + {"platformDetailsType", platfomCreator}, + {"sdk:platformDetailsType", platfomCreator}, + {"http://schemas.android.com/sdk/android/repo/repository2/01:platformDetailsType", platfomCreator}, + + // Source + {"sourceDetailsType", sourceCreator}, + {"sdk:sourceDetailsType", sourceCreator}, + {"http://schemas.android.com/sdk/android/repo/repository2/01:sourceDetailsType", sourceCreator}, + + // System image + {"sysImgDetailsType", systemImageCreator}, + {"sys-img:sysImgDetailsType", systemImageCreator}, + {"http://schemas.android.com/sdk/android/repo/sys-img2/01:sysImgDetailsType", systemImageCreator}, + + // Addon + {"addonDetailsType", addonCreator}, + {"addon:addonDetailsType", addonCreator}, + {"http://schemas.android.com/sdk/android/repo/addon2/01:addonDetailsType", addonCreator}, + + // Extra + {"extraDetailsType", extraCreator}, + {"addon:extraDetailsType", extraCreator}, + {"http://schemas.android.com/sdk/android/repo/addon2/01:extraDetailsType", extraCreator}, + + // Maven + {"mavenType", mavenCreator}, + {"addon:mavenType", mavenCreator}, + {"http://schemas.android.com/sdk/android/repo/addon2/01:mavenType", mavenCreator}, + }; + + public static TypeDetailsParser CreateInstance (ParserContext parserContext, XElement element, Dictionary namespaces) + { + if (parserContext == null) + throw new ArgumentNullException (nameof (parserContext)); + + if (element == null) + throw new ArgumentNullException (nameof (element)); + + element.GetNamespaces (parserContext.CurrentManifestURL, ref namespaces); + XNamespace xsins = ElementParser.GetNamespace ("xsi", namespaces); + if (xsins == null) { + parserContext.ErrorHandler.Error (parserContext.CurrentManifestURL, element, $"Namespace 'xsi' undefined, unable to determine the TypeDetails mapping"); + return null; + } + + XAttribute xsiType = element.GetAttribute (xsins, "type"); + if (xsiType == null) { + parserContext.ErrorHandler.Error (parserContext.CurrentManifestURL, element, $"'xsi:type' attribute not found on element {element.Name} {element.GetLocation ()}"); + return null; + } + + TypeCreator creator = FindCreator (parserContext, xsiType.Value, element, namespaces); + if (creator == null) + return null; + + TypeDetailsParser ret = creator (parserContext, element, namespaces); + if (ret == null) { + parserContext.ErrorHandler.Error (parserContext.CurrentManifestURL, element, $"Failed to map TypeDetails element {element.Name} ('{xsiType.Value}') to a managed type {element.GetLocation ()}"); + return null; + } + + return ret; + } + + static TypeCreator FindCreator (ParserContext parserContext, string typeName, XElement element, Dictionary namespaces) + { + TypeCreator creator = LookupCreator (typeName); + if (creator != null) + return creator; + + string[] parts = typeName.Split (':'); + if (parts.Length != 2) + goto noCreator; + + XNamespace ns = ElementParser.GetNamespace (parts[0], namespaces); + creator = LookupCreator ($"{ns.NamespaceName}:{parts[1]}"); + if (creator != null) + return creator; + + creator = LookupCreator (parts[1]); + if (creator == null) + goto noCreator; + return creator; + + noCreator: + parserContext.ErrorHandler.Error (parserContext.CurrentManifestURL, element, $"Unknown type of the TypeDetails element {element.Name}: '{typeName}' {element.GetLocation ()}"); + return null; + } + + static TypeCreator LookupCreator (string typeName) + { + if (!typeDetailsMap.TryGetValue (typeName, out TypeCreator creator) || creator == null) + return null; + return creator; + } + } +} diff --git a/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/GoogleV2/Parsing/TypeDetailsPlatformParser.cs b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/GoogleV2/Parsing/TypeDetailsPlatformParser.cs new file mode 100644 index 00000000000..e649128cf6b --- /dev/null +++ b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/GoogleV2/Parsing/TypeDetailsPlatformParser.cs @@ -0,0 +1,71 @@ +// +// Authors: +// Marek Habersack +// +// Copyright (c) 2017, Microsoft, Inc +// +// All rights reserved. +// +using System; +using System.Collections.Generic; +using System.Xml.Linq; + +using Xamarin.Installer.AndroidSDK.Common; + +namespace Xamarin.Installer.AndroidSDK.GoogleV2.Parsing +{ + class TypeDetailsPlatformParser : TypeDetailsParser + { + protected string ApiLevel { get; private set; } + protected string CodeName { get; private set; } + protected string LayoutLibApi { get; private set; } + protected string BaseExtension { get; private set; } + protected string ExtensionLevel { get; private set; } + + public TypeDetailsPlatformParser (ParserContext parserContext, XElement element, Dictionary namespaces = null) : base (parserContext, element, namespaces) + {} + + protected override Dictionary> GetKnownChildElements () + { + Dictionary> dict = EnsureDictionary (base.GetKnownChildElements ()); + dict["api-level"] = (XElement e) => ApiLevel = e.GetElementValue (); + dict["codename"] = (XElement e) => CodeName = e.GetElementValue (); + dict["layoutlib"] = ParseChildElement_LayoutLib; + + // https://dl.google.com/android/repository/repository2-3.xml [304:4] + dict["base-extension"] = (XElement e) => BaseExtension = e.GetElementValue (); + + // https://dl.google.com/android/repository/repository2-3.xml [304:4] + dict["extension-level"] = (XElement e) => ExtensionLevel = e.GetElementValue (); + + return dict; + } + + protected override AndroidComponentInfo CreateComponentInfo () + { + // Android SDK component is considered "experimental" / "not stable" when codename is not empty & "base-extension" is not empty + // See android-Tiramisu for example + return new AndroidComponentInfoPlatform (Type, ApiLevel, CodeName, LayoutLibApi) { + Preview = !string.IsNullOrEmpty (CodeName) && !string.IsNullOrEmpty (BaseExtension) && !string.IsNullOrEmpty (ExtensionLevel), + }; + } + + protected override bool Validate () + { + bool ret = base.Validate () && ValidateApiLevel (ApiLevel); + + if (String.IsNullOrEmpty (LayoutLibApi)) { + ErrorHandler.Error (Context.CurrentManifestURL, Element, $"Missing attribute 'layoutlib' on element {Element.Name}"); + ret = false; + } + + return ret; + } + + void ParseChildElement_LayoutLib (XElement element) + { + XAttribute api = element.GetAttribute ("api"); + LayoutLibApi = api?.Value?.Trim (); + } + } +} diff --git a/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/GoogleV2/Parsing/TypeDetailsSourceParser.cs b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/GoogleV2/Parsing/TypeDetailsSourceParser.cs new file mode 100644 index 00000000000..a1d0f2d2774 --- /dev/null +++ b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/GoogleV2/Parsing/TypeDetailsSourceParser.cs @@ -0,0 +1,48 @@ +// +// Authors: +// Marek Habersack +// +// Copyright (c) 2017, Marek Habersack +// +// All rights reserved. +// +using System; +using System.Collections.Generic; +using System.Xml.Linq; + +using Xamarin.Installer.AndroidSDK.Common; + +namespace Xamarin.Installer.AndroidSDK.GoogleV2.Parsing +{ + class TypeDetailsSourceParser : TypeDetailsParser + { + protected string ApiLevel { get; private set; } + protected string CodeName { get; private set; } + + public TypeDetailsSourceParser (ParserContext parserContext, XElement element, Dictionary namespaces = null) : base (parserContext, element, namespaces) + {} + + protected override Dictionary> GetKnownChildElements () + { + Dictionary> dict = EnsureDictionary (base.GetKnownChildElements ()); + dict["api-level"] = (XElement e) => ApiLevel = e.GetElementValue (); + dict["codename"] = (XElement e) => CodeName = e.GetElementValue (); + + // https://dl.google.com/android/repository/repository2-3.xml [1200:4] + dict["base-extension"] = (XElement e) => {}; // ignore + dict["extension-level"] = (XElement e) => { }; // ignore + + return dict; + } + + protected override AndroidComponentInfo CreateComponentInfo () + { + return new AndroidComponentInfoSource (Type, ApiLevel, CodeName); + } + + protected override bool Validate () + { + return base.Validate () && ValidateApiLevel (ApiLevel); + } + } +} diff --git a/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/GoogleV2/Parsing/TypeDetailsSystemImageParser.cs b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/GoogleV2/Parsing/TypeDetailsSystemImageParser.cs new file mode 100644 index 00000000000..efad401481d --- /dev/null +++ b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/GoogleV2/Parsing/TypeDetailsSystemImageParser.cs @@ -0,0 +1,79 @@ +// +// Authors: +// Marek Habersack +// +// Copyright (c) 2017, Microsoft, Inc +// +// All rights reserved. +// +using System; +using System.Collections.Generic; +using System.Xml.Linq; + +using Xamarin.Installer.AndroidSDK.Common; + +namespace Xamarin.Installer.AndroidSDK.GoogleV2.Parsing +{ + class TypeDetailsSystemImageParser : TypeDetailsParser + { + protected AndroidSystemImageAbi? Abi { get; private set; } + protected string AbiName { get; private set; } + protected string ApiLevel { get; private set; } + protected string CodeName { get; private set; } + protected PackageTag Tag { get; private set; } + protected PackageVendor Vendor { get; private set; } + + public TypeDetailsSystemImageParser (ParserContext parserContext, XElement element, Dictionary namespaces = null) : base (parserContext, element, namespaces) + {} + + protected override Dictionary> GetKnownChildElements () + { + Dictionary> dict = EnsureDictionary (base.GetKnownChildElements ()); + dict["abi"] = ParseChildElement_ABI; + dict["api-level"] = (XElement e) => ApiLevel = e.GetElementValue (); + dict["codename"] = (XElement e) => CodeName = e.GetElementValue (); + dict["tag"] = (XElement e) => Tag = ParseChildElement_Tag (e); + dict["vendor"] = (XElement e) => Vendor = ParseChildElement_Vendor (e); + + // https://dl.google.com/android/repository/sys-img/android/sys-img2-3.xml [702:4] + dict["base-extension"] = (XElement e) => {}; // ignore + + return dict; + } + + protected override AndroidComponentInfo CreateComponentInfo () + { + return new AndroidComponentInfoSystemImage (Type, Abi.Value, AbiName, ApiLevel, CodeName, Tag, Vendor); + } + + void ParseChildElement_ABI (XElement element) + { + string abiName = element.Value?.Trim (); + + if (String.IsNullOrEmpty (abiName)) + return; + + AbiName = abiName; + AndroidSystemImageAbi abi = AndroidComponentInfoSystemImage.DefaultAbi; + try { + abi = AndroidUtilities.StringToAbi (abiName); + } catch (ArgumentOutOfRangeException) { + ErrorHandler.Warning (Context.CurrentManifestURL, Element, $"Unknown ABI: '{abiName}'"); + return; + } + Abi = abi; + } + + protected override bool Validate () + { + bool ret = base.Validate () && ValidateApiLevel (ApiLevel); + + if (!Abi.HasValue) { + ErrorHandler.Error (Context.CurrentManifestURL, Element, $"Missing 'abi' child element in element {Element.Name}"); + ret = false; + } + + return ret; + } + } +} diff --git a/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/GoogleV2/RemotePackage.cs b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/GoogleV2/RemotePackage.cs new file mode 100644 index 00000000000..0febaebb130 --- /dev/null +++ b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/GoogleV2/RemotePackage.cs @@ -0,0 +1,256 @@ +// +// Authors: +// Marek Habersack +// +// Copyright (c) 2017, Microsoft Corp. (http://microsoft.com) +// +// All rights reserved. +// +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Xml; +using System.Xml.Linq; + +using Xamarin.Installer.AndroidSDK.Common; +using Xamarin.Installer.Common; + +using IOPath = System.IO.Path; + +namespace Xamarin.Installer.AndroidSDK.GoogleV2 +{ + public class RemotePackage : BasePackage, IEquatable + { + IParserErrorHandler errorHandler; + + public override AndroidComponentType ComponentType => GetComponentType (Info); + public override Channel Channel => Repository.GetChannel (ChannelID); + public string ChannelID { get; set; } + + public bool IncludeAllArchives { get; set; } // For Xamarin manifest generator this is 'true' + + public RemotePackage (Repository repository, Uri manifestURL, IXmlLineInfo location, IParserErrorHandler errorHandler, IList archives) : base (repository, manifestURL, location, archives) + { + this.errorHandler = errorHandler ?? throw new ArgumentNullException (nameof (errorHandler)); + } + + // This is just a way to see in the logs whether the package is sane, apply some hacks to work around bugs in + // Google manifests etc. + public void Verify () + { + OnStatusChange (AndroidComponentStatus.VerificationStarted); + try { + DoVerify (); + } finally { + OnStatusChange (AndroidComponentStatus.VerificationEnded); + } + } + + void DoVerify () + { + string location = Location?.AsString () ?? String.Empty; + string path = String.IsNullOrEmpty (Path) ? "Unknown" : Path; + + if (OriginalArchives?.Any () == true) { + PopulatePlatformArchives (Info is AndroidComponentInfoSystemImage || IncludeAllArchives); + if (Info is AndroidComponentInfoSystemImage && !Archives.Any ()) { + // This is a workaround for a bug in the Google manifest which has the following archives entry: + // + // + // + // + // + // 238333358 + // 7cf2ad756e54a3acfd81064b63cb0cb9dff2798d + // armeabi-v7a-23_r06.zip + // + // windows + // + // + // + // which marks the archive as valid only on Windows. The issue is that system images are not + // specific to any host OS since they contain Android installation to be executed inside the + // emulator. That's why we ignore the host OS check for system images here. + Archives = new List { OriginalArchives.First () }.AsReadOnly (); + } + } + + if (Archives == null || Archives.Count == 0) + Logger.Warning ($"Package loaded from {ManifestURL} {location}, with path '{path}' has no valid archives (Host OS: {AndroidSDKContext.Instance.Platform})"); + } + + public static RemotePackage Clone (RemotePackage source) + { + if (source == null) + return null; + + // Archive instance aren't cloned because we want them to be shared amongst all the SDK instances and as the list is read-only, there's no + // need to clone it either. + var cloned = new RemotePackage (source.Repository, source.ManifestURL, source.Location, source.errorHandler, source.OriginalArchives) { + IsEssential = source.IsEssential, + IgnoreFailure = source.IgnoreFailure, + Present = source.Present, + NeedsUpdate = source.NeedsUpdate, + Path = source.Path, + FileSystemPath = source.FileSystemPath, + Obsolete = source.Obsolete, + Info = source.Info, + Revision = source.Revision, + InstalledRevision = source.InstalledRevision, + DisplayName = source.DisplayName, + Dependencies = source.Dependencies, + ChannelID = source.ChannelID, + LicenseID = source.LicenseID, + ForceInstallation = source.ForceInstallation, + }; + cloned.Verify (); + + return cloned; + } + + protected override PackageMetadata CreateMetadata (BasePackage component) + { + return new RemotePackageMetadata (component as RemotePackage); + } + + protected override bool MetadataChanged (PackageMetadata metadata, bool update = false, bool ignoreInstalledState = false) + { + if (!base.MetadataChanged (metadata, update, ignoreInstalledState)) + return false; + + return MetadataChanged (metadata as RemotePackageMetadata, update, ignoreInstalledState); + } + + public override void RefreshMetadata (IAndroidComponent component, bool ignoreInstalledState = true) + { + RefreshMetadata (component as RemotePackage); + } + + void RefreshMetadata (RemotePackage rp, bool ignoreInstalledState = true) + { + if (rp == null) + throw new ArgumentNullException (nameof (rp)); + + MetadataChanged (CreateMetadata (rp), true, ignoreInstalledState); + } + + bool MetadataChanged (RemotePackageMetadata metadata, bool update, bool ignoreInstalledState) + { + if (metadata == null) + throw new ArgumentNullException (nameof (metadata)); + + bool haveChanges = false; + if (String.Compare (metadata.ChannelID, ChannelID, StringComparison.Ordinal) != 0) { + if (update) { + ChannelID = metadata.ChannelID; + haveChanges = true; + } else + return true; + } + + if (String.Compare (metadata.LicenseID, LicenseID, StringComparison.Ordinal) != 0) { + if (update) { + LicenseID = metadata.LicenseID; + haveChanges = true; + } else + return true; + } + + return haveChanges; + } + + AndroidComponentType GetComponentType (AndroidComponentInfo info) + { + if (info is AndroidComponentInfoAddon) + return AndroidComponentType.Addon; + + if (info is AndroidComponentInfoExtra) + return AndroidComponentType.Extra; + + if (info is AndroidComponentInfoGeneric) + return AndroidComponentType.Generic; + + if (info is AndroidComponentInfoMaven) + return AndroidComponentType.Maven; + + if (info is AndroidComponentInfoPlatform) + return AndroidComponentType.Platform; + + if (info is AndroidComponentInfoSource) + return AndroidComponentType.Source; + + if (info is AndroidComponentInfoSystemImage) + return AndroidComponentType.SystemImage; + + errorHandler.Debug ($"Unknown component info type '{info?.GetType ()}'"); + return AndroidComponentType.Unknown; + } + + public override bool MatchesTo (IAndroidComponent other) + { + if (!(other is RemotePackage)) + return false; + + return base.MatchesTo (other); + } + + + public override bool BasicMetadataEquals (BasePackage other) + { + if (!base.BasicMetadataEquals (other)) + return false; + + return BasicMetadataEquals (other as RemotePackage); + } + + bool BasicMetadataEquals (RemotePackage other) + { + if (String.Compare (ChannelID, other.ChannelID, StringComparison.Ordinal) != 0) + return false; + + return true; + } + + public bool Equals (RemotePackage other) + { + if (other == null) + return false; + + return base.Equals (other as BasePackage); + } + + public override bool Equals (object obj) + { + return Equals (obj as RemotePackage); + } + + public override int GetHashCode () + { + int hashCode = base.GetHashCode (); + + return hashCode.XorWith (ChannelID?.GetHashCode ()); + } + + protected override void LogError (string message) + { + errorHandler.Error (message); + } + + protected override void LogWarning (string message) + { + errorHandler.Warning (message); + } + + protected override void LogInfo (string message) + { + errorHandler.Info (message); + } + + protected override void LogDebug (string message) + { + errorHandler.Debug (message); + } + } +} diff --git a/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/GoogleV2/RemotePackageMetadata.cs b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/GoogleV2/RemotePackageMetadata.cs new file mode 100644 index 00000000000..39c9cf5a45a --- /dev/null +++ b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/GoogleV2/RemotePackageMetadata.cs @@ -0,0 +1,27 @@ +// +// Authors: +// Marek Habersack +// +// Copyright (c) 2018, Microsoft Corp. (https://microsoft.com) +// +// All rights reserved. +// +using System; +using System.Collections.Generic; + +using Xamarin.Installer.AndroidSDK.Common; + +namespace Xamarin.Installer.AndroidSDK.GoogleV2 +{ + class RemotePackageMetadata : PackageMetadata + { + public string ChannelID { get; set; } + + public RemotePackageMetadata (RemotePackage rp) : base (rp) + { + if (rp == null) + throw new ArgumentNullException (nameof (rp)); + ChannelID = rp.ChannelID; + } + } +} diff --git a/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/Helpers.cs b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/Helpers.cs new file mode 100644 index 00000000000..610c838714f --- /dev/null +++ b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/Helpers.cs @@ -0,0 +1,106 @@ +// +// Authors: +// Marek Habersack +// +// Copyright (c) 2017, Marek Habersack +// +// All rights reserved. +// +using System; +using System.Collections.Generic; +using System.Text; +using System.Xml; +using System.Xml.Linq; + +using Xamarin.Installer.Common; + +namespace Xamarin.Installer.AndroidSDK +{ + public class Helpers + { + static readonly string utf8BOM = Encoding.UTF8.GetString (Encoding.UTF8.GetPreamble ()); + + public static void AddDictionaryItem (T item, string key, ref Dictionary dict, Action duplicateHandler = null) + { + if (dict == null) + dict = new Dictionary (StringComparer.Ordinal); + + if (dict.ContainsKey (key)) { + if (duplicateHandler != null) + duplicateHandler (key); + dict[key] = item; + } else + dict.Add (key, item); + } + + public static string GetComponentArchiveSelector (string os, string arch, uint osbits) + { + os = os?.Trim (); + if (String.IsNullOrEmpty (os)) + os = "any"; + arch = arch?.Trim (); + if (String.IsNullOrEmpty (arch)) + arch = "any"; + + return os + arch + osbits; + } + + public static XmlDocument LoadXML (string xml, string documentName, bool initializeNamespace, out XmlNamespaceManager nsmgr) + { + // XmlDocument breaks when the XML starts with the BOM. Funny thing is, the XML + // was previously written by XmlWriter... Because why not...??? + if (xml.StartsWith (utf8BOM, StringComparison.Ordinal)) + xml = xml.Remove (0, utf8BOM.Length); + var doc = new XmlDocument (); + doc.LoadXml (xml); + + nsmgr = new XmlNamespaceManager (doc.NameTable); + if (initializeNamespace) { + XmlElement root = doc.DocumentElement; + if (root.Attributes != null) { + + foreach (XmlAttribute attr in root.Attributes) { + if (attr == null || String.IsNullOrEmpty (attr.Name)) + continue; + + string name = GetXMLNamespaceName (attr.Name); + if (String.IsNullOrEmpty (name)) + continue; + + Logger.Debug ("Adding {0} XML namespace '{1}' == '{2}'", documentName, name, attr.Value); + nsmgr.AddNamespace (name, attr.Value); + } + } + } + + return doc; + } + + public static Uri GetBaseURL (Uri fullUrl) + { + if (fullUrl == null) + throw new ArgumentNullException (nameof (fullUrl)); + + string url = fullUrl.ToString (); + int fileStart = url.LastIndexOf ('/'); + if (fileStart >= 0) + return new Uri (url.Substring (0, fileStart + 1)); + else + return fullUrl; + } + + static string GetXMLNamespaceName (string xmlns) + { + xmlns = xmlns.SafeTrim (); + if (String.IsNullOrEmpty (xmlns)) + return String.Empty; + + int colon; + if ((colon = xmlns.IndexOf (':')) < 0) + return xmlns; + + return xmlns.Substring (colon + 1); + } + } + +} diff --git a/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/IProgressMonitor.cs b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/IProgressMonitor.cs new file mode 100644 index 00000000000..d8d9fcdc9c3 --- /dev/null +++ b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/IProgressMonitor.cs @@ -0,0 +1,112 @@ +// +// IProgressMonitor.cs +// +// Author: +// Vsevolod Kukol +// +// Copyright (c) 2017, Microsoft, Inc +// + +using System; +using System.Threading; + +namespace Xamarin.Installer.AndroidSDK.Manager +{ + /// + /// The Progress monitor factory interface is used by to + /// create installation progress monitors on demand. + /// + public interface IProgressMonitorFactory + { + /// + /// Creates a progress monitor to observe the SDK Manager installation progress. + /// + /// The progress monitor. + /// The SDK Manager will create a new progress monitor for each installation process. + IProgressMonitor CreateProgressMonitor (); + } + + /// + /// The cancellable progress monitor can cancel a running operation. + /// + /// + /// Implement this interface to be able tonotify the SDK Manager that the running operation should + /// be canceled. + /// + public interface ICancellableProgressMonitor : IProgressMonitor, IDisposable + { + /// + /// Gets the cancellation token. + /// + /// The cancellation token. + CancellationToken CancellationToken { get; } + + /// + /// Gets a value indicating whether this + /// should be disposed after the work is done. + /// + /// true to dispose when finished; otherwise, false. + bool DisposeOnFinish { get; } + } + + /// + /// A progress monitor with total progress will be notified by the SDK Manager with overall progress of installation steps + /// + public interface IProgressMonitorWithTotalProgress : ICancellableProgressMonitor + { + /// + /// Reports the total progress of all the download and installation steps + /// + /// Overall progress percentage from 0.0 to 1.0 + void ReportTotalProgress (double loadPercentage, + int mainComponentsCount, int mainComponentIndex, + int subComponentsCount, int subComponentIndex); + } + + /// + /// A progress monitor will be notified by the SDK Manager of all installation steps. + /// + /// + /// Implement this interface to observe all installation steps and progress updates. + /// + public interface IProgressMonitor + { + /// + /// Begin a new step. + /// + /// The translated message associated with the step to be presented to the user. + void BeginStep (string step); + + /// + /// Begin a new step with progress notification. + /// + /// The translated message associated with the step to be presented to the user. + /// The total length of the progress to be done in this step. + void BeginStep (string step, long totalWork); + + /// + /// End the last step started with BeginStep () + /// + void EndStep (AndroidSDKComponentInstallationResult result = null); + + /// + /// Reports the progress of the currently running step. + /// + /// The current step progress. + void ReportProgress (long work); + + /// + /// Show a message to the user. + /// + /// The message to be shown. + /// Messages provide additional status information for the currently running step. + void ReportMessage (string message); + + /// + /// Notify the user when an error occured. + /// + /// The error message. + /// The exception + void ReportError (string message, Exception ex); + } +} diff --git a/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/JavaDependencyInstaller.cs b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/JavaDependencyInstaller.cs new file mode 100644 index 00000000000..cbe0eaa7434 --- /dev/null +++ b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/JavaDependencyInstaller.cs @@ -0,0 +1,117 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; + +using Kajabity.Tools.Java; + +using Xamarin.Installer.Common; +using Xamarin.Installer.AndroidSDK.Common; +using Xamarin.Installer.AndroidSDK.Manager; +using Xamarin.Installer.AndroidSDK.Xamarin; + +namespace Xamarin.Installer.AndroidSDK +{ + public class JavaDependencyInstaller + { + public Repository Repository { get; private set; } + + public string JdkPath { get; set; } + + public JavaDependencyInstaller (IHelpers helpers = null, Uri manifestURL = null, bool useManifestCaching = false) + { + CommonUtilities.Helpers = helpers ?? new Helper (); + Repository = new XamarinRepository (manifestURL, useManifestCaching); + } + + public void Discover () + { + if (!Repository.Parsed) { + Repository.Parse (); + } + } + + public IEnumerable GetJdkArchivesWithVersion (AndroidRevision version) + { + return Repository.JdkComponents.Where (j => j.Revision == version).SelectMany (j => j.Archives); + } + + public JdkArchive GetFirstValidJdkArchiveWithVersion (AndroidRevision version) + { + var jdks = GetJdkArchivesWithVersion (version); + return jdks.FirstOrDefault (j => j.IsValidForSystem ()); + } + + public bool GetJdkRevision (out AndroidRevision version) + { + if (string.IsNullOrEmpty (JdkPath)) { + version = new AndroidRevision (0); + return false; + } + JavaProperties props = AndroidUtilities.ReadAndroidProperties (Path.Combine (JdkPath, "release")); + bool foundUnparsedVersion = props.GetProperty ("JAVA_VERSION", out string javaVersion); + javaVersion = javaVersion?.Replace ("\"", string.Empty).Replace ("'", string.Empty); + version = new AndroidRevision (javaVersion); + return foundUnparsedVersion && version != new AndroidRevision (0); + } + + public bool IsJdkPathValid () + { + if (string.IsNullOrEmpty (JdkPath)) { + return false; + } + + // If the requested path is in a known VS installation Program Files path on Windows, request a different path + if (Platform.IsWindows) { + if (JdkPath.StartsWith (Environment.GetFolderPath (Environment.SpecialFolder.ProgramFiles), StringComparison.OrdinalIgnoreCase) + || JdkPath.StartsWith (Environment.GetFolderPath (Environment.SpecialFolder.ProgramFilesX86), StringComparison.OrdinalIgnoreCase)) { + return false; + } + } + + // If the requested path is not writable, request a different path + try { + Directory.CreateDirectory (JdkPath); + using (File.Create (Path.Combine (JdkPath, "tmp"), 1, FileOptions.DeleteOnClose)); + } catch (Exception) { + return false; + } + + return true; + } + + public void InstallJdk (JdkArchive archive) + { + var unzipDestination = Path.Combine (Path.GetTempPath (), Path.GetRandomFileName ()); + Directory.CreateDirectory (unzipDestination); + + if (Platform.IsWindows) { + CommonUtilities.Helpers.Unzip (unzipDestination, archive.DownloadedFilePath, null, null); + } else { + var extractResult = CommonUtilities.RunCommand ( + workingDirectory: unzipDestination, + command: "/usr/bin/tar", + exitCode: out int exitCode, + arguments: new [] { "-xzf", archive.DownloadedFilePath }); + if (exitCode != 0) + throw new Exception ($"Failed to extract temp Java SDK archive {archive.DownloadedFilePath}:\n{extractResult}"); + } + + var jdkSubDir = Directory.GetDirectories (unzipDestination).FirstOrDefault (); + if (Platform.IsMac) { + jdkSubDir = Path.Combine (jdkSubDir, "Contents", "Home"); + } + RemoveJdk (); + CommonUtilities.MoveDirectory (jdkSubDir, JdkPath); + if (Directory.Exists (unzipDestination)) + Directory.Delete (unzipDestination, true); + } + + public void RemoveJdk () + { + if (Directory.Exists (JdkPath)) + Directory.Delete (JdkPath, true); + } + + } +} diff --git a/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/Kajabity.Tools.Java/JavaProperties.cs b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/Kajabity.Tools.Java/JavaProperties.cs new file mode 100644 index 00000000000..49c1579a083 --- /dev/null +++ b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/Kajabity.Tools.Java/JavaProperties.cs @@ -0,0 +1,192 @@ +/* + * Copyright 2009-15 Williams Technologies Limited. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Kajbity is a trademark of Williams Technologies Limited. + * + * http://www.kajabity.com + */ + +using System; +using System.Collections; +using System.IO; +using System.Text; + + +namespace Kajabity.Tools.Java +{ + /// + /// Hold Java style properties as key-value pairs and allow them to be loaded from or + /// saved to a ".properties" file. The file is stored with character set ISO-8859-1 which extends US-ASCII + /// (the characters 0-127 are the same) and forms the first part of the Unicode character set. Within the + /// application are Unicode - but all values outside the basic US-ASCII set are escaped. + /// + public class JavaProperties: Hashtable + { + /// + /// Gets a reference to the ISO-8859-1 encoding (code page 28592). This is the Java standard for .properties files. + /// + internal static Encoding DefaultEncoding { get { return Encoding.GetEncoding( 28592 ); } } + + /// + /// A reference to an optional set of default properties - these values are returned + /// if the value has not been loaded from a ".properties" file or set programatically. + /// + protected Hashtable defaults; + + /// + /// An empty constructor that doesn't set the defaults. + /// + public JavaProperties() + { + } + + /// + /// Use this constructor to provide a set of default values. The default values are kept separate + /// to the ones in this instant. + /// + /// A Hashtable that holds a set of defafult key value pairs to + /// return when the requested key has not been set. + public JavaProperties( Hashtable defaults ) + { + this.defaults = defaults; + } + + /// + /// Load Java Properties from a stream expecting the format as described in . + /// + /// An input stream to read properties from. + /// If the stream source is invalid. + public void Load( Stream streamIn ) + { + JavaPropertyReader reader = new JavaPropertyReader( this ); + reader.Parse( streamIn ); + } + + /// + /// Load Java Properties from a stream with the specified encoding and + /// expecting the format as described in . + /// + /// An input stream to read properties from. + /// The stream's encoding. + public void Load( Stream streamIn, Encoding encoding ) + { + JavaPropertyReader reader = new JavaPropertyReader( this ); + reader.Parse( streamIn, encoding ); + } + + /// + /// Store the contents of this collection of properties to the stream in the format + /// used for Java ".properties" files using an instance of . + /// The keys and values will be minimally escaped to ensure special characters are read back + /// in properly. Keys are not sorted. The file will begin with a comment identifying the + /// date - and an additional comment may be included. + /// + /// An output stream to write the properties to. + /// Optional additional comment to include at the head of the output. + public void Store( Stream streamOut, string comments ) + { + JavaPropertyWriter writer = new JavaPropertyWriter( this ); + writer.Write( streamOut, comments ); + } + + /// + /// Get the value for the specified key value. If the key is not found, then return the + /// default value - and if still not found, return null. + /// + /// The key whose value should be returned. + /// The value corresponding to the key - or null if not found. + public string GetProperty( string key ) + { + Object objectValue = this[ key ]; + if( objectValue != null ) + { + return AsString( objectValue ); + } + else if( defaults != null ) + { + return AsString( defaults[ key ] ); + } + + return null; + } + + /// + /// Get the value for the specified key value. If the key is not found, then return the + /// default value - and if still not found, return defaultValue. + /// + /// The key whose value should be returned. + /// The default value if the key is not found. + /// The value corresponding to the key - or null if not found. + public string GetProperty( string key, string defaultValue ) + { + string val = GetProperty( key ); + return (val == null) ? defaultValue : val; + } + + /// + /// Set the value for a property key. The old value is returned - if any. + /// + /// The key whose value is to be set. + /// The new value off the key. + /// The original value of the key - as a string. + public string SetProperty( string key, string newValue ) + { + string oldValue = AsString( this[ key ] ); + this[ key ] = newValue; + return oldValue; + } + + /// + /// Returns an enumerator of all the properties available in this instance - including the + /// defaults. + /// + /// An enumarator for all of the keys including defaults. + public IEnumerator PropertyNames() + { + Hashtable combined; + if( defaults != null ) + { + combined = new Hashtable( defaults ); + + for( IEnumerator e = this.Keys.GetEnumerator(); e.MoveNext(); ) + { + string key = AsString( e.Current ); + combined.Add( key, this[ key ] ); + } + } + else + { + combined = new Hashtable( this ); + } + + return combined.Keys.GetEnumerator(); + } + + /// + /// A utility method to safely convert an Object to a string. + /// + /// An Object or null to be returned as a string. + /// string value of the object - or null. + private string AsString( Object o ) + { + if( o == null ) + { + return null; + } + + return o.ToString(); + } + } +} diff --git a/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/Kajabity.Tools.Java/JavaPropertyReader.cs b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/Kajabity.Tools.Java/JavaPropertyReader.cs new file mode 100644 index 00000000000..b0c55ed9153 --- /dev/null +++ b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/Kajabity.Tools.Java/JavaPropertyReader.cs @@ -0,0 +1,496 @@ +/* + * Copyright 2009-15 Williams Technologies Limited. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Kajbity is a trademark of Williams Technologies Limited. + * + * http://www.kajabity.com + */ + +using System; +using System.Collections; +using System.IO; +using System.Text; + +using System.Diagnostics; +using System.Globalization; + + +namespace Kajabity.Tools.Java +{ + /// + /// This class reads Java style properties from an input stream. + /// + public class JavaPropertyReader + { + private const int MATCH_end_of_input = 1; + private const int MATCH_terminator = 2; + private const int MATCH_whitespace = 3; + private const int MATCH_any = 4; + + private const int ACTION_add_to_key = 1; + private const int ACTION_add_to_value = 2; + private const int ACTION_store_property = 3; + private const int ACTION_escape = 4; + private const int ACTION_ignore = 5; + + private const int STATE_start = 0; + private const int STATE_comment = 1; + private const int STATE_key = 2; + private const int STATE_key_escape = 3; + private const int STATE_key_ws = 4; + private const int STATE_before_separator = 5; + private const int STATE_after_separator = 6; + private const int STATE_value = 7; + private const int STATE_value_escape = 8; + private const int STATE_value_ws = 9; + private const int STATE_finish = 10; + + private static string [] stateNames = new string[] + { "STATE_start", "STATE_comment", "STATE_key", "STATE_key_escape", "STATE_key_ws", + "STATE_before_separator", "STATE_after_separator", "STATE_value", "STATE_value_escape", + "STATE_value_ws", "STATE_finish" }; + + private static readonly int [][] states = new int[][] { + new int[]{//STATE_start + MATCH_end_of_input, STATE_finish, ACTION_ignore, + MATCH_terminator, STATE_start, ACTION_ignore, + '#', STATE_comment, ACTION_ignore, + '!', STATE_comment, ACTION_ignore, + MATCH_whitespace, STATE_start, ACTION_ignore, + '\\', STATE_key_escape, ACTION_escape, + ':', STATE_after_separator, ACTION_ignore, + '=', STATE_after_separator, ACTION_ignore, + MATCH_any, STATE_key, ACTION_add_to_key, + }, + new int[]{//STATE_comment + MATCH_end_of_input, STATE_finish, ACTION_ignore, + MATCH_terminator, STATE_start, ACTION_ignore, + MATCH_any, STATE_comment, ACTION_ignore, + }, + new int[]{//STATE_key + MATCH_end_of_input, STATE_finish, ACTION_store_property, + MATCH_terminator, STATE_start, ACTION_store_property, + MATCH_whitespace, STATE_before_separator, ACTION_ignore, + '\\', STATE_key_escape, ACTION_escape, + ':', STATE_after_separator, ACTION_ignore, + '=', STATE_after_separator, ACTION_ignore, + MATCH_any, STATE_key, ACTION_add_to_key, + }, + new int[]{//STATE_key_escape + MATCH_terminator, STATE_key_ws, ACTION_ignore, + MATCH_any, STATE_key, ACTION_add_to_key, + }, + new int[]{//STATE_key_ws + MATCH_end_of_input, STATE_finish, ACTION_store_property, + MATCH_terminator, STATE_start, ACTION_store_property, + MATCH_whitespace, STATE_key_ws, ACTION_ignore, + '\\', STATE_key_escape, ACTION_escape, + ':', STATE_after_separator, ACTION_ignore, + '=', STATE_after_separator, ACTION_ignore, + MATCH_any, STATE_key, ACTION_add_to_key, + }, + new int[]{//STATE_before_separator + MATCH_end_of_input, STATE_finish, ACTION_store_property, + MATCH_terminator, STATE_start, ACTION_store_property, + MATCH_whitespace, STATE_before_separator, ACTION_ignore, + '\\', STATE_value_escape, ACTION_escape, + ':', STATE_after_separator, ACTION_ignore, + '=', STATE_after_separator, ACTION_ignore, + MATCH_any, STATE_value, ACTION_add_to_value, + }, + new int[]{//STATE_after_separator + MATCH_end_of_input, STATE_finish, ACTION_store_property, + MATCH_terminator, STATE_start, ACTION_store_property, + MATCH_whitespace, STATE_after_separator, ACTION_ignore, + '\\', STATE_value_escape, ACTION_escape, + MATCH_any, STATE_value, ACTION_add_to_value, + }, + new int[]{//STATE_value + MATCH_end_of_input, STATE_finish, ACTION_store_property, + MATCH_terminator, STATE_start, ACTION_store_property, + '\\', STATE_value_escape, ACTION_escape, + MATCH_any, STATE_value, ACTION_add_to_value, + }, + new int[]{//STATE_value_escape + MATCH_terminator, STATE_value_ws, ACTION_ignore, + MATCH_any, STATE_value, ACTION_add_to_value + }, + new int[]{//STATE_value_ws + MATCH_end_of_input, STATE_finish, ACTION_store_property, + MATCH_terminator, STATE_start, ACTION_store_property, + MATCH_whitespace, STATE_value_ws, ACTION_ignore, + '\\', STATE_value_escape, ACTION_escape, + MATCH_any, STATE_value, ACTION_add_to_value, + } + }; + + private Hashtable hashtable; + + private const int bufferSize = 1000; + + private bool escaped = false; + private StringBuilder keyBuilder = new StringBuilder(); + private StringBuilder valueBuilder = new StringBuilder(); + + /// + /// Construct a reader passing a reference to a Hashtable (or JavaProperties) instance + /// where the keys are to be stored. + /// + /// A reference to a hashtable where the key-value pairs can be stored. + public JavaPropertyReader( Hashtable hashtable ) + { + this.hashtable = hashtable; + } + + /// + /// Load key value pairs (properties) from an input Stream expected to have ISO-8859-1 encoding (code page 28592). + /// The input stream (usually reading from a ".properties" file) consists of a series of lines (terminated + /// by \r, \n or \r\n) each a key value pair, a comment or a blank line. + /// + /// Leading whitespace (spaces, tabs, formfeeds) are ignored at the start of any line - and a line that is empty or + /// contains only whitespace is blank and ignored. + /// + /// A line with the first non-whitespace character is a '#' or '!' is a comment line and the rest of the line is + /// ignored. + /// + /// If the first non-whitespace character is not '#' or '!' then it is the start of a key. A key is all the + /// characters up to the first whitespace or a key/value separator - '=' or ':'. + /// + /// The separator is optional. Any whitespace after the key or after the separator (if present) is ignored. + /// + /// The first non-whitespace character after the separator (or after the key if no separator) begins the value. + /// The value may include whitespace, separators, or comment characters. + /// + /// Any unicode character may be included in either key or value by using escapes preceded by the escape + /// character '\'. + /// + /// The following special cases are defined: + /// + /// '\t' - horizontal tab. + /// '\f' - form feed. + /// '\r' - return + /// '\n' - new line + /// '\\' - add escape character. + /// + /// '\ ' - add space in a key or at the start of a value. + /// '\!', '\#' - add comment markers at the start of a key. + /// '\=', '\:' - add a separator in a key. + /// + /// + /// Any unicode character using the following escape: + /// + /// '\uXXXX' - where XXXX represents the unicode character code as 4 hexadecimal digits. + /// + /// + /// Finally, longer lines can be broken by putting an escape at the very end of the line. Any leading space + /// (unless escaped) is skipped at the beginning of the following line. + /// + /// Examples + /// + /// a-key = a-value + /// a-key : a-value + /// a-key=a-value + /// a-key a-value + /// + /// + /// + /// ! comment... + /// # another comment... + /// + /// + /// The above are two examples of comments. + /// + /// Honk\ Kong = Near China + /// + /// + /// The above shows how to embed a space in a key - key is "Hong Kong", value is "Near China". + /// + /// a-longer-key-example = a really long value that is \ + /// split over two lines. + /// + /// + /// An example of a long line split into two. + /// + /// The input stream that the properties are read from. + public void Parse( Stream stream ) + { + Parse( stream, null ); + } + + /// + /// Load key value pairs (properties) from an input Stream expected to have ISO-8859-1 encoding (code page 28592). + /// The input stream (usually reading from a ".properties" file) consists of a series of lines (terminated + /// by \r, \n or \r\n) each a key value pair, a comment or a blank line. + /// + /// Leading whitespace (spaces, tabs, formfeeds) are ignored at the start of any line - and a line that is empty or + /// contains only whitespace is blank and ignored. + /// + /// A line with the first non-whitespace character is a '#' or '!' is a comment line and the rest of the line is + /// ignored. + /// + /// If the first non-whitespace character is not '#' or '!' then it is the start of a key. A key is all the + /// characters up to the first whitespace or a key/value separator - '=' or ':'. + /// + /// The separator is optional. Any whitespace after the key or after the separator (if present) is ignored. + /// + /// The first non-whitespace character after the separator (or after the key if no separator) begins the value. + /// The value may include whitespace, separators, or comment characters. + /// + /// Any unicode character may be included in either key or value by using escapes preceded by the escape + /// character '\'. + /// + /// The following special cases are defined: + /// + /// '\t' - horizontal tab. + /// '\f' - form feed. + /// '\r' - return + /// '\n' - new line + /// '\\' - add escape character. + /// + /// '\ ' - add space in a key or at the start of a value. + /// '\!', '\#' - add comment markers at the start of a key. + /// '\=', '\:' - add a separator in a key. + /// + /// + /// Any unicode character using the following escape: + /// + /// '\uXXXX' - where XXXX represents the unicode character code as 4 hexadecimal digits. + /// + /// + /// Finally, longer lines can be broken by putting an escape at the very end of the line. Any leading space + /// (unless escaped) is skipped at the beginning of the following line. + /// + /// Examples + /// + /// a-key = a-value + /// a-key : a-value + /// a-key=a-value + /// a-key a-value + /// + /// + /// + /// ! comment... + /// # another comment... + /// + /// + /// The above are two examples of comments. + /// + /// Honk\ Kong = Near China + /// + /// + /// The above shows how to embed a space in a key - key is "Hong Kong", value is "Near China". + /// + /// a-longer-key-example = a really long value that is \ + /// split over two lines. + /// + /// + /// An example of a long line split into two. + /// + /// The input stream that the properties are read from. + /// The encoding that is used to read the properies file stream. + public void Parse( Stream stream, Encoding encoding ) + { + var bufferedStream = new BufferedStream( stream, bufferSize ); + // the default encoding ISO-8859-1 (codepabe 28592) will be used if we do not pass explicitly different encoding + var parserEncoding = encoding ?? JavaProperties.DefaultEncoding; + reader = new BinaryReader( bufferedStream, parserEncoding ); + + int state = STATE_start; + do + { + int ch = nextChar(); + + bool matched = false; + + for( int s = 0; s < states[ state ].Length; s += 3 ) + { + if( matches( states[ state ][ s ], ch ) ) + { + //Debug.WriteLine( stateNames[ state ] + ", " + (s/3) + ", " + ch + (ch>20?" (" + (char) ch + ")" : "") ); + matched = true; + doAction( states[ state ][ s + 2 ], ch ); + + state = states[ state ][ s + 1 ]; + break; + } + } + + if( !matched ) + { + throw new ParseException( "Unexpected character at " + 1 + ": <<<" + ch + ">>>" ); + } + } while( state != STATE_finish ); + } + + private bool matches( int match, int ch ) + { + switch( match ) + { + case MATCH_end_of_input: + return ch == -1; + + case MATCH_terminator: + if( ch == '\r' ) + { + if( peekChar() == '\n') + { + saved = false; + } + return true; + } + else if( ch == '\n' ) + { + return true; + } + return false; + + case MATCH_whitespace: + return ch == ' ' || ch == '\t' || ch == '\f'; + + case MATCH_any: + return true; + + default: + return ch == match; + } + } + + private void doAction( int action, int ch ) + { + switch( action ) + { + case ACTION_add_to_key: + keyBuilder.Append( escapedChar( ch ) ); + escaped = false; + break; + + case ACTION_add_to_value: + valueBuilder.Append( escapedChar( ch ) ); + escaped = false; + break; + + case ACTION_store_property: + //Debug.WriteLine( keyBuilder.ToString() + "=" + valueBuilder.ToString() ); + // Corrected to avoid duplicate entry errors - thanks to David Tanner. + hashtable[ keyBuilder.ToString() ] = valueBuilder.ToString(); + keyBuilder.Length = 0; + valueBuilder.Length = 0; + escaped = false; + break; + + case ACTION_escape: + escaped = true; + break; + + //case ACTION_ignore: + default: + escaped = false; + break; + } + } + + private char escapedChar( int ch ) + { + if( escaped ) + { + switch( ch ) + { + case 't': + return '\t'; + case 'r': + return '\r'; + case 'n': + return '\n'; + case 'f': + return '\f'; + case 'u': + int uch = 0; + for( int i = 0; i < 4; i++ ) + { + ch = nextChar(); + if( ch >= '0' && ch <='9' ) + { + uch = (uch << 4) + ch - '0'; + } + else if( ch >= 'a' && ch <='z' ) + { + uch = (uch << 4) + ch - 'a' + 10; + } + else if( ch >= 'A' && ch <='Z' ) + { + uch = (uch << 4) + ch - 'A' + 10; + } + else + { + throw new ParseException( "Invalid Unicode character." ); + } + } + return (char) uch; + } + } + + return (char) ch; + } + + // we now use a BinaryReader, which supports encodings + private BinaryReader reader = null; + private int savedChar; + private bool saved = false; + + private int nextChar() + { + if( saved ) + { + saved = false; + return savedChar; + } + + return ReadCharSafe(); + } + + private int peekChar() + { + if( saved ) + { + return savedChar; + } + + saved = true; + return savedChar = ReadCharSafe(); + } + + /// + /// A method to substitute calls to stream.ReadByte(). + /// The now uses a to read properties. + /// Unlike a plain stream, the will not return -1 when the stream end is reached, + /// instead an is to be thrown. + /// + /// In this method we perform a check if the stream is already processed to the end, and return -1. + /// + /// + /// + private int ReadCharSafe() + { + if (this.reader.BaseStream.Position == this.reader.BaseStream.Length) + { + // We have reached the end of the stream. The reder will throw exception if we call Read any further. + // We just return -1 now; + return -1; + } + // reader.ReadChar() will take into account the encoding. + return this.reader.ReadChar(); + } + } +} diff --git a/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/Kajabity.Tools.Java/JavaPropertyWriter.cs b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/Kajabity.Tools.Java/JavaPropertyWriter.cs new file mode 100644 index 00000000000..a6dffc00dbe --- /dev/null +++ b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/Kajabity.Tools.Java/JavaPropertyWriter.cs @@ -0,0 +1,208 @@ +/* + * Copyright 2009-15 Williams Technologies Limited. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Kajbity is a trademark of Williams Technologies Limited. + * + * http://www.kajabity.com + */ + +using System; +using System.IO; +using System.Collections; +using System.Text; + +namespace Kajabity.Tools.Java +{ + /// + /// Use this class for writing a set of key value pair strings to an + /// output stream using the Java properties format. + /// + public class JavaPropertyWriter + { + private static char[] HEX = new char[] { '0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F' }; + + private Hashtable hashtable; + + /// + /// Construct an instance of this class. + /// + /// The Hashtable (or JavaProperties) instance + /// whose values are to be written. + public JavaPropertyWriter( Hashtable hashtable ) + { + this.hashtable = hashtable; + } + + /// + /// Write the properties to the output stream. + /// + /// The output stream where the properties are written. + /// Optional comments that are placed at the beginning of the output. + public void Write( Stream stream, string comments ) + { + // Create a writer to output to an ISO-8859-1 encoding (code page 28592). + StreamWriter writer = new StreamWriter( stream, System.Text.Encoding.GetEncoding( 28592 ) ); + + //TODO: Confirm correct codepage: + // 28592 iso-8859-2 Central European (ISO) + // 28591 iso-8859-1 Western European (ISO) + // from http://msdn.microsoft.com/en-us/library/system.text.encodinginfo.getencoding.aspx + + if( comments != null) + { + writer.WriteLine( "# " + comments ); + } + + writer.WriteLine( "# " + DateTime.Now.ToString() ); + + for( IEnumerator e = hashtable.Keys.GetEnumerator(); e.MoveNext(); ) + { + string key = e.Current.ToString(); + string val = hashtable[ key ].ToString(); + + writer.WriteLine( escapeKey( key ) + "=" + escapeValue( val ) ); + } + + writer.Flush(); + } + + /// + /// Escape the string as a Key with character set ISO-8859-1 - + /// the characters 0-127 are US-ASCII and we will escape any others. The passed string is Unicode which extends + /// ISO-8859-1 - so all is well. + /// + /// + /// + private string escapeKey( string s ) + { + StringBuilder buf = new StringBuilder(); + bool first = true; + + foreach( char c in s ) + { + // Avoid confusing with a comment: '!' (33), '#' (35). + if( first ) + { + first = false; + if( c == '!' || c == '#' ) + { + buf.Append( '\\' ); + } + } + + switch( c ) + { + case '\t': // =09 U+0009 HORIZONTAL TABULATION \t + buf.Append( '\\' ).Append( 't' ); + break; + case '\n': // =0A U+000A LINE FEED \n + buf.Append( '\\' ).Append( 'n' ); + break; + case '\f': // =0C U+000C FORM FEED \f + buf.Append( '\\' ).Append( 'f' ); + break; + case '\r': // =0D U+000D CARRIAGE RETURN \r + buf.Append( '\\' ).Append( 'r' ); + break; + + case ' ': // 32: ' ' + case ':': // 58: ':' + case '=': // 61: '=' + case '\\': // 92: '\' + buf.Append( '\\' ).Append( c ); + break; + + default: + if( c > 31 && c < 127 ) + { + buf.Append( c ); + } + else + { + buf.Append( '\\' ).Append( 'u' ); + buf.Append( HEX[ (c >> 12) & 0xF ] ); + buf.Append( HEX[ (c >> 8) & 0xF ] ); + buf.Append( HEX[ (c >> 4) & 0xF ] ); + buf.Append( HEX[ c & 0xF ] ); + } + break; + } + } + + return buf.ToString(); + } + + private string escapeValue( string s ) + { + StringBuilder buf = new StringBuilder(); + bool first = true; + + foreach( char c in s ) + { + // Handle value starting with whitespace. + if( first ) + { + first = false; + if( c == ' ' ) + { + buf.Append( '\\' ).Append( ' ' ); + continue; + } + else if( c == '\t' ) // =09 U+0009 HORIZONTAL TABULATION \t + { + buf.Append( '\\' ).Append( 't' ); + continue; + } + } + + switch( c ) + { + case '\t': // =09 U+0009 HORIZONTAL TABULATION \t + buf.Append( '\t' ); //OK after first position. + break; + case '\n': // =0A U+000A LINE FEED \n + buf.Append( '\\' ).Append( 'n' ); + break; + case '\f': // =0C U+000C FORM FEED \f + buf.Append( '\\' ).Append( 'f' ); + break; + case '\r': // =0D U+000D CARRIAGE RETURN \r + buf.Append( '\\' ).Append( 'r' ); + break; + case '\\': // 92: '\' + buf.Append( '\\' ).Append( c ); + break; + + default: + if( c > 31 && c < 127 ) + { + buf.Append( c ); + } + else + { + buf.Append( '\\' ).Append( 'u' ); + buf.Append( HEX[ (c >> 12) & 0xF ] ); + buf.Append( HEX[ (c >> 8) & 0xF ] ); + buf.Append( HEX[ (c >> 4) & 0xF ] ); + buf.Append( HEX[ c & 0xF ] ); + } + break; + } + } + + return buf.ToString(); + } + } +} diff --git a/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/Kajabity.Tools.Java/ParseException.cs b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/Kajabity.Tools.Java/ParseException.cs new file mode 100644 index 00000000000..f170de110b0 --- /dev/null +++ b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/Kajabity.Tools.Java/ParseException.cs @@ -0,0 +1,39 @@ +/* + * Copyright 2009-15 Williams Technologies Limited. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Kajbity is a trademark of Williams Technologies Limited. + * + * http://www.kajabity.com + */ + +using System; + +namespace Kajabity.Tools.Java +{ + /// + /// An exception thrown by when parsing + /// a properties stream. + /// + public class ParseException : System.Exception + { + /// + /// Construct an exception with an error message. + /// + /// A descriptive message for the exception + public ParseException( string message ) : base( message ) + { + } + } +} diff --git a/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/LocalManifestProvider.cs b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/LocalManifestProvider.cs new file mode 100644 index 00000000000..73f48405e14 --- /dev/null +++ b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/LocalManifestProvider.cs @@ -0,0 +1,87 @@ +using System; +using System.IO; + +using Xamarin.Installer.AndroidSDK; +using Xamarin.Installer.AndroidSDK.Manager; +using Xamarin.Installer.Common; + +namespace Xamarin.Installer.AndroidSDK +{ + public interface ILocalManifestProvider { + void SaveManifest (string manifest, string fileName = null); + string GetManifest (string fileName = null); + } + class LocalManifestProvider: ILocalManifestProvider + { + const string AppName = "AndroidSDKManager"; + + const string XAMARIN_MANIFEST_FILE_NAME = "XamarinManifest.xml"; + const string GOOGLE_MANIFEST_FILE_NAME = "GoogleManifest.xml"; + + static string CacheFolder = ConfigManager.ConfigFolder; + + public static LocalManifestProvider CreateGoogleManifestProvider () + { + return new LocalManifestProvider (AndroidManifestType.GoogleV2); + } + + public static LocalManifestProvider CreateXamarinManifestProvider () + { + return new LocalManifestProvider (AndroidManifestType.Xamarin); + } + + AndroidManifestType manifestType; + + + LocalManifestProvider (AndroidManifestType manifestType) + { + this.manifestType = manifestType; + if (manifestType != AndroidManifestType.GoogleV2 && manifestType != AndroidManifestType.Xamarin) + throw new ArgumentException ("LocalManifestProvider supports GoogleV2 and Xamarin manifest types only", "manifestType"); + } + + public void SaveManifest (string manifest, string fileName = null) + { + try { + if (!Directory.Exists (CacheFolder)) + Directory.CreateDirectory (CacheFolder); + + File.WriteAllText (GetManifestFilePath (fileName), manifest); + } catch (Exception ex) { + Logger.Error ($"Failed to save manifest: {ex}"); + } + } + + public string GetManifest (string fileName = null) + { + var manifestFilePath = GetManifestFilePath(fileName); + if (!File.Exists(manifestFilePath)) + { + Logger.Info("GetManifest:: manifestFilePath " + manifestFilePath); + return null; + } + + + try { + return File.ReadAllText (manifestFilePath); + } catch (Exception ex) { + Logger.Error ($"Failed to load local manifest: {ex}"); + } + + return null; + } + + string GetManifestFilePath (string fileName = null) + { + fileName = fileName ?? ManifestFileName; + return Path.Combine (CacheFolder, fileName); + } + + string ManifestFileName { + get { + return manifestType == AndroidManifestType.Xamarin ? XAMARIN_MANIFEST_FILE_NAME : GOOGLE_MANIFEST_FILE_NAME; + } + } + + } +} \ No newline at end of file diff --git a/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/LocalSDK/LocalSDKRepository.cs b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/LocalSDK/LocalSDKRepository.cs new file mode 100644 index 00000000000..960bd94e19c --- /dev/null +++ b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/LocalSDK/LocalSDKRepository.cs @@ -0,0 +1,386 @@ +// +// Authors: +// Marek Habersack +// +// Copyright (c) 2017, Microsoft, Inc (http://microsoft.com) +// +// All rights reserved. +// +using System; +using System.Collections.Generic; +using System.IO; +using System.Xml.Linq; +using System.Linq; + +using IniParser; +using IniParser.Model; +using Kajabity.Tools.Java; +using Xamarin.Installer.AndroidSDK.Common; +using Xamarin.Installer.AndroidSDK.GoogleV2; +using Xamarin.Installer.AndroidSDK.GoogleV2.Parsing; +using Xamarin.Installer.Common; + +namespace Xamarin.Installer.AndroidSDK.LocalSDK +{ + class LocalSDKRepository : Repository + { + sealed class InformationSource + { + public string Name; + public Func Parser; + } + + IParserErrorHandler errorHandler; + + readonly List component_info_sources; + + public LocalSDKRepository (IParserErrorHandler errorHandler) : base ("Local SDK Repository") + { + this.errorHandler = errorHandler ?? throw new ArgumentNullException (nameof (errorHandler)); + component_info_sources = new List { + new InformationSource {Name = "package.xml", Parser = Parse_Package_Xml}, + new InformationSource {Name = "source.properties", Parser = Parse_Source_Properties} + }; + } + + public override void Parse () + { + // nothing to do here + Parsed = true; + } + + protected override void DetectInternal (AndroidSdkInstance sdkInstance) + { + if (!Directory.Exists (sdkInstance.Path)) { + Logger.Info ($"SDK directory '{sdkInstance.Path}' doesn't exist, unable to perform detection"); + return; + } + + Logger.Debug ($"Detecting Android SDK in '{sdkInstance.Path}'"); + foreach (string dir in Directory.EnumerateDirectories (sdkInstance.Path, "*", SearchOption.AllDirectories)) { + MaybeParseComponent (dir, sdkInstance.Path); + } + sdkInstance.Components = Components.Where(c => c != null).Select(c => c.Clone()).ToList(); + base.DetectInternal(sdkInstance); + } + + void MaybeParseComponent (string dir, string sdkRoot) + { + foreach (InformationSource info in component_info_sources) { + if (String.IsNullOrEmpty (info?.Name) || info?.Parser == null) + continue; + + string file = Path.Combine (dir, info.Name); + if (!File.Exists (file)) + continue; + + Logger.Debug ($"Parsing Android component information from '{file}'"); + if (info.Parser (file, sdkRoot)) + return; + } + } + + bool Parse_Package_Xml (string filePath, string sdkRoot) + { + var manifest = new Uri (filePath); + var namespaces = new Dictionary (StringComparer.Ordinal); + using (var parserContext = new ParserContext (errorHandler, manifest)) { + parserContext.CurrentManifestURL = manifest; + using (var fs = File.OpenRead (filePath)) { + var doc = ParseManifest ("LocalAndroidManifest", fs); + doc.Root.GetNamespaces (manifest, ref namespaces); + Namespaces = namespaces; + XElement element = doc.Root.Element ("localPackage"); + if (element == null) + return false; + + var rp = new RemotePackageParser (this, parserContext, element, Namespaces); + rp.Parse (); + AddComponent (rp.Package); + if (rp.Package == null) + return false; + element = doc.Root.Element ("license"); + if (element == null) + return true; + var lp = new LicenseParser (parserContext, element, Namespaces); + lp.Parse (); + AddLicense (lp.License); + } + } + return true; + } + + bool Parse_Source_Properties (string filePath, string sdkRoot) + { + JavaProperties props = AndroidUtilities.ReadAndroidProperties (filePath); + + string sv; + AndroidRevision installedRevision; + props.GetProperty ("Pkg.Revision", out sv); + if (!String.IsNullOrEmpty (sv)) + installedRevision = new AndroidRevision (sv, false); + else + installedRevision = new AndroidRevision (0); + + string displayName; + props.GetProperty ("Pkg.Desc", out displayName); + + string path; + props.GetProperty ("Pkg.Path", out path); + + string license; + props.GetProperty ("Pkg.License", out license); + + string licenseId; + props.GetProperty ("Pkg.LicenseRef", out licenseId); + + string manifestUrl; + props.GetProperty ("Pkg.SourceUrl", out manifestUrl); + + // Detect component info + AndroidComponentInfo info = null; + + // Platform + if (props.GetProperty ("Platform.Version", out sv)) + info = CreatePlatformInfo (props); + + // Source + if (info == null && IsInParentDir (filePath, "sources")) + info = CreateSourceInfo (props); + + // System image + if (info == null && props.GetProperty ("SystemImage.Abi", out sv)) + info = CreateSystemImageInfo (props, sv); + + // Addon + if (info == null && IsInParentDir (filePath, "add-ons")) + info = CreateAddonInfo (props, filePath); + + // Extra + if (info == null && props.GetProperty ("Extra.Path", out sv)) + info = CreateExtraInfo (props); + + // Maven + if (info == null && props.GetProperty ("Maven.Version", out sv)) + info = CreateMavenInfo (props); + + // Generic + if (info == null) + info = new AndroidComponentInfoGeneric ("generic:genericDetailsType"); + + if (!String.IsNullOrEmpty (licenseId) && !String.IsNullOrEmpty (license)) + AddLicense (new License (licenseId, "text", license)); + + if (String.IsNullOrEmpty (path)) + path = GetRelativePath (filePath, sdkRoot); + + AddComponent (new RemotePackage (this, new Uri (filePath), null, errorHandler, null) { + Present = true, + Path = ConstructPackagePath (filePath, sdkRoot), + FileSystemPath = path, + Info = info, + DisplayName = displayName, + InstalledRevision = installedRevision, + Revision = installedRevision, + LicenseID = license + }); + + return true; + } + + string ConstructPackagePath (string filePath, string sdkRoot) + { + return GetRelativePath (filePath, sdkRoot)?.Replace (Path.DirectorySeparatorChar, ';'); + } + + string GetRelativePath (string filePath, string sdkRoot) + { + if (String.IsNullOrEmpty (filePath) || String.IsNullOrEmpty (sdkRoot) || filePath.Length <= sdkRoot.Length) + return null; + + return Path.GetDirectoryName (filePath).Substring (sdkRoot.Length + 1); + } + + AndroidComponentInfoMaven CreateMavenInfo (JavaProperties props) + { + return new AndroidComponentInfoMaven ( + "addon:mavenType", + CreateVendor (props, "Extra.VendorId", "Extra.VendorDisplay") + ); + } + + AndroidComponentInfoExtra CreateExtraInfo (JavaProperties props) + { + return new AndroidComponentInfoExtra ( + "addon:extraDetailsType", + CreateVendor (props, "Extra.VendorId", "Extra.VendorDisplay") + ); + } + + AndroidComponentInfoAddon CreateAddonInfo (JavaProperties props, string propsFilePath) + { + string apiLevel; + if (!props.GetProperty ("AndroidVersion.ApiLevel", out apiLevel)) + return null; + + string codename; + props.GetProperty ("AndroidVersion.CodeName", out codename); + + PackageTag tag = CreateTag (props, "SystemImage.TagId", "Addon.NameDisplay"); + PackageVendor vendor = CreateVendor (props, "Addon.VendorId", "Addon.VendorDisplay"); + List libraries = GetAddonLibraries (Path.Combine (Path.GetDirectoryName (propsFilePath), "manifest.ini")); + + return new AndroidComponentInfoAddon ("addon:addonDetailsType", apiLevel, codename, tag, vendor, libraries); + } + + List GetAddonLibraries (string manifestIni) + { + if (!File.Exists (manifestIni)) + return null; + + var parser = new FileIniDataParser (); + IniData data = parser.ReadFile (manifestIni); + string libs = data.GetKey ("libraries")?.Trim (); + if (String.IsNullOrEmpty (libs)) + return null; + + List libraries = null; + string[] libids = libs.Split (';'); + foreach (string lid in libids) { + string id = lid.Trim (); + if (String.IsNullOrEmpty (lid)) + continue; + AddPackageLibrary (id, data.GetKey (id), ref libraries); + } + + return libraries; + } + + void AddPackageLibrary (string id, string details, ref List libraries) + { + string jarPath, description; + + string[] dparts = details?.Split (';'); + if (dparts.Length == 1) { + jarPath = dparts[0].Trim (); + description = id; + } else { + jarPath = dparts[0].Trim (); + description = dparts[1].Trim (); + } + + if (libraries == null) + libraries = new List (); + libraries.Add (new PackageLibrary { + Name = id, + LocalJarPath = jarPath, + Description = description + }); + } + + AndroidComponentInfoSystemImage CreateSystemImageInfo (JavaProperties props, string abiName) + { + string apiLevel; + if (!props.GetProperty ("AndroidVersion.ApiLevel", out apiLevel)) + return null; + + AndroidSystemImageAbi abi; + try { + abi = AndroidUtilities.StringToAbi (abiName); + } catch (ArgumentOutOfRangeException) { + abi = AndroidSystemImageAbi.Any; + } + + string codename; + props.GetProperty ("AndroidVersion.CodeName", out codename); + + PackageTag tag = CreateTag (props, "SystemImage.TagId", "SystemImage.TagDisplay"); + PackageVendor vendor = CreateVendor (props, "Addon.VendorId", "Addon.VendorDisplay"); + + return new AndroidComponentInfoSystemImage ("sys-img:sysImgDetailsType", abi, abiName, apiLevel, codename, tag, vendor); + } + + AndroidComponentInfoSource CreateSourceInfo (JavaProperties props) + { + string apiLevel; + if (!props.GetProperty ("AndroidVersion.ApiLevel", out apiLevel)) + return null; + + string codename; + props.GetProperty ("AndroidVersion.CodeName", out codename); + + return new AndroidComponentInfoSource ("sdk:sourceDetailsType", apiLevel, codename); + } + + AndroidComponentInfoPlatform CreatePlatformInfo (JavaProperties props) + { + string apiLevel; + if (!props.GetProperty ("AndroidVersion.ApiLevel", out apiLevel)) + return null; + + string layoutLibApi; + if (!props.GetProperty ("Layoutlib.Api", out layoutLibApi)) + return null; + + string codename; + props.GetProperty ("Platform.CodeName", out codename); + + if (String.IsNullOrEmpty (codename)) + props.GetProperty ("AndroidVersion.CodeName", out codename); + + return new AndroidComponentInfoPlatform ("sdk:platformDetailsType", apiLevel, codename, layoutLibApi); + } + + PackageVendor CreateVendor (JavaProperties props, string idKeyName, string displayKeyName) + { + return GetIdProperties (props, idKeyName, displayKeyName, out string id, out string display) ? new PackageVendor (id, display) : null; + } + + PackageTag CreateTag (JavaProperties props, string idKeyName, string displayKeyName) + { + return GetIdProperties (props, idKeyName, displayKeyName, out string id, out string display) ? new PackageTag (id, display) : null; + } + + bool GetIdProperties (JavaProperties props, string idKeyName, string displayKeyName, out string id, out string display) + { + display = null; + id = null; + if (String.IsNullOrEmpty (idKeyName)) + return false; + + if (props.GetProperty (idKeyName, out id) && !String.IsNullOrEmpty (id)) { + if (!String.IsNullOrEmpty (displayKeyName)) + props.GetProperty (displayKeyName, out display); + return true; + } + return false; + } + + bool IsInParentDir (string filePath, string parentDirName) + { + if (String.IsNullOrEmpty (filePath) || String.IsNullOrEmpty (parentDirName)) + return false; + + string dir = Path.Combine (Path.GetDirectoryName (filePath), ".."); + return String.Compare (parentDirName, Path.GetFileName (Path.GetFullPath (dir)), StringComparison.OrdinalIgnoreCase) == 0; + } + + void AddLicense (License license) + { + if (license == null) + return; + if (Licenses.Values.Contains (license)) + return; + Licenses[license.ID] = license; + } + + void AddComponent (IAndroidComponent component) + { + if (component == null) + return; + if (Components.Contains (component)) + return; + Components.Add (component); + } + } +} diff --git a/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/MonitorWithTotalProgress.cs b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/MonitorWithTotalProgress.cs new file mode 100644 index 00000000000..2918a85ef6b --- /dev/null +++ b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/MonitorWithTotalProgress.cs @@ -0,0 +1,134 @@ +using System; +using System.Threading; +using Xamarin.Installer.AndroidSDK.Manager; + +namespace Xamarin.Installer.AndroidSDK +{ + public class MonitorWithTotalProgress : ICancellableProgressMonitor + { + float currentTaskTotalWork; + int mainComponentsCount; + float lastLoadPercentage; + const float minPercentageChange = 0.01f; + float fractionPerComponent, fractionPerSubComponent; + + public bool DisposeOnFinish => monitor is ICancellableProgressMonitor + ? ((ICancellableProgressMonitor) monitor).DisposeOnFinish + : false; + + public CancellationToken CancellationToken => monitor is ICancellableProgressMonitor + ? ((ICancellableProgressMonitor) monitor).CancellationToken + : CancellationToken.None; + + IProgressMonitor monitor; + private int subComponentsCount; + + public bool IsMonitorWithTotalProgress { get; private set; } + public int MainComponentIndex { get; set; } + public int SubComponentIndex { get; set; } + public int SubComponentsCount { get => subComponentsCount; + set { + if (value <= 0) + throw new ArgumentOutOfRangeException (); + + subComponentsCount = value; + fractionPerSubComponent = (fractionPerComponent * 0.5f) / value; + } + } + public States State { get; set; } + + public enum States + { + Download, + Install + } + + public MonitorWithTotalProgress (IProgressMonitor monitor, int mainComponentsCount) + { + if (mainComponentsCount <= 0) + throw new ArgumentOutOfRangeException (nameof (mainComponentsCount)); + + this.monitor = monitor; + this.mainComponentsCount = mainComponentsCount; + fractionPerComponent = 1f / mainComponentsCount; + IsMonitorWithTotalProgress = monitor is IProgressMonitorWithTotalProgress; + } + + public void BeginStep (string step) + { + currentTaskTotalWork = 1; + monitor.BeginStep (step); + } + + public void BeginStep (string step, long totalWork) + { + currentTaskTotalWork = totalWork; + monitor.BeginStep (step, totalWork); + } + + public void EndStep (AndroidSDKComponentInstallationResult result = null) + { + monitor.EndStep (result); + } + + public void ReportProgress (long work) + { + monitor.ReportProgress (work); + + //Console.WriteLine ("MonitorWithTotalProgress.ReportProgress.State = " + State); + + if (IsMonitorWithTotalProgress) { + var loadPercentage = ReportTotalProgress (work); + + if (lastLoadPercentage > 0.0 && Math.Abs (lastLoadPercentage - loadPercentage) <= minPercentageChange) + return; + + lastLoadPercentage = loadPercentage; + ((IProgressMonitorWithTotalProgress) monitor).ReportTotalProgress (loadPercentage, + mainComponentsCount, MainComponentIndex, + subComponentsCount, SubComponentIndex); + } + } + + float ReportTotalProgress (long work) + { + // [ MainComponent1 ][ MainComponent2 ] ... [ MainComponentN ] + // MainComponent Installation consists of archives download: [ Archive1 ][ Archive2 ] ... [ ArchiveM ] + // and then their installation: [ Archive1 ][ Archive2 ] ... [ ArchiveM ] + // so we map MainComponentProgress as download 0 .. 50, install 50 .. 100 + // + // MainComponentDownload = (Archive1DownloadBytes / Archive1DownloadTotalBytes -> 0 .. 100) (Archive2DownloadBytes / Archive2DownloadTotalBytes -> 0 .. 100) ... (ArchiveMDownloadBytes / ArchiveMDownloadTotalBytes -> 0 .. 100) + // + // and then map each maincomponent to total progress as + // 0 .. 100 -> 1/MainComponentsCount * MainComponentIndex .. 1/MainComponentsCount * (MainComponentIndex + 1) + + float totalProgress; + float currentTaskProgress = 1f * work / currentTaskTotalWork; // 0 .. 1 + + float subComponentsProgress = SubComponentIndex * fractionPerSubComponent + currentTaskProgress * fractionPerSubComponent; + + if (State == States.Download) + totalProgress = MainComponentIndex * fractionPerComponent + subComponentsProgress; // 0 .. 50 (of local main component' progress) + else + totalProgress = MainComponentIndex * fractionPerComponent + fractionPerComponent / 2f + subComponentsProgress; // 50 .. 100 (of local main component' progress) + + return totalProgress; + } + + public void ReportMessage (string message) + { + monitor.ReportMessage (message); + } + + public void ReportError (string message, Exception ex) + { + monitor.ReportError (message, ex); + } + + public void Dispose () + { + if (monitor is ICancellableProgressMonitor) + ((ICancellableProgressMonitor) monitor)?.Dispose (); + } + } +} diff --git a/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/ParserErrorLevel.cs b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/ParserErrorLevel.cs new file mode 100644 index 00000000000..9cbb8fc7e1f --- /dev/null +++ b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/ParserErrorLevel.cs @@ -0,0 +1,38 @@ +// +// Authors: +// Marek Habersack +// +// Copyright (c) 2017, Microsoft Corp. (http://microsoft.com) +// +// All rights reserved. +// +using System; + +namespace Xamarin.Installer.AndroidSDK +{ + /// + /// Parser error level. + /// + public enum ParserErrorLevel + { + /// + /// Debug + /// + Debug, + + /// + /// Info + /// + Info, + + /// + /// Warning + /// + Warning, + + /// + /// Error + /// + Error + } +} diff --git a/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/Repository.cs b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/Repository.cs new file mode 100644 index 00000000000..3935879336b --- /dev/null +++ b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/Repository.cs @@ -0,0 +1,348 @@ +// +// Authors: +// Marek Habersack +// +// opyright (c) 2017, Microsoft Corp. (http://microsoft.com) +// +// All rights reserved. +// +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Xml.Linq; +using Xamarin.Installer.AndroidSDK.Common; +using Xamarin.Installer.Common; + +namespace Xamarin.Installer.AndroidSDK +{ + public abstract class Repository + { + public string Name { get; } + public Uri ManifestURL { get; protected set; } + public IList Components { get; } + public IList JdkComponents { get; } = new List(); + public bool Parsed { get; protected set; } + public IDictionary Licenses { get; } + public IDictionary Channels { get; protected set; } + public Channel DefaultChannel { get; protected set; } + public Dictionary Namespaces { get; protected set; } + + /// + /// The repository is considered Offline if manifest failed to download. + /// + /// + public bool IsOffline { get; protected set; } + + /// + /// Used to cache and retrieve cached manifest. + /// + /// + protected ILocalManifestProvider ManifestCacher { get; set; } + + protected Repository(string name, Uri manifestURL) : this(name) + { + ManifestURL = manifestURL ?? throw new ArgumentNullException(nameof(manifestURL)); + } + + protected Repository(string name) + { + Components = new List(); + Licenses = new Dictionary(StringComparer.Ordinal); + Channels = new Dictionary(StringComparer.Ordinal); + Name = String.IsNullOrEmpty(name) ? "Unnamed Repository" : name; + } + + public void Remove(IAndroidComponent component, string androidSDKRoot) + { + if (component == null) + throw new ArgumentNullException(nameof(component)); + + androidSDKRoot = androidSDKRoot?.Trim(); + if (String.IsNullOrEmpty(androidSDKRoot)) + throw new ArgumentException("must not be null or empty", nameof(androidSDKRoot)); + + var rp = EnsureBasePackage(component); + Logger.Info($"Removing Android SDK component: {rp.DetailedDescription} ({rp.Path})"); + rp.Remove(androidSDKRoot); + } + + public void Install(IAndroidComponent component, string archivePath, string androidSDKRoot, + InstallationProgressEventArgs.InstallationProgressActionDelegate progressCallback = null) + { + if (component == null) + throw new ArgumentNullException(nameof(component)); + + archivePath = archivePath?.Trim(); + if (String.IsNullOrEmpty(archivePath)) + throw new ArgumentException("must not be null or empty", nameof(archivePath)); + + androidSDKRoot = androidSDKRoot?.Trim(); + if (String.IsNullOrEmpty(androidSDKRoot)) + throw new ArgumentException("must not be null or empty", nameof(androidSDKRoot)); + + InstallComponent(component, archivePath, androidSDKRoot, progressCallback); + } + + protected virtual void InstallComponent(IAndroidComponent component, string archivePath, string androidSDKRoot, + InstallationProgressEventArgs.InstallationProgressActionDelegate progressCallback = null) + { + var rp = EnsureBasePackage(component); + Logger.Info($"Installing Android SDK component: {rp.DetailedDescription} ({rp.Path})"); + try + { + rp.Install(archivePath, androidSDKRoot, progressCallback); + } + catch (Exception ex) + { + Logger.Exception($"Installation of Android SDK component '{rp.DetailedDescription}' failed with exception.", ex); + if (component.IgnoreFailure) + Logger.Info($"Ignoring exception for Android SDK component '{rp.DetailedDescription}' - it is not critical for development."); + else + throw; + } + } + + public void Detect(AndroidSdkInstance sdkInstance) + { + if (sdkInstance == null) + throw new ArgumentNullException(nameof(sdkInstance)); + + sdkInstance.Components = Components.Where(c => c != null).Select(c => c.Clone()).ToList(); + DetectInternal(sdkInstance); + } + + public void Refresh(AndroidSdkInstance sdkInstance) + { + if (sdkInstance == null) + throw new ArgumentNullException(nameof(sdkInstance)); + + if (Components == null || Components.Count == 0 || sdkInstance.Components == null | sdkInstance.Components.Count == 0) + return; + + foreach (IAndroidComponent c in Components) + { + if (c == null) + continue; + + IAndroidComponent match = FindMatchedComponent(c, sdkInstance.Components); + + if (match == null) + { + sdkInstance.Components.Add(c); + continue; + } + + BasePackage remotePackage = EnsureBasePackage(c); + BasePackage instancePackage = EnsureBasePackage(match); + instancePackage.PerformDetection(sdkInstance.Path, true); + instancePackage.RefreshMetadata(remotePackage); + } + + var componentsToRemove = sdkInstance.Components + .Where(c => FindMatchedComponent(c, Components) == null) + .ToArray(); + foreach (var c in componentsToRemove) + sdkInstance.Components.Remove(c); + } + + IAndroidComponent FindMatchedComponent(IAndroidComponent c, IList componentList) + { + return componentList.FirstOrDefault(component => component?.MatchesTo(c) == true); + } + + BasePackage EnsureBasePackage(IAndroidComponent c) + { + var ret = c as BasePackage; + if (ret == null) + throw new InvalidOperationException($"Internal error: unsupported component type {c.GetType()}"); + + return ret; + } + + protected virtual void DetectInternal(AndroidSdkInstance sdkInstance) + { + if (sdkInstance.Components.Count == 0) + { + Logger.Info($"Unable to perform detection in '{Name}', no known components"); + return; + } + + foreach (IAndroidComponent c in sdkInstance.Components) + { + DetectComponent(sdkInstance, c); + } + } + + protected virtual void DetectComponent(AndroidSdkInstance sdkInstance, IAndroidComponent c) + { + EnsureBasePackage(c)?.PerformDetection(sdkInstance.Path); + } + + public Channel GetChannel(string channelId) + { + return GetDictionaryEntry(Channels, channelId); + } + + public License GetLicense(string licenseId) + { + return GetDictionaryEntry(Licenses, licenseId); + } + + protected T GetDictionaryEntry(IDictionary dict, string entryId) where T : class + { + if (dict == null) + return null; + + entryId = entryId?.Trim(); + if (String.IsNullOrEmpty(entryId)) + return null; + + T ret; + if (!dict.TryGetValue(entryId, out ret)) + return null; + + return ret; + } + + protected void CopyDictionary(IDictionary source, IDictionary destination) + { + if (source == null) + return; + if (destination == null) + throw new ArgumentNullException(nameof(destination)); + + destination.Clear(); + foreach (var kvp in source) + destination.Add(kvp.Key, kvp.Value); + } + + public string GetNamespaceUri(string nsName) + { + if (String.IsNullOrEmpty(nsName) || Namespaces == null) + return String.Empty; + + XNamespace ns; + if (Namespaces.TryGetValue(nsName, out ns) && ns != null) + return ns.NamespaceName ?? String.Empty; + + return String.Empty; + } + + public abstract void Parse(); + + /// + /// Loads online manifest using ManifestURL; + /// If it fails, tries to load local cached manifest; + /// If local manifest is loaded, sets IsOffline to true and returns it. + /// + /// + /// + protected string LoadManifest() + { + string manifest = null; + Exception exception = null; + try + { + if (CommonUtilities.Helpers.DownloadToString(ManifestURL, out manifest) && manifest != null) + { + IsOffline = false; + ManifestCacher?.SaveManifest(manifest); + return manifest; + } + + } + catch (Exception ex) + { + exception = ex; + } + + // In case something throwed during manifest caching. + if (manifest != null) + return manifest; + + IsOffline = true; + Logger.Error($"Failed to download {Name} manifest from {ManifestURL}. {exception}"); + + // If download from primary URL fails, attempt to use cached manifest + if (ManifestCacher != null) + { + Logger.Info("Trying to load cached manifest..."); + + manifest = ManifestCacher.GetManifest(); + if (manifest != null) + { + Logger.Info("Using cached manifest..."); + return manifest; + } + else + { + Logger.Info("Unable to use cached manifest."); + } + } + + // If both primary URL and cached manifest fail, try fallback URL + var fallbackUrl = GetFallbackManifestUrl(); + if (fallbackUrl != null) + { + Logger.Info("Trying to load fallback manifest..."); + if (CommonUtilities.Helpers.DownloadToString(fallbackUrl, out manifest) && manifest != null) + { + + IsOffline = false; + Logger.Info("Successfully loaded the fallback manifest file"); + return manifest; + } + } + if (exception != null) + throw exception; + + return null; + } + + /// + /// Parses manifest xml string into XDocument; + /// Re-throws if XDocument.Load throws; + /// Logs manfiest if the parsing failes. + /// + /// + /// + /// Returns XDocument + protected XDocument ParseManifest(string manifestName, string manifest) + { + using (System.IO.Stream input = new System.IO.MemoryStream(Encoding.UTF8.GetBytes(manifest))) + { + return ParseManifest(manifestName, input); + } + } + + /// + /// Parses manifest xml stream into XDocument; + /// Re-throws if XDocument.Load throws; + /// Logs manfiest if the parsing failes. + /// + /// + /// + /// + /// Returns XDocument + protected XDocument ParseManifest(string manifestName, System.IO.Stream manifestStream, LoadOptions loadOptions = LoadOptions.None) + { + try + { + return XDocument.Load(manifestStream, LoadOptions.SetLineInfo); + } + catch (Exception ex) + { + Logger.Exception($"Could not parse {manifestName} xml. For more info, check the logged manifest file", ex); + + Logger.LogManifest(manifestName, manifestStream); + throw; + } + } + + public virtual Uri GetFallbackManifestUrl() + { + return null; + } + } +} diff --git a/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/SystemImageSpec.cs b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/SystemImageSpec.cs new file mode 100644 index 00000000000..928489743e0 --- /dev/null +++ b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/SystemImageSpec.cs @@ -0,0 +1,30 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +using Xamarin.Installer.AndroidSDK.Common; + +namespace Xamarin.Installer.AndroidSDK +{ + /// + /// Specification of a single Android SDK System Image + /// + public class SystemImageSpec + { + /// + /// ABI of the system image + /// + public AndroidSystemImageAbi ABI { get; set; } + + /// + /// Tag (flavor) of the System Image (e.g. 'default', 'google_apis' etc - the list is + /// not defined anywhere, the only source of information is the Android SDK itself, namely + /// the 'system-images' directory off the Android SDK installation root. Subdirectories of + /// that directory correspond to the Android SDK platforms and their subdirectories, in turn, + /// are the names of the tags you can use here. + /// + public PackageTag Tag { get; set; } + } +} diff --git a/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/Xamarin/XamarinPackage.cs b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/Xamarin/XamarinPackage.cs new file mode 100644 index 00000000000..bc55f72d54b --- /dev/null +++ b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/Xamarin/XamarinPackage.cs @@ -0,0 +1,104 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using System.Xml; + +using Xamarin.Installer.AndroidSDK.Common; +using Xamarin.Installer.Common; + +namespace Xamarin.Installer.AndroidSDK.Xamarin +{ + class XamarinPackage : BasePackage, IEquatable + { + public override Channel Channel => Repository.GetChannel ("channel-0"); + public override AndroidComponentType ComponentType { get; } + public Uri OriginalManifestUri { get; set; } + + public XamarinPackage (Repository repository, Uri manifestURL, IXmlLineInfo location, IList archives, AndroidComponentType componentType) : base (repository, manifestURL, location, archives) + { + ComponentType = componentType; + PopulatePlatformArchives (false); + } + + public static XamarinPackage Clone (XamarinPackage source) + { + if (source == null) + return null; + + // Archive instance aren't cloned because we want them to be shared amongst all the SDK instances and as the list is read-only, there's no + // need to clone it either. + return new XamarinPackage (source.Repository, source.ManifestURL, source.Location, source.OriginalArchives, source.ComponentType) { + IsEssential = source.IsEssential, + IgnoreFailure = source.IgnoreFailure, + Present = source.Present, + NeedsUpdate = source.NeedsUpdate, + Path = source.Path, + FileSystemPath = source.FileSystemPath, + Obsolete = source.Obsolete, + Info = source.Info, + Revision = source.Revision, + InstalledRevision = source.InstalledRevision, + DisplayName = source.DisplayName, + Dependencies = source.Dependencies, + LicenseID = source.LicenseID, + ForceInstallation = source.ForceInstallation + }; + } + + public override void RefreshMetadata (IAndroidComponent component, bool ignoreInstalledState = true) + { + RefreshMetadata (component as XamarinPackage); + } + + void RefreshMetadata (XamarinPackage rp, bool ignoreInstalledState = true) + { + if (rp == null) + throw new ArgumentNullException (nameof (rp)); + + MetadataChanged (CreateMetadata (rp), true, ignoreInstalledState); + } + + protected override PackageMetadata CreateMetadata (BasePackage component) + { + return new PackageMetadata (component); + } + + public bool Equals (XamarinPackage other) + { + if (other == null) + return false; + + return base.Equals (other as BasePackage); + } + + public override bool Equals (object obj) + { + return Equals (obj as XamarinPackage); + } + + public override int GetHashCode () + { + return base.GetHashCode (); + } + + protected override void LogError (string message) + { + Logger.Error (message); + } + + protected override void LogWarning (string message) + { + Logger.Warning (message); + } + + protected override void LogInfo (string message) + { + Logger.Info (message); + } + + protected override void LogDebug (string message) + { + Logger.Debug (message); + } + } +} diff --git a/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/Xamarin/XamarinRepository.cs b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/Xamarin/XamarinRepository.cs new file mode 100644 index 00000000000..93da5ed5036 --- /dev/null +++ b/src/Xamarin.Installer.AndroidSDK/Xamarin.Installer.AndroidSDK/Xamarin/XamarinRepository.cs @@ -0,0 +1,374 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Xml.Linq; +using System.Xml.XPath; +using Xamarin.Installer.AndroidSDK.Common; +using Xamarin.Installer.Common; + +namespace Xamarin.Installer.AndroidSDK.Xamarin +{ + class XamarinRepository : Repository + { + readonly Dictionary> infoParsers; + readonly Dictionary infoToComponentType; + + public XamarinRepository(Uri manifestURL = null, bool cacheManifest = false) : base("Xamarin Repository") + { + this.ManifestURL = manifestURL ?? GetDefaultManifestURL(); + + infoParsers = new Dictionary> { + [typeof (AndroidComponentInfoPlatform)] = ParsePlatformPackageInfo, + [typeof (AndroidComponentInfoSystemImage)] = ParseSystemImageInfo, + [typeof (AndroidComponentInfoAddon)] = ParseAddonInfo, + [typeof (AndroidComponentInfoExtra)] = ParseExtraInfo, + }; + + infoToComponentType = new Dictionary { + [typeof (AndroidComponentInfoPlatform)] = AndroidComponentType.Platform, + [typeof (AndroidComponentInfoSystemImage)] = AndroidComponentType.SystemImage, + [typeof (AndroidComponentInfoAddon)] = AndroidComponentType.Addon, + [typeof (AndroidComponentInfoExtra)] = AndroidComponentType.Extra, + }; + + var defaultChannel = new Channel("channel-0", "stable"); + DefaultChannel = defaultChannel; + Channels.Add("channel-0", defaultChannel); + + if (cacheManifest) + ManifestCacher = LocalManifestProvider.CreateXamarinManifestProvider(); + } + + Uri GetDefaultManifestURL() + { + Logger.Debug($"Android Manifest Version: {Constants.RequiredComponentVersions.AndroidManifestFeedVersion}"); + var androidManifestFeedVersion = Constants.RequiredComponentVersions.AndroidManifestFeedVersion?.Replace(".", "-"); + return new Uri($"https://aka.ms/AndroidManifestFeed/d{androidManifestFeedVersion}"); + } + + public override void Parse() + { + ParseInternal(); + Parsed = true; + } + + void ParseInternal() + { + string manifest = LoadManifest(); + if (string.IsNullOrEmpty(manifest)) + { + Logger.Warning($"Android manifest should not be empty! Manifest URL: {ManifestURL}"); + // todo: should we throw here? + return; + } + + Uri baseURL = Helpers.GetBaseURL(ManifestURL); + Logger.Debug($"Xamarin manifest base URL: {baseURL}"); + + Components?.Clear(); + + XDocument doc = ParseManifest("XamarinAndroidManifest", manifest); + + var namespaces = new Dictionary(StringComparer.Ordinal); + doc.Root.GetNamespaces(ManifestURL, ref namespaces); + Namespaces = namespaces; + + XElement root = doc.Root; + + var licenses = new Dictionary(StringComparer.Ordinal); + foreach (XElement element in root.XPathSelectElements("//license[string-length (@id) > 0]")) + { + string licenseId = element.GetAttributeValue("id", documentUrl: ManifestURL); + if (licenses.ContainsKey(licenseId)) + continue; + string licenseType = element.GetAttributeValue("type", required: false, documentUrl: ManifestURL); + licenses.Add(licenseId, new License(licenseId, licenseType, element.Value)); + } + if (licenses.Count > 0) + CopyDictionary(licenses, Licenses); + + ParsePackages("platform tools", root.XPathSelectElements("./platform-tools")); + + // Obsolete. Using for backward compatibility. + ParsePackages("tools", root.XPathSelectElements("./tools")); + + ParsePackages("cmdline-tools", root.XPathSelectElements("./cmdline-tools")); + ParsePackages("build tools", root.XPathSelectElements("./build-tool")); + ParsePackages("emulators", root.XPathSelectElements("./emulator")); + ParsePackages("NDK", root.XPathSelectElements("./ndk")); + ParsePackages("LLDB", root.XPathSelectElements("./lldb")); + ParsePackages("patchers", root.XPathSelectElements("./patcher")); + ParsePackages("platforms", root.XPathSelectElements("./platform")); + ParsePackages("system images", root.XPathSelectElements("./system-image")); + ParsePackages("addons", root.XPathSelectElements("./addon")); + ParsePackages("extras", root.XPathSelectElements("./extra")); + ParseJdkPackages(root.XPathSelectElements("./jdk")); + } + + void ParseJdkPackages(IEnumerable elements) + { + foreach (XElement element in elements) + { + JdkComponents.Add(ParseJdkPackage(element)); + } + } + + JdkPackage ParseJdkPackage(XElement element) + { + var description = element.GetAttributeValue("description", documentUrl: ManifestURL); + var vendorId = element.GetAttributeValue("vendor-id", documentUrl: ManifestURL); + var vendorDisplay = element.GetAttributeValue("vendor-display", documentUrl: ManifestURL); + var vendor = CreateVendor(vendorId, vendorDisplay); + var revision = new AndroidRevision(element.GetAttributeValue("revision", documentUrl: ManifestURL)); + string licenseId = element.GetAttributeValue("license", documentUrl: ManifestURL); + bool obsolete = element.GetAttributeValue("obsolete", documentUrl: ManifestURL).AsBool(); + bool preview = element.GetAttributeValue("preview", documentUrl: ManifestURL).AsBool(); + + var archives = new List(); + foreach (XElement urlElement in element.XPathSelectElements("./urls/url")) + { + var archive = ParseJDKArchive(urlElement); + archives.Add(archive); + } + + if (archives.Count == 0) + throw new InvalidOperationException($"Package {element.Name} at {element.GetLocation(ManifestURL)} does not have any valid URLs"); + + return new JdkPackage(description, obsolete, preview, licenseId, vendor, revision, archives); + } + + void ParsePackages(string name, IEnumerable elements) where TInfo : AndroidComponentInfo + { + Logger.Debug($"Parsing: {name}"); + if (!elements.Any()) + { + Logger.Debug("No elements of this kind found"); + return; + } + + AndroidComponentInfo info = null; + Func infoParser; + + foreach (XElement element in elements) + { + string type = element.GetAttributeValue("original-type", documentUrl: ManifestURL); + info = null; + if (infoParsers.TryGetValue(typeof(TInfo), out infoParser) && infoParser != null) + { + info = infoParser(name, type, element); + } + + if (info == null) + info = new AndroidComponentInfoGeneric(type); + + Components.Add(ParseCommonPackageData(name, info, element)); + } + } + + XamarinPackage ParseCommonPackageData(string name, AndroidComponentInfo info, XElement element) + { + // Ignored attributes (obsolete): + // preview + // + var revision = new AndroidRevision(element.GetAttributeValue("revision", documentUrl: ManifestURL)); + string path = element.GetAttributeValue("path", documentUrl: ManifestURL); + string filesystemPath = element.GetAttributeValue("filesystem-path", documentUrl: ManifestURL); + string description = element.GetAttributeValue("description", documentUrl: ManifestURL); + string licenseId = element.GetAttributeValue("license", documentUrl: ManifestURL); + bool obsolete = element.GetAttributeValue("path", documentUrl: ManifestURL).AsBool(); + Uri originalManifestUri = element.GetAttributeValue("manifest-url", documentUrl: ManifestURL).AsUri(); + + var archives = new List(); + foreach (XElement urlElement in element.XPathSelectElements("./urls/url")) + { + archives.Add(ParseArchive(urlElement)); + } + + if (archives.Count == 0) + throw new InvalidOperationException($"Package {element.Name} at {element.GetLocation(ManifestURL)} does not have any valid URLs"); + + var dependencies = new List(); + foreach (XElement urlElement in element.XPathSelectElements("./dependencies/dependency")) + { + Dependency dep = ParseDependency(urlElement); + if (dep.Platform != AndroidSDKPlatform.Any && dep.Platform != AndroidSDKPlatform.Unknown && dep.Platform != AndroidSDKContext.Instance.Platform) + continue; + dependencies.Add(dep); + } + if (dependencies.Count == 0) + dependencies = null; + + AndroidComponentType componentType; + if (!infoToComponentType.TryGetValue(info.GetType(), out componentType)) + componentType = AndroidComponentType.Generic; + + return new XamarinPackage(this, ManifestURL, element.GetLineInfo(), archives, componentType) + { + Revision = revision, + Path = path, + FileSystemPath = filesystemPath, + DisplayName = description, + Obsolete = obsolete, + OriginalManifestUri = originalManifestUri, + LicenseID = licenseId, + Info = info, + Dependencies = dependencies, + }; + } + + Dependency ParseDependency(XElement element) + { + string path = element.GetAttributeValue("path", documentUrl: ManifestURL); + string minRevision = element.GetAttributeValue("min-revision", documentUrl: ManifestURL); + string hostOS = element.GetAttributeValue("host-os", documentUrl: ManifestURL); + + return new Dependency(path, String.IsNullOrEmpty(minRevision) ? null : new AndroidRevision(minRevision)) + { + Platform = String.IsNullOrEmpty(hostOS) ? AndroidSDKPlatform.Unknown : AndroidUtilities.GetPlatformFromOS(hostOS) + }; + } + + JdkArchive ParseJDKArchive(XElement element) + { + string payloadFileName = element.GetAttributeValue("payloadFileName", documentUrl: ManifestURL); + var archive = ParseArchive(element); + archive.PayloadFileName = payloadFileName; + return archive; + } + + T ParseArchive(XElement element) where T : ArchiveBase + { + ulong size = element.GetAttributeValue("size", documentUrl: ManifestURL).AsULong(); + string checksum = element.GetAttributeValue("checksum", documentUrl: ManifestURL); + string checksumType = element.GetAttributeValue("checksum-type", required: false, documentUrl: ManifestURL); + string hostOS = element.GetAttributeValue("host-os", documentUrl: ManifestURL); + string hostArch = element.GetAttributeValue("host-arch", required: false, documentUrl: ManifestURL); + uint hostBits = element.GetAttributeValue("host-bits", documentUrl: ManifestURL).AsUInt(); + Uri url = element.GetElementValue().AsUri(true); + + var archive = (T)Activator.CreateInstance(typeof(T), new object[] { hostOS }); + archive.Size = size; + archive.Checksum = checksum; + archive.ChecksumType = checksumType; + archive.HostArch = hostArch; + archive.HostBits = hostBits; + archive.Url = url; + + return archive; + } + + AndroidComponentInfo ParsePlatformPackageInfo(string name, string packageType, XElement element) + { + // Ignored attributes (obsolete): + // min-tools-rev + // layoutlib-revision + // + string apiLevel = element.GetAttributeValue("api", documentUrl: ManifestURL); + string codeName = element.GetAttributeValue("codename", documentUrl: ManifestURL); + string layoutLibApi = element.GetAttributeValue("layoutlib-api", documentUrl: ManifestURL); + string description = element.GetAttributeValue("description", documentUrl: ManifestURL); + string preview = element.GetAttributeValue("preview", documentUrl: ManifestURL); + + return new AndroidComponentInfoPlatform("platform", apiLevel, codeName, layoutLibApi) + { + Description = description, + Preview = preview != "False", + }; + } + + AndroidComponentInfo ParseSystemImageInfo(string name, string packageType, XElement element) + { + string apiLevel = element.GetAttributeValue("api", documentUrl: ManifestURL); + string abi = element.GetAttributeValue("abi", documentUrl: ManifestURL); + string codeName = element.GetAttributeValue("codename", documentUrl: ManifestURL); + string tagId = element.GetAttributeValue("tag-id", documentUrl: ManifestURL); + string tagDisplay = element.GetAttributeValue("tag-display", documentUrl: ManifestURL); + string vendorId = element.GetAttributeValue("vendor-id", documentUrl: ManifestURL); + string vendorDisplay = element.GetAttributeValue("vendor-display", documentUrl: ManifestURL); + + return new AndroidComponentInfoSystemImage( + packageType, + AndroidUtilities.StringToAbi(abi), + abi, + apiLevel, + codeName, + CreateTag(tagId, tagDisplay), + CreateVendor(vendorId, vendorDisplay) + ); + } + + AndroidComponentInfo ParseAddonInfo(string name, string packageType, XElement element) + { + string apiLevel = element.GetAttributeValue("api", documentUrl: ManifestURL); + string codeName = element.GetAttributeValue("codename", documentUrl: ManifestURL); + string tagId = element.GetAttributeValue("tag-id", documentUrl: ManifestURL); + string tagDisplay = element.GetAttributeValue("tag-display", documentUrl: ManifestURL); + string vendorId = element.GetAttributeValue("vendor-id", documentUrl: ManifestURL); + string vendorDisplay = element.GetAttributeValue("vendor-display", documentUrl: ManifestURL); + + var libraries = new List(); + foreach (XElement libElement in element.XPathSelectElements("./libraries/library")) + { + libraries.Add(ParseLibrary(libElement)); + } + + return new AndroidComponentInfoAddon( + packageType, + apiLevel, + codeName, + CreateTag(tagId, tagDisplay), + CreateVendor(vendorId, vendorDisplay), + libraries + ); + } + + PackageLibrary ParseLibrary(XElement element) + { + string name = element.GetAttributeValue("name", documentUrl: ManifestURL); + string localJarPath = element.GetAttributeValue("local-jar-path", documentUrl: ManifestURL); + string description = element.GetAttributeValue("description", documentUrl: ManifestURL); + + return new PackageLibrary + { + Name = name, + LocalJarPath = localJarPath, + Description = description + }; + } + + AndroidComponentInfo ParseExtraInfo(string name, string packageType, XElement element) + { + string vendorId = element.GetAttributeValue("vendor-id", documentUrl: ManifestURL); + string vendorDisplay = element.GetAttributeValue("vendor-display", documentUrl: ManifestURL); + + return new AndroidComponentInfoExtra(packageType, CreateVendor(vendorId, vendorDisplay)); + } + + PackageTag CreateTag(string id, string display) + { + if (String.IsNullOrEmpty(id)) + return null; + + return new PackageTag(id, display); + } + + PackageVendor CreateVendor(string id, string display) + { + if (String.IsNullOrEmpty(id)) + return null; + + return new PackageVendor(id, display); + } + + public override Uri GetFallbackManifestUrl() + { + Logger.Debug($"Fallback Android Manifest Version: {Constants.RequiredComponentVersions.AndroidManifestFeedVersion}"); + var dirPathParent = new Uri(typeof(XamarinRepository).Assembly.Location); + var manifestUri = new Uri(dirPathParent, $"AndroidManifestFeed_d{Constants.RequiredComponentVersions.AndroidManifestFeedVersion}.xml"); + + Logger.Debug($"Fallback Manifest Url:: manifestUri: {manifestUri}"); + return manifestUri; + } + } +} diff --git a/src/Xamarin.Installer.Build.Tasks/Properties/Resources.Designer.cs b/src/Xamarin.Installer.Build.Tasks/Properties/Resources.Designer.cs new file mode 100644 index 00000000000..3fe90ebc924 --- /dev/null +++ b/src/Xamarin.Installer.Build.Tasks/Properties/Resources.Designer.cs @@ -0,0 +1,172 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Xamarin.Installer.Build.Tasks.Properties { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Xamarin.Installer.Build.Tasks.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to Failed to locate Android SDK at path `{0}`.. + /// + internal static string AndroidSdk_Not_Found { + get { + return ResourceManager.GetString("AndroidSdk_Not_Found", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The path to the Android SDK has not been set. Either provide the '-p:AndroidSdkDirectory' MSbuild property with a valid path or set the 'ANDROID_HOME' or 'ANDROID_SDK_ROOT' environment variables and try again.. + /// + internal static string AndroidSdk_Not_Set { + get { + return ResourceManager.GetString("AndroidSdk_Not_Set", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Unable to install the Java SDK into `{0}`. Please set `$(JavaSdkDirectory)` to a valid non-administrator path.. + /// + internal static string Invalid_JavaSdkDirectory { + get { + return ResourceManager.GetString("Invalid_JavaSdkDirectory", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Detected an out of date Java SDK with version `{1}` at `{0}`, but we are unable to install the recommended Java SDK (version `{2}`) to this location. + ///Please update your Java SDK to version `{2}`, or set `$(JavaSdkDirectory)` to a valid non-administrator path to install the recommended Java SDK version to a new location. + /// + internal static string Invalid_JavaSdkDirectory_ExistingInstall { + get { + return ResourceManager.GetString("Invalid_JavaSdkDirectory_ExistingInstall", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Unable to find a Java SDK with version `{0}`. Please set `$(JavaSdkVersion)` to a valid version.. + /// + internal static string Invalid_JavaSdkVersion { + get { + return ResourceManager.GetString("Invalid_JavaSdkVersion", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The Android SDK license agreements were not accepted, please set `$(AcceptAndroidSDKLicenses)` to accept.. + /// + internal static string License_Not_Accepted { + get { + return ResourceManager.GetString("License_Not_Accepted", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to an error occurred trying to accept the Android SDK licenses. {0}. + /// + internal static string Licenses_Acceptance_Failed { + get { + return ResourceManager.GetString("Licenses_Acceptance_Failed", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Internal error: expected to install {0} components, but no downloads were available.. + /// + internal static string Required_Downloadable_Mismatch { + get { + return ResourceManager.GetString("Required_Downloadable_Mismatch", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Dependency `{0}` should have been installed but could not be resolved. You can attempt to install it with: `{1} --install "{0}"`. + /// + internal static string Required_Not_Installed { + get { + return ResourceManager.GetString("Required_Not_Installed", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Attempt to install required Android SDK components timed out or was cancelled. Consider setting `$(AndroidDependencyInstallationTimeout)` to a value larger than `{0}` minutes.. + /// + internal static string Task_Cancelled { + get { + return ResourceManager.GetString("Task_Cancelled", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Failed to install required Android SDK components.. + /// + internal static string Task_Failed { + get { + return ResourceManager.GetString("Task_Failed", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `$(AndroidManifestType)` value of `{0}` is not supported. Defaulting to `Xamarin`.. + /// + internal static string Unsupported_Manifest_Type { + get { + return ResourceManager.GetString("Unsupported_Manifest_Type", resourceCulture); + } + } + } +} diff --git a/src/Xamarin.Installer.Build.Tasks/Properties/Resources.cs.resx b/src/Xamarin.Installer.Build.Tasks/Properties/Resources.cs.resx new file mode 100644 index 00000000000..7fa9547b04f --- /dev/null +++ b/src/Xamarin.Installer.Build.Tasks/Properties/Resources.cs.resx @@ -0,0 +1,178 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Nebyla nastavena cesta k sadě Android SDK. Buď zadejte vlastnost MSbuild -p:AndroidSdkDirectory s platnou cestou, nebo nastavte proměnné prostředí ANDROID_HOME nebo ANDROID_SDK_ROOT a zkuste to znovu. + The following are literal names and should not be translated: `Android SDK`, `ANDROID_HOME`, `ANDROID_SDK_ROOT` and `-p:AndroidSdkDirectory` + + + Nepodařilo se najít Android SDK v cestě {0}. + The following are literal names and should not be translated: `Android SDK` +{0} - The path to the Android SDK + + + Licenční smlouvy Android SDK nebyly přijaty. Nastavte prosím $(AcceptAndroidSDKLicenses) tak, aby se přijaly. + The following are literal names and should not be translated: `Android SDK`, `$(AcceptAndroidSDKLicenses)` + + + Vnitřní chyba: Očekávala se instalace komponent {0}, ale nebylo nic staženo. + {0} - The number of SDK components that need to be installed + + + Závislost {0} by měla být nainstalována, ale nedá se přeložit. Můžete se ji pokusit nainstalovat pomocí příkazu {1} --install "{0}". + The following are literal names and should not be translated: `{1} --install "{0}"` +{0} - The dependency that was not installed +{1} - The path to the sdkmanager tool + + + Nepodařilo se nainstalovat požadované součásti Android SDK. + The following are literal names and should not be translated: `Android SDK` + + + Při pokusu o instalaci požadovaných komponent sady Android SDK vypršel časový limit, případně byl tento pokus zrušen. Zvažte nastavení $(AndroidDependencyInstallationTimeout) na hodnotu větší než {0} min. + The following are literal names and should not be translated: `Android SDK`, `$(AndroidDependencyInstallationTimeout)` +{0} - The timeout value for the task + + + $(AndroidManifestType) s hodnotou {0} se nepodporuje. Použije se výchozí nastavení Xamarin. + The following are literal names and should not be translated: `$(AndroidManifestType)`, `Xamarin` +{0} - The unsupported manifest name + + + Nepodařilo se nainstalovat sadu Java SDK {0}. Nastavte prosím $(JavaSdkDirectory) na platnou cestu bez oprávnění správce. + The following are literal names and should not be translated: `Java SDK`, `$(JavaSdkDirectory)`, `non-administrator` +{0} - The value of $(JavaSdkDirectory) that was provided to the task + + + Byla zjištěna zastaralá sada Java SDK s verzí {1} v adresáři {0}, ale do tohoto umístění nemůžeme nainstalovat doporučenou sadu Java SDK (verze {2}). +Pokud chcete nainstalovat doporučenou verzi sady Java SDK do nového umístění, aktualizujte prosím sadu Java SDK na verzi {2} nebo nastavte $(JavaSdkDirectory) na platnou cestu, která není určena pro správce. + The following are literal names and should not be translated: `Java SDK`, `$(JavaSdkDirectory)`, `non-administrator` +{0} - The value of $(JavaSdkDirectory) that was provided to the task +{1} - The major version of the the Java SDK that was already installed +{2} - The major version of the the Java SDK to install + + + Nepovedlo se najít sadu Java SDK s verzí {0}. Nastavte $(JavaSdkVersion) na platnou verzi. + The following are literal names and should not be translated: `Java SDK`, `$(JavaSdkVersion)` +{0} - The value of $(JavaSdkVersion) that was provided to the task + + + Při pokusu o přijetí licencí Android SDK došlo k chybě. {0} + + \ No newline at end of file diff --git a/src/Xamarin.Installer.Build.Tasks/Properties/Resources.de.resx b/src/Xamarin.Installer.Build.Tasks/Properties/Resources.de.resx new file mode 100644 index 00000000000..3c4ec9451f2 --- /dev/null +++ b/src/Xamarin.Installer.Build.Tasks/Properties/Resources.de.resx @@ -0,0 +1,178 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Der Pfad zum Android SDK wurde nicht festgelegt. Geben Sie die MSbuild-Eigenschaft "-p:AndroidSdkDirectory" mit einem gültigen Pfad an, oder legen Sie die Umgebungsvariablen "ANDROID_HOME" oder "ANDROID_SDK_ROOT" fest, und versuchen Sie es noch mal. + The following are literal names and should not be translated: `Android SDK`, `ANDROID_HOME`, `ANDROID_SDK_ROOT` and `-p:AndroidSdkDirectory` + + + Fehler beim Suchen der Android SDK im Pfad „{0}“. + The following are literal names and should not be translated: `Android SDK` +{0} - The path to the Android SDK + + + Die Android SDK-Lizenzverträge wurden nicht akzeptiert. Legen Sie zum Akzeptieren „$(AcceptAndroidSDKLicenses)“ fest. + The following are literal names and should not be translated: `Android SDK`, `$(AcceptAndroidSDKLicenses)` + + + Interner Fehler: Es wurde erwartet, dass {0} Komponenten installiert werden, es waren jedoch keine Downloads verfügbar. + {0} - The number of SDK components that need to be installed + + + Die Abhängigkeit „{0}“ hätte installiert werden sollen, konnte aber nicht aufgelöst werden. Sie können versuchen, die Installation mit folgendem Befehl auszuführen: „{1} --install "{0}"“. + The following are literal names and should not be translated: `{1} --install "{0}"` +{0} - The dependency that was not installed +{1} - The path to the sdkmanager tool + + + Fehler beim Installieren der erforderlichen Android SDK-Komponenten. + The following are literal names and should not be translated: `Android SDK` + + + Timeout oder Abbruch des Installationsversuchs für erforderliche Android SDK-Komponenten. Erwägen Sie, "$(AndroidDependencyInstallationTimeout)" auf einen Wert festzulegen, der größer als "{0}" Minuten ist. + The following are literal names and should not be translated: `Android SDK`, `$(AndroidDependencyInstallationTimeout)` +{0} - The timeout value for the task + + + Der Wert „$(AndroidManifestType)“ von „{0}“ wird nicht unterstützt. Der Standardwert ist „Xamarin“. + The following are literal names and should not be translated: `$(AndroidManifestType)`, `Xamarin` +{0} - The unsupported manifest name + + + Das Java SDK kann nicht in "{0}" installiert werden. Legen Sie "$(JavaSdkDirectory)" auf einen gültigen Nichtadministratorpfad fest. + The following are literal names and should not be translated: `Java SDK`, `$(JavaSdkDirectory)`, `non-administrator` +{0} - The value of $(JavaSdkDirectory) that was provided to the task + + + Es wurde ein veraltetes Java SDK mit Version `{1}` bei `{0}` erkannt, aber wir können das empfohlene Java SDK (Version `{2}`) nicht an diesem Speicherort installieren. +Aktualisieren Sie Ihr Java SDK auf Version `{2}`, oder legen Sie `$(JavaSdkDirectory)` auf einen gültigen Nichtadministratorpfad fest, um die empfohlene Java SDK-Version an einem neuen Speicherort zu installieren. + The following are literal names and should not be translated: `Java SDK`, `$(JavaSdkDirectory)`, `non-administrator` +{0} - The value of $(JavaSdkDirectory) that was provided to the task +{1} - The major version of the the Java SDK that was already installed +{2} - The major version of the the Java SDK to install + + + Es wurde kein Java SDK mit der Version "{0}" gefunden. Legen Sie "$(JavaSdkVersion)" auf eine gültige Version fest. + The following are literal names and should not be translated: `Java SDK`, `$(JavaSdkVersion)` +{0} - The value of $(JavaSdkVersion) that was provided to the task + + + Fehler beim Akzeptieren der Android SDK-Lizenzen. {0} + + \ No newline at end of file diff --git a/src/Xamarin.Installer.Build.Tasks/Properties/Resources.es.resx b/src/Xamarin.Installer.Build.Tasks/Properties/Resources.es.resx new file mode 100644 index 00000000000..d1d3d072854 --- /dev/null +++ b/src/Xamarin.Installer.Build.Tasks/Properties/Resources.es.resx @@ -0,0 +1,178 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + No se ha establecido la ruta de acceso al Android SDK. Proporcione la propiedad "-p:AndroidSdkDirectory" de MSbuild con una ruta de acceso válida o establezca las variables de entorno "ANDROID_HOME" o "ANDROID_SDK_ROOT" y vuelva a intentarlo. + The following are literal names and should not be translated: `Android SDK`, `ANDROID_HOME`, `ANDROID_SDK_ROOT` and `-p:AndroidSdkDirectory` + + + No se pudo encontrar Android SDK en la ruta de acceso '{0}'. + The following are literal names and should not be translated: `Android SDK` +{0} - The path to the Android SDK + + + No se aceptaron los contratos de licencia de Android SDK. Establezca '$(AcceptAndroidSDKLicenses)' para aceptar. + The following are literal names and should not be translated: `Android SDK`, `$(AcceptAndroidSDKLicenses)` + + + Error interno: se esperaba instalar {0} componentes, pero no hubo descargas disponibles. + {0} - The number of SDK components that need to be installed + + + La dependencia '{0}' debería haberse instalado, pero no se pudo resolver. Puede intentar instalarla con: '{1} --install "{0}"' + The following are literal names and should not be translated: `{1} --install "{0}"` +{0} - The dependency that was not installed +{1} - The path to the sdkmanager tool + + + No se pudieron instalar los componentes de Android SDK necesarios. + The following are literal names and should not be translated: `Android SDK` + + + Se agotó el tiempo de espera del intento de instalación de los componentes de Android SDK necesarios o se canceló. Considere la posibilidad de establecer "$(AndroidDependencyInstallationTimeout)" en un valor mayor que "{0}" minutos. + The following are literal names and should not be translated: `Android SDK`, `$(AndroidDependencyInstallationTimeout)` +{0} - The timeout value for the task + + + El valor '$(AndroidManifestType)' de '{0}' no se admite. El valor predeterminado es "Xamarin". + The following are literal names and should not be translated: `$(AndroidManifestType)`, `Xamarin` +{0} - The unsupported manifest name + + + No se pudo instalar el Java SDK en "{0}". Establezca "$(JavaSdkDirectory)" en una ruta de acceso que no sea de administrador válida. + The following are literal names and should not be translated: `Java SDK`, `$(JavaSdkDirectory)`, `non-administrator` +{0} - The value of $(JavaSdkDirectory) that was provided to the task + + + Se detectó un SDK de Java obsoleto con la versión "{1}" en "{0}", pero no se puede instalar el SDK de Java recomendado (versión "{2}") en esta ubicación. +Actualice el SDK de Java a la versión "{2}" o establezca "$(JavaSdkDirectory)" en una ruta de acceso válida que no sea de administrador para instalar la versión recomendada del SDK de Java en una nueva ubicación. + The following are literal names and should not be translated: `Java SDK`, `$(JavaSdkDirectory)`, `non-administrator` +{0} - The value of $(JavaSdkDirectory) that was provided to the task +{1} - The major version of the the Java SDK that was already installed +{2} - The major version of the the Java SDK to install + + + No se encuentra un Java SDK con la versión "{0}". Establezca "$(JavaSdkVersion)" en una versión válida. + The following are literal names and should not be translated: `Java SDK`, `$(JavaSdkVersion)` +{0} - The value of $(JavaSdkVersion) that was provided to the task + + + Se ha producido un error al intentar aceptar licencias de Android SDK. {0} + + \ No newline at end of file diff --git a/src/Xamarin.Installer.Build.Tasks/Properties/Resources.fr.resx b/src/Xamarin.Installer.Build.Tasks/Properties/Resources.fr.resx new file mode 100644 index 00000000000..89f20518461 --- /dev/null +++ b/src/Xamarin.Installer.Build.Tasks/Properties/Resources.fr.resx @@ -0,0 +1,178 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Le chemin d’accès au Android SDK n’a pas été défini. Indiquez la propriété MSbuild '-p :AndroidSdkDirectory' avec un chemin d’accès valide ou définissez les variables d’environnement 'ANDROID_HOME' ou 'ANDROID_SDK_ROOT', puis réessayez. + The following are literal names and should not be translated: `Android SDK`, `ANDROID_HOME`, `ANDROID_SDK_ROOT` and `-p:AndroidSdkDirectory` + + + Nous n’avons pas pu localiser Android SDK dans le chemin `{0}`. + The following are literal names and should not be translated: `Android SDK` +{0} - The path to the Android SDK + + + Les contrats de licence Android SDK n’ont pas été acceptés. Définissez `$(AcceptAndroidSDKLicenses)` pour accepter. + The following are literal names and should not be translated: `Android SDK`, `$(AcceptAndroidSDKLicenses)` + + + Erreur interne : installation attendue de {0} composants, mais aucun téléchargement n’était disponible. + {0} - The number of SDK components that need to be installed + + + La dépendance `{0}` aurait dû être installée, mais n’a pas pu être résolue. Vous pouvez essayer de l’installer avec : `{1} --install "{0}"`. + The following are literal names and should not be translated: `{1} --install "{0}"` +{0} - The dependency that was not installed +{1} - The path to the sdkmanager tool + + + Échec de l’installation des composants Android SDK requis. + The following are literal names and should not be translated: `Android SDK` + + + La tentative d’installation des composants Android SDK requis a expiré ou a été annulée. Envisagez de définir « $(AndroidDependencyInstallationTimeout) » sur une valeur supérieure à « {0} » minutes. + The following are literal names and should not be translated: `Android SDK`, `$(AndroidDependencyInstallationTimeout)` +{0} - The timeout value for the task + + + La valeur `$(AndroidManifestType)` de `{0}` n’est pas prise en charge. La valeur par défaut est `Xamarin`. + The following are literal names and should not be translated: `$(AndroidManifestType)`, `Xamarin` +{0} - The unsupported manifest name + + + Impossible d’installer le Kit de développement logiciel (SDK) Java dans « {0} ». Veuillez définir « $(JavaSdkDirectory) » sur un chemin d’accès non administrateur valide. + The following are literal names and should not be translated: `Java SDK`, `$(JavaSdkDirectory)`, `non-administrator` +{0} - The value of $(JavaSdkDirectory) that was provided to the task + + + Un Kit de développement logiciel (SDK) Java obsolète avec la version « {1} » a été détecté à « {0} », mais nous ne pouvons pas installer le Kit de développement logiciel (SDK) Java recommandé (version « {2} ») à cet emplacement. +Mettez à jour votre Kit de développement logiciel (SDK) Java vers la version « {2} », ou définissez « $(JavaSdkDirectory) » sur un chemin d’accès non administrateur(-trice) valide pour installer la version recommandée du Kit de développement logiciel (SDK) Java à un nouvel emplacement. + The following are literal names and should not be translated: `Java SDK`, `$(JavaSdkDirectory)`, `non-administrator` +{0} - The value of $(JavaSdkDirectory) that was provided to the task +{1} - The major version of the the Java SDK that was already installed +{2} - The major version of the the Java SDK to install + + + Impossible de trouver un SDK Java avec la version « {0} ». Veuillez définir « $(JavaSdkVersion) » sur une version valide. + The following are literal names and should not be translated: `Java SDK`, `$(JavaSdkVersion)` +{0} - The value of $(JavaSdkVersion) that was provided to the task + + + Désolé, une erreur s’est produite lors de la tentative d’acceptation des licences Android SDK. {0} + + \ No newline at end of file diff --git a/src/Xamarin.Installer.Build.Tasks/Properties/Resources.it.resx b/src/Xamarin.Installer.Build.Tasks/Properties/Resources.it.resx new file mode 100644 index 00000000000..0e4c0cc26a0 --- /dev/null +++ b/src/Xamarin.Installer.Build.Tasks/Properties/Resources.it.resx @@ -0,0 +1,178 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Il percorso per Android SDK non è stato impostato. Specificare la proprietà MSbuild "-p:AndroidSdkDirectory" con un percorso valido oppure impostare le variabili di ambiente "ANDROID_HOME" o "ANDROID_SDK_ROOT" e riprovare. + The following are literal names and should not be translated: `Android SDK`, `ANDROID_HOME`, `ANDROID_SDK_ROOT` and `-p:AndroidSdkDirectory` + + + Non è possibile individuare Android SDK nel percorso `{0}`. + The following are literal names and should not be translated: `Android SDK` +{0} - The path to the Android SDK + + + I contratti di licenza di Android SDK non sono stati accettati. Impostare `$(AcceptAndroidSDKLicenses)` su accetta. + The following are literal names and should not be translated: `Android SDK`, `$(AcceptAndroidSDKLicenses)` + + + Errore interno: è prevista l'installazione dei componenti {0}, ma non ci sono download disponibili. + {0} - The number of SDK components that need to be installed + + + La dipendenza `{0}` dovrebbe essere stata installata ma non è stato possibile risolverla. È possibile provare a installarla con: `{1} --install "{0}"` + The following are literal names and should not be translated: `{1} --install "{0}"` +{0} - The dependency that was not installed +{1} - The path to the sdkmanager tool + + + Non è stato possibile installare i componenti Android SDK necessari. + The following are literal names and should not be translated: `Android SDK` + + + Il tentativo di installare i componenti Android SDK necessari ha raggiunto il timeout o è stato annullato. Provare a impostare `$(AndroidDependencyInstallationTimeout)` su un valore maggiore di `{0}` minuti. + The following are literal names and should not be translated: `Android SDK`, `$(AndroidDependencyInstallationTimeout)` +{0} - The timeout value for the task + + + Il valore `$(AndroidManifestType)` di `{0}` non è supportato. L'impostazione predefinita è `Xamarin`. + The following are literal names and should not be translated: `$(AndroidManifestType)`, `Xamarin` +{0} - The unsupported manifest name + + + Non è possibile installare Java SDK in `{0}`. Impostare '$(JavaSdkDirectory)' in un percorso non amministratore valido. + The following are literal names and should not be translated: `Java SDK`, `$(JavaSdkDirectory)`, `non-administrator` +{0} - The value of $(JavaSdkDirectory) that was provided to the task + + + È stato rilevato un SDK Java non aggiornato con versione “{1}” in “{0}”, ma non è possibile installare l'SDK Java consigliato (versione “{2}”) in questo percorso. +Aggiornare Java SDK alla versione “{2}”oppure impostare “$(JavaSdkDirectory)” su un percorso non amministratore valido per installare la versione consigliata di Java SDK in un nuovo percorso. + The following are literal names and should not be translated: `Java SDK`, `$(JavaSdkDirectory)`, `non-administrator` +{0} - The value of $(JavaSdkDirectory) that was provided to the task +{1} - The major version of the the Java SDK that was already installed +{2} - The major version of the the Java SDK to install + + + Non è possibile trovare un SDK Java con la versione '{0}'. Impostare `$(JavaSdkVersion)` su una versione valida. + The following are literal names and should not be translated: `Java SDK`, `$(JavaSdkVersion)` +{0} - The value of $(JavaSdkVersion) that was provided to the task + + + si è verificato un errore durante il tentativo di accettare le licenze di Android SDK. {0} + + \ No newline at end of file diff --git a/src/Xamarin.Installer.Build.Tasks/Properties/Resources.ja.resx b/src/Xamarin.Installer.Build.Tasks/Properties/Resources.ja.resx new file mode 100644 index 00000000000..4ba60a8324f --- /dev/null +++ b/src/Xamarin.Installer.Build.Tasks/Properties/Resources.ja.resx @@ -0,0 +1,178 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Android SDK へのパスが設定されていません。'-p:AndroidSdkDirectory' MSbuild プロパティに有効なパスを指定するか、'ANDROID_HOME' または 'ANDROID_SDK_ROOT' 環境変数を設定してから、もう一度お試しください。 + The following are literal names and should not be translated: `Android SDK`, `ANDROID_HOME`, `ANDROID_SDK_ROOT` and `-p:AndroidSdkDirectory` + + + パス `{0}` で Android SDK が見つかりませんでした。 + The following are literal names and should not be translated: `Android SDK` +{0} - The path to the Android SDK + + + Android SDK ライセンス契約が承諾されませんでした。同意するには `$(AcceptAndroidSDKLicenses)` を設定してください。 + The following are literal names and should not be translated: `Android SDK`, `$(AcceptAndroidSDKLicenses)` + + + 内部エラー: {0} 個のコンポーネントをインストールする必要がありましたが、ダウンロードできませんでした。 + {0} - The number of SDK components that need to be installed + + + 依存関係 '{0}' がインストールされているはずですが、解決できませんでした。'{1} --install "{0}"' でインストールを試みることができます。 + The following are literal names and should not be translated: `{1} --install "{0}"` +{0} - The dependency that was not installed +{1} - The path to the sdkmanager tool + + + 必要な Android SDK コンポーネントをインストールできませんでした。 + The following are literal names and should not be translated: `Android SDK` + + + 必要な Android SDK コンポーネントのインストール試行がタイムアウトしたか、取り消されました。'$(AndroidDependencyInstallationTimeout)' を '{0}' 分より大きい値に設定することを検討してください。 + The following are literal names and should not be translated: `Android SDK`, `$(AndroidDependencyInstallationTimeout)` +{0} - The timeout value for the task + + + `{0}` の `$(AndroidManifestType)` 値はサポートされていません。既定値の `Xamarin` になります。 + The following are literal names and should not be translated: `$(AndroidManifestType)`, `Xamarin` +{0} - The unsupported manifest name + + + Java SDK を '{0}' にインストールできません。'$(JavaSdkDirectory)' を有効な管理者以外のパスに設定してください。 + The following are literal names and should not be translated: `Java SDK`, `$(JavaSdkDirectory)`, `non-administrator` +{0} - The value of $(JavaSdkDirectory) that was provided to the task + + + バージョン `{1}` の古い Java SDK が `{0}` で検出されましたが、推奨される Java SDK (バージョン `{2}`) をこの場所にインストールできません。 +Java SDK をバージョン `{2}` に更新するか、`$(JavaSdkDirectory)` を有効な管理者以外のパスに設定して、推奨される Java SDK バージョンを新しい場所にインストールしてください。 + The following are literal names and should not be translated: `Java SDK`, `$(JavaSdkDirectory)`, `non-administrator` +{0} - The value of $(JavaSdkDirectory) that was provided to the task +{1} - The major version of the the Java SDK that was already installed +{2} - The major version of the the Java SDK to install + + + バージョン '{0}' の Java SDK が見つかりません。'$(JavaSdkVersion)' を有効なバージョンに設定してください。 + The following are literal names and should not be translated: `Java SDK`, `$(JavaSdkVersion)` +{0} - The value of $(JavaSdkVersion) that was provided to the task + + + Android SDK ライセンスを承諾しようとしてエラーが発生しました。{0} + + \ No newline at end of file diff --git a/src/Xamarin.Installer.Build.Tasks/Properties/Resources.ko.resx b/src/Xamarin.Installer.Build.Tasks/Properties/Resources.ko.resx new file mode 100644 index 00000000000..f678445a8b9 --- /dev/null +++ b/src/Xamarin.Installer.Build.Tasks/Properties/Resources.ko.resx @@ -0,0 +1,178 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Android SDK 경로가 설정되지 않았습니다. '-p:AndroidSdkDirectory' MSbuild 속성에 유효한 경로를 제공하거나 'ANDROID_HOME' 또는 'ANDROID_SDK_ROOT' 환경 변수를 설정하고 다시 시도하세요. + The following are literal names and should not be translated: `Android SDK`, `ANDROID_HOME`, `ANDROID_SDK_ROOT` and `-p:AndroidSdkDirectory` + + + 경로 `{0}`에서 Android SDK를 찾지 못했습니다. + The following are literal names and should not be translated: `Android SDK` +{0} - The path to the Android SDK + + + Android SDK 라이선스 계약이 수락되지 않았습니다. `$(AcceptAndroidSDKLicenses)`을(를) 수락하도록 설정하세요. + The following are literal names and should not be translated: `Android SDK`, `$(AcceptAndroidSDKLicenses)` + + + 내부 오류: {0} 구성 요소를 설치해야 하지만 다운로드할 수 없습니다. + {0} - The number of SDK components that need to be installed + + + 종속성 `{0}`이(가) 설치되어 있어야 하지만 해결할 수 없습니다. 다음을 사용하여 설치할 수 있습니다. `{1} --install "{0}"` + The following are literal names and should not be translated: `{1} --install "{0}"` +{0} - The dependency that was not installed +{1} - The path to the sdkmanager tool + + + 필요한 Android SDK 구성 요소를 설치하지 못했습니다. + The following are literal names and should not be translated: `Android SDK` + + + 필요한 Android SDK 구성 요소 설치 시도가 시간 초과되었거나 취소되었습니다. `$(AndroidDependencyInstallationTimeout)`을 `{0}`분보다 큰 값으로 설정하는 것이 좋습니다. + The following are literal names and should not be translated: `Android SDK`, `$(AndroidDependencyInstallationTimeout)` +{0} - The timeout value for the task + + + `{0}`의 `$(AndroidManifestType)` 값은 지원되지 않습니다. 기본값은 `Xamarin`입니다. + The following are literal names and should not be translated: `$(AndroidManifestType)`, `Xamarin` +{0} - The unsupported manifest name + + + Java SDK를 `{0}`에 설치할 수 없습니다. `$(JavaSdkDirectory)`를 유효한 비관리자 경로로 설정하세요. + The following are literal names and should not be translated: `Java SDK`, `$(JavaSdkDirectory)`, `non-administrator` +{0} - The value of $(JavaSdkDirectory) that was provided to the task + + + `{0}`에서 `{1}` 버전의 오래된 Java SDK를 감지했지만 이 위치에 권장 Java SDK(`{2}` 버전)를 설치할 수 없습니다. +Java SDK를 `{2}` 버전으로 업데이트하거나 `$(JavaSdkDirectory)`를 관리자가 아닌 올바른 경로로 설정하여 권장되는 Java SDK 버전을 새 위치에 설치하세요. + The following are literal names and should not be translated: `Java SDK`, `$(JavaSdkDirectory)`, `non-administrator` +{0} - The value of $(JavaSdkDirectory) that was provided to the task +{1} - The major version of the the Java SDK that was already installed +{2} - The major version of the the Java SDK to install + + + 버전이 `{0}`인 Java SDK를 찾을 수 없습니다. `$(JavaSdkVersion)`을 유효한 버전으로 설정하세요. + The following are literal names and should not be translated: `Java SDK`, `$(JavaSdkVersion)` +{0} - The value of $(JavaSdkVersion) that was provided to the task + + + Android SDK 라이선스를 수락하는 동안 오류가 발생했습니다. {0} + + \ No newline at end of file diff --git a/src/Xamarin.Installer.Build.Tasks/Properties/Resources.pl.resx b/src/Xamarin.Installer.Build.Tasks/Properties/Resources.pl.resx new file mode 100644 index 00000000000..b7eb5032a89 --- /dev/null +++ b/src/Xamarin.Installer.Build.Tasks/Properties/Resources.pl.resx @@ -0,0 +1,178 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Ścieżka do zestawu Android SDK nie została ustawiona. Podaj właściwość MSbuild „-p:AndroidSdkDirectory” z prawidłową ścieżką lub ustaw zmienne środowiskowe „ANDROID_HOME” lub „ANDROID_SDK_ROOT” i spróbuj ponownie. + The following are literal names and should not be translated: `Android SDK`, `ANDROID_HOME`, `ANDROID_SDK_ROOT` and `-p:AndroidSdkDirectory` + + + Nie można zlokalizować zestawu Android SDK w ścieżce „{0}”. + The following are literal names and should not be translated: `Android SDK` +{0} - The path to the Android SDK + + + Umowy licencyjne zestawu Android SDK nie zostały zaakceptowane. Ustaw wartość „$(AcceptAndroidSDKLicenses)”, aby zaakceptować. + The following are literal names and should not be translated: `Android SDK`, `$(AcceptAndroidSDKLicenses)` + + + Błąd wewnętrzny: oczekiwano zainstalowania składników {0}, ale nie było dostępnych plików do pobrania. + {0} - The number of SDK components that need to be installed + + + Zależność „{0}” powinna być zainstalowana, ale nie można jej rozpoznać. Możesz spróbować zainstalować ją za pomocą `{1} --install "{0}"` + The following are literal names and should not be translated: `{1} --install "{0}"` +{0} - The dependency that was not installed +{1} - The path to the sdkmanager tool + + + Nie można zainstalować wymaganych składników zestawu Android SDK. + The following are literal names and should not be translated: `Android SDK` + + + Próba zainstalowania wymaganych składników zestawu Android SDK przekroczyła limit czasu lub została anulowana. Rozważ ustawienie wartości limitu '$(AndroidDependencyInstallationTimeout)' na wartość większą niż „{0}” min. + The following are literal names and should not be translated: `Android SDK`, `$(AndroidDependencyInstallationTimeout)` +{0} - The timeout value for the task + + + Wartość „$(AndroidManifestType)” elementu „{0}” nie jest obsługiwana. Domyślnie jest to „Xamarin”. + The following are literal names and should not be translated: `$(AndroidManifestType)`, `Xamarin` +{0} - The unsupported manifest name + + + Nie można zainstalować zestawu Java SDK w katalogu „{0}”. Ustaw prawidłową ścieżkę nieadministracyjną dla katalogu „$(JavaSdkDirectory)”. + The following are literal names and should not be translated: `Java SDK`, `$(JavaSdkDirectory)`, `non-administrator` +{0} - The value of $(JavaSdkDirectory) that was provided to the task + + + Wykryto nieaktualny zestaw Java SDK w wersji „{1}” w lokalizacji „{0}”, ale nie możemy zainstalować rekomendowanego zestawu Java SDK (wersja „{2}”) w tej lokalizacji. +Zaktualizuj zestaw Java SDK do wersji „{2}” lub ustaw „$(JavaSdkDirectory)” na prawidłową ścieżkę dla użytkowników innych niż administrator, aby zainstalować rekomendowaną wersję zestawu Java SDK w nowej lokalizacji. + The following are literal names and should not be translated: `Java SDK`, `$(JavaSdkDirectory)`, `non-administrator` +{0} - The value of $(JavaSdkDirectory) that was provided to the task +{1} - The major version of the the Java SDK that was already installed +{2} - The major version of the the Java SDK to install + + + Nie można odnaleźć zestawu Java SDK z wersją „{0}”. Ustaw prawidłową wersję zestawu „$(JavaSdkVersion)”. + The following are literal names and should not be translated: `Java SDK`, `$(JavaSdkVersion)` +{0} - The value of $(JavaSdkVersion) that was provided to the task + + + wystąpił błąd podczas próby zaakceptowania licencji zestawu Android SDK. {0} + + \ No newline at end of file diff --git a/src/Xamarin.Installer.Build.Tasks/Properties/Resources.pt-BR.resx b/src/Xamarin.Installer.Build.Tasks/Properties/Resources.pt-BR.resx new file mode 100644 index 00000000000..ef5e1bd7680 --- /dev/null +++ b/src/Xamarin.Installer.Build.Tasks/Properties/Resources.pt-BR.resx @@ -0,0 +1,178 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + O caminho para o SDK do Android não foi definido. Forneça a propriedade MSbuild "-p:AndroidSdkDirectory" com um caminho válido ou defina as variáveis de ambiente "ANDROID_HOME" ou "ANDROID_SDK_ROOT" e tente novamente. + The following are literal names and should not be translated: `Android SDK`, `ANDROID_HOME`, `ANDROID_SDK_ROOT` and `-p:AndroidSdkDirectory` + + + Falha ao localizar o SDK do Android no caminho `{0}`. + The following are literal names and should not be translated: `Android SDK` +{0} - The path to the Android SDK + + + Os contratos de licença do SDK do Android não foram aceitos, defina `$(AcceptAndroidSDKLicenses)` para aceitar. + The following are literal names and should not be translated: `Android SDK`, `$(AcceptAndroidSDKLicenses)` + + + Erro interno: espera-se que instale {0} componentes, mas não há downloads disponíveis. + {0} - The number of SDK components that need to be installed + + + A dependência `{0}` deveria ter sido instalada mas não pôde ser resolvida. Você pode tentar instalá-lo com: `{1} --install "{0}"` + The following are literal names and should not be translated: `{1} --install "{0}"` +{0} - The dependency that was not installed +{1} - The path to the sdkmanager tool + + + Falha ao instalar os componentes necessários do SDK do Android. + The following are literal names and should not be translated: `Android SDK` + + + A tentativa de instalar os componentes necessários do SDK do Android expirou ou foi cancelada. Considere a possibilidade de definir `$(AndroidDependencyInstallationTimeout)` para um valor maior que `{0}` minutos. + The following are literal names and should not be translated: `Android SDK`, `$(AndroidDependencyInstallationTimeout)` +{0} - The timeout value for the task + + + O valor `$(AndroidManifestType)` de `{0}` não é suportado. O padrão é `Xamarin`. + The following are literal names and should not be translated: `$(AndroidManifestType)`, `Xamarin` +{0} - The unsupported manifest name + + + Não foi possível instalar o SDK do Java em `{0}`. Defina `$(JavaSdkDirectory)` como um caminho válido que não seja do administrador. + The following are literal names and should not be translated: `Java SDK`, `$(JavaSdkDirectory)`, `non-administrator` +{0} - The value of $(JavaSdkDirectory) that was provided to the task + + + Foi detectado um SDK do Java desatualizado com a versão `{1}` em `{0}`, mas não é possível instalar o SDK do Java recomendado (versão `{2}`) neste local. +Atualize o SDK do Java para a versão `{2}` ou defina `$(JavaSdkDirectory)` como um caminho não de administrador válido para instalar a versão recomendada do SDK do Java para um novo local. + The following are literal names and should not be translated: `Java SDK`, `$(JavaSdkDirectory)`, `non-administrator` +{0} - The value of $(JavaSdkDirectory) that was provided to the task +{1} - The major version of the the Java SDK that was already installed +{2} - The major version of the the Java SDK to install + + + Não foi possível encontrar um SDK do Java com a versão `{0}`. Defina `$(JavaSdkVersion)` para uma versão válida. + The following are literal names and should not be translated: `Java SDK`, `$(JavaSdkVersion)` +{0} - The value of $(JavaSdkVersion) that was provided to the task + + + ocorreu um erro ao tentar aceitar as licenças do SDK do Android. {0} + + \ No newline at end of file diff --git a/src/Xamarin.Installer.Build.Tasks/Properties/Resources.resx b/src/Xamarin.Installer.Build.Tasks/Properties/Resources.resx new file mode 100644 index 00000000000..b1f5a0f1612 --- /dev/null +++ b/src/Xamarin.Installer.Build.Tasks/Properties/Resources.resx @@ -0,0 +1,178 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + The path to the Android SDK has not been set. Either provide the '-p:AndroidSdkDirectory' MSbuild property with a valid path or set the 'ANDROID_HOME' or 'ANDROID_SDK_ROOT' environment variables and try again. + The following are literal names and should not be translated: `Android SDK`, `ANDROID_HOME`, `ANDROID_SDK_ROOT` and `-p:AndroidSdkDirectory` + + + Failed to locate Android SDK at path `{0}`. + The following are literal names and should not be translated: `Android SDK` +{0} - The path to the Android SDK + + + The Android SDK license agreements were not accepted, please set `$(AcceptAndroidSDKLicenses)` to accept. + The following are literal names and should not be translated: `Android SDK`, `$(AcceptAndroidSDKLicenses)` + + + Internal error: expected to install {0} components, but no downloads were available. + {0} - The number of SDK components that need to be installed + + + Dependency `{0}` should have been installed but could not be resolved. You can attempt to install it with: `{1} --install "{0}"` + The following are literal names and should not be translated: `{1} --install "{0}"` +{0} - The dependency that was not installed +{1} - The path to the sdkmanager tool + + + Failed to install required Android SDK components. + The following are literal names and should not be translated: `Android SDK` + + + Attempt to install required Android SDK components timed out or was cancelled. Consider setting `$(AndroidDependencyInstallationTimeout)` to a value larger than `{0}` minutes. + The following are literal names and should not be translated: `Android SDK`, `$(AndroidDependencyInstallationTimeout)` +{0} - The timeout value for the task + + + `$(AndroidManifestType)` value of `{0}` is not supported. Defaulting to `Xamarin`. + The following are literal names and should not be translated: `$(AndroidManifestType)`, `Xamarin` +{0} - The unsupported manifest name + + + Unable to install the Java SDK into `{0}`. Please set `$(JavaSdkDirectory)` to a valid non-administrator path. + The following are literal names and should not be translated: `Java SDK`, `$(JavaSdkDirectory)`, `non-administrator` +{0} - The value of $(JavaSdkDirectory) that was provided to the task + + + Detected an out of date Java SDK with version `{1}` at `{0}`, but we are unable to install the recommended Java SDK (version `{2}`) to this location. +Please update your Java SDK to version `{2}`, or set `$(JavaSdkDirectory)` to a valid non-administrator path to install the recommended Java SDK version to a new location. + The following are literal names and should not be translated: `Java SDK`, `$(JavaSdkDirectory)`, `non-administrator` +{0} - The value of $(JavaSdkDirectory) that was provided to the task +{1} - The major version of the the Java SDK that was already installed +{2} - The major version of the the Java SDK to install + + + Unable to find a Java SDK with version `{0}`. Please set `$(JavaSdkVersion)` to a valid version. + The following are literal names and should not be translated: `Java SDK`, `$(JavaSdkVersion)` +{0} - The value of $(JavaSdkVersion) that was provided to the task + + + an error occurred trying to accept the Android SDK licenses. {0} + + \ No newline at end of file diff --git a/src/Xamarin.Installer.Build.Tasks/Properties/Resources.ru.resx b/src/Xamarin.Installer.Build.Tasks/Properties/Resources.ru.resx new file mode 100644 index 00000000000..2317bd502a8 --- /dev/null +++ b/src/Xamarin.Installer.Build.Tasks/Properties/Resources.ru.resx @@ -0,0 +1,178 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Путь к пакету SDK для Android не задан. Укажите свойство MSBuild "-p:AndroidSdkDirectory" с допустимым путем или задайте переменные среды "ANDROID_HOME" либо "ANDROID_SDK_ROOT", после чего повторите попытку. + The following are literal names and should not be translated: `Android SDK`, `ANDROID_HOME`, `ANDROID_SDK_ROOT` and `-p:AndroidSdkDirectory` + + + Не удалось обнаружить пакет SDK для Android по пути "{0}". + The following are literal names and should not be translated: `Android SDK` +{0} - The path to the Android SDK + + + Лицензионные соглашения на пакет SDK для Android не приняты. Для принятия установите параметр "$(AcceptAndroidSDKLicenses)". + The following are literal names and should not be translated: `Android SDK`, `$(AcceptAndroidSDKLicenses)` + + + Внутренняя ошибка: ожидается установка компонентов {0}, но загрузки недоступны. + {0} - The number of SDK components that need to be installed + + + Зависимость "{0}" должна была быть установлена, но ее не удалось разрешить. Вы можете попытаться установить ее с помощью: "{1} --install "{0}"" + The following are literal names and should not be translated: `{1} --install "{0}"` +{0} - The dependency that was not installed +{1} - The path to the sdkmanager tool + + + Не удалось установить необходимые компоненты пакета SDK для Android. + The following are literal names and should not be translated: `Android SDK` + + + Время ожидания попытки установки требуемых компонентов пакета SDK для Android истекло, либо она была отменена. Настройте для параметра "$(AndroidDependencyInstallationTimeout)" значение, большее, чем "{0}" мин. + The following are literal names and should not be translated: `Android SDK`, `$(AndroidDependencyInstallationTimeout)` +{0} - The timeout value for the task + + + Значение "$(AndroidManifestType)" "{0}" не поддерживается. По умолчанию задается значение "Xamarin". + The following are literal names and should not be translated: `$(AndroidManifestType)`, `Xamarin` +{0} - The unsupported manifest name + + + Не удалось установить пакет SDK Java в "{0}". Настройте для параметра "$(JavaSdkDirectory)" допустимый путь без прав администратора. + The following are literal names and should not be translated: `Java SDK`, `$(JavaSdkDirectory)`, `non-administrator` +{0} - The value of $(JavaSdkDirectory) that was provided to the task + + + Обнаружен устаревший пакет SDK для Java с версией "{1}" в "{0}", но не удалось установить рекомендуемый пакет SDK для Java (версия "{2}") в этом расположении. +Обновите свой пакет SDK для Java до версии "{2}" или настройте для "$(JavaSdkDirectory)" допустимый неадминистративный путь, чтобы установить рекомендуемую версию пакета SDK для Java в новое расположение. + The following are literal names and should not be translated: `Java SDK`, `$(JavaSdkDirectory)`, `non-administrator` +{0} - The value of $(JavaSdkDirectory) that was provided to the task +{1} - The major version of the the Java SDK that was already installed +{2} - The major version of the the Java SDK to install + + + Не удалось найти пакет SDK Java с версией "{0}". Настройте для параметра "$(JavaSdkVersion)" допустимую версию. + The following are literal names and should not be translated: `Java SDK`, `$(JavaSdkVersion)` +{0} - The value of $(JavaSdkVersion) that was provided to the task + + + произошла ошибка при попытке принять лицензии пакета SDK для Android. {0} + + \ No newline at end of file diff --git a/src/Xamarin.Installer.Build.Tasks/Properties/Resources.tr.resx b/src/Xamarin.Installer.Build.Tasks/Properties/Resources.tr.resx new file mode 100644 index 00000000000..0e2d9da92b4 --- /dev/null +++ b/src/Xamarin.Installer.Build.Tasks/Properties/Resources.tr.resx @@ -0,0 +1,178 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Android SDK’sının yolu ayarlanmadı. Geçerli bir yol içeren '-p:AndroidSdkDirectory' MSbuild özelliğini sağlayın ya da 'ANDROID_HOME' veya 'ANDROID_SDK_ROOT' bağımsız değişkenini ayarlayın ve yeniden deneyin. + The following are literal names and should not be translated: `Android SDK`, `ANDROID_HOME`, `ANDROID_SDK_ROOT` and `-p:AndroidSdkDirectory` + + + `{0}` yolunda Android SDK bulunamadı. + The following are literal names and should not be translated: `Android SDK` +{0} - The path to the Android SDK + + + Android SDK lisans sözleşmeleri kabul edilmedi, lütfen `$(AcceptAndroidSDKLicenses)` seçeneğini kabul et olarak ayarlayın. + The following are literal names and should not be translated: `Android SDK`, `$(AcceptAndroidSDKLicenses)` + + + İç hata: {0} bileşenin yüklenmesi bekleniyordu, ancak kullanılabilir indirme yoktu. + {0} - The number of SDK components that need to be installed + + + `{0}` bağımlılığı yüklenmiş olmalıdır, ancak çözümlenemedi. `{1} --install "{0}"` ile yüklemeyi deneyebilirsiniz + The following are literal names and should not be translated: `{1} --install "{0}"` +{0} - The dependency that was not installed +{1} - The path to the sdkmanager tool + + + Gerekli Android SDK bileşenlerinin yüklenemedi. + The following are literal names and should not be translated: `Android SDK` + + + Gerekli Android SDK bileşenlerini yükleme girişimi zaman aşımına uğradı veya iptal edildi. `$(AndroidDependencyInstallationTimeout)` seçeneğini `{0}` dakikadan büyük bir değere ayarlamayı deneyin. + The following are literal names and should not be translated: `Android SDK`, `$(AndroidDependencyInstallationTimeout)` +{0} - The timeout value for the task + + + `{0}` öğesinin `$(AndroidManifestType)` değeri desteklenmiyor. Varsayılan olarak `Xamarin` değerine ayarlanıyor. + The following are literal names and should not be translated: `$(AndroidManifestType)`, `Xamarin` +{0} - The unsupported manifest name + + + Java SDK `{0}` dizinine yüklenemedi. Lütfen '$(JavaSdkDirectory)' dizinini geçerli bir non-administrator yola ayarlayın. + The following are literal names and should not be translated: `Java SDK`, `$(JavaSdkDirectory)`, `non-administrator` +{0} - The value of $(JavaSdkDirectory) that was provided to the task + + + '{1}' sürümünde '{0}' sürümüne sahip güncel olmayan bir Java SDK'sı algılandı ancak önerilen Java SDK'sı (sürüm '{2}') bu konuma yüklenemedi. +Lütfen Java SDK'nızı '{2}' sürümüne güncelleştirin veya önerilen Java SDK sürümünü yeni bir konuma yüklemek için '$(JavaSdkDirectory)' öğesini geçerli bir yönetici olmayan yola ayarlayın. + The following are literal names and should not be translated: `Java SDK`, `$(JavaSdkDirectory)`, `non-administrator` +{0} - The value of $(JavaSdkDirectory) that was provided to the task +{1} - The major version of the the Java SDK that was already installed +{2} - The major version of the the Java SDK to install + + + '{0}' sürümüne sahip bir Java SDK bulunamıyor. Lütfen '$(JavaSdkVersion)' sürümünü geçerli bir sürüme ayarlayın. + The following are literal names and should not be translated: `Java SDK`, `$(JavaSdkVersion)` +{0} - The value of $(JavaSdkVersion) that was provided to the task + + + Android SDK lisansları kabul edilmeye çalışılırken bir hata oluştu. {0} + + \ No newline at end of file diff --git a/src/Xamarin.Installer.Build.Tasks/Properties/Resources.zh-Hans.resx b/src/Xamarin.Installer.Build.Tasks/Properties/Resources.zh-Hans.resx new file mode 100644 index 00000000000..7f108fef1cd --- /dev/null +++ b/src/Xamarin.Installer.Build.Tasks/Properties/Resources.zh-Hans.resx @@ -0,0 +1,178 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 尚未设置指向 Android SDK 的路径。请为“-p:AndroidSdkDirectory”MSbuild 属性提供有效路径,或设置“ANDROID_HOME”或“ANDROID_SDK_ROOT”环境变量,然后重试。 + The following are literal names and should not be translated: `Android SDK`, `ANDROID_HOME`, `ANDROID_SDK_ROOT` and `-p:AndroidSdkDirectory` + + + 无法在路径“{0}”下找到 Android SDK。 + The following are literal names and should not be translated: `Android SDK` +{0} - The path to the Android SDK + + + 未接受 Android SDK 许可协议,请将“$(AcceptAndroidSDKLicenses)”设置为接受。 + The following are literal names and should not be translated: `Android SDK`, `$(AcceptAndroidSDKLicenses)` + + + 内部错误: 预期安装 {0} 个组件,但没有可用的下载。 + {0} - The number of SDK components that need to be installed + + + 依赖项“{0}”应该已安装,但无法解析。你可以尝试使用以下命令进行安装:“{1} --install "{0}"” + The following are literal names and should not be translated: `{1} --install "{0}"` +{0} - The dependency that was not installed +{1} - The path to the sdkmanager tool + + + 无法安装所需的 Android SDK 组件。 + The following are literal names and should not be translated: `Android SDK` + + + 安装所需 Android SDK 组件的尝试已超时或已取消。请考虑将 `$(AndroidDependencyInstallationTimeout)` 设置为大于 `{0}` 分钟的值。 + The following are literal names and should not be translated: `Android SDK`, `$(AndroidDependencyInstallationTimeout)` +{0} - The timeout value for the task + + + “{0}”的“$(AndroidManifestType)”值不受支持。默认为“Xamarin”。 + The following are literal names and should not be translated: `$(AndroidManifestType)`, `Xamarin` +{0} - The unsupported manifest name + + + 无法将 Java SDK 安装到 `{0}` 中。请将 `$(JavaSdkDirectory)` 设置为有效的非管理员路径。 + The following are literal names and should not be translated: `Java SDK`, `$(JavaSdkDirectory)`, `non-administrator` +{0} - The value of $(JavaSdkDirectory) that was provided to the task + + + 在 `{0}` 处检测到版本为 `{1}` 的过期 Java SDK,但我们无法将推荐的 Java SDK (版本 `{2}`)安装到此位置。 +请将 Java SDK 更新为版本 `{2}`,或将 `$(JavaSdkDirectory)` 设置为有效的非管理员路径,以便将建议的 Java SDK 版本安装到新位置。 + The following are literal names and should not be translated: `Java SDK`, `$(JavaSdkDirectory)`, `non-administrator` +{0} - The value of $(JavaSdkDirectory) that was provided to the task +{1} - The major version of the the Java SDK that was already installed +{2} - The major version of the the Java SDK to install + + + 找不到版本为 `{0}` 的 Java SDK。请将 `$(JavaSdkVersion)` 设置为有效版本。 + The following are literal names and should not be translated: `Java SDK`, `$(JavaSdkVersion)` +{0} - The value of $(JavaSdkVersion) that was provided to the task + + + 尝试接受 Android SDK 许可证时出错。{0} + + \ No newline at end of file diff --git a/src/Xamarin.Installer.Build.Tasks/Properties/Resources.zh-Hant.resx b/src/Xamarin.Installer.Build.Tasks/Properties/Resources.zh-Hant.resx new file mode 100644 index 00000000000..f8c8ff66ad4 --- /dev/null +++ b/src/Xamarin.Installer.Build.Tasks/Properties/Resources.zh-Hant.resx @@ -0,0 +1,178 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 尚未設定 Android SDK 的路徑。請為 '-p:AndroidSdkDirectory' MSbuild 屬性提供有效路徑,或設定 'ANDROID_HOME' 或 'ANDROID_SDK_ROOT' 環境變數,然後再試一次。 + The following are literal names and should not be translated: `Android SDK`, `ANDROID_HOME`, `ANDROID_SDK_ROOT` and `-p:AndroidSdkDirectory` + + + 在路徑 '{0}' 找不到 Android SDK。 + The following are literal names and should not be translated: `Android SDK` +{0} - The path to the Android SDK + + + 未接受 Android SDK 授權合約,請將 '$(AcceptAndroidSDKLicenses)' 設定為接受。 + The following are literal names and should not be translated: `Android SDK`, `$(AcceptAndroidSDKLicenses)` + + + 內部錯誤: 預期要安裝 {0} 元件,但無法下載。 + {0} - The number of SDK components that need to be installed + + + 相依性 '{0}' 應該已安裝但無法解析。您可以嘗試以下列方式安裝: '{1} --install "{0}"' + The following are literal names and should not be translated: `{1} --install "{0}"` +{0} - The dependency that was not installed +{1} - The path to the sdkmanager tool + + + 無法安裝必要的 Android SDK 元件。 + The following are literal names and should not be translated: `Android SDK` + + + 嘗試安裝必要的 Android SDK 元件逾時或被取消。請考慮將 `$(AndroidDependencyInstallationTimeout)` 設定為大於 `{0}` 分鐘的值。 + The following are literal names and should not be translated: `Android SDK`, `$(AndroidDependencyInstallationTimeout)` +{0} - The timeout value for the task + + + 不支援 '$(AndroidManifestType)' 值 '{0}'。預設為 'Xamarin'。 + The following are literal names and should not be translated: `$(AndroidManifestType)`, `Xamarin` +{0} - The unsupported manifest name + + + 無法將 Java SDK 安裝至 `{0}`。請將 `$(JavaSdkDirectory)` 設定為有效的非系統管理員路徑。 + The following are literal names and should not be translated: `Java SDK`, `$(JavaSdkDirectory)`, `non-administrator` +{0} - The value of $(JavaSdkDirectory) that was provided to the task + + + 偵測到過時的 Java SDK,其版本為 `{1}` `{0}`,但我們無法將建議的 Java SDK (版本 `{2}`) 安裝至此位置。 +請將您的 Java SDK 更新為版本 `{2}`,或將 `$(JavaSdkDirectory)` 設定為有效的非系統管理員路徑,以將建議的 Java SDK 版本安裝至新位置。 + The following are literal names and should not be translated: `Java SDK`, `$(JavaSdkDirectory)`, `non-administrator` +{0} - The value of $(JavaSdkDirectory) that was provided to the task +{1} - The major version of the the Java SDK that was already installed +{2} - The major version of the the Java SDK to install + + + 找不到版本為 `{0}` 的 Java SDK。請將 `$(JavaSdkVersion)` 設定為有效的版本。 + The following are literal names and should not be translated: `Java SDK`, `$(JavaSdkVersion)` +{0} - The value of $(JavaSdkVersion) that was provided to the task + + + 嘗試接受 Android SDK 授權時發生錯誤。{0} + + \ No newline at end of file diff --git a/src/Xamarin.Installer.Build.Tasks/README.md b/src/Xamarin.Installer.Build.Tasks/README.md new file mode 100644 index 00000000000..7ed432c4cfe --- /dev/null +++ b/src/Xamarin.Installer.Build.Tasks/README.md @@ -0,0 +1,51 @@ +Xamarin.Installer.Build.Tasks +================================================= + +Xamarin.Installer.Build.Tasks contains the build tasks and targets that support the +`InstallAndroidDependencies` target that ships with the .NET for Android SDK. + + +### Testing + +The Xamarin.Installer.Build.Tests project contains a test project that is used to test the +`InstallAndroidDependencies` task via a mock MSBuild environment. + +The following steps can be used to test the `InstallAndroidDependencies` target directly +_without_ building dotnet/android, though it does require a dotnet/android checkout and some setup. + +* Clone or navigate to your `dotnet/android` checkout, and install the sandboxed .NET preview: + ``` + dotnet run --project build-tools/xaprepare/xaprepare/xaprepare.csproj -- -s AndroidTestDependencies + ``` + +* Download the `nuget-unsigned` build artifact from the [latest build][0] from the branch you want to test, and move it to the `bin/BuildDebug` folder. + ``` + mv ~/Downloads/nuget-unsigned bin/BuildDebug/nuget-unsigned/ + ``` + +* Set up a sandboxed .NET for Android workload install: + ``` + dotnet build -t:ExtractWorkloadPacks build-tools/create-packs/Microsoft.Android.Sdk.proj + ```` + +Alternatively, follow build instructions in the README to build from scratch. +After setting up this sandbox environment, you can drag over your custom Xamarin.Installer.Build.Tasks outputs. + +* Build your custom branch of Xamarin.Installer.Build.Tasks + ``` + dotnet build Xamarin.Installer.Build.Tasks.csproj + ``` + +* Copy the relevant `Xamarin.Installer` outputs to the sandboxed .NET preview set up earlier: + ``` + cp /path/to/android-sdk-installer/Xamarin.Installer.Build.Tasks/bin/Debug/*.dll bin/Debug/dotnet/packs/Microsoft.Android.Sdk.Darwin/34.99.0-preview.7.346/tools + ``` + +* Create and build a test project that will run against your sandboxed .NET preview: + ``` + bin/Debug/dotnet/dotnet new android -o InstallDepsTest + bin/Debug/dotnet/dotnet build --configfile NuGet.config -v:n -tl:off -t:InstallAndroidDependencies InstallDeps -p:AndroidSdkDirectory=/path/to/empty/sdk -p:AcceptAndroidSDKLicenses=true + ``` + + +[0]: https://devdiv.visualstudio.com/DevDiv/_build?definitionId=11410&_a=summary diff --git a/src/Xamarin.Installer.Build.Tasks/Tasks/InstallAndroidDependencies.cs b/src/Xamarin.Installer.Build.Tasks/Tasks/InstallAndroidDependencies.cs new file mode 100644 index 00000000000..4c16a497834 --- /dev/null +++ b/src/Xamarin.Installer.Build.Tasks/Tasks/InstallAndroidDependencies.cs @@ -0,0 +1,541 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net.Http; +using System.Runtime.InteropServices; +using System.Threading.Tasks; +using Microsoft.Build.Framework; +using Microsoft.Android.Build.Tasks; +using Xamarin.Installer.AndroidSDK; +using Xamarin.Installer.AndroidSDK.Common; +using Xamarin.Installer.AndroidSDK.Manager; +using Xamarin.Installer.Build.Tasks.Properties; +using Xamarin.Installer.Common; +using System.Threading; + +namespace Xamarin.Installer.Build.Tasks +{ + public class InstallAndroidDependencies : AsyncTask, ILogAdapter + { + public override string TaskPrefix => "IAD"; + + [Required] + public ITaskItem [] Dependencies { get; set; } + + [Required] + public ITaskItem [] JavaDependencies { get; set; } + + [Required] + public string AndroidSdkPath { get; set; } + + public string JavaSdkPath { get; set; } + + public bool AcceptAndroidSDKLicenses { get; set; } + + // Provide a default timeout value of 10 minutes if a value is not provided. + public int TimeoutInMinutes { get; set; } = 10; + + public bool InstallJavaDependencies { get; set; } = true; + + List temporaryFiles = new List (); + + public string ManifestType { get; set; } + + public async override Task RunTaskAsync () + { + if (Dependencies?.Length == 0 && JavaDependencies?.Length == 0) { + return; + } + + Logger.LogAdapter = this; + + try { + using (var installationContext = ResolveInstallationContext()) + { + if (installationContext == null) + { + return; + } + + var token = installationContext.CancellationToken; + + var success = await InstallJavaSDKAsync(installationContext, token); + if (!success || token.IsCancellationRequested) + { + return; + } + + success = await InstallAndroidSDKAsync(installationContext, token); + if (!success || token.IsCancellationRequested) + { + return; + } + } + } + catch (TaskCanceledException ex) + { + Exception(Resources.Task_Cancelled, ex, TimeoutInMinutes); + } catch (Exception ex) { + Exception (Resources.Task_Failed, ex); + } finally { + Logger.LogAdapter = null; + + // removes temp files + foreach (var temp in temporaryFiles) { + if (File.Exists (temp)) { + try { + Debug ("Deleting temporary file '{0}'", temp); + File.Delete (temp); + } catch (Exception ex) { + Debug ("Error deleting temporary file '{0}'", ex, temp); + } + } + } + + Complete (); + } + } + + InstallationContext ResolveInstallationContext() + { + var manifestType = AndroidManifestType.Xamarin; + + if (!string.IsNullOrEmpty(ManifestType) && + !Enum.TryParse(ManifestType, out manifestType)) + { + Warning(Resources.Unsupported_Manifest_Type, ManifestType); + manifestType = AndroidManifestType.Xamarin; + } + + string path = AndroidSdkPath; + if (string.IsNullOrEmpty(path)) + { + var androidHome = Environment.GetEnvironmentVariable("ANDROID_HOME"); + path = string.IsNullOrEmpty(androidHome) ? Environment.GetEnvironmentVariable("ANDROID_SDK_ROOT") : androidHome; + } + + if (string.IsNullOrEmpty(path)) + { + path = GetDefaultAndroidSdkPath(); + } + + if (string.IsNullOrEmpty(path)) + { + Error(Resources.AndroidSdk_Not_Set); + return null; + } + + // Resolve JavaSdkPath + string resolvedJavaSdkPath = JavaSdkPath; + if (string.IsNullOrEmpty(resolvedJavaSdkPath)) + { + resolvedJavaSdkPath = Environment.GetEnvironmentVariable("JAVA_HOME"); + } + if (string.IsNullOrEmpty(resolvedJavaSdkPath)) + { + resolvedJavaSdkPath = Environment.GetEnvironmentVariable("JDK_HOME"); + } + if (string.IsNullOrEmpty(resolvedJavaSdkPath)) + { + resolvedJavaSdkPath = GetDefaultJavaSdkPath(); + } + + var installer = new AndroidSDKInstaller(new Helper(), manifestType); + installer.Discover(new List { path }); + + var sdkInstance = installer.FindInstance(path); + if (sdkInstance == null) + { + Error(Resources.AndroidSdk_Not_Found, path); + return null; + } + + IList installationSet = new List(); + var unknownInputs = new List(); + var components = new List(); + foreach (var dependency in Dependencies) + { + string dependencyPath = dependency.ItemSpec.Replace('/', ';'); + string version = dependency.GetMetadata("Version"); + var v = !string.IsNullOrWhiteSpace(version) ? new AndroidRevision(version) : null; + + /* + * It's possible a component is in multiple channels, the channel list is of the form: + * stable + * beta + * dev + * canary + */ + IAndroidComponent matchingComponent = sdkInstance.Components + .Where (c => c.Path == dependencyPath && c.Revision == (v ?? c.Revision)) + .OrderBy (c => c.Channel?.ID ?? "channel-99") // null sorted last + .FirstOrDefault (); + if (matchingComponent != null) + { + if (!matchingComponent.Present && !matchingComponent.Obsolete) { + Debug ($"Adding dependency '{dependencyPath}/{version}'."); + components.Add (matchingComponent); + } else { + Debug ($"Skipping dependency '{dependencyPath}/{version}' as it is already installed or obsolete."); + } + } + else + { + Debug ($"Unknown dependency: '{dependencyPath}/{version}'"); + + unknownInputs.Add(new AndroidDependencyInput + { + Path = dependencyPath, + Version = version, + }); + } + } + + if (components.Count > 0) + { + installationSet = installer.GetInstallationSet(sdkInstance, components); + } + + return new InstallationContext(TimeoutInMinutes, CancellationToken) + { + HttpClient = new HttpClient(), + Installer = installer, + SdkInstance = sdkInstance, + InstallationSet = installationSet, + Components = components, + UnknownInputs = unknownInputs, + JavaSdkPath = resolvedJavaSdkPath + }; + } + + async Task InstallAndroidSDKAsync(InstallationContext installationContext, CancellationToken cancellationToken) + { + var sdkInstance = installationContext.SdkInstance; + var components = installationContext.Components; + + if (components.Count > 0) + { + foreach (var component in components) + { + Debug("Dependency found: {0}", component.DisplayName); + } + + var installationSet = installationContext.InstallationSet; + foreach (var component in installationSet) + { + Debug("Dependency to be installed: {0}", component.DisplayName); + } + + if (!AcceptAndroidSDKLicenses) + { + Error(Resources.License_Not_Accepted); + return false; + } + + var installer = installationContext.Installer; + var downloads = installer.GetDownloadItems(installationSet); + if (downloads == null) + { + Error(Resources.Required_Downloadable_Mismatch, installationSet.Count); + return false; + } + + await Task.WhenAll(downloads.Select(d => DownloadAsync(installationContext.HttpClient, d, cancellationToken))); + + if (!cancellationToken.IsCancellationRequested) + { + installer.Install(sdkInstance, installationSet); + } + + await AcceptLicensesAsync(installationContext, cancellationToken); + } + else + { + Debug("No Android SDK components need to be installed."); + } + + if (!cancellationToken.IsCancellationRequested) + { + var unknownInputs = installationContext.UnknownInputs; + if (unknownInputs.Any()) + { + var sdkManagerDirectory = Directory.EnumerateFiles( + Path.Combine(sdkInstance.Path, "cmdline-tools"), "sdkmanager*", SearchOption.AllDirectories) + .LastOrDefault(s => s.IndexOf("bin", StringComparison.OrdinalIgnoreCase) != -1) ?? "sdkmanager"; + foreach (var unresolved in unknownInputs) + { + Warning(Resources.Required_Not_Installed, unresolved.Path, sdkManagerDirectory); + } + } + } + + return true; + } + + async Task InstallJavaSDKAsync(InstallationContext installationContext, CancellationToken cancellationToken) + { + // Install requested Java SDK + if (!InstallJavaDependencies) + { + Info("Skipping Java SDK installation."); + return true; + } + + var javaInstaller = new JavaDependencyInstaller(new Helper()) + { + JdkPath = installationContext.JavaSdkPath, + }; + + var requestedJdkVersion = new AndroidRevision(JavaDependencies?.FirstOrDefault(j => j.ItemSpec == "jdk")?.GetMetadata("Version") ?? string.Empty); + bool didParseExistingJdkVersion = javaInstaller.GetJdkRevision(out AndroidRevision currentVersion); + if (currentVersion.Major >= requestedJdkVersion.Major && didParseExistingJdkVersion) + { + Info($"Requested Java SDK with major version '{requestedJdkVersion.Major}' (or greater) is already installed at '{installationContext.JavaSdkPath}'."); + return true; + } + + Info($"Attempting to install Java SDK version '{requestedJdkVersion}' to '{installationContext.JavaSdkPath}'."); + + if (!javaInstaller.IsJdkPathValid()) + { + if (didParseExistingJdkVersion) + { + Error(Resources.Invalid_JavaSdkDirectory_ExistingInstall, installationContext.JavaSdkPath, currentVersion.Major, requestedJdkVersion.Major); + } + else + { + Error(Resources.Invalid_JavaSdkDirectory, installationContext.JavaSdkPath); + } + + return false; + } + + javaInstaller.Discover(); + var jdkToInstall = javaInstaller.GetFirstValidJdkArchiveWithVersion(requestedJdkVersion); + if (jdkToInstall == null) + { + Error(Resources.Invalid_JavaSdkVersion, requestedJdkVersion); + return false; + } + + await DownloadAsync(installationContext.HttpClient, jdkToInstall, cancellationToken); + + if (!cancellationToken.IsCancellationRequested) + { + javaInstaller.InstallJdk(jdkToInstall); + } + + return true; + } + + async Task AcceptLicensesAsync(InstallationContext installationContext, CancellationToken cancellationToken) + { + if (AcceptAndroidSDKLicenses) + { + var installationSet = installationContext.InstallationSet; + + //Trying to mimic Android SDK's prompt, let's at least log all the licenses + foreach (var component in installationSet) + { + if (cancellationToken.IsCancellationRequested) + { + return; + } + + LogMessage(component.License.Text); + } + + try + { + Debug($"Accepting all SDK package licenses, via $({nameof(AcceptAndroidSDKLicenses)})"); + + var licenses = installationSet.Select(component => component.License); + + var installer = installationContext.Installer; + var sdkInstance = installationContext.SdkInstance; + + var logPath = string.Empty; // TODO: set a file path + await installer.AcceptLicensesAsync(sdkInstance, licenses, cancellationToken, javaSdkPath: installationContext.JavaSdkPath, logPath: logPath, throwsErrorIfValidationFailed: true); + + Debug($"All SDK package licenses have been accepted"); + } + catch (Exception e) + { + // Exception is not propagated to ensure complete the installation process + Warning(Resources.Licenses_Acceptance_Failed, e.Message); + Debug(Resources.Licenses_Acceptance_Failed, e, e.Message); + } + } + } + + async Task DownloadAsync(HttpClient httpClient, Archive archive, CancellationToken cancellationToken) + { + Info ("Downloading archive from '{0}'", archive.Url); + using (var response = await httpClient.GetAsync (archive.Url, HttpCompletionOption.ResponseHeadersRead, cancellationToken)) { + response.EnsureSuccessStatusCode(); + var fileLength = response.Content.Headers.ContentLength.Value; + var path = Path.GetTempFileName (); + temporaryFiles.Add (path); + using (var fileStream = File.OpenWrite (path)) { + using (var httpStream = await response.Content.ReadAsStreamAsync ()) { + var buffer = new byte[16 * 1024]; + int bytesRead; + double bytesWritten = 0; + double previousProgress = 0; + while ((bytesRead = httpStream.Read (buffer, 0, buffer.Length)) > 0) { + fileStream.Write (buffer, 0, bytesRead); + bytesWritten += bytesRead; + // Log download progress roughly every 10%. + var progress = bytesWritten / fileLength; + if (progress - previousProgress > .10) { + Info ($"Downloaded {progress:P0} of {Path.GetFileName (archive.Url.AbsolutePath)}"); + previousProgress = progress; + } + } + fileStream.Flush (); + } + } + Info ("Wrote '{0}' to '{1}'", archive.Url, path); + archive.DownloadedFilePath = path; + } + } + + public void Action (string format, params object [] parms) + { + LogDebugMessage (format, parms); + } + + public void Debug (string format, params object [] parms) + { + LogDebugMessage (format, parms); + } + + public void Debug (string format, Exception ex, params object [] parms) + { + LogDebugMessage (format, parms); + LogDebugMessage ("[Exception]: {0}", ex); + } + + public void Error (string format, params object [] parms) + { + LogError (format, parms); + } + + public void Exception (string format, Exception ex, params object [] parms) + { + LogError (format, parms); + this.LogUnhandledException (TaskPrefix, ex); + } + + public void Info (string format, params object [] parms) + { + LogMessage (format, parms); + } + + public void Warning (string format, params object [] parms) + { + LogWarning (format, parms); + } + + public void SetOperationStatus (OperationStatus status) { } + + /// + /// Gets the default Android SDK path based on the current platform. + /// Windows: %LocalAppData%\Android\Sdk + /// macOS: ~/Library/Android/sdk + /// Linux: ~/Android/Sdk + /// + string GetDefaultAndroidSdkPath() + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + return Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "Android", "Sdk"); + } + else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + { + return Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), "Library", "Android", "sdk"); + } + else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + { + return Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), "Android", "Sdk"); + } + return null; + } + + /// + /// Gets the default Java SDK path based on the current platform. + /// These paths are user-writable and discovered by dotnet/android-tools. + /// Windows: %LocalAppData%\Android\jdk-{majorVersion} + /// - Discovered via GetWindowsUserFileSystemJdks: https://github.com/dotnet/android-tools/commit/ebd3aaf34b6650b0d0b763f824d5ba3f2d6802e3 + /// macOS: ~/Library/Android/microsoft-{majorVersion}.jdk + /// - Discovered via GetMacOSUserFileSystemJdks: https://github.com/dotnet/android-tools/commit/ebd3aaf34b6650b0d0b763f824d5ba3f2d6802e3 + /// Linux: No default - must set JAVA_HOME or pass $(JavaSdkPath) explicitly + /// + string GetDefaultJavaSdkPath() + { + var requestedJdkVersion = JavaDependencies?.FirstOrDefault(j => j.ItemSpec == "jdk")?.GetMetadata("Version"); + int majorVersion = 21; // Default if parsing fails + if (!string.IsNullOrEmpty(requestedJdkVersion)) + { + var revision = new AndroidRevision(requestedJdkVersion); + if (revision.Major > 0) + { + majorVersion = revision.Major; + } + } + + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + return Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "Android", $"jdk-{majorVersion}"); + } + else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + { + return Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), "Library", "Android", $"microsoft-{majorVersion}.jdk"); + } + // Linux: no default, require explicit JAVA_HOME or JavaSdkPath + return null; + } + } + + public class InstallationContext : IDisposable + { + private CancellationTokenSource cancellationTokenSource; + public InstallationContext(int timeoutInMinutes, CancellationToken cancellationToken) + { + cancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); + cancellationTokenSource.CancelAfter(TimeSpan.FromMinutes(timeoutInMinutes)); + Components = new List(); + UnknownInputs = new List(); + InstallationSet = new List(); + } + + public HttpClient HttpClient { get; set; } + + public AndroidSDKInstaller Installer { get; set; } + + public AndroidSdkInstance SdkInstance { get; set; } + + public List Components { get; internal set; } + + public List UnknownInputs { get; internal set; } + + public IList InstallationSet { get; set; } + + public string JavaSdkPath { get; set; } + + public CancellationToken CancellationToken => cancellationTokenSource.Token; + + void IDisposable.Dispose() + { + HttpClient?.Dispose(); + cancellationTokenSource?.Dispose(); + } + } + + public class AndroidDependencyInput + { + public string Path { get; set; } + public string Version { get; set; } + } +} diff --git a/src/Xamarin.Installer.Build.Tasks/Xamarin.Installer.Build.Tasks.csproj b/src/Xamarin.Installer.Build.Tasks/Xamarin.Installer.Build.Tasks.csproj new file mode 100644 index 00000000000..89275e645a8 --- /dev/null +++ b/src/Xamarin.Installer.Build.Tasks/Xamarin.Installer.Build.Tasks.csproj @@ -0,0 +1,52 @@ + + + + $(NoWarn);CA1305 + + + netstandard2.0 + 12 + false + WINDOWS + true + $(MicrosoftAndroidSdkOutDir) + + + + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + + + + + + + + + + + + + + + True + True + Resources.resx + + + + + ResXFileCodeGenerator + Resources.Designer.cs + + + diff --git a/src/Xamarin.Installer.Build.Tasks/Xamarin.Installer.Common.props b/src/Xamarin.Installer.Build.Tasks/Xamarin.Installer.Common.props new file mode 100644 index 00000000000..72484b07ce6 --- /dev/null +++ b/src/Xamarin.Installer.Build.Tasks/Xamarin.Installer.Common.props @@ -0,0 +1,13 @@ + + + + 36.0.0 + 19.0 + 36.0.0 + + android-36 + 27.2.12479018 + 21.0.8 + 18.0 + + diff --git a/src/Xamarin.Installer.Build.Tasks/Xamarin.Installer.Common.targets b/src/Xamarin.Installer.Build.Tasks/Xamarin.Installer.Common.targets new file mode 100644 index 00000000000..b95c78b6847 --- /dev/null +++ b/src/Xamarin.Installer.Build.Tasks/Xamarin.Installer.Common.targets @@ -0,0 +1,31 @@ + + + + + + InstallAndroidDependencies; + $(BuildDependsOn); + + 10 + + + + + true + + + + + + + + \ No newline at end of file diff --git a/src/Xamarin.Installer.Common/Properties/AssemblyInfo.cs b/src/Xamarin.Installer.Common/Properties/AssemblyInfo.cs new file mode 100644 index 00000000000..f20efb8e465 --- /dev/null +++ b/src/Xamarin.Installer.Common/Properties/AssemblyInfo.cs @@ -0,0 +1,7 @@ +using System.Reflection; +using System.Runtime.InteropServices; + +[assembly: AssemblyTitle("Xamarin Android SDK Installer Common")] +[assembly: AssemblyDescription("Xamarin Android SDK Installer Common")] +[assembly: AssemblyProduct("Xamarin Android SDK Manager")] +[assembly: DefaultDllImportSearchPaths(DllImportSearchPath.SafeDirectories)] diff --git a/src/Xamarin.Installer.Common/Properties/AssemblyInfoTemplate.cs b/src/Xamarin.Installer.Common/Properties/AssemblyInfoTemplate.cs new file mode 100644 index 00000000000..d5e404adb22 --- /dev/null +++ b/src/Xamarin.Installer.Common/Properties/AssemblyInfoTemplate.cs @@ -0,0 +1,17 @@ +using System; +using System.Reflection; + +[assembly: AssemblyCopyright("Copyright © Microsoft 2020")] +[assembly: AssemblyCompany("Microsoft")] + +[assembly: AssemblyVersion (ThisAssembly.Git.SemVer.Major + "." + ThisAssembly.Git.SemVer.Minor + "." + ThisAssembly.Git.BaseVersion.Patch + "." + ThisAssembly.Git.Commits)] + +[assembly: AssemblyFileVersion (ThisAssembly.Git.SemVer.Major + "." + ThisAssembly.Git.SemVer.Minor + "." + ThisAssembly.Git.BaseVersion.Patch + "." + ThisAssembly.Git.Commits)] + +[assembly: AssemblyInformationalVersion ( + ThisAssembly.Git.SemVer.Major + "." + + ThisAssembly.Git.SemVer.Minor + "." + + ThisAssembly.Git.BaseVersion.Patch + "." + + ThisAssembly.Git.Commits + "-" + + ThisAssembly.Git.Branch + "+" + + ThisAssembly.Git.Commit)] diff --git a/src/Xamarin.Installer.Common/Properties/AssemblyInfoTemplate.tt b/src/Xamarin.Installer.Common/Properties/AssemblyInfoTemplate.tt new file mode 100644 index 00000000000..cf3c6ce76b8 --- /dev/null +++ b/src/Xamarin.Installer.Common/Properties/AssemblyInfoTemplate.tt @@ -0,0 +1,18 @@ +<#@ template language="C#" #> +using System; +using System.Reflection; + +[assembly: AssemblyCopyright("Copyright © Microsoft <#=DateTime.Now.Year#>")] +[assembly: AssemblyCompany("Microsoft")] + +[assembly: AssemblyVersion (ThisAssembly.Git.SemVer.Major + "." + ThisAssembly.Git.SemVer.Minor + "." + ThisAssembly.Git.BaseVersion.Patch + "." + ThisAssembly.Git.Commits)] + +[assembly: AssemblyFileVersion (ThisAssembly.Git.SemVer.Major + "." + ThisAssembly.Git.SemVer.Minor + "." + ThisAssembly.Git.BaseVersion.Patch + "." + ThisAssembly.Git.Commits)] + +[assembly: AssemblyInformationalVersion ( + ThisAssembly.Git.SemVer.Major + "." + + ThisAssembly.Git.SemVer.Minor + "." + + ThisAssembly.Git.BaseVersion.Patch + "." + + ThisAssembly.Git.Commits + "-" + + ThisAssembly.Git.Branch + "+" + + ThisAssembly.Git.Commit)] diff --git a/src/Xamarin.Installer.Common/Properties/Resources.Designer.cs b/src/Xamarin.Installer.Common/Properties/Resources.Designer.cs new file mode 100644 index 00000000000..95e6bd6fcd1 --- /dev/null +++ b/src/Xamarin.Installer.Common/Properties/Resources.Designer.cs @@ -0,0 +1,90 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Xamarin.Installer.Common.Properties { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Xamarin.Installer.Common.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to Unable to download update.. + /// + internal static string UnableToDownloadUpdate { + get { + return ResourceManager.GetString("UnableToDownloadUpdate", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to URL format of the update is invalid.. + /// + internal static string UrlFormatInvalid { + get { + return ResourceManager.GetString("UrlFormatInvalid", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to URL scheme of the update is not supported.. + /// + internal static string UrlSchemeNotSupported { + get { + return ResourceManager.GetString("UrlSchemeNotSupported", resourceCulture); + } + } + } +} diff --git a/src/Xamarin.Installer.Common/Properties/Resources.cs.resx b/src/Xamarin.Installer.Common/Properties/Resources.cs.resx new file mode 100644 index 00000000000..077561f1a8f --- /dev/null +++ b/src/Xamarin.Installer.Common/Properties/Resources.cs.resx @@ -0,0 +1,129 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Aktualizaci nejde stáhnout. + + + Formát URL aktualizace je neplatný. + + + Schéma adresy URL aktualizace není podporováno. + + \ No newline at end of file diff --git a/src/Xamarin.Installer.Common/Properties/Resources.de.resx b/src/Xamarin.Installer.Common/Properties/Resources.de.resx new file mode 100644 index 00000000000..9f48602f719 --- /dev/null +++ b/src/Xamarin.Installer.Common/Properties/Resources.de.resx @@ -0,0 +1,129 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Download des Updates nicht möglich. + + + Das URL-Format des Updates ist ungültig. + + + Das URL-Schema des Updates wird nicht unterstützt. + + \ No newline at end of file diff --git a/src/Xamarin.Installer.Common/Properties/Resources.es.resx b/src/Xamarin.Installer.Common/Properties/Resources.es.resx new file mode 100644 index 00000000000..e7d0ad1b62a --- /dev/null +++ b/src/Xamarin.Installer.Common/Properties/Resources.es.resx @@ -0,0 +1,129 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + No se pudo descargar la actualización + + + El formato de dirección URL de la actualización no es válido. + + + No se admite el esquema de dirección URL de la actualización. + + \ No newline at end of file diff --git a/src/Xamarin.Installer.Common/Properties/Resources.fr.resx b/src/Xamarin.Installer.Common/Properties/Resources.fr.resx new file mode 100644 index 00000000000..f20adae16ed --- /dev/null +++ b/src/Xamarin.Installer.Common/Properties/Resources.fr.resx @@ -0,0 +1,129 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Impossible de télécharger la mise à jour. + + + Le format de l’URL de la mise à jour n’est pas valide. + + + Le schéma d’URL de la mise à jour n’est pas pris en charge. + + \ No newline at end of file diff --git a/src/Xamarin.Installer.Common/Properties/Resources.it.resx b/src/Xamarin.Installer.Common/Properties/Resources.it.resx new file mode 100644 index 00000000000..13ceda2771f --- /dev/null +++ b/src/Xamarin.Installer.Common/Properties/Resources.it.resx @@ -0,0 +1,129 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Non è possibile scaricare l'aggiornamento. + + + Il formato dell'URL dell'aggiornamento non è valido. + + + Lo schema dell'URL dell'aggiornamento non è supportato. + + \ No newline at end of file diff --git a/src/Xamarin.Installer.Common/Properties/Resources.ja.resx b/src/Xamarin.Installer.Common/Properties/Resources.ja.resx new file mode 100644 index 00000000000..3e59cfca712 --- /dev/null +++ b/src/Xamarin.Installer.Common/Properties/Resources.ja.resx @@ -0,0 +1,129 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 更新プログラムをダウンロードできません。 + + + 更新プログラムの URL 形式が正しくありません。 + + + 更新プログラムの URL スキームはサポートされていません。 + + \ No newline at end of file diff --git a/src/Xamarin.Installer.Common/Properties/Resources.ko.resx b/src/Xamarin.Installer.Common/Properties/Resources.ko.resx new file mode 100644 index 00000000000..9cb8a223da2 --- /dev/null +++ b/src/Xamarin.Installer.Common/Properties/Resources.ko.resx @@ -0,0 +1,129 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 업데이트를 다운로드할 수 없습니다. + + + 업데이트의 URL 형식이 잘못되었습니다. + + + 업데이트의 URL 체계가 지원되지 않습니다. + + \ No newline at end of file diff --git a/src/Xamarin.Installer.Common/Properties/Resources.pl.resx b/src/Xamarin.Installer.Common/Properties/Resources.pl.resx new file mode 100644 index 00000000000..106ffac32c1 --- /dev/null +++ b/src/Xamarin.Installer.Common/Properties/Resources.pl.resx @@ -0,0 +1,129 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Nie można pobrać aktualizacji. + + + Format adresu URL aktualizacji jest nieprawidłowy. + + + Schemat adresu URL aktualizacji nie jest obsługiwany. + + \ No newline at end of file diff --git a/src/Xamarin.Installer.Common/Properties/Resources.pt-BR.resx b/src/Xamarin.Installer.Common/Properties/Resources.pt-BR.resx new file mode 100644 index 00000000000..1c3bbbb3610 --- /dev/null +++ b/src/Xamarin.Installer.Common/Properties/Resources.pt-BR.resx @@ -0,0 +1,129 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Não foi possível baixar a atualização. + + + O formato do URL da atualização é inválido. + + + O esquema de URL da atualização não é compatível. + + \ No newline at end of file diff --git a/src/Xamarin.Installer.Common/Properties/Resources.resx b/src/Xamarin.Installer.Common/Properties/Resources.resx new file mode 100644 index 00000000000..2680f30f420 --- /dev/null +++ b/src/Xamarin.Installer.Common/Properties/Resources.resx @@ -0,0 +1,129 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Unable to download update. + + + URL format of the update is invalid. + + + URL scheme of the update is not supported. + + \ No newline at end of file diff --git a/src/Xamarin.Installer.Common/Properties/Resources.ru.resx b/src/Xamarin.Installer.Common/Properties/Resources.ru.resx new file mode 100644 index 00000000000..4c280f89d10 --- /dev/null +++ b/src/Xamarin.Installer.Common/Properties/Resources.ru.resx @@ -0,0 +1,129 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Не удалось загрузить обновление + + + Недопустимый формат URL-адреса обновления. + + + Схема URL-адресов обновления не поддерживается. + + \ No newline at end of file diff --git a/src/Xamarin.Installer.Common/Properties/Resources.tr.resx b/src/Xamarin.Installer.Common/Properties/Resources.tr.resx new file mode 100644 index 00000000000..a98b7869682 --- /dev/null +++ b/src/Xamarin.Installer.Common/Properties/Resources.tr.resx @@ -0,0 +1,129 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Güncelleştirme indirilemiyor. + + + Güncelleştirmenin URL biçimi geçersiz. + + + Güncelleştirmenin URL düzeni desteklenmiyor. + + \ No newline at end of file diff --git a/src/Xamarin.Installer.Common/Properties/Resources.zh-Hans.resx b/src/Xamarin.Installer.Common/Properties/Resources.zh-Hans.resx new file mode 100644 index 00000000000..5cc2fa7ca5c --- /dev/null +++ b/src/Xamarin.Installer.Common/Properties/Resources.zh-Hans.resx @@ -0,0 +1,129 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 无法下载更新。 + + + 更新的 URL 格式无效。 + + + 不支持更新的 URL 方案。 + + \ No newline at end of file diff --git a/src/Xamarin.Installer.Common/Properties/Resources.zh-Hant.resx b/src/Xamarin.Installer.Common/Properties/Resources.zh-Hant.resx new file mode 100644 index 00000000000..aa4d9dd0846 --- /dev/null +++ b/src/Xamarin.Installer.Common/Properties/Resources.zh-Hant.resx @@ -0,0 +1,129 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 無法下載更新。 + + + 更新的 URL 格式無效。 + + + 不支援更新的 URL 方案。 + + \ No newline at end of file diff --git a/src/Xamarin.Installer.Common/Xamarin.Installer.Common.csproj b/src/Xamarin.Installer.Common/Xamarin.Installer.Common.csproj new file mode 100644 index 00000000000..67146b6d8a1 --- /dev/null +++ b/src/Xamarin.Installer.Common/Xamarin.Installer.Common.csproj @@ -0,0 +1,71 @@ + + + + $(NoWarn);CA1305 + + + 12 + netstandard2.0 + false + false + $(MicrosoftAndroidSdkOutDir) + + + bin\Debug\Xamarin.Installer.Common.xml + + + bin\Release\Xamarin.Installer.Common.xml + + + true + $(CodeSigningFile) + + + + AssemblyInfoTemplate.tt + True + True + + + True + True + Resources.resx + + + + + + + 1.0.0 + + + + + ResXFileCodeGenerator + Resources.Designer.cs + + + + + TextTemplatingFileGenerator + AssemblyInfoTemplate.cs + + + + + + + $(MSBuildThisFileDirectory)..\..\build-tools\android-platform-support\version.txt + + + + + + Microsoft400 + + + Microsoft400 + + + + diff --git a/src/Xamarin.Installer.Common/Xamarin.Installer.Common/CommonUtilities.cs b/src/Xamarin.Installer.Common/Xamarin.Installer.Common/CommonUtilities.cs new file mode 100644 index 00000000000..34aad4b7f22 --- /dev/null +++ b/src/Xamarin.Installer.Common/Xamarin.Installer.Common/CommonUtilities.cs @@ -0,0 +1,344 @@ +using Microsoft.Win32; +using System; +using System.Collections.Generic; +using System.Collections.Specialized; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Net; +using System.Runtime.InteropServices; +using System.Security.AccessControl; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +using Xamarin.Installer.Common.Properties; + +namespace Xamarin.Installer.Common +{ + public static class CommonUtilities + { + const int SHORT_PATH_BUFFER_LENGTH = 1024; + + static readonly char[] invalidPathChars = Path.GetInvalidPathChars (); + + static IHelpers __helpers; + static string __productName; + + public const string MANIFEST_TYPE_PROPERTY_KEY = "ManifestType"; + public const string DONT_SHOW_REPAIR_AGAIN_KEY = "DontShowRepairAgain"; + + public static IHelpers Helpers { + get { + if (__helpers == null) + throw new InvalidOperationException ("Internal error: Helpers must be initialized!"); + return __helpers; + } + + set { + if (value == null) + throw new ArgumentNullException ("value"); + __helpers = value; + } + } + + // Used in the proxy forms + public static string ProductName { + get { + if (String.IsNullOrEmpty (__productName)) + throw new InvalidOperationException ("Internal error: ProductName must be initialized to a non-empty value"); + return __productName; + } + + set { + if (String.IsNullOrEmpty (value)) + throw new ArgumentException ("Must not be empty or null", "value"); + __productName = value; + } + } + + public static void CopyDirectory (string sourceDirName, string destDirName, bool overwrite = false) + { + MoveDirectory (sourceDirName, destDirName, false, overwrite); + } + + // Based on code from: http://foldermover.codeplex.com (Apache license) + public static void MoveDirectory (string sourceDirName, string destDirName, bool move = true, bool overwrite = false, bool createSourceDirRootAtDestination = false) + { + if (sourceDirName == null) + throw new ArgumentNullException ("sourceDirName", "The source directory cannot be null."); + if (destDirName == null) + throw new ArgumentNullException ("destDirName", "The destination directory cannot be null."); + + sourceDirName = sourceDirName.Trim (); + destDirName = destDirName.Trim (); + + if ((sourceDirName.Length == 0) || (destDirName.Length == 0)) + throw new ArgumentException ("sourceDirName or destDirName is a zero-length string."); + + if (sourceDirName.IndexOfAny (invalidPathChars) > 0) + throw new ArgumentException ("The directory contains invalid path characters.", "sourceDirName"); + + if (destDirName.IndexOfAny (invalidPathChars) > 0) + throw new ArgumentException ("The directory contains invalid path characters.", "destDirName"); + + if (createSourceDirRootAtDestination) + destDirName = Path.Combine (destDirName, Path.GetFileName (sourceDirName)); + + var sourceDir = new DirectoryInfo (sourceDirName); + var destDir = new DirectoryInfo (destDirName); + + if (!sourceDir.Exists) + throw new DirectoryNotFoundException ("The path specified by sourceDirName is invalid: " + sourceDirName); + bool destExists = destDir.Exists; + if (!overwrite && destExists) + throw new IOException ("The path specified by destDirName already exists: " + destDirName); + + if (!destExists) + Directory.CreateDirectory (destDirName); + + //Copy the files in the current directory. + FileInfo[] files = sourceDir.GetFiles (); + IHelpers helpers = Helpers; + foreach (FileInfo file in files) { + string newPath = Path.Combine (destDirName, file.Name); + if (helpers.IsSpecialFile (file.FullName)) + helpers.CopySpecialFile (file.FullName, newPath); + else + Copy (file, newPath, overwrite); + + } + + //Copy all sub directories. + DirectoryInfo[] subDirs = sourceDir.GetDirectories (); + foreach (DirectoryInfo subDir in subDirs) { + string newPath = Path.Combine (destDirName, subDir.Name); + MoveDirectory (subDir.FullName, newPath, move, overwrite); + } + + if (move) { + try { + Directory.Delete (sourceDirName, true); + } catch (Exception ex) { + // Treat the error as warning - the files got copied to the destination and this is the + // most important thing here. + Logger.Exception ("Failed to delete source directory '{0}' after copying it to '{1}'", ex, sourceDir, destDir); + Logger.Warning ("You will need to delete the source directory '{0}' manually", sourceDirName); + } + } + } + + static void Copy (FileInfo source, string target, bool overwrite) + { + if (overwrite && File.Exists (target)) + File.Delete (target); + source.CopyTo (target); + } + + public static bool DeleteDirectoryRecursively (string path, bool throwOnError = false) + { + if (String.IsNullOrEmpty (path)) { + Logger.Error ("Cannot delete directory recursively, no path given."); + if (throwOnError) + throw new ArgumentNullException ("path"); + return true; + } + if (!Directory.Exists (path)) { + Logger.Warning ("Attempt to delete a non-existing directory '{0}'", path); + if (throwOnError) + throw new ArgumentException ("Directory does not exist", "path"); + return true; + } + + try { + DoDeleteDirectoryRecursively (new DirectoryInfo (path)); + return true; + } catch (Exception ex) { + Logger.Exception ("Failed to recursively delete directory '{0}'", ex, path); + if (throwOnError) + throw; + return false; + } + } + + static void DoDeleteDirectoryRecursively (FileSystemInfo fi) + { + if (fi == null) + return; + + fi.Attributes = FileAttributes.Normal; + var di = fi as DirectoryInfo; + string kind; + if (di != null) { + kind = "directory"; + foreach (var info in di.GetFileSystemInfos ()) + DoDeleteDirectoryRecursively (info); + } else + kind = "file"; + + try { + fi.Delete (); + } catch (Exception ex) { + Logger.Exception ("Failed to delete {0} '{1}',", ex, kind, fi.FullName); + throw; + } + } + + public static string RunCommand (string command, out int exitCode, params string[] arguments) + { + return RunCommand (null, command, out exitCode, false, null, arguments); + } + + public static string RunCommand (string workingDirectory, string command, out int exitCode, params string[] arguments) + { + return RunCommand (workingDirectory, command, out exitCode, false, null, arguments); + } + + public static string RunCommand (string command, out int exitCode, bool fireAndForget, params string[] arguments) + { + return RunCommand (null, command, out exitCode, fireAndForget, null, arguments); + } + + public static string RunCommand (string workingDirectory, string command, out int exitCode, bool fireAndForget, params string[] arguments) + { + return RunCommand (workingDirectory, command, out exitCode, fireAndForget, null, arguments); + } + + public static string RunCommand (string workingDirectory, string command, out int exitCode, bool fireAndForget, Action modifyStartInfo, params string[] arguments) + { + if (String.IsNullOrEmpty (command)) + throw new ArgumentNullException ("command"); + + var si = new ProcessStartInfo (command) { + UseShellExecute = false, + CreateNoWindow = true, + }; + + if (!String.IsNullOrEmpty (workingDirectory)) + si.WorkingDirectory = workingDirectory; + + CleanEnvironment (si.EnvironmentVariables); + StringBuilder output = null; + ManualResetEvent stdout_completed = null; + ManualResetEvent stderr_completed = null; + + if (!fireAndForget) { + si.RedirectStandardOutput = true; + si.RedirectStandardError = true; + si.StandardOutputEncoding = Encoding.Default; + si.StandardErrorEncoding = Encoding.Default; + output = new StringBuilder (); + stdout_completed = new ManualResetEvent (false); + stderr_completed = new ManualResetEvent (false); + } + if (arguments != null && arguments.Length > 0) { + var sb = new StringBuilder (); + bool first = true; + foreach (string a in arguments) { + if (first) + first = false; + else + sb.Append (' '); + if (a.Any (c => Char.IsWhiteSpace (c))) + sb.AppendFormat ("\"{0}\"", a); + else + sb.Append (a); + } + si.Arguments = sb.ToString (); + } + + if (modifyStartInfo != null) { + modifyStartInfo (si); + if (!si.RedirectStandardError) + si.StandardErrorEncoding = null; + if (!si.RedirectStandardOutput) + si.StandardOutputEncoding = null; + } + + var p = new Process { + StartInfo = si + }; + p.Start (); + + if (!fireAndForget) { + if (si.RedirectStandardOutput) { + p.OutputDataReceived += (sender, e) => { + if (e.Data != null) { + lock (output) { + output.AppendLine (e.Data); + } + } else + stdout_completed.Set (); + }; + p.BeginOutputReadLine (); + } + + if (si.RedirectStandardError) { + p.ErrorDataReceived += (sender, e) => { + if (e.Data != null) { + lock (output) { + output.AppendLine (e.Data); + } + } else + stderr_completed.Set (); + }; + p.BeginErrorReadLine (); + } + + p.WaitForExit (); + if (si.RedirectStandardError) + stderr_completed.WaitOne (TimeSpan.FromSeconds (1)); + if (si.RedirectStandardOutput) + stdout_completed.WaitOne (TimeSpan.FromSeconds (1)); + + exitCode = p.ExitCode; + return output.ToString (); + } else { + if (p.HasExited) + exitCode = p.ExitCode; + else + exitCode = 0; + } + + return null; + } + + [DllImport ("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)] + static extern uint GetShortPathName ( + [MarshalAs (UnmanagedType.LPTStr)] string longPath, + [MarshalAs (UnmanagedType.LPTStr)] StringBuilder shortPath, + uint shortPathLength); + + public static string GetShortPath (string path) + { + if (String.IsNullOrEmpty (path)) + return path; + + if (!Directory.Exists (path) && !File.Exists (path)) + return path; + + var sb = new StringBuilder (SHORT_PATH_BUFFER_LENGTH); + try { + uint result = GetShortPathName (path, sb, SHORT_PATH_BUFFER_LENGTH); + if (result == 0) { + int error = Marshal.GetLastWin32Error (); + Logger.Warning ("failed to retrieve short path name for '{0}'. Long path form will be used instead. Error: {1}", path, error); + return path; + } + } catch (Exception ex) { + Logger.Exception ("failed to get short path name for '{0}'. Long path form will be used instead. Exception was thrown.", ex, path); + return path; + } + + return sb.ToString (); + } + + static void CleanEnvironment (StringDictionary envvars) + { + envvars.Remove ("MONO_PATH"); + envvars.Remove ("MONO_GAC_PREFIX"); + envvars.Remove ("MONOMAC_LOGDIR"); + envvars.Remove ("PKG_CONFIG_PATH"); + } + } +} diff --git a/src/Xamarin.Installer.Common/Xamarin.Installer.Common/Extensions.String.cs b/src/Xamarin.Installer.Common/Xamarin.Installer.Common/Extensions.String.cs new file mode 100644 index 00000000000..123a15a6c8b --- /dev/null +++ b/src/Xamarin.Installer.Common/Xamarin.Installer.Common/Extensions.String.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Xamarin.Installer.Common +{ + static partial class Extensions + { + public static string SafeTrim (this string s) + { + return (s ?? String.Empty).Trim (); + } + + public static string RemoveTrailingDirectorySeparator (this string path) + { + if (String.IsNullOrEmpty (path) || (!path.EndsWith (Path.DirectorySeparatorChar.ToString ()) && !path.EndsWith (Path.AltDirectorySeparatorChar.ToString ()))) + return path; + return path.Substring (0, path.Length - 1); + } + } +} diff --git a/src/Xamarin.Installer.Common/Xamarin.Installer.Common/Extensions.Version.cs b/src/Xamarin.Installer.Common/Xamarin.Installer.Common/Extensions.Version.cs new file mode 100644 index 00000000000..f18d6b5ad20 --- /dev/null +++ b/src/Xamarin.Installer.Common/Xamarin.Installer.Common/Extensions.Version.cs @@ -0,0 +1,37 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Xamarin.Installer.Common +{ + public static partial class Extensions + { + public static Version CloneFillWithZeros (this Version v) + { + return v.CloneValidVersion (); + } + + public static Version CloneValidVersion (this Version v, int defaultMajor = -1, int defaultMinor = -1, int defaultBuild = -1, int defaultRevision = -1) + { + if (v == null) + return null; + + return new Version ( + GetValidValue (v.Major, defaultMajor), + GetValidValue (v.Minor, defaultMinor), + GetValidValue (v.Build, defaultBuild), + GetValidValue (v.Revision, defaultRevision) + ); + } + + static int GetValidValue (int versionPart, int defaultValue) + { + if (versionPart >= 0) + return versionPart; + + return defaultValue > 0 ? defaultValue : 0; + } + } +} diff --git a/src/Xamarin.Installer.Common/Xamarin.Installer.Common/Extensions.cs b/src/Xamarin.Installer.Common/Xamarin.Installer.Common/Extensions.cs new file mode 100644 index 00000000000..d43deb59051 --- /dev/null +++ b/src/Xamarin.Installer.Common/Xamarin.Installer.Common/Extensions.cs @@ -0,0 +1,49 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Xamarin.Web.Installer +{ + public static partial class Extensions + { + static readonly DateTime epoch = new DateTime (1970, 1, 1, 0, 0, 0, 0).ToUniversalTime (); + + public static string JsonQuoted (this string text) + { + return "\"" + text + "\""; + } + + public static long ToUnixTimestamp (this DateTime dt) + { + return (long)(dt - epoch).TotalSeconds; + } + + public static string ToBase64 (this string data) + { + if (String.IsNullOrEmpty (data)) + return String.Empty; + + return Convert.ToBase64String (Encoding.UTF8.GetBytes (data)); + } + + public static string FromBase64 (this string data) + { + if (String.IsNullOrEmpty (data)) + return String.Empty; + + return Encoding.UTF8.GetString (Convert.FromBase64String (data)); + } + + public static IEnumerable DistinctBy + (this IEnumerable source, Func keySelector) + { + HashSet knownKeys = new HashSet (); + foreach (TSource element in source) { + if (knownKeys.Add (keySelector (element))) { + yield return element; + } + } + } + } +} diff --git a/src/Xamarin.Installer.Common/Xamarin.Installer.Common/Helpers.cs b/src/Xamarin.Installer.Common/Xamarin.Installer.Common/Helpers.cs new file mode 100644 index 00000000000..14551f9d537 --- /dev/null +++ b/src/Xamarin.Installer.Common/Xamarin.Installer.Common/Helpers.cs @@ -0,0 +1,175 @@ +using Microsoft.Win32; +using System; +using System.IO; + + +namespace Xamarin.Installer.Common +{ + public static class Helpers + { + const string INSTALLER_MACHINE_ID_REGISTRY_KVALUE = "IMID"; + static bool onWindows; + public static readonly string UserApplicationDataPath = Environment.GetFolderPath (Environment.SpecialFolder.LocalApplicationData); + public static readonly string XamarinBaseRegistryKValue = "SOFTWARE\\Xamarin\\MonoAndroid"; + + static Helpers () + { + onWindows = Environment.OSVersion.Platform != PlatformID.MacOSX && Environment.OSVersion.Platform != PlatformID.Unix; + } + + public static Guid GetMachineID () + { + if (onWindows) + return WindowsGetMachineID (); + else + return UnixGetMachineID (); + } + + static Guid UnixGetMachineID () + { + string idPath = Path.Combine (UserApplicationDataPath, "Xamarin", ".machineid"); + Guid machineId; + + try { + if (!File.Exists (idPath)) + return GenerateAndSaveId (idPath); + + string guid = File.ReadAllText (idPath).Trim (); + if (String.IsNullOrEmpty (guid)) + return GenerateAndSaveId (idPath); + else if (!Guid.TryParse (guid, out machineId)) + return GenerateAndSaveId (idPath); + return machineId; + } catch (Exception) { + // ignore + return Guid.NewGuid (); + } + } + + static Guid WindowsGetMachineID () + { + string uuid; + Guid machineId; + if (!GetRegistryKeyValue (XamarinBaseRegistryKValue, INSTALLER_MACHINE_ID_REGISTRY_KVALUE, out uuid, RegistryHive.CurrentUser) || String.IsNullOrEmpty (uuid)) { + machineId = Guid.NewGuid (); + SetRegistryKeyValue (XamarinBaseRegistryKValue, INSTALLER_MACHINE_ID_REGISTRY_KVALUE, machineId.ToString (), RegistryValueKind.String, RegistryHive.CurrentUser); + } else { + if (!Guid.TryParse (uuid, out machineId) || machineId == Guid.Empty) + machineId = Guid.NewGuid (); + } + + return machineId; + } + + static void SaveIdToFile (string idPath, Guid machineId) + { + string xamarinDir = Path.GetDirectoryName (idPath); + if (!Directory.Exists (xamarinDir)) + Directory.CreateDirectory (xamarinDir); + File.WriteAllText (idPath, machineId.ToString ()); + } + + static Guid GenerateAndSaveId (string idPath) + { + var machineId = Guid.NewGuid (); + SaveIdToFile (idPath, machineId); + return machineId; + } + + public static bool GetRegistryKeyValue (string subKeyPath, string keyName, out T result, RegistryHive hive = RegistryHive.LocalMachine, bool check64Node = true) + { + result = default (T); + if (String.IsNullOrEmpty (subKeyPath)) + return false; + + string wow3264Value = null; + if (check64Node && hive == RegistryHive.LocalMachine) { + int offset = 0; + if (subKeyPath [0] == '\\') + offset++; + if (subKeyPath.StartsWith ("SOFTWARE\\", StringComparison.OrdinalIgnoreCase) || subKeyPath.StartsWith ("\\SOFTWARE\\", StringComparison.OrdinalIgnoreCase)) { + offset += 8; + wow3264Value = "SOFTWARE\\Wow6432Node\\" + subKeyPath.Substring (offset + 1); + } + } + + if (!String.IsNullOrEmpty (wow3264Value) && GetRegistryKeyValueInternal(wow3264Value, keyName, out result, hive)) + return true; + return GetRegistryKeyValueInternal(subKeyPath, keyName, out result, hive); + } + + static bool GetRegistryKeyValueInternal (string subKeyPath, string keyName, out T result, RegistryHive hive = RegistryHive.LocalMachine) + { + result = default (T); + RegistryKey parent = HiveToKey (hive); + using (RegistryKey rk = parent.OpenSubKey (subKeyPath, false)) { + if (rk == null) + return false; + + object o = rk.GetValue (keyName); + if (o == null) + return false; + + try { + result = (T)Convert.ChangeType (o, typeof (T)); + return true; + } catch { + // ignore + } + + return false; + } + } + + public static void SetRegistryKeyValue (string subKeyPath, string keyName, object value, RegistryValueKind kind = RegistryValueKind.String, RegistryHive hive = RegistryHive.CurrentUser) + { + if (String.IsNullOrEmpty (subKeyPath)) + throw new ArgumentException ("subKeyPath"); + if (String.IsNullOrEmpty (keyName)) + throw new ArgumentException ("keyName"); + + RegistryKey parent = HiveToKey (hive); + RegistryKey rk = null; + try { + rk = parent.OpenSubKey (subKeyPath, true); + if (rk == null) { + rk = parent.CreateSubKey (subKeyPath); + if (rk == null) + return; + } + + rk.SetValue (keyName, value, kind); + } finally { + if (rk != null) + rk.Dispose (); + } + } + + public static RegistryKey HiveToKey (RegistryHive hive) + { + switch (hive) { + case RegistryHive.ClassesRoot: + return Registry.ClassesRoot; + + case RegistryHive.CurrentConfig: + return Registry.CurrentConfig; + + case RegistryHive.CurrentUser: + return Registry.CurrentUser; + + case RegistryHive.LocalMachine: + return Registry.LocalMachine; + + case RegistryHive.PerformanceData: + return Registry.PerformanceData; + + case RegistryHive.Users: + return Registry.Users; + + default: + throw new InvalidOperationException ("Unknown registry hive"); + } + } + + } +} diff --git a/src/Xamarin.Installer.Common/Xamarin.Installer.Common/IHelpers.cs b/src/Xamarin.Installer.Common/Xamarin.Installer.Common/IHelpers.cs new file mode 100644 index 00000000000..3e482ec6d42 --- /dev/null +++ b/src/Xamarin.Installer.Common/Xamarin.Installer.Common/IHelpers.cs @@ -0,0 +1,153 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net; +using System.Text; +using System.Threading.Tasks; + +namespace Xamarin.Installer.Common +{ + /// + /// Interface to be implemented by the client code to help the installer perform certain tasks that + /// may be implemented differently in different environments (e.g. Unified Installer vs IDE vs MSBuild + /// targets in Xamarin.Android) + /// + public interface IHelpers + { + /// + /// Returns true if the app is running on ARM64 machine + /// + bool IsArm64 { get; } + + /// + /// Returns true if the current OS is a 64-bit one + /// + bool Is64BitOS { get; } + + /// + /// Gets the home directory of the current user. + /// + /// Home directory of the current user + string HomeDirectory { get; } + + /// + /// Gets the name of the current user. + /// + /// The name of the current user. + string UserName { get; } + + /// + /// Gets a value indicating the current OS has case-sensitive (or case-aware) file systems + /// + /// true if the current OS has case sensitive file systems; otherwise, false. + bool IsCaseSensitiveFileSystem { get; } + + /// + /// Determine whether the file at exists and is a special file + /// (e.g. a paging file on Windows or a character device on Unix) + /// + /// Path to the file + /// true if file is a special one, false otherwise + bool IsSpecialFile (string filePath); + + /// + /// Copy a special file (such as Windows paging file or Unix character device) from + /// to + /// + /// Source file + /// Destination file + void CopySpecialFile (string source, string target); + + /// + /// Attempt to determine the content length of the resource at the URL. + /// + /// URL to obtain content length of + /// The resource's content length. 0 should be returned if an error accessing the URL occurs. + ulong GetUrlContentLength (Uri url); + + /// + /// Unzip the archive found at the path pointed to by placing its contents in the + /// directory indicated by . If is specified, the + /// extracted files will be owned by that user. + /// + /// The archive is will contain a single top-level directory which should be located by + /// the unzipper and used, in combination with , as the return value of this method. + /// Top-level directory in which to unpack the archive + /// Path to the compressed archive + /// Owner of the unpacked content + /// Path to the root directory of the *unpacked* content + string Unzip (string baseDirectory, string archivePath, string fileOwnerName = null); + + /// + /// Unzip the archive found at the path pointed to by placing its contents in the + /// directory indicated by . If is specified, the + /// extracted files will be owned by that user. + /// + /// The archive is will contain a single top-level directory which should be located by + /// the unzipper and used, in combination with , as the return value of this method. + /// Top-level directory in which to unpack the archive + /// Path to the compressed archive + /// Owner of the unpacked content + /// + /// Path to the root directory of the *unpacked* content + string Unzip (string baseDirectory, string archivePath, string fileOwnerName = null, InstallationProgressEventArgs.InstallationProgressActionDelegate progressCallback = null); + + /// + /// Download contents of the given and place it in the string + /// + /// true, if download was successful, false otherwise. + /// URL to download from + /// Output string + bool DownloadToString (Uri url, out string output); + + /// + /// Checks whether web resource pointed to by exists. + /// + /// true, if URL exists, false otherwise. + /// URL to check the existence of + bool URLExists (Uri url); + + /// + /// Gets the registry key value. On platforms other than Windows it should return an empty string. + /// + /// The registry key value or empty string on platforms other than Windows. + /// Sub key path. + /// Key name. + /// If set to true check the 32-bit registry hive on 64-bit windows. + string GetRegistryKeyValue (string subKeyPath, string keyName, bool check64Node); + + /// + /// Look up translation of the provided string. + /// + /// Translated string + /// String to look up translation of + string GetString (string s); + + /// + /// Look up translation of the provided string, for strings which include numerals. + /// + /// Translated string + /// String to look up translation of, singular form + /// String to look up translation of, plural form + /// integer used to select plural or singular form of the string + string GetPluralString (string s, string p, int n); + + /// + /// Gets existing property value by its key + /// + /// Property key + /// Default value + /// Property value or defaultValue + string GetProperty (string key, string defaultValue = ""); + + /// + /// Sets a new property or updates an existing one + /// + /// Property key + /// Property value + void SetProperty (string key, string value); + + Task CheckIfNetworkIsAvailableAsync (); + } +} diff --git a/src/Xamarin.Installer.Common/Xamarin.Installer.Common/ILogAdapter.cs b/src/Xamarin.Installer.Common/Xamarin.Installer.Common/ILogAdapter.cs new file mode 100644 index 00000000000..6b555555877 --- /dev/null +++ b/src/Xamarin.Installer.Common/Xamarin.Installer.Common/ILogAdapter.cs @@ -0,0 +1,30 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Xamarin.Installer.Common +{ + public interface ILogAdapter + { + void Action(string format, params object[] parms); + void Debug (string format, params object[] parms); + void Debug (string format, Exception ex, params object[] parms); + void Error (string format, params object[] parms); + void Exception (string format, Exception ex, params object[] parms); + void Info (string format, params object[] parms); + void Warning (string format, params object[] parms); + void SetOperationStatus (OperationStatus status); + } + + public interface ILogAdapterExtended: ILogAdapter + { + void SaveManifest (string manifestName, string content); + } + + public interface ILogPathProvider + { + string GetLogPath(string fileNameSuffix); + } +} diff --git a/src/Xamarin.Installer.Common/Xamarin.Installer.Common/InstallationProgressEventArgs.cs b/src/Xamarin.Installer.Common/Xamarin.Installer.Common/InstallationProgressEventArgs.cs new file mode 100644 index 00000000000..45fed0effd8 --- /dev/null +++ b/src/Xamarin.Installer.Common/Xamarin.Installer.Common/InstallationProgressEventArgs.cs @@ -0,0 +1,33 @@ +using System; + +namespace Xamarin.Installer.Common +{ + /// + /// Passed as the argument to the the event + /// handler. + /// + public class InstallationProgressEventArgs : EventArgs + { + /// + /// Installation progress message + /// + public string Message { get; set; } + + /// + /// Progress is in range 0f .. 100f + /// + public float Progress { get; set; } + + /// + /// This is True for the very start of a component installation + /// + public bool IsInitialEvent { get; set; } + + /// + /// Definition for progress callback + /// + /// + public delegate void InstallationProgressActionDelegate (float progress); + } +} + diff --git a/src/Xamarin.Installer.Common/Xamarin.Installer.Common/Log.cs b/src/Xamarin.Installer.Common/Xamarin.Installer.Common/Log.cs new file mode 100644 index 00000000000..4cdef62027d --- /dev/null +++ b/src/Xamarin.Installer.Common/Xamarin.Installer.Common/Log.cs @@ -0,0 +1,81 @@ +using System; +using System.Collections.Generic; +using System.Collections.Concurrent; +using System.IO; +using System.Text; +using System.Threading; + +namespace Xamarin.Installer.Common +{ + public static class Logger + { + public static ILogAdapter LogAdapter { get; set; } + + public static void Action(string format, params object[] parms) + { + if (LogAdapter == null) + return; + LogAdapter.Action(format, parms); + } + + public static void Debug (string format, params object[] parms) + { + if (LogAdapter == null) + return; + LogAdapter.Debug (format, parms); + } + + public static void Debug (string format, Exception ex, params object[] parms) + { + if (LogAdapter == null) + return; + LogAdapter.Debug (format, ex, parms); + } + + public static void Error (string format, params object[] parms) + { + if (LogAdapter == null) + return; + LogAdapter.Error (format, parms); + } + + public static void Exception (string format, Exception ex, params object[] parms) + { + if (LogAdapter == null) + return; + LogAdapter.Exception (format, ex, parms); + } + + public static void Info (string format, params object[] parms) + { + if (LogAdapter == null) + return; + LogAdapter.Info (format, parms); + } + + public static void Warning (string format, params object[] parms) + { + if (LogAdapter == null) + return; + LogAdapter.Warning (format, parms); + } + + public static void SetOperationStatus (OperationStatus status) + { + if (LogAdapter == null) + return; + LogAdapter.SetOperationStatus (status); + } + + public static void LogManifest (string manifestName, Stream contentStream) + { + contentStream.Seek (0, SeekOrigin.Begin); + var content = new StreamReader (contentStream).ReadToEnd (); + if (LogAdapter is ILogAdapterExtended extendedLogAdapter) { + extendedLogAdapter.SaveManifest (manifestName, content); + } else { + Debug ($"{manifestName} :\n{content}"); + } + } + } +} diff --git a/src/Xamarin.Installer.Common/Xamarin.Installer.Common/OperationStatus.cs b/src/Xamarin.Installer.Common/Xamarin.Installer.Common/OperationStatus.cs new file mode 100644 index 00000000000..70ba8d3a370 --- /dev/null +++ b/src/Xamarin.Installer.Common/Xamarin.Installer.Common/OperationStatus.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Xamarin.Installer.Common +{ + public enum OperationStatus + { + Failure + } +} diff --git a/src/Xamarin.Installer.Common/Xamarin.Installer.Common/XmlElementExtensions.cs b/src/Xamarin.Installer.Common/Xamarin.Installer.Common/XmlElementExtensions.cs new file mode 100644 index 00000000000..3cb0c502de9 --- /dev/null +++ b/src/Xamarin.Installer.Common/Xamarin.Installer.Common/XmlElementExtensions.cs @@ -0,0 +1,83 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Xml; + +namespace Xamarin.Installer.Common +{ + public static class XmlElementExtensions + { + public static string SelectSingleElementValue (this XmlElement element, string xpath) + { + if (element == null || String.IsNullOrEmpty (xpath)) + return null; + + var found = element.SelectSingleNode (xpath) as XmlElement; + return found == null ? null : found.InnerText; + } + + public static string GetChildValue (this XmlElement element, string childName) + { + if (element == null || String.IsNullOrEmpty (childName)) + return null; + + XmlElement child = element[childName]; + if (child == null) + return null; + + string ret = child.InnerText; + if (String.IsNullOrEmpty (ret)) + return null; + + return ret; + } + + public static bool GetAttributeValue (this XmlElement element, string attributeName, out T result) + { + result = default (T); + if (element == null) + return false; + return ConvertValue (element.GetAttribute (attributeName), out result); + } + + public static bool GetChildValue (this XmlElement element, string childName, out T result) + { + result = default (T); + if (element == null) + return false; + return ConvertValue (element.GetChildValue (childName), out result); + } + + static bool ConvertValue (string value, out T result) + { + result = default (T); + if (value != null) + value = value.Trim (); + if (String.IsNullOrEmpty (value)) + return false; + + if (typeof (T) == typeof (string)) { + result = (T) ((object) value); + return true; + } + + try { + result = (T) Convert.ChangeType (value, typeof (T)); + } catch { + // ignore + return false; + } + + return true; + } + + public static XmlElement GetChildElement (this XmlElement parent, XmlNamespaceManager nsmgr, string xpath, bool required = true) + { + var ret = parent.SelectSingleNode (xpath, nsmgr) as XmlElement; + if (ret == null && required) + throw new InvalidOperationException ("Required child element not found"); + return ret; + } + } +} diff --git a/tools/fastdev/Makefile b/tools/fastdev/Makefile new file mode 100644 index 00000000000..808f47562a9 --- /dev/null +++ b/tools/fastdev/Makefile @@ -0,0 +1,21 @@ +include ../../env.config +include $(TOP)/mk/build.make + +VERBOSITY=diag +SLN=fastdevtools.csproj +MSBUILD_ARGS=/verbosity:$(VERBOSITY) /nologo /restore +MSBUILD=msbuild $(MSBUILD_ARGS) +srcdir=$(shell pwd) + +OUTPUT_FIND = $(ENABLED_RUNTIMES:%=$(BUILDDIR)/lib/xamarin.android/xbuild/Xamarin/Android/lib/%/xamarin.find) +OUTPUT_STAT = $(ENABLED_RUNTIMES:%=$(BUILDDIR)/lib/xamarin.android/xbuild/Xamarin/Android/lib/%/xamarin.stat) +OUTPUT_SYNC = $(ENABLED_RUNTIMES:%=$(BUILDDIR)/lib/xamarin.android/xbuild/Xamarin/Android/lib/%/xamarin.sync) + +OUTPUT = $(OUTPUT_FIND) $(OUTPUT_STAT) $(OUTPUT_SYNC) +SRC = $(SLN) xamarin.find/CMakeLists.txt xamarin.find/main.cc xamarin.stat/CMakeLists.txt xamarin.stat/main.cc xamarin.sync/CMakeLists.txt xamarin.sync/main.cc + +all $(OUTPUT):: $(SRC) + -$(MSBUILD) $(SLN) /property:Configuration=$(CONFIGURATION) + +clean:: + -$(MSBUILD) $(SLN) /property:Configuration=$(CONFIGURATION) /t:Clean \ No newline at end of file diff --git a/tools/fastdev/elfcleaner/CMakeLists.txt b/tools/fastdev/elfcleaner/CMakeLists.txt new file mode 100644 index 00000000000..bba82e0fe5f --- /dev/null +++ b/tools/fastdev/elfcleaner/CMakeLists.txt @@ -0,0 +1,33 @@ +# Set the minimum version of CMake that can be used +# To find the cmake version run +# $ cmake --version +cmake_minimum_required(VERSION 3.5) + +# Set the project name +project (elfcleaner C CXX) + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_EXTENSIONS ON) + +set(CMAKE_C_STANDARD 99) +set(CMAKE_C_STANDARD_REQUIRED ON) + +if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) + message("Setting build type to 'Release' as none was specified.") + set(CMAKE_BUILD_TYPE Release CACHE STRING "Choose the type of build." FORCE) + # Set the possible values of build type for cmake-gui + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug" "Release" + "MinSizeRel" "Release") +endif() + +set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -O2 -Wall") +set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O2 -Wall") + +set(ELFCLEANER_SOURCES + ../../../external/termux-elf-cleaner/termux-elf-cleaner.cpp + ) + +# Add an executable +add_executable(elfcleaner ${ELFCLEANER_SOURCES}) +set_target_properties(elfcleaner PROPERTIES LINK_FLAGS_RELEASE -s) \ No newline at end of file diff --git a/tools/fastdev/fastdev.sln b/tools/fastdev/fastdev.sln new file mode 100644 index 00000000000..46c7c774c9a --- /dev/null +++ b/tools/fastdev/fastdev.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.5.002.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "fastdevtools", "fastdevtools.csproj", "{300AEB8B-01B3-4B78-81D7-8FEA92C54B1D}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {300AEB8B-01B3-4B78-81D7-8FEA92C54B1D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {300AEB8B-01B3-4B78-81D7-8FEA92C54B1D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {300AEB8B-01B3-4B78-81D7-8FEA92C54B1D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {300AEB8B-01B3-4B78-81D7-8FEA92C54B1D}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {AA094E47-8CBF-49F2-832E-1BE7CE119CE7} + EndGlobalSection +EndGlobal diff --git a/tools/fastdev/fastdevtools.csproj b/tools/fastdev/fastdevtools.csproj new file mode 100644 index 00000000000..8b222937ee4 --- /dev/null +++ b/tools/fastdev/fastdevtools.csproj @@ -0,0 +1,19 @@ + + + + + $(NoWarn);CA1305 + + + + 12 + netstandard2.0 + $(MicrosoftAndroidSdkOutDir)lib + false + + $(MicrosoftAndroidSdkOutDir.TrimEnd('\\').TrimEnd('/')) + + + + + diff --git a/tools/fastdev/fastdevtools.projitems b/tools/fastdev/fastdevtools.projitems new file mode 100644 index 00000000000..7ee0d0564ae --- /dev/null +++ b/tools/fastdev/fastdevtools.projitems @@ -0,0 +1,47 @@ + + + + + 21 + %(Identity) + + + 21 + %(Identity) + + + 16 + %(Identity) + + + 16 + %(Identity) + + + + + xamarin.sync + ..\..\..\..\xamarin.sync + $(DebuggingToolsOutputDirectory)\lib\%(FastDevBuildItems.Arch) + -DLZ4_SRC_DIR=../../../external/lz4/lib -DANDROID_NATIVE_API_LEVEL=%(FastDevBuildItems.NativeApiLevel) -DANDROID_PLATFORM=android-%(FastDevBuildItems.NativeApiLevel) + + + xamarin.find + ..\..\..\..\xamarin.find + $(DebuggingToolsOutputDirectory)\lib\%(FastDevBuildItems.Arch) + -DANDROID_NATIVE_API_LEVEL=%(FastDevBuildItems.NativeApiLevel) -DANDROID_PLATFORM=android-%(FastDevBuildItems.NativeApiLevel) + + + xamarin.stat + ..\..\..\..\xamarin.stat + $(DebuggingToolsOutputDirectory)\lib\%(FastDevBuildItems.Arch) + -DANDROID_NATIVE_API_LEVEL=%(FastDevBuildItems.NativeApiLevel) -DANDROID_PLATFORM=android-%(FastDevBuildItems.NativeApiLevel) + + + xamarin.cp + ..\..\..\..\xamarin.cp + $(DebuggingToolsOutputDirectory)\lib\%(FastDevBuildItems.Arch) + -DANDROID_NATIVE_API_LEVEL=%(FastDevBuildItems.NativeApiLevel) -DANDROID_PLATFORM=android-%(FastDevBuildItems.NativeApiLevel) + + + \ No newline at end of file diff --git a/tools/fastdev/fastdevtools.targets b/tools/fastdev/fastdevtools.targets new file mode 100644 index 00000000000..50e991890cb --- /dev/null +++ b/tools/fastdev/fastdevtools.targets @@ -0,0 +1,57 @@ + + + + + + + <_CmakeToolPath Condition=" !Exists('$(CmakePath)') " Include="$(AndroidSdkDirectory)\cmake\*\bin\cmake*" /> + <_NinjaToolPath Condition=" !Exists('$(NinjaPath)') " Include="$(AndroidSdkDirectory)\cmake\*\bin\ninja*" /> + + + %(_CmakeToolPath.Identity) + cmake + %(_NinjaToolPath.Identity) + ninja + <_CmakeAndroidFlags>-GNinja -DENABLE_NDK=ON -DCMAKE_MAKE_PROGRAM="$(NinjaPath)" -DANDROID_TOOLCHAIN=clang -DANDROID_SUPPORT_FLEXIBLE_PAGE_SIZES=ON + <_CmakeAndroidFlags>$(_CmakeAndroidFlags) -DCMAKE_TOOLCHAIN_FILE="$(AndroidNdkDirectory)/build/cmake/android.toolchain.cmake" + <_CmakeAndroidFlags>$(_CmakeAndroidFlags) -DANDROID_NDK="$(AndroidNdkDirectory)" -DCMAKE_BUILD_TYPE=Debug + + + + + + + + + + + $(IntermediateOutputPath)elfcleaner + ..\..\..\elfcleaner + elfcleaner + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/tools/fastdev/xamarin.cp/CMakeLists.txt b/tools/fastdev/xamarin.cp/CMakeLists.txt new file mode 100644 index 00000000000..87c50a25f65 --- /dev/null +++ b/tools/fastdev/xamarin.cp/CMakeLists.txt @@ -0,0 +1,52 @@ +# Set the minimum version of CMake that can be used +# To find the cmake version run +# $ cmake --version +cmake_minimum_required(VERSION 3.5) + +# Set the project name +project (xamarin.cp C CXX) +#set(CMAKE_FIND_LIBRARY_SUFFIXES ".a") +#find_package(ZLIB REQUIRED) + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_EXTENSIONS ON) + +set(CMAKE_C_STANDARD 99) +set(CMAKE_C_STANDARD_REQUIRED ON) + +option(ENABLE_NDK "Build with Android's NDK" OFF) + +if(ENABLE_NDK) + add_definitions("-DPLATFORM_ANDROID") + add_definitions("-DANDROID") + add_definitions("-DLINUX -Dlinux -D__linux__") +endif() + +if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) + message("Setting build type to 'Release' as none was specified.") + set(CMAKE_BUILD_TYPE Release CACHE STRING "Choose the type of build." FORCE) + # Set the possible values of build type for cmake-gui + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug" "Release" + "MinSizeRel" "Release") +endif() + +set(COMMON_COMPILER_OPTIONS + -O2 + -Wall + -fstack-protector-strong + -fstrict-return + -fno-omit-frame-pointer +) + +add_compile_options("$<$:${COMMON_COMPILER_OPTIONS}>") +add_compile_options("$<$:${COMMON_COMPILER_OPTIONS}>") +#set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "../../../../out/${ANDROID_ABI}") + +set(XAMARINCP_SOURCES + main.cc + ) + +# Add an executable +add_executable(xamarin.cp ${XAMARINCP_SOURCES}) +set_target_properties(xamarin.cp PROPERTIES LINK_FLAGS_RELEASE -s) diff --git a/tools/fastdev/xamarin.cp/main.cc b/tools/fastdev/xamarin.cp/main.cc new file mode 100644 index 00000000000..b4115f2530e --- /dev/null +++ b/tools/fastdev/xamarin.cp/main.cc @@ -0,0 +1,111 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +void printHelp (); + +int main (int argc, char **argv) +{ + if (argc <= 3) { + printHelp (); + return 1; + } + + uint64_t modifieddatetime = strtoull (argv[3], nullptr, 10); + if (modifieddatetime == 0 || modifieddatetime == ULLONG_MAX || errno == ERANGE) { + fprintf(stderr, "error: invalid argument '%s'.", argv[4]); + printHelp (); + return 1; + } + + struct stat fi; + decltype (fi.st_blksize) blockSize; + int ret = stat (".", &fi); + + if (ret == 0 && fi.st_blksize >= 0) { + blockSize = fi.st_blksize; + } else { + blockSize = 4096; + } + + const char *sourcePath = argv[1]; + const char *destinationPath = argv[2]; + + FILE *sourceFile = fopen (sourcePath, "rb"); + if (sourceFile == nullptr) { + perror ("Error opening source file"); + return 1; + } + + if (access (destinationPath, F_OK) != -1) { + if (access (destinationPath, W_OK) == -1) { + if (chmod (destinationPath, S_IWUSR | S_IRUSR) == -1) { + if (remove (destinationPath) == -1) { + fprintf (stderr, "error: could not set write permissions on '%s': %s", destinationPath, strerror (errno)); + return 1; + } + } + } + } + + FILE *destinationFile = fopen (destinationPath, "wb"); + if (destinationFile == nullptr) { + perror ("Error opening destination file"); + fclose (sourceFile); + return 1; + } + + uint8_t buffer[blockSize]; + size_t bytesRead; + while ((bytesRead = fread(buffer, 1, sizeof(buffer), sourceFile)) > 0) { + if (fwrite (buffer, 1, bytesRead, destinationFile) != bytesRead) { + perror ("Error writing to destination file"); + fclose (sourceFile); + fclose (destinationFile); + return 1; + } + } + + fclose (sourceFile); + fclose (destinationFile); + + timeval modifiedtimes[] { + { + .tv_sec = static_cast (modifieddatetime / 1000), + .tv_usec = static_cast (modifieddatetime % 1000), + }, + + { + .tv_sec = static_cast (modifieddatetime / 1000), + .tv_usec = static_cast (modifieddatetime % 1000), + }, + }; + + utimes (argv[2], modifiedtimes); + if (chmod (argv[2], S_IRUSR) == -1) { + fprintf (stderr, "error: could not set read permissions on '%s'. %s", argv[2], strerror (errno)); + return 1; + } + + printf ("moved [%s] to [%s] modifieddate [%" PRId64 "]\n", argv[1], argv[2], modifieddatetime); + + return 0; +} + +void printHelp () +{ + printf ("xamarin.cp\n"); + printf ("Usage: xamarin.cp \n"); + printf ("\t : The source file to copy (required). \n"); + printf ("\t : The destination file(required). \n"); + printf ("\t : The modified date to use for this file in unix time.\n"); +} diff --git a/tools/fastdev/xamarin.find/CMakeLists.txt b/tools/fastdev/xamarin.find/CMakeLists.txt new file mode 100644 index 00000000000..efd6d0fb459 --- /dev/null +++ b/tools/fastdev/xamarin.find/CMakeLists.txt @@ -0,0 +1,53 @@ +# Set the minimum version of CMake that can be used +# To find the cmake version run +# $ cmake --version +cmake_minimum_required(VERSION 3.5) + +# Set the project name +project (xamarin.find C CXX) +#set(CMAKE_FIND_LIBRARY_SUFFIXES ".a") +#find_package(ZLIB REQUIRED) + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_EXTENSIONS ON) + +set(CMAKE_C_STANDARD 99) +set(CMAKE_C_STANDARD_REQUIRED ON) + +option(ENABLE_NDK "Build with Android's NDK" OFF) + +if(ENABLE_NDK) + add_definitions("-DPLATFORM_ANDROID") + add_definitions("-DANDROID") + add_definitions("-DLINUX -Dlinux -D__linux__") +endif() + +if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) + message("Setting build type to 'Release' as none was specified.") + set(CMAKE_BUILD_TYPE Release CACHE STRING "Choose the type of build." FORCE) + # Set the possible values of build type for cmake-gui + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug" "Release" + "MinSizeRel" "Release") +endif() + +set(COMMON_COMPILER_OPTIONS + -O2 + -Wall + -fstack-protector-strong + -fstrict-return + -fno-omit-frame-pointer +) + +add_compile_options("$<$:${COMMON_COMPILER_OPTIONS}>") +add_compile_options("$<$:${COMMON_COMPILER_OPTIONS}>") +#set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "../../../../out/${ANDROID_ABI}") + + +set(XAMARINFIND_SOURCES + main.cc + ) + +# Add an executable +add_executable(xamarin.find ${XAMARINFIND_SOURCES}) +set_target_properties(xamarin.find PROPERTIES LINK_FLAGS_RELEASE -s) diff --git a/tools/fastdev/xamarin.find/main.cc b/tools/fastdev/xamarin.find/main.cc new file mode 100644 index 00000000000..2f317cbba8c --- /dev/null +++ b/tools/fastdev/xamarin.find/main.cc @@ -0,0 +1,111 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +constexpr uint64_t THOUSAND = 1000; + +void findAllFiles(const char *path); +void printHelp (); + +bool verbose = 0; +bool debug = 0; +int main(int argc, char **argv) +{ + if (argc <= 1) { + printHelp (); + return 1; + } + int opt; + while((opt = getopt (argc, argv, "vd")) != -1) { + switch (opt) + { + case 'v': + verbose = 1; + break; + case 'd': + debug = 1; + break; + default: + printHelp (); + return 1; + } + } + if (chdir (argv[optind]) == -1) { + fprintf (stderr, "error: failed to change current directory to '%s'. %s.", argv[optind], strerror (errno)); + return 1; + } + findAllFiles("."); + return 0; +} + +void printHelp () +{ + printf ("xamarin.find\n"); + printf ("Usage: xamarin.find -v -d \n"); + printf ("\t-v : Print Size and Last Modified Time of the files (optional)\n"); + printf ("\t-d : Print additional diagnostics\n"); + printf ("\t : The directory to recursively list the files for (required). \n"); +} + +/** + * find all files and sub-directories recursively + * expected output is relative path to file. + * + * For example a call to + * xamarin.find /data/data/com.foo.bar/files/.__overriide__ + * + * would result in + * ./a.dll + * ./fr-FR/a.resources.dll + * + * for filesize output use -v + * + * xamarin.find -v /data/data/com.foo.bar/files/.__overriide__ + * ./a.dll 1025 3765434 + * ./fr-FR/a.resources.dll 23 12431341 + */ +void findAllFiles(const char *basePath) +{ + struct dirent *dp; + DIR *dir = opendir(basePath); + + // Unable to open directory stream + if (!dir) + return; + + while ((dp = readdir(dir)) != NULL) { + if (strcmp(dp->d_name, ".") == 0 || strcmp(dp->d_name, "..") == 0) + continue; + // Construct new path from our base path + char *path = new char[strlen (basePath)+strlen(dp->d_name)+2]; + strcpy(path, basePath); + strcat(path, "/"); + strcat(path, dp->d_name); + + if (dp->d_type == DT_REG) { + if (verbose) { + struct stat fi; + stat(path, &fi); + int64_t tv_sec = static_cast (fi.st_mtim.tv_sec); + int64_t tv_nsec = static_cast (fi.st_mtim.tv_nsec); + if (debug) + printf ("DEBUG: %s size:%" PRId64 " mtime:%" PRId64 " tv_sec:%" PRId64 " tv_nsec:%" PRId64 "\n", path, + static_cast ((tv_sec * THOUSAND) + (tv_nsec / THOUSAND)),static_cast (fi.st_size), static_cast (fi.st_mtim.tv_sec), static_cast (fi.st_mtim.tv_nsec)); + printf("%s\t%" PRId64 "\t%" PRId64 "\n", path, static_cast (fi.st_size), static_cast ((tv_sec * THOUSAND) + (tv_nsec / THOUSAND))); + } else { + printf("%s\n", path); + } + } + + findAllFiles(path); + delete[] path; + } + + closedir(dir); +} \ No newline at end of file diff --git a/tools/fastdev/xamarin.stat/CMakeLists.txt b/tools/fastdev/xamarin.stat/CMakeLists.txt new file mode 100644 index 00000000000..19af176744a --- /dev/null +++ b/tools/fastdev/xamarin.stat/CMakeLists.txt @@ -0,0 +1,53 @@ +# Set the minimum version of CMake that can be used +# To find the cmake version run +# $ cmake --version +cmake_minimum_required(VERSION 3.5) + +# Set the project name +project (xamarin.stat C CXX) +#set(CMAKE_FIND_LIBRARY_SUFFIXES ".a") +#find_package(ZLIB REQUIRED) + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_EXTENSIONS ON) + +set(CMAKE_C_STANDARD 99) +set(CMAKE_C_STANDARD_REQUIRED ON) + +option(ENABLE_NDK "Build with Android's NDK" OFF) + +if(ENABLE_NDK) + add_definitions("-DPLATFORM_ANDROID") + add_definitions("-DANDROID") + add_definitions("-DLINUX -Dlinux -D__linux__") +endif() + +if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) + message("Setting build type to 'Release' as none was specified.") + set(CMAKE_BUILD_TYPE Release CACHE STRING "Choose the type of build." FORCE) + # Set the possible values of build type for cmake-gui + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug" "Release" + "MinSizeRel" "Release") +endif() + +set(COMMON_COMPILER_OPTIONS + -O2 + -Wall + -fstack-protector-strong + -fstrict-return + -fno-omit-frame-pointer +) + +add_compile_options("$<$:${COMMON_COMPILER_OPTIONS}>") +add_compile_options("$<$:${COMMON_COMPILER_OPTIONS}>") +#set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "../../../../out/${ANDROID_ABI}") + + +set(XAMARINFIND_SOURCES + main.cc + ) + +# Add an executable +add_executable(xamarin.stat ${XAMARINFIND_SOURCES}) +set_target_properties(xamarin.stat PROPERTIES LINK_FLAGS_RELEASE -s) diff --git a/tools/fastdev/xamarin.stat/main.cc b/tools/fastdev/xamarin.stat/main.cc new file mode 100644 index 00000000000..7966f996df7 --- /dev/null +++ b/tools/fastdev/xamarin.stat/main.cc @@ -0,0 +1,28 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +int main(int argc, char **argv) +{ + struct stat fi; + if (argc <= 1) { + printf("Usage: xamarin.stat PATH\n"); + return 1; + } + int r = stat(argv[1], &fi); + if (r != 0) { + fprintf (stderr, "error: File or Directory '%s' does not exist. %s.\n", argv[1], strerror (errno)); + return 1; + } + if (fi.st_size >= 0) { + printf("%" PRId64 "\t%" PRId64 "\t%" PRId64 "\n", static_cast (fi.st_blksize), static_cast (fi.st_size), static_cast ((fi.st_mtim.tv_sec * 1000) + (fi.st_mtim.tv_nsec / 1000))); + return 0; + } + fprintf (stderr, "error: File or Directory '%s' does not exist. %s.\n", argv[1], strerror (errno)); + return 1; +} \ No newline at end of file diff --git a/tools/fastdev/xamarin.sync/CMakeLists.txt b/tools/fastdev/xamarin.sync/CMakeLists.txt new file mode 100644 index 00000000000..53f5cf8f4bb --- /dev/null +++ b/tools/fastdev/xamarin.sync/CMakeLists.txt @@ -0,0 +1,70 @@ +# Set the minimum version of CMake that can be used +# To find the cmake version run +# $ cmake --version +cmake_minimum_required(VERSION 3.5) + +# Set the project name +project (xamarin.sync C CXX) +#set(CMAKE_FIND_LIBRARY_SUFFIXES ".a") +#find_package(ZLIB REQUIRED) + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_EXTENSIONS ON) + +set(CMAKE_C_STANDARD 99) +set(CMAKE_C_STANDARD_REQUIRED ON) + +option(ENABLE_NDK "Build with Android's NDK" OFF) + +if(ENABLE_NDK) + add_definitions("-DPLATFORM_ANDROID") + add_definitions("-DANDROID") + add_definitions("-DLINUX -Dlinux -D__linux__") +endif() + +if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) + message("Setting build type to 'Release' as none was specified.") + set(CMAKE_BUILD_TYPE Release CACHE STRING "Choose the type of build." FORCE) + # Set the possible values of build type for cmake-gui + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug" "Release" + "MinSizeRel" "Release") +endif() + +set(COMMON_COMPILER_OPTIONS + -O2 + -Wall + -fstack-protector-strong + -fstrict-return + -fno-omit-frame-pointer +) + +add_compile_options("$<$:${COMMON_COMPILER_OPTIONS}>") +add_compile_options("$<$:${COMMON_COMPILER_OPTIONS}>") +#set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "../../../../out/${ANDROID_ABI}") + +if(NOT DEFINED ${LZ4_SRC_DIR}) + set(LZ4_SRC_DIR, "../../../external/lz4/lib") +endif() +set(LZ4_INCLUDE_DIR ${LZ4_SRC_DIR}) +set(LZ4_SOURCES + "${LZ4_SRC_DIR}/lz4.c" + ) + +include_directories(${LZ4_INCLUDE_DIR}) + +set(XAMARINCOPY_SOURCES + ${LZ4_SOURCES} + main.cc + ) + +# Add an executable +add_executable(xamarin.sync ${XAMARINCOPY_SOURCES}) +set_target_properties(xamarin.sync PROPERTIES LINK_FLAGS_RELEASE -s) + +#if(NOT APPLE) +# target_link_libraries(xamarin.sync -static) +#endif() + +#target_link_libraries(xamarin.sync "${ZLIB_LIBRARIES}") +#include_directories("${ZLIB_INCLUDE_DIRS}") \ No newline at end of file diff --git a/tools/fastdev/xamarin.sync/main.cc b/tools/fastdev/xamarin.sync/main.cc new file mode 100644 index 00000000000..a3f8963dcbd --- /dev/null +++ b/tools/fastdev/xamarin.sync/main.cc @@ -0,0 +1,212 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +constexpr uint64_t BILLION = 1000000000; + +void printHelp (); +bool closeOutfile (FILE * fp); + +// Streams stdio to a destination for a given number of bytes +// Arguments size destination. +int main(int argc, char **argv) +{ + if (argc < 5) + { + printHelp (); + return 1; + } + + timespec start; + timespec end; + clock_gettime(CLOCK_MONOTONIC, &start); + + unsigned long defaultBuffersize = strtoul (argv[1], nullptr, 10); + if (defaultBuffersize == 0) { + fprintf(stderr, "error: invalid argument '%s'.", argv[1]); + printHelp (); + return 1; + } + unsigned long size = strtoul (argv[2], nullptr, 10); + if (size == 0) { + fprintf(stderr, "error: invalid argument '%s'.", argv[2]); + printHelp (); + return 1; + } + char * outfile = argv[3]; + + errno = 0; + uint64_t modifieddatetime = strtoull (argv[4], nullptr, 10); + if (modifieddatetime == 0 || modifieddatetime == ULLONG_MAX || errno == ERANGE) { + fprintf(stderr, "error: invalid argument '%s'.", argv[4]); + printHelp (); + return 1; + } + + int count = 0; + int inTotal = 0; + unsigned int bufferSize = defaultBuffersize; + unsigned int bufferHeader; + + char *compr = new char[defaultBuffersize]; + char *uncompr = new char[defaultBuffersize]; + + size_t n = 0; + struct pollfd fds[1]; + + int timeout = 1000 * 5; // Timeout in milliseconds (5 seconds) + + //fds[0].fd = fileno (stdin); // Get file descriptor for stdin + //fds[0].events = POLLIN | POLLPRI; // Listen for data available events. + if (access (outfile, F_OK) != -1) { + if (access (outfile, W_OK) == -1) { + if (chmod (outfile, S_IWUSR | S_IRUSR) == -1) { + if (remove (outfile) == -1) { + fprintf (stderr, "error: could not set write permissions on '%s': %s", outfile, strerror (errno)); + return 1; + } + } + } + } + + + FILE * fp = fopen(outfile, "w"); + if (fp == nullptr) { + fprintf(stderr, "error: could not open '%s'.", outfile); + return 1; + } + while (count < size) { + int pr = 1; + fds[0].fd = fileno (stdin); + fds[0].events = POLLIN | POLLPRI | POLLRDNORM | POLLRDBAND | POLLERR; + fds[0].revents = 0; + do { + pr = poll(fds, 1, timeout); + } while (pr == -1 && errno == EINTR); + + // printf ("DEBUG pr= %d\n", pr); + // if (fds[0].revents != 0) { + // printf ("DEBUG Read data ok. data = %d of %d\n", count, size); + // } + if (pr == 0) { + clock_gettime(CLOCK_MONOTONIC, &end); + uint64_t time_taken = BILLION * (end.tv_sec - start.tv_sec) + end.tv_nsec - start.tv_nsec; + fprintf (stderr, "error: Could not read data from stdin. The operation timed out. [%" PRIu64 "] ", time_taken); + if (fclose (fp) != 0) { + fprintf (stderr, "error: failed to read stdin data."); + } + return 1; + } + if (pr == -1) { + clock_gettime(CLOCK_MONOTONIC, &end); + uint64_t time_taken = BILLION * (end.tv_sec - start.tv_sec) + end.tv_nsec - start.tv_nsec; + fprintf (stderr, "error: Could not read data from stdin. An error '%s' occurred. [%" PRIu64 "]", strerror (errno), time_taken); + if (fclose (fp) != 0) { + fprintf (stderr, "error: failed to read stdin data."); + } + return 1; + } + // read length of compressed buffer this is a 4 byte (u_int) value. + n = fread(&bufferHeader, 1, sizeof(bufferHeader), stdin); + switch (n) + { + // We should only ever read 4 bytes of data for the header. + // If we don't that is an error and we should abort. + case 4: { + inTotal += 4; + bufferSize = ntohl (bufferHeader); + if (bufferSize > defaultBuffersize) { + delete [] compr; + delete [] uncompr; + fprintf(stderr, "error: Data length %lu CANNOT be greater than %lu. ", bufferSize, defaultBuffersize); + if (fclose (fp) != 0) { + fprintf(stderr, "error: failed to write '%s' during data length error.", outfile); + } + return 1; + } + n = fread (compr, 1, bufferSize, stdin); + inTotal += n; + int decompressed = 0; + if (n == defaultBuffersize) { + memcpy (uncompr, compr, n); + decompressed = n; + } else { + decompressed = LZ4_decompress_safe (compr, uncompr, n, defaultBuffersize); + } + if (decompressed > 0) { + n = fwrite (uncompr, 1, decompressed, fp); + if (n != decompressed || ferror (fp) != 0) { + delete [] compr; + delete [] uncompr; + fprintf (stderr, "error: Failed to write data to '%s'. %s", outfile, strerror (errno)); + if (fclose (fp) != 0) { + fprintf(stderr, "error: failed to write '%s' during data write failure.", outfile); + } + return 1; + } + count += n; + } + break; + } + default: { + delete [] compr; + delete [] uncompr; + fprintf(stderr, "error: Failed to read package length."); + if (fclose (fp) != 0) { + fprintf(stderr, "error: failed to write '%s' during package length failure.", outfile); + } + return 1; + } + } + } + if (fclose (fp) != 0) { + fprintf(stderr, "error: failed to write '%s'.", outfile); + delete [] compr; + delete [] uncompr; + return 1; + } + timeval modifiedtimes[] { + { + .tv_sec = static_cast (modifieddatetime / 1000), + .tv_usec = static_cast (modifieddatetime % 1000), + }, + + { + .tv_sec = static_cast (modifieddatetime / 1000), + .tv_usec = static_cast (modifieddatetime % 1000), + }, + }; + utimes (outfile, modifiedtimes); + if (chmod (outfile, S_IRUSR) == -1) { + fprintf (stderr, "error: could not set read permissions on '%s'. %s", outfile, strerror (errno)); + return 1; + } + delete [] compr; + delete [] uncompr; + clock_gettime(CLOCK_MONOTONIC, &end); + uint64_t time_taken = BILLION * (end.tv_sec - start.tv_sec) + end.tv_nsec - start.tv_nsec; + printf("wrote [%d] received [%d] time [%" PRIu64 "] modifieddate [%" PRId64 "]\n", count, inTotal, time_taken, modifieddatetime); + return 0; +} + +void printHelp () +{ + printf ("xamarin.sync\n"); + printf ("This tool reads encoded data from stdin and writes that data to disk."); + printf ("The incoming data are blocks of compressed L4Z data."); + printf ("Each block consists of a 4 byte length followed by the compressed data itself."); + printf ("Usage: xamarin.sync \n"); + printf ("\t : Size of the Buffer to use\n"); + printf ("\t : Size in Bytes of Input Stream\n"); + printf ("\t : File to write the data too.\n"); + printf ("\t : The modified date to use for this file in unix time.\n"); +} diff --git a/tools/workload-dependencies/WorkloadDependencies.proj b/tools/workload-dependencies/WorkloadDependencies.proj index 7253f281107..aac9488e2a7 100644 --- a/tools/workload-dependencies/WorkloadDependencies.proj +++ b/tools/workload-dependencies/WorkloadDependencies.proj @@ -51,8 +51,8 @@ DependsOnTargets="_CheckDeps"> - <_AndroidPlatformSupportFeed>$(MSBuildThisFileDirectory)/../../external/android-platform-support/Feeds/AndroidManifestFeed_d18.0.xml - <_Feed Condition=" Exists($(_AndroidPlatformSupportFeed)) ">$(_AndroidPlatformSupportFeed) + <_AndroidManifestFeed>$(MSBuildThisFileDirectory)..\..\src\Xamarin.Installer.AndroidSDK\Feeds\AndroidManifestFeed_d18.0.xml + <_Feed Condition=" Exists('$(_AndroidManifestFeed)') ">$(_AndroidManifestFeed) <_Feed Condition=" '$(_Feed)' == '' ">https://aka.ms/AndroidManifestFeed/d18-0 <_Project>$(MSBuildThisFileDirectory)workload-dependencies.csproj $(OutputPath)workload-manifest\WorkloadDependencies.json