From 40e4b0580a76c4e85c65b7ec3466e9221c1e6c1b Mon Sep 17 00:00:00 2001 From: Rob Reynolds Date: Wed, 3 Jun 2015 16:56:30 -0500 Subject: [PATCH] (GH-14) Cygwin Alternate source Provide the ability to install with cygwin, but not much else. The executable unfortunately doesn't also execute without popping a window, which is very unfortunate. I will probably look at using the cyg-get wrapper instead. --- src/chocolatey/chocolatey.csproj | 1 + .../registration/ContainerBinding.cs | 7 +- .../services/CygwinService.cs | 290 ++++++++++++++++++ 3 files changed, 295 insertions(+), 3 deletions(-) create mode 100644 src/chocolatey/infrastructure.app/services/CygwinService.cs diff --git a/src/chocolatey/chocolatey.csproj b/src/chocolatey/chocolatey.csproj index 2f365f2c05..5c2bb2ed0d 100644 --- a/src/chocolatey/chocolatey.csproj +++ b/src/chocolatey/chocolatey.csproj @@ -106,6 +106,7 @@ + diff --git a/src/chocolatey/infrastructure.app/registration/ContainerBinding.cs b/src/chocolatey/infrastructure.app/registration/ContainerBinding.cs index bf47635cf3..a5cdb31b06 100644 --- a/src/chocolatey/infrastructure.app/registration/ContainerBinding.cs +++ b/src/chocolatey/infrastructure.app/registration/ContainerBinding.cs @@ -93,9 +93,10 @@ public void RegisterComponents(Container container) var list = new List { container.GetInstance(), - new WebPiService(container.GetInstance(),container.GetInstance()), - new WindowsFeatureService(container.GetInstance(),container.GetInstance(),container.GetInstance()), - new RubyGemsService(container.GetInstance(),container.GetInstance()) + new WebPiService(container.GetInstance(), container.GetInstance()), + new WindowsFeatureService(container.GetInstance(), container.GetInstance(), container.GetInstance()), + new CygwinService(container.GetInstance(), container.GetInstance(), container.GetInstance(), container.GetInstance()), + new RubyGemsService(container.GetInstance(), container.GetInstance()) }; return list.AsReadOnly(); }, Lifestyle.Singleton); diff --git a/src/chocolatey/infrastructure.app/services/CygwinService.cs b/src/chocolatey/infrastructure.app/services/CygwinService.cs new file mode 100644 index 0000000000..bb393519c0 --- /dev/null +++ b/src/chocolatey/infrastructure.app/services/CygwinService.cs @@ -0,0 +1,290 @@ +// Copyright © 2011 - Present RealDimensions Software, LLC +// +// 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. + +namespace chocolatey.infrastructure.app.services +{ + using System; + using System.Collections.Concurrent; + using System.Collections.Generic; + using System.Text.RegularExpressions; + using Microsoft.Win32; + using configuration; + using domain; + using filesystem; + using infrastructure.commands; + using logging; + using results; + + /// + /// Alternative Source for Cygwin + /// + /// + /// https://cygwin.com/faq/faq.html#faq.setup.cli + /// + public sealed class CygwinService : ISourceRunner + { + private readonly ICommandExecutor _commandExecutor; + private readonly INugetService _nugetService; + private readonly IFileSystem _fileSystem; + private readonly IRegistryService _registryService; + private const string PACKAGE_NAME_TOKEN = "{{packagename}}"; + private const string INSTALL_ROOT_TOKEN = "{{installroot}}"; + public const string CYGWIN_PACKAGE = "cygwin"; + private string _rootDirectory = string.Empty; + + private const string APP_NAME = "Cygwin"; + public const string PACKAGE_NAME_GROUP = "PkgName"; + public static readonly Regex InstalledRegex = new Regex(@"Extracting from file", RegexOptions.Compiled); + public static readonly Regex PackageNameRegex = new Regex(@"/(?<{0}>[^/]*).tar.".format_with(PACKAGE_NAME_GROUP), RegexOptions.Compiled); + + private readonly IDictionary _installArguments = new Dictionary(StringComparer.InvariantCultureIgnoreCase); + + public CygwinService(ICommandExecutor commandExecutor, INugetService nugetService, IFileSystem fileSystem, IRegistryService registryService) + { + _commandExecutor = commandExecutor; + _nugetService = nugetService; + _fileSystem = fileSystem; + _registryService = registryService; + set_cmd_args_dictionaries(); + } + + /// + /// Set any command arguments dictionaries necessary for the service + /// + private void set_cmd_args_dictionaries() + { + set_install_dictionary(_installArguments); + } + + /// + /// Sets install dictionary + /// + private void set_install_dictionary(IDictionary args) + { + //args.Add("_cmd_c_", new ExternalCommandArgument { ArgumentOption = "/c", Required = true }); + //args.Add("_app_", new ExternalCommandArgument + //{ + // ArgumentOption = "", + // ArgumentValue = _fileSystem.combine_paths(INSTALL_ROOT_TOKEN, "cygwinsetup.exe"), + // QuoteValue = false, + // UseValueOnly = true, + // Required = true + //}); + args.Add("_quiet_", new ExternalCommandArgument {ArgumentOption = "--quiet-mode", Required = true}); + args.Add("_no_desktop_", new ExternalCommandArgument {ArgumentOption = "--no-desktop", Required = true}); + args.Add("_no_startmenu_", new ExternalCommandArgument {ArgumentOption = "--no-startmenu", Required = true}); + args.Add("_root_", new ExternalCommandArgument + { + ArgumentOption = "--root ", + ArgumentValue = INSTALL_ROOT_TOKEN, + QuoteValue = false, + Required = true + }); + args.Add("_local_pkgs_dir_", new ExternalCommandArgument + { + ArgumentOption = "--local-package-dir ", + ArgumentValue = "{0}\\packages".format_with(INSTALL_ROOT_TOKEN), + QuoteValue = false, + Required = true + }); + + args.Add("_site_", new ExternalCommandArgument + { + ArgumentOption = "--site ", + ArgumentValue = "http://mirrors.kernel.org/sourceware/cygwin/", + QuoteValue = false, + Required = true + }); + + args.Add("_package_name_", new ExternalCommandArgument + { + ArgumentOption = "--packages ", + ArgumentValue = PACKAGE_NAME_TOKEN, + QuoteValue = false, + Required = true + }); + } + + public SourceType SourceType + { + get { return SourceType.cygwin; } + } + + public void ensure_source_app_installed(ChocolateyConfiguration config, Action ensureAction) + { + var runnerConfig = new ChocolateyConfiguration + { + Sources = ApplicationParameters.PackagesLocation, + Debug = config.Debug, + Force = config.Force, + Verbose = config.Verbose, + CommandExecutionTimeoutSeconds = config.CommandExecutionTimeoutSeconds, + CacheLocation = config.CacheLocation, + RegularOutput = config.RegularOutput, + PromptForConfirmation = false, + AcceptLicense = true, + }; + runnerConfig.ListCommand.LocalOnly = true; + + var localPackages = _nugetService.list_run(runnerConfig, logResults: false); + + if (!localPackages.ContainsKey(CYGWIN_PACKAGE)) + { + runnerConfig.PackageNames = CYGWIN_PACKAGE; + runnerConfig.Sources = ApplicationParameters.ChocolateyCommunityFeedSource; + + var prompt = config.PromptForConfirmation; + config.PromptForConfirmation = false; + _nugetService.install_run(runnerConfig, ensureAction.Invoke); + config.PromptForConfirmation = prompt; + } + + set_root_dir_if_not_set(); + } + + public void set_root_dir_if_not_set() + { + if (!string.IsNullOrWhiteSpace(_rootDirectory)) return; + + var setupKey = _registryService.get_key(RegistryHive.LocalMachine, "SOFTWARE\\Cygwin\\setup"); + if (setupKey != null) + { + _rootDirectory = setupKey.GetValue("rootdir", string.Empty).to_string(); + } + + if (string.IsNullOrWhiteSpace(_rootDirectory)) + { + var binRoot = Environment.GetEnvironmentVariable("ChocolateyBinRoot"); + if (string.IsNullOrWhiteSpace(binRoot)) binRoot = "c:\\tools"; + + _rootDirectory = _fileSystem.combine_paths(binRoot,"cygwin"); + } + } + + public string get_exe(string rootpath) + { + return _fileSystem.combine_paths(rootpath, "cygwinsetup.exe"); + } + + public void list_noop(ChocolateyConfiguration config) + { + this.Log().Warn(ChocolateyLoggers.Important, "{0} does not implement list".format_with(APP_NAME)); + } + + public ConcurrentDictionary list_run(ChocolateyConfiguration config, bool logResults) + { + throw new NotImplementedException("{0} does not implement list".format_with(APP_NAME)); + } + + public string build_args(ChocolateyConfiguration config, IDictionary argsDictionary) + { + var args = ExternalCommandArgsBuilder.build_arguments(config, argsDictionary); + + args = args.Replace(INSTALL_ROOT_TOKEN, _rootDirectory); + + return args; + } + + public void install_noop(ChocolateyConfiguration config, Action continueAction) + { + var args = build_args(config, _installArguments); + this.Log().Info("Would have run '{0} {1}'".format_with(get_exe(_rootDirectory), args)); + } + + public ConcurrentDictionary install_run(ChocolateyConfiguration config, Action continueAction) + { + var args = build_args(config, _installArguments); + var packageResults = new ConcurrentDictionary(StringComparer.InvariantCultureIgnoreCase); + + foreach (var packageToInstall in config.PackageNames.Split(new[] {' '}, StringSplitOptions.RemoveEmptyEntries)) + { + var argsForPackage = args.Replace(PACKAGE_NAME_TOKEN, packageToInstall); + + var exitCode = _commandExecutor.execute( + get_exe(_rootDirectory), + argsForPackage, + config.CommandExecutionTimeoutSeconds, + (s, e) => + { + var logMessage = e.Data; + if (string.IsNullOrWhiteSpace(logMessage)) return; + this.Log().Info(() => " [{0}] {1}".format_with(APP_NAME, logMessage)); + + if (InstalledRegex.IsMatch(logMessage)) + { + var packageName = get_value_from_output(logMessage, PackageNameRegex, PACKAGE_NAME_GROUP); + var results = packageResults.GetOrAdd(packageName, new PackageResult(packageName, null, null)); + results.Messages.Add(new ResultMessage(ResultType.Note, packageName)); + this.Log().Info(ChocolateyLoggers.Important, " {0} has been installed successfully.".format_with(string.IsNullOrWhiteSpace(packageName) ? packageToInstall : packageName)); + } + }, + (s, e) => + { + var logMessage = e.Data; + if (string.IsNullOrWhiteSpace(logMessage)) return; + this.Log().Error("[{0}] {1}".format_with(APP_NAME, logMessage)); + }, + updateProcessPath: false + ); + + if (exitCode != 0) + { + Environment.ExitCode = exitCode; + } + } + + return packageResults; + } + + public ConcurrentDictionary upgrade_noop(ChocolateyConfiguration config, Action continueAction) + { + this.Log().Warn(ChocolateyLoggers.Important, "{0} does not implement upgrade".format_with(APP_NAME)); + return new ConcurrentDictionary(StringComparer.InvariantCultureIgnoreCase); + } + + public ConcurrentDictionary upgrade_run(ChocolateyConfiguration config, Action continueAction) + { + throw new NotImplementedException("{0} does not implement upgrade".format_with(APP_NAME)); + } + + public void uninstall_noop(ChocolateyConfiguration config, Action continueAction) + { + this.Log().Warn(ChocolateyLoggers.Important, "{0} does not implement uninstall".format_with(APP_NAME)); + } + + public ConcurrentDictionary uninstall_run(ChocolateyConfiguration config, Action continueAction) + { + throw new NotImplementedException("{0} does not implement upgrade".format_with(APP_NAME)); + } + + /// + /// Grabs a value from the output based on the regex. + /// + /// The output. + /// The regex. + /// Name of the group. + /// + private static string get_value_from_output(string output, Regex regex, string groupName) + { + var matchGroup = regex.Match(output).Groups[groupName]; + if (matchGroup != null) + { + return matchGroup.Value; + } + + return string.Empty; + } + } +} \ No newline at end of file