Skip to content

Commit 9bebb98

Browse files
authored
Create PackageSpec type and helpers for project.json Migration (#6826)
1 parent fa9f2fd commit 9bebb98

7 files changed

Lines changed: 1144 additions & 4 deletions

File tree

src/NuGet.Clients/NuGet.PackageManagement.VisualStudio/GlobalSuppressions.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,3 +116,4 @@
116116
[assembly: SuppressMessage("Reliability", "CA2016:Forward the 'CancellationToken' parameter to methods", Justification = "<Pending>", Scope = "member", Target = "~M:NuGet.PackageManagement.VisualStudio.VsManagedLanguagesProjectSystemServices.GetPackageReferencesAsync(NuGet.Frameworks.NuGetFramework,System.Threading.CancellationToken)~System.Threading.Tasks.Task{System.Collections.Generic.IEnumerable{NuGet.LibraryModel.LibraryDependency}}")]
117117
[assembly: SuppressMessage("Reliability", "CA2016:Forward the 'CancellationToken' parameter to methods", Justification = "<Pending>", Scope = "member", Target = "~M:NuGet.PackageManagement.VisualStudio.VsManagedLanguagesProjectSystemServices.GetProjectReferencesAsync(NuGet.Common.ILogger,System.Threading.CancellationToken)~System.Threading.Tasks.Task{System.Collections.Generic.IEnumerable{NuGet.ProjectModel.ProjectRestoreReference}}")]
118118
[assembly: SuppressMessage("Reliability", "CA2016:Forward the 'CancellationToken' parameter to methods", Justification = "<Pending>", Scope = "member", Target = "~M:NuGet.PackageManagement.VisualStudio.VsMSBuildProjectSystem.SaveProjectAsync(System.Threading.CancellationToken)~System.Threading.Tasks.Task")]
119+
[assembly: SuppressMessage("Globalization", "CA1305:Specify IFormatProvider", Justification = "<Pending>", Scope = "member", Target = "~M:NuGet.PackageManagement.VisualStudio.Migrate.Utf8JsonReaderExtensions.ReadNumberAsString(System.Text.Json.Utf8JsonReader@)~System.String")]
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System.Collections.Generic;
5+
using NuGet.LibraryModel;
6+
using NuGet.ProjectModel;
7+
using NuGet.RuntimeModel;
8+
9+
namespace NuGet.PackageManagement.VisualStudio.Migrate
10+
{
11+
internal class PackageSpecProjectJsonMigrationCandidate
12+
{
13+
/// <summary>
14+
/// List of dependencies that apply to all frameworks.
15+
/// <see cref="ProjectStyle.PackageReference"/> based projects must not use this list and instead use the one in
16+
/// the <see cref="PackageSpec.TargetFrameworks"/> property which is a list of the <see cref="TargetFrameworkInformation"/> type.
17+
/// </summary>
18+
public IList<LibraryDependency> Dependencies { get; init; }
19+
public IList<TargetFrameworkInformation> TargetFrameworks { get; init; }
20+
public RuntimeGraph RuntimeGraph { get; init; }
21+
22+
public PackageSpecProjectJsonMigrationCandidate(
23+
IList<LibraryDependency> dependencies,
24+
IList<TargetFrameworkInformation> targetFrameworks,
25+
RuntimeGraph runtimeGraph)
26+
{
27+
Dependencies = dependencies;
28+
TargetFrameworks = targetFrameworks;
29+
RuntimeGraph = runtimeGraph;
30+
}
31+
}
32+
}

src/NuGet.Clients/NuGet.PackageManagement.VisualStudio/Migrate/ProjectJsonMigrationCandidatePackageSpecReader.cs

Lines changed: 590 additions & 0 deletions
Large diffs are not rendered by default.

src/NuGet.Clients/NuGet.PackageManagement.VisualStudio/Migrate/ProjectJsonToPackageRefMigrator.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@
1212
using Microsoft.VisualStudio.Threading;
1313
using NuGet.Common;
1414
using NuGet.LibraryModel;
15+
using NuGet.PackageManagement.VisualStudio.Migrate;
1516
using NuGet.ProjectManagement.Projects;
16-
using NuGet.ProjectModel;
1717
using NuGet.VisualStudio;
1818
using Task = System.Threading.Tasks.Task;
1919

@@ -35,7 +35,7 @@ public static async Task MigrateAsync(
3535
throw new FileNotFoundException(string.Format(CultureInfo.CurrentCulture, Strings.Error_FileNotExists, projectJsonFilePath));
3636
}
3737

38-
var packageSpec = JsonPackageSpecReader.GetPackageSpec(
38+
PackageSpecProjectJsonMigrationCandidate packageSpec = ProjectJsonMigrationCandidatePackageSpecReader.GetPackageSpec(
3939
Path.GetFileNameWithoutExtension(project.MSBuildProjectPath),
4040
projectJsonFilePath);
4141

@@ -57,7 +57,7 @@ await CreateBackupAsync(project,
5757
projectJsonFilePath);
5858
}
5959

60-
private static async Task MigrateDependenciesAsync(BuildIntegratedNuGetProject project, PackageSpec packageSpec)
60+
private static async Task MigrateDependenciesAsync(BuildIntegratedNuGetProject project, PackageSpecProjectJsonMigrationCandidate packageSpec)
6161
{
6262
if (packageSpec.TargetFrameworks.Count > 1)
6363
{
@@ -83,7 +83,7 @@ private static async Task MigrateDependenciesAsync(BuildIntegratedNuGetProject p
8383
}
8484
}
8585

86-
private static void MigrateRuntimes(PackageSpec packageSpec, Microsoft.Build.Evaluation.Project buildProject)
86+
private static void MigrateRuntimes(PackageSpecProjectJsonMigrationCandidate packageSpec, Microsoft.Build.Evaluation.Project buildProject)
8787
{
8888
ThreadHelper.ThrowIfNotOnUIThread();
8989
var runtimes = packageSpec.RuntimeGraph.Runtimes;
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System;
5+
using System.Text.Json;
6+
7+
namespace NuGet.PackageManagement.VisualStudio.Migrate
8+
{
9+
internal static class Utf8JsonReaderExtensions
10+
{
11+
internal static string ReadTokenAsString(this ref Utf8JsonReader reader)
12+
{
13+
switch (reader.TokenType)
14+
{
15+
case JsonTokenType.True:
16+
return bool.TrueString;
17+
case JsonTokenType.False:
18+
return bool.FalseString;
19+
case JsonTokenType.Number:
20+
return reader.ReadNumberAsString();
21+
case JsonTokenType.String:
22+
return reader.GetString();
23+
case JsonTokenType.None:
24+
case JsonTokenType.Null:
25+
return null;
26+
default:
27+
throw new InvalidCastException();
28+
}
29+
}
30+
31+
private static string ReadNumberAsString(this ref Utf8JsonReader reader)
32+
{
33+
if (reader.TryGetInt64(out long value))
34+
{
35+
return value.ToString();
36+
}
37+
return reader.GetDouble().ToString();
38+
}
39+
}
40+
}
Lines changed: 283 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,283 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System;
5+
using System.Buffers;
6+
using System.Collections.Generic;
7+
using System.IO;
8+
using System.Linq;
9+
using System.Text.Json;
10+
11+
namespace NuGet.PackageManagement.VisualStudio.Migrate
12+
{
13+
internal ref struct Utf8JsonStreamReader
14+
{
15+
private static readonly char[] DelimitedStringDelimiters = [' ', ','];
16+
private static readonly byte[] Utf8Bom = [0xEF, 0xBB, 0xBF];
17+
private static readonly JsonReaderOptions DefaultJsonReaderOptions = new JsonReaderOptions
18+
{
19+
AllowTrailingCommas = true,
20+
CommentHandling = JsonCommentHandling.Skip,
21+
};
22+
23+
private const int BufferSizeDefault = 16 * 1024;
24+
private const int MinBufferSize = 1024;
25+
private Utf8JsonReader _reader;
26+
#pragma warning disable CA2213 // Disposable fields should be disposed
27+
private Stream _stream;
28+
#pragma warning restore CA2213 // Disposable fields should be disposed
29+
// The buffer is used to read from the stream in chunks.
30+
private byte[] _buffer;
31+
private bool _disposed;
32+
private ArrayPool<byte> _bufferPool;
33+
private int _bufferUsed = 0;
34+
35+
internal bool ValueTextEquals(ReadOnlySpan<byte> utf8Text) => _reader.ValueTextEquals(utf8Text);
36+
internal bool GetBoolean() => _reader.GetBoolean();
37+
internal string GetString() => _reader.GetString();
38+
internal JsonTokenType TokenType => _reader.TokenType;
39+
40+
internal Utf8JsonStreamReader(Stream stream, int bufferSize = BufferSizeDefault, ArrayPool<byte> arrayPool = null)
41+
{
42+
if (stream is null)
43+
{
44+
throw new ArgumentNullException(nameof(stream));
45+
}
46+
47+
if (bufferSize < MinBufferSize)
48+
{
49+
throw new ArgumentException($"Buffer size must be at least {MinBufferSize} bytes", nameof(bufferSize));
50+
}
51+
52+
_bufferPool = arrayPool ?? ArrayPool<byte>.Shared;
53+
_buffer = _bufferPool.Rent(bufferSize);
54+
_disposed = false;
55+
_stream = stream;
56+
57+
if (_stream.Read(_buffer, offset: 0, count: 1) == 1 &&
58+
_stream.Read(_buffer, offset: ++_bufferUsed, count: 1) == 1 &&
59+
_stream.Read(_buffer, offset: ++_bufferUsed, count: 1) == 1)
60+
{
61+
++_bufferUsed;
62+
63+
bool hasUtf8Bom = Utf8Bom.AsSpan().SequenceEqual(_buffer.AsSpan(start: 0, length: 3));
64+
65+
if (hasUtf8Bom)
66+
{
67+
_bufferUsed = 0;
68+
}
69+
}
70+
71+
var initialJsonReaderState = new JsonReaderState(DefaultJsonReaderOptions);
72+
73+
ReadStreamIntoBuffer(initialJsonReaderState);
74+
_reader.Read();
75+
}
76+
77+
// This function is called when Read() returns false and we're not already in the final block
78+
private void GetMoreBytesFromStream()
79+
{
80+
if (_reader.BytesConsumed < _bufferUsed)
81+
{
82+
// If the number of bytes consumed by the reader is less than the amount set in the buffer then we have leftover bytes
83+
var oldBuffer = _buffer;
84+
ReadOnlySpan<byte> leftover = oldBuffer.AsSpan((int)_reader.BytesConsumed);
85+
_bufferUsed = leftover.Length;
86+
87+
// If the leftover bytes are the same as the buffer size then we are at capacity and need to double the buffer size
88+
if (leftover.Length == _buffer.Length)
89+
{
90+
_buffer = _bufferPool.Rent(_buffer.Length * 2);
91+
leftover.CopyTo(_buffer);
92+
_bufferPool.Return(oldBuffer, true);
93+
}
94+
else
95+
{
96+
leftover.CopyTo(_buffer);
97+
}
98+
}
99+
else
100+
{
101+
_bufferUsed = 0;
102+
}
103+
104+
ReadStreamIntoBuffer(_reader.CurrentState);
105+
}
106+
107+
/// <summary>
108+
/// Loops through the stream and reads it into the buffer until the buffer is full or the stream is empty, creates the Utf8JsonReader.
109+
/// </summary>
110+
private void ReadStreamIntoBuffer(JsonReaderState jsonReaderState)
111+
{
112+
int bytesRead;
113+
do
114+
{
115+
var spaceLeftInBuffer = _buffer.Length - _bufferUsed;
116+
bytesRead = _stream.Read(_buffer, _bufferUsed, spaceLeftInBuffer);
117+
_bufferUsed += bytesRead;
118+
}
119+
while (bytesRead != 0 && _bufferUsed != _buffer.Length);
120+
_reader = new Utf8JsonReader(_buffer.AsSpan(0, _bufferUsed), isFinalBlock: bytesRead == 0, jsonReaderState);
121+
}
122+
123+
internal string ReadNextTokenAsString()
124+
{
125+
ThrowExceptionIfDisposed();
126+
127+
if (Read())
128+
{
129+
return _reader.ReadTokenAsString();
130+
}
131+
132+
return null;
133+
}
134+
135+
internal IList<string> ReadStringArrayAsIList(IList<string> strings = null)
136+
{
137+
if (TokenType == JsonTokenType.StartArray)
138+
{
139+
while (Read() && TokenType != JsonTokenType.EndArray)
140+
{
141+
string value = _reader.ReadTokenAsString();
142+
143+
strings ??= new List<string>();
144+
145+
strings.Add(value);
146+
}
147+
}
148+
return strings;
149+
}
150+
151+
internal bool ReadNextTokenAsBoolOrFalse()
152+
{
153+
ThrowExceptionIfDisposed();
154+
155+
if (Read() && (TokenType == JsonTokenType.False || TokenType == JsonTokenType.True))
156+
{
157+
return GetBoolean();
158+
}
159+
return false;
160+
}
161+
162+
internal bool ReadNextTokenAsBoolOrThrowAnException(byte[] propertyName)
163+
{
164+
ThrowExceptionIfDisposed();
165+
166+
if (Read() && (TokenType == JsonTokenType.False || TokenType == JsonTokenType.True))
167+
{
168+
return GetBoolean();
169+
}
170+
else
171+
{
172+
throw new ArgumentException("Invalid attribute", nameof(propertyName));
173+
}
174+
}
175+
176+
internal IReadOnlyList<string> ReadStringArrayAsReadOnlyListFromArrayStart()
177+
{
178+
ThrowExceptionIfDisposed();
179+
180+
List<string> strings = null;
181+
182+
while (Read() && _reader.TokenType != JsonTokenType.EndArray)
183+
{
184+
string value = _reader.ReadTokenAsString();
185+
186+
strings ??= new List<string>();
187+
188+
strings.Add(value);
189+
}
190+
191+
return (IReadOnlyList<string>)strings ?? Array.Empty<string>();
192+
}
193+
194+
internal IReadOnlyList<string> ReadNextStringOrArrayOfStringsAsReadOnlyList()
195+
{
196+
ThrowExceptionIfDisposed();
197+
198+
if (Read())
199+
{
200+
switch (_reader.TokenType)
201+
{
202+
case JsonTokenType.String:
203+
return new[] { _reader.GetString() };
204+
205+
case JsonTokenType.StartArray:
206+
return ReadStringArrayAsReadOnlyListFromArrayStart();
207+
208+
case JsonTokenType.StartObject:
209+
return null;
210+
}
211+
}
212+
213+
return null;
214+
}
215+
216+
internal IReadOnlyList<string> ReadDelimitedString()
217+
{
218+
ThrowExceptionIfDisposed();
219+
220+
if (Read())
221+
{
222+
switch (TokenType)
223+
{
224+
case JsonTokenType.String:
225+
var value = GetString();
226+
227+
return value.Split(DelimitedStringDelimiters, StringSplitOptions.RemoveEmptyEntries);
228+
229+
default:
230+
throw new InvalidCastException();
231+
}
232+
}
233+
234+
return null;
235+
}
236+
237+
internal bool Read()
238+
{
239+
ThrowExceptionIfDisposed();
240+
241+
bool wasRead;
242+
while (!(wasRead = _reader.Read()) && !_reader.IsFinalBlock)
243+
{
244+
GetMoreBytesFromStream();
245+
}
246+
return wasRead;
247+
}
248+
249+
internal void Skip()
250+
{
251+
ThrowExceptionIfDisposed();
252+
253+
bool wasSkipped;
254+
while (!(wasSkipped = _reader.TrySkip()) && !_reader.IsFinalBlock)
255+
{
256+
GetMoreBytesFromStream();
257+
}
258+
if (!wasSkipped)
259+
{
260+
_reader.Skip();
261+
}
262+
}
263+
264+
public void Dispose()
265+
{
266+
if (!_disposed)
267+
{
268+
_disposed = true;
269+
byte[] toReturn = _buffer;
270+
_buffer = null!;
271+
_bufferPool.Return(toReturn, true);
272+
}
273+
}
274+
275+
private void ThrowExceptionIfDisposed()
276+
{
277+
if (_disposed)
278+
{
279+
throw new ObjectDisposedException(nameof(Utf8JsonStreamReader));
280+
}
281+
}
282+
}
283+
}

0 commit comments

Comments
 (0)