using System.Collections.Generic; using System.IO; using System.Linq; using Sharpmake; using Configuration = Sharpmake.Project.Configuration; namespace JustDance.Tools.Helpers { /// /// Creates ntfs hard link to references instead of copying them locally, /// in order to improve the build time and reduces the output size. /// /// Should be called after all the projects have been resolved. internal static class CopyLocalOptimizer { public static void ConvertCopiesIntoHardlinks(this Project project) { foreach (var configuration in project.Configurations) { configuration.ConvertCopiesIntoHardlinks(); } } private static void ConvertCopiesIntoHardlinks(this Configuration configuration) { var dependencies = new HashSet(); var mklinker = Path.Combine(Globals.BranchRoot, @"extern\mklinker\mklinker.exe"); CollectDependenciesRecursive(dependencies, configuration); var filesToCopy = dependencies.SelectMany(GetFilesToCopy).Concat(GetReferencesByPath(configuration)); foreach (var toCopy in filesToCopy) { var sourceFile = toCopy.Source; var targetFile = Path.Combine(configuration.TargetPath, toCopy.RelativePath); // C# supports a single declaration of post build events, we must replace the target // configuration with its corresponding macro. var postBuild = string.Format("\"{0}\" \"{1}\" \"{2}\"", mklinker, targetFile, sourceFile); postBuild = postBuild.Replace(string.Format("\\{0}\\", configuration.Target.Name), "\\$(ConfigurationName)\\"); configuration.EventPostBuild.Add(postBuild); } } private static IEnumerable GetFilesToCopy(Configuration dependency) { var toCopy = new List(); toCopy.AddRange(GetCustomBuildActionFiles(dependency)); toCopy.AddRange(GetReferencesByPath(dependency)); toCopy.Add(FileToCopy.Create(Path.Combine(dependency.TargetPath, dependency.TargetFileFullName + GetAssemblyExtension(dependency)))); toCopy.Add(FileToCopy.Create(Path.Combine(dependency.TargetPath, dependency.TargetFileFullName + ".pdb"))); return toCopy; } private static IEnumerable GetReferencesByPath(Configuration dependency) { return dependency.ReferencesByPath.Select(file => Util.PathGetAbsolute(dependency.TargetPath, file)).Select(FileToCopy.Create); } /// /// Returns the files in the project that are copied to output. They should be relative to their folder. /// private static IEnumerable GetCustomBuildActionFiles(Configuration dependency) { var project = dependency.Project as CSharpProject; if (project != null) { return from additionalFile in project.AdditionalContentAlwaysCopy.Concat(project.AdditionalContentCopyIfNewer) let absolutePath = Path.Combine(project.SourceRootPath, additionalFile) from file in ExpandWildcards(absolutePath) select new FileToCopy { Source = file, RelativePath = Util.PathGetRelative(project.SourceRootPath, file) }; } return new FileToCopy[0]; } /// /// Expands the * into their files. /// private static IEnumerable ExpandWildcards(string sourceFile) { if (sourceFile.EndsWith("*")) { var directory = sourceFile.TrimEnd('*'); if (Directory.Exists(directory)) { foreach (var file in Directory.GetFiles(directory, "*", SearchOption.AllDirectories)) { yield return file; } } } else { yield return sourceFile; } } private static void CollectDependenciesRecursive(HashSet configurations, Configuration configuration) { foreach (var dependency in configuration.ResolvedDependencies) { if (configurations.Add(dependency)) { CollectDependenciesRecursive(configurations, dependency); } } } private static string GetAssemblyExtension(Configuration dependency) { return (dependency.Output == Configuration.OutputType.Exe || dependency.Output == Configuration.OutputType.DotNetWindowsApp || dependency.Output == Configuration.OutputType.DotNetConsoleApp) ? ".exe" : ".dll"; } private struct FileToCopy { /// /// The full path of the sourceFolder file. /// public string Source { get; set; } /// /// The destination relative to the output. /// public string RelativePath { get; set; } public static FileToCopy Create(string source) { return new FileToCopy { Source = source, RelativePath = Path.GetFileName(source) }; } } } }