2019-01-01 00:00:00 +00:00
using System ;
using System.Collections.Generic ;
using System.IO ;
using System.Linq ;
using System.Security ;
using System.Security.Cryptography ;
using System.Text ;
using System.Text.RegularExpressions ;
using System.Xml ;
using UnityEditor ;
using UnityEditor.Compilation ;
using UnityEditor.PackageManager ;
using UnityEditor.Scripting.Compilers ;
using UnityEditorInternal ;
using UnityEngine ;
namespace VisualStudioEditor
{
public interface IGenerator {
bool SyncIfNeeded ( IEnumerable < string > affectedFiles , IEnumerable < string > reimportedFiles ) ;
void Sync ( ) ;
bool HasSolutionBeenGenerated ( ) ;
string SolutionFile ( ) ;
string ProjectDirectory { get ; }
2019-03-05 00:00:00 +00:00
void GenerateAll ( bool generateAll ) ;
2019-01-01 00:00:00 +00:00
}
public interface IAssemblyNameProvider
{
string GetAssemblyNameFromScriptPath ( string path ) ;
IEnumerable < Assembly > GetAllAssemblies ( Func < string , bool > shouldFileBePartOfSolution ) ;
IEnumerable < string > GetAllAssetPaths ( ) ;
2019-03-05 00:00:00 +00:00
UnityEditor . PackageManager . PackageInfo FindForAssetPath ( string assetPath ) ;
}
public struct TestSettings {
public bool ShouldSync ;
public Dictionary < string , string > SyncPath ;
2019-01-01 00:00:00 +00:00
}
class AssemblyNameProvider : IAssemblyNameProvider
{
public string GetAssemblyNameFromScriptPath ( string path )
{
return CompilationPipeline . GetAssemblyNameFromScriptPath ( path ) ;
}
public IEnumerable < Assembly > GetAllAssemblies ( Func < string , bool > shouldFileBePartOfSolution )
{
return CompilationPipeline . GetAssemblies ( ) . Where ( i = > 0 < i . sourceFiles . Length & & i . sourceFiles . Any ( shouldFileBePartOfSolution ) ) ;
}
public IEnumerable < string > GetAllAssetPaths ( )
{
return AssetDatabase . GetAllAssetPaths ( ) ;
}
2019-03-05 00:00:00 +00:00
public UnityEditor . PackageManager . PackageInfo FindForAssetPath ( string assetPath )
{
return UnityEditor . PackageManager . PackageInfo . FindForAssetPath ( assetPath ) ;
}
2019-01-01 00:00:00 +00:00
}
public class ProjectGeneration : IGenerator
{
enum ScriptingLanguage
{
None ,
CSharp
}
public static readonly string MSBuildNamespaceUri = "http://schemas.microsoft.com/developer/msbuild/2003" ;
const string k_WindowsNewline = "\r\n" ;
/// <summary>
/// Map source extensions to ScriptingLanguages
/// </summary>
static readonly Dictionary < string , ScriptingLanguage > k_BuiltinSupportedExtensions = new Dictionary < string , ScriptingLanguage >
{
{ "cs" , ScriptingLanguage . CSharp } ,
{ "uxml" , ScriptingLanguage . None } ,
{ "uss" , ScriptingLanguage . None } ,
{ "shader" , ScriptingLanguage . None } ,
{ "compute" , ScriptingLanguage . None } ,
{ "cginc" , ScriptingLanguage . None } ,
{ "hlsl" , ScriptingLanguage . None } ,
{ "glslinc" , ScriptingLanguage . None }
} ;
string m_SolutionProjectEntryTemplate = string . Join ( "\r\n" ,
@"Project(""{{{0}}}"") = ""{1}"", ""{2}"", ""{{{3}}}""" ,
@"EndProject" ) . Replace ( " " , "\t" ) ;
string m_SolutionProjectConfigurationTemplate = string . Join ( "\r\n" ,
@" {{{0}}}.Debug|Any CPU.ActiveCfg = Debug|Any CPU" ,
@" {{{0}}}.Debug|Any CPU.Build.0 = Debug|Any CPU" ,
@" {{{0}}}.Release|Any CPU.ActiveCfg = Release|Any CPU" ,
@" {{{0}}}.Release|Any CPU.Build.0 = Release|Any CPU" ) . Replace ( " " , "\t" ) ;
static readonly string [ ] k_ReimportSyncExtensions = { ".dll" , ".asmdef" } ;
static readonly Regex k_ScriptReferenceExpression = new Regex (
@"^Library.ScriptAssemblies.(?<dllname>(?<project>.*)\.dll$)" ,
RegexOptions . Compiled | RegexOptions . IgnoreCase ) ;
string [ ] m_ProjectSupportedExtensions = new string [ 0 ] ;
public string ProjectDirectory { get ; }
2019-03-05 00:00:00 +00:00
public TestSettings Settings { get ; set ; }
2019-01-01 00:00:00 +00:00
readonly string m_ProjectName ;
readonly IAssemblyNameProvider m_AssemblyNameProvider ;
2019-03-05 00:00:00 +00:00
bool m_ShouldGenerateAll ;
2019-01-01 00:00:00 +00:00
2019-03-05 00:00:00 +00:00
public ProjectGeneration ( ) : this ( Directory . GetParent ( Application . dataPath ) . FullName , new AssemblyNameProvider ( ) )
2019-01-01 00:00:00 +00:00
{
}
public ProjectGeneration ( string tempDirectory ) : this ( tempDirectory , new AssemblyNameProvider ( ) ) {
}
public ProjectGeneration ( string tempDirectory , IAssemblyNameProvider assemblyNameProvider ) {
2019-03-05 00:00:00 +00:00
Settings = new TestSettings { ShouldSync = true } ;
2019-01-01 00:00:00 +00:00
ProjectDirectory = tempDirectory . Replace ( '\\' , '/' ) ;
m_ProjectName = Path . GetFileName ( ProjectDirectory ) ;
m_AssemblyNameProvider = assemblyNameProvider ;
}
2019-03-05 00:00:00 +00:00
public void GenerateAll ( bool generateAll )
{
m_ShouldGenerateAll = generateAll ;
}
2019-01-01 00:00:00 +00:00
/// <summary>
/// Syncs the scripting solution if any affected files are relevant.
/// </summary>
/// <returns>
/// Whether the solution was synced.
/// </returns>
/// <param name='affectedFiles'>
/// A set of files whose status has changed
/// </param>
/// <param name="reimportedFiles">
/// A set of files that got reimported
/// </param>
public bool SyncIfNeeded ( IEnumerable < string > affectedFiles , IEnumerable < string > reimportedFiles )
{
SetupProjectSupportedExtensions ( ) ;
// Don't sync if we haven't synced before
if ( HasSolutionBeenGenerated ( ) & & HasFilesBeenModified ( affectedFiles , reimportedFiles ) )
{
Sync ( ) ;
return true ;
}
return false ;
}
bool HasFilesBeenModified ( IEnumerable < string > affectedFiles , IEnumerable < string > reimportedFiles )
{
return affectedFiles . Any ( ShouldFileBePartOfSolution ) | | reimportedFiles . Any ( ShouldSyncOnReimportedAsset ) ;
}
static bool ShouldSyncOnReimportedAsset ( string asset )
{
return k_ReimportSyncExtensions . Contains ( new FileInfo ( asset ) . Extension ) ;
}
public void Sync ( )
{
SetupProjectSupportedExtensions ( ) ;
bool externalCodeAlreadyGeneratedProjects = OnPreGeneratingCSProjectFiles ( ) ;
if ( ! externalCodeAlreadyGeneratedProjects )
{
GenerateAndWriteSolutionAndProjects ( ) ;
}
OnGeneratedCSProjectFiles ( ) ;
}
public bool HasSolutionBeenGenerated ( )
{
return File . Exists ( SolutionFile ( ) ) ;
}
void SetupProjectSupportedExtensions ( )
{
m_ProjectSupportedExtensions = EditorSettings . projectGenerationUserExtensions ;
}
bool ShouldFileBePartOfSolution ( string file )
{
string extension = Path . GetExtension ( file ) ;
// Exclude files coming from packages except if they are internalized.
2019-03-05 00:00:00 +00:00
if ( ! m_ShouldGenerateAll & & IsInternalizedPackagePath ( file ) )
2019-01-01 00:00:00 +00:00
{
return false ;
}
// Dll's are not scripts but still need to be included..
if ( extension = = ".dll" )
return true ;
if ( file . ToLower ( ) . EndsWith ( ".asmdef" ) )
return true ;
return IsSupportedExtension ( extension ) ;
}
bool IsSupportedExtension ( string extension )
{
extension = extension . TrimStart ( '.' ) ;
if ( k_BuiltinSupportedExtensions . ContainsKey ( extension ) )
return true ;
if ( m_ProjectSupportedExtensions . Contains ( extension ) )
return true ;
return false ;
}
static ScriptingLanguage ScriptingLanguageFor ( Assembly island )
{
return ScriptingLanguageFor ( GetExtensionOfSourceFiles ( island . sourceFiles ) ) ;
}
static string GetExtensionOfSourceFiles ( string [ ] files )
{
return files . Length > 0 ? GetExtensionOfSourceFile ( files [ 0 ] ) : "NA" ;
}
static string GetExtensionOfSourceFile ( string file )
{
var ext = Path . GetExtension ( file ) . ToLower ( ) ;
ext = ext . Substring ( 1 ) ; //strip dot
return ext ;
}
static ScriptingLanguage ScriptingLanguageFor ( string extension )
{
return k_BuiltinSupportedExtensions . TryGetValue ( extension . TrimStart ( '.' ) , out var result )
? result
: ScriptingLanguage . None ;
}
2019-04-12 00:00:00 +00:00
static List < Type > SafeGetTypes ( System . Reflection . Assembly a )
{
var ret = new List < Type > ( ) ;
try
{
ret = a . GetTypes ( ) . ToList ( ) ;
}
catch ( System . Reflection . ReflectionTypeLoadException rtl )
{
ret = rtl . Types . ToList ( ) ;
}
catch ( Exception )
{
return new List < Type > ( ) ;
}
return ret . Where ( r = > r ! = null ) . ToList ( ) ;
}
2019-01-01 00:00:00 +00:00
public void GenerateAndWriteSolutionAndProjects ( )
{
// Only synchronize islands that have associated source files and ones that we actually want in the project.
// This also filters out DLLs coming from .asmdef files in packages.
var assemblies = m_AssemblyNameProvider . GetAllAssemblies ( ShouldFileBePartOfSolution ) ;
var allAssetProjectParts = GenerateAllAssetProjectParts ( ) ;
var monoIslands = assemblies . ToList ( ) ;
SyncSolution ( monoIslands ) ;
var allProjectIslands = RelevantIslandsForMode ( monoIslands ) . ToList ( ) ;
foreach ( Assembly assembly in allProjectIslands )
{
var responseFileData = ParseResponseFileData ( assembly ) ;
SyncProject ( assembly , allAssetProjectParts , responseFileData , allProjectIslands ) ;
}
}
IEnumerable < ResponseFileData > ParseResponseFileData ( Assembly assembly )
{
var systemReferenceDirectories = CompilationPipeline . GetSystemAssemblyDirectories ( assembly . compilerOptions . ApiCompatibilityLevel ) ;
Dictionary < string , ResponseFileData > responseFilesData = assembly . compilerOptions . ResponseFiles . ToDictionary ( x = > x , x = > CompilationPipeline . ParseResponseFile (
Path . Combine ( ProjectDirectory , x ) ,
ProjectDirectory ,
systemReferenceDirectories
) ) ;
Dictionary < string , ResponseFileData > responseFilesWithErrors = responseFilesData . Where ( x = > x . Value . Errors . Any ( ) )
. ToDictionary ( x = > x . Key , x = > x . Value ) ;
if ( responseFilesWithErrors . Any ( ) )
{
foreach ( var error in responseFilesWithErrors )
foreach ( var valueError in error . Value . Errors )
{
Debug . LogError ( $"{error.Key} Parse Error : {valueError}" ) ;
}
}
return responseFilesData . Select ( x = > x . Value ) ;
}
Dictionary < string , string > GenerateAllAssetProjectParts ( )
{
Dictionary < string , StringBuilder > stringBuilders = new Dictionary < string , StringBuilder > ( ) ;
foreach ( string asset in m_AssemblyNameProvider . GetAllAssetPaths ( ) )
{
// Exclude files coming from packages except if they are internalized.
2019-03-05 00:00:00 +00:00
if ( ! m_ShouldGenerateAll & & IsInternalizedPackagePath ( asset ) )
2019-01-01 00:00:00 +00:00
{
continue ;
}
string extension = Path . GetExtension ( asset ) ;
if ( IsSupportedExtension ( extension ) & & ScriptingLanguage . None = = ScriptingLanguageFor ( extension ) )
{
// Find assembly the asset belongs to by adding script extension and using compilation pipeline.
var assemblyName = m_AssemblyNameProvider . GetAssemblyNameFromScriptPath ( asset + ".cs" ) ;
if ( string . IsNullOrEmpty ( assemblyName ) )
{
continue ;
}
assemblyName = Utility . FileNameWithoutExtension ( assemblyName ) ;
if ( ! stringBuilders . TryGetValue ( assemblyName , out var projectBuilder ) )
{
projectBuilder = new StringBuilder ( ) ;
stringBuilders [ assemblyName ] = projectBuilder ;
}
projectBuilder . Append ( " <None Include=\"" ) . Append ( EscapedRelativePathFor ( asset ) ) . Append ( "\" />" ) . Append ( k_WindowsNewline ) ;
}
}
var result = new Dictionary < string , string > ( ) ;
foreach ( var entry in stringBuilders )
result [ entry . Key ] = entry . Value . ToString ( ) ;
return result ;
}
2019-03-05 00:00:00 +00:00
bool IsInternalizedPackagePath ( string file )
2019-01-01 00:00:00 +00:00
{
if ( string . IsNullOrWhiteSpace ( file ) )
{
return false ;
}
2019-03-05 00:00:00 +00:00
var packageInfo = m_AssemblyNameProvider . FindForAssetPath ( file ) ;
2019-01-01 00:00:00 +00:00
if ( packageInfo = = null ) {
return false ;
}
var packageSource = packageInfo . source ;
return packageSource ! = PackageSource . Embedded & & packageSource ! = PackageSource . Local ;
}
void SyncProject (
Assembly island ,
Dictionary < string , string > allAssetsProjectParts ,
IEnumerable < ResponseFileData > responseFilesData ,
List < Assembly > allProjectIslands )
{
SyncProjectFileIfNotChanged ( ProjectFile ( island ) , ProjectText ( island , allAssetsProjectParts , responseFilesData , allProjectIslands ) ) ;
}
2019-03-05 00:00:00 +00:00
void SyncProjectFileIfNotChanged ( string path , string newContents )
2019-01-01 00:00:00 +00:00
{
if ( Path . GetExtension ( path ) = = ".csproj" )
{
newContents = OnGeneratedCSProject ( path , newContents ) ;
}
SyncFileIfNotChanged ( path , newContents ) ;
}
2019-03-05 00:00:00 +00:00
void SyncSolutionFileIfNotChanged ( string path , string newContents )
2019-01-01 00:00:00 +00:00
{
newContents = OnGeneratedSlnSolution ( path , newContents ) ;
SyncFileIfNotChanged ( path , newContents ) ;
}
static void OnGeneratedCSProjectFiles ( )
{
2019-04-12 00:00:00 +00:00
IEnumerable < Type > types = AppDomain . CurrentDomain . GetAssemblies ( ) . SelectMany ( x = > SafeGetTypes ( x ) )
2019-01-01 00:00:00 +00:00
. Where ( x = > typeof ( AssetPostprocessor ) . IsAssignableFrom ( x ) & & ! x . IsInterface & & ! x . IsAbstract ) ;
var args = new object [ 0 ] ;
foreach ( var type in types )
{
var method = type . GetMethod ( "OnGeneratedCSProjectFiles" , System . Reflection . BindingFlags . Public | System . Reflection . BindingFlags . NonPublic | System . Reflection . BindingFlags . Static ) ;
if ( method = = null )
{
continue ;
}
method . Invoke ( null , args ) ;
}
}
static bool OnPreGeneratingCSProjectFiles ( )
{
2019-04-12 00:00:00 +00:00
IEnumerable < Type > types = AppDomain . CurrentDomain . GetAssemblies ( ) . SelectMany ( x = > SafeGetTypes ( x ) )
2019-01-01 00:00:00 +00:00
. Where ( x = > typeof ( AssetPostprocessor ) . IsAssignableFrom ( x ) & & ! x . IsInterface & & ! x . IsAbstract ) ;
bool result = false ;
foreach ( var type in types )
{
var args = new object [ 0 ] ;
var method = type . GetMethod ( "OnPreGeneratingCSProjectFiles" , System . Reflection . BindingFlags . Public | System . Reflection . BindingFlags . NonPublic | System . Reflection . BindingFlags . Static ) ;
if ( method = = null )
{
continue ;
}
var returnValue = method . Invoke ( null , args ) ;
if ( method . ReturnType = = typeof ( bool ) )
{
result | = ( bool ) returnValue ;
}
}
return result ;
}
static string OnGeneratedCSProject ( string path , string content )
{
2019-04-12 00:00:00 +00:00
IEnumerable < Type > types = AppDomain . CurrentDomain . GetAssemblies ( ) . SelectMany ( x = > SafeGetTypes ( x ) )
2019-01-01 00:00:00 +00:00
. Where ( x = > typeof ( AssetPostprocessor ) . IsAssignableFrom ( x ) & & ! x . IsInterface & & ! x . IsAbstract ) ;
foreach ( var type in types )
{
var args = new [ ] { path , content } ;
var method = type . GetMethod ( "OnGeneratedCSProject" , System . Reflection . BindingFlags . Public | System . Reflection . BindingFlags . NonPublic | System . Reflection . BindingFlags . Static ) ;
if ( method = = null )
{
continue ;
}
var returnValue = method . Invoke ( null , args ) ;
if ( method . ReturnType = = typeof ( string ) )
{
content = ( string ) returnValue ;
}
}
return content ;
}
static string OnGeneratedSlnSolution ( string path , string content )
{
2019-04-12 00:00:00 +00:00
IEnumerable < Type > types = AppDomain . CurrentDomain . GetAssemblies ( ) . SelectMany ( x = > SafeGetTypes ( x ) )
2019-01-01 00:00:00 +00:00
. Where ( x = > typeof ( AssetPostprocessor ) . IsAssignableFrom ( x ) & & ! x . IsInterface & & ! x . IsAbstract ) ;
foreach ( var type in types )
{
var args = new [ ] { path , content } ;
var method = type . GetMethod ( "OnGeneratedSlnSolution" , System . Reflection . BindingFlags . Public | System . Reflection . BindingFlags . NonPublic | System . Reflection . BindingFlags . Static ) ;
if ( method = = null )
{
continue ;
}
var returnValue = method . Invoke ( null , args ) ;
if ( method . ReturnType = = typeof ( string ) )
{
content = ( string ) returnValue ;
}
}
return content ;
}
2019-03-05 00:00:00 +00:00
void SyncFileIfNotChanged ( string filename , string newContents )
2019-01-01 00:00:00 +00:00
{
if ( File . Exists ( filename ) & &
newContents = = File . ReadAllText ( filename ) )
{
return ;
}
2019-03-05 00:00:00 +00:00
if ( Settings . ShouldSync )
{
File . WriteAllText ( filename , newContents , Encoding . UTF8 ) ;
}
else
{
var utf8 = Encoding . UTF8 ;
byte [ ] utfBytes = utf8 . GetBytes ( newContents ) ;
Settings . SyncPath [ filename ] = utf8 . GetString ( utfBytes , 0 , utfBytes . Length ) ;
}
2019-01-01 00:00:00 +00:00
}
string ProjectText ( Assembly assembly ,
Dictionary < string , string > allAssetsProjectParts ,
IEnumerable < ResponseFileData > responseFilesData ,
List < Assembly > allProjectIslands )
{
var projectBuilder = new StringBuilder ( ProjectHeader ( assembly , responseFilesData ) ) ;
var references = new List < string > ( ) ;
var projectReferences = new List < Match > ( ) ;
foreach ( string file in assembly . sourceFiles )
{
if ( ! ShouldFileBePartOfSolution ( file ) )
continue ;
var extension = Path . GetExtension ( file ) . ToLower ( ) ;
var fullFile = EscapedRelativePathFor ( file ) ;
if ( ".dll" ! = extension )
{
projectBuilder . Append ( " <Compile Include=\"" ) . Append ( fullFile ) . Append ( "\" />" ) . Append ( k_WindowsNewline ) ;
}
else
{
references . Add ( fullFile ) ;
}
}
var assemblyName = Utility . FileNameWithoutExtension ( assembly . outputPath ) ;
// Append additional non-script files that should be included in project generation.
if ( allAssetsProjectParts . TryGetValue ( assemblyName , out var additionalAssetsForProject ) )
projectBuilder . Append ( additionalAssetsForProject ) ;
var islandRefs = references . Union ( assembly . allReferences ) ;
foreach ( string reference in islandRefs )
{
if ( reference . EndsWith ( "/UnityEditor.dll" , StringComparison . Ordinal )
| | reference . EndsWith ( "/UnityEngine.dll" , StringComparison . Ordinal )
| | reference . EndsWith ( "\\UnityEditor.dll" , StringComparison . Ordinal )
| | reference . EndsWith ( "\\UnityEngine.dll" , StringComparison . Ordinal ) )
continue ;
var match = k_ScriptReferenceExpression . Match ( reference ) ;
if ( match . Success )
{
// assume csharp language
// Add a reference to a project except if it's a reference to a script assembly
// that we are not generating a project for. This will be the case for assemblies
// coming from .assembly.json files in non-internalized packages.
var dllName = match . Groups [ "dllname" ] . Value ;
if ( allProjectIslands . Any ( i = > Path . GetFileName ( i . outputPath ) = = dllName ) )
{
projectReferences . Add ( match ) ;
continue ;
}
}
string fullReference = Path . IsPathRooted ( reference ) ? reference : Path . Combine ( ProjectDirectory , reference ) ;
AppendReference ( fullReference , projectBuilder ) ;
}
var responseRefs = responseFilesData . SelectMany ( x = > x . FullPathReferences . Select ( r = > r ) ) ;
foreach ( var reference in responseRefs )
{
AppendReference ( reference , projectBuilder ) ;
}
if ( 0 < projectReferences . Count )
{
projectBuilder . AppendLine ( " </ItemGroup>" ) ;
projectBuilder . AppendLine ( " <ItemGroup>" ) ;
foreach ( Match reference in projectReferences )
{
var referencedProject = reference . Groups [ "project" ] . Value ;
projectBuilder . Append ( " <ProjectReference Include=\"" ) . Append ( referencedProject ) . Append ( GetProjectExtension ( ) ) . Append ( "\">" ) . Append ( k_WindowsNewline ) ;
projectBuilder . Append ( " <Project>{" ) . Append ( ProjectGuid ( Path . Combine ( "Temp" , reference . Groups [ "project" ] . Value + ".dll" ) ) ) . Append ( "}</Project>" ) . Append ( k_WindowsNewline ) ;
projectBuilder . Append ( " <Name>" ) . Append ( referencedProject ) . Append ( "</Name>" ) . Append ( k_WindowsNewline ) ;
projectBuilder . AppendLine ( " </ProjectReference>" ) ;
}
}
projectBuilder . Append ( ProjectFooter ( ) ) ;
return projectBuilder . ToString ( ) ;
}
static void AppendReference ( string fullReference , StringBuilder projectBuilder )
{
//replace \ with / and \\ with /
var escapedFullPath = SecurityElement . Escape ( fullReference ) ;
escapedFullPath = escapedFullPath . Replace ( "\\" , "/" ) ;
escapedFullPath = escapedFullPath . Replace ( "\\\\" , "/" ) ;
projectBuilder . Append ( " <Reference Include=\"" ) . Append ( Utility . FileNameWithoutExtension ( escapedFullPath ) ) . Append ( "\">" ) . Append ( k_WindowsNewline ) ;
projectBuilder . Append ( " <HintPath>" ) . Append ( escapedFullPath ) . Append ( "</HintPath>" ) . Append ( k_WindowsNewline ) ;
projectBuilder . Append ( " </Reference>" ) . Append ( k_WindowsNewline ) ;
}
public string ProjectFile ( Assembly assembly )
{
return Path . Combine ( ProjectDirectory , $"{Utility.FileNameWithoutExtension(assembly.outputPath)}.csproj" ) ;
}
public string SolutionFile ( )
{
return Path . Combine ( ProjectDirectory , $"{m_ProjectName}.sln" ) ;
}
string ProjectHeader (
Assembly island ,
IEnumerable < ResponseFileData > responseFilesData
)
{
var toolsVersion = "4.0" ;
var productVersion = "10.0.20506" ;
const string baseDirectory = "." ;
var targetFrameworkVersion = "v4.7.1" ;
var targetLanguageVersion = "latest" ;
var arguments = new object [ ]
{
toolsVersion , productVersion , ProjectGuid ( island . outputPath ) ,
InternalEditorUtility . GetEngineAssemblyPath ( ) ,
InternalEditorUtility . GetEditorAssemblyPath ( ) ,
string . Join ( ";" , new [ ] { "DEBUG" , "TRACE" } . Concat ( EditorUserBuildSettings . activeScriptCompilationDefines ) . Concat ( island . defines ) . Concat ( responseFilesData . SelectMany ( x = > x . Defines ) ) . Distinct ( ) . ToArray ( ) ) ,
MSBuildNamespaceUri ,
Utility . FileNameWithoutExtension ( island . outputPath ) ,
EditorSettings . projectGenerationRootNamespace ,
targetFrameworkVersion ,
targetLanguageVersion ,
baseDirectory ,
island . compilerOptions . AllowUnsafeCode | responseFilesData . Any ( x = > x . Unsafe )
} ;
try
{
return string . Format ( GetProjectHeaderTemplate ( ) , arguments ) ;
}
catch ( Exception )
{
throw new NotSupportedException ( "Failed creating c# project because the c# project header did not have the correct amount of arguments, which is " + arguments . Length ) ;
}
}
static string GetSolutionText ( )
{
return string . Join ( "\r\n" ,
@"" ,
@"Microsoft Visual Studio Solution File, Format Version {0}" ,
@"# Visual Studio {1}" ,
@"{2}" ,
@"Global" ,
@" GlobalSection(SolutionConfigurationPlatforms) = preSolution" ,
@" Debug|Any CPU = Debug|Any CPU" ,
@" Release|Any CPU = Release|Any CPU" ,
@" EndGlobalSection" ,
@" GlobalSection(ProjectConfigurationPlatforms) = postSolution" ,
@"{3}" ,
@" EndGlobalSection" ,
@" GlobalSection(SolutionProperties) = preSolution" ,
@" HideSolutionNode = FALSE" ,
@" EndGlobalSection" ,
@"EndGlobal" ,
@"" ) . Replace ( " " , "\t" ) ;
}
static string GetProjectFooterTemplate ( )
{
return string . Join ( "\r\n" ,
@" </ItemGroup>" ,
@" <Import Project=""$(MSBuildToolsPath)\Microsoft.CSharp.targets"" />" ,
@" <!-- To modify your build process, add your task inside one of the targets below and uncomment it. " ,
@" Other similar extension points exist, see Microsoft.Common.targets." ,
@" <Target Name=""BeforeBuild"">" ,
@" </Target>" ,
@" <Target Name=""AfterBuild"">" ,
@" </Target>" ,
@" -->" ,
@"</Project>" ,
@"" ) ;
}
static string GetProjectHeaderTemplate ( )
{
var header = new [ ]
{
@"<?xml version=""1.0"" encoding=""utf-8""?>" ,
@"<Project ToolsVersion=""{0}"" DefaultTargets=""Build"" xmlns=""{6}"">" ,
@" <PropertyGroup>" ,
@" <LangVersion>{10}</LangVersion>" ,
@" </PropertyGroup>" ,
@" <PropertyGroup>" ,
@" <Configuration Condition="" '$(Configuration)' == '' "">Debug</Configuration>" ,
@" <Platform Condition="" '$(Platform)' == '' "">AnyCPU</Platform>" ,
@" <ProductVersion>{1}</ProductVersion>" ,
@" <SchemaVersion>2.0</SchemaVersion>" ,
@" <RootNamespace>{8}</RootNamespace>" ,
@" <ProjectGuid>{{{2}}}</ProjectGuid>" ,
@" <OutputType>Library</OutputType>" ,
@" <AppDesignerFolder>Properties</AppDesignerFolder>" ,
@" <AssemblyName>{7}</AssemblyName>" ,
@" <TargetFrameworkVersion>{9}</TargetFrameworkVersion>" ,
@" <FileAlignment>512</FileAlignment>" ,
@" <BaseDirectory>{11}</BaseDirectory>" ,
@" </PropertyGroup>" ,
@" <PropertyGroup Condition="" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "">" ,
@" <DebugSymbols>true</DebugSymbols>" ,
@" <DebugType>full</DebugType>" ,
@" <Optimize>false</Optimize>" ,
@" <OutputPath>Temp\bin\Debug\</OutputPath>" ,
@" <DefineConstants>{5}</DefineConstants>" ,
@" <ErrorReport>prompt</ErrorReport>" ,
@" <WarningLevel>4</WarningLevel>" ,
@" <NoWarn>0169</NoWarn>" ,
@" <AllowUnsafeBlocks>{12}</AllowUnsafeBlocks>" ,
@" </PropertyGroup>" ,
@" <PropertyGroup Condition="" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "">" ,
@" <DebugType>pdbonly</DebugType>" ,
@" <Optimize>true</Optimize>" ,
@" <OutputPath>Temp\bin\Release\</OutputPath>" ,
@" <ErrorReport>prompt</ErrorReport>" ,
@" <WarningLevel>4</WarningLevel>" ,
@" <NoWarn>0169</NoWarn>" ,
@" <AllowUnsafeBlocks>{12}</AllowUnsafeBlocks>" ,
@" </PropertyGroup>"
} ;
var forceExplicitReferences = new [ ]
{
@" <PropertyGroup>" ,
@" <NoConfig>true</NoConfig>" ,
@" <NoStdLib>true</NoStdLib>" ,
@" <AddAdditionalExplicitAssemblyReferences>false</AddAdditionalExplicitAssemblyReferences>" ,
@" <ImplicitlyExpandNETStandardFacades>false</ImplicitlyExpandNETStandardFacades>" ,
@" <ImplicitlyExpandDesignTimeFacades>false</ImplicitlyExpandDesignTimeFacades>" ,
@" </PropertyGroup>"
} ;
var itemGroupStart = new [ ]
{
@" <ItemGroup>"
} ;
var footer = new [ ]
{
@" <Reference Include=""UnityEngine"">" ,
@" <HintPath>{3}</HintPath>" ,
@" </Reference>" ,
@" <Reference Include=""UnityEditor"">" ,
@" <HintPath>{4}</HintPath>" ,
@" </Reference>" ,
@" </ItemGroup>" ,
@" <ItemGroup>" ,
@""
} ;
var text = header . Concat ( forceExplicitReferences ) . Concat ( itemGroupStart ) . Concat ( footer ) . ToArray ( ) ;
return string . Join ( "\r\n" , text ) ;
}
void SyncSolution ( IEnumerable < Assembly > islands )
{
SyncSolutionFileIfNotChanged ( SolutionFile ( ) , SolutionText ( islands ) ) ;
}
string SolutionText ( IEnumerable < Assembly > islands )
{
var fileversion = "11.00" ;
var vsversion = "2010" ;
var relevantIslands = RelevantIslandsForMode ( islands ) ;
string projectEntries = GetProjectEntries ( relevantIslands ) ;
string projectConfigurations = string . Join ( k_WindowsNewline , relevantIslands . Select ( i = > GetProjectActiveConfigurations ( ProjectGuid ( i . outputPath ) ) ) . ToArray ( ) ) ;
return string . Format ( GetSolutionText ( ) , fileversion , vsversion , projectEntries , projectConfigurations ) ;
}
static IEnumerable < Assembly > RelevantIslandsForMode ( IEnumerable < Assembly > islands )
{
IEnumerable < Assembly > relevantIslands = islands . Where ( i = > ScriptingLanguage . CSharp = = ScriptingLanguageFor ( i ) ) ;
return relevantIslands ;
}
/// <summary>
/// Get a Project("{guid}") = "MyProject", "MyProject.unityproj", "{projectguid}"
/// entry for each relevant language
/// </summary>
string GetProjectEntries ( IEnumerable < Assembly > islands )
{
var projectEntries = islands . Select ( i = > string . Format (
m_SolutionProjectEntryTemplate ,
SolutionGuid ( i ) , Utility . FileNameWithoutExtension ( i . outputPath ) , Path . GetFileName ( ProjectFile ( i ) ) , ProjectGuid ( i . outputPath )
) ) ;
return string . Join ( k_WindowsNewline , projectEntries . ToArray ( ) ) ;
}
/// <summary>
/// Generate the active configuration string for a given project guid
/// </summary>
string GetProjectActiveConfigurations ( string projectGuid )
{
return string . Format (
m_SolutionProjectConfigurationTemplate ,
projectGuid ) ;
}
string EscapedRelativePathFor ( string file )
{
var projectDir = ProjectDirectory . Replace ( '/' , '\\' ) ;
file = file . Replace ( '/' , '\\' ) ;
var path = SkipPathPrefix ( file , projectDir ) ;
2019-03-05 00:00:00 +00:00
var packageInfo = m_AssemblyNameProvider . FindForAssetPath ( path . Replace ( '\\' , '/' ) ) ;
2019-01-01 00:00:00 +00:00
if ( packageInfo ! = null ) {
// We have to normalize the path, because the PackageManagerRemapper assumes
// dir seperators will be os specific.
var absolutePath = Path . GetFullPath ( NormalizePath ( path ) ) . Replace ( '/' , '\\' ) ;
path = SkipPathPrefix ( absolutePath , projectDir ) ;
}
return SecurityElement . Escape ( path ) ;
}
static string SkipPathPrefix ( string path , string prefix )
{
2019-03-05 00:00:00 +00:00
if ( path . Replace ( "\\" , "/" ) . StartsWith ( $"{prefix}/" ) )
2019-01-01 00:00:00 +00:00
return path . Substring ( prefix . Length + 1 ) ;
return path ;
}
static string NormalizePath ( string path )
{
if ( Path . DirectorySeparatorChar = = '\\' )
return path . Replace ( '/' , Path . DirectorySeparatorChar ) ;
return path . Replace ( '\\' , Path . DirectorySeparatorChar ) ;
}
string ProjectGuid ( string assembly )
{
return SolutionGuidGenerator . GuidForProject ( m_ProjectName + Utility . FileNameWithoutExtension ( assembly ) ) ;
}
string SolutionGuid ( Assembly island )
{
return SolutionGuidGenerator . GuidForSolution ( m_ProjectName , GetExtensionOfSourceFiles ( island . sourceFiles ) ) ;
}
static string ProjectFooter ( )
{
return GetProjectFooterTemplate ( ) ;
}
static string GetProjectExtension ( )
{
return ".csproj" ;
}
}
public static class SolutionGuidGenerator
{
public static string GuidForProject ( string projectName )
{
return ComputeGuidHashFor ( projectName + "salt" ) ;
}
public static string GuidForSolution ( string projectName , string sourceFileExtension )
{
if ( sourceFileExtension . ToLower ( ) = = "cs" )
// GUID for a C# class library: http://www.codeproject.com/Reference/720512/List-of-Visual-Studio-Project-Type-GUIDs
return "FAE04EC0-301F-11D3-BF4B-00C04F79EFBC" ;
return ComputeGuidHashFor ( projectName ) ;
}
static string ComputeGuidHashFor ( string input )
{
var hash = MD5 . Create ( ) . ComputeHash ( Encoding . Default . GetBytes ( input ) ) ;
return HashAsGuid ( HashToString ( hash ) ) ;
}
static string HashAsGuid ( string hash )
{
var guid = hash . Substring ( 0 , 8 ) + "-" + hash . Substring ( 8 , 4 ) + "-" + hash . Substring ( 12 , 4 ) + "-" + hash . Substring ( 16 , 4 ) + "-" + hash . Substring ( 20 , 12 ) ;
return guid . ToUpper ( ) ;
}
static string HashToString ( byte [ ] bs )
{
var sb = new StringBuilder ( ) ;
foreach ( byte b in bs )
sb . Append ( b . ToString ( "x2" ) ) ;
return sb . ToString ( ) ;
}
}
}