2023-08-07 11:53:07 +02:00
Import-Module " $PSScriptRoot /Common.Helpers.psm1 "
2020-10-31 18:41:04 +03:00
Import-Module " $PSScriptRoot /Xcode.Helpers.psm1 "
function Install-XcodeVersion {
2023-12-04 12:13:08 +01:00
param (
2020-10-31 18:41:04 +03:00
[ Parameter ( Mandatory ) ]
2023-12-04 12:13:08 +01:00
[ string ] $Version ,
2020-10-31 18:41:04 +03:00
[ Parameter ( Mandatory ) ]
2023-12-15 16:44:17 +01:00
[ string ] $LinkTo ,
[ Parameter ( Mandatory ) ]
[ string ] $Sha256Sum
2020-10-31 18:41:04 +03:00
)
$xcodeDownloadDirectory = " $env:HOME /Library/Caches/XcodeInstall "
2020-11-01 11:13:45 +03:00
$xcodeTargetPath = Get-XcodeRootPath -Version $LinkTo
2021-02-18 15:08:41 +03:00
$xcodeXipDirectory = Invoke-DownloadXcodeArchive -DownloadDirectory $xcodeDownloadDirectory -Version $Version
2023-07-18 17:47:39 +02:00
Expand-XcodeXipArchive -DownloadDirectory $xcodeXipDirectory . FullName -TargetPath $xcodeTargetPath
2021-02-18 15:08:41 +03:00
Remove-Item -Path $xcodeXipDirectory -Force -Recurse
2020-10-31 18:41:04 +03:00
}
function Invoke-DownloadXcodeArchive {
2023-12-04 12:13:08 +01:00
param (
2021-02-18 15:08:41 +03:00
[ Parameter ( Mandatory ) ]
2023-12-04 12:13:08 +01:00
[ string ] $DownloadDirectory ,
2020-10-31 18:41:04 +03:00
[ Parameter ( Mandatory ) ]
2023-12-04 12:13:08 +01:00
[ string ] $Version
2020-10-31 18:41:04 +03:00
)
2023-07-06 11:36:08 +02:00
Write-Host " Downloading Xcode $Version "
$tempXipDirectory = New-Item -Path $DownloadDirectory -Name " Xcode $Version " -ItemType " Directory "
$xcodeFileName = 'Xcode-{0}.xip' -f $Version
2023-10-03 11:40:35 +02:00
$xcodeUri = '{0}{1}?{2}' -f $ { env : XCODE_INSTALL_STORAGE_URL } , $xcodeFileName , $ { env : XCODE_INSTALL_SAS }
2023-12-15 16:44:17 +01:00
$xcodeFullPath = Join-Path $tempXipDirectory . FullName $xcodeFileName
Invoke-DownloadWithRetry -Url $xcodeUri -Path $xcodeFullPath | Out-Null
2023-12-04 12:13:08 +01:00
2023-12-15 16:44:17 +01:00
# Validating checksum
$xcodeSha256 = Get-FileHash -Path $xcodeFullPath -Algorithm SHA256 | Select-Object -ExpandProperty Hash
if ( $xcodeSha256 -ne $Sha256Sum ) {
throw " Xcode $Version checksum mismatch. Expected: $Sha256Sum , Actual: $xcodeSha256 "
}
2024-01-04 10:46:00 +01:00
2021-02-18 15:08:41 +03:00
return $tempXipDirectory
2020-10-31 18:41:04 +03:00
}
function Expand-XcodeXipArchive {
2023-12-04 12:13:08 +01:00
param (
2020-10-31 18:41:04 +03:00
[ Parameter ( Mandatory ) ]
2023-12-04 12:13:08 +01:00
[ string ] $DownloadDirectory ,
2020-10-31 18:41:04 +03:00
[ Parameter ( Mandatory ) ]
2023-12-04 12:13:08 +01:00
[ string ] $TargetPath
2020-10-31 18:41:04 +03:00
)
2023-07-06 11:36:08 +02:00
$xcodeXipPath = Get-ChildItem -Path $DownloadDirectory -Filter " Xcode-*.xip " | Select-Object -First 1
2020-10-31 18:41:04 +03:00
Write-Host " Extracting Xcode from ' $xcodeXipPath ' "
Push-Location $DownloadDirectory
2023-12-04 12:13:08 +01:00
if ( [ boolean ] ( Get-Command 'unxip' -ErrorAction 'SilentlyContinue' ) ) {
2023-08-07 11:53:07 +02:00
Invoke-ValidateCommand " unxip $xcodeXipPath "
} else {
Invoke-ValidateCommand " xip -x $xcodeXipPath "
}
2020-10-31 18:41:04 +03:00
Pop-Location
if ( Test-Path " $DownloadDirectory /Xcode-beta.app " ) {
Write-Host " Renaming Xcode-beta.app to Xcode.app "
2020-11-01 11:13:45 +03:00
Rename-Item -Path " $DownloadDirectory /Xcode-beta.app " -NewName " Xcode.app "
2020-10-31 18:41:04 +03:00
}
if ( -not ( Test-Path " $DownloadDirectory /Xcode.app " ) ) {
throw " XIP archive ' $xcodeXipPath ' doesn't contain 'Xcode.app' "
}
Write-Host " Moving ' $DownloadDirectory /Xcode.app' to ' $TargetPath ' "
Move-Item -Path " $DownloadDirectory /Xcode.app " -Destination $TargetPath
}
function Confirm-XcodeIntegrity {
2023-12-04 12:13:08 +01:00
param (
2020-10-31 18:41:04 +03:00
[ Parameter ( Mandatory ) ]
2023-12-04 12:13:08 +01:00
[ string ] $Version
2020-10-31 18:41:04 +03:00
)
2020-11-01 11:13:45 +03:00
$XcodeRootPath = Get-XcodeRootPath -Version $Version
2020-10-31 18:41:04 +03:00
if ( Test-XcodeStableRelease -XcodeRootPath $XcodeRootPath ) {
2020-11-06 14:38:43 +03:00
Write-Host " Validating Xcode integrity for ' $XcodeRootPath '... "
2020-11-11 13:24:11 +03:00
Invoke-ValidateCommand " spctl --assess --raw $XcodeRootPath "
2020-10-31 18:41:04 +03:00
}
}
function Approve-XcodeLicense {
2023-12-04 12:13:08 +01:00
param (
2020-10-31 18:41:04 +03:00
[ Parameter ( Mandatory ) ]
2023-12-04 12:13:08 +01:00
[ string ] $Version
2020-10-31 18:41:04 +03:00
)
2023-08-15 15:23:02 +02:00
$os = Get-OSVersion
2020-11-06 14:38:43 +03:00
$XcodeRootPath = Get-XcodeRootPath -Version $Version
Write-Host " Approving Xcode license for ' $XcodeRootPath '... "
$xcodeBuildPath = Get-XcodeToolPath -XcodeRootPath $XcodeRootPath -ToolName " xcodebuild "
2023-08-15 15:23:02 +02:00
2026-02-06 11:24:37 +01:00
if ( $os . IsSonoma ) {
2023-08-15 15:23:02 +02:00
Invoke-ValidateCommand -Command " sudo $xcodeBuildPath -license accept " -Timeout 15
} else {
Invoke-ValidateCommand -Command " sudo $xcodeBuildPath -license accept "
}
2020-10-31 18:41:04 +03:00
}
2025-09-26 15:14:53 +02:00
function Install-XcodeAdditionalComponents {
2023-12-04 12:13:08 +01:00
param (
2020-10-31 18:41:04 +03:00
[ Parameter ( Mandatory ) ]
2023-12-04 12:13:08 +01:00
[ string ] $Version
2020-10-31 18:41:04 +03:00
)
2025-09-26 15:14:53 +02:00
Write-Host " Installing additional MetalToolchain component for Xcode $Version ... "
2020-10-31 18:41:04 +03:00
$xcodeRootPath = Get-XcodeRootPath -Version $Version
2025-09-26 15:14:53 +02:00
$xcodeBuildPath = Get-XcodeToolPath -XcodeRootPath $xcodeRootPath -ToolName " xcodebuild "
Invoke-ValidateCommand " $xcodeBuildPath -downloadComponent MetalToolchain " | Out-Null
2020-10-31 18:41:04 +03:00
}
function Invoke-XcodeRunFirstLaunch {
2023-12-04 12:13:08 +01:00
param (
2020-10-31 18:41:04 +03:00
[ Parameter ( Mandatory ) ]
2023-12-04 12:13:08 +01:00
[ string ] $Version
2020-10-31 18:41:04 +03:00
)
Write-Host " Running 'runFirstLaunch' for Xcode $Version ... "
$xcodeRootPath = Get-XcodeToolPath -Version $Version -ToolName " xcodebuild "
2020-11-11 13:24:11 +03:00
Invoke-ValidateCommand " sudo $xcodeRootPath -runFirstLaunch "
2020-10-31 18:41:04 +03:00
}
2025-09-26 15:14:53 +02:00
function Install-XcodeAdditionalSimulatorRuntimes {
2023-12-04 12:13:08 +01:00
param (
2023-03-01 10:43:47 +01:00
[ Parameter ( Mandatory ) ]
2025-01-24 10:57:28 +01:00
[ string ] $Version ,
[ Parameter ( Mandatory ) ]
2025-03-23 12:40:15 +01:00
[ string ] $Arch ,
[ Parameter ( Mandatory ) ]
[ object ] $Runtimes
2023-03-01 10:43:47 +01:00
)
2023-06-30 17:14:01 +02:00
Write-Host " Installing Simulator Runtimes for Xcode $Version ... "
2025-01-24 10:57:28 +01:00
$xcodebuildPath = Get-XcodeToolPath -Version $Version -ToolName 'xcodebuild'
2025-03-23 12:40:15 +01:00
$validRuntimes = @ ( " iOS " , " watchOS " , " tvOS " )
2026-01-19 12:47:50 +01:00
# Determine architecture variant suffix for Xcode 26+
$archSuffix = " "
if ( $Version -match '^(\d+)\.' -and [ int ] $matches [ 1 ] -ge 26 ) {
$archSuffix = " -architectureVariant universal "
}
2025-03-23 12:40:15 +01:00
# visionOS is only available on arm64
if ( $Arch -eq " arm64 " ) {
$validRuntimes + = " visionOS "
}
# Install all runtimes / skip runtimes
if ( $Runtimes -eq " default " ) {
Write-Host " Installing all runtimes for Xcode $Version ... "
2026-01-19 12:47:50 +01:00
Invoke-ValidateCommand " $xcodebuildPath -downloadAllPlatforms $archSuffix " | Out-Null
2025-03-23 12:40:15 +01:00
return
} elseif ( $Runtimes -eq " none " ) {
Write-Host " Skipping runtimes installation for Xcode $Version ... "
return
}
# Convert $Runtimes to hashtable
if ( $Runtimes -is [ System.Object[] ] ) {
$convertedRuntimes = @ { }
foreach ( $entry in $Runtimes ) {
if ( $entry -is [ PSCustomObject ] ) {
$entry = $entry | ConvertTo-Json -Compress | ConvertFrom-Json -AsHashtable
}
# Copy all keys and values from the entry to the converted runtimes
foreach ( $key in $entry . Keys ) {
if ( $convertedRuntimes . ContainsKey ( $key ) ) {
$convertedRuntimes [ $key ] + = $entry [ $key ]
} else {
$convertedRuntimes [ $key ] = $entry [ $key ]
}
}
2025-01-24 10:57:28 +01:00
}
2025-03-23 12:40:15 +01:00
$Runtimes = $convertedRuntimes
2025-01-24 10:57:28 +01:00
}
2025-03-23 12:40:15 +01:00
# Validate runtimes format
if ( $Runtimes -isnot [ System.Collections.Hashtable ] ) {
throw " Invalid runtime format for Xcode $( Version ) : Expected hashtable, got [ $( $Runtimes . GetType ( ) ) ] "
2025-01-24 10:57:28 +01:00
}
2025-03-23 12:40:15 +01:00
# Install runtimes for specified platforms
foreach ( $platform in $validRuntimes ) {
if ( -not $Runtimes . ContainsKey ( $platform ) ) {
Write-Host " No runtimes specified for $platform in the toolset for Xcode $Version , please check the toolset. "
return
}
foreach ( $platformVersion in $Runtimes [ $platform ] ) {
switch ( $platformVersion ) {
" skip " {
Write-Host " Skipping $platform runtimes installation for Xcode $Version ... "
continue
}
" default " {
Write-Host " Installing default $platform runtime for Xcode $Version ... "
2026-01-19 12:47:50 +01:00
Invoke-ValidateCommand " $xcodebuildPath -downloadPlatform $platform $archSuffix " | Out-Null
2025-03-23 12:40:15 +01:00
continue
}
default {
# Version might be a semver or a build number
if ( ( $platformVersion -match " ^\d{1,2}\.\d(\.\d)? $ " ) -or ( $platformVersion -match " ^[a-zA-Z0-9]{6,8} $ " ) ) {
Write-Host " Installing $platform $platformVersion runtime for Xcode $Version ... "
2026-01-19 12:47:50 +01:00
Invoke-ValidateCommand " $xcodebuildPath -downloadPlatform $platform -buildVersion $platformVersion $archSuffix " | Out-Null
2025-03-23 12:40:15 +01:00
continue
}
2026-01-19 12:47:50 +01:00
throw " $platformVersion is not a valid value for $platform version. Valid values are 'default', or 'skip', or a semver from 0.0 to 99.9.(9), or a build number. "
2025-03-23 12:40:15 +01:00
}
}
}
2025-01-24 10:57:28 +01:00
}
2023-03-01 10:43:47 +01:00
}
2020-10-31 18:41:04 +03:00
function Build-XcodeSymlinks {
2023-12-04 12:13:08 +01:00
param (
2020-10-31 18:41:04 +03:00
[ Parameter ( Mandatory ) ]
2023-12-04 12:13:08 +01:00
[ string ] $Version ,
[ string[] ] $Symlinks
2020-10-31 18:41:04 +03:00
)
$sourcePath = Get-XcodeRootPath -Version $Version
2020-11-02 11:19:30 +03:00
$Symlinks | Where-Object { $_ } | ForEach-Object {
2020-10-31 18:41:04 +03:00
$targetPath = Get-XcodeRootPath -Version $_
Write-Host " Creating symlink: ' $targetPath ' -> ' $sourcePath ' "
2020-11-02 18:03:20 +03:00
New-Item -Path $targetPath -ItemType SymbolicLink -Value $sourcePath | Out-Null
2020-10-31 18:41:04 +03:00
}
}
2023-12-04 12:13:08 +01:00
function Initialize-XcodeLaunchServicesDb {
param (
2021-12-01 12:58:26 +05:00
[ Parameter ( Mandatory ) ]
2023-12-04 12:13:08 +01:00
[ string ] $Version
2021-12-01 12:58:26 +05:00
)
$xcodePath = Get-XcodeRootPath -Version $Version
$lsregister = '/System/Library/Frameworks/CoreServices.framework/Frameworks/LaunchServices.framework/Support/lsregister'
Get-ChildItem -Recurse -Filter " *.app " $xcodePath | Foreach-Object { & $lsregister -f $_ . FullName }
}
2020-10-31 18:41:04 +03:00
function Build-ProvisionatorSymlink {
2023-12-04 12:13:08 +01:00
param (
2020-10-31 18:41:04 +03:00
[ Parameter ( Mandatory ) ]
2023-12-04 12:13:08 +01:00
[ string ] $Version
2020-10-31 18:41:04 +03:00
)
$sourcePath = Get-XcodeRootPath -Version $Version
$versionInfo = Get-XcodeVersionInfo -XcodeRootPath $sourcePath
$targetVersion = [ SemVer ] :: Parse ( $versionInfo . Version ) . ToString ( )
$targetPath = Get-XcodeRootPath -Version $targetVersion
if ( $sourcePath -ne $targetPath ) {
2020-11-02 21:35:36 +03:00
Write-Host " Creating symlink: ' $targetPath ' -> ' $sourcePath ' "
2020-11-02 18:03:20 +03:00
New-Item -Path $targetPath -ItemType SymbolicLink -Value $sourcePath | Out-Null
2020-10-31 18:41:04 +03:00
}
}
function Set-XcodeDeveloperDirEnvironmentVariables {
2023-12-04 12:13:08 +01:00
param (
2020-11-01 11:13:45 +03:00
[ Parameter ( Mandatory ) ]
2023-12-04 12:13:08 +01:00
[ string[] ] $XcodeList
2020-11-01 11:13:45 +03:00
)
$exactVersionsList = $XcodeList | Where-Object { Test-XcodeStableRelease -Version $_ } | ForEach-Object {
$xcodeRootPath = Get-XcodeRootPath -Version $_
$xcodeVersionInfo = Get-XcodeVersionInfo -XcodeRootPath $xcodeRootPath
return @ {
RootPath = $xcodeRootPath
Version = [ SemVer ] :: Parse ( $xcodeVersionInfo . Version )
}
} | Sort-Object -Property Version -Descending
$majorVersions = $exactVersionsList . Version . Major | Select-Object -Unique
$majorVersions | ForEach-Object {
2020-11-02 18:03:20 +03:00
$majorVersion = $_
$latestXcodeVersion = $exactVersionsList | Where-Object { $_ . Version . Major -eq $majorVersion } | Select-Object -First 1
2020-11-01 11:13:45 +03:00
$variableName = " XCODE_ ${_} _DEVELOPER_DIR "
$variableValue = " $( $latestXcodeVersion . RootPath ) /Contents/Developer "
Write-Host " Set ${variableName} = ${variableValue} "
" export ${variableName} = ${variableValue} " | Out-File " $env:HOME /.bashrc " -Append
}
2021-12-01 12:58:26 +05:00
}
2023-12-04 12:13:08 +01:00
function Invoke-ValidateCommand {
param (
[ Parameter ( Mandatory ) ]
[ string ] $Command ,
[ Uint ] $Timeout = 0
)
if ( $Timeout -eq 0 ) {
$output = Invoke-Expression -Command $Command
if ( $LASTEXITCODE -ne 0 ) {
throw " Command ' $Command ' has finished with exit code $LASTEXITCODE "
}
return $output
} else {
$job = $command | Start-Job -ScriptBlock {
$output = Invoke-Expression -Command $input
if ( $LASTEXITCODE -ne 0 ) {
throw 'Command failed'
}
return $output
}
$waitObject = $job | Wait-Job -Timeout $Timeout
if ( -not $waitObject ) {
throw " Command ' $Command ' has timed out "
}
if ( $waitObject . State -eq 'Failed' ) {
throw " Command ' $Command ' has failed "
}
Receive-Job -Job $job
}
}
2025-10-09 12:06:17 +02:00
function Update-DyldCache {
param (
[ Parameter ( Mandatory ) ]
2025-11-19 19:06:51 +01:00
[ string ] $Version
2025-10-09 12:06:17 +02:00
)
2025-11-19 19:06:51 +01:00
Write-Host " Updating dyld shared cache for Xcode $Version ... "
Switch-Xcode -Version $Version
Invoke-ValidateCommand " xcrun simctl runtime dyld_shared_cache update --all "
2025-10-09 12:06:17 +02:00
}