diff --git a/src/chocolatey/ObjectExtensions.cs b/src/chocolatey/ObjectExtensions.cs index 44346c5e7a..3fbb18e353 100644 --- a/src/chocolatey/ObjectExtensions.cs +++ b/src/chocolatey/ObjectExtensions.cs @@ -15,6 +15,9 @@ namespace chocolatey { + using System.IO; + using System.Runtime.Serialization.Formatters.Binary; + /// /// Extensions for Object /// @@ -31,5 +34,17 @@ public static string to_string(this object input) return input.ToString(); } + + public static T deep_copy(this T other) + { + using (var ms = new MemoryStream()) + { + var formatter = new BinaryFormatter(); + formatter.Serialize(ms, other); + ms.Position = 0; + return (T)formatter.Deserialize(ms); + } + } + } } \ No newline at end of file diff --git a/src/chocolatey/chocolatey.csproj b/src/chocolatey/chocolatey.csproj index b59a2a2c25..ddc78c96dc 100644 --- a/src/chocolatey/chocolatey.csproj +++ b/src/chocolatey/chocolatey.csproj @@ -91,6 +91,8 @@ + + diff --git a/src/chocolatey/infrastructure.app/configuration/ChocolateyConfiguration.cs b/src/chocolatey/infrastructure.app/configuration/ChocolateyConfiguration.cs index 68477f4cf4..2e0c8c394e 100644 --- a/src/chocolatey/infrastructure.app/configuration/ChocolateyConfiguration.cs +++ b/src/chocolatey/infrastructure.app/configuration/ChocolateyConfiguration.cs @@ -25,6 +25,7 @@ namespace chocolatey.infrastructure.app.configuration /// /// The chocolatey configuration. /// + [Serializable] public class ChocolateyConfiguration { public ChocolateyConfiguration() @@ -202,6 +203,7 @@ private void output_tostring(StringBuilder propertyValues, IEnumerable TemplateProperties { get; private set; } } + [Serializable] public sealed class SourcesCommandConfiguration { public string Name { get; set; } public SourceCommandType Command { get; set; } public string Username { get; set; } public string Password { get; set; } - } - + } + + [Serializable] public sealed class FeatureCommandConfiguration { public string Name { get; set; } public FeatureCommandType Command { get; set; } } + [Serializable] public sealed class PinCommandConfiguration { public string Name { get; set; } public PinCommandType Command { get; set; } } + [Serializable] public sealed class ApiKeyCommandConfiguration { public string Key { get; set; } } + [Serializable] public sealed class PushCommandConfiguration { public string Key { get; set; } diff --git a/src/chocolatey/infrastructure.app/configuration/PackagesConfigFilePackageSetting.cs b/src/chocolatey/infrastructure.app/configuration/PackagesConfigFilePackageSetting.cs new file mode 100644 index 0000000000..95f58f137f --- /dev/null +++ b/src/chocolatey/infrastructure.app/configuration/PackagesConfigFilePackageSetting.cs @@ -0,0 +1,55 @@ +// 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.configuration +{ + using System; + using System.Xml.Serialization; + + /// + /// XML packages.config file package element + /// + [Serializable] + //[XmlType("package")] + public sealed class PackagesConfigFilePackageSetting + { + [XmlAttribute(AttributeName = "id")] + public string Id { get; set; } + + [XmlAttribute(AttributeName = "source")] + public string Source { get; set; } + + [XmlAttribute(AttributeName = "version")] + public string Version { get; set; } + + [XmlAttribute(AttributeName = "installArguments")] + public string InstallArguments { get; set; } + + [XmlAttribute(AttributeName = "packageParameters")] + public string PackageParameters { get; set; } + + [XmlAttribute(AttributeName = "forceX86")] + public bool ForceX86 { get; set; } + + [XmlAttribute(AttributeName = "allowMultipleVersions")] + public bool AllowMultipleVersions { get; set; } + + [XmlAttribute(AttributeName = "ignoreDependencies")] + public bool IgnoreDependencies { get; set; } + + [XmlAttribute(AttributeName = "disabled")] + public bool Disabled { get; set; } + } +} \ No newline at end of file diff --git a/src/chocolatey/infrastructure.app/configuration/PackagesConfigFileSettings.cs b/src/chocolatey/infrastructure.app/configuration/PackagesConfigFileSettings.cs new file mode 100644 index 0000000000..204c1749e6 --- /dev/null +++ b/src/chocolatey/infrastructure.app/configuration/PackagesConfigFileSettings.cs @@ -0,0 +1,32 @@ +// 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.configuration +{ + using System; + using System.Collections.Generic; + using System.Xml.Serialization; + + /// + /// XML packages.config configuration file + /// + [Serializable] + [XmlRoot("packages")] + public class PackagesConfigFileSettings + { + [XmlElement("package")] + public HashSet Packages { get; set; } + } +} \ No newline at end of file diff --git a/src/chocolatey/infrastructure.app/services/ChocolateyPackageService.cs b/src/chocolatey/infrastructure.app/services/ChocolateyPackageService.cs index 0363cc79c8..e3de220ce9 100644 --- a/src/chocolatey/infrastructure.app/services/ChocolateyPackageService.cs +++ b/src/chocolatey/infrastructure.app/services/ChocolateyPackageService.cs @@ -23,6 +23,7 @@ namespace chocolatey.infrastructure.app.services using configuration; using domain; using filesystem; + using infrastructure.services; using logging; using platforms; using results; @@ -36,8 +37,9 @@ public class ChocolateyPackageService : IChocolateyPackageService private readonly IRegistryService _registryService; private readonly IChocolateyPackageInformationService _packageInfoService; private readonly IAutomaticUninstallerService _autoUninstallerService; + private readonly IXmlService _xmlService; - public ChocolateyPackageService(INugetService nugetService, IPowershellService powershellService, IShimGenerationService shimgenService, IFileSystem fileSystem, IRegistryService registryService, IChocolateyPackageInformationService packageInfoService, IAutomaticUninstallerService autoUninstallerService) + public ChocolateyPackageService(INugetService nugetService, IPowershellService powershellService, IShimGenerationService shimgenService, IFileSystem fileSystem, IRegistryService registryService, IChocolateyPackageInformationService packageInfoService, IAutomaticUninstallerService autoUninstallerService, IXmlService xmlService) { _nugetService = nugetService; _powershellService = powershellService; @@ -46,6 +48,7 @@ public ChocolateyPackageService(INugetService nugetService, IPowershellService p _registryService = registryService; _packageInfoService = packageInfoService; _autoUninstallerService = autoUninstallerService; + _xmlService = xmlService; } public void list_noop(ChocolateyConfiguration config) @@ -140,7 +143,11 @@ public void push_run(ChocolateyConfiguration config) public void install_noop(ChocolateyConfiguration config) { - _nugetService.install_noop(config, (pkg) => _powershellService.install_noop(pkg)); + // each package can specify its own configuration values + foreach (var packageConfig in set_config_from_package_names_and_packages_config(config, new ConcurrentDictionary()).or_empty_list_if_null()) + { + _nugetService.install_noop(packageConfig, (pkg) => _powershellService.install_noop(pkg)); + } } public void handle_package_result(PackageResult packageResult, ChocolateyConfiguration config, CommandNameType commandName) @@ -210,10 +217,20 @@ public ConcurrentDictionary install_run(ChocolateyConfigu this.Log().Info(ChocolateyLoggers.Important, @"{0}".format_with(config.PackageNames)); this.Log().Info(@"By installing you accept licenses for the packages."); - var packageInstalls = _nugetService.install_run( - config, - (packageResult) => handle_package_result(packageResult, config, CommandNameType.install) - ); + + var packageInstalls = new ConcurrentDictionary(); + + foreach (var packageConfig in set_config_from_package_names_and_packages_config(config, packageInstalls).or_empty_list_if_null()) + { + var results = _nugetService.install_run( + packageConfig, + (packageResult) => handle_package_result(packageResult, packageConfig, CommandNameType.install) + ); + foreach (var result in results) + { + packageInstalls.GetOrAdd(result.Key, result.Value); + } + } var installFailures = packageInstalls.Count(p => !p.Value.Success); this.Log().Warn(() => @"{0}{1} installed {2}/{3} packages. {4} packages failed.{0}See the log for details.".format_with( @@ -240,6 +257,58 @@ public ConcurrentDictionary install_run(ChocolateyConfigu return packageInstalls; } + private IEnumerable set_config_from_package_names_and_packages_config(ChocolateyConfiguration config, ConcurrentDictionary packageInstalls) + { + // if there are any .config files, split those off of the config. Then return the config without those package names. + foreach (var packageConfigFile in config.PackageNames.Split(new[] {ApplicationParameters.PackageNamesSeparator}, StringSplitOptions.RemoveEmptyEntries).or_empty_list_if_null().Where(p => p.Contains(".config")).ToList()) + { + config.PackageNames = config.PackageNames.Replace(packageConfigFile, string.Empty); + + foreach (var packageConfig in get_packages_from_config(packageConfigFile, config, packageInstalls).or_empty_list_if_null()) + { + yield return packageConfig; + } + } + + yield return config; + } + + private IEnumerable get_packages_from_config(string packageConfigFile, ChocolateyConfiguration config, ConcurrentDictionary packageInstalls) + { + IList packageConfigs = new List(); + + if (!_fileSystem.file_exists(_fileSystem.get_full_path(packageConfigFile))) + { + var logMessage = "Could not find '{0}' in the location specified.".format_with(packageConfigFile); + this.Log().Error(ChocolateyLoggers.Important, logMessage); + var results = packageInstalls.GetOrAdd(packageConfigFile, new PackageResult(packageConfigFile, null, null)); + results.Messages.Add(new ResultMessage(ResultType.Error, logMessage)); + + return packageConfigs; + } + + var settings = _xmlService.deserialize(_fileSystem.get_full_path(packageConfigFile)); + foreach (var pkgSettings in settings.Packages.or_empty_list_if_null()) + { + if (!pkgSettings.Disabled) + { + var packageConfig = config.deep_copy(); + packageConfig.PackageNames = pkgSettings.Id; + packageConfig.Sources = string.IsNullOrWhiteSpace(pkgSettings.Source) ? packageConfig.Sources : pkgSettings.Source; + packageConfig.Version = pkgSettings.Version; + packageConfig.InstallArguments = string.IsNullOrWhiteSpace(pkgSettings.InstallArguments) ? packageConfig.InstallArguments : pkgSettings.InstallArguments; + packageConfig.PackageParameters = string.IsNullOrWhiteSpace(pkgSettings.PackageParameters) ? packageConfig.PackageParameters : pkgSettings.PackageParameters; + if (pkgSettings.ForceX86) packageConfig.ForceX86 = true; + if (pkgSettings.AllowMultipleVersions) packageConfig.AllowMultipleVersions = true; + if (pkgSettings.IgnoreDependencies) packageConfig.IgnoreDependencies = true; + + packageConfigs.Add(packageConfig); + } + } + + return packageConfigs; + } + public void upgrade_noop(ChocolateyConfiguration config) { _nugetService.upgrade_noop(config, (pkg) => _powershellService.install_noop(pkg)); diff --git a/src/chocolatey/infrastructure.app/services/NugetService.cs b/src/chocolatey/infrastructure.app/services/NugetService.cs index 720fe47ca6..c1018cb9a7 100644 --- a/src/chocolatey/infrastructure.app/services/NugetService.cs +++ b/src/chocolatey/infrastructure.app/services/NugetService.cs @@ -253,57 +253,49 @@ public ConcurrentDictionary install_run(ChocolateyConfigu foreach (string packageName in packageNames.or_empty_list_if_null()) { - if (packageName.to_lower().EndsWith(".config")) + //todo: get smarter about realizing multiple versions have been installed before and allowing that + + remove_existing_rollback_directory(packageName); + + IPackage installedPackage = packageManager.LocalRepository.FindPackage(packageName); + + if (installedPackage != null && (version == null || version == installedPackage.Version) && !config.Force) { - //todo: determine if .config file for packages .config - //todo: determine if config file exists + string logMessage = "{0} v{1} already installed.{2} Use --force to reinstall, specify a version to install, or try upgrade.".format_with(installedPackage.Id, installedPackage.Version, Environment.NewLine); + var results = packageInstalls.GetOrAdd(packageName, new PackageResult(installedPackage, ApplicationParameters.PackagesLocation)); + results.Messages.Add(new ResultMessage(ResultType.Inconclusive, logMessage)); + this.Log().Warn(ChocolateyLoggers.Important, logMessage); + continue; } - else - { - //todo: get smarter about realizing multiple versions have been installed before and allowing that - - remove_existing_rollback_directory(packageName); - IPackage installedPackage = packageManager.LocalRepository.FindPackage(packageName); - - if (installedPackage != null && (version == null || version == installedPackage.Version) && !config.Force) - { - string logMessage = "{0} v{1} already installed.{2} Use --force to reinstall, specify a version to install, or try upgrade.".format_with(installedPackage.Id, installedPackage.Version, Environment.NewLine); - var results = packageInstalls.GetOrAdd(packageName, new PackageResult(installedPackage, ApplicationParameters.PackagesLocation)); - results.Messages.Add(new ResultMessage(ResultType.Inconclusive, logMessage)); - this.Log().Warn(ChocolateyLoggers.Important, logMessage); - continue; - } - - if (installedPackage != null && (version == null || version == installedPackage.Version) && config.Force) - { - this.Log().Debug(() => "{0} v{1} already installed. Forcing reinstall.".format_with(installedPackage.Id, installedPackage.Version)); - version = installedPackage.Version; - } + if (installedPackage != null && (version == null || version == installedPackage.Version) && config.Force) + { + this.Log().Debug(() => "{0} v{1} already installed. Forcing reinstall.".format_with(installedPackage.Id, installedPackage.Version)); + version = installedPackage.Version; + } - IPackage availablePackage = packageManager.SourceRepository.FindPackage(packageName, version, config.Prerelease, allowUnlisted: false); - if (availablePackage == null) - { - var logMessage = "{0} not installed. The package was not found with the source(s) listed.{1} If you specified a particular version and are receiving this message, it is possible that the package name exists but the version does not.{1} Version: \"{2}\"{1} Source(s): \"{3}\"".format_with(packageName, Environment.NewLine, config.Version, config.Sources); - this.Log().Error(ChocolateyLoggers.Important, logMessage); - var results = packageInstalls.GetOrAdd(packageName, new PackageResult(packageName, version.to_string(), null)); - results.Messages.Add(new ResultMessage(ResultType.Error, logMessage)); - continue; - } + IPackage availablePackage = packageManager.SourceRepository.FindPackage(packageName, version, config.Prerelease, allowUnlisted: false); + if (availablePackage == null) + { + var logMessage = "{0} not installed. The package was not found with the source(s) listed.{1} If you specified a particular version and are receiving this message, it is possible that the package name exists but the version does not.{1} Version: \"{2}\"{1} Source(s): \"{3}\"".format_with(packageName, Environment.NewLine, config.Version, config.Sources); + this.Log().Error(ChocolateyLoggers.Important, logMessage); + var results = packageInstalls.GetOrAdd(packageName, new PackageResult(packageName, version.to_string(), null)); + results.Messages.Add(new ResultMessage(ResultType.Error, logMessage)); + continue; + } - if (installedPackage != null && (installedPackage.Version == availablePackage.Version)) - { - packageManager.UninstallPackage(installedPackage, forceRemove: config.Force, removeDependencies: config.ForceDependencies); - } + if (installedPackage != null && (installedPackage.Version == availablePackage.Version)) + { + packageManager.UninstallPackage(installedPackage, forceRemove: config.Force, removeDependencies: config.ForceDependencies); + } - using (packageManager.SourceRepository.StartOperation( - RepositoryOperationNames.Install, - packageName, - version == null ? null : version.ToString())) - { - packageManager.InstallPackage(availablePackage, config.IgnoreDependencies, config.Prerelease); - //packageManager.InstallPackage(packageName, version, configuration.IgnoreDependencies, configuration.Prerelease); - } + using (packageManager.SourceRepository.StartOperation( + RepositoryOperationNames.Install, + packageName, + version == null ? null : version.ToString())) + { + packageManager.InstallPackage(availablePackage, config.IgnoreDependencies, config.Prerelease); + //packageManager.InstallPackage(packageName, version, configuration.IgnoreDependencies, configuration.Prerelease); } } @@ -341,7 +333,7 @@ public ConcurrentDictionary upgrade_run(ChocolateyConfigu SemanticVersion version = config.Version != null ? new SemanticVersion(config.Version) : null; var packageManager = NugetCommon.GetPackageManager( - config, + config, _nugetLogger, installSuccessAction: (e) => { @@ -372,7 +364,7 @@ public ConcurrentDictionary upgrade_run(ChocolateyConfigu if (config.RegularOuptut) this.Log().Error(ChocolateyLoggers.Important, logMessage); continue; } - + var pkgInfo = _packageInfoService.get_package_information(installedPackage); if (pkgInfo != null && pkgInfo.IsPinned) { @@ -468,7 +460,7 @@ public void backup_existing_version(ChocolateyConfiguration config, IPackage ins this.Log().Debug("Backing up existing {0} prior to upgrade.".format_with(installedPackage.Id)); var backupLocation = pkgInstallPath + ApplicationParameters.RollbackPackageSuffix; - _fileSystem.copy_directory(pkgInstallPath,backupLocation,overwriteExisting:true); + _fileSystem.copy_directory(pkgInstallPath, backupLocation, overwriteExisting: true); } }