diff --git a/Tools/CppMerge.exe b/Tools/CppMerge.exe index bfb19ada..a9045629 100644 Binary files a/Tools/CppMerge.exe and b/Tools/CppMerge.exe differ diff --git a/Tools/GacBuild.ps1 b/Tools/GacBuild.ps1 new file mode 100644 index 00000000..aa0d86a5 --- /dev/null +++ b/Tools/GacBuild.ps1 @@ -0,0 +1,114 @@ +<# +Do an incremental build to all GacUI Xml Resource files directly or indirectly in the folder containing $FileName. +If you set -Dump, then this scripts stops before the real building process, it outputs following files for debug: + $($FileName).log\ResourceFiles.txt: All resource files that are found + $($FileName).log\BuildCandidates.txt: All resource files that are outdated, so they will be build + $($FileName).log\ResourceAnonymousFiles.txt: All anonymous resource files + $($FileName).log\ResourceNamedFiles.txt: All named resource files in the correct order sorted using dependencies + $($FileName).log\ResourceNamedMapping.txt: Resource name to full path mappings for GacGen.exe /P + +$FileName should points to an Xml file with this format: + + + ... + + +GacBuild.ps1 searches all GacUI Xml Resource files, but whose full patch matches one of the pattern will be ignored. +You should use "/" instead of "\" in the pattern attribute. + +In order to make your resource file, be able to depend on others, or be able to be depended by others, +you should add the following metadata in GacGenConfig like this: + + + + + + + + .... + + + + ... + ... + ... + + + ... + +#> +[CmdLetBinding()] +param ( + [Parameter(Mandatory=$true)][String]$FileName, + [Switch]$Dump +) + +. $PSScriptRoot\StartProcess.ps1 +. $PSScriptRoot\GacCommon.ps1 + +# Prevent from displaying "Debug or Close Application" dialog on crash +$dontshowui_key = "HKCU:\Software\Microsoft\Windows\Windows Error Reporting" +$dontshowui_value = (Get-ItemProperty $dontshowui_key).DontShowUI +Set-ItemProperty $dontshowui_key -Name DontShowUI -Value 1 + +try { + if (-not (Test-Path -Path $FileName)) { + throw "Input does not exist: $FileName" + } + $FileName = (Resolve-Path -Path $FileName).Path + Remove-Item -Path "$($FileName).log" -Recurse | Out-Null + New-Item -ItemType Directory "$($FileName).log" | Out-Null + + EnumerateResourceFiles $FileName + if (-not (Test-Path -Path "$($FileName).log\ResourceFiles.txt")) { + throw "Failed to enumerate GacUI Xml Resource files" + } + + $resource_dump_files = @{} + $resource_dumps = @{} + DumpResourceFiles $FileName $resource_dump_files + $resource_dump_files.Keys | ForEach-Object { + $resource_dumps[$_] = [Xml](Get-Content $resource_dump_files[$_]) + } + + $build_candidates_file = "$($FileName).log\BuildCandidates.txt" + $anonymous_file = "$($FileName).log\ResourceAnonymousFiles.txt" + $named_file = "$($FileName).log\ResourceNamedFiles.txt" + $mapping_file = "$($FileName).log\ResourceNamedMapping.txt" + + EnumerateBuildCandidates $resource_dumps $build_candidates_file + EnumerateAnonymousResources $resource_dumps $anonymous_file + EnumerateNamedResources $resource_dumps $named_file $mapping_file + + if ($dump) { + Write-Host "Dumps:" + Write-Host " $($build_candidates_file)" + Write-Host " $($anonymous_file)" + Write-Host " $($named_file)" + Write-Host " $($mapping_file)" + } + + if (-not $dump) { + Write-Host "Rebuilding all outdated binaries ..." + $build_candidates = Get-Content $build_candidates_file + + (@() + (ForceArray (Get-Content $anonymous_file)) + (ForceArray(Get-Content $named_file))) | ForEach-Object { + try { + if ($build_candidates -contains $_) { + Write-Host "[BUILD] $($_)" + & $PSScriptRoot\GacGen.ps1 -FileName $_ -MappingFileName $mapping_file + } else { + Write-Host "[SKIPPED] $($_)" + } + } catch { + Write-Host $_.Exception.Message -ForegroundColor Red + } + } + } +} +catch { + Write-Host $_.Exception.Message -ForegroundColor Red +} + +Set-ItemProperty $dontshowui_key -Name DontShowUI -Value $dontshowui_value +[Console]::ResetColor() \ No newline at end of file diff --git a/Tools/GacClear.ps1 b/Tools/GacClear.ps1 new file mode 100644 index 00000000..0fb2b967 --- /dev/null +++ b/Tools/GacClear.ps1 @@ -0,0 +1,46 @@ +<# +See comments in GacBuild.ps1 for $FileName. +GacClear.ps1 deletes all cached compile result of all GacUI Xml Resource files, to cause GacBuild.ps1 do a full build. +If your resource files named Resource.xml, then the Resource.xml.log folder cached all files, which will be deleted. +#> +param ( + [String]$FileName +) + +. $PSScriptRoot\StartProcess.ps1 +. $PSScriptRoot\GacCommon.ps1 + +# Prevent from displaying "Debug or Close Application" dialog on crash +$dontshowui_key = "HKCU:\Software\Microsoft\Windows\Windows Error Reporting" +$dontshowui_value = (Get-ItemProperty $dontshowui_key).DontShowUI +Set-ItemProperty $dontshowui_key -Name DontShowUI -Value 1 + +try { + if (-not (Test-Path -Path $FileName)) { + throw "Input does not exist: $FileName" + } + $FileName = (Resolve-Path -Path $FileName).Path + Remove-Item -Path "$($FileName).log" -Recurse | Out-Null + New-Item -ItemType Directory "$($FileName).log" | Out-Null + + EnumerateResourceFiles $FileName + if (-not (Test-Path -Path "$($FileName).log\ResourceFiles.txt")) { + throw "Failed to enumerate GacUI Xml 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 $_ + $log_folder = "$($input_file).log" + if (Test-Path -Path $log_folder) { + Write-Host "Deleting: " $log_folder + Remove-Item -Path $log_folder -Recurse + } + } +} +catch { + Write-Host $_.Exception.Message -ForegroundColor Red +} + +Set-ItemProperty $dontshowui_key -Name DontShowUI -Value $dontshowui_value +[Console]::ResetColor() \ No newline at end of file diff --git a/Tools/GacCommon.ps1 b/Tools/GacCommon.ps1 new file mode 100644 index 00000000..cf8ee3f7 --- /dev/null +++ b/Tools/GacCommon.ps1 @@ -0,0 +1,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_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 GacGen32.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\GacGen32.exe", "/D `"$($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 + GacGen32.exe /P + GacGen32.exe /P + 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) +} \ No newline at end of file diff --git a/Tools/GacGen.ps1 b/Tools/GacGen.ps1 index f3cd662f..39fa2b4b 100644 --- a/Tools/GacGen.ps1 +++ b/Tools/GacGen.ps1 @@ -1,12 +1,14 @@ param ( - [String]$FileName + [Parameter(Mandatory=$true)][String]$FileName, + [String]$MappingFileName ) . $PSScriptRoot\StartProcess.ps1 Write-Host "Compiling GacUI Resource: $FileName ..." -Start-Process-And-Wait (,("$PSScriptRoot\GacGen32.exe", "/P $FileName")) -Start-Process-And-Wait (,("$PSScriptRoot\GacGen64.exe", "/P $FileName")) +Remove-Item -Path "$($FileName).log" -Recurse -ErrorAction Ignore | Out-Null +Start-Process-And-Wait (,("$PSScriptRoot\GacGen32.exe", "/P $FileName $MappingFileName")) +Start-Process-And-Wait (,("$PSScriptRoot\GacGen64.exe", "/P $FileName $MappingFileName")) if (Test-Path -Path "$($FileName).log\x32\Errors.txt") { Write-Host (Get-Content "$($FileName).log\x32\Errors.txt") -ForegroundColor Red -Separator "`r`n" @@ -16,16 +18,19 @@ if (Test-Path -Path "$($FileName).log\x64\Errors.txt") { Write-Host (Get-Content "$($FileName).log\x64\Errors.txt") -ForegroundColor Red -Separator "`r`n" throw "Failed to compile GacUI Resource (x64): $FileName" } - -$output_folder = Get-Content "$($FileName).log\x32\CppOutput.txt" -$x32_folder = "$($FileName).log\x32\Source" -$x64_folder = "$($FileName).log\x64\Source" -if (!(Test-Path -Path $output_folder)) { - New-Item $output_folder -ItemType directory | Out-Null -} -Get-ChildItem -Path $x32_folder -ErrorAction SilentlyContinue | %{ - Write-Host " Merging C++ Source File: $($_.Name) ..." - Start-Process-And-Wait (,("$PSScriptRoot\CppMerge.exe", "`"$x32_folder\$($_.Name)`" `"$x64_folder\$($_.Name)`" `"$output_folder\$($_.Name)`"")) + +$output_cpp = "$($FileName).log\x32\CppOutput.txt" +if (Test-Path -Path $output_cpp) { + $output_folder = Get-Content $output_cpp + $x32_folder = "$($FileName).log\x32\Source" + $x64_folder = "$($FileName).log\x64\Source" + if (!(Test-Path -Path $output_folder)) { + New-Item $output_folder -ItemType directory | Out-Null + } + Get-ChildItem -Path $x32_folder -ErrorAction SilentlyContinue | %{ + Write-Host " Merging C++ Source File: $($_.Name) ..." + Start-Process-And-Wait (,("$PSScriptRoot\CppMerge.exe", "`"$x32_folder\$($_.Name)`" `"$x64_folder\$($_.Name)`" `"$output_folder\$($_.Name)`"")) + } } $deploy = "$($FileName).log\x32\Deploy.bat" diff --git a/Tools/GacGen32.exe b/Tools/GacGen32.exe index f53f158f..7747416d 100644 Binary files a/Tools/GacGen32.exe and b/Tools/GacGen32.exe differ diff --git a/Tools/GacGen64.exe b/Tools/GacGen64.exe index 8a133377..40b64ac5 100644 Binary files a/Tools/GacGen64.exe and b/Tools/GacGen64.exe differ diff --git a/Tutorial/GacUI_ControlTemplate/BlackSkin/UI/FullControlTest/Source/DocumentEditorBase.cpp b/Tutorial/GacUI_ControlTemplate/BlackSkin/UI/FullControlTest/Source/DocumentEditorBase.cpp index 6aca7483..aba425c8 100644 --- a/Tutorial/GacUI_ControlTemplate/BlackSkin/UI/FullControlTest/Source/DocumentEditorBase.cpp +++ b/Tutorial/GacUI_ControlTemplate/BlackSkin/UI/FullControlTest/Source/DocumentEditorBase.cpp @@ -124,46 +124,25 @@ namespace demo USERIMPL(/* ::demo::DocumentEditorBase */) void DocumentEditorBase::LoadAsPrivateFormat(const ::vl::WString& fileName) { - vl::stream::FileStream fileStream(fileName, vl::stream::FileStream::ReadOnly); - auto model = vl::presentation::LoadDocumentFromClipboardStream(fileStream); - document->SetDocument(model); + throw ::vl::Exception(L"You should implement this function."); } USERIMPL(/* ::demo::DocumentEditorBase */) void DocumentEditorBase::SaveAsPrivateFormat(const ::vl::WString& fileName) { - document->SelectAll(); - auto model = document->GetSelectionModel(); - - vl::presentation::ModifyDocumentForClipboard(model); - vl::stream::FileStream fileStream(fileName, vl::stream::FileStream::WriteOnly); - vl::presentation::SaveDocumentToClipboardStream(model, fileStream); + throw ::vl::Exception(L"You should implement this function."); } USERIMPL(/* ::demo::DocumentEditorBase */) void DocumentEditorBase::SaveAsRTF(const ::vl::WString& fileName) { - document->SelectAll(); - auto model = document->GetSelectionModel(); - - vl::AString rtf; - vl::presentation::SaveDocumentToRtf(model, rtf); - vl::stream::FileStream fileStream(fileName, vl::stream::FileStream::WriteOnly); - fileStream.Write((void*)rtf.Buffer(), rtf.Length()); + throw ::vl::Exception(L"You should implement this function."); } USERIMPL(/* ::demo::DocumentEditorBase */) void DocumentEditorBase::SaveAsHTML(const ::vl::WString& fileName) { - document->SelectAll(); - auto model = document->GetSelectionModel(); - - vl::AString header, content, footer; - vl::presentation::SaveDocumentToHtmlUtf8(model, header, content, footer); - vl::stream::FileStream fileStream(fileName, vl::stream::FileStream::WriteOnly); - fileStream.Write((void*)header.Buffer(), header.Length()); - fileStream.Write((void*)content.Buffer(), content.Length()); - fileStream.Write((void*)footer.Buffer(), footer.Length()); + throw ::vl::Exception(L"You should implement this function."); } void DocumentEditorBase::SaveDocument() diff --git a/Tutorial/GacUI_HelloWorlds/UIRes/Xml.bin.x64 b/Tutorial/GacUI_HelloWorlds/UIRes/Xml.bin.x64 index c5ea4c78..8602a6a8 100644 Binary files a/Tutorial/GacUI_HelloWorlds/UIRes/Xml.bin.x64 and b/Tutorial/GacUI_HelloWorlds/UIRes/Xml.bin.x64 differ diff --git a/Tutorial/GacUI_HelloWorlds/UIRes/Xml.bin.x86 b/Tutorial/GacUI_HelloWorlds/UIRes/Xml.bin.x86 index 131a93b2..95f398b3 100644 Binary files a/Tutorial/GacUI_HelloWorlds/UIRes/Xml.bin.x86 and b/Tutorial/GacUI_HelloWorlds/UIRes/Xml.bin.x86 differ