2019-10-08 12:40:25 +02:00
using System ;
using System.Collections.Generic ;
using System.IO ;
using System.Linq ;
using System.Reflection ;
2019-10-08 19:20:35 +02:00
using System.Text.RegularExpressions ;
2023-12-21 17:48:16 +01:00
using Microsoft.Build.Construction ;
2019-10-08 12:40:25 +02:00
using Microsoft.Xna.Framework.Content.Pipeline ;
2019-10-08 19:20:35 +02:00
using Newtonsoft.Json ;
2023-12-21 18:11:43 +01:00
using NuGet.Configuration ;
2019-10-08 12:40:25 +02:00
2022-09-25 23:29:02 +02:00
namespace Contentless ;
2019-10-08 12:40:25 +02:00
2022-09-25 23:29:02 +02:00
public static class Program {
2023-12-21 18:11:43 +01:00
2022-09-25 23:29:02 +02:00
public static void Main ( string [ ] args ) {
2023-12-21 17:48:16 +01:00
if ( args . Length < 1 ) {
2023-12-21 18:11:43 +01:00
Console . Error . WriteLine ( "Please specify the location of the content file you want to use" ) ;
2022-09-25 23:29:02 +02:00
return ;
}
2019-10-08 15:46:34 +02:00
2022-09-25 23:29:02 +02:00
var contentFile = new FileInfo ( Path . GetFullPath ( Path . Combine ( Environment . CurrentDirectory , args [ 0 ] ) ) ) ;
2023-12-21 21:20:47 +01:00
if ( ! contentFile . Exists | | contentFile . Extension ! = ".mgcb" ) {
Console . Error . WriteLine ( $"Unable to find valid content file at {contentFile}" ) ;
2022-09-25 23:29:02 +02:00
return ;
}
Console . WriteLine ( $"Using content file {contentFile}" ) ;
var content = Program . ReadContent ( contentFile ) ;
// load config
var config = new Config ( ) ;
var configFile = new FileInfo ( Path . Combine ( contentFile . DirectoryName ! , "Contentless.json" ) ) ;
if ( configFile . Exists ) {
using var stream = configFile . OpenText ( ) ;
try {
config = JsonConvert . DeserializeObject < Config > ( stream . ReadToEnd ( ) ) ;
Console . WriteLine ( $"Using config from {configFile}" ) ;
} catch ( Exception e ) {
2023-12-21 18:11:43 +01:00
Console . Error . WriteLine ( $"Error loading config from {configFile}: {e}" ) ;
2022-09-25 23:29:02 +02:00
}
} else {
Console . WriteLine ( "Using default config" ) ;
}
var excluded = config . ExcludedFiles . Select ( Program . MakeFileRegex ) . ToArray ( ) ;
var overrides = Program . GetOverrides ( config . Overrides ) . ToArray ( ) ;
2023-12-21 18:11:43 +01:00
string packagesFolder = null ;
var referencesVersions = config . References . ToDictionary ( x = > x , _ = > ( string ) null , StringComparer . OrdinalIgnoreCase ) ;
2023-12-21 17:48:16 +01:00
if ( config . References . Length > 0 ) {
if ( args . Length > 1 ) {
2023-12-21 19:09:18 +01:00
var csprojFullPath = Path . GetFullPath ( Path . Combine ( Environment . CurrentDirectory , args [ 1 ] ) ) ;
2023-12-21 21:20:47 +01:00
if ( ! File . Exists ( csprojFullPath ) | | Path . GetExtension ( csprojFullPath ) ! = ".csproj" ) {
Console . Error . WriteLine ( $"Unable to find valid project file at {contentFile}" ) ;
return ;
}
2023-12-21 19:09:18 +01:00
Program . ExtractVersions ( csprojFullPath , referencesVersions ) ;
var settings = Settings . LoadDefaultSettings ( Path . GetDirectoryName ( csprojFullPath ) ) ;
2023-12-21 18:11:43 +01:00
packagesFolder = SettingsUtility . GetGlobalPackagesFolder ( settings ) ;
} else {
Console . Error . WriteLine ( "The config file contains references, but no project file was specified. Please specify the location of the content file you want to use for gathering references as the second argument." ) ;
2023-12-21 17:48:16 +01:00
}
}
2023-12-21 18:11:43 +01:00
const string referenceHeader = "/reference:" ;
2023-12-21 17:48:16 +01:00
var changed = false ;
var referencesSyncs = new HashSet < string > ( StringComparer . OrdinalIgnoreCase ) ;
2022-09-25 23:29:02 +02:00
// load any references to be able to include custom content types as well
2023-12-21 18:11:43 +01:00
for ( var i = 0 ; i < content . Count ; i + + ) {
2023-12-21 17:48:16 +01:00
var line = content [ i ] ;
2023-12-21 18:11:43 +01:00
if ( ! line . StartsWith ( referenceHeader ) )
2022-09-25 23:29:02 +02:00
continue ;
2023-12-21 18:11:43 +01:00
var reference = line [ referenceHeader . Length . . ] ;
2023-12-21 17:48:16 +01:00
var libraryName = Path . GetFileName ( reference ) [ . . ^ 4 ] ;
if ( referencesVersions . TryGetValue ( libraryName , out var version ) & & version is not null ) {
2023-12-21 18:11:43 +01:00
var fullLibraryPath = Program . CalculateFullPathToLibrary ( packagesFolder , libraryName , version ) ;
2023-12-21 17:48:16 +01:00
if ( reference ! = fullLibraryPath ) {
2023-12-21 18:11:43 +01:00
Console . WriteLine ( $"Changing reference from {reference} to {fullLibraryPath}" ) ;
2023-12-21 17:48:16 +01:00
reference = fullLibraryPath ;
2023-12-21 18:11:43 +01:00
content [ i ] = referenceHeader + fullLibraryPath ;
2023-12-21 17:48:16 +01:00
changed = true ;
2023-12-21 18:11:43 +01:00
} else {
if ( config . LogSkipped )
2023-12-21 18:28:24 +01:00
Console . WriteLine ( $"Skipping reference replacement for {fullLibraryPath} which already matched" ) ;
2023-12-21 17:48:16 +01:00
}
referencesSyncs . Add ( libraryName ) ;
}
2023-12-21 18:11:43 +01:00
2022-09-25 23:29:02 +02:00
var refPath = Path . GetFullPath ( Path . Combine ( contentFile . DirectoryName , reference ) ) ;
2023-12-21 18:11:43 +01:00
Program . SafeAssemblyLoad ( refPath ) ;
Console . WriteLine ( $"Using reference {refPath}" ) ;
2023-12-21 17:48:16 +01:00
}
// check references not in .mgcb now
var referencesLastIndex = 0 ;
// find place where I can add new reference
2023-12-21 18:11:43 +01:00
for ( var i = 0 ; i < content . Count ; i + + ) {
2023-12-21 17:48:16 +01:00
var line = content [ i ] ;
2023-12-21 18:28:24 +01:00
if ( line . StartsWith ( referenceHeader ) ) {
2023-12-21 17:48:16 +01:00
referencesLastIndex = i + 1 ;
2023-12-21 18:28:24 +01:00
} else if ( line . StartsWith ( "/importer:" ) | | line . StartsWith ( "/processor:" ) | | line . StartsWith ( "/build:" ) | | line . Contains ( "-- Content --" ) ) {
2023-12-21 17:48:16 +01:00
if ( referencesLastIndex = = 0 )
referencesLastIndex = i ;
break ;
2019-10-08 12:40:25 +02:00
}
2022-09-25 23:29:02 +02:00
}
2023-12-21 17:48:16 +01:00
foreach ( var reference in referencesVersions )
if ( ! referencesSyncs . Contains ( reference . Key ) & & reference . Value is not null ) {
try {
2023-12-21 18:11:43 +01:00
var path = Program . CalculateFullPathToLibrary ( packagesFolder , reference . Key , reference . Value ) ;
content . Insert ( referencesLastIndex + + , referenceHeader + path ) ;
2023-12-21 17:48:16 +01:00
changed = true ;
2023-12-21 18:11:43 +01:00
Program . SafeAssemblyLoad ( path ) ;
Console . WriteLine ( $"Adding reference {path}" ) ;
} catch ( Exception e ) {
Console . Error . WriteLine ( $"Error adding reference {reference.Key} {e}" ) ;
2023-12-21 17:48:16 +01:00
}
}
2019-10-08 15:46:34 +02:00
2022-09-25 23:29:02 +02:00
// load content importers
var ( importers , processors ) = Program . GetContentData ( ) ;
Console . WriteLine ( $"Found possible importer types {string.Join(" , ", importers)}" ) ;
Console . WriteLine ( $"Found possible processor types {string.Join(" , ", processors)}" ) ;
foreach ( var file in contentFile . Directory . EnumerateFiles ( "*" , SearchOption . AllDirectories ) ) {
// is the file the content or config file?
if ( file . Name = = contentFile . Name | | file . Name = = configFile . Name )
continue ;
2023-12-21 18:28:24 +01:00
var relative = Path . GetRelativePath ( contentFile . DirectoryName , file . FullName ) . Replace ( '\\' , '/' ) ;
2022-09-25 23:29:02 +02:00
// is the file in an excluded directory?
if ( excluded . Any ( e = > e . IsMatch ( relative ) ) ) {
if ( config . LogSkipped )
Console . WriteLine ( $"Skipping excluded file {relative}" ) ;
continue ;
2019-10-08 19:20:35 +02:00
}
2022-09-25 23:29:02 +02:00
// is the file already in the content file?
if ( Program . HasEntry ( content , relative ) ) {
if ( config . LogSkipped )
Console . WriteLine ( $"Skipping file {relative} as it is already part of the content file" ) ;
continue ;
2019-10-08 15:46:34 +02:00
}
2022-09-25 23:29:02 +02:00
ImporterInfo importer = null ;
string processor = null ;
Dictionary < string , string > processorParams = null ;
2019-10-08 15:46:34 +02:00
2022-09-25 23:29:02 +02:00
// override importers
var over = Program . GetOverrideFor ( relative , overrides ) ;
if ( over ! = null ) {
processorParams = over . Override . ProcessorParams ;
2019-10-08 12:40:25 +02:00
2022-09-25 23:29:02 +02:00
// copy special case
if ( over . Override . Copy ) {
Program . CopyFile ( content , relative ) ;
changed = true ;
2019-10-08 12:40:25 +02:00
continue ;
}
2022-09-25 23:29:02 +02:00
if ( ! string . IsNullOrEmpty ( over . Override . Importer ) ) {
importer = importers . Find ( i = > i . Type . Name = = over . Override . Importer ) ;
if ( importer = = null ) {
2023-12-21 18:11:43 +01:00
Console . Error . WriteLine ( $"Override importer {over.Override.Importer} not found for file {relative}" ) ;
2019-10-28 23:54:06 +01:00
continue ;
}
2019-10-08 12:40:25 +02:00
}
2022-09-25 23:29:02 +02:00
if ( ! string . IsNullOrEmpty ( over . Override . Processor ) ) {
processor = processors . Find ( p = > p = = over . Override . Processor ) ;
if ( processor = = null ) {
2023-12-21 18:11:43 +01:00
Console . Error . WriteLine ( $"Override processor {over.Override.Processor} not found for file {relative}" ) ;
2022-09-25 23:29:02 +02:00
continue ;
2019-12-04 16:08:08 +01:00
}
2019-11-19 14:11:52 +01:00
}
}
2022-09-25 23:29:02 +02:00
// normal importers
importer ? ? = Program . GetImporterFor ( relative , importers ) ;
if ( importer ! = null & & processor = = null )
processor = processors . Find ( p = > p = = importer . Importer . DefaultProcessor ) ;
2019-11-19 14:11:52 +01:00
2022-09-25 23:29:02 +02:00
// no importer found :(
if ( importer = = null | | processor = = null ) {
2023-12-21 18:11:43 +01:00
Console . Error . WriteLine ( $"No importer or processor found for file {relative}" ) ;
2022-09-25 23:29:02 +02:00
continue ;
2019-10-28 23:43:48 +01:00
}
2022-09-25 23:29:02 +02:00
Program . AddFile ( content , relative , importer . Type . Name , processor , processorParams ) ;
changed = true ;
2019-10-08 12:40:25 +02:00
}
2022-09-25 23:29:02 +02:00
if ( changed ) {
contentFile . Delete ( ) ;
using ( var stream = contentFile . CreateText ( ) ) {
foreach ( var line in content )
stream . WriteLine ( line ) ;
2019-10-08 12:40:25 +02:00
}
2022-09-25 23:29:02 +02:00
Console . WriteLine ( "Wrote changes to content file" ) ;
2019-10-08 12:40:25 +02:00
}
2022-09-25 23:29:02 +02:00
Console . Write ( "Done" ) ;
}
2019-10-08 12:40:25 +02:00
2023-12-21 18:11:43 +01:00
private static void SafeAssemblyLoad ( string refPath ) {
2023-12-21 17:48:16 +01:00
try {
Assembly . LoadFrom ( refPath ) ;
} catch ( Exception e ) {
2023-12-21 18:11:43 +01:00
Console . Error . WriteLine ( $"Error loading reference {refPath} {e}" ) ;
2023-12-21 17:48:16 +01:00
}
}
2023-12-21 18:11:43 +01:00
private static void ExtractVersions ( string csprojPath , Dictionary < string , string > referencesVersions ) {
2023-12-21 17:48:16 +01:00
Console . WriteLine ( $"Using project file {csprojPath}" ) ;
var projectRootElement = ProjectRootElement . Open ( csprojPath ) ;
2023-12-21 18:11:43 +01:00
foreach ( var property in projectRootElement . AllChildren . Where ( x = > x . ElementName = = "PackageReference" ) . Select ( x = > x as ProjectItemElement ) ) {
2023-12-21 17:48:16 +01:00
var libraryName = property . Include ;
if ( property . Children . FirstOrDefault ( x = > x . ElementName = = "Version" ) is not ProjectMetadataElement versionElement )
continue ;
var version = versionElement . Value ;
2023-12-21 18:11:43 +01:00
if ( referencesVersions . ContainsKey ( libraryName ) ) {
2023-12-21 17:48:16 +01:00
referencesVersions [ libraryName ] = version ;
2023-12-21 18:11:43 +01:00
Console . WriteLine ( $"Found reference {libraryName} {version} in project file" ) ;
2023-12-21 17:48:16 +01:00
}
}
foreach ( var library in referencesVersions )
2023-12-21 18:11:43 +01:00
if ( library . Value is null )
Console . Error . WriteLine ( $"Unable to find reference {library.Key} in project file" ) ;
2023-12-21 17:48:16 +01:00
}
2023-12-21 18:11:43 +01:00
private static string CalculateFullPathToLibrary ( string packageFolder , string libraryName , string referencesVersion ) {
2023-12-21 18:28:24 +01:00
return Path . Combine ( packageFolder , libraryName . ToLowerInvariant ( ) , referencesVersion , "tools" , libraryName + ".dll" ) . Replace ( '\\' , '/' ) ;
2023-12-21 17:48:16 +01:00
}
2022-09-25 23:29:02 +02:00
private static ( List < ImporterInfo > , List < string > ) GetContentData ( ) {
var importers = new List < ImporterInfo > ( ) ;
var processors = new List < string > ( ) ;
foreach ( var assembly in AppDomain . CurrentDomain . GetAssemblies ( ) ) {
try {
foreach ( var type in assembly . GetTypes ( ) ) {
var importer = ( ContentImporterAttribute ) type . GetCustomAttribute ( typeof ( ContentImporterAttribute ) , true ) ;
if ( importer ! = null )
importers . Add ( new ImporterInfo ( importer , type ) ) ;
var processor = type . GetCustomAttribute ( typeof ( ContentProcessorAttribute ) , true ) ;
if ( processor ! = null )
processors . Add ( type . Name ) ;
2019-10-08 12:40:25 +02:00
}
2022-09-25 23:29:02 +02:00
} catch ( Exception e ) {
2023-12-21 18:11:43 +01:00
Console . Error . WriteLine ( $"Error gathering types in reference {assembly}: {e}" ) ;
2019-10-08 12:40:25 +02:00
}
}
2022-09-25 23:29:02 +02:00
return ( importers , processors ) ;
}
2019-10-08 12:40:25 +02:00
2022-09-25 23:29:02 +02:00
private static IEnumerable < OverrideInfo > GetOverrides ( Dictionary < string , Override > config ) {
foreach ( var entry in config )
yield return new OverrideInfo ( Program . MakeFileRegex ( entry . Key ) , entry . Value ) ;
}
private static OverrideInfo GetOverrideFor ( string file , IEnumerable < OverrideInfo > overrides ) {
foreach ( var over in overrides ) {
if ( over . Regex . IsMatch ( file ) )
return over ;
2019-10-08 12:40:25 +02:00
}
2022-09-25 23:29:02 +02:00
return null ;
}
2019-10-08 12:40:25 +02:00
2022-09-25 23:29:02 +02:00
private static ImporterInfo GetImporterFor ( string file , IEnumerable < ImporterInfo > importers ) {
var extension = Path . GetExtension ( file ) ;
foreach ( var importer in importers ) {
if ( importer . Importer . FileExtensions . Contains ( extension ) )
return importer ;
2019-10-28 23:54:06 +01:00
}
2022-09-25 23:29:02 +02:00
return null ;
}
2019-10-28 23:54:06 +01:00
2022-09-25 23:29:02 +02:00
private static bool HasEntry ( IEnumerable < string > content , string relativeFile ) {
foreach ( var line in content ) {
if ( line . StartsWith ( $"#begin {relativeFile}" ) )
return true ;
2019-10-08 13:23:18 +02:00
}
2022-09-25 23:29:02 +02:00
return false ;
}
private static List < string > ReadContent ( FileInfo file ) {
var content = new List < string > ( ) ;
using var stream = file . OpenText ( ) ;
while ( stream . ReadLine ( ) is { } line )
content . Add ( line ) ;
return content ;
}
2019-10-08 13:23:18 +02:00
2022-09-25 23:29:02 +02:00
private static void AddFile ( ICollection < string > content , string relative , string importer , string processor , Dictionary < string , string > processorParams ) {
content . Add ( $"#begin {relative}" ) ;
content . Add ( $"/importer:{importer}" ) ;
content . Add ( $"/processor:{processor}" ) ;
if ( processorParams ! = null ) {
foreach ( var kv in processorParams )
content . Add ( $"/processorParam:{kv.Key}={kv.Value}" ) ;
2019-10-28 23:43:48 +01:00
}
2022-09-25 23:29:02 +02:00
content . Add ( $"/build:{relative}" ) ;
content . Add ( "" ) ;
Console . WriteLine ( $"Adding file {relative} with importer {importer} and processor {processor}" ) ;
}
private static void CopyFile ( ICollection < string > content , string relative ) {
content . Add ( $"#begin {relative}" ) ;
content . Add ( $"/copy:{relative}" ) ;
content . Add ( "" ) ;
Console . WriteLine ( $"Adding file {relative} with the Copy build action" ) ;
}
2019-10-28 23:43:48 +01:00
2022-09-25 23:29:02 +02:00
private static Regex MakeFileRegex ( string s ) {
return new Regex ( s . Replace ( "." , "[.]" ) . Replace ( "*" , ".*" ) . Replace ( "?" , "." ) ) ;
}
2023-12-21 18:11:43 +01:00
}