Skip to content

Instantly share code, notes, and snippets.

@awakecoding
Last active August 4, 2024 21:07
Show Gist options
  • Save awakecoding/c9171965001df09f02f44edec5439ade to your computer and use it in GitHub Desktop.
Save awakecoding/c9171965001df09f02f44edec5439ade to your computer and use it in GitHub Desktop.
ReadyToRun.ps1
param(
[Parameter(Mandatory = $true, Position = 0)]
[string] $InputPath,
[string] $OutputPath,
[ValidateSet("x64", "arm64")]
[string] $TargetArch = "x64",
[ValidateSet("windows", "linux", "osx")]
[string] $TargetOS = "windows",
[Parameter(Mandatory = $true)]
[string] $RuntimeConfigFile,
[string[]] $IncludeFilter,
[switch] $UseCache,
[switch] $PrintCommands
)
function Find-CrossGen2Exe
{
$NugetPackagesPath = "$Env:USERPROFILE\.nuget\packages"
$NugetPackageName = "Microsoft.NETCore.App.Crossgen2.win-x64"
$NugetPackageCache = Join-Path $NugetPackagesPath ($NugetPackageName.ToLower())
$CrossGen2Tool = Get-Item "$NugetPackageCache\*\tools\crossgen2.exe" | Sort-Object -Descending | Select-Object -First 1
$CrossGen2Tool.FullName
}
function Test-DotNetAssembly
{
param (
[Parameter(Mandatory = $true, Position = 0)]
[string] $FilePath
)
try {
[System.Reflection.AssemblyName]::GetAssemblyName($FilePath) | Out-Null
return $true
} catch {
return $false
}
}
function Get-RuntimeAssemblyPaths
{
param (
[Parameter(Mandatory = $true)]
[string] $RuntimeConfigPath
)
$DotNetRootShared = "$Env:ProgramFiles\dotnet\shared"
$RuntimeConfig = Get-Content $RuntimeConfigPath -Raw | ConvertFrom-Json
$FrameworkAssemblyPaths = @()
$Runtimeconfig.runtimeOptions.frameworks | ForEach-Object {
$FrameworkName = $_.Name
$Version = [Version] $_.version
$VersionFilter = "$($Version.Major).$($Version.Minor).*"
$FrameworkAssemblyPath = Get-Item "$DotNetRootShared\$FrameworkName\$VersionFilter" | Sort-Object -Descending | Select-Object -First 1
$FrameworkAssemblyPaths += $FrameworkAssemblyPath.FullName
}
$FrameworkAssemblyPaths
}
function Get-ApplicationBasePath
{
param (
[Parameter(Mandatory = $true)]
[string] $RuntimeConfigPath
)
[System.IO.Path]::GetDirectoryName($RuntimeConfigPath)
}
function Get-ReferencedAssemblies
{
param (
[Parameter(Mandatory = $true)]
[string] $AssemblyPath,
[Parameter(Mandatory = $true)]
[string] $ApplicationBasePath
)
$Assembly = [System.Reflection.Assembly]::LoadFile($AssemblyPath)
$Assembly.GetReferencedAssemblies()
| ForEach-Object { Join-Path $ApplicationBasePath ($_.Name + ".dll") }
| Where-Object { Test-Path -Path $_ -PathType Leaf }
}
if ([string]::IsNullOrEmpty($OutputPath)) {
$OutputPath = $InputPath
$OutputIsInput = $true
}
$RuntimeConfigPath = Join-Path $InputPath $RuntimeConfigFile
if (-Not $IncludeFilter) {
$IncludeFilter = @('*')
}
if (-Not $OutputIsInput) {
#Remove-Item -Path $OutputPath -ErrorAction SilentlyContinue -Recurse -Force | Out-Null
#Copy-Item -Path $InputPath -Destination $OutputPath -Recurse
& robocopy.exe $InputPath $OutputPath /E /DCOPY:DA /COPY:DAT /MT:16 /R:3 /W:5 /IS /NFL /NDL /NS /NC /NP
}
$TargetRID = ($TargetOS -Replace "windows", "win") + "-" + $TargetArch
$ReadyToRunInclude = Get-ChildItem -Path "$InputPath/*.dll" -Include $IncludeFilter
$ApplicationBasePath = Get-ApplicationBasePath -RuntimeConfigPath $RuntimeConfigPath
$FrameworkAssemblyPaths = Get-RuntimeAssemblyPaths -RuntimeConfigPath $RuntimeConfigPath
$FrameworkAssemblies = $FrameworkAssemblyPaths
| ForEach-Object { Get-Item "$_/*.dll" }
| Select-Object -ExpandProperty FullName
| Where-Object { Test-DotNetAssembly $_ }
$SystemReferenceArgs = @()
$FrameworkAssemblies | ForEach-Object {
$SystemReferenceArgs += "-r`:`"$_`""
}
$CrossGen2Exe = Find-CrossGen2Exe
Write-Host "Using $CrossGen2Exe"
if (Test-Path Env:R2R_CACHE_PATH) {
$R2RCachePath = $Env:R2R_CACHE_PATH
} else {
$R2RCachePath = Join-Path (Get-Location) "r2r-cache"
}
if ($UseCache) {
New-Item -Path $R2RCachePath -ItemType Directory -ErrorAction SilentlyContinue | Out-Null
}
$StartTime = [datetime]::Now
$TotalSizeDifference = 0
$CacheHitCount = 0
$AssemblyCount = $ReadyToRunInclude.Count
$ReadyToRunInclude | ForEach-Object {
$InputFile = $AssemblyPath = $_.FullName
$OutputFile = Join-Path $OutputPath $_.Name
if ($OutputIsInput) {
$OutputFile = [System.IO.Path]::ChangeExtension($OutputFile, ".ni.dll")
}
$InputFileName = [System.IO.Path]::GetFileName($InputFile)
$InputFileHash = (Get-FileHash $InputFile SHA256).Hash.ToLower()
# <r2r-cache>/<TargetRID>/<InputFileName>/<InputFileHash>/
$CacheFileDir = Join-Path $R2RCachePath $TargetRID $InputFileName $InputFileHash
$CacheFilePath = Join-Path $CacheFileDir $InputFileName
$CacheFileDeps = $CacheFilePath + ".deps"
$ReferencedAssemblies = Get-ReferencedAssemblies -AssemblyPath $AssemblyPath -ApplicationBasePath $ApplicationBasePath
$Dependencies = ""
foreach ($ReferencedAssembly in $ReferencedAssemblies) {
$FileName = [System.IO.Path]::GetFileName($ReferencedAssembly)
$FileHash = (Get-FileHash $ReferencedAssembly SHA256).Hash.ToLower()
$Dependencies += "$FileHash $FileName`n"
}
$Dependencies = $Dependencies.TrimEnd("`n")
$sha256 = [System.Security.Cryptography.SHA256]::Create()
$hashBytes = $sha256.ComputeHash([System.Text.Encoding]::UTF8.GetBytes($Dependencies))
$InputDepsHash = ([BitConverter]::ToString($hashBytes) -Replace '-', '').ToLower()
$ReferenceArgs = @()
$ReferenceArgs += $SystemReferenceArgs
$ReferencedAssemblies | ForEach-Object {
$ReferenceArgs += "-r:`"$_`""
}
$CrossGenArgs = @("--targetos:$TargetOS", "--targetarch:$TargetArch")
$CrossGenArgs += @("-O")
$CrossGenArgs += $ReferenceArgs
$CrossGenArgs += "--out:`"$OutputFile`""
$CrossGenArgs += $InputFile
if ($PrintCommands) {
Write-Host "crossgen2.exe" $CrossGenArgs -Separator "`r`n"
}
$CacheHit = $false
if ($UseCache) {
if ((Test-Path -Path $CacheFilePath -PathType Leaf) -and (Test-Path -Path $CacheFileDeps -PathType Leaf)) {
$CachedDepsHash = (Get-FileHash $CacheFileDeps SHA256).Hash.ToLower()
if ($InputDepsHash -eq $CachedDepsHash) {
$CacheHit = $true
$CacheHitCount++
}
}
}
if ($CacheHit) {
Copy-Item $CacheFilePath $OutputFile -Force
} else {
$ResponseFilePath = [System.IO.Path]::Combine([System.IO.Path]::GetTempPath(), "crossgen2_args.rsp")
$CrossGenArgs | Out-File -FilePath $ResponseFilePath -Encoding Utf8NoBOM
Start-Process -FilePath $CrossGen2Exe -ArgumentList "@$ResponseFilePath" -NoNewWindow -Wait
Remove-Item $ResponseFilePath
}
$InputFileSize = (Get-Item $InputFile).Length
$OutputFileSize = (Get-Item $OutputFile).Length
$SizeDifference = $OutputFileSize - $InputFileSize
$SizePercentage = [math]::Round(($SizeDifference / $InputFileSize) * 100, 2)
$TotalSizeDifference += $SizeDifference
Write-Output "$($_.Name)`: $SizePercentage`% larger"
if ($OutputIsInput) {
Remove-Item $InputFile | Out-Null
Move-Item $OutputFile $InputFile
$OutputFile = $InputFile
}
if ($UseCache) {
New-Item -Path $CacheFileDir -ItemType Directory -ErrorAction SilentlyContinue | Out-Null
Copy-Item $OutputFile $CacheFilePath -Force
Set-Content -Path $CacheFileDeps -Value $Dependencies -Encoding Utf8NoBOM -NoNewLine -Force
}
}
$TotalSizeDiffMB = [math]::Round($TotalSizeDifference / 1000 / 1000, 2)
Write-Output "ReadyToRun output is $TotalSizeDiffMB MB larger"
$EndTime = [datetime]::Now
$TotalTimeSeconds = [math]::Round(($EndTime - $StartTime).TotalSeconds, 2)
Write-Output "Total generation time: $TotalTimeSeconds seconds"
if ($UseCache) {
$CacheHitPercentage = [math]::Round(($CacheHitCount / $AssemblyCount) * 100, 2)
Write-Output "Cache hits: $CacheHitCount ($CacheHitPercentage%)"
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment