Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support building free-threaded CPython #319

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 41 additions & 23 deletions .github/workflows/build-python-packages.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@ on:
required: true
type: boolean
default: false
THREADING_BUILD_MODES:
description: 'CPython threading build modes'
required: true
type: str
default: 'default,freethreaded'
PLATFORMS:
description: 'Platforms for execution in "os" or "os_arch" format (arch is "x64" by default)'
required: true
Expand Down Expand Up @@ -40,32 +45,42 @@ jobs:
id: generate-matrix
run: |
[String[]]$configurations = "${{ inputs.platforms || 'ubuntu-20.04,ubuntu-22.04,ubuntu-22.04_arm64,ubuntu-24.04,ubuntu-24.04_arm64,macos-13,macos-14_arm64,windows-2019_x64,windows-2019_x86,windows-2019_arm64' }}".Split(",").Trim()
[String[]]$buildModes = "${{ inputs.threading_build_modes || 'default' }}".Split(",").Trim()
$matrix = @()

foreach ($configuration in $configurations) {
$parts = $configuration.Split("_")
$os = $parts[0]
$arch = if ($parts[1]) {$parts[1]} else {"x64"}
switch -wildcard ($os) {
"*ubuntu*" { $platform = $os.Replace("ubuntu","linux")}
"*macos*" { $platform = 'darwin' }
"*windows*" { $platform = 'win32' }
}

if ($configuration -eq "ubuntu-22.04_arm64") {
$os = "setup-actions-ubuntu-arm64-2-core"
}
elseif ($configuration -eq "ubuntu-24.04_arm64") {
$os = "setup-actions-ubuntu24-arm64-2-core"
}
elseif ($configuration -eq "windows-2019_arm64") {
$os = "setup-actions-windows-arm64-4-core"
}

$matrix += @{
'platform' = $platform
'os' = $os
'arch' = $arch
foreach ($buildMode in $buildModes) {
$parts = $configuration.Split("_")
$os = $parts[0]
$arch = if ($parts[1]) {$parts[1]} else {"x64"}
switch -wildcard ($os) {
"*ubuntu*" { $platform = $os.Replace("ubuntu","linux")}
"*macos*" { $platform = 'darwin' }
"*windows*" { $platform = 'win32' }
}

if ($configuration -eq "ubuntu-22.04_arm64") {
$os = "setup-actions-ubuntu-arm64-2-core"
}
elseif ($configuration -eq "ubuntu-24.04_arm64") {
$os = "setup-actions-ubuntu24-arm64-2-core"
}
elseif ($configuration -eq "windows-2019_arm64") {
$os = "setup-actions-windows-arm64-4-core"
}

if ($buildMode -eq "freethreaded") {
if ([semver]"${{ inputs.VERSION }}" -lt [semver]"3.13.0") {
continue;
}
$arch += "-freethreaded"
}

$matrix += @{
'platform' = $platform
'os' = $os
'arch' = $arch
}
}
}
echo "matrix=$($matrix | ConvertTo-Json -Compress -AsArray)" >> $env:GITHUB_OUTPUT
Expand Down Expand Up @@ -201,6 +216,9 @@ jobs:
python-version: ${{ env.VERSION }}
architecture: ${{ matrix.arch }}

- name: Python version
run: python -VVV

- name: Verbose sysconfig dump
if: runner.os == 'Linux' || runner.os == 'macOS'
run: python ./sources/python-config-output.py
Expand Down
33 changes: 33 additions & 0 deletions builders/macos-python-builder.psm1
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,37 @@ class macOSPythonBuilder : NixPythonBuilder {
return $pkgLocation
}

[string] GetFrameworkName() {
<#
.SYNOPSIS
Get the Python installation Package name.
#>

if ($this.IsFreeThreaded()) {
return "PythonT.framework"
} else {
return "Python.framework"
}
}

[string] GetPkgChoices() {
<#
.SYNOPSIS
Reads the configuration XML file for the Python installer
#>

$config = if ($this.IsFreeThreaded()) { "freethreaded" } else { "default" }
$choicesFile = Join-Path $PSScriptRoot "../config/macos-pkg-choices-$($config).xml"
$choicesTemplate = Get-Content -Path $choicesFile -Raw

$variablesToReplace = @{
"{{__VERSION_MAJOR_MINOR__}}" = "$($this.Version.Major).$($this.Version.Minor)";
}

$variablesToReplace.keys | ForEach-Object { $choicesTemplate = $choicesTemplate.Replace($_, $variablesToReplace[$_]) }
return $choicesTemplate
}

[void] CreateInstallationScriptPkg() {
<#
.SYNOPSIS
Expand All @@ -165,6 +196,8 @@ class macOSPythonBuilder : NixPythonBuilder {
"{{__VERSION_FULL__}}" = $this.Version;
"{{__PKG_NAME__}}" = $this.GetPkgName();
"{{__ARCH__}}" = $this.Architecture;
"{{__FRAMEWORK_NAME__}}" = $this.GetFrameworkName();
"{{__PKG_CHOICES__}}" = $this.GetPkgChoices();
}

$variablesToReplace.keys | ForEach-Object { $installationTemplateContent = $installationTemplateContent.Replace($_, $variablesToReplace[$_]) }
Expand Down
2 changes: 1 addition & 1 deletion builders/nix-python-builder.psm1
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ class NixPythonBuilder : PythonBuilder {
Write-Debug "make Python $($this.Version)-$($this.Architecture) $($this.Platform)"
$buildOutputLocation = New-Item -Path $this.WorkFolderLocation -Name "build_output.txt" -ItemType File

Execute-Command -Command "make 2>&1 | tee $buildOutputLocation" -ErrorAction Continue
Execute-Command -Command "make 2>&1 | tee $buildOutputLocation" -ErrorAction Continue
Execute-Command -Command "make install" -ErrorAction Continue

Write-Debug "Done; Make log location: $buildOutputLocation"
Expand Down
18 changes: 18 additions & 0 deletions builders/python-builder.psm1
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,24 @@ class PythonBuilder {
return "$($this.Version.Major).$($this.Version.Minor).$($this.Version.Patch)"
}

[string] GetHardwareArchitecture() {
<#
.SYNOPSIS
The hardware architecture (x64, arm64) without any Python free threading suffix.
#>

return $this.Architecture.Replace("-freethreaded", "")
}

[bool] IsFreeThreaded() {
<#
.SYNOPSIS
Check if Python version is free threaded.
#>

return $this.Architecture.EndsWith("-freethreaded")
}

[void] PreparePythonToolcacheLocation() {
<#
.SYNOPSIS
Expand Down
8 changes: 8 additions & 0 deletions builders/ubuntu-python-builder.psm1
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,14 @@ class UbuntuPythonBuilder : NixPythonBuilder {
$configureString += " --enable-shared"
$configureString += " --enable-optimizations"

if ($this.IsFreeThreaded()) {
if ($this.Version -lt "3.13.0") {
Write-Host "Python versions lower than 3.13.0 do not support free threading"
exit 1
}
$configureString += " --disable-gil"
}

### Compile with support of loadable sqlite extensions.
### Link to documentation (https://docs.python.org/3/library/sqlite3.html#sqlite3.Connection.enable_load_extension)
$configureString += " --enable-loadable-sqlite-extensions"
Expand Down
5 changes: 3 additions & 2 deletions builders/win-python-builder.psm1
Original file line number Diff line number Diff line change
Expand Up @@ -54,13 +54,13 @@ class WinPythonBuilder : PythonBuilder {
#>

$ArchitectureExtension = ""
if ($this.Architecture -eq "x64") {
if ($this.GetHardwareArchitecture() -eq "x64") {
if ($this.Version -ge "3.5") {
$ArchitectureExtension = "-amd64"
} else {
$ArchitectureExtension = ".amd64"
}
}elseif ($this.Architecture -eq "arm64") {
} elseif ($this.GetHardwareArchitecture() -eq "arm64") {
$ArchitectureExtension = "-arm64"
}

Expand Down Expand Up @@ -113,6 +113,7 @@ class WinPythonBuilder : PythonBuilder {

$variablesToReplace = @{
"{{__ARCHITECTURE__}}" = $this.Architecture;
"{{__HARDWARE_ARCHITECTURE__}}" = $this.GetHardwareArchitecture();
"{{__VERSION__}}" = $this.Version;
"{{__PYTHON_EXEC_NAME__}}" = $pythonExecName
}
Expand Down
8 changes: 8 additions & 0 deletions config/macos-pkg-choices-default.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<array>
<dict>
</dict>
</array>
</plist>
14 changes: 14 additions & 0 deletions config/macos-pkg-choices-freethreaded.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<array>
<dict>
<key>attributeSetting</key>
<integer>1</integer>
<key>choiceAttribute</key>
<string>selected</string>
<key>choiceIdentifier</key>
<string>org.python.Python.PythonTFramework-{{__VERSION_MAJOR_MINOR__}}</string>
</dict>
</array>
</plist>
2 changes: 1 addition & 1 deletion config/python-manifest-config.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"regex": "python-\\d+\\.\\d+\\.\\d+-(\\w+\\.\\d+)?-?(\\w+)-(\\d+\\.\\d+)?-?((x|arm)\\d+)",
"regex": "python-\\d+\\.\\d+\\.\\d+-(\\w+\\.\\d+)?-?(\\w+)-(\\d+\\.\\d+)?-?((x|arm)\\d+(-freethreaded)?)",
"groups": {
"arch": 4,
"platform": 2,
Expand Down
20 changes: 17 additions & 3 deletions installers/macos-pkg-setup-template.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@ set -e

PYTHON_FULL_VERSION="{{__VERSION_FULL__}}"
PYTHON_PKG_NAME="{{__PKG_NAME__}}"
PYTHON_FRAMEWORK_NAME="{{__FRAMEWORK_NAME__}}"
PYTHON_PKG_CHOICES=$(cat << 'EOF'
{{__PKG_CHOICES__}}
EOF
)
ARCH="{{__ARCH__}}"
MAJOR_VERSION=$(echo $PYTHON_FULL_VERSION | cut -d '.' -f 1)
MINOR_VERSION=$(echo $PYTHON_FULL_VERSION | cut -d '.' -f 2)
Expand All @@ -20,7 +25,7 @@ fi
PYTHON_TOOLCACHE_PATH=$TOOLCACHE_ROOT/Python
PYTHON_TOOLCACHE_VERSION_PATH=$PYTHON_TOOLCACHE_PATH/$PYTHON_FULL_VERSION
PYTHON_TOOLCACHE_VERSION_ARCH_PATH=$PYTHON_TOOLCACHE_VERSION_PATH/$ARCH
PYTHON_FRAMEWORK_PATH="/Library/Frameworks/Python.framework/Versions/${MAJOR_VERSION}.${MINOR_VERSION}"
PYTHON_FRAMEWORK_PATH="/Library/Frameworks/${PYTHON_FRAMEWORK_NAME}/Versions/${MAJOR_VERSION}.${MINOR_VERSION}"
PYTHON_APPLICATION_PATH="/Applications/Python ${MAJOR_VERSION}.${MINOR_VERSION}"

echo "Check if Python hostedtoolcache folder exist..."
Expand All @@ -38,8 +43,11 @@ else
done
fi

PYTHON_PKG_CHOICES_FILES=$(mktemp)
echo "$PYTHON_PKG_CHOICES" > $PYTHON_PKG_CHOICES_FILES

echo "Install Python binaries from prebuilt package"
sudo installer -pkg $PYTHON_PKG_NAME -target /
sudo installer -pkg $PYTHON_PKG_NAME -applyChoiceChangesXML $PYTHON_PKG_CHOICES_FILES -target /

echo "Create hostedtoolcach symlinks (Required for the backward compatibility)"
echo "Create Python $PYTHON_FULL_VERSION folder"
Expand All @@ -53,7 +61,9 @@ ln -s "${PYTHON_FRAMEWORK_PATH}/lib" lib

echo "Create additional symlinks (Required for the UsePythonVersion Azure Pipelines task and the setup-python GitHub Action)"
ln -s ./bin/$PYTHON_MAJOR_DOT_MINOR python
chmod +x python

# Note that bin is a symlink so referencing .. from bin will not work as expected
cd bin/

# This symlink already exists if Python version with the same major.minor version is installed,
Expand All @@ -62,11 +72,15 @@ if [ ! -f $PYTHON_MAJOR_MINOR ]; then
ln -s $PYTHON_MAJOR_DOT_MINOR $PYTHON_MAJOR_MINOR
fi

if [ ! -f $PYTHON_MAJOR ]; then
ln -s $PYTHON_MAJOR_DOT_MINOR $PYTHON_MAJOR
fi

if [ ! -f python ]; then
ln -s $PYTHON_MAJOR_DOT_MINOR python
fi

chmod +x ../python $PYTHON_MAJOR $PYTHON_MAJOR_DOT_MINOR $PYTHON_MAJOR_MINOR python
chmod +x $PYTHON_MAJOR $PYTHON_MAJOR_DOT_MINOR $PYTHON_MAJOR_MINOR python

echo "Upgrading pip..."
export PIP_ROOT_USER_ACTION=ignore
Expand Down
21 changes: 18 additions & 3 deletions installers/win-setup-template.ps1
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
[String] $Architecture = "{{__ARCHITECTURE__}}"
[String] $HardwareArchitecture = "{{__HARDWARE_ARCHITECTURE__}}"
[String] $Version = "{{__VERSION__}}"
[String] $PythonExecName = "{{__PYTHON_EXEC_NAME__}}"

Expand All @@ -25,7 +26,7 @@ function Remove-RegistryEntries {
[Parameter(Mandatory)][Int32] $MinorVersion
)

$versionFilter = Get-RegistryVersionFilter -Architecture $Architecture -MajorVersion $MajorVersion -MinorVersion $MinorVersion
$versionFilter = Get-RegistryVersionFilter -Architecture $HardwareArchitecture -MajorVersion $MajorVersion -MinorVersion $MinorVersion

$regPath = "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Products"
if (Test-Path -Path Registry::$regPath) {
Expand Down Expand Up @@ -61,13 +62,15 @@ function Remove-RegistryEntries {
function Get-ExecParams {
param(
[Parameter(Mandatory)][Boolean] $IsMSI,
[Parameter(Mandatory)][Boolean] $IsFreeThreaded,
[Parameter(Mandatory)][String] $PythonArchPath
)

if ($IsMSI) {
"TARGETDIR=$PythonArchPath ALLUSERS=1"
} else {
"DefaultAllUsersTargetDir=$PythonArchPath InstallAllUsers=1"
$Include_freethreaded = if ($IsFreeThreaded) { "Include_freethreaded=1" } else { "" }
"DefaultAllUsersTargetDir=$PythonArchPath InstallAllUsers=1 $Include_freethreaded"
}
}

Expand All @@ -81,6 +84,7 @@ $PythonVersionPath = Join-Path -Path $PythonToolcachePath -ChildPath $Version
$PythonArchPath = Join-Path -Path $PythonVersionPath -ChildPath $Architecture

$IsMSI = $PythonExecName -match "msi"
$IsFreeThreaded = $Architecture -match "-freethreaded"

$MajorVersion = $Version.Split('.')[0]
$MinorVersion = $Version.Split('.')[1]
Expand Down Expand Up @@ -120,13 +124,24 @@ Write-Host "Copy Python binaries to $PythonArchPath"
Copy-Item -Path ./$PythonExecName -Destination $PythonArchPath | Out-Null

Write-Host "Install Python $Version in $PythonToolcachePath..."
$ExecParams = Get-ExecParams -IsMSI $IsMSI -PythonArchPath $PythonArchPath
$ExecParams = Get-ExecParams -IsMSI $IsMSI -IsFreeThreaded $IsFreeThreaded -PythonArchPath $PythonArchPath

cmd.exe /c "cd $PythonArchPath && call $PythonExecName $ExecParams /quiet"
if ($LASTEXITCODE -ne 0) {
Throw "Error happened during Python installation"
}

# print out all files in $PythonArchPath
Write-Host "Files in $PythonArchPath"
$files = Get-ChildItem -Path $PythonArchPath -File -Recurse
Write-Output $files

if ($IsFreeThreaded) {
# Delete python.exe and create a symlink to free-threaded exe
Remove-Item -Path "$PythonArchPath\python.exe" -Force
New-Item -Path "$PythonArchPath\python.exe" -ItemType SymbolicLink -Value "$PythonArchPath\python${MajorVersion}.${MinorVersion}t.exe"
}

Write-Host "Create `python3` symlink"
if ($MajorVersion -ne "2") {
New-Item -Path "$PythonArchPath\python3.exe" -ItemType SymbolicLink -Value "$PythonArchPath\python.exe"
Expand Down
4 changes: 3 additions & 1 deletion tests/python-tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ param (
$Architecture
)

$HardwareArchitecture = $Architecture -replace "-freethreaded", ""

Import-Module (Join-Path $PSScriptRoot "../helpers/pester-extensions.psm1")
Import-Module (Join-Path $PSScriptRoot "../helpers/common-helpers.psm1")
Import-Module (Join-Path $PSScriptRoot "../builders/python-version.psm1")
Expand Down Expand Up @@ -58,7 +60,7 @@ Describe "Tests" {
# }
# }

if (($Version -ge "3.2.0") -and ($Version -lt "3.11.0") -and (($Platform -ne "darwin") -or ($Architecture -ne "arm64"))) {
if (($Version -ge "3.2.0") -and ($Version -lt "3.11.0") -and (($Platform -ne "darwin") -or ($HardwareArchitecture -ne "arm64"))) {
It "Check if sqlite3 module is installed" {
"python ./sources/python-sqlite3.py" | Should -ReturnZeroExitCode
}
Expand Down
Loading