@@ -247,48 +247,92 @@ public async Task<OperationResult<LibraryInstallationGoalState>> GetInstallation
247
247
248
248
private OperationResult < LibraryInstallationGoalState > GenerateGoalState ( ILibraryInstallationState desiredState , ILibrary library )
249
249
{
250
+ var mappings = new List < FileMapping > ( desiredState . FileMappings ?? [ ] ) ;
250
251
List < IError > errors = null ;
251
-
252
- if ( string . IsNullOrEmpty ( desiredState . DestinationPath ) )
253
- {
254
- return OperationResult < LibraryInstallationGoalState > . FromError ( PredefinedErrors . DestinationNotSpecified ( desiredState . Name ) ) ;
255
- }
256
-
257
- IEnumerable < string > outFiles ;
258
- if ( desiredState . Files == null || desiredState . Files . Count == 0 )
252
+ if ( desiredState . Files is { Count : > 0 } )
259
253
{
260
- outFiles = library . Files . Keys ;
254
+ mappings . Add ( new FileMapping { Destination = desiredState . DestinationPath , Files = desiredState . Files } ) ;
261
255
}
262
- else
256
+ else if ( desiredState . FileMappings is null or { Count : 0 } )
263
257
{
264
- outFiles = FileGlobbingUtility . ExpandFileGlobs ( desiredState . Files , library . Files . Keys ) ;
258
+ // no files specified and no file mappings => include all files
259
+ mappings . Add ( new FileMapping { Destination = desiredState . DestinationPath } ) ;
265
260
}
266
261
267
262
Dictionary < string , string > installFiles = new ( ) ;
268
- if ( library . GetInvalidFiles ( outFiles . ToList ( ) ) is IReadOnlyList < string > invalidFiles
269
- && invalidFiles . Count > 0 )
270
- {
271
- errors ??= [ ] ;
272
- errors . Add ( PredefinedErrors . InvalidFilesInLibrary ( desiredState . Name , invalidFiles , library . Files . Keys ) ) ;
273
- }
274
263
275
- foreach ( string outFile in outFiles )
264
+ foreach ( FileMapping fileMapping in mappings )
276
265
{
277
- // strip the source prefix
278
- string destinationFile = Path . Combine ( HostInteraction . WorkingDirectory , desiredState . DestinationPath , outFile ) ;
279
- if ( ! FileHelpers . IsUnderRootDirectory ( destinationFile , HostInteraction . WorkingDirectory ) )
266
+ // if Root is not specified, assume it's the root of the library
267
+ string mappingRoot = fileMapping . Root ?? string . Empty ;
268
+ // if Destination is not specified, inherit from the library entry
269
+ string destination = fileMapping . Destination ?? desiredState . DestinationPath ;
270
+
271
+ if ( destination is null )
272
+ {
273
+ errors ??= [ ] ;
274
+ string libraryId = LibraryNamingScheme . GetLibraryId ( desiredState . Name , desiredState . Version ) ;
275
+ errors . Add ( PredefinedErrors . DestinationNotSpecified ( libraryId ) ) ;
276
+ continue ;
277
+ }
278
+
279
+ IReadOnlyList < string > fileFilters ;
280
+ if ( fileMapping . Files is { Count : > 0 } )
281
+ {
282
+ fileFilters = fileMapping . Files ;
283
+ }
284
+ else
285
+ {
286
+ fileFilters = [ "**" ] ;
287
+ }
288
+
289
+ if ( mappingRoot . Length > 0 )
290
+ {
291
+ // prefix mappingRoot to each fileFilter item
292
+ fileFilters = fileFilters . Select ( f => $ "{ mappingRoot } /{ f } ") . ToList ( ) ;
293
+ }
294
+
295
+ List < string > outFiles = FileGlobbingUtility . ExpandFileGlobs ( fileFilters , library . Files . Keys ) . ToList ( ) ;
296
+
297
+ if ( library . GetInvalidFiles ( outFiles ) is IReadOnlyList < string > invalidFiles
298
+ && invalidFiles . Count > 0 )
280
299
{
281
300
errors ??= [ ] ;
282
- errors . Add ( PredefinedErrors . PathOutsideWorkingDirectory ( ) ) ;
301
+ errors . Add ( PredefinedErrors . InvalidFilesInLibrary ( desiredState . Name , invalidFiles , library . Files . Keys ) ) ;
283
302
}
284
- destinationFile = FileHelpers . NormalizePath ( destinationFile ) ;
285
303
286
- // don't forget to include the cache folder in the path
287
- string sourceFile = GetCachedFileLocalPath ( desiredState , outFile ) ;
288
- sourceFile = FileHelpers . NormalizePath ( sourceFile ) ;
304
+ foreach ( string outFile in outFiles )
305
+ {
306
+ // strip the source prefix
307
+ string relativeOutFile = mappingRoot . Length > 0 ? outFile . Substring ( mappingRoot . Length + 1 ) : outFile ;
308
+ string destinationFile = Path . Combine ( HostInteraction . WorkingDirectory , destination , relativeOutFile ) ;
309
+ destinationFile = FileHelpers . NormalizePath ( destinationFile ) ;
310
+
311
+ if ( ! FileHelpers . IsUnderRootDirectory ( destinationFile , HostInteraction . WorkingDirectory ) )
312
+ {
313
+ errors ??= [ ] ;
314
+ errors . Add ( PredefinedErrors . PathOutsideWorkingDirectory ( ) ) ;
315
+ continue ;
316
+ }
289
317
290
- // map destination back to the library-relative file it originated from
291
- installFiles . Add ( destinationFile , sourceFile ) ;
318
+ // include the cache folder in the path
319
+ string sourceFile = GetCachedFileLocalPath ( desiredState , outFile ) ;
320
+ sourceFile = FileHelpers . NormalizePath ( sourceFile ) ;
321
+
322
+ // map destination back to the library-relative file it originated from
323
+ if ( installFiles . ContainsKey ( destinationFile ) )
324
+ {
325
+ // this file is already being installed from another mapping
326
+ errors ??= [ ] ;
327
+ string libraryId = LibraryNamingScheme . GetLibraryId ( desiredState . Name , desiredState . Version ) ;
328
+ errors . Add ( PredefinedErrors . LibraryCannotBeInstalledDueToConflicts ( destinationFile , [ libraryId ] ) ) ;
329
+ continue ;
330
+ }
331
+ else
332
+ {
333
+ installFiles . Add ( destinationFile , sourceFile ) ;
334
+ }
335
+ }
292
336
}
293
337
294
338
if ( errors is not null )
0 commit comments