1+ //===--------------- Driver.swift - Swift Driver --------------------------===//
2+ //
3+ // This source file is part of the Swift.org open source project
4+ //
5+ // Copyright (c) 2014 - 2019 Apple Inc. and the Swift project authors
6+ // Licensed under Apache License v2.0 with Runtime Library Exception
7+ //
8+ // See https://swift.org/LICENSE.txt for license information
9+ // See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+ //
11+ //===----------------------------------------------------------------------===//
112import Foundation
213
314import struct TSCBasic. AbsolutePath
@@ -11,114 +22,187 @@ import typealias TSCBasic.ProcessEnvironmentBlock
1122import func TSCBasic. getEnvSearchPaths
1223import func TSCBasic. lookupExecutablePath
1324
25+ #if os(Windows)
26+ import WinSDK
27+ #endif
28+
1429/// Check that the architecture of the toolchain matches the architecture
1530/// of the Python installation.
1631///
1732/// 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
33+ /// install an x86 version of Python, they will get a cryptic error message
1934/// when running lldb (`0xC000007B`). Calling this function before invoking
20- /// lldb gives them a warning to help troublshoot the issue.
35+ /// lldb gives them a warning to help troubleshoot the issue.
2136///
2237/// - Parameters:
2338/// - cwd: The current working directory.
24- /// - env: A dictionary of the environment variables and their values. Usually of the parent shell.
39+ /// - env: The parent shell's ProcessEnvironmentBlock .
2540/// - diagnosticsEngine: DiagnosticsEngine instance to use for printing the warning.
2641public func checkIfMatchingPythonArch(
27- cwd: AbsolutePath ? , envBlock: ProcessEnvironmentBlock , diagnosticsEngine: DiagnosticsEngine
42+ cwd: AbsolutePath ? , envBlock: ProcessEnvironmentBlock ,
43+ toolchainArchitecture: ExecutableArchitecture , diagnosticsEngine: DiagnosticsEngine
2844) {
29- #if arch(arm64)
30- let toolchainArchitecture = COFFBinaryExecutableArchitecture . arm64
31- #elseif arch(x86_64)
32- let toolchainArchitecture = COFFBinaryExecutableArchitecture . x64
33- #elseif arch(x86)
34- let toolchainArchitecture = COFFBinaryExecutableArchitecture . x86
35- #else
36- return
45+ #if os(Windows) || os(macOS)
46+ #if os(Windows)
47+ let pythonArchitecture = Process . readWindowsExecutableArchitecture (
48+ cwd: cwd, envBlock: envBlock, filename: " python.exe " )
49+ #elseif os(macOS)
50+ let pythonArchitecture = Process . readDarwinExecutableArchitecture (
51+ cwd: cwd, envBlock: envBlock, filename: " python3 " )
52+ if pythonArchitecture == . universal {
53+ return
54+ }
55+ #endif
56+
57+ guard toolchainArchitecture == pythonArchitecture else {
58+ diagnosticsEngine. emit (
59+ . warning(
60+ """
61+ There is an architecture mismatch between the installed toolchain and the resolved Python's architecture:
62+ Toolchain: \( toolchainArchitecture)
63+ Python: \( pythonArchitecture)
64+ """ ) )
65+ return
66+ }
3767 #endif
38- let pythonArchitecture = COFFBinaryExecutableArchitecture . readWindowsExecutableArchitecture (
39- cwd: cwd, envBlock: envBlock, filename: " python.exe " )
40-
41- if toolchainArchitecture != pythonArchitecture {
42- diagnosticsEngine. emit (
43- . warning(
44- """
45- There is an architecture mismatch between the installed toolchain and the resolved Python's architecture:
46- Toolchain: \( toolchainArchitecture)
47- Python: \( pythonArchitecture)
48- """ ) )
49- }
5068}
5169
5270/// Some of the architectures that can be stored in a COFF header.
53- enum COFFBinaryExecutableArchitecture : String {
71+ public enum ExecutableArchitecture : String {
5472 case x86 = " X86 "
5573 case x64 = " X64 "
5674 case arm64 = " ARM64 "
75+ case universal = " Universal "
5776 case unknown = " Unknown "
5877
59- static func fromPEMachineByte( machine: UInt16 ) -> Self {
60- // https://learn.microsoft.com/en-us/windows/win32/debug/pe-format#machine-types
61- switch machine {
62- case 0x014c : return . x86
63- case 0x8664 : return . x64
64- case 0xAA64 : return . arm64
65- default : return . unknown
66- }
67- }
68-
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
78+ #if os(Windows)
79+ static func fromPEMachineByte( machine: Int32 ) -> Self {
80+ // https://learn.microsoft.com/en-us/windows/win32/debug/pe-format#machine-types
81+ switch machine {
82+ case IMAGE_FILE_MACHINE_I386: return . x86
83+ case IMAGE_FILE_MACHINE_AMD64: return . x64
84+ case IMAGE_FILE_MACHINE_ARM64: return . arm64
85+ default : return . unknown
86+ }
8987 }
88+ #endif
9089
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
90+ #if os(macOS)
91+ static func fromMachoCPUType( cpuType: Int32 ) -> Self {
92+ // https://en.wikipedia.org/wiki/Mach-O
93+ switch cpuType {
94+ case 0x0100_0007 : return . x86
95+ case 0x0100_000c : return . arm64
96+ default : return . unknown
97+ }
10698 }
99+ #endif
100+ }
107101
108- let peHeaderOffset = offsetPointer. withUnsafeBytes { $0. load ( as: UInt32 . self) }
102+ extension Process {
103+ #if os(Windows)
104+ /// Resolves the filename from the `Path` environment variable and read its COFF header to determine the architecture
105+ /// of the binary.
106+ ///
107+ /// - Parameters:
108+ /// - cwd: The current working directory.
109+ /// - env: A dictionary of the environment variables and their values. Usually of the parent shell.
110+ /// - filename: The name of the file we are resolving the architecture of.
111+ /// - Returns: The architecture of the file which was found in the `Path`.
112+ static func readWindowsExecutableArchitecture(
113+ cwd: AbsolutePath ? , envBlock: ProcessEnvironmentBlock , filename: String
114+ ) -> ExecutableArchitecture {
115+ let searchPaths = getEnvSearchPaths (
116+ pathString: envBlock [ " Path " ] , currentWorkingDirectory: cwd)
117+ guard
118+ let filePath = lookupExecutablePath (
119+ filename: filename, currentWorkingDirectory: cwd, searchPaths: searchPaths)
120+ else {
121+ return . unknown
122+ }
123+ guard let fileHandle = FileHandle ( forReadingAtPath: filePath. pathString) else {
124+ return . unknown
125+ }
109126
110- fileHandle. seek ( toFileOffset: UInt64 ( peHeaderOffset) )
111- guard let coffHeader = try ? fileHandle. read ( upToCount: 6 ) , coffHeader. count == 6 else {
112- return . unknown
113- }
127+ defer { fileHandle. closeFile ( ) }
128+
129+ // Infering the architecture of a Windows executable from its COFF header involves the following:
130+ // 1. Get the COFF header offset from the pointer located at the 0x3C offset (4 bytes long).
131+ // 2. Jump to that offset and read the next 6 bytes.
132+ // 3. The first 4 are the signature which should be equal to 0x50450000.
133+ // 4. The last 2 are the machine architecture which can be infered from the value we get.
134+ //
135+ // The link below provides a visualization of the COFF header and the process to get to it.
136+ // https://upload.wikimedia.org/wikipedia/commons/1/1b/Portable_Executable_32_bit_Structure_in_SVG_fixed.svg
137+ guard ( try ? fileHandle. seek ( toOffset: 0x3C ) ) != nil else {
138+ return . unknown
139+ }
140+ guard let offsetPointer = try ? fileHandle. read ( upToCount: 4 ) ,
141+ offsetPointer. count == 4
142+ else {
143+ return . unknown
144+ }
145+
146+ let peHeaderOffset = offsetPointer. withUnsafeBytes { $0. load ( as: UInt32 . self) }
114147
115- let signature = coffHeader. prefix ( 4 )
116- let machineBytes = coffHeader. suffix ( 2 )
148+ guard ( try ? fileHandle. seek ( toOffset: UInt64 ( peHeaderOffset) ) ) != nil else {
149+ return . unknown
150+ }
151+ guard let coffHeader = try ? fileHandle. read ( upToCount: 6 ) , coffHeader. count == 6 else {
152+ return . unknown
153+ }
117154
118- guard signature == Data ( [ 0x50 , 0x45 , 0x00 , 0x00 ] ) else {
119- return . unknown
155+ let signature = coffHeader. prefix ( 4 )
156+ let machineBytes = coffHeader. suffix ( 2 )
157+
158+ guard signature == Data ( [ 0x50 , 0x45 , 0x00 , 0x00 ] ) else {
159+ return . unknown
160+ }
161+
162+ let machine = machineBytes. withUnsafeBytes { $0. load ( as: UInt16 . self) }
163+ return . fromPEMachineByte( machine: Int32 ( machine) )
120164 }
165+ #endif
166+
167+ #if os(macOS)
168+ static func readDarwinExecutableArchitecture(
169+ cwd: AbsolutePath ? , envBlock: ProcessEnvironmentBlock , filename: String
170+ ) -> ExecutableArchitecture {
171+ let magicNumber : UInt32 = 0xcafe_babe
172+
173+ let searchPaths = getEnvSearchPaths (
174+ pathString: envBlock [ " PATH " ] , currentWorkingDirectory: cwd)
175+ guard
176+ let filePath = lookupExecutablePath (
177+ filename: filename, currentWorkingDirectory: cwd, searchPaths: searchPaths)
178+ else {
179+ return . unknown
180+ }
181+ guard let fileHandle = FileHandle ( forReadingAtPath: filePath. pathString) else {
182+ return . unknown
183+ }
121184
122- return . fromPEMachineByte( machine: machineBytes. withUnsafeBytes { $0. load ( as: UInt16 . self) } )
123- }
185+ defer {
186+ try ? fileHandle. close ( )
187+ }
188+
189+ // The first 4 bytes of a Mach-O header contain the magic number. We use it to determine if the binary is
190+ // universal.
191+ // https://github.com/apple/darwin-xnu/blob/main/EXTERNAL_HEADERS/mach-o/loader.h
192+ let magicData = fileHandle. readData ( ofLength: 4 )
193+ let magic = magicData. withUnsafeBytes { $0. load ( as: UInt32 . self) . bigEndian }
194+
195+ if magic == magicNumber {
196+ return . universal
197+ }
198+
199+ // If the binary is not universal, the next 4 bytes contain the CPU type.
200+ guard ( try ? fileHandle. seek ( toOffset: 4 ) ) != nil else {
201+ return . unknown
202+ }
203+ let cpuTypeData = fileHandle. readData ( ofLength: 4 )
204+ let cpuType = cpuTypeData. withUnsafeBytes { $0. load ( as: Int32 . self) }
205+ return . fromMachoCPUType( cpuType: cpuType)
206+ }
207+ #endif
124208}
0 commit comments