PageRenderTime 26ms CodeModel.GetById 27ms RepoModel.GetById 1ms app.codeStats 0ms

/src/Microsoft.Framework.PackageManager/Packing/PackProject.cs

https://github.com/Alxandr/KRuntime
C# | 510 lines | 382 code | 85 blank | 43 comment | 41 complexity | 819c335327f650e6a1bed13e3689dad6 MD5 | raw file
Possible License(s): Apache-2.0
  1. // Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
  2. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
  3. using System;
  4. using System.Collections.Generic;
  5. using System.IO;
  6. using System.IO.Compression;
  7. using System.Linq;
  8. using System.Security.Cryptography;
  9. using System.Xml;
  10. using System.Xml.Linq;
  11. using Microsoft.Framework.Runtime;
  12. using Newtonsoft.Json.Linq;
  13. using NuGet;
  14. namespace Microsoft.Framework.PackageManager.Packing
  15. {
  16. public class PackProject
  17. {
  18. private readonly ProjectReferenceDependencyProvider _projectReferenceDependencyProvider;
  19. private readonly IProjectResolver _projectResolver;
  20. private readonly LibraryDescription _libraryDescription;
  21. private string _applicationBase;
  22. public PackProject(
  23. ProjectReferenceDependencyProvider projectReferenceDependencyProvider,
  24. IProjectResolver projectResolver,
  25. LibraryDescription libraryDescription)
  26. {
  27. _projectReferenceDependencyProvider = projectReferenceDependencyProvider;
  28. _projectResolver = projectResolver;
  29. _libraryDescription = libraryDescription;
  30. }
  31. public string Name { get { return _libraryDescription.Identity.Name; } }
  32. public string TargetPath { get; private set; }
  33. public string WwwRoot { get; set; }
  34. public string WwwRootOut { get; set; }
  35. public void Emit(PackRoot root)
  36. {
  37. root.Reports.Quiet.WriteLine("Using {0} dependency {1} for {2}", _libraryDescription.Type,
  38. _libraryDescription.Identity, _libraryDescription.Framework.ToString().Yellow().Bold());
  39. if (root.NoSource)
  40. {
  41. EmitNupkg(root);
  42. }
  43. else
  44. {
  45. EmitSource(root);
  46. }
  47. root.Reports.Quiet.WriteLine();
  48. }
  49. private void EmitSource(PackRoot root)
  50. {
  51. root.Reports.Quiet.WriteLine(" Copying source code from {0} dependency {1}",
  52. _libraryDescription.Type, _libraryDescription.Identity.Name);
  53. Runtime.Project project;
  54. if (!_projectResolver.TryResolveProject(_libraryDescription.Identity.Name, out project))
  55. {
  56. throw new Exception("TODO: unable to resolve project named " + _libraryDescription.Identity.Name);
  57. }
  58. var targetName = project.Name;
  59. TargetPath = Path.Combine(root.OutputPath, PackRoot.AppRootName, "src", targetName);
  60. // If root.OutputPath is specified by --out option, it might not be a full path
  61. TargetPath = Path.GetFullPath(TargetPath);
  62. root.Reports.Quiet.WriteLine(" Source {0}", _libraryDescription.Path.Bold());
  63. root.Reports.Quiet.WriteLine(" Target {0}", TargetPath);
  64. root.Operations.Delete(TargetPath);
  65. CopyProject(root, project, TargetPath, includeSource: true);
  66. CopyRelativeSources(project);
  67. UpdateWebRoot(root, TargetPath);
  68. _applicationBase = Path.Combine("..", PackRoot.AppRootName, "src", project.Name);
  69. }
  70. private void EmitNupkg(PackRoot root)
  71. {
  72. root.Reports.Quiet.WriteLine(" Packing nupkg from {0} dependency {1}",
  73. _libraryDescription.Type, _libraryDescription.Identity.Name);
  74. Runtime.Project project;
  75. if (!_projectResolver.TryResolveProject(_libraryDescription.Identity.Name, out project))
  76. {
  77. throw new Exception("TODO: unable to resolve project named " + _libraryDescription.Identity.Name);
  78. }
  79. var resolver = new DefaultPackagePathResolver(root.TargetPackagesPath);
  80. var targetNupkg = resolver.GetPackageFileName(project.Name, project.Version);
  81. TargetPath = resolver.GetInstallPath(project.Name, project.Version);
  82. root.Reports.Quiet.WriteLine(" Source {0}", _libraryDescription.Path.Bold());
  83. root.Reports.Quiet.WriteLine(" Target {0}", TargetPath);
  84. if (Directory.Exists(TargetPath))
  85. {
  86. if (root.Overwrite)
  87. {
  88. root.Operations.Delete(TargetPath);
  89. }
  90. else
  91. {
  92. root.Reports.Quiet.WriteLine(" {0} already exists.", TargetPath);
  93. return;
  94. }
  95. }
  96. // Generate nupkg from this project dependency
  97. var buildOptions = new BuildOptions();
  98. buildOptions.ProjectDir = project.ProjectDirectory;
  99. buildOptions.OutputDir = Path.Combine(project.ProjectDirectory, "bin");
  100. buildOptions.Configurations.Add(root.Configuration);
  101. buildOptions.Reports = root.Reports;
  102. var buildManager = new BuildManager(root.HostServices, buildOptions);
  103. if (!buildManager.Build())
  104. {
  105. return;
  106. }
  107. // Extract the generated nupkg to target path
  108. var srcNupkgPath = Path.Combine(buildOptions.OutputDir, root.Configuration, targetNupkg);
  109. var targetNupkgPath = resolver.GetPackageFilePath(project.Name, project.Version);
  110. var hashFile = resolver.GetHashPath(project.Name, project.Version);
  111. using (var sourceStream = new FileStream(srcNupkgPath, FileMode.Open, FileAccess.Read))
  112. {
  113. using (var archive = new ZipArchive(sourceStream, ZipArchiveMode.Read))
  114. {
  115. root.Operations.ExtractNupkg(archive, TargetPath);
  116. }
  117. }
  118. using (var sourceStream = new FileStream(srcNupkgPath, FileMode.Open, FileAccess.Read))
  119. {
  120. using (var targetStream = new FileStream(targetNupkgPath, FileMode.Create, FileAccess.Write, FileShare.None))
  121. {
  122. sourceStream.CopyTo(targetStream);
  123. }
  124. sourceStream.Seek(0, SeekOrigin.Begin);
  125. var sha512Bytes = SHA512.Create().ComputeHash(sourceStream);
  126. File.WriteAllText(hashFile, Convert.ToBase64String(sha512Bytes));
  127. }
  128. // Copy content files (e.g. html, js and images) of main project into "root" folder of the exported package
  129. var rootFolderPath = Path.Combine(TargetPath, "root");
  130. var rootProjectJson = Path.Combine(rootFolderPath, Runtime.Project.ProjectFileName);
  131. CopyProject(root, project, rootFolderPath, includeSource: false);
  132. UpdateWebRoot(root, rootFolderPath);
  133. UpdateJson(rootProjectJson, jsonObj =>
  134. {
  135. // Update the project entrypoint
  136. jsonObj["entryPoint"] = _libraryDescription.Identity.Name;
  137. // Set mark this as non loadable
  138. jsonObj["loadable"] = false;
  139. // Update the dependencies node to reference the main project
  140. var deps = new JObject();
  141. jsonObj["dependencies"] = deps;
  142. deps[_libraryDescription.Identity.Name] = _libraryDescription.Identity.Version.ToString();
  143. });
  144. _applicationBase = Path.Combine("..", PackRoot.AppRootName, "packages", resolver.GetPackageDirectory(_libraryDescription.Identity.Name, _libraryDescription.Identity.Version), "root");
  145. }
  146. private void CopyRelativeSources(Runtime.Project project)
  147. {
  148. // We can reference source files outside of project root with "code" property in project.json,
  149. // e.g. { "code" : "..\\ExternalProject\\**.cs" }
  150. // So we find out external source files and copy them separately
  151. var rootDirectory = ProjectResolver.ResolveRootDirectory(project.ProjectDirectory);
  152. foreach (var sourceFile in project.SourceFiles)
  153. {
  154. // This source file is in project root directory. So it was already copied.
  155. if (PathUtility.IsChildOfDirectory(dir: project.ProjectDirectory, candidate: sourceFile))
  156. {
  157. continue;
  158. }
  159. // This source file is in solution root but out of project root,
  160. // it is an external source file that we should copy here
  161. if (PathUtility.IsChildOfDirectory(dir: rootDirectory, candidate: sourceFile))
  162. {
  163. // Keep the relativeness between external source files and project root,
  164. var relativeSourcePath = PathUtility.GetRelativePath(project.ProjectFilePath, sourceFile);
  165. var relativeParentDir = Path.GetDirectoryName(relativeSourcePath);
  166. Directory.CreateDirectory(Path.Combine(TargetPath, relativeParentDir));
  167. var targetFile = Path.Combine(TargetPath, relativeSourcePath);
  168. if (!File.Exists(targetFile))
  169. {
  170. File.Copy(sourceFile, targetFile);
  171. }
  172. }
  173. else
  174. {
  175. Console.WriteLine(
  176. string.Format("TODO: Warning: the referenced source file '{0}' is not in solution root and it is not packed to output.", sourceFile));
  177. }
  178. }
  179. }
  180. private void UpdateWebRoot(PackRoot root, string targetPath)
  181. {
  182. // Update the 'webroot' property, which was specified with '--wwwroot-out' option
  183. if (!string.IsNullOrEmpty(WwwRootOut))
  184. {
  185. var targetProjectJson = Path.Combine(targetPath, Runtime.Project.ProjectFileName);
  186. UpdateJson(targetProjectJson, jsonObj =>
  187. {
  188. var targetWebRootPath = Path.Combine(root.OutputPath, WwwRootOut);
  189. jsonObj["webroot"] = PathUtility.GetRelativePath(targetProjectJson, targetWebRootPath, separator: '/');
  190. });
  191. }
  192. }
  193. private static void UpdateJson(string jsonFile, Action<JObject> modifier)
  194. {
  195. var jsonObj = JObject.Parse(File.ReadAllText(jsonFile));
  196. modifier(jsonObj);
  197. File.WriteAllText(jsonFile, jsonObj.ToString());
  198. }
  199. private void CopyProject(PackRoot root, Runtime.Project project, string targetPath, bool includeSource)
  200. {
  201. // A set of excluded files/directories used as a filter when doing copy
  202. var excludeSet = new HashSet<string>(project.PackExcludeFiles, StringComparer.OrdinalIgnoreCase);
  203. var contentFiles = new HashSet<string>(project.ContentFiles, StringComparer.OrdinalIgnoreCase);
  204. // If a public folder is specified with 'webroot' or '--wwwroot', we ignore it when copying project files
  205. var wwwRootPath = string.Empty;
  206. if (!string.IsNullOrEmpty(WwwRoot))
  207. {
  208. wwwRootPath = Path.Combine(project.ProjectDirectory, WwwRoot);
  209. wwwRootPath = PathUtility.EnsureTrailingSlash(wwwRootPath);
  210. }
  211. // If project root is used as value of '--wwwroot', we shouldn't exclude it when copying
  212. if (string.Equals(wwwRootPath, PathUtility.EnsureTrailingSlash(project.ProjectDirectory)))
  213. {
  214. wwwRootPath = string.Empty;
  215. }
  216. root.Operations.Copy(project.ProjectDirectory, targetPath, itemPath =>
  217. {
  218. // If current file/folder is in the exclusion list, we don't copy it
  219. if (excludeSet.Contains(itemPath))
  220. {
  221. return false;
  222. }
  223. // If current file is in the public folder, we don't copy it to destination project
  224. if (!string.IsNullOrEmpty(wwwRootPath) && itemPath.StartsWith(wwwRootPath))
  225. {
  226. return false;
  227. }
  228. // The full path of target generated by copy operation should also be excluded
  229. var targetFullPath = itemPath.Replace(project.ProjectDirectory, TargetPath);
  230. excludeSet.Add(targetFullPath);
  231. if (includeSource)
  232. {
  233. return true;
  234. }
  235. if (Directory.Exists(itemPath))
  236. {
  237. return true;
  238. }
  239. return contentFiles.Contains(itemPath);
  240. });
  241. }
  242. public void PostProcess(PackRoot root)
  243. {
  244. // If --wwwroot-out doesn't have a non-empty value, we don't need a public app folder in output
  245. if (string.IsNullOrEmpty(WwwRootOut))
  246. {
  247. return;
  248. }
  249. Runtime.Project project;
  250. if (!_projectResolver.TryResolveProject(_libraryDescription.Identity.Name, out project))
  251. {
  252. throw new Exception("TODO: unable to resolve project named " + _libraryDescription.Identity.Name);
  253. }
  254. // Construct path to public app folder, which contains content files and tool dlls
  255. // The name of public app folder is specified with "--appfolder" option
  256. // Default name of public app folder is the same as main project
  257. var wwwRootOutPath = Path.Combine(root.OutputPath, WwwRootOut);
  258. // Delete old public app folder because we don't want leftovers from previous operations
  259. root.Operations.Delete(wwwRootOutPath);
  260. Directory.CreateDirectory(wwwRootOutPath);
  261. // Copy content files (e.g. html, js and images) of main project into public app folder
  262. CopyContentFiles(root, project, wwwRootOutPath);
  263. GenerateWebConfigFileForWwwRootOut(root, project, wwwRootOutPath);
  264. CopyAspNetLoaderDll(root, wwwRootOutPath);
  265. }
  266. private void GenerateWebConfigFileForWwwRootOut(PackRoot root, Runtime.Project project, string wwwRootOutPath)
  267. {
  268. // Generate web.config for public app folder
  269. var wwwRootOutWebConfigFilePath = Path.Combine(wwwRootOutPath, "web.config");
  270. var wwwRootSourcePath = GetWwwRootSourcePath(project.ProjectDirectory, WwwRoot);
  271. var webConfigFilePath = Path.Combine(wwwRootSourcePath, "web.config");
  272. XDocument xDoc;
  273. if (File.Exists(webConfigFilePath))
  274. {
  275. xDoc = XDocument.Parse(File.ReadAllText(webConfigFilePath));
  276. }
  277. else
  278. {
  279. xDoc = new XDocument();
  280. }
  281. if (xDoc.Root == null)
  282. {
  283. xDoc.Add(new XElement("configuration"));
  284. }
  285. if (xDoc.Root.Name != "configuration")
  286. {
  287. throw new InvalidDataException("'configuration' is the only valid name for root element of web.config file");
  288. }
  289. var appSettingsElement = GetOrAddElement(parent: xDoc.Root, name: "appSettings");
  290. var relativePackagesPath = PathUtility.GetRelativePath(wwwRootOutWebConfigFilePath, root.TargetPackagesPath);
  291. var defaultRuntime = root.Runtimes.FirstOrDefault();
  292. var keyValuePairs = new Dictionary<string, string>()
  293. {
  294. { "kpm-package-path", relativePackagesPath},
  295. { "bootstrapper-version", GetBootstrapperVersion(root)},
  296. { "kre-package-path", relativePackagesPath},
  297. { "kre-version", GetRuntimeVersion(defaultRuntime)},
  298. { "kre-clr", GetRuntimeFlavor(defaultRuntime)},
  299. { "kre-app-base", _applicationBase},
  300. };
  301. foreach (var pair in keyValuePairs)
  302. {
  303. var addElement = appSettingsElement.Elements()
  304. .Where(x => x.Name == "add" && x.Attribute("key").Value == pair.Key)
  305. .SingleOrDefault();
  306. if (addElement == null)
  307. {
  308. addElement = new XElement("add");
  309. addElement.SetAttributeValue("key", pair.Key);
  310. appSettingsElement.Add(addElement);
  311. }
  312. addElement.SetAttributeValue("value", pair.Value);
  313. }
  314. var xmlWriterSettings = new XmlWriterSettings
  315. {
  316. Indent = true,
  317. ConformanceLevel = ConformanceLevel.Auto
  318. };
  319. using (var xmlWriter = XmlWriter.Create(File.Create(wwwRootOutWebConfigFilePath), xmlWriterSettings))
  320. {
  321. xDoc.WriteTo(xmlWriter);
  322. }
  323. }
  324. private static XElement GetOrAddElement(XElement parent, string name)
  325. {
  326. var child = parent.Elements().Where(x => x.Name == name).FirstOrDefault();
  327. if (child == null)
  328. {
  329. child = new XElement(name);
  330. parent.Add(child);
  331. }
  332. return child;
  333. }
  334. private static string GetBootstrapperVersion(PackRoot root)
  335. {
  336. // Use version of Microsoft.AspNet.Loader.IIS.Interop as version of bootstrapper
  337. var package = root.Packages.SingleOrDefault(
  338. x => string.Equals(x.Library.Name, "Microsoft.AspNet.Loader.IIS.Interop"));
  339. return package == null ? string.Empty : package.Library.Version.ToString();
  340. }
  341. // Expected runtime name format: KRE-{FLAVOR}-{ARCHITECTURE}.{VERSION}
  342. // Sample input: KRE-CoreCLR-x86.1.0.0.0
  343. // Sample output: CoreCLR
  344. private static string GetRuntimeFlavor(PackRuntime runtime)
  345. {
  346. if (runtime == null)
  347. {
  348. return string.Empty;
  349. }
  350. var segments = runtime.Name.Split(new[] { '.' }, 2);
  351. segments = segments[0].Split(new[] { '-' }, 3);
  352. return segments[1];
  353. }
  354. // Expected runtime name format: KRE-{FLAVOR}-{ARCHITECTURE}.{VERSION}
  355. // Sample input: KRE-CoreCLR-x86.1.0.0.0
  356. // Sample output: 1.0.0.0
  357. private static string GetRuntimeVersion(PackRuntime runtime)
  358. {
  359. if (runtime == null)
  360. {
  361. return string.Empty;
  362. }
  363. var segments = runtime.Name.Split(new[] { '.' }, 2);
  364. return segments[1];
  365. }
  366. private static void CopyAspNetLoaderDll(PackRoot root, string wwwRootOutPath)
  367. {
  368. // Tool dlls including AspNet.Loader.dll go to bin folder under public app folder
  369. var wwwRootOutBinPath = Path.Combine(wwwRootOutPath, "bin");
  370. // Copy Microsoft.AspNet.Loader.IIS.Interop/tools/*.dll into bin to support AspNet.Loader.dll
  371. var package = root.Packages.SingleOrDefault(
  372. x => string.Equals(x.Library.Name, "Microsoft.AspNet.Loader.IIS.Interop"));
  373. if (package == null)
  374. {
  375. return;
  376. }
  377. var resolver = new DefaultPackagePathResolver(root.SourcePackagesPath);
  378. var packagePath = resolver.GetInstallPath(package.Library.Name, package.Library.Version);
  379. var packageToolsPath = Path.Combine(packagePath, "tools");
  380. if (Directory.Exists(packageToolsPath))
  381. {
  382. foreach (var packageToolFile in Directory.EnumerateFiles(packageToolsPath, "*.dll").Select(Path.GetFileName))
  383. {
  384. // Create the bin folder only when we need to put something inside it
  385. if (!Directory.Exists(wwwRootOutBinPath))
  386. {
  387. Directory.CreateDirectory(wwwRootOutBinPath);
  388. }
  389. // Copy to bin folder under public app folder
  390. File.Copy(
  391. Path.Combine(packageToolsPath, packageToolFile),
  392. Path.Combine(wwwRootOutBinPath, packageToolFile),
  393. overwrite: true);
  394. }
  395. }
  396. }
  397. private void CopyContentFiles(PackRoot root, Runtime.Project project, string targetFolderPath)
  398. {
  399. root.Reports.Quiet.WriteLine("Copying contents of {0} dependency {1} to {2}",
  400. _libraryDescription.Type, _libraryDescription.Identity.Name, targetFolderPath);
  401. var contentSourcePath = GetWwwRootSourcePath(project.ProjectDirectory, WwwRoot);
  402. root.Reports.Quiet.WriteLine(" Source {0}", contentSourcePath);
  403. root.Reports.Quiet.WriteLine(" Target {0}", targetFolderPath);
  404. root.Operations.Copy(contentSourcePath, targetFolderPath);
  405. }
  406. private static string GetWwwRootSourcePath(string projectDirectory, string wwwRoot)
  407. {
  408. wwwRoot = wwwRoot ?? string.Empty;
  409. var wwwRootSourcePath = Path.Combine(projectDirectory, wwwRoot);
  410. // If the value of '--wwwroot' is ".", we need to pack the project root dir
  411. // Use Path.GetFullPath() to get rid of the trailing "."
  412. return Path.GetFullPath(wwwRootSourcePath);
  413. }
  414. private bool IncludeRuntimeFileInBundle(string relativePath, string fileName)
  415. {
  416. return true;
  417. }
  418. private string BasePath(string relativePath)
  419. {
  420. var index1 = (relativePath + Path.DirectorySeparatorChar).IndexOf(Path.DirectorySeparatorChar);
  421. var index2 = (relativePath + Path.AltDirectorySeparatorChar).IndexOf(Path.AltDirectorySeparatorChar);
  422. return relativePath.Substring(0, Math.Min(index1, index2));
  423. }
  424. }
  425. }