From 907e108ec3025bc754ffbc852b030de440da710c Mon Sep 17 00:00:00 2001 From: Jonathan Pryor Date: Fri, 28 Jul 2017 21:51:07 -0400 Subject: [PATCH] [setup-windows] Provide a Windows installer I really didn't want to do this. I wanted the Xamarin.Android SDK on Windows to be usable side-by-side, so that multiple `oss-xamarin.android*.zip` files could be extracted, and developers could switch between them by overriding MSBuild properties. This is what was documented in commit 87ca2737, by overriding the `$(TargetFrameworkRootPath)` MSBuild property. There's just one "minor" problem with that approach: it only works if the project that is being built, and *all* project dependencies, are Xamarin.Android projects. If *any* other kind of dependency is brought in, this approach will no longer work, as the `GetReferenceAssemblies` target -- which looks for assemblies *rooted within `$(TargetFrameworkRootPath)`* -- won't be able to find them. Unfortunately, *everything of interest* doesn't fit within this restriction. A Xamarin.Forms app, or any other app using PCL assemblies, quickly runs afoul of it: error MSB3644: The reference assemblies for framework ".NETPortable,Version=v4.5,Profile=Profile259" were not found. Consequently, the instructions from commit 87ca2737 are borderline worthless. There is only one way to actually build such projects, and that's to install Xamarin.Android *system-wide*, so that MSBuild's `GetReferenceAssemblies` target can find everything it needs. :-( Thus, we need an "installer." I was hoping for a simple `.cmd` file, but that stymied me. Then I hoped for a PowerShell script, but installation requires access to the `%VSINSTALLDIR%` environment variable, which is only exported from Visual Studio Developer Command Prompts, and all the solutions I found to import the VS command prompt environment info into PowerShell looked decidedly ugly. Which brings us to a minimal effort command-line installer: `setup-windows.exe`. This utility backs up existing installs, then creates symbolic links within the system-wide directories, pointing them into the extracted `oss-xamarin.android*.zip` contents which contains `setup-windows.exe`. `setup-windows.exe /uninstall` is also provided, to put directories back the way they were found. `Documentation/UsingJenkinsBuildArtifacts.md` has been updated accordingly. --- Documentation/UsingJenkinsBuildArtifacts.md | 72 +++++--- Xamarin.Android.sln | 57 +++--- .../setup-windows/Properties/AssemblyInfo.cs | 26 +++ tools/setup-windows/SetupWindows.cs | 162 ++++++++++++++++++ tools/setup-windows/setup-windows.csproj | 39 +++++ 5 files changed, 307 insertions(+), 49 deletions(-) create mode 100644 tools/setup-windows/Properties/AssemblyInfo.cs create mode 100644 tools/setup-windows/SetupWindows.cs create mode 100644 tools/setup-windows/setup-windows.csproj diff --git a/Documentation/UsingJenkinsBuildArtifacts.md b/Documentation/UsingJenkinsBuildArtifacts.md index 0ca38b9a44c..1b4b3f88ae2 100644 --- a/Documentation/UsingJenkinsBuildArtifacts.md +++ b/Documentation/UsingJenkinsBuildArtifacts.md @@ -44,22 +44,8 @@ and download the `oss-xamarin.android*.zip` file, e.g. There are two ways to install a Jenkins build of Xamarin.Android on Windows: -1. Through the `oss-xamarin.android*.zip` file. -2. Through the `Xamarin.Android.Sdk-OSS*.vsix` file. - -### `oss-xamarin.android*.zip` Installation - -Windows users can right-click the `oss-xamarin.android*.zip` file within -Windows Explorer and click **Extract All...**, and in the -**Extract Compressed (Zipped) Folders** dialog enter a *short* path such as -`C:\xa-sdk`. This is necessary because some of the contained filenames are -quite long. This will result in a path such as: - - C:\xa-sdk\oss-xamarin.android_v7.2.99.19_Darwin-x86_64_master_3b893cd\bin\Debug\bin\mono-symbolicate.cmd - -See the [**Command-line use: Windows**](#cmd-use-Windows) section for details -on using this installation within a **Developer Command Prompt for VS 2017** -window. +1. Through the `Xamarin.Android.Sdk-OSS*.vsix` file. +2. Through the `oss-xamarin.android*.zip` file. ### `Xamarin.Android.Sdk-OSS*.vsix` Installation @@ -103,6 +89,51 @@ Once you've selected the desired Visual Studio products, click the **Install** button to install the Xamarin.Android SDK extension into Visual Studio 2017. +### `oss-xamarin.android*.zip` Installation + +Windows users can right-click the `oss-xamarin.android*.zip` file within +Windows Explorer and click **Extract All...**, and in the +**Extract Compressed (Zipped) Folders** dialog enter a *short* path such as +`C:\xa-sdk`. This is necessary because some of the contained filenames are +quite long. This will result in a path such as: + + C:\xa-sdk\oss-xamarin.android_v7.4.99.57_Darwin-x86_64_master_97f08f7\bin\Debug\bin\setup-windows.exe + +Once the `.zip` file has been extracted, please run the `setup-windows.exe` +utility within the `bin\Debug` or `bin\Release` folders. If you have +Visual Studio 2017 installed, this utility *must* be run within an +Administrator-elevated **Developer Command Prompt for VS 2017** window: + +1. In the Start menu, search for **Developer Command Prompt for VS 2017**. +2. Right-click the **Developer Command Prompt for VS 2017** entry. +3. Click **Run as administrator**. + +Within the elevated command prompt, execute the `setup-windows.exe` program: + + > C:\xa-sdk\oss-xamarin.android_v7.4.99.57_Darwin-x86_64_master_97f08f7\bin\Debug\bin\setup-windows.exe + Executing: MKLINK /D "C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\MonoAndroid" "C:\xa-sdk\oss-xamarin.android_v7.4.99.57_Darwin-x86_64_master_97f08f7\bin\Debug\lib\xamarin.android\xbuild-frameworks\MonoAndroid" + Executing: MKLINK /D "C:\Program Files (x86)\Microsoft Visual Studio\2017\Enterprise\MSBuild\Xamarin\Android" "C:\xa-sdk\oss-xamarin.android_v7.4.99.57_Darwin-x86_64_master_97f08f7\bin\Debug\lib\xamarin.android\xbuild\Xamarin\Android" + Success! + +To uninstall, run `setup-windows.exe /uninstall`: + + > C:\xa-sdk\oss-xamarin.android_v7.4.99.57_Darwin-x86_64_master_97f08f7\bin\Debug\bin\setup-windows.exe /uninstall + +The `setup-windows.exe` utility checks for an existing Xamarin.Android install, +renames the existing directories for backup/easy restoration purposes, then +create symbolic links into the extracted Xamarin.Android directory. + +(Unfortunately, this means that you can't easily have side-by-side installs +of the Xamarin.Android SDK. Only one install can be active at a time.) + +If Visual Studio 2017 isn't installed, it should be fine to right-click +`setup-windows.exe` from within Windows Explorer, then click +**Run as administrator**. (`setup-windows.exe` will *not* do the correct thing +when Visual Studio 2017 is installed, as `%VSINSTALLDIR%` isn't properly +set outside of the Developer Command Prompt for VS 2017, so the *global* +`%ProgramFiles(x86)%\MSBuild\Xamarin\Android` directory is modified, not the +per-SKU Visual Studio 2017 directory.) + # Using Jenkins Build Artifacts ## Command-line use: Linux and macOS @@ -145,20 +176,11 @@ MSBuild properties: * `AndroidSdkDirectory`: The location of the Android SDK. * `AndroidNdkDirectory`: The location of the Android NDK. * `JavaSdkDirectory`: The location of the Java SDK/JDK. -* `MonoAndroidBinDirectory`: The `xbuild\Xamarin\Android` directory in the - `oss-xamarin.android*.zip` installation. -* `MonoAndroidToolsDirectory`: The `xbuild\Xamarin\Android` directory in the - `oss-xamarin.android*.zip` installation. -* `TargetFrameworkRootPath`: The `xbuild-frameworks` directory in the - `oss-xamarin.android*.zip` installation. For example (using the paths from [Android SDK Setup](#Android_SDK_Setup)): msbuild /p:AndroidSdkDirectory="C:\xa-sdk\android-sdk" ^ /p:AndroidNdkDirectory="C:\xa-sdk\android-ndk\android-ndk-r14" ^ - /p:MonoAndroidBinDirectory="C:\xa-sdk\oss-xamarin.android_v7.2.99.19_Darwin-x86_64_master_3b893cd\bin\Debug\lib\xamarin.android\xbuild\Xamarin\Android" ^ - /p:MonoAndroidToolsDirectory="C:\xa-sdk\oss-xamarin.android_v7.2.99.19_Darwin-x86_64_master_3b893cd\bin\Debug\lib\xamarin.android\xbuild\Xamarin\Android" ^ - /p:TargetFrameworkRootPath="C:\xa-sdk\oss-xamarin.android_v7.2.99.19_Darwin-x86_64_master_3b893cd\bin\Debug\lib\xamarin.android\xbuild-frameworks" ^ /t:SignAndroidPackage ^ samples\HelloWorld\HelloWorld.csproj diff --git a/Xamarin.Android.sln b/Xamarin.Android.sln index 01220fe8f55..cefbf33946c 100644 --- a/Xamarin.Android.sln +++ b/Xamarin.Android.sln @@ -29,10 +29,6 @@ Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "Xamarin.Android.NamingCusto EndProject Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "Java.Interop.Tools.TypeNameMappings", "external\Java.Interop\src\Java.Interop.Tools.TypeNameMappings\Java.Interop.Tools.TypeNameMappings.shproj", "{E706B6F2-5562-4765-8F07-8CF84A797B30}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Mono.Android", "src\Mono.Android\Mono.Android.csproj", "{66CF299A-CE95-4131-BCD8-DB66E30C4BF7}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Mono.Android.Export", "src\Mono.Android.Export\Mono.Android.Export.csproj", "{B8105878-D423-4159-A3E7-028298281EC6}" -EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Xamarin.Android.Tools.Aidl", "src\Xamarin.Android.Tools.Aidl\Xamarin.Android.Tools.Aidl.csproj", "{D27AD8F7-7710-40BE-B03B-55EFBEC13C44}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Xamarin.Android.Build.Tasks", "src\Xamarin.Android.Build.Tasks\Xamarin.Android.Build.Tasks.csproj", "{3F1F2F50-AF1A-4A5A-BEDB-193372F068D7}" @@ -111,6 +107,12 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "create-vsix", "build-tools\ EndProject Project("{9344BDBB-3E7F-41FC-A0DD-8665D75EE146}") = "netstandard", "src\netstandard\netstandard.mdproj", "{93614CB8-4564-43B9-93B0-4AF4B3B16AAE}" EndProject +Project("{9344BDBB-3E7F-41FC-A0DD-8665D75EE146}") = "Mono.Android", "src\Mono.Android\Mono.Android.csproj", "{8B10353F-2D58-41D4-BB26-E75218A4A800}" +EndProject +Project("{9344BDBB-3E7F-41FC-A0DD-8665D75EE146}") = "Mono.Android.Export", "src\Mono.Android.Export\Mono.Android.Export.csproj", "{C31712E0-CFB1-47E5-855B-D0B59979F7B0}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "setup-windows", "tools\setup-windows\setup-windows.csproj", "{73DF9E10-E933-4222-B8E1-F4536FFF9FAD}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|AnyCPU = Debug|AnyCPU @@ -135,14 +137,6 @@ Global {3FC3E78B-F7D4-42EA-BBE8-4535DF42BFF8}.XAIntegrationDebug|Any CPU.Build.0 = Debug|Any CPU {3FC3E78B-F7D4-42EA-BBE8-4535DF42BFF8}.XAIntegrationRelease|Any CPU.ActiveCfg = Debug|An yCPU {3FC3E78B-F7D4-42EA-BBE8-4535DF42BFF8}.XAIntegrationRelease|Any CPU.Build.0 = Debug|An yCPU - {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 - {66CF299A-CE95-4131-BCD8-DB66E30C4BF7}.XAIntegrationDebug|Any CPU.ActiveCfg = Debug|Any CPU - {66CF299A-CE95-4131-BCD8-DB66E30C4BF7}.XAIntegrationDebug|Any CPU.Build.0 = Debug|Any CPU - {66CF299A-CE95-4131-BCD8-DB66E30C4BF7}.XAIntegrationRelease|Any CPU.ActiveCfg = Debug|Any CPU - {66CF299A-CE95-4131-BCD8-DB66E30C4BF7}.XAIntegrationRelease|Any CPU.Build.0 = Debug|Any CPU {8FF78EB6-6FC8-46A7-8A15-EBBA9045C5FA}.Debug|AnyCPU.ActiveCfg = Debug|Any CPU {8FF78EB6-6FC8-46A7-8A15-EBBA9045C5FA}.Debug|AnyCPU.Build.0 = Debug|Any CPU {8FF78EB6-6FC8-46A7-8A15-EBBA9045C5FA}.Release|AnyCPU.ActiveCfg = Release|Any CPU @@ -181,14 +175,6 @@ Global {B17475BC-45A2-47A3-B8FC-62F3A0959EE0}.XAIntegrationDebug|Any CPU.Build.0 = Debug|Any CPU {B17475BC-45A2-47A3-B8FC-62F3A0959EE0}.XAIntegrationRelease|Any CPU.ActiveCfg = Debug|Any CPU {B17475BC-45A2-47A3-B8FC-62F3A0959EE0}.XAIntegrationRelease|Any CPU.Build.0 = Debug|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 - {B8105878-D423-4159-A3E7-028298281EC6}.XAIntegrationDebug|Any CPU.ActiveCfg = Debug|Any CPU - {B8105878-D423-4159-A3E7-028298281EC6}.XAIntegrationDebug|Any CPU.Build.0 = Debug|Any CPU - {B8105878-D423-4159-A3E7-028298281EC6}.XAIntegrationRelease|Any CPU.ActiveCfg = Debug|Any CPU - {B8105878-D423-4159-A3E7-028298281EC6}.XAIntegrationRelease|Any CPU.Build.0 = Debug|Any CPU {53EE4C57-1C03-405A-8243-8DA539546C88}.XAIntegrationDebug|Any CPU.ActiveCfg = Debug|Any CPU {53EE4C57-1C03-405A-8243-8DA539546C88}.XAIntegrationRelease|Any CPU.ActiveCfg = Debug|Any CPU {53EE4C57-1C03-405A-8243-8DA539546C88}.Debug|AnyCPU.Build.0 = Debug|Any CPU @@ -509,6 +495,30 @@ Global {93614CB8-4564-43B9-93B0-4AF4B3B16AAE}.XAIntegrationDebug|AnyCPU.Build.0 = Debug|Any CPU {93614CB8-4564-43B9-93B0-4AF4B3B16AAE}.XAIntegrationRelease|AnyCPU.ActiveCfg = Debug|Any CPU {93614CB8-4564-43B9-93B0-4AF4B3B16AAE}.XAIntegrationRelease|AnyCPU.Build.0 = Debug|Any CPU + {8B10353F-2D58-41D4-BB26-E75218A4A800}.Debug|AnyCPU.ActiveCfg = Debug|Any CPU + {8B10353F-2D58-41D4-BB26-E75218A4A800}.Debug|AnyCPU.Build.0 = Debug|Any CPU + {8B10353F-2D58-41D4-BB26-E75218A4A800}.Release|AnyCPU.ActiveCfg = Release|Any CPU + {8B10353F-2D58-41D4-BB26-E75218A4A800}.Release|AnyCPU.Build.0 = Release|Any CPU + {8B10353F-2D58-41D4-BB26-E75218A4A800}.XAIntegrationDebug|Any CPU.ActiveCfg = Debug|Any CPU + {8B10353F-2D58-41D4-BB26-E75218A4A800}.XAIntegrationDebug|Any CPU.Build.0 = Debug|Any CPU + {8B10353F-2D58-41D4-BB26-E75218A4A800}.XAIntegrationRelease|Any CPU.ActiveCfg = Debug|Any CPU + {8B10353F-2D58-41D4-BB26-E75218A4A800}.XAIntegrationRelease|Any CPU.Build.0 = Debug|Any CPU + {C31712E0-CFB1-47E5-855B-D0B59979F7B0}.Debug|AnyCPU.ActiveCfg = Debug|Any CPU + {C31712E0-CFB1-47E5-855B-D0B59979F7B0}.Debug|AnyCPU.Build.0 = Debug|Any CPU + {C31712E0-CFB1-47E5-855B-D0B59979F7B0}.Release|AnyCPU.ActiveCfg = Release|Any CPU + {C31712E0-CFB1-47E5-855B-D0B59979F7B0}.Release|AnyCPU.Build.0 = Release|Any CPU + {C31712E0-CFB1-47E5-855B-D0B59979F7B0}.XAIntegrationDebug|Any CPU.ActiveCfg = Debug|Any CPU + {C31712E0-CFB1-47E5-855B-D0B59979F7B0}.XAIntegrationDebug|Any CPU.Build.0 = Debug|Any CPU + {C31712E0-CFB1-47E5-855B-D0B59979F7B0}.XAIntegrationRelease|Any CPU.ActiveCfg = Debug|Any CPU + {C31712E0-CFB1-47E5-855B-D0B59979F7B0}.XAIntegrationRelease|Any CPU.Build.0 = Debug|Any CPU + {73DF9E10-E933-4222-B8E1-F4536FFF9FAD}.Debug|AnyCPU.ActiveCfg = Debug|AnyCPU + {73DF9E10-E933-4222-B8E1-F4536FFF9FAD}.Debug|AnyCPU.Build.0 = Debug|AnyCPU + {73DF9E10-E933-4222-B8E1-F4536FFF9FAD}.Release|AnyCPU.ActiveCfg = Release|AnyCPU + {73DF9E10-E933-4222-B8E1-F4536FFF9FAD}.Release|AnyCPU.Build.0 = Release|AnyCPU + {73DF9E10-E933-4222-B8E1-F4536FFF9FAD}.XAIntegrationDebug|AnyCPU.ActiveCfg = Debug|AnyCPU + {73DF9E10-E933-4222-B8E1-F4536FFF9FAD}.XAIntegrationDebug|AnyCPU.Build.0 = Debug|AnyCPU + {73DF9E10-E933-4222-B8E1-F4536FFF9FAD}.XAIntegrationRelease|AnyCPU.ActiveCfg = Release|AnyCPU + {73DF9E10-E933-4222-B8E1-F4536FFF9FAD}.XAIntegrationRelease|AnyCPU.Build.0 = Release|AnyCPU EndGlobalSection GlobalSection(NestedProjects) = preSolution {8FF78EB6-6FC8-46A7-8A15-EBBA9045C5FA} = {E351F97D-EA4F-4E7F-AAA0-8EBB1F2A4A62} @@ -521,8 +531,6 @@ Global {FE789F04-5E95-42C5-AEF1-E33F8DF06B3F} = {04E3E11E-B47D-4599-8AFC-50515A95E715} {74598F5C-B8CC-4CE6-8EE2-AB9CA1400076} = {04E3E11E-B47D-4599-8AFC-50515A95E715} {E706B6F2-5562-4765-8F07-8CF84A797B30} = {04E3E11E-B47D-4599-8AFC-50515A95E715} - {66CF299A-CE95-4131-BCD8-DB66E30C4BF7} = {04E3E11E-B47D-4599-8AFC-50515A95E715} - {B8105878-D423-4159-A3E7-028298281EC6} = {04E3E11E-B47D-4599-8AFC-50515A95E715} {D27AD8F7-7710-40BE-B03B-55EFBEC13C44} = {04E3E11E-B47D-4599-8AFC-50515A95E715} {3F1F2F50-AF1A-4A5A-BEDB-193372F068D7} = {04E3E11E-B47D-4599-8AFC-50515A95E715} {91713046-C358-4647-B162-ED4E1442F3D8} = {04E3E11E-B47D-4599-8AFC-50515A95E715} @@ -562,11 +570,12 @@ Global {BD1D66BF-5AC7-4926-8EBE-B2198A112EB0} = {CAB438D8-B0F5-4AF0-BEBD-9E2ADBD7B483} {94756FEB-1F64-411D-A18E-81B5158F776A} = {E351F97D-EA4F-4E7F-AAA0-8EBB1F2A4A62} {93614CB8-4564-43B9-93B0-4AF4B3B16AAE} = {04E3E11E-B47D-4599-8AFC-50515A95E715} + {8B10353F-2D58-41D4-BB26-E75218A4A800} = {04E3E11E-B47D-4599-8AFC-50515A95E715} + {C31712E0-CFB1-47E5-855B-D0B59979F7B0} = {04E3E11E-B47D-4599-8AFC-50515A95E715} + {73DF9E10-E933-4222-B8E1-F4536FFF9FAD} = {864062D3-A415-4A6F-9324-5820237BA058} EndGlobalSection GlobalSection(MonoDevelopProperties) = preSolution Policies = $0 $0.DotNetNamingPolicy = $1 - $1.DirectoryNamespaceAssociation = None - $1.ResourceNamePolicy = FileFormatDefault EndGlobalSection EndGlobal diff --git a/tools/setup-windows/Properties/AssemblyInfo.cs b/tools/setup-windows/Properties/AssemblyInfo.cs new file mode 100644 index 00000000000..bfa4525d92e --- /dev/null +++ b/tools/setup-windows/Properties/AssemblyInfo.cs @@ -0,0 +1,26 @@ +using System.Reflection; +using System.Runtime.CompilerServices; + +// Information about this assembly is defined by the following attributes. +// Change them to the values specific to your project. + +[assembly: AssemblyTitle ("setup-windows")] +[assembly: AssemblyDescription ("")] +[assembly: AssemblyConfiguration ("")] +[assembly: AssemblyCompany ("Microsoft Corporation")] +[assembly: AssemblyProduct ("")] +[assembly: AssemblyCopyright ("")] +[assembly: AssemblyTrademark ("")] +[assembly: AssemblyCulture ("")] + +// The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}". +// The form "{Major}.{Minor}.*" will automatically update the build and revision, +// and "{Major}.{Minor}.{Build}.*" will update just the revision. + +[assembly: AssemblyVersion ("1.0.*")] + +// The following attributes are used to specify the signing key for the assembly, +// if desired. See the Mono documentation for more information about signing. + +//[assembly: AssemblyDelaySign(false)] +//[assembly: AssemblyKeyFile("")] diff --git a/tools/setup-windows/SetupWindows.cs b/tools/setup-windows/SetupWindows.cs new file mode 100644 index 00000000000..9858b4f3d5c --- /dev/null +++ b/tools/setup-windows/SetupWindows.cs @@ -0,0 +1,162 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text.RegularExpressions; + +namespace Xamarin.Android.Tools +{ + enum SymbolLinkFlag { + File = 0, + Directory = 1, + } + + class SetupWindows + { + static string AppName; + + public static int Main (string [] args) + { + // appPath is expected to be similar to: oss-xamarin.anroid-*/bin/Debug/bin/setup-windows.exe + var appPath = Environment.GetCommandLineArgs () [0]; + AppName = Path.GetFileName (appPath); + var appDir = Path.GetDirectoryName (appPath); + if (Path.GetFileName (appDir) != "bin") { + Console.Error.WriteLine ($"{AppName}: This program must be run from the `bin` directory."); + return 1; + } + // prefix should be: oss-xamarin.anroid-*/bin/Debug + var prefix = Path.GetDirectoryName (appDir); + var hash = XAZipFolderNameToHash (Path.GetFileName (Path.GetDirectoryName (Path.GetDirectoryName (prefix)))); + + var progFiles = Environment.GetEnvironmentVariable ("ProgramFiles(x86)"); + var vsInstall = Environment.GetEnvironmentVariable ("VSINSTALLDIR"); + if (string.IsNullOrEmpty (vsInstall)) { + vsInstall = progFiles; + } + var msbuildTargets = Path.Combine (vsInstall, "MSBuild", "Xamarin", "Android"); + var newTargets = Path.Combine (prefix, "lib", "xamarin.android", "xbuild", "Xamarin", "Android"); + var refAssemblies = Path.Combine (progFiles, "Reference Assemblies", "Microsoft", "Framework", "MonoAndroid"); + var newAssemblies = Path.Combine (prefix, "lib", "xamarin.android", "xbuild-frameworks", "MonoAndroid"); + + if (Path.DirectorySeparatorChar != '\\') { + Console.Error.WriteLine ($"{AppName}: This program is for use on Windows."); + return 1; + } + + if (args.Length == 0 || args.Any (v => string.Equals (v, "install", StringComparison.OrdinalIgnoreCase) || string.Equals (v, "/install", StringComparison.OrdinalIgnoreCase))) { + return Install (msbuildTargets, newTargets, refAssemblies, newAssemblies, hash); + } + if (args.Any (v => string.Equals (v, "uninstall", StringComparison.OrdinalIgnoreCase) || string.Equals (v, "/uninstall", StringComparison.OrdinalIgnoreCase))) { + return Uninstall (msbuildTargets, refAssemblies, hash); + } + Console.Error.WriteLine ($"{AppName}: Invalid command `{string.Join (" ", args)}`."); + return 1; + } + + static int Install (string msbuildTargets, string newTargets, string refAssemblies, string newAssemblies, string hash) + { + var backupAssemblies = GetNewBackupName (refAssemblies, hash); + var backupTargets = GetNewBackupName (msbuildTargets, hash); + try { + Directory.CreateDirectory (Path.GetDirectoryName (refAssemblies)); + Directory.CreateDirectory (Path.GetDirectoryName (msbuildTargets)); + + if (CreateSymbolicLink (refAssemblies, newAssemblies, backupAssemblies) && + CreateSymbolicLink (msbuildTargets, newTargets, backupTargets)) { + Console.WriteLine ("Success!"); + return 0; + } + return 1; + } + catch (UnauthorizedAccessException e) { + Console.Error.WriteLine ($"{AppName}: {e.Message}"); + Console.Error.WriteLine (e.ToString ()); + return 1; + } + catch (Exception e) { + Console.Error.WriteLine ($"{AppName}: {e.Message}"); + Console.Error.WriteLine (e); + return 1; + } + } + + // XAZipFolderName is build-tools/scripts/BuildEverything.mk!$(ZIP_OUTPUT_BASENAME), + // oss-xamarin.android_v$(PRODUCT_VERSION).$(-num-commits-since-version-change)_$(OS)-$(OS_ARCH)_$(GIT_BRANCH)_$(GIT_COMMIT) + static string XAZipFolderNameToHash (string folderName) + { + var r = new Regex (@"^oss-xamarin.android_v(?[^_]+)_(?[^-]+)-(?[^_]+)_(?.*)_(?[A-Za-z0-9]+)$"); + var m = r.Match (folderName); + if (!m.Success) + return "Unknown"; + return m.Groups ["commit"].Value; + } + + static string GetNewBackupName (string folder, string hash) + { + return GetBackupNames (folder, hash).First (d => !Directory.Exists (d)); + } + + static IEnumerable GetBackupNames (string folder, string hash) + { + folder = GetBackupNamePrefix (folder, hash); + yield return folder; + int count = 1; + while (true) { + yield return $"{folder}+{count}"; + count++; + } + } + + static string GetBackupNamePrefix (string folder, string hash) + { + return folder + ".pre-" + hash; + } + + static bool CreateSymbolicLink (string source, string target, string backup) + { + Console.WriteLine ($"Executing: MKLINK /D \"{source}\" \"{target}\""); + if (Directory.Exists (source)) { + Directory.Move (source, backup); + } + if (!CreateSymbolicLink (source, target, SymbolLinkFlag.Directory)) { + var error = new Win32Exception (Marshal.GetLastWin32Error ()).Message; + Console.Error.WriteLine ($"{AppName}: Unable to create symbolic link from `{source}` to `{target}`: {error}"); + Directory.Move (backup, source); + return false; + } + return true; + } + + static int Uninstall (string msbuildTargets, string refAssemblies, string hash) + { + var backupTargets = GetExistingBackupName (msbuildTargets, hash); + var backupAssemblies = GetExistingBackupName (refAssemblies, hash); + + Directory.Delete (msbuildTargets); + if (backupTargets != null && Directory.Exists (backupTargets)) { + Directory.Move (backupTargets, msbuildTargets); + } + Directory.Delete (refAssemblies); + if (backupAssemblies != null && Directory.Exists (backupAssemblies)) { + Directory.Move (backupAssemblies, refAssemblies); + } + return 0; + } + + static string GetExistingBackupName (string folder, string hash) + { + var prefix = GetBackupNamePrefix (folder, hash); + var path = Path.GetDirectoryName (prefix); + var pattern = Path.GetFileName (prefix) + "*.*"; + return Directory.EnumerateDirectories (path, pattern, SearchOption.TopDirectoryOnly) + .FirstOrDefault (); + } + + [DllImport ("kernel32.dll")] + [return: MarshalAs (UnmanagedType.I1)] + static extern bool CreateSymbolicLink (string lpSymlinkFileName, string lpTargetFileName, SymbolLinkFlag dwFlags); + } +} diff --git a/tools/setup-windows/setup-windows.csproj b/tools/setup-windows/setup-windows.csproj new file mode 100644 index 00000000000..8d483d5e6f1 --- /dev/null +++ b/tools/setup-windows/setup-windows.csproj @@ -0,0 +1,39 @@ + + + + Debug + AnyCPU + {73DF9E10-E933-4222-B8E1-F4536FFF9FAD} + Exe + setupwindows + setup-windows + v4.6.1 + + + true + full + false + ..\..\bin\Debug\bin + DEBUG; + prompt + 4 + true + obj\AnyCPU\Debug + + + true + ..\..\bin\Release\bin + prompt + 4 + true + obj\AnyCPU\Release + + + + + + + + + + \ No newline at end of file