// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using System.Collections.Generic; using System.IO; using System.Linq; using Microsoft.Extensions.ProjectModel.Utilities; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using NuGet.Frameworks; using NuGet.Packaging; using NuGet.Packaging.Core; using NuGet.Versioning; namespace Microsoft.Extensions.ProjectModel.Graph { public static class LockFileReader { public static LockFile Read(string path) { using(var fs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read)) { return Read(fs); } } public static LockFile Read(Stream stream) { using (var textReader = new StreamReader(stream)) { try { using (var jsonReader = new JsonTextReader(textReader)) { while (jsonReader.TokenType != JsonToken.StartObject) { if (!jsonReader.Read()) { throw new InvalidDataException(); } } var token = JToken.Load(jsonReader); return ReadLockFile(token as JObject); } } catch { // Ran into parsing errors, mark it as unlocked and out-of-date return new LockFile { Version = int.MinValue }; } } } public static void Write(Stream stream, LockFile lockFile) { using (var textWriter = new StreamWriter(stream)) { using (var jsonWriter = new JsonTextWriter(textWriter)) { jsonWriter.Formatting = Formatting.Indented; var json = WriteLockFile(lockFile); json.WriteTo(jsonWriter); } } } private static LockFile ReadLockFile(JObject cursor) { var lockFile = new LockFile(); lockFile.Version = ReadInt(cursor, "version", defaultValue: int.MinValue); lockFile.Targets = ReadObject(cursor["targets"] as JObject, ReadTarget); lockFile.ProjectFileDependencyGroups = ReadObject(cursor["projectFileDependencyGroups"] as JObject, ReadProjectFileDependencyGroup); ReadLibrary(cursor["libraries"] as JObject, lockFile); return lockFile; } private static JObject WriteLockFile(LockFile lockFile) { var json = new JObject(); json["locked"] = new JValue(false); json["version"] = new JValue(LockFile.CurrentVersion); json["targets"] = WriteObject(lockFile.Targets, WriteTarget); json["libraries"] = WriteLibraries(lockFile); json["projectFileDependencyGroups"] = WriteObject(lockFile.ProjectFileDependencyGroups, WriteProjectFileDependencyGroup); return json; } private static void ReadLibrary(JObject json, LockFile lockFile) { if (json == null) { return; } foreach (var property in json) { var value = property.Value as JObject; if (value == null) { continue; } var parts = property.Key.Split(new[] { '/' }, 2); var name = parts[0]; var version = parts.Length == 2 ? NuGetVersion.Parse(parts[1]) : null; var type = value["type"]?.Value(); if (type == null || type == "package") { lockFile.PackageLibraries.Add(new LockFilePackageLibrary { Name = name, Version = version, IsServiceable = ReadBool(value, "serviceable", defaultValue: false), Sha512 = ReadString(value["sha512"]), Files = ReadPathArray(value["files"] as JArray, ReadString) }); } else if (type == "project") { lockFile.ProjectLibraries.Add(new LockFileProjectLibrary { Name = name, Version = version, Path = ReadString(value["path"]) }); } } } private static JObject WriteLibraries(LockFile lockFile) { var result = new JObject(); foreach (var library in lockFile.ProjectLibraries) { var value = new JObject(); value["type"] = WriteString("project"); value["path"] = WriteString(library.Path); result[$"{library.Name}/{library.Version.ToString()}"] = value; } foreach (var library in lockFile.PackageLibraries) { var value = new JObject(); value["type"] = WriteString("package"); if (library.IsServiceable) { WriteBool(value, "serviceable", library.IsServiceable); } value["sha512"] = WriteString(library.Sha512); WritePathArray(value, "files", library.Files.OrderBy(f => f), WriteString); result[$"{library.Name}/{library.Version.ToString()}"] = value; } return result; } private static JProperty WriteTarget(LockFileTarget target) { var json = WriteObject(target.Libraries, WriteTargetLibrary); var key = target.TargetFramework + (target.RuntimeIdentifier == null ? "" : "/" + target.RuntimeIdentifier); return new JProperty(key, json); } private static LockFileTarget ReadTarget(string property, JToken json) { var target = new LockFileTarget(); var parts = property.Split(new[] { '/' }, 2); target.TargetFramework = NuGetFramework.Parse(parts[0]); if (parts.Length == 2) { target.RuntimeIdentifier = parts[1]; } target.Libraries = ReadObject(json as JObject, ReadTargetLibrary); return target; } private static LockFileTargetLibrary ReadTargetLibrary(string property, JToken json) { var library = new LockFileTargetLibrary(); var parts = property.Split(new[] { '/' }, 2); library.Name = parts[0]; if (parts.Length == 2) { library.Version = NuGetVersion.Parse(parts[1]); } var type = json["type"]; if (type != null) { library.Type = ReadString(type); } var framework = json["framework"]; if (framework != null) { library.TargetFramework = NuGetFramework.Parse(ReadString(framework)); } library.Dependencies = ReadObject(json["dependencies"] as JObject, ReadPackageDependency); library.FrameworkAssemblies = new HashSet(ReadArray(json["frameworkAssemblies"] as JArray, ReadFrameworkAssemblyReference), StringComparer.OrdinalIgnoreCase); library.RuntimeAssemblies = ReadObject(json["runtime"] as JObject, ReadFileItem); library.CompileTimeAssemblies = ReadObject(json["compile"] as JObject, ReadFileItem); library.ResourceAssemblies = ReadObject(json["resource"] as JObject, ReadFileItem); library.NativeLibraries = ReadObject(json["native"] as JObject, ReadFileItem); return library; } private static JProperty WriteTargetLibrary(LockFileTargetLibrary library) { var json = new JObject(); json["type"] = WriteString(library.Type); if (library.TargetFramework != null) { json["framework"] = WriteString(library.TargetFramework.ToString()); } if (library.Dependencies.Count > 0) { json["dependencies"] = WriteObject(library.Dependencies.OrderBy(p => p.Id), WritePackageDependency); } if (library.FrameworkAssemblies.Count > 0) { json["frameworkAssemblies"] = WriteArray(library.FrameworkAssemblies.OrderBy(f => f), WriteFrameworkAssemblyReference); } if (library.CompileTimeAssemblies.Count > 0) { json["compile"] = WriteObject(library.CompileTimeAssemblies, WriteFileItem); } if (library.RuntimeAssemblies.Count > 0) { json["runtime"] = WriteObject(library.RuntimeAssemblies, WriteFileItem); } if (library.ResourceAssemblies.Count > 0) { json["resource"] = WriteObject(library.ResourceAssemblies, WriteFileItem); } if (library.NativeLibraries.Count > 0) { json["native"] = WriteObject(library.NativeLibraries, WriteFileItem); } return new JProperty(library.Name + "/" + library.Version, json); } private static ProjectFileDependencyGroup ReadProjectFileDependencyGroup(string property, JToken json) { return new ProjectFileDependencyGroup( NuGetFramework.Parse(property), ReadArray(json as JArray, ReadString)); } private static JProperty WriteProjectFileDependencyGroup(ProjectFileDependencyGroup frameworkInfo) { return new JProperty( frameworkInfo.FrameworkName?.DotNetFrameworkName ?? string.Empty, WriteArray(frameworkInfo.Dependencies, WriteString)); } private static PackageDependencyGroup ReadPackageDependencySet(string property, JToken json) { var targetFramework = string.Equals(property, "*") ? null : NuGetFramework.Parse(property); return new PackageDependencyGroup( targetFramework, ReadObject(json as JObject, ReadPackageDependency)); } private static JProperty WritePackageDependencySet(PackageDependencyGroup item) { return new JProperty( item.TargetFramework?.ToString() ?? "*", WriteObject(item.Packages, WritePackageDependency)); } private static PackageDependency ReadPackageDependency(string property, JToken json) { var versionStr = json.Value(); return new PackageDependency( property, versionStr == null ? null : VersionRange.Parse(versionStr)); } private static JProperty WritePackageDependency(PackageDependency item) { return new JProperty( item.Id, WriteString(item.VersionRange?.ToString())); } private static LockFileItem ReadFileItem(string property, JToken json) { var item = new LockFileItem { Path = PathUtility.GetPathWithDirectorySeparator(property) }; foreach (var subProperty in json.OfType()) { item.Properties[subProperty.Name] = subProperty.Value.Value(); } return item; } private static JProperty WriteFileItem(LockFileItem item) { return new JProperty( item.Path, new JObject(item.Properties.Select(x => new JProperty(x.Key, x.Value)))); } private static string ReadFrameworkAssemblyReference(JToken json) { return json.Value(); } private static JToken WriteFrameworkAssemblyReference(string item) { return new JValue(item); } private static FrameworkSpecificGroup ReadPackageReferenceSet(JToken json) { var frameworkName = json["targetFramework"]?.ToString(); return new FrameworkSpecificGroup( string.IsNullOrEmpty(frameworkName) ? null : NuGetFramework.Parse(frameworkName), ReadArray(json["references"] as JArray, ReadString)); } private static JToken WritePackageReferenceSet(FrameworkSpecificGroup item) { var json = new JObject(); json["targetFramework"] = item.TargetFramework?.ToString(); json["references"] = WriteArray(item.Items, WriteString); return json; } private static IList ReadArray(JArray json, Func readItem) { if (json == null) { return new List(); } var items = new List(); foreach (var child in json) { items.Add(readItem(child)); } return items; } private static IList ReadPathArray(JArray json, Func readItem) { return ReadArray(json, readItem).Select(f => PathUtility.GetPathWithDirectorySeparator(f)).ToList(); } private static void WriteArray(JToken json, string property, IEnumerable items, Func writeItem) { if (items.Any()) { json[property] = WriteArray(items, writeItem); } } private static void WritePathArray(JToken json, string property, IEnumerable items, Func writeItem) { WriteArray(json, property, items.Select(f => PathUtility.GetPathWithForwardSlashes(f)), writeItem); } private static JArray WriteArray(IEnumerable items, Func writeItem) { var array = new JArray(); foreach (var item in items) { array.Add(writeItem(item)); } return array; } private static JArray WritePathArray(IEnumerable items, Func writeItem) { return WriteArray(items.Select(f => PathUtility.GetPathWithForwardSlashes(f)), writeItem); } 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, child.Value)); } return items; } private static void WriteObject(JToken json, string property, IEnumerable items, Func writeItem) { if (items.Any()) { json[property] = WriteObject(items, writeItem); } } private static JObject WriteObject(IEnumerable items, Func writeItem) { var array = new JObject(); foreach (var item in items) { array.Add(writeItem(item)); } return array; } private static bool ReadBool(JToken cursor, string property, bool defaultValue) { var valueToken = cursor[property]; if (valueToken == null) { return defaultValue; } return valueToken.Value(); } private static int ReadInt(JToken cursor, string property, int defaultValue) { var valueToken = cursor[property]; if (valueToken == null) { return defaultValue; } return valueToken.Value(); } private static string ReadString(JToken json) { return json.Value(); } private static NuGetVersion ReadSemanticVersion(JToken json, string property) { var valueToken = json[property]; if (valueToken == null) { throw new ArgumentException($"lock file missing required property '{property}'", nameof(property)); } return NuGetVersion.Parse(valueToken.Value()); } private static void WriteBool(JToken token, string property, bool value) { token[property] = new JValue(value); } private static JToken WriteString(string item) { return item != null ? new JValue(item) : JValue.CreateNull(); } private static NuGetFramework ReadFrameworkName(JToken json) { return json == null ? null : NuGetFramework.Parse(json.Value()); } private static JToken WriteFrameworkName(NuGetFramework item) { return item != null ? new JValue(item.DotNetFrameworkName) : JValue.CreateNull(); } } }