fix(idf_tools): Fix platform detection for arm64 machine but arm32 environment

This commit is contained in:
Anton Maklakov 2023-12-18 20:01:27 +07:00
parent da9536dbab
commit 04b77f56f5
5 changed files with 65 additions and 17 deletions

View File

@ -165,6 +165,42 @@ class Platforms:
'Linux-arm': PLATFORM_LINUX_ARM32,
}
@staticmethod
def detect_linux_arm_platform(supposed_platform): # type: (Optional[str]) -> Optional[str]
"""
We probe the python binary to check exactly what environment the script is running in.
ARM platform may run on armhf hardware but having armel installed packages.
To avoid possible armel/armhf libraries mixing need to define user's
packages architecture to use the same
See note section in https://gcc.gnu.org/onlinedocs/gcc/ARM-Options.html#index-mfloat-abi
ARM platform may run on aarch64 hardware but having armhf installed packages
(it happens if a docker container is running on arm64 hardware, but using an armhf image).
"""
if supposed_platform not in (PLATFORM_LINUX_ARM32, PLATFORM_LINUX_ARMHF, PLATFORM_LINUX_ARM64):
return supposed_platform
# suppose that installed python was built with the right ABI
with open(sys.executable, 'rb') as f:
# see ELF header description in https://man7.org/linux/man-pages/man5/elf.5.html, offsets depend on ElfN size
if int.from_bytes(f.read(4), sys.byteorder) != int.from_bytes(b'\x7fELF', sys.byteorder):
return supposed_platform # ELF magic not found. Use the default platform name from PLATFORM_FROM_NAME
f.seek(18) # seek to e_machine
e_machine = int.from_bytes(f.read(2), sys.byteorder)
if e_machine == 183: # EM_AARCH64, https://github.com/ARM-software/abi-aa/blob/main/aaelf64/aaelf64.rst
supposed_platform = PLATFORM_LINUX_ARM64
elif e_machine == 40: # EM_ARM, https://github.com/ARM-software/abi-aa/blob/main/aaelf32/aaelf32.rst
f.seek(36) # seek to e_flags
e_flags = int.from_bytes(f.read(4), sys.byteorder)
if e_flags & 0x400:
supposed_platform = PLATFORM_LINUX_ARMHF
else:
supposed_platform = PLATFORM_LINUX_ARM32
return supposed_platform
@staticmethod
def get(platform_alias): # type: (Optional[str]) -> Optional[str]
if platform_alias is None:
@ -172,21 +208,8 @@ class Platforms:
if platform_alias == 'any' and CURRENT_PLATFORM:
platform_alias = CURRENT_PLATFORM
platform_name = Platforms.PLATFORM_FROM_NAME.get(platform_alias, None)
# ARM platform may run on armhf hardware but having armel installed packages.
# To avoid possible armel/armhf libraries mixing need to define user's
# packages architecture to use the same
# See note section in https://gcc.gnu.org/onlinedocs/gcc/ARM-Options.html#index-mfloat-abi
if platform_name in (PLATFORM_LINUX_ARM32, PLATFORM_LINUX_ARMHF) and 'arm' in platform.machine():
# suppose that installed python was built with a right ABI
with open(sys.executable, 'rb') as f:
if int.from_bytes(f.read(4), sys.byteorder) != int.from_bytes(b'\x7fELF', sys.byteorder):
return platform_name # ELF magic not found. Use default platform name from PLATFORM_FROM_NAME
f.seek(36) # seek to e_flags (https://man7.org/linux/man-pages/man5/elf.5.html)
e_flags = int.from_bytes(f.read(4), sys.byteorder)
platform_name = PLATFORM_LINUX_ARMHF if e_flags & 0x400 else PLATFORM_LINUX_ARM32
platform_name = Platforms.detect_linux_arm_platform(platform_name)
return platform_name
@staticmethod
@ -1389,7 +1412,7 @@ def get_idf_version() -> str:
"""
idf_version: Optional[str] = None
version_file_path = os.path.join(global_idf_path, 'version.txt')
version_file_path = os.path.join(global_idf_path or '', 'version.txt')
if os.path.exists(version_file_path):
with open(version_file_path, 'r') as version_file:
idf_version_str = version_file.read()
@ -1400,7 +1423,7 @@ def get_idf_version() -> str:
if idf_version is None:
try:
with open(os.path.join(global_idf_path, 'components', 'esp_common', 'include', 'esp_idf_version.h')) as f:
with open(os.path.join(global_idf_path or '', 'components', 'esp_common', 'include', 'esp_idf_version.h')) as f:
m = re.search(r'^#define\s+ESP_IDF_VERSION_MAJOR\s+(\d+).+?^#define\s+ESP_IDF_VERSION_MINOR\s+(\d+)',
f.read(), re.DOTALL | re.MULTILINE)
if m:

View File

@ -2,7 +2,6 @@
#
# SPDX-FileCopyrightText: 2019-2023 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Apache-2.0
import json
import os
import re
@ -10,6 +9,7 @@ import shutil
import sys
import tempfile
import unittest
from unittest.mock import patch
try:
from contextlib import redirect_stdout
@ -606,5 +606,30 @@ class TestMaintainer(unittest.TestCase):
self.assertEqual(json.load(f1), expected_json, "Please check 'tools/tools.new.json' to find a cause!")
class TestArmDetection(unittest.TestCase):
ELF_HEADERS = {
idf_tools.PLATFORM_LINUX_ARM64: 'platform_detection/arm64_header.elf',
idf_tools.PLATFORM_LINUX_ARMHF: 'platform_detection/armhf_header.elf',
idf_tools.PLATFORM_LINUX_ARM32: 'platform_detection/arm32_header.elf',
}
ARM_PLATFORMS = {
idf_tools.PLATFORM_LINUX_ARM64,
idf_tools.PLATFORM_LINUX_ARMHF,
idf_tools.PLATFORM_LINUX_ARM32,
}
def test_arm_detection(self):
for platform in idf_tools.Platforms.PLATFORM_FROM_NAME.values():
with patch('sys.executable', __file__): # use invalid ELF as executable. In this case passed parameter must return
self.assertEqual(idf_tools.Platforms.detect_linux_arm_platform(platform), platform)
# detect_linux_arm_platform() intended to return arch that detected in sys.executable ELF
for exec_platform in (idf_tools.PLATFORM_LINUX_ARM64, idf_tools.PLATFORM_LINUX_ARMHF, idf_tools.PLATFORM_LINUX_ARM32):
with patch('sys.executable', TestArmDetection.ELF_HEADERS[exec_platform]):
for platform in TestArmDetection.ARM_PLATFORMS:
self.assertEqual(idf_tools.Platforms.detect_linux_arm_platform(platform), exec_platform)
if __name__ == '__main__':
unittest.main()