1
1
using System ;
2
2
using System . Collections . Generic ;
3
3
using System . IO ;
4
- using System . IO . Compression ;
5
4
using System . Linq ;
6
5
using System . Threading ;
7
6
using System . Threading . Tasks ;
8
7
using NuGet . Common ;
9
- using NuGet . Packaging ;
10
- using NuGet . Packaging . Core ;
11
8
12
9
namespace Sleet
13
10
{
14
11
public static class PushCommand
15
12
{
13
+ public const int DefaultBatchSize = 4096 ;
14
+
16
15
public static async Task < bool > RunAsync ( LocalSettings settings , ISleetFileSystem source , List < string > inputs , bool force , bool skipExisting , ILogger log )
17
16
{
18
17
var token = CancellationToken . None ;
19
18
var now = DateTimeOffset . UtcNow ;
20
- var packages = new List < PackageInput > ( ) ;
19
+ var success = false ;
20
+ var perfTracker = source . LocalCache . PerfTracker ;
21
21
22
22
await log . LogAsync ( LogLevel . Minimal , $ "Reading feed { source . BaseURI . AbsoluteUri } ") ;
23
23
24
- // Read packages before locking the feed.
25
- packages . AddRange ( await GetPackageInputs ( inputs , now , log ) ) ;
26
-
27
- // Check if already initialized
28
- using ( var feedLock = await SourceUtility . VerifyInitAndLock ( settings , source , log , token ) )
24
+ using ( var timer = PerfEntryWrapper . CreateSummaryTimer ( "Total execution time: {0}" , perfTracker ) )
29
25
{
30
- // Validate source
31
- await SourceUtility . ValidateFeedForClient ( source , log , token ) ;
26
+ // Partition package inputs to avoid reading 100K nuspecs at the same time.
27
+ var packagePaths = GetPackagePaths ( inputs ) ;
28
+ var inputBatches = packagePaths . Partition ( DefaultBatchSize ) ;
29
+ ISleetFileSystemLock feedLock = null ;
32
30
33
- // Push
34
- return await PushPackages ( settings , source , packages , force , skipExisting , log , token ) ;
31
+ try
32
+ {
33
+ for ( var i = 0 ; i < inputBatches . Count ; i ++ )
34
+ {
35
+ var inputBatch = inputBatches [ i ] ;
36
+ if ( inputBatches . Count > 1 )
37
+ {
38
+ await log . LogAsync ( LogLevel . Minimal , $ "Pushing { inputBatch . Count } packages. Batch: { i + 1 } / { inputBatches . Count } ") ;
39
+ }
40
+
41
+ // Read packages before locking the feed the first time.
42
+ var packages = new List < PackageInput > ( await GetPackageInputs ( inputBatch , now , perfTracker , log ) ) ;
43
+
44
+ if ( feedLock == null )
45
+ {
46
+ // Check if already initialized
47
+ feedLock = await SourceUtility . VerifyInitAndLock ( settings , source , log , token ) ;
48
+
49
+ // Validate source
50
+ await SourceUtility . ValidateFeedForClient ( source , log , token ) ;
51
+ }
52
+
53
+ // Push
54
+ success = await PushPackages ( settings , source , packages , force , skipExisting , log , token ) ;
55
+ }
56
+ }
57
+ finally
58
+ {
59
+ // Unlock the feed
60
+ feedLock ? . Dispose ( ) ;
61
+ }
35
62
}
63
+
64
+ // Write out perf summary
65
+ await perfTracker . LogSummary ( log ) ;
66
+
67
+ return success ;
36
68
}
37
69
38
70
/// <summary>
@@ -42,13 +74,23 @@ public static async Task<bool> RunAsync(LocalSettings settings, ISleetFileSystem
42
74
public static async Task < bool > PushPackages ( LocalSettings settings , ISleetFileSystem source , List < string > inputs , bool force , bool skipExisting , ILogger log , CancellationToken token )
43
75
{
44
76
var now = DateTimeOffset . UtcNow ;
45
- var packages = new List < PackageInput > ( ) ;
77
+ var success = true ;
46
78
47
- // Get packages
48
- packages . AddRange ( await GetPackageInputs ( inputs , now , log ) ) ;
79
+ var packagePaths = GetPackagePaths ( inputs ) ;
49
80
50
- // Add packages
51
- return await PushPackages ( settings , source , packages , force , skipExisting , log , token ) ;
81
+ // Partition input files to avoid reading 100K nuspec files at once.
82
+ foreach ( var inputSegment in packagePaths . Partition ( DefaultBatchSize ) )
83
+ {
84
+ var packages = new List < PackageInput > ( ) ;
85
+
86
+ // Get packages
87
+ packages . AddRange ( await GetPackageInputs ( inputSegment , now , source . LocalCache . PerfTracker , log ) ) ;
88
+
89
+ // Add packages
90
+ success &= await PushPackages ( settings , source , packages , force , skipExisting , log , token ) ;
91
+ }
92
+
93
+ return success ;
52
94
}
53
95
54
96
/// <summary>
@@ -74,10 +116,11 @@ public static async Task<bool> PushPackages(LocalSettings settings, ISleetFileSy
74
116
SourceSettings = sourceSettings ,
75
117
Log = log ,
76
118
Source = source ,
77
- Token = token
119
+ Token = token ,
120
+ PerfTracker = source . LocalCache . PerfTracker
78
121
} ;
79
122
80
- await log . LogAsync ( LogLevel . Information , "Reading existing package index" ) ;
123
+ await log . LogAsync ( LogLevel . Verbose , "Reading existing package index" ) ;
81
124
82
125
var packageIndex = new PackageIndex ( context ) ;
83
126
await PushPackages ( packages , context , packageIndex , force , skipExisting , log ) ;
@@ -122,9 +165,6 @@ private static async Task PushPackages(List<PackageInput> packageInputs, SleetCo
122
165
}
123
166
}
124
167
125
- await log . LogAsync ( LogLevel . Minimal , $ "Pushing { packageString } ") ;
126
- await log . LogAsync ( LogLevel . Information , $ "Checking if package exists.") ;
127
-
128
168
var exists = false ;
129
169
130
170
if ( package . IsSymbolsPackage )
@@ -140,26 +180,29 @@ private static async Task PushPackages(List<PackageInput> packageInputs, SleetCo
140
180
{
141
181
if ( skipExisting )
142
182
{
143
- await log . LogAsync ( LogLevel . Minimal , $ "Package already exists, skipping { packageString } ") ;
183
+ await log . LogAsync ( LogLevel . Minimal , $ "Skip exisiting package: { packageString } ") ;
144
184
continue ;
145
185
}
146
186
else if ( force )
147
187
{
148
188
toRemove . Add ( package ) ;
149
- await log . LogAsync ( LogLevel . Information , $ "Package already exists, removing { packageString } ") ;
189
+ await log . LogAsync ( LogLevel . Information , $ "Replace existing package: { packageString } ") ;
150
190
}
151
191
else
152
192
{
153
193
throw new InvalidOperationException ( $ "Package already exists: { packageString } .") ;
154
194
}
155
195
}
196
+ else
197
+ {
198
+ await log . LogAsync ( LogLevel . Minimal , $ "Add new package: { packageString } ") ;
199
+ }
156
200
157
201
// Add to list of packages to push
158
202
toAdd . Add ( package ) ;
159
- await log . LogAsync ( LogLevel . Information , $ "Adding { packageString } ") ;
160
203
}
161
204
162
- await log . LogAsync ( LogLevel . Minimal , $ "Syncing feed files and modifying them locally ") ;
205
+ await log . LogAsync ( LogLevel . Minimal , $ "Processing feed changes ") ;
163
206
164
207
// Add/Remove packages
165
208
var changeContext = SleetOperations . Create ( existingPackageSets , toAdd , toRemove ) ;
@@ -169,7 +212,18 @@ private static async Task PushPackages(List<PackageInput> packageInputs, SleetCo
169
212
/// <summary>
170
213
/// Parse input arguments for nupkg paths.
171
214
/// </summary>
172
- private static async Task < List < PackageInput > > GetPackageInputs ( List < string > inputs , DateTimeOffset now , ILogger log )
215
+ private static async Task < List < PackageInput > > GetPackageInputs ( List < string > packagePaths , DateTimeOffset now , IPerfTracker perfTracker , ILogger log )
216
+ {
217
+ using ( var timer = PerfEntryWrapper . CreateSummaryTimer ( "Loaded package nuspecs in {0}" , perfTracker ) )
218
+ {
219
+ var tasks = packagePaths . Select ( e => new Func < Task < PackageInput > > ( ( ) => GetPackageInput ( e , log ) ) ) ;
220
+ var packageInputs = await TaskUtils . RunAsync ( tasks , useTaskRun : true , token : CancellationToken . None ) ;
221
+ var packagesSorted = packageInputs . OrderBy ( e => e ) . ToList ( ) ;
222
+ return packagesSorted ;
223
+ }
224
+ }
225
+
226
+ private static List < string > GetPackagePaths ( List < string > inputs )
173
227
{
174
228
// Check inputs
175
229
if ( inputs . Count < 1 )
@@ -178,15 +232,9 @@ private static async Task<List<PackageInput>> GetPackageInputs(List<string> inpu
178
232
}
179
233
180
234
// Get package inputs
181
- var packagePaths = inputs . SelectMany ( GetFiles )
182
- . Distinct ( PathUtility . GetStringComparerBasedOnOS ( ) )
183
- . ToList ( ) ;
184
-
185
- var tasks = packagePaths . Select ( e => new Func < Task < PackageInput > > ( ( ) => GetPackageInput ( e , log ) ) ) ;
186
- var packageInputs = await TaskUtils . RunAsync ( tasks , useTaskRun : true , token : CancellationToken . None ) ;
187
- var packagesSorted = packageInputs . OrderBy ( e => e ) . ToList ( ) ;
188
-
189
- return packagesSorted ;
235
+ return inputs . SelectMany ( GetFiles )
236
+ . Distinct ( PathUtility . GetStringComparerBasedOnOS ( ) )
237
+ . ToList ( ) ;
190
238
}
191
239
192
240
private static void CheckForDuplicates ( List < PackageInput > packages )
@@ -206,7 +254,7 @@ private static void CheckForDuplicates(List<PackageInput> packages)
206
254
private static Task < PackageInput > GetPackageInput ( string file , ILogger log )
207
255
{
208
256
// Validate package
209
- log . LogInformation ( $ "Reading { file } ") ;
257
+ log . LogVerbose ( $ "Reading { file } ") ;
210
258
PackageInput packageInput = null ;
211
259
212
260
try
@@ -219,7 +267,7 @@ private static Task<PackageInput> GetPackageInput(string file, ILogger log)
219
267
log . LogError ( $ "Invalid package '{ file } '.") ;
220
268
throw ;
221
269
}
222
-
270
+
223
271
// Display a message for non-normalized packages
224
272
if ( packageInput . Identity . Version . ToString ( ) != packageInput . Identity . Version . ToNormalizedString ( ) )
225
273
{
0 commit comments