SilentSin
SilentSin

Reputation: 1036

Getting the file path of the .cs file where a type was declared

I'm working on a procedural asset generation system and I want it to be able to detect if the source file of a particular asset has changed so that it only has to regenerate assets that will actually be different.

Some googling has told me that there is no way to get the source file of a type using simple reflection, so I'm trying to come up with a workaround. Here's what I'm doing:

  1. Get a list of all .cs files in the project directory using Directory.GetFiles.
  2. Compile each file into its own assembly using CSharpCodeProvider.CompileAssemblyFromFile.
  3. If compiledAssembly.GetType(targetType.FullName) exists, then that is the source file of targetType.

The problem is that step 2 is giving compile errors without any sort of description:

I think that this may be because the current assembly or app domain or whatever already contains the exact type it's trying to compile, but that's just a guess. Does anyone know what might be causing those errors or how I can avoid them?

Edit: here's the main code for step 2:

private static readonly CSharpCodeProvider CodeProvider = new CSharpCodeProvider();
private static readonly CompilerParameters CompilerOptions = new CompilerParameters();

static SourceInterpreter()
{
    // We want a DLL in memory.
    CompilerOptions.GenerateExecutable = false;
    CompilerOptions.GenerateInMemory = true;

    // Add references for UnityEngine and UnityEditor DLLs.
    CompilerOptions.ReferencedAssemblies.Add(typeof(Application).Assembly.Location);
    CompilerOptions.ReferencedAssemblies.Add(typeof(EditorApplication).Assembly.Location);
}

private static Assembly CompileCS(string filePath, out CompilerErrorCollection errors)
{
    // Compile the assembly from the source script text.
    CompilerResults result = CodeProvider.CompileAssemblyFromFile(CompilerOptions, filePath);

    // Store any errors and warnings.
    errors = result.Errors;

    foreach (CompilerError e in errors)
    {
        if (!e.IsWarning) Debug.Log(e);
    }

    return result.CompiledAssembly;
}

Upvotes: 1

Views: 2814

Answers (1)

Bunny83
Bunny83

Reputation: 1119

Scripts in unity are treated as assets as well. Unity uses the MonoScript class to represent the script file itself. An instance of the MonoScript class has the GetClass method to get the System.Type object for the class that is declared inside that script file.

The MonoScript class is derived from TextAsset and as such a UnityEngine.Object as well. Loaded instances usually can be found with Resources.FindObjectsOfTypeAll. Additionally there are the two static methods FromMonoBehaviour and FromScriptableObject which find and return the correct MonoScript instance for a given MonoBehaviour or ScriptableObject instance.

Once you have a MonoScript instance you can use GetAssetPath to determine where the script file is stored.

I wouldn't suggest to manually compile each class since, as you already mentioned, you can't load the same class twice from different assemblies if they are in the same namespace.

Unfortunately there is no FindFromType method in the MonoScript class so you have to have an instance of your class or check each MonoScript in the project manually.

Upvotes: 1

Related Questions