How to exclude UnityEditor reference from asmdef?
Why I need it:
I have an asmdef file. For example, it is MyAssembly/MyAssembly.asmdef. The MyAssembly contains a lot of features and each feature staff is placed in its own folder. And some of these features has a code that is needed only in editor, and it refers to UnityEditor namespace. Such editor code is placed into an Editor folder.
But as you know, Editor folder name means nothing in terms of asmdef usage. So I add AssemblyDefenitionReference in each folder and refer it to the MyAssemblyEditor.asmdef assembly definition. So the paths looks like this:
MyAssembly/MyAssembly.asmdef
MyAssembly/Editor/MyAssemblyEditor.asmdef - this folder contains no code. It's needed just to place asmdef, because it's not allowed to place two asmdefs in a single folder.
MyAssembly/SomeFeature/Editor/*feature editor staff*
MyAssembly/SomeFeature/Editor/Editor.asmref - refers to MyAssemblyEditor.asmdef
MyAssembly/SomeFeature/*feature staff*
All this works good. But the problem is that, when some developer adds a new feature, he can forget to add a reference to the MyAssemblyEditor.asmdef in the editor folder. And there are no any errors will be shown in this case. This mistake will be revealed only when the build will be cooked. But I'd like that using of UnityEditor in MyAssembly will be instantly marked as an error.
Feel free to suggest other solution for this problem.
This thread got me thinking I can use CsprojPostprocessor to remove all references to UnityEditor from my csproj file. I wrote such class:
using System.Text.RegularExpressions;
using UnityEditor;
// ReSharper disable once CheckNamespace
public class CsprojPostprocessor : AssetPostprocessor
{
public static string OnGeneratedCSProject(string path, string content)
{
if (!path.EndsWith("Editor.csproj") && !path.EndsWith("Tests.csproj"))
{
var newContent =
Regex.Replace(content, "<Reference Include=\"UnityEditor(.|\n)*?</Reference>", "");
return newContent;
}
return content;
}
}
It also can be done with an xml parser or something.
The only thing, that confuse me is that this mechanism is badly documented and doesn't look like something simple users should use. So I use it at my own risk, but looks like there is no guarantee it will be strongly supported in future.
I am trying to generate external modules rather than a type definition file. I believe I need to do the following:
Change the extension of the file to .ts instead of .d.ts.
Generate one file per module.
Add the key word "Export" in front of each interface and enum.
I was easily able to change the extension of the file by changing the "output extension" setting in the tt file.
I cannot figure out how to split the modules into separate files.
I cannot figure out how to add the Export key word to each interface.
TypeLITE doesn't support generating multiple files. This feature has been requested by several users, but I am not aware of a simple way to generate multiple files from the single tt file.
export keyword can't be added without changing source code of the library (TsGenerator.cs). This is very specific requirement, so I probably won't add it to the library.
TypeLite is a good project but lacking in Documentation and examples, it's open source so anyone can contribute and make it better.
As for creating a file per class i solved it using the code below.
private static void GenerateTypeScriptContracts(string assemblyFile, string outputPath)
{
// Clean TS Folder
System.IO.DirectoryInfo di = new DirectoryInfo(outputPath);
foreach (FileInfo file in di.GetFiles())
{
file.Delete();
}
// --
var assembly = Assembly.LoadFrom(assemblyFile);
// If you want a subset of classes from this assembly, filter them here
var models = assembly.GetTypes();
foreach (var model in models)
{
var generator = new TypeScriptFluent()
.WithConvertor<Guid>(c => "string")
.WithMemberFormatter((identifier) => Char.ToLower(identifier.Name[0]) + identifier.Name.Substring(1));
generator.ModelBuilder.Add(model);
// Generate TS interface definitions
var tsClassDefinitions = generator.Generate(TsGeneratorOutput.Properties | TsGeneratorOutput.Fields);
File.WriteAllText(Path.Combine(outputPath, "I" + model.FullName.Replace("ProjectName.DtoModels.", "") + ".ts"), tsClassDefinitions);
}
}
Can't save state in windows 8
{
Error The type or namespace name 'Common' ...does not exist in the namespace 'ProjectName' (are you missing an assembly reference?)
}
Everything below is the default code in my App.Xaml.cs file the only line I added was
ProjectNameSpace.Common.SuspensionManager.RegisterFrame(rootFrame, "appframe");
which is from the windows 8 tutorial here and I have followed part 1 before attempting this http://msdn.microsoft.com/en-us/library/windows/apps/hh986968.aspx. I have it working in another project that has the same references and using statements. There is only one namespace in the project and I even Rebuilt/Cleaned. Does anybody have any extra information?
protected override void OnLaunched(LaunchActivatedEventArgs args)
{
Frame rootFrame = Window.Current.Content as Frame;
if (rootFrame == null)
{
rootFrame = new Frame();
ProjectNameSpace.Common.SuspensionManager.RegisterFrame(rootFrame, "appframe");
if (args.PreviousExecutionState == ApplicationExecutionState.Terminated)
{
}
Window.Current.Content = rootFrame;
EDIT:
The problem was that I needed to add a basic page in my blank template. This auto generates some classes needed to do basic functionality. Below is a screenshot of the minimum items that the common folder needs to contain.
Do you have the Common folder present in your project? This folder is usually included in the Visual Studio 2012 App project templates (except the Blank App template) which contains a bunch of classes with boilerplate code for layout, styles and other functionality.
If you created your project with a Blank App template, you may not have this. To get it included, create a new Basic Page (Right-click on your Project > Add > New Item > (Visual C#)* > Basic Page), and Visual Studio will ask if you would like to create these files.
*I'm not sure what this is for VB .NET or WinJS, but I assume it would be the same structure.
My ultimate goal is to create an eclipse plugin that sets up a PDT project, but with some added builders (and custom build scripts) as well as a specific folder layout (and different folders should be treated as source and some as regular folders).
I've looked at / followed eclipse plugin development tutorials, and ok. I get the gist of creating a wizard that creates a file, but I'm having trouble trying to figure out how to create a project, and more importantly, make that project associated with the PDT (PHP Development Tools) feature.
Answer:
I did stumble upon a solution myself before the answer was given, but it's quite similar.
First, WizardNewProjectCreationPage was used as the first page of my wizard.
Second, on performFinish() I ran the following code:
IProgressMonitor m = new NullProgressMonitor();
IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot();
IProject project = root.getProject(_pageOne.getProjectName());
if (!project.exists())
{
IProjectDescription desc = project.getWorkspace().newProjectDescription(project.getName());
URI projectLocation = _pageOne.getLocationURI();
// desc.setLocationURI(projectLocation);
String[] natures = desc.getNatureIds();
String[] newNatures = new String[natures.length + 1];
System.arraycopy(natures, 0, newNatures, 0, natures.length);
newNatures[natures.length] = PHPNature.ID;
desc.setNatureIds(newNatures);
project.create(desc, m);
project.open(m);
}
how to create a project
Take a look at BasicNewProjectResourceWizard.createNewProject() method.
make that project associated with the PDT (PHP Development Tools) feature
You need to add "org.eclipse.php.core.PHPNature" to the project (that's what Add PHP Support... action does). Use IProjectDescription.setNatureIds().
this post ideally continues my other post on MEF plugins, but my first post was too full of comments and this sample is more complete. Here I summarize my updated scenario, with all my findings up to this point. Hope this can be useful to other CM newbies like me.
You can download a full repro sample scenario: it's an almost do-nothing dumb skeleton for a CM + MEF plugins-based application:
VS2010 repro solution (updated)
This is a minimal stripped down solution representing my issues with CM+MEF.
There are 3 projects:
the main UI (CmRepro).
a core DLL shared among all the addins (AddinCore), with a couple of interfaces and custom attributes used for MEF metadata.
a sample addin DLL (AlphaAddin), with a view and a viewmodel implementing the interfaces.
The core contains 2 interfaces representing a viewmodel and its view, and 2 attributes to be used for decorating the viewmodels and the views. The viewmodel interface describes a class which should compose a greeting message from some person name, so it exposes a couple of properties and a method for this. The view interface just exposes a property returning its DataContext cast to the viewmodel interface.
The sample addin has an implementation for a viewmodel and a view; both are MEF-exports, decorated by the corresponding attribute. In the real-world solution several properties of these attributes are used for filtering; here I just have a dummy Language property which should allow for other plugins for different languages.
The main UI has a MEF bootstrapper which adds code for retrieving MEF exports from an Addins folder. I modified this code to include exports from MEF directories and get a better understanding of some MEF exceptions, but still I cannot figure out how to properly "register" them with CM.
The main viewmodel has 2 methods: one (A) uses a MEF catalog to retrieve a viewmodel and its view, bind them and show them into a window. Another (B) uses the same catalog to get a viewmodel, and then a CM window manager to locate, create, bind and show the corresponding view according to CM naming conventions. These methods represent two alternative ways I should deal with in my real-world code, i.e. instantiating some crucial objects "by myself" just using MEF but then let them work for CM, or letting CM (with a MEF-bootstrapper) do most of the work starting from a viewmodel.
Anyway, it seems that in both cases I am missing something as for registration with CM. Issues:
(A) how do I wire up VM+V for CM so that the conventions for databinding etc are applied? At this time I can build my MEF parts together, but CM ignores them as it was not used to instantiate none of them.
I answer to myself here:
ViewModelBinder.Bind(viewmodel, (UserControl)view, null);
(B) how do I register the exports from MEF in CM so that the CM window manager can find the view? Currently it does not manage to locate the view from the viewmodel.
Addition (21 jun)
I try to explain better for whom cannot access the repro solution. I use a "standard" MEF bootstrapper, changing the Configure override like:
_container = new CompositionContainer(
new AggregateCatalog(AssemblySource.Instance.Select(
x => new AssemblyCatalog(x)).OfType()
.Union(GetAddinDirectoryCatalogs())));
this creates a MEF composition container which aggregates the catalog from AssemblySource, with CM types like event aggregator or window manager, with a catalog from several addin directories, which contain exports for both V and VM's.
In my sample main viewmodel I create a new VM from a plugin, found in the host application directory among others, and I'd like a CM window manager to locate, instantiate and show its view in a dialog, e.g.:
viewmodel = GetMyViewModelFromAddin();
windowmanager.ShowDialog(viewmodel);
CM anyway cannot locate the view. AFAIK, naming conventions are honored: both V and VM are in the same addin assembly, marked as MEF exports, named like SomethingViewModel / SomethingView. Anyway, as Leaf pointed out in his clarification, AssemblySource.Instance is a static IObservableCollection collection of assemblies and I have not added my addins to it. But this is right the point: I would not like to add all of them in advance, because this means loading ALL the addins without yet knowing which (if any) will be ever used. A robust plugin system is the reason for using MEF, after all. I'm new to CM and not sure if it is possible (and where) to find an extension point for CM in this scenario. The window manager does not call my bootstrapper implementation at all, clearly because there is nothing to be instantiated by IoC, as no match was found in assembly source Instance. So, it seems I'm stuck here, the only solution being loading in advance all the assemblies in the Instance, but this seems defeating the whole purpose of using MEF.
The plugin-based application I'm developing loads tons of V+VM CM "pairs" representing user interface widgets, which in turn often use a window manager to popup other V+VM's pairs as dialogs. I can bypass instantiation by CM and use MEF to retrieve V+VM for each widget, but still I'm facing the same view location issue for each widget requiring a window manager. The other alternative (workaround) I can see is avoding the use of window manager and implement my own mechanism for the purpose of showing dialogs from widgets, but this makes feel me a little wrong about CM...:). Usually when I find myself writing much more code than expected I am inclined to think I'm not using the tool in the right way. Any idea?
Caliburn.Micro goes through 3 stages to find a view from a viewmodel instance.
Does text transforms to transform view type name to viewmodel type name. There are a set of standard conventions for these transformations, e.g. SomeNamespace.ViewModels.CustomerViewModel might map to SomeNamespace.Views.CustomerView.
With the view type name(s), Caliburn.Micro then uses AssemblySource.Instance (a static IObservableCollection<Assembly> collection of assemblies) to find the first matching Type.
Caliburn.Micro attempts to create an instance of that Type via one of the IoC.GetInstance() methods (which delegate to your bootstrapper and hence MEF).
I'm guessing (your file share site is blocked here) that the problem with resolving views from viewmodels is due to the second step and the AssemblySource.Instance collection not containing your dynamic assembly.
One solution might be to add each dynamically loaded addin's assembly to AssemblySource.Instance when they are loaded, or if you know all assemblies at startup then you can override the Bootstrapper.SelectAssemblies method to return list of assemblies you expect to contain views and viewmodels.
Update to show how you could pull assemblies from MEF loaded parts
If you are using DirectoryCatalog to load your parts from other assemblies then you could find the assemblies used like this:
var directoryCatalog = new DirectoryCatalog(#"./");
AssemblySource.Instance.AddRange(
directoryCatalog.Parts
.Select(part => ReflectionModelServices.GetPartType(part).Value.Assembly)
.Where(assembly => !AssemblySource.Instance.Contains(assembly)));
If your addins folder changes during the runtime of the application then you would have to DirectoryCatalog.Refresh() the catalog and run the code to add any new assemblies to AssemblySource.Instance
I found a workaround. It's not that pretty, but it allows CM and its window manager to work. I summarize here my findings, hope this will help others or let someone point me to a better solution.
Given that (a) I do not want to load ALL the assemblies including all their dependencies in my plugins folder, to avoid polluting my app domain with unused stuff; and that (b) the only available CM extension point for this seems SelectAssemblies, my goal is add my assemblies there, but only the plugin assemblies requiring to be registered with CM.
So I started looking for a way of loading all the DLL's into a temporary app domain, scan them for some aspect marking them as plugins, and then load into the current app domain only the plugin ones, passing them to SelectAssemblies. This is far from being the optimal solution, as I cannot use a general purpose scanning way like MEF, and I feel I'm duplicating the effort, but at least it's a working solution.
First, to provide at least a way for loading only the plugins, I decorate my plugin assemblies requiring CM registration with a custom attribute marking them as plugins, and further enumerate their types looking for the ones which should be later used by MEF.
The attribute is as simple as:
[AttributeUsage(AttributeTargets.Assembly)]
public class AssemblyRegisteredWithCMAttribute : Attribute {}
I then found this very good piece of code for scanning assemblies into another temporary AppDomain:
http://sachabarber.net/?p=560
I had to modify it a bit, because it was failing when loading assemblies with dependencies, and it was scanning the assemblies types while in my case I just need to check for the attribute.
Here is my code:
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Security.Policy;
using System.Reflection;
using System.Diagnostics.CodeAnalysis;
namespace CmRepro
{
///
/// Separate AppDomain assembly loader.
///
/// Modified from http://sachabarber.net/?p=560 .
public class SeparateAppDomainAssemblyLoader
{
///
/// Loads an assembly into a new AppDomain returning the names of the files
/// containing assemblies marked with the assembly attribute name matching
/// the specified name. The new AppDomain is then Unloaded.
///
/// list of files to load
/// assemblies directory
/// matching attribute name
/// list of found namespaces
/// null files, assembly directory or matching attribute
public List LoadAssemblies(string[] aFiles, string sAssemblyDirectory, string sMatchingAttribute)
{
if (aFiles == null) throw new ArgumentNullException("aFiles");
if (sAssemblyDirectory == null) throw new ArgumentNullException("sAssemblyDirectory");
if (sMatchingAttribute == null) throw new ArgumentNullException("sMatchingAttribute");
List<String> namespaces = new List<String>();
AppDomain childDomain = BuildChildDomain(AppDomain.CurrentDomain);
try
{
Type loaderType = typeof(AssemblyLoader);
if (loaderType.Assembly != null)
{
AssemblyLoader loader = (AssemblyLoader)childDomain.
CreateInstanceFrom(
loaderType.Assembly.Location,
loaderType.FullName).Unwrap();
namespaces = loader.LoadAssemblies(aFiles, sAssemblyDirectory, sMatchingAttribute);
} //eif
return namespaces;
}
finally
{
AppDomain.Unload(childDomain);
}
}
/// <summary>
/// Creates a new AppDomain based on the parent AppDomains
/// Evidence and AppDomainSetup.
/// </summary>
/// <param name="parentDomain">The parent AppDomain</param>
/// <returns>A newly created AppDomain</returns>
private AppDomain BuildChildDomain(AppDomain parentDomain)
{
Evidence evidence = new Evidence(parentDomain.Evidence);
AppDomainSetup setup = parentDomain.SetupInformation;
return AppDomain.CreateDomain("DiscoveryRegion", evidence, setup);
}
/// <summary>
/// Remotable AssemblyLoader, this class inherits from <c>MarshalByRefObject</c>
/// to allow the CLR to marshall this object by reference across AppDomain boundaries.
/// </summary>
private class AssemblyLoader : MarshalByRefObject
{
private string _sRootAsmDir;
/// <summary>
/// ReflectionOnlyLoad of single Assembly based on the assemblyPath parameter.
/// </summary>
/// <param name="aFiles">files names</param>
/// <param name="sAssemblyDirectory">assemblies directory</param>
/// <param name="sMatchingAttribute">matching attribute name</param>
[SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic")]
internal List<string> LoadAssemblies(string[] aFiles, string sAssemblyDirectory, string sMatchingAttribute)
{
AppDomain.CurrentDomain.ReflectionOnlyAssemblyResolve += OnReflectionOnlyAssemblyResolve;
_sRootAsmDir = sAssemblyDirectory;
List<string> aAssemblies = new List<String>();
try
{
sMatchingAttribute = "." + sMatchingAttribute;
foreach (string sFile in aFiles)
Assembly.ReflectionOnlyLoadFrom(sFile);
aAssemblies.AddRange(from asm in AppDomain.CurrentDomain.ReflectionOnlyGetAssemblies()
let attrs = CustomAttributeData.GetCustomAttributes(asm)
where attrs.Any(a => a.ToString().Contains(sMatchingAttribute))
select asm.FullName);
return aAssemblies;
}
catch (FileNotFoundException)
{
/* Continue loading assemblies even if an assembly
* can not be loaded in the new AppDomain. */
return aAssemblies;
}
}
private Assembly OnReflectionOnlyAssemblyResolve(object sender, ResolveEventArgs e)
{
// http://blogs.msdn.com/b/junfeng/archive/2004/08/24/219691.aspx
System.Diagnostics.Debug.WriteLine(e.Name);
AssemblyName name = new AssemblyName(e.Name);
string sAsmToCheck = Path.GetDirectoryName(_sRootAsmDir) + "\\" + name.Name + ".dll";
return File.Exists(sAsmToCheck)
? Assembly.ReflectionOnlyLoadFrom(sAsmToCheck)
: Assembly.ReflectionOnlyLoad(e.Name);
}
}
}
}
Now in my bootstrapper I override the SelectAssemblies method as follows:
...
protected override IEnumerable SelectAssemblies()
{
string sAddinPath = GetAbsolutePath(ADDIN_PATH);
FileCheckList list = new FileCheckList(sAddinPath);
// check only DLL files which were added or changed since last check
SeparateAppDomainAssemblyLoader loader = new SeparateAppDomainAssemblyLoader();
List<string> aAssembliesToRegister = loader.LoadAssemblies(list.GetFiles(null),
sAddinPath, "AssemblyRegisteredWithCM");
string[] aFilesToRegister = (from s in aAssembliesToRegister
select Path.Combine(sAddinPath, s.Substring(0, s.IndexOf(',')) + ".dll")).ToArray();
// update checklist
foreach (string sFile in aFilesToRegister) list.SetCheck(sFile, true);
list.UncheckAllNull();
list.Save();
// register required files
return (new[]
{
Assembly.GetExecutingAssembly(),
}).Union((from s in list.GetFiles(true)
select Assembly.LoadFrom(s))).ToArray();
}
...
As you can see, I am calling the loader not for all the DLL's in my addins path, but only for the ones which a cached files checklist tells me have been added or modified in that folder since the last full scan. This should speed up things a bit and does not require the checklist file to exist: if not found it will be recreated at startup by scanning all the files, if found only added or changed files will be scanned again (I use a CRC to detect changes). So I get the addins folder, create a files checklist for that folder, get from it a list of new or changed files, and pass it to the assemblies loader. This returns only the names of the DLL files which should be registered (i.e. the ones containing assemblies marked with my attribute); I then update the checklist for the next startup, and register only the required files. This way I can let my addin VM's use window managers and correctly locate the view for each required viewmodel. Somewhat ugly, but working. Thanks again to Leaf, who explained me the working of CM.