-
Notifications
You must be signed in to change notification settings - Fork 266
/
GacCommon.ps1
240 lines (214 loc) · 9.86 KB
/
GacCommon.ps1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
<#
Force the object from command or function result to array.
Sometimes when a command returns zero or one object, it becomes $null or the object.
Only when a command returns multiple objects, it becomes an array.
This function let you get an array anyway to make writing script easier.
#>
function ForceArray($nodes) {
if ($nodes -eq $null) {
return ,@()
} elseif (($nodes -is [System.Array]) -or ($nodes -is [System.Collections.ArrayList])) {
return $nodes
} else {
return ,@($nodes)
}
}
<#
Search all GacUI Xml Resource file names in the directory that contains GacUI.xml, which is specified by $FileName.
#>
function EnumerateResourceFiles([String] $FileName) {
Write-Host "Searching for all resource files ..."
<# Load GacUI.xml and find all path patterns that we want to exclude from our result #>
[Xml]$gacui_xml = Get-Content $FileName
$excludes = (ForceArray (Select-Xml -Xml $gacui_xml -XPath "//GacUI/Exclude/@Pattern")).Node.Value
$search_directory = Split-Path -Parent (Resolve-Path $FileName)
<# Enumerate all Xml file, if it is not excluded, and it matches <Resource><Folder name="GacGenConfig"/></Resource> #>
$resource_files = (Get-ChildItem $search_directory -Filter "*.xml" -Recurse | ForEach-Object {
$normalized_path = $_.FullName -replace '\\','/'
if (($excludes | Where-Object { $normalized_path.Contains($_) }).Length -eq 0) {
if ((Select-Xml -Path $_.FullName -XPath "//Resource/Folder[@name='GacGenConfig']") -ne $null) {
$_.FullName.Substring($search_directory.Length)
}
}
})
[System.IO.File]::WriteAllLines("$($FileName).log\ResourceFiles.txt", $resource_files)
}
<#
Call GacGen.exe to dump metadatas from resource files.
Input files is save in $($FileName).log\ResourceFiles.txt, which is generated by EnumerateResourceFiles function
Output files is specified in $ResourceDumpFiles(resource_file_name => dump_file_name)
#>
function DumpResourceFiles([String] $FileName, [HashTable]$ResourceDumpFiles) {
Write-Host "Dumping all resource files ..."
$search_directory = Split-Path -Parent (Resolve-Path $FileName)
Get-Content "$($FileName).log\ResourceFiles.txt" | ForEach-Object {
$input_file = Join-Path -Path $search_directory -ChildPath $_
$output_file = "$($FileName).log\$($_ -replace '\\','_')"
$ResourceDumpFiles[$input_file] = $output_file
}
$ResourceDumpFiles.Keys | ForEach-Object {
$input_file = $_
$output_file = $ResourceDumpFiles[$_]
Start-Process-And-Wait (,("$PSScriptRoot\GacGen.exe", "/D32 `"$($input_file)`" `"$($output_file)`"")) $true
if (-not (Test-Path -Path $output_file)) {
throw "Failed to dump GacUI Xml Resource File: " + $input_file
}
}
}
<#
Given a metadata dump, determine if the compile result of this resource file is outdated.
It collects last modify times of all input files and output files.
If any output files' time is eariler than any input files' time, it is outdated.
#>
function NeedBuild([Xml] $Dump) {
$input_files = (ForceArray (Select-Xml -Xml $Dump -XPath "//ResourceMetadata/Inputs/Input/@Path")).Node.Value
$output_files = (ForceArray (Select-Xml -Xml $Dump -XPath "//ResourceMetadata/Outputs/Output/@Path")).Node.Value
if (($output_files | Where-Object { -not [System.IO.File]::Exists($_) }) -ne $null) {
return $true
}
$input_file_times = ForceArray ($input_files | ForEach-Object {
[System.IO.FileInfo]::new($_).LastWriteTimeUtc
})
$output_file_times = ForceArray ($output_files | ForEach-Object {
[System.IO.FileInfo]::new($_).LastWriteTimeUtc
})
$outdated = $output_file_times | Where-Object {
$output = $_
$modifieds = $input_file_times | Where-Object {
return $_ -gt $output
}
return $modifieds -ne $null
}
return $outdated -ne $null
}
<#
Given all metadata dumps $ResourceDumps(resource_file_name => Xml dump),
generate $name_to_file_map(resource_name => resource_file_name),
and $name_to_dep_map(resource_name => all dependencies)
#>
function ExtractDeps([HashTable] $ResourceDumps, [HashTable] $name_to_file_map, [HashTable] $name_to_dep_map)
{
$ResourceDumps.Keys | ForEach-Object {
$xml = $ResourceDumps[$_]
$name = (Select-Xml -Xml $xml -XPath "//ResourceMetadata/ResourceMetadata/@Name").Node.Value
if ($name -ne "") {
$attrs = ForceArray (Select-Xml -Xml $xml -XPath "//ResourceMetadata/ResourceMetadata/Dependencies/Resource/@Name")
$deps = ForceArray $attrs.Node.Value
$name_to_file_map[$name] = $_
$name_to_dep_map[$name] = [System.Collections.ArrayList]::new($deps)
}
}
}
<#
Given all metadata dumps $ResourceDumps(resource_file_name => Xml dump),
write all resources that is outdated to $OutputFileName.
#>
function EnumerateBuildCandidates([HashTable] $ResourceDumps, [String] $OutputFileName) {
Write-Host "Finding resource files that need rebuild ..."
$direct_candidates = ForceArray ($ResourceDumps.Keys | Where-Object { NeedBuild $ResourceDumps[$_] })
$name_to_file_map = @{}
$name_to_dep_map = @{}
ExtractDeps $ResourceDumps $name_to_file_map $name_to_dep_map
$file_to_name_map = @{}
$name_to_file_map.Keys | ForEach-Object { $file_to_name_map[$name_to_file_map[$_]] = $_ }
<# Get all names of named resources that are outdated #>
$names = ForceArray ($direct_candidates | Where-Object { $file_to_name_map.ContainsKey($_) } | ForEach-Object { $file_to_name_map[$_] })
$names = [System.Collections.ArrayList]::new($names)
<# Get all names of named resources that are not outdated #>
$pool = ForceArray ($name_to_file_map.Keys | Where-Object { -not $names.Contains($_) })
$pool = [System.Collections.ArrayList]::new($pool)
<# Grow $names from $pool #>
while ($true)
{
<# If any resource in $pool depends on any resource in $name, it is moved from $pool to $names #>
$selection = ForceArray ($pool | Where-Object {
$deps = $name_to_dep_map[$_]
return (ForceArray ($deps | Where-Object { $names.Contains($_) })).Count -ne 0
})
if ($selection.Count -eq 0) { break }
$names.AddRange($selection)
$selection | ForEach-Object { $pool.Remove($_) }
}
<# List all anonymous resource files before named resource files #>
$anonymous_candidates = ForceArray ($direct_candidates | Where-Object { -not $name_to_file_map.ContainsValue($_) })
$named_candidates = ForceArray ($names | ForEach-Object { $name_to_file_map[$_] })
[System.IO.File]::WriteAllLines($OutputFileName, $anonymous_candidates + $named_candidates)
}
<#
Given all metadata dumps $ResourceDumps(resource_file_name => Xml dump),
Write all paths of anonymous resource files to $OutputFileName.
#>
function EnumerateAnonymousResources([HashTable] $ResourceDumps, [String] $OutputFileName) {
Write-Host "Finding anonymouse resource files ..."
$file_names = $ResourceDumps.Keys | Where-Object {
return (ForceArray (Select-Xml -Xml $ResourceDumps[$_] -XPath "//ResourceMetadata/ResourceMetadata/@Name"))[0].Node.Value -eq ""
} | Sort-Object
[System.IO.File]::WriteAllLines($OutputFileName, (ForceArray $file_names))
}
<#
Verify if $name_to_dep_name(resource_name => dependencies) contains any dependencies that are not in this map
#>
function ValidateDeps([HashTable] $name_to_dep_map)
{
$hasError = $false
$name_to_dep_map.Keys | ForEach-Object {
$key = $_
$name_to_dep_map[$key] | ForEach-Object {
if (-not $name_to_dep_map.ContainsKey($key)) {
$hasError = $true
Write-Host "Resource $($key) depends on $($_) but $($_) does not exist."
}
}
}
if ($hasError) { throw "Please check your metadata." }
}
<#
Sort all named resource files in partial order
#>
function SortDeps([HashTable] $name_to_dep_map)
{
$compile_order = [System.Collections.ArrayList]::new()
while ($name_to_dep_map.Count -gt 0) {
$selection = ForceArray ($name_to_dep_map.Keys | Where-Object { $name_to_dep_map[$_].Count -eq 0 })
if ($selection.Count -eq 0) {
Write-Host "Found circle dependency in the following resources:"
$name_to_dep_map.Keys | ForEach-Object { Write-Host " $($_)" }
$hasError = $true;
break
} else {
$compile_order.AddRange((ForceArray ($selection | Select-Object)))
$selection | ForEach-Object {
$ready = $_
$name_to_dep_map.Remove($ready)
$name_to_dep_map.Values | ForEach-Object { $_.Remove($ready) }
}
}
}
if ($hasError) { throw "Please check your metadata." }
return $compile_order
}
<#
Given all metadata dumps $ResourceDumps(resource_file_name => Xml dump),
write all paths of named resource files in the correct build order to $OutputNames,
with all "resource_name=>resource_file_path" to $OutputMapping.
The $OutputMapping will be consumed
GacGen.exe /P32 <resource-xml> <HERE>
GacGen.exe /P64 <resource-xml> <HERE>
as an optional parameter
#>
function EnumerateNamedResources([HashTable] $ResourceDumps, [String] $OutputNames, [String] $OutputMapping) {
Write-Host "Finding named resource files ..."
$name_to_file_map = @{}
$name_to_dep_map = @{}
ExtractDeps $ResourceDumps $name_to_file_map $name_to_dep_map
ValidateDeps $name_to_dep_map
$compile_order = ForceArray (SortDeps $name_to_dep_map)
$file_names = ForceArray ($compile_order | ForEach-Object {
return $name_to_file_map[$_]
})
$file_mapping = ForceArray ($name_to_file_map.Keys | ForEach-Object {
return "$($_)=>$($name_to_file_map[$_])"
} | Sort-Object)
[System.IO.File]::WriteAllLines($OutputNames, $file_names)
[System.IO.File]::WriteAllLines($OutputMapping, $file_mapping)
}