Skip to content

Instantly share code, notes, and snippets.

@citizenmatt
Created August 8, 2013 17:29
Show Gist options
  • Save citizenmatt/6186754 to your computer and use it in GitHub Desktop.
Save citizenmatt/6186754 to your computer and use it in GitHub Desktop.

ReSharper 8 extensions get automatic loading of settings + annotations from within the package. VS2008+R#8 doesn't support extensions OOB, since extensions are nuget based, and that requires .net 4. VS2008+R#8 can still load plugins, but then they have to manually load settings and annotations. Here are two ways to do it. Which is best?

The first file re-implements the settings and annotations loaders. Annotation loading is dead easy thanks to a simple interface. This can easily be hand written. Settings requires more legwork to add a file to the Extensions layer (provided by ReSharper 8). This would probably be a bit of copy and paste from the plugin devguide.

The second file implements a custom extension provider, in the same way as ReSharper implements the nuget package support. It creates an extension based on the loaded plugin, and publishes it to the system in the same way the nuget implementation publishes nuget package extensions. So the default annotation and settings loaders then ask the extension for the files and load them as normal. And any other extensions that try to load other kinds of files from extensions can also work. It requires you to copy and paste the ExtensionsFromPluginProvider into your code (e.g. from the devguide) and then implement ExtensionsInstaller, to simply define your extension id (the id of your nuget extension package) and to tell the custom provider to run if the extension isn't already installed.

The first is less code, but you have to implement more of it. The second is more code, but mostly copy/paste. It also allows you to treat your plugin as an extension. Which is better?

(Note there is an implicit third option - implement a custom extension provider for VS2008 that replicates the default nuget provider, perhaps as a simple read-only implementation. Then all of your already installed extensions would load. This is the ideal scenario)

using System.Collections.Generic;
using System.Reflection;
using JetBrains.Application;
using JetBrains.Application.Extensions;
using JetBrains.Application.FileSystemTracker;
using JetBrains.Application.Settings.Storage.Persistence;
using JetBrains.Application.Settings.Store.Implementation;
using JetBrains.Application.Settings.UserInterface;
using JetBrains.DataFlow;
using JetBrains.Metadata.Utils;
using JetBrains.ReSharper.Psi.Impl.Reflection2.ExternalAnnotations;
using JetBrains.Threading;
using JetBrains.Util;
namespace XunitContrib.Runner.ReSharper.UnitTestProvider.Annotations
{
[ShellComponent]
public class NonExtensionBasedAnnotationsLoader : IExternalAnnotationsFileProvider
{
private readonly SimpleExtensionManager extensionManager;
public NonExtensionBasedAnnotationsLoader(SimpleExtensionManager extensionManager)
{
this.extensionManager = extensionManager;
}
public IEnumerable<FileSystemPath> GetAnnotationsFiles(AssemblyNameInfo assemblyName,
FileSystemPath assemblyLocation)
{
// Check to see if we've been loaded as an extension - so we're either loaded
// as a plugin on the command line (debugging) or we're in an environment that
// doesn't support the default extension provider (e.g. VS2008)
if (extensionManager.IsInstalled())
return EmptyList<FileSystemPath>.InstanceList;
var location =
FileSystemPath.CreateByCanonicalPath(Assembly.GetExecutingAssembly().Location)
.Directory.Combine("ExternalAnnotations");
return location.GetChildFiles("*.xml", PathSearchFlags.RecurseIntoSubdirectories);
}
}
[ShellComponent]
public class NonExtensionBaseSettingsLoader
{
private readonly SimpleExtensionManager extensionManager;
private readonly IThreading threading;
private readonly IFileSystemTracker fileSystemTracker;
private readonly FileSettingsStorageBehavior behaviour;
private readonly UserInjectedSettingsLayers userInjectedSettingsLayers;
private readonly ExtensionsSettingsMountPointProvider extensionsSettingsMountPointProvider;
// Damn. Wish I'd made ExtensionSettingsLoader a bit more OCP
public NonExtensionBaseSettingsLoader(Lifetime lifetime, SimpleExtensionManager extensionManager,
IThreading threading, IFileSystemTracker fileSystemTracker,
FileSettingsStorageBehavior behaviour,
UserInjectedSettingsLayers userInjectedSettingsLayers,
ExtensionsSettingsMountPointProvider extensionsSettingsMountPointProvider)
{
this.extensionManager = extensionManager;
this.threading = threading;
this.fileSystemTracker = fileSystemTracker;
this.behaviour = behaviour;
this.userInjectedSettingsLayers = userInjectedSettingsLayers;
this.extensionsSettingsMountPointProvider = extensionsSettingsMountPointProvider;
// Check to see if we've been loaded as an extension - so we're either loaded
// as a plugin on the command line (debugging) or we're in an environment that
// doesn't support the default extension provider (e.g. VS2008)
if (extensionManager.IsInstalled())
return;
var location = FileSystemPath.CreateByCanonicalPath(Assembly.GetExecutingAssembly().Location).Directory;
LoadSettings(lifetime, location.GetChildFiles("*.dotSettings", PathSearchFlags.RecurseIntoSubdirectories));
}
private void LoadSettings(Lifetime lifetime, IEnumerable<FileSystemPath> settingsFiles)
{
foreach (var settingsFile in settingsFiles)
LoadSetting(lifetime, settingsFile);
}
private void LoadSetting(Lifetime lifetime, FileSystemPath path)
{
var identity = string.Format("extension::{0}-{1}", extensionManager.ExtensionId, path.Name);
var persistentId =
new UserInjectedSettingsLayers.InjectedLayerPersistentIdentity(identity);
var pathAsProperty = new Property<FileSystemPath>(lifetime, "InjectedFileStoragePath", path);
var serialization = new XmlFileSettingsStorage(lifetime, identity, pathAsProperty,
SettingsStoreSerializationToXmlDiskFile.SavingEmptyContent.DeleteFile,
threading, fileSystemTracker, behaviour);
var descriptor = new UserInjectedSettingsLayers.UserInjectedLayerDescriptor(lifetime,
extensionsSettingsMountPointProvider.Id,
persistentId, serialization.Storage,
SettingsStorageMountPoint.MountPath.Default,
() => { });
descriptor.InitialMetadata.Set(UserFriendlySettingsLayers.DisplayName,
string.Format("{0} » {1}", extensionManager.ExtensionId, path.NameWithoutExtension));
descriptor.InitialMetadata.Set(UserFriendlySettingsLayers.Origin,
string.Format("Published by extension: {0}", extensionManager.ExtensionId));
descriptor.InitialMetadata.Set(UserFriendlySettingsLayers.DiskFilePath, path);
descriptor.InitialMetadata.Set(UserFriendlySettingsLayers.IsNonUserEditable, true);
userInjectedSettingsLayers.RegisterUserInjectedLayer(lifetime, descriptor);
}
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using JetBrains.Application;
using JetBrains.Application.Env;
using JetBrains.Application.Extensions;
using JetBrains.Application.PluginSupport;
using JetBrains.DataFlow;
using JetBrains.Util;
namespace XunitContrib.Runner.ReSharper.UnitTestProvider.Annotations
{
// NOTE: You copy/paste this class and simply change ExtensionId
[EnvironmentComponent(Sharing.Common)]
public class ExtensionInstaller
{
private const string ExtensionId = "xunitcontrib";
public ExtensionInstaller(ExtensionManager extensionManager, ExtensionsFromPluginProvider myExtensionProvider)
{
if (!extensionManager.IsInstalled(ExtensionId))
myExtensionProvider.Initialise(ExtensionId);
}
}
// NOTE: You copy/paste this class and don't change it. It does all the work
[EnvironmentComponent(Sharing.Product)]
public class ExtensionsFromPluginProvider : IExtensionProvider
{
private readonly ExtensionLocations extensionLocations;
private readonly CollectionEvents<IExtension> extensions;
private readonly Lifetime lifetime;
private readonly PluginsDirectory pluginsDirectory;
public ExtensionsFromPluginProvider(Lifetime lifetime, ExtensionLocations extensionLocations,
PluginsDirectory pluginsDirectory)
{
this.lifetime = lifetime;
this.extensionLocations = extensionLocations;
this.pluginsDirectory = pluginsDirectory;
extensions = new CollectionEvents<IExtension>(lifetime, "ExtensionsFromPluginProvider");
}
public string Name { get { return "plugins"; } }
public IViewable<IExtension> Extensions { get { return extensions; } }
public void Initialise(string extensionId)
{
Plugin plugin = pluginsDirectory.Plugins.FirstOrDefault(p => p.ID == extensionId);
if (plugin == null)
{
plugin = (from p in pluginsDirectory.Plugins
from a in p.AssemblyPaths
where a == FileSystemPath.CreateByCanonicalPath(GetType().Assembly.Location)
select p).FirstOrDefault();
}
if (plugin != null)
extensions.Add(lifetime, new ExtensionFromPlugin(lifetime, plugin, this, extensionLocations));
}
private class ExtensionFromPlugin : IExtension
{
private readonly ExtensionLocations extensionLocations;
private readonly Plugin plugin;
public ExtensionFromPlugin(Lifetime lifetime, Plugin plugin, IExtensionProvider provider,
ExtensionLocations extensionLocations)
{
this.plugin = plugin;
this.extensionLocations = extensionLocations;
Id = plugin.ID;
Enabled = new Property<bool?>(lifetime, string.Format("ExtensionFromPlugin:{0}", Id));
RuntimeInfoRecords = new ListEvents<ExtensionRecord>(lifetime,
string.Format("ExtensionFromPlugin:{0}", Id));
Source = provider;
Metadata = new ExtensionFromPluginMetadata(plugin);
}
public IEnumerable<FileSystemPath> GetFiles(string fileType)
{
// Don't return any plugins - we'd be loading ourself!
if (fileType == "plugins")
yield break;
List<FileSystemPath> searchLocations =
extensionLocations.ExtensionComponentSearchPaths.Concat(FileSystemPath.Empty).ToList();
List<FileSystemPath> pluginLocations = plugin.AssemblyPaths.Select(p => p.Directory).Distinct().ToList();
FileSystemPath root = pluginLocations.Count > 1
? pluginLocations.Aggregate(FileSystemPath.GetDeepestCommonParent)
: pluginLocations[0];
List<FileSystemPath> files = (from pluginLocation in pluginLocations
from searchLocation in searchLocations
let directory = pluginLocation.Combine(searchLocation.Combine(fileType))
from file in directory.GetChildFiles("*", PathSearchFlags.RecurseIntoSubdirectories)
select file).ToList();
ReportFiles(fileType, root, files);
foreach (FileSystemPath file in files)
yield return file;
}
public string Id { get; private set; }
public Version Version { get { return plugin.Presentation.AssemblyNameInfo.Version; } }
public string SemanticVersion { get { return Version.ToString(); } }
public IExtensionMetadata Metadata { get; private set; }
public bool Supported { get { return true; } }
public IProperty<bool?> Enabled { get; private set; }
public ListEvents<ExtensionRecord> RuntimeInfoRecords { get; private set; }
public IExtensionProvider Source { get; private set; }
private void ReportFiles(string fileType, FileSystemPath root, IList<FileSystemPath> files)
{
if (files.Any())
{
this.AddInfo(this, string.Format("Found {0} files under {1}", files.Count, root));
}
else
{
this.AddInfo(this, string.Format("The package contains no files in {0}", root));
}
}
}
private class ExtensionFromPluginMetadata : IExtensionMetadata
{
private readonly Plugin plugin;
public ExtensionFromPluginMetadata(Plugin plugin)
{
this.plugin = plugin;
Copyright = null; // TODO: Load a plugin assembly looking for AssemblyCopyrightAttribute
}
public string Title { get { return plugin.Presentation.Title; } }
public string Description { get { return plugin.Presentation.Description; } }
public string Summary { get { return string.Empty; } }
public string Copyright { get; private set; }
public IEnumerable<string> Authors { get { return new[] {plugin.Presentation.Vendor}; } }
public IEnumerable<string> Owners { get { return Authors; } }
public IEnumerable<string> Tags { get { return new string[0]; } }
public IEnumerable<string> DependencyIds { get { return new string[0]; } }
public IEnumerable<string> DependencyDescriptions { get { return new string[0]; } }
public Uri IconUrl { get { return null; } }
public Uri LicenseUrl { get { return null; } }
public Uri ProjectUrl { get { return null; } }
public DateTimeOffset? Created { get { return null; } }
public bool PreRelease { get { return false; } }
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment