use namespace="System" use namespace="System.Collections.Generic" use namespace="System.IO" use namespace="System.Linq" use namespace="System.Reflection" use namespace="System.Text" use namespace="System.Web.Script.Serialization" use namespace="System.Xml.Linq" use assembly="System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" @{/* k-generate-projects Generate csproj files from project.json solutionPath='' Required. Path to the solution directory */} content var='net45' include href='net45.txt' content var='k10' include href='k10.txt' @{ ProjectGenerator.Logger = Log; var templates = new Dictionary { { "net45", net45 }, { "k10", k10 } }; ProjectGenerator.MakeProjects(solutionPath, templates); } functions @{ class ProjectGenerator { public static Sake.Engine.Logging.ILog Logger { get; set; } static void Log(string message, params object[] args) { Logger.Info(String.Format(message, args)); } static void Warn(string message, params object[] args) { Logger.Warn(String.Format(message, args)); } public static void MakeProjects(string solutionPath, IDictionary templates) { var jsonFiles = GetJsonFiles(solutionPath); var projectMapping = GetProjectMapping(solutionPath, jsonFiles); Log("Found {0} projects", jsonFiles.Length); foreach (var p in jsonFiles) { Log(p); } foreach (var path in jsonFiles) { ProduceProjectFilesForProject(path, projectMapping, templates); } } private static string[] GetJsonFiles(string solutionPath) { Func getFiles = dir => { string path = Path.Combine(solutionPath, dir); if (!Directory.Exists(path)) { return new string[0]; } return Directory.GetFiles(path, "project.json", SearchOption.AllDirectories); }; return getFiles("src").Concat(getFiles("samples")) .Concat(getFiles("test")) .ToArray(); } private static IDictionary GetProjectMapping(string solutionPath, string[] jsonFiles) { var dict = new Dictionary(); foreach (var path in jsonFiles) { string projectDir = Path.GetDirectoryName(path); string projectName = projectDir.Substring(Path.GetDirectoryName(projectDir).Length).Trim(Path.DirectorySeparatorChar); // { // "p1" : { "net45" : "id", "k10" : "pid1", path: "src\p1" }, // "p2" : { "net45" : "id", "k10" : "pid2", path: "src\p2" } // } // string net45Project = Path.Combine(projectDir, GetProjectFileName(projectName, "net45")); string k10Project = Path.Combine(projectDir, GetProjectFileName(projectName, "k10")); var configs = new Dictionary(); configs["net45"] = GetProjectGuidFromFileOrCreateNew(net45Project); configs["k10"] = GetProjectGuidFromFileOrCreateNew(k10Project); configs["path"] = Path.GetDirectoryName(path.Substring(solutionPath.Length).TrimStart(Path.DirectorySeparatorChar)); dict[projectName] = configs; } return dict; } private static string GetProjectGuidFromFileOrCreateNew(string projectPath) { if (!File.Exists(projectPath)) { return Guid.NewGuid().ToString().ToUpper(); } var projectGuid = XDocument.Parse(File.ReadAllText(projectPath)) .Descendants() .FirstOrDefault(e => e.Name.LocalName.Equals("ProjectGuid")); if (projectGuid == null) { return Guid.NewGuid().ToString(); } return projectGuid.Value.Trim((char)'{', (char)'}'); } private static void ProduceProjectFilesForProject(string jsonPath, IDictionary projectMapping, IDictionary templates) { var serializer = new JavaScriptSerializer(); string projectDir = Path.GetDirectoryName(jsonPath); string projectName = projectDir.Substring(Path.GetDirectoryName(projectDir).Length).Trim(Path.DirectorySeparatorChar); Log("Generated projects for {0}", projectName); var jsonText = File.ReadAllText(jsonPath); var d = serializer.DeserializeObject(jsonText) as IDictionary; var configs = GetObject(d, "configurations") ?? new Dictionary(); var dependencies = GetObject(d, "dependencies") ?? new Dictionary(); var compilationOptions = GetObject(d, "compilationOptions") ?? new Dictionary(); if(configs.Count == 0) { // If the project doesn't specify any configurations generate both configs["k10"] = new Dictionary(); configs["net45"] = new Dictionary(); } // Get the list of files var filesString = String.Join(Environment.NewLine, Directory.GetFiles(projectDir, "*.cs", SearchOption.AllDirectories) .Select(p => p.Substring(projectDir.Length).Trim(Path.DirectorySeparatorChar)) .Where(p => !p.StartsWith("obj")) .Select(p => String.Format( @"", p))); var packageReferences = dependencies.Where(r => !projectMapping.ContainsKey(r.Key)) .ToDictionary(k => k.Key, k => (string)k.Value); var projectReferences = dependencies.Where(r => projectMapping.ContainsKey(r.Key)) .Select(r => r.Key) .ToList(); var defines = Get>(compilationOptions, "define") ?? new object[0]; object unsafeValue = Get(compilationOptions, "allowUnsafe"); bool allowUnsafeCode = unsafeValue == null ? false : (bool)unsafeValue; string extraProperties = allowUnsafeCode ? "\ntrue" : ""; // HACK: Assume the packages folder is 2 up from the projectDir string packagesDir = Path.GetFullPath(Path.Combine(projectDir, "..", "..", "packages")); foreach (var pair in configs) { var targetFramework = pair.Key; var props = (IDictionary)pair.Value; string id = (string)GetObject(projectMapping, projectName)[targetFramework]; var specificDependencies = GetObject(props, "dependencies") ?? new Dictionary(); var gacReferences = new List(); foreach(var dep in specificDependencies) { if (!projectMapping.ContainsKey(dep.Key)) { if(String.IsNullOrEmpty((string)dep.Value)) { gacReferences.Add(dep.Key); } } else { projectReferences.Add(dep.Key); } } var template = templates[targetFramework] .Replace("{ProjectGuid}", id) .Replace("{Name}", projectName) .Replace("{Defines}", String.Join(";", defines.Select(def => def.ToString()))) .Replace("{ExtraProperties}", extraProperties) .Replace("{Files}", filesString) .Replace("{ProjectReferences}", BuildProjectReferences(projectReferences, targetFramework, projectMapping)) .Replace("{References}", BuildReferences(packageReferences, gacReferences, packagesDir, targetFramework, GetCandidates(targetFramework))); if (targetFramework.StartsWith("k")) { template = template.Replace("{CSharpTargetsPath}", GetProjectKTargets(packagesDir)); } string output = Path.Combine(projectDir, GetProjectFileName(projectName, targetFramework)); Log("Generated {0}", output); File.WriteAllText(output, template); } } private static string GetProjectKTargets(string packagesDir) { var projectK = Directory.GetDirectories(packagesDir, "ProjectK*") .Select(p => new { Path = p, Build = Int32.Parse(p.Substring(p.LastIndexOf('-') + 1)) }) .OrderByDescending(p => p.Build) .FirstOrDefault(); if (projectK == null) { Warn("Project K targets aren't installed"); return ""; } return Path.Combine("..", "..", projectK.Path.Substring(projectK.Path.IndexOf("packages")), "Framework\\K\\v1.0\\ProjectK.CSharp.targets"); } private static string GetProjectFileName(string projectName, string config) { return projectName + "." + config + ".csproj"; } private static string BuildProjectReferences(IList projectReferences, string config, IDictionary projectMapping) { if (projectReferences.Count == 0) { return ""; } var sb = new StringBuilder(); foreach (var reference in projectReferences) { var info = GetObject(projectMapping, reference); if (info == null) { Warn("No project reference found for {0}", reference); continue; } string projectFileName = GetProjectFileName(reference, config); string path = Path.Combine((string)info["path"], projectFileName); sb.AppendFormat(@" {{{1}}} {2} ", path, info[config], reference); } return sb.ToString(); } private static string[] GetCandidates(string config) { if (config == "net45") { return new[] { "net45", "net40", "net35", "net20" }; } return new[] { config }; } private static string BuildReferences(IDictionary references, List gacReferences, string packagesDir, string configName, string[] candidates) { Log("Building package references for {0}", configName); var sb = new StringBuilder(); foreach(var gacRef in gacReferences) { sb.AppendFormat(@" ", gacRef); } foreach (var reference in references) { var version = (string)reference.Value; string pattern = version.IndexOf("*") != -1 ? reference.Key + "*" : reference.Key + "." + reference.Value; var packageDir = Directory.GetDirectories(packagesDir, pattern).FirstOrDefault(); if (packageDir == null) { Warn(reference.Key + " = " + version + " ==> UNRESOLVED"); continue; } Log(reference.Key + " = " + version + " ==> " + packageDir); var candidate = candidates.Select(c => Path.Combine(packageDir, "lib", c)) .FirstOrDefault(Directory.Exists); if (candidate == null) { Warn("Unable to find package reference for {0}, target framework = {1}", reference.Key, configName); continue; } var dlls = Directory.EnumerateFiles(candidate, "*.dll") .Distinct() .Where(File.Exists) .ToList(); foreach (var dllPath in dlls) { sb.AppendFormat(@" False ..\..\{1} ", AssemblyName.GetAssemblyName(dllPath).FullName, dllPath.Substring(dllPath.IndexOf("packages"))); } } return sb.ToString(); } private static T Get(IDictionary obj, string key) where T : class { object value; if (obj.TryGetValue(key, out value)) { return value as T; } return null; } private static IDictionary GetObject(IDictionary obj, string key) { return Get>(obj, key); } } }