@@ -554,107 +554,165 @@ function Request-UnitySetupInstaller {
554
554
# name and this corrects the separators to current environment.
555
555
$fullCachePath = (Resolve-Path - Path $Cache ).Path
556
556
557
- $downloads = @ ()
557
+ $allInstallers = @ ()
558
558
}
559
559
process {
560
+ # Append the full list of installers to enable batch downloading of installers.
560
561
$Installers | ForEach-Object {
561
- $installerFileName = [io.Path ]::GetFileName($_.DownloadUrl )
562
- $destination = [io.Path ]::Combine($fullCachePath , " Installers" , " Unity-$ ( $_.Version ) " , " $installerFileName " )
563
-
564
- # Already downloaded?
565
- if ( Test-Path $destination ) {
566
- $destinationItem = Get-Item $destination
567
- if ( ($destinationItem.Length -eq $_.Length ) -and
568
- ($destinationItem.LastWriteTime -eq $_.LastModified ) ) {
569
- Write-Verbose " Skipping download because it's already in the cache: $ ( $_.DownloadUrl ) "
570
-
571
- $resource = New-Object UnitySetupResource - Property @ {
572
- ' ComponentType' = $_.ComponentType
573
- ' Path' = $destination
562
+ $allInstallers += , $_
563
+ }
564
+ }
565
+ end {
566
+ $downloads = @ ()
567
+
568
+ try {
569
+ $global :downloadData = [ordered ]@ {}
570
+ $downloadIndex = 1
571
+
572
+ $allInstallers | ForEach-Object {
573
+ $installerFileName = [io.Path ]::GetFileName($_.DownloadUrl )
574
+ $destination = [io.Path ]::Combine($fullCachePath , " Installers" , " Unity-$ ( $_.Version ) " , " $installerFileName " )
575
+
576
+ # Already downloaded?
577
+ if ( Test-Path $destination ) {
578
+ $destinationItem = Get-Item $destination
579
+ if ( ($destinationItem.Length -eq $_.Length ) -and
580
+ ($destinationItem.LastWriteTime -eq $_.LastModified ) ) {
581
+ Write-Verbose " Skipping download because it's already in the cache: $ ( $_.DownloadUrl ) "
582
+
583
+ $resource = New-Object UnitySetupResource - Property @ {
584
+ ' ComponentType' = $_.ComponentType
585
+ ' Path' = $destination
586
+ }
587
+ $downloads += , $resource
588
+ return
574
589
}
575
- $downloads += , $resource
576
- return
577
590
}
578
- }
579
591
580
- $destinationDirectory = [io.path ]::GetDirectoryName($destination )
581
- if (! (Test-Path $destinationDirectory - PathType Container)) {
582
- New-Item " $destinationDirectory " - ItemType Directory | Out-Null
583
- }
592
+ $destinationDirectory = [io.path ]::GetDirectoryName($destination )
593
+ if (! (Test-Path $destinationDirectory - PathType Container)) {
594
+ New-Item " $destinationDirectory " - ItemType Directory | Out-Null
595
+ }
584
596
585
- $webClient = New-Object System.Net.WebClient
597
+ $webClient = New-Object System.Net.WebClient
598
+
599
+ ++ $downloadIndex
600
+ $global :downloadData [$installerFileName ] = New-Object PSObject - Property @ {
601
+ installerFileName = $installerFileName
602
+ startTime = Get-Date
603
+ totalBytes = $_.Length
604
+ receivedBytes = 0
605
+ isDownloaded = $false
606
+ destination = $destination
607
+ lastModified = $_.LastModified
608
+ componentType = $_.ComponentType
609
+ webClient = $webClient
610
+ downloadIndex = $downloadIndex
611
+ }
586
612
587
- # Register to events for showing progress of file download.
588
- Register-ObjectEvent - InputObject $webClient - EventName DownloadProgressChanged - SourceIdentifier DownloadProgressChanged - Action {
589
- $global :DownloadProgressEvent = $event
590
- } | Out-Null
591
- Register-ObjectEvent - InputObject $webClient - EventName DownloadFileCompleted - SourceIdentifier DownloadFileCompleted - Action {
592
- $global :isDownloaded = $true
593
- } | Out-Null
613
+ # Register to events for showing progress of file download.
614
+ Register-ObjectEvent - InputObject $webClient - EventName DownloadProgressChanged - SourceIdentifier " $installerFileName -Changed" - MessageData $installerFileName - Action {
615
+ $global :downloadData [$event.MessageData ].receivedBytes = $event.SourceArgs.BytesReceived
616
+ } | Out-Null
617
+ Register-ObjectEvent - InputObject $webClient - EventName DownloadFileCompleted - SourceIdentifier " $installerFileName -Completed" - MessageData $installerFileName - Action {
618
+ $global :downloadData [$event.MessageData ].isDownloaded = $true
619
+ } | Out-Null
620
+
621
+ try
622
+ {
623
+ Write-Verbose " Downloading $ ( $_.DownloadUrl ) to $destination "
624
+ $webClient.DownloadFileAsync ($_.DownloadUrl , $destination )
625
+ }
626
+ catch [System.Net.WebException ] {
627
+ Write-Error " Failed downloading $installerFileName - $ ( $_.Exception.Message ) "
628
+ $global :downloadData.Remove ($installerFileName )
594
629
595
- try
596
- {
597
- $startTime = Get-Date
630
+ Unregister-Event - SourceIdentifier " $installerFileName -Completed" - Force
631
+ Unregister-Event - SourceIdentifier " $installerFileName -Changed" - Force
598
632
599
- $global :DownloadProgressEvent = $null
600
- $global :isDownloaded = $false
633
+ $webClient.Dispose ()
634
+ }
635
+ }
601
636
602
- Write-Verbose " Downloading $ ( $_.DownloadUrl ) to $destination "
603
- $webClient.DownloadFileAsync ($_.DownloadUrl , $destination )
637
+ $totalDownloads = $global :downloadData.Count
604
638
605
- # Showing progress of file download
606
- $totalBytes = $_.Length
607
- while (-not $global :isDownloaded ) {
608
- if ($null -eq $global :DownloadProgressEvent ) {
609
- continue
610
- }
639
+ # Showing progress of all file downloads
640
+ while ($global :downloadData.Count -gt 0 ) {
641
+ $global :downloadData.Keys | ForEach-Object {
642
+ $installerFileName = $_
643
+ $data = $global :downloadData [$installerFileName ]
611
644
612
- $elapsedTime = (Get-Date ) - $startTime
645
+ # Finished downloading
646
+ if ($null -eq $data.webClient ) {
647
+ return
648
+ }
649
+ if ($data.isDownloaded ) {
650
+ Write-Progress - Activity " Downloading $installerFileName " - Status " Done" - Completed `
651
+ - Id $data.downloadIndex
613
652
614
- $receivedBytes = $global :DownloadProgressEvent.SourceArgs.BytesReceived
615
- $progress = [int ](($receivedBytes / [double ]$totalBytes ) * 100 )
653
+ Unregister-Event - SourceIdentifier " $installerFileName -Completed" - Force
654
+ Unregister-Event - SourceIdentifier " $installerFileName -Changed" - Force
655
+
656
+ $data.webClient.Dispose ()
657
+ $data.webClient = $null
658
+
659
+ # Re-writes the last modified time for ensuring downloads are cached properly.
660
+ $downloadedFile = Get-Item $data.destination
661
+ $downloadedFile.LastWriteTime = $data.lastModified
662
+
663
+ $resource = New-Object UnitySetupResource - Property @ {
664
+ ' ComponentType' = $data.componentType
665
+ ' Path' = $data.destination
666
+ }
667
+ $downloads += , $resource
668
+ return
669
+ }
616
670
617
- $averageSpeed = $receivedBytes / $elapsedTime.TotalSeconds
618
- $secondsRemaining = ($totalBytes - $receivedBytes ) / $averageSpeed
671
+ $elapsedTime = (Get-Date ) - $data.startTime
619
672
673
+ $progress = [int ](($data.receivedBytes / [double ]$data.totalBytes ) * 100 )
674
+
675
+ $averageSpeed = $data.receivedBytes / $elapsedTime.TotalSeconds
676
+ $secondsRemaining = ($data.totalBytes - $data.receivedBytes ) / $averageSpeed
677
+
620
678
if ([double ]::IsInfinity($secondsRemaining )) {
621
679
$averageSpeed = 0
622
680
# -1 for Write-Progress prevents seconds remaining from showing.
623
681
$secondsRemaining = -1
624
682
}
625
-
626
- $downloadSpeed = Format-BitsPerSecond - Bytes $receivedBytes - Seconds $elapsedTime.TotalSeconds
683
+
684
+ $downloadSpeed = Format-BitsPerSecond - Bytes $data . receivedBytes - Seconds $elapsedTime.TotalSeconds
627
685
628
686
Write-Progress - Activity " Downloading $installerFileName | $downloadSpeed " `
629
- - Status " $ ( $receivedBytes | Format-Bytes ) of $ ( $totalBytes | Format-Bytes ) " `
687
+ - Status " $ ( $data . receivedBytes | Format-Bytes ) of $ ( $data . totalBytes | Format-Bytes ) " `
630
688
- SecondsRemaining $secondsRemaining `
631
- - PercentComplete $progress
689
+ - PercentComplete $progress `
690
+ - Id $data.downloadIndex
632
691
}
633
692
}
634
- catch [System.Net.WebException ] {
635
- Write-Error " Failed downloading $installerFileName - $ ( $_.Exception.Message ) "
636
- }
637
- finally {
638
- Unregister-Event - SourceIdentifier DownloadFileCompleted - Force
639
- Unregister-Event - SourceIdentifier DownloadProgressChanged - Force
693
+ }
694
+ finally {
695
+ # If the script is stopped, e.g. Ctrl+C, we want to cancel any remaining downloads
696
+ $global :downloadData.Keys | ForEach-Object {
697
+ $installerFileName = $_
698
+ $data = $global :downloadData [$installerFileName ]
699
+
700
+ if ($null -ne $data.webClient ) {
701
+ if (-not $data.isDownloaded ) {
702
+ $data.webClient.CancelAsync ()
703
+ }
640
704
641
- $webClient.Dispose ()
705
+ Unregister-Event - SourceIdentifier " $installerFileName -Completed" - Force
706
+ Unregister-Event - SourceIdentifier " $installerFileName -Changed" - Force
642
707
643
- Write-Progress - Activity " Downloading $ ( $_.DownloadUrl ) " - Status " Done" - Completed
708
+ $data.webClient.Dispose ()
709
+ $data.webClient = $null
710
+ }
644
711
}
645
712
646
- # Re-writes the last modified time for ensuring downloads are cached properly.
647
- $downloadedFile = Get-Item $destination
648
- $downloadedFile.LastWriteTime = $_.LastModified
649
-
650
- $resource = New-Object UnitySetupResource - Property @ {
651
- ' ComponentType' = $_.ComponentType
652
- ' Path' = $destination
653
- }
654
- $downloads += , $resource
713
+ Remove-Variable - Name downloadData - Scope Global
655
714
}
656
- }
657
- end {
715
+
658
716
return $downloads
659
717
}
660
718
}
@@ -1109,6 +1167,14 @@ function Get-UnityProjectInstance {
1109
1167
What serial should be used by Unity for activation? Implies BatchMode and Quit if they're not supplied by the User.
1110
1168
. PARAMETER ReturnLicense
1111
1169
Unity should return the current license it's been activated with. Implies Quit if not supplied by the User.
1170
+ . PARAMETER EditorTestsCategories
1171
+ Filter tests by category names.
1172
+ . PARAMETER EditorTestsFilter
1173
+ Filter tests by test names.
1174
+ . PARAMETER EditorTestsResultFile
1175
+ Where to put the results? Unity states, "If the path is a folder, the command line uses a default file name. If not specified, it places the results in the project’s root folder."
1176
+ . PARAMETER RunEditorTests
1177
+ Should Unity run the editor tests? Unity states, "[...]it’s good practice to run it with batchmode argument. quit is not required, because the Editor automatically closes down after the run is finished."
1112
1178
. PARAMETER BatchMode
1113
1179
Should the Unity Editor start in batch mode?
1114
1180
. PARAMETER Quit
@@ -1175,6 +1241,14 @@ function Start-UnityEditor {
1175
1241
[parameter (Mandatory = $false )]
1176
1242
[switch ]$ForceFree ,
1177
1243
[parameter (Mandatory = $false )]
1244
+ [string []]$EditorTestsCategory ,
1245
+ [parameter (Mandatory = $false )]
1246
+ [string []]$EditorTestsFilter ,
1247
+ [parameter (Mandatory = $false )]
1248
+ [string ]$EditorTestsResultFile ,
1249
+ [parameter (Mandatory = $false )]
1250
+ [switch ]$RunEditorTests ,
1251
+ [parameter (Mandatory = $false )]
1178
1252
[switch ]$BatchMode ,
1179
1253
[parameter (Mandatory = $false )]
1180
1254
[switch ]$Quit ,
@@ -1266,6 +1340,10 @@ function Start-UnityEditor {
1266
1340
if ( $ExportPackage ) { $sharedArgs += " -exportPackage" , " $ExportPackage " }
1267
1341
if ( $ImportPackage ) { $sharedArgs += " -importPackage" , " $ImportPackage " }
1268
1342
if ( $Credential ) { $sharedArgs += ' -username' , $Credential.UserName }
1343
+ if ( $EditorTestsCategory ) { $sharedArgs += ' -editorTestsCategories' , ($EditorTestsCategory -join ' ,' ) }
1344
+ if ( $EditorTestsFilter ) { $sharedArgs += ' -editorTestsFilter' , ($EditorTestsFilter -join ' ,' ) }
1345
+ if ( $EditorTestsResultFile ) { $sharedArgs += ' -editorTestsResultFile' , $EditorTestsResultFile }
1346
+ if ( $RunEditorTests ) { $sharedArgs += ' -runEditorTests' }
1269
1347
if ( $ForceFree ) { $sharedArgs += ' -force-free' }
1270
1348
1271
1349
$instanceArgs = @ ()
@@ -1361,12 +1439,16 @@ function Start-UnityEditor {
1361
1439
1362
1440
$process = Start-Process @setProcessArgs
1363
1441
if ( $Wait ) {
1364
- if ( $process.ExitCode -ne 0 ) {
1365
- if ( $LogFile -and (Test-Path $LogFile - Type Leaf) ) {
1366
- Write-Verbose " Writing $LogFile to Information stream Tagged as 'Logs'"
1367
- Get-Content $LogFile | ForEach-Object { Write-Information - MessageData $_ - Tags ' Logs' }
1368
- }
1442
+ if ( $LogFile -and (Test-Path $LogFile - Type Leaf) ) {
1443
+ # Note that Unity sometimes returns a success ExitCode despite the presence of errors, but we want
1444
+ # to make sure that we flag such errors.
1445
+ Write-UnityErrors $LogFile
1446
+
1447
+ Write-Verbose " Writing $LogFile to Information stream Tagged as 'Logs'"
1448
+ Get-Content $LogFile | ForEach-Object { Write-Information - MessageData $_ - Tags ' Logs' }
1449
+ }
1369
1450
1451
+ if ( $process.ExitCode -ne 0 ) {
1370
1452
Write-Error " Unity quit with non-zero exit code: $ ( $process.ExitCode ) "
1371
1453
}
1372
1454
}
@@ -1376,6 +1458,46 @@ function Start-UnityEditor {
1376
1458
}
1377
1459
}
1378
1460
1461
+ # Open the specified Unity log file and write any errors found in the file to the error stream.
1462
+ function Write-UnityErrors {
1463
+ param ([string ] $LogFileName )
1464
+ Write-Verbose " Checking $LogFileName for errors"
1465
+ $errors = Get-Content $LogFileName | Where-Object { Get-IsUnityError $_ }
1466
+ if ( $errors.Count -gt 0 ) {
1467
+ $errors = $errors | select - uniq # Unity prints out errors as they occur and also in a summary list. We only want to see each unique error once.
1468
+ $errorMessage = $errors -join " `r`n "
1469
+ $errorMessage = " Errors were found in $LogFileName `:`r`n $errorMessage "
1470
+ Write-Error $errorMessage
1471
+ }
1472
+ }
1473
+
1474
+ function Get-IsUnityError {
1475
+ param ([string ] $LogLine )
1476
+
1477
+ # Detect Unity License error, for example:
1478
+ # BatchMode: Unity has not been activated with a valid License. Could be a new activation or renewal...
1479
+ if ( $LogLine -match ' Unity has not been activated with a valid License' ) {
1480
+ return $true
1481
+ }
1482
+
1483
+ # Detect that the method specified by -ExecuteMethod doesn't exist, for example:
1484
+ # executeMethod method 'Invoke' in class 'Build' could not be found.
1485
+ if ( $LogLine -match ' executeMethod method .* could not be found' ) {
1486
+ return $true
1487
+ }
1488
+
1489
+ # Detect compilation error, for example:
1490
+ # Assets/Errors.cs(7,9): error CS0103: The name `NonexistentFunction' does not exist in the current context
1491
+ if ( $LogLine -match ' \.cs\(\d+,\d+\): error ' ) {
1492
+ return $true
1493
+ }
1494
+
1495
+ # In the future, additional kinds of errors that can be found in Unity logs could be added here:
1496
+ # ...
1497
+
1498
+ return $false
1499
+ }
1500
+
1379
1501
function ConvertTo-DateTime {
1380
1502
param ([string ] $Text )
1381
1503
0 commit comments