// Copyright (c) .NET Foundation and contributors. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System; using System.Collections.Generic; using System.IO; using System.Linq; using Microsoft.DotNet.ProjectModel.Utilities; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using NuGet.Frameworks; using NuGet.Packaging.Core; using NuGet.Versioning; namespace Microsoft.DotNet.ProjectModel.Graph { public class LockFileReader { private readonly LockFileSymbolTable _symbols; public LockFileReader() : this(new LockFileSymbolTable()) { } public LockFileReader(LockFileSymbolTable symbols) { _symbols = symbols; } public static LockFile Read(string lockFilePath, bool designTime) { using (var stream = ResilientFileStreamOpener.OpenFile(lockFilePath)) { try { return new LockFileReader().ReadLockFile(lockFilePath, stream, designTime); } catch (FileFormatException ex) { throw ex.WithFilePath(lockFilePath); } catch (Exception ex) { throw FileFormatException.Create(ex, lockFilePath); } } } public LockFile ReadLockFile(string lockFilePath, Stream stream, bool designTime) { try { var reader = new StreamReader(stream); var jobject = JObject.Load(new JsonTextReader(reader)); if (jobject == null) { throw new InvalidDataException(); } var lockFile = ReadLockFile(lockFilePath, jobject); if (!designTime) { var patcher = new LockFilePatcher(lockFile, this); patcher.Patch(); } return lockFile; } catch (LockFilePatchingException) { throw; } catch { // Ran into parsing errors, mark it as unlocked and out-of-date return new LockFile(lockFilePath) { Version = int.MinValue }; } } public ExportFile ReadExportFile(string fragmentLockFilePath) { using (var stream = ResilientFileStreamOpener.OpenFile(fragmentLockFilePath)) { try { var rootJObject = JObject.ReadFrom(new JsonTextReader(new StreamReader(stream))) as JObject; if (rootJObject == null) { throw new InvalidDataException(); } var version = ReadInt(rootJObject, "version", defaultValue: int.MinValue); var exports = ReadObject(rootJObject.Value("exports"), ReadTargetLibrary); return new ExportFile(fragmentLockFilePath, version, exports); } catch (FileFormatException ex) { throw ex.WithFilePath(fragmentLockFilePath); } catch (Exception ex) { throw FileFormatException.Create(ex, fragmentLockFilePath); } } } private LockFile ReadLockFile(string lockFilePath, JObject cursor) { var lockFile = new LockFile(lockFilePath); lockFile.Version = ReadInt(cursor, "version", defaultValue: int.MinValue); lockFile.Targets = ReadObject(cursor.Value("targets"), ReadTarget); lockFile.ProjectFileDependencyGroups = ReadObject(cursor.Value("projectFileDependencyGroups"), ReadProjectFileDependencyGroup); ReadLibrary(cursor.Value("libraries"), lockFile); return lockFile; } private void ReadLibrary(JObject json, LockFile lockFile) { if (json == null) { return; } foreach (var child in json) { var key = child.Key; var value = json.Value(key); if (value == null) { throw FileFormatException.Create("The value type is not object.", json[key]); } var parts = key.Split(new[] { '/' }, 2); var name = parts[0]; var version = parts.Length == 2 ? _symbols.GetVersion(parts[1]) : null; var type = _symbols.GetString(value.Value("type")); if (type == null || string.Equals(type, "package", StringComparison.OrdinalIgnoreCase)) { lockFile.PackageLibraries.Add(new LockFilePackageLibrary { Name = name, Version = version, IsServiceable = ReadBool(value, "serviceable", defaultValue: false), Sha512 = ReadString(value["sha512"]), Files = ReadPathArray(value["files"], ReadString) }); } else if (type == "project") { var projectLibrary = new LockFileProjectLibrary { Name = name, Version = version }; var pathValue = value["path"]; projectLibrary.Path = pathValue == null ? null : ReadString(pathValue); var buildTimeDependencyValue = value["msbuildProject"]; projectLibrary.MSBuildProject = buildTimeDependencyValue == null ? null : ReadString(buildTimeDependencyValue); lockFile.ProjectLibraries.Add(projectLibrary); } } } private LockFileTarget ReadTarget(string property, JToken json) { var jobject = json as JObject; if (jobject == null) { throw FileFormatException.Create("The value type is not an object.", json); } var target = new LockFileTarget(); var parts = property.Split(new[] { '/' }, 2); target.TargetFramework = _symbols.GetFramework(parts[0]); if (parts.Length == 2) { target.RuntimeIdentifier = _symbols.GetString(parts[1]); } target.Libraries = ReadObject(jobject, ReadTargetLibrary); return target; } private LockFileTargetLibrary ReadTargetLibrary(string property, JToken json) { var jobject = json as JObject; if (jobject == null) { throw FileFormatException.Create("The value type is not an object.", json); } var library = new LockFileTargetLibrary(); var parts = property.Split(new[] { '/' }, 2); library.Name = _symbols.GetString(parts[0]); if (parts.Length == 2) { library.Version = _symbols.GetVersion(parts[1]); } library.Type = _symbols.GetString(jobject.Value("type")); var framework = jobject.Value("framework"); if (framework != null) { library.TargetFramework = _symbols.GetFramework(framework); } library.Dependencies = ReadObject(jobject.Value("dependencies"), ReadPackageDependency); library.FrameworkAssemblies = new HashSet(ReadArray(jobject["frameworkAssemblies"], ReadFrameworkAssemblyReference), StringComparer.OrdinalIgnoreCase); library.RuntimeAssemblies = ReadObject(jobject.Value("runtime"), ReadFileItem); library.CompileTimeAssemblies = ReadObject(jobject.Value("compile"), ReadFileItem); library.ResourceAssemblies = ReadObject(jobject.Value("resource"), ReadFileItem); library.NativeLibraries = ReadObject(jobject.Value("native"), ReadFileItem); library.ContentFiles = ReadObject(jobject.Value("contentFiles"), ReadContentFile); library.RuntimeTargets = ReadObject(jobject.Value("runtimeTargets"), ReadRuntimeTarget); return library; } private LockFileRuntimeTarget ReadRuntimeTarget(string property, JToken json) { var jsonObject = json as JObject; if (jsonObject == null) { throw FileFormatException.Create("The value type is not an object.", json); } return new LockFileRuntimeTarget( path: _symbols.GetString(property), runtime: _symbols.GetString(jsonObject.Value("rid")), assetType: _symbols.GetString(jsonObject.Value("assetType")) ); } private LockFileContentFile ReadContentFile(string property, JToken json) { var contentFile = new LockFileContentFile() { Path = property }; var jsonObject = json as JObject; if (jsonObject != null) { BuildAction action; BuildAction.TryParse(jsonObject.Value("buildAction"), out action); contentFile.BuildAction = action; var codeLanguage = _symbols.GetString(jsonObject.Value("codeLanguage")); if (codeLanguage == "any") { codeLanguage = null; } contentFile.CodeLanguage = codeLanguage; contentFile.OutputPath = jsonObject.Value("outputPath"); contentFile.PPOutputPath = jsonObject.Value("ppOutputPath"); contentFile.CopyToOutput = ReadBool(jsonObject, "copyToOutput", false); } return contentFile; } private ProjectFileDependencyGroup ReadProjectFileDependencyGroup(string property, JToken json) { return new ProjectFileDependencyGroup( string.IsNullOrEmpty(property) ? null : NuGetFramework.Parse(property), ReadArray(json, ReadString)); } private PackageDependency ReadPackageDependency(string property, JToken json) { var versionStr = ReadString(json); return new PackageDependency( _symbols.GetString(property), versionStr == null ? null : _symbols.GetVersionRange(versionStr)); } private LockFileItem ReadFileItem(string property, JToken json) { var item = new LockFileItem { Path = _symbols.GetString(PathUtility.GetPathWithDirectorySeparator(property)) }; var jobject = json as JObject; if (jobject != null) { foreach (var child in jobject) { item.Properties[_symbols.GetString(child.Key)] = jobject.Value(child.Key); } } return item; } private string ReadFrameworkAssemblyReference(JToken json) { return ReadString(json); } private static IList ReadArray(JToken json, Func readItem) { if (json == null) { return new List(); } var jarray = json as JArray; if (jarray == null) { throw FileFormatException.Create("The value type is not array.", json); } var items = new List(); for (int i = 0; i < jarray.Count; ++i) { items.Add(readItem(jarray[i])); } return items; } private IList ReadPathArray(JToken json, Func readItem) { return ReadArray(json, readItem).Select(f => _symbols.GetString(PathUtility.GetPathWithDirectorySeparator(f))).ToList(); } private static IList ReadObject(JObject json, Func readItem) { if (json == null) { return new List(); } var items = new List(); foreach (var child in json) { items.Add(readItem(child.Key, json[child.Key])); } return items; } private static bool ReadBool(JObject cursor, string property, bool defaultValue) { var valueToken = cursor[property] as JValue; if (valueToken == null || valueToken.Type != JTokenType.Boolean) { return defaultValue; } return (bool)valueToken.Value; } private static int ReadInt(JObject cursor, string property, int defaultValue) { var number = cursor[property] as JValue; if (number == null || number.Type != JTokenType.Integer) { return defaultValue; } try { var resultInInt = Convert.ToInt32(number.Value); return resultInInt; } catch (Exception ex) { // FormatException or OverflowException throw FileFormatException.Create(ex, cursor); } } private string ReadString(JToken json) { if (json.Type == JTokenType.String) { return _symbols.GetString(json.ToString()); } else if (json.Type == JTokenType.Null) { return null; } else { throw FileFormatException.Create("The value type is not string.", json); } } } }