@@ -15,28 +15,40 @@ import func TSCBasic.lookupExecutablePath
15
15
/// of the Python installation.
16
16
///
17
17
/// When installing the x86 toolchain on ARM64 Windows, if the user does not
18
- /// install an x86 version of Python, they will get acryptic error message
18
+ /// install an x86 version of Python, they will get a cryptic error message
19
19
/// when running lldb (`0xC000007B`). Calling this function before invoking
20
- /// lldb gives them a warning to help troublshoot the issue.
20
+ /// lldb gives them a warning to help troubleshoot the issue.
21
21
///
22
22
/// - Parameters:
23
23
/// - cwd: The current working directory.
24
- /// - env: A dictionary of the environment variables and their values. Usually of the parent shell.
24
+ /// - env: The parent shell's ProcessEnvironmentBlock .
25
25
/// - diagnosticsEngine: DiagnosticsEngine instance to use for printing the warning.
26
26
public func checkIfMatchingPythonArch(
27
27
cwd: AbsolutePath ? , envBlock: ProcessEnvironmentBlock , diagnosticsEngine: DiagnosticsEngine
28
28
) {
29
29
#if arch(arm64)
30
- let toolchainArchitecture = COFFBinaryExecutableArchitecture . arm64
30
+ let toolchainArchitecture = ExecutableArchitecture . arm64
31
31
#elseif arch(x86_64)
32
- let toolchainArchitecture = COFFBinaryExecutableArchitecture . x64
32
+ let toolchainArchitecture = ExecutableArchitecture . x64
33
33
#elseif arch(x86)
34
- let toolchainArchitecture = COFFBinaryExecutableArchitecture . x86
34
+ let toolchainArchitecture = ExecutableArchitecture . x86
35
35
#else
36
36
return
37
37
#endif
38
- let pythonArchitecture = COFFBinaryExecutableArchitecture . readWindowsExecutableArchitecture (
39
- cwd: cwd, envBlock: envBlock, filename: " python.exe " )
38
+
39
+ #if os(Windows)
40
+ let pythonArchitecture = ExecutableArchitecture . readWindowsExecutableArchitecture (
41
+ cwd: cwd, envBlock: envBlock, filename: " python.exe " )
42
+ #elseif os(macOS)
43
+ let pythonArchitecture = ExecutableArchitecture . readDarwinExecutableArchitecture (
44
+ cwd: cwd, envBlock: envBlock, filename: " python3 " )
45
+ #else
46
+ return
47
+ #endif
48
+
49
+ if pythonArchitecture == . universal {
50
+ return
51
+ }
40
52
41
53
if toolchainArchitecture != pythonArchitecture {
42
54
diagnosticsEngine. emit (
@@ -50,10 +62,11 @@ public func checkIfMatchingPythonArch(
50
62
}
51
63
52
64
/// Some of the architectures that can be stored in a COFF header.
53
- enum COFFBinaryExecutableArchitecture : String {
65
+ enum ExecutableArchitecture : String {
54
66
case x86 = " X86 "
55
67
case x64 = " X64 "
56
68
case arm64 = " ARM64 "
69
+ case universal = " Universal "
57
70
case unknown = " Unknown "
58
71
59
72
static func fromPEMachineByte( machine: UInt16 ) -> Self {
@@ -66,59 +79,111 @@ enum COFFBinaryExecutableArchitecture: String {
66
79
}
67
80
}
68
81
69
- /// Resolves the filename from the `Path` environment variable and read its COFF header to determine the architecture
70
- /// of the binary.
71
- ///
72
- /// - Parameters:
73
- /// - cwd: The current working directory.
74
- /// - env: A dictionary of the environment variables and their values. Usually of the parent shell.
75
- /// - filename: The name of the file we are resolving the architecture of.
76
- /// - Returns: The architecture of the file which was found in the `Path`.
77
- static func readWindowsExecutableArchitecture(
78
- cwd: AbsolutePath ? , envBlock: ProcessEnvironmentBlock , filename: String
79
- ) -> Self {
80
- let searchPaths = getEnvSearchPaths ( pathString: envBlock [ " Path " ] , currentWorkingDirectory: cwd)
81
- guard
82
- let filePath = lookupExecutablePath (
83
- filename: filename, currentWorkingDirectory: cwd, searchPaths: searchPaths)
84
- else {
85
- return . unknown
86
- }
87
- guard let fileHandle = FileHandle ( forReadingAtPath: filePath. pathString) else {
88
- return . unknown
82
+ static func fromMachoCPUType( cpuType: Int32 ) -> Self {
83
+ // https://en.wikipedia.org/wiki/Mach-O
84
+ switch cpuType {
85
+ case 0x0100_0007 : return . x86
86
+ case 0x0100_000c : return . arm64
87
+ default : return . unknown
89
88
}
89
+ }
90
90
91
- defer { fileHandle. closeFile ( ) }
92
-
93
- // Infering the architecture of a Windows executable from its COFF header involves the following:
94
- // 1. Get the COFF header offset from the pointer located at the 0x3C offset (4 bytes long).
95
- // 2. Jump to that offset and read the next 6 bytes.
96
- // 3. The first 4 are the signature which should be equal to 0x50450000.
97
- // 4. The last 2 are the machine architecture which can be infered from the value we get.
98
- //
99
- // The link below provides a visualization of the COFF header and the process to get to it.
100
- // https://upload.wikimedia.org/wikipedia/commons/1/1b/Portable_Executable_32_bit_Structure_in_SVG_fixed.svg
101
- fileHandle. seek ( toFileOffset: 0x3C )
102
- guard let offsetPointer = try ? fileHandle. read ( upToCount: 4 ) ,
103
- offsetPointer. count == 4
104
- else {
105
- return . unknown
106
- }
91
+ #if os(Windows)
92
+ /// Resolves the filename from the `Path` environment variable and read its COFF header to determine the architecture
93
+ /// of the binary.
94
+ ///
95
+ /// - Parameters:
96
+ /// - cwd: The current working directory.
97
+ /// - env: A dictionary of the environment variables and their values. Usually of the parent shell.
98
+ /// - filename: The name of the file we are resolving the architecture of.
99
+ /// - Returns: The architecture of the file which was found in the `Path`.
100
+ static func readWindowsExecutableArchitecture(
101
+ cwd: AbsolutePath ? , envBlock: ProcessEnvironmentBlock , filename: String
102
+ ) -> Self {
103
+ let searchPaths = getEnvSearchPaths (
104
+ pathString: envBlock [ " Path " ] , currentWorkingDirectory: cwd)
105
+ guard
106
+ let filePath = lookupExecutablePath (
107
+ filename: filename, currentWorkingDirectory: cwd, searchPaths: searchPaths)
108
+ else {
109
+ return . unknown
110
+ }
111
+ guard let fileHandle = FileHandle ( forReadingAtPath: filePath. pathString) else {
112
+ return . unknown
113
+ }
107
114
108
- let peHeaderOffset = offsetPointer . withUnsafeBytes { $0 . load ( as : UInt32 . self ) }
115
+ defer { fileHandle . closeFile ( ) }
109
116
110
- fileHandle. seek ( toFileOffset: UInt64 ( peHeaderOffset) )
111
- guard let coffHeader = try ? fileHandle. read ( upToCount: 6 ) , coffHeader. count == 6 else {
112
- return . unknown
113
- }
117
+ // Infering the architecture of a Windows executable from its COFF header involves the following:
118
+ // 1. Get the COFF header offset from the pointer located at the 0x3C offset (4 bytes long).
119
+ // 2. Jump to that offset and read the next 6 bytes.
120
+ // 3. The first 4 are the signature which should be equal to 0x50450000.
121
+ // 4. The last 2 are the machine architecture which can be infered from the value we get.
122
+ //
123
+ // The link below provides a visualization of the COFF header and the process to get to it.
124
+ // https://upload.wikimedia.org/wikipedia/commons/1/1b/Portable_Executable_32_bit_Structure_in_SVG_fixed.svg
125
+ fileHandle. seek ( toFileOffset: 0x3C )
126
+ guard let offsetPointer = try ? fileHandle. read ( upToCount: 4 ) ,
127
+ offsetPointer. count == 4
128
+ else {
129
+ return . unknown
130
+ }
114
131
115
- let signature = coffHeader. prefix ( 4 )
116
- let machineBytes = coffHeader. suffix ( 2 )
132
+ let peHeaderOffset = offsetPointer. withUnsafeBytes { $0. load ( as: UInt32 . self) }
117
133
118
- guard signature == Data ( [ 0x50 , 0x45 , 0x00 , 0x00 ] ) else {
119
- return . unknown
134
+ fileHandle. seek ( toFileOffset: UInt64 ( peHeaderOffset) )
135
+ guard let coffHeader = try ? fileHandle. read ( upToCount: 6 ) , coffHeader. count == 6 else {
136
+ return . unknown
137
+ }
138
+
139
+ let signature = coffHeader. prefix ( 4 )
140
+ let machineBytes = coffHeader. suffix ( 2 )
141
+
142
+ guard signature == Data ( [ 0x50 , 0x45 , 0x00 , 0x00 ] ) else {
143
+ return . unknown
144
+ }
145
+
146
+ return . fromPEMachineByte( machine: machineBytes. withUnsafeBytes { $0. load ( as: UInt16 . self) } )
120
147
}
148
+ #endif
121
149
122
- return . fromPEMachineByte( machine: machineBytes. withUnsafeBytes { $0. load ( as: UInt16 . self) } )
123
- }
150
+ #if os(macOS)
151
+ static func readDarwinExecutableArchitecture(
152
+ cwd: AbsolutePath ? , envBlock: ProcessEnvironmentBlock , filename: String
153
+ ) -> Self {
154
+ let magicNumber : UInt32 = 0xcafe_babe
155
+
156
+ let searchPaths = getEnvSearchPaths (
157
+ pathString: envBlock [ " PATH " ] , currentWorkingDirectory: cwd)
158
+ guard
159
+ let filePath = lookupExecutablePath (
160
+ filename: filename, currentWorkingDirectory: cwd, searchPaths: searchPaths)
161
+ else {
162
+ return . unknown
163
+ }
164
+ guard let fileHandle = FileHandle ( forReadingAtPath: filePath. pathString) else {
165
+ return . unknown
166
+ }
167
+
168
+ defer {
169
+ try ? fileHandle. close ( )
170
+ }
171
+
172
+ // The first 4 bytes of a Mach-O header contain the magic number. We use it to determine if the binary is
173
+ // universal.
174
+ // https://github.com/apple/darwin-xnu/blob/main/EXTERNAL_HEADERS/mach-o/loader.h
175
+ let magicData = fileHandle. readData ( ofLength: 4 )
176
+ let magic = magicData. withUnsafeBytes { $0. load ( as: UInt32 . self) . bigEndian }
177
+
178
+ if magic == magicNumber {
179
+ return . universal
180
+ }
181
+
182
+ // If the binary is not universal, the next 4 bytes contain the CPU type.
183
+ fileHandle. seek ( toFileOffset: 4 )
184
+ let cpuTypeData = fileHandle. readData ( ofLength: 4 )
185
+ let cpuType = cpuTypeData. withUnsafeBytes { $0. load ( as: Int32 . self) }
186
+ return Self . fromMachoCPUType ( cpuType: cpuType)
187
+ }
188
+ #endif
124
189
}
0 commit comments