505 lines
21 KiB
Plaintext
505 lines
21 KiB
Plaintext
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<string, string> {
|
||
{ "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<string, string> 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<string, string[]> 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<string, object> GetProjectMapping(string solutionPath, string[] jsonFiles)
|
||
{
|
||
var solutionProjects = new Dictionary<string, string>();
|
||
|
||
foreach (var solutionFile in Directory.GetFiles(solutionPath, "*.sln"))
|
||
{
|
||
foreach (var line in File.ReadAllLines(solutionFile))
|
||
{
|
||
if (!line.StartsWith("Project", StringComparison.OrdinalIgnoreCase))
|
||
{
|
||
continue;
|
||
}
|
||
|
||
var eq = line.IndexOf('=');
|
||
|
||
if (eq == -1)
|
||
{
|
||
continue;
|
||
}
|
||
|
||
var parts = line.Substring(eq + 1).Trim().Split((char)',')
|
||
.Select(p => p.Trim().Trim((char)'"'))
|
||
.ToList();
|
||
|
||
if (parts.Count != 3)
|
||
{
|
||
continue;
|
||
}
|
||
|
||
string name = parts[0];
|
||
string path = Path.Combine(solutionPath, parts[1]);
|
||
string guid = parts[2].Trim((char)'{', (char)'}');
|
||
|
||
solutionProjects[path] = guid;
|
||
}
|
||
}
|
||
|
||
var dict = new Dictionary<string, object>();
|
||
|
||
foreach (var path in jsonFiles)
|
||
{
|
||
string projectDir = Path.GetDirectoryName(path);
|
||
string projectName = projectDir.Substring(Path.GetDirectoryName(projectDir).Length).Trim(Path.DirectorySeparatorChar);
|
||
|
||
string net45Project = Path.Combine(projectDir, GetProjectFileName(projectName, "net45"));
|
||
string k10Project = Path.Combine(projectDir, GetProjectFileName(projectName, "k10"));
|
||
|
||
var configs = new Dictionary<string, object>();
|
||
configs["net45"] = solutionProjects.ContainsKey(net45Project) ?
|
||
solutionProjects[net45Project] :
|
||
GetProjectGuidFromFileOrCreateNew(net45Project);
|
||
|
||
configs["k10"] = solutionProjects.ContainsKey(k10Project) ?
|
||
solutionProjects[k10Project] :
|
||
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<string, object> projectMapping,
|
||
IDictionary<string, string> 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<string, object>;
|
||
var configs = GetObject(d, "configurations") ?? new Dictionary<string, object>();
|
||
var dependencies = GetObject(d, "dependencies") ?? new Dictionary<string, object>();
|
||
var compilationOptions = GetObject(d, "compilationOptions") ?? new Dictionary<string, object>();
|
||
|
||
if(configs.Count == 0)
|
||
{
|
||
// If the project doesn't specify any configurations generate both
|
||
configs["k10"] = new Dictionary<string, object>();
|
||
configs["net45"] = new Dictionary<string, object>();
|
||
}
|
||
|
||
// Build the list of cs files
|
||
var csFiles
|
||
= String.Join(Environment.NewLine,
|
||
FindFilesOfType(projectDir, "*.cs")
|
||
.Select(p => String.Format(@"<Compile Include=""{0}"" />", p)));
|
||
|
||
// Build the list of resx files
|
||
var resxFiles
|
||
= String.Join(Environment.NewLine,
|
||
FindFilesOfType(projectDir, "*.resx")
|
||
.Select(p => String.Format(@"<EmbeddedResource Include=""{0}"">
|
||
<LogicalName>{1}.{2}.resources</LogicalName>
|
||
</EmbeddedResource>", p, projectName, Path.GetFileNameWithoutExtension(p))));
|
||
|
||
bool isSample = Path.GetDirectoryName(projectDir)
|
||
.TrimEnd(Path.DirectorySeparatorChar)
|
||
.EndsWith("samples");
|
||
|
||
Log("Processing sample project '{0}'", projectName);
|
||
|
||
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 sharedDefines = Get<IEnumerable<object>>(compilationOptions, "define") ?? new object[0];
|
||
object unsafeValue = Get<object>(compilationOptions, "allowUnsafe");
|
||
bool sharedAllowUnsafeCode = unsafeValue == null ? false : (bool)unsafeValue;
|
||
|
||
// 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 allPackageReferences = new Dictionary<string, string>(packageReferences);
|
||
|
||
var targetFramework = pair.Key;
|
||
var props = (IDictionary<string, object>)pair.Value;
|
||
|
||
var specificCompilationOptions = GetObject(props, "compilationOptions") ?? compilationOptions ?? new Dictionary<string, object>();
|
||
|
||
var specificDefines = Get<IEnumerable<object>>(specificCompilationOptions, "define") ?? new object[0];
|
||
object specificUnsafeValue = Get<object>(specificCompilationOptions, "allowUnsafe");
|
||
bool allowUnsafeCode = unsafeValue == null ? sharedAllowUnsafeCode : (bool)specificUnsafeValue;
|
||
|
||
string extraProperties = (allowUnsafeCode ? "\n<AllowUnsafeBlocks>true</AllowUnsafeBlocks>" : "");
|
||
if (isSample)
|
||
{
|
||
extraProperties += GenerateStartupAction(projectDir, projectName, packagesDir, pair.Key);
|
||
}
|
||
|
||
string id = (string)GetObject(projectMapping, projectName)[targetFramework];
|
||
|
||
var defines = new HashSet<string>(specificDefines.Select(sd => sd.ToString()));
|
||
foreach(var def in sharedDefines)
|
||
{
|
||
defines.Add(def.ToString());
|
||
}
|
||
|
||
var specificDependencies = GetObject(props, "dependencies") ?? new Dictionary<string, object>();
|
||
var gacReferences = new List<string>();
|
||
|
||
foreach(var dep in specificDependencies)
|
||
{
|
||
if (!projectMapping.ContainsKey(dep.Key))
|
||
{
|
||
var version = (string)dep.Value;
|
||
|
||
if(String.IsNullOrEmpty(version))
|
||
{
|
||
gacReferences.Add(dep.Key);
|
||
}
|
||
else
|
||
{
|
||
allPackageReferences[dep.Key] = version;
|
||
}
|
||
}
|
||
else
|
||
{
|
||
projectReferences.Add(dep.Key);
|
||
}
|
||
}
|
||
|
||
var template = templates[targetFramework]
|
||
.Replace("{ProjectGuid}", id)
|
||
.Replace("{Name}", projectName)
|
||
.Replace("{Defines}", String.Join(";", defines))
|
||
.Replace("{ExtraProperties}", extraProperties)
|
||
.Replace("{Files}", String.Join(Environment.NewLine, csFiles, resxFiles))
|
||
.Replace("{ProjectReferences}", BuildProjectReferences(projectReferences, targetFramework, projectMapping))
|
||
.Replace("{References}", BuildReferences(allPackageReferences, gacReferences, packagesDir, targetFramework, GetCandidates(targetFramework)));
|
||
|
||
if (targetFramework.StartsWith("k"))
|
||
{
|
||
template = template.Replace("{CSharpTargetsPath}", GetProjectKTargets(packagesDir));
|
||
}
|
||
|
||
string output = Path.Combine(projectDir, GetProjectFileName(projectName, targetFramework));
|
||
string current = "";
|
||
|
||
try
|
||
{
|
||
current = File.Exists(output) ? File.ReadAllText(output) : "";
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Warn(ex.Message);
|
||
}
|
||
|
||
if (current != template)
|
||
{
|
||
File.WriteAllText(output, template);
|
||
|
||
Log("Generated {0}", output);
|
||
}
|
||
else
|
||
{
|
||
Log("No changes required for {0}", output);
|
||
}
|
||
}
|
||
}
|
||
|
||
private static IEnumerable<string> FindFilesOfType(string projectDir, string pattern)
|
||
{
|
||
return Directory.GetFiles(projectDir, pattern, SearchOption.AllDirectories)
|
||
.Select(p => p.Substring(projectDir.Length).Trim(Path.DirectorySeparatorChar))
|
||
.Where(p => !p.StartsWith(@"obj\"));
|
||
}
|
||
|
||
private static string GenerateStartupAction(string projectRoot, string projectName, string packagesDir, string configType)
|
||
{
|
||
// packages\ProjectK.*\tools\bin\klr.exe<78>
|
||
// packages\ProjectK.*\tools\Microsoft.Net.ApplicationHost\bin\net45\Microsoft.Net.ApplicationHost.dll<6C>
|
||
|
||
string klrPath = Directory.EnumerateFiles(packagesDir, "klr.exe", SearchOption.AllDirectories).FirstOrDefault();
|
||
if (klrPath == null)
|
||
{
|
||
Warn("Unable to locate klr.exe under packages");
|
||
return "";
|
||
}
|
||
|
||
string toolsDir = Path.GetDirectoryName(Path.GetDirectoryName(klrPath));
|
||
string outputDll = Path.Combine(projectRoot, "bin", "Debug", configType == "k10" ? "k" : "net45", projectName + ".dll");
|
||
|
||
return String.Format(@"<StartAction>Program</StartAction>
|
||
<StartProgram>{0}</StartProgram>
|
||
<StartArguments>{1}</StartArguments>", klrPath, outputDll);
|
||
}
|
||
|
||
private static string GetProjectKTargets(string packagesDir)
|
||
{
|
||
Func<string, long> getVersion = version => {
|
||
var dash = version.LastIndexOf('-');
|
||
|
||
if(dash != -1)
|
||
{
|
||
var lastToken = version.Substring(dash + 1);
|
||
|
||
if(lastToken.StartsWith("t"))
|
||
{
|
||
return Int64.Parse(lastToken.Substring(1));
|
||
}
|
||
|
||
return Int64.Parse(lastToken);
|
||
}
|
||
return Int64.MaxValue;
|
||
};
|
||
|
||
var projectK = Directory.GetDirectories(packagesDir, "ProjectK*")
|
||
.Select(p => new { Path = p, Build = getVersion(p) })
|
||
.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<string> projectReferences, string config, IDictionary<string, object> 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(@"<ProjectReference Include=""..\..\{0}"">
|
||
<Project>{{{1}}}</Project>
|
||
<Name>{2}</Name>
|
||
</ProjectReference>", path, info[config], reference);
|
||
}
|
||
|
||
return sb.ToString();
|
||
}
|
||
|
||
private static string[] GetCandidates(string config)
|
||
{
|
||
if (config == "net45")
|
||
{
|
||
return new[] { "net45", "net40", "net35", "net20" };
|
||
}
|
||
// If it's k10 then look for netcore45 as well
|
||
return new[] { config, "netcore45" };
|
||
}
|
||
|
||
private static string BuildReferences(IDictionary<string, string> references, List<string> 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(@"
|
||
<Reference Include=""{0}""></Reference>
|
||
", gacRef);
|
||
}
|
||
|
||
foreach (var reference in references)
|
||
{
|
||
var version = (string)reference.Value;
|
||
var pattern = reference.Key + "." + version;
|
||
|
||
var packageDir = Directory.GetDirectories(packagesDir, pattern).OrderByDescending(p => p).FirstOrDefault();
|
||
|
||
if (packageDir == null)
|
||
{
|
||
Warn(reference.Key + " = " + version + " ==> UNRESOLVED");
|
||
continue;
|
||
}
|
||
|
||
Log(reference.Key + " = " + version + " ==> " + packageDir);
|
||
|
||
var libPath = Path.Combine(packageDir, "lib");
|
||
|
||
var candidate
|
||
= candidates.Select(c => Path.Combine(libPath, c))
|
||
.FirstOrDefault(Directory.Exists)
|
||
?? candidates
|
||
.SelectMany(c => Directory.GetDirectories(libPath, "*" + c + "*"))
|
||
.FirstOrDefault();
|
||
|
||
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(@"
|
||
<Reference Include=""{0}"">
|
||
<SpecificVersion>False</SpecificVersion>
|
||
<HintPath>..\..\{1}</HintPath>
|
||
</Reference>", AssemblyName.GetAssemblyName(dllPath).FullName, dllPath.Substring(dllPath.IndexOf("packages")));
|
||
}
|
||
}
|
||
|
||
return sb.ToString();
|
||
}
|
||
|
||
private static T Get<T>(IDictionary<string, object> obj, string key) where T : class
|
||
{
|
||
object value;
|
||
if (obj.TryGetValue(key, out value))
|
||
{
|
||
return value as T;
|
||
}
|
||
|
||
return null;
|
||
}
|
||
|
||
private static IDictionary<string, object> GetObject(IDictionary<string, object> obj, string key)
|
||
{
|
||
return Get<IDictionary<string, object>>(obj, key);
|
||
}
|
||
}
|
||
}
|