2
2
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3
3
4
4
using System ;
5
+ using System . Collections . Generic ;
5
6
using System . IO ;
6
- using System . Linq ;
7
7
using System . Reflection ;
8
8
using System . Runtime . Serialization . Json ;
9
+ using System . Text ;
9
10
using Microsoft . Build . Framework ;
10
11
using Microsoft . Build . Utilities ;
12
+ using ResourceHashesByNameDictionary = System . Collections . Generic . Dictionary < string , string > ;
11
13
12
14
namespace Microsoft . AspNetCore . Blazor . Build
13
15
{
@@ -17,53 +19,107 @@ public class GenerateBlazorBootJson : Task
17
19
public string AssemblyPath { get ; set ; }
18
20
19
21
[ Required ]
20
- public ITaskItem [ ] References { get ; set ; }
22
+ public ITaskItem [ ] Resources { get ; set ; }
23
+
24
+ [ Required ]
25
+ public bool DebugBuild { get ; set ; }
21
26
22
27
[ Required ]
23
28
public bool LinkerEnabled { get ; set ; }
24
29
30
+ [ Required ]
31
+ public bool CacheBootResources { get ; set ; }
32
+
25
33
[ Required ]
26
34
public string OutputPath { get ; set ; }
27
35
28
36
public override bool Execute ( )
29
37
{
38
+ using var fileStream = File . Create ( OutputPath ) ;
30
39
var entryAssemblyName = AssemblyName . GetAssemblyName ( AssemblyPath ) . Name ;
31
- var assemblies = References . Select ( GetUriPath ) . OrderBy ( c => c , StringComparer . Ordinal ) . ToArray ( ) ;
32
40
33
- using var fileStream = File . Create ( OutputPath ) ;
34
- WriteBootJson ( fileStream , entryAssemblyName , assemblies , LinkerEnabled ) ;
41
+ try
42
+ {
43
+ WriteBootJson ( fileStream , entryAssemblyName ) ;
44
+ }
45
+ catch ( Exception ex )
46
+ {
47
+ Log . LogErrorFromException ( ex ) ;
48
+ }
35
49
36
- return true ;
50
+ return ! Log . HasLoggedErrors ;
51
+ }
37
52
38
- static string GetUriPath ( ITaskItem item )
53
+ // Internal for tests
54
+ internal void WriteBootJson ( Stream output , string entryAssemblyName )
55
+ {
56
+ var result = new BootJsonData
39
57
{
40
- var outputPath = item . GetMetadata ( "RelativeOutputPath" ) ;
41
- if ( string . IsNullOrEmpty ( outputPath ) )
58
+ entryAssembly = entryAssemblyName ,
59
+ cacheBootResources = CacheBootResources ,
60
+ debugBuild = DebugBuild ,
61
+ linkerEnabled = LinkerEnabled ,
62
+ resources = new Dictionary < ResourceType , ResourceHashesByNameDictionary > ( )
63
+ } ;
64
+
65
+ // Build a two-level dictionary of the form:
66
+ // - BootResourceType (e.g., "assembly")
67
+ // - UriPath (e.g., "System.Text.Json.dll")
68
+ // - ContentHash (e.g., "4548fa2e9cf52986")
69
+ if ( Resources != null )
70
+ {
71
+ foreach ( var resource in Resources )
42
72
{
43
- outputPath = Path . GetFileName ( item . ItemSpec ) ;
44
- }
73
+ var resourceTypeMetadata = resource . GetMetadata ( "BootResourceType" ) ;
74
+ if ( ! Enum . TryParse < ResourceType > ( resourceTypeMetadata , out var resourceType ) )
75
+ {
76
+ throw new NotSupportedException ( $ "Unsupported BootResourceType metadata value: { resourceTypeMetadata } ") ;
77
+ }
45
78
46
- return outputPath . Replace ( '\\ ' , '/' ) ;
79
+ if ( ! result . resources . TryGetValue ( resourceType , out var resourceList ) )
80
+ {
81
+ resourceList = new ResourceHashesByNameDictionary ( ) ;
82
+ result . resources . Add ( resourceType , resourceList ) ;
83
+ }
84
+
85
+ var resourceFileRelativePath = GetResourceFileRelativePath ( resource ) ;
86
+ if ( ! resourceList . ContainsKey ( resourceFileRelativePath ) )
87
+ {
88
+ resourceList . Add ( resourceFileRelativePath , $ "sha256-{ resource . GetMetadata ( "FileHash" ) } ") ;
89
+ }
90
+ }
47
91
}
92
+
93
+ var serializer = new DataContractJsonSerializer ( typeof ( BootJsonData ) , new DataContractJsonSerializerSettings
94
+ {
95
+ UseSimpleDictionaryFormat = true
96
+ } ) ;
97
+
98
+ using var writer = JsonReaderWriterFactory . CreateJsonWriter ( output , Encoding . UTF8 , ownsStream : false , indent : true ) ;
99
+ serializer . WriteObject ( writer , result ) ;
48
100
}
49
101
50
- internal static void WriteBootJson ( Stream stream , string entryAssemblyName , string [ ] assemblies , bool linkerEnabled )
102
+ private static string GetResourceFileRelativePath ( ITaskItem item )
51
103
{
52
- var data = new BootJsonData
104
+ // The build targets use RelativeOutputPath in the case of satellite assemblies, which
105
+ // will have relative paths like "fr\\SomeAssembly.resources.dll". If RelativeOutputPath
106
+ // is specified, we want to use all of it.
107
+ var outputPath = item . GetMetadata ( "RelativeOutputPath" ) ;
108
+
109
+ if ( string . IsNullOrEmpty ( outputPath ) )
53
110
{
54
- entryAssembly = entryAssemblyName ,
55
- assemblies = assemblies ,
56
- linkerEnabled = linkerEnabled ,
57
- } ;
111
+ // If RelativeOutputPath was not specified, we assume the item will be placed at the
112
+ // root of whatever directory is used for its resource type (e.g., assemblies go in _bin)
113
+ outputPath = Path . GetFileName ( item . ItemSpec ) ;
114
+ }
58
115
59
- var serializer = new DataContractJsonSerializer ( typeof ( BootJsonData ) ) ;
60
- serializer . WriteObject ( stream , data ) ;
116
+ return outputPath . Replace ( '\\ ' , '/' ) ;
61
117
}
62
118
119
+ #pragma warning disable IDE1006 // Naming Styles
63
120
/// <summary>
64
121
/// Defines the structure of a Blazor boot JSON file
65
122
/// </summary>
66
- #pragma warning disable IDE1006 // Naming Styles
67
123
public class BootJsonData
68
124
{
69
125
/// <summary>
@@ -72,15 +128,39 @@ public class BootJsonData
72
128
public string entryAssembly { get ; set ; }
73
129
74
130
/// <summary>
75
- /// Gets the closure of assemblies to be loaded by Blazor WASM. This includes the application entry assembly.
131
+ /// Gets the set of resources needed to boot the application. This includes the transitive
132
+ /// closure of .NET assemblies (including the entrypoint assembly), the dotnet.wasm file,
133
+ /// and any PDBs to be loaded.
134
+ ///
135
+ /// Within <see cref="ResourceHashesByNameDictionary"/>, dictionary keys are resource names,
136
+ /// and values are SHA-256 hashes formatted in prefixed base-64 style (e.g., 'sha256-abcdefg...')
137
+ /// as used for subresource integrity checking.
76
138
/// </summary>
77
- public string [ ] assemblies { get ; set ; }
139
+ public Dictionary < ResourceType , ResourceHashesByNameDictionary > resources { get ; set ; }
140
+
141
+ /// <summary>
142
+ /// Gets a value that determines whether to enable caching of the <see cref="resources"/>
143
+ /// inside a CacheStorage instance within the browser.
144
+ /// </summary>
145
+ public bool cacheBootResources { get ; set ; }
146
+
147
+ /// <summary>
148
+ /// Gets a value that determines if this is a debug build.
149
+ /// </summary>
150
+ public bool debugBuild { get ; set ; }
78
151
79
152
/// <summary>
80
153
/// Gets a value that determines if the linker is enabled.
81
154
/// </summary>
82
155
public bool linkerEnabled { get ; set ; }
83
156
}
157
+
158
+ public enum ResourceType
159
+ {
160
+ assembly ,
161
+ pdb ,
162
+ wasm
163
+ }
84
164
#pragma warning restore IDE1006 // Naming Styles
85
165
}
86
166
}
0 commit comments