From 0a8c584bfd450757069453f0439042e2ae61caff Mon Sep 17 00:00:00 2001 From: Brian Gallagher Date: Wed, 24 Jun 2026 22:49:41 +0000 Subject: [PATCH 1/6] Add library function to parse optical_devices.json Signed-off-by: Brian Gallagher --- .../sonic_py_common/device_info.py | 74 +++++++++++++ src/sonic-py-common/tests/device_info_test.py | 101 ++++++++++++++++++ 2 files changed, 175 insertions(+) diff --git a/src/sonic-py-common/sonic_py_common/device_info.py b/src/sonic-py-common/sonic_py_common/device_info.py index 8709941afcf..d280735ad44 100644 --- a/src/sonic-py-common/sonic_py_common/device_info.py +++ b/src/sonic-py-common/sonic_py_common/device_info.py @@ -6,6 +6,7 @@ import re import subprocess import yaml +from typing import List, Optional from natsort import natsorted from sonic_py_common.general import getstatusoutput_noshell_pipe from swsscommon.swsscommon import ConfigDBConnector, SonicV2Connector @@ -21,6 +22,9 @@ # Port configuration file names PORT_CONFIG_FILE = "port_config.ini" PLATFORM_JSON_FILE = "platform.json" + +# Optical devices topology file name (e.g CPO) +OPTICAL_DEVICES_JSON_FILE = "optical_devices.json" BMC_BUILD_CONFIG_FILE = '/etc/sonic/bmc_config.json' GLOBAL_BMC_DATA_FILE = '/etc/sonic/bmc.json' @@ -201,6 +205,76 @@ def get_platform_json_data(): return None +def get_optical_devices_data() -> Optional[dict]: + """ + Retrieve the data from the optical_devices.json file. + + Locates the file using a two-stage lookup: a hwsku-specific file takes + precedence over a platform-wide file. Lane fields are normalized from + comma-separated strings ("41,42") into lists of ints ([41, 42]); all + other fields, including vendor-specific ones, are returned verbatim. + None is returned if the file does not exist or cannot be parsed. + """ + platform = get_platform() + if not platform: + return None + + optical_devices_file = _find_optical_devices_file() + if not optical_devices_file: + return None + + try: + with open(optical_devices_file, 'r') as f: + optical_devices_data = json.loads(f.read()) + except (json.JSONDecodeError, IOError, TypeError, ValueError): + # Handle any file reading and JSON parsing errors + return None + + _normalize_optical_devices_lanes(optical_devices_data) + return optical_devices_data + + +def _find_optical_devices_file() -> Optional[str]: + """ + Locate optical_devices.json, preferring the hwsku directory over the + platform directory. + Returns the path to the first optical_devices.json found, or None. + """ + try: + hwsku_file = os.path.join(get_path_to_hwsku_dir(), OPTICAL_DEVICES_JSON_FILE) + if os.path.isfile(hwsku_file): + return hwsku_file + + platform_file = os.path.join(get_path_to_platform_dir(), OPTICAL_DEVICES_JSON_FILE) + if os.path.isfile(platform_file): + return platform_file + except OSError: + pass + + return None + + +def _parse_lane_string(lane_string: str) -> List[int]: + """'41,42,43' -> [41, 42, 43]; tolerates spaces and a trailing comma.""" + return [int(tok) for tok in lane_string.split(',') if tok.strip() != ''] + + +def _normalize_optical_devices_lanes(optical_devices_data: dict) -> None: + """ + In-place normalization of the known lane fields from comma-separated + strings to lists of ints. All other fields (vendor-specific included) are + left untouched. + """ + for device in optical_devices_data.get('devices', {}).values(): + if 'lanes' in device: + device['lanes'] = _parse_lane_string(device['lanes']) + if 'laser_to_lane_mapping' in device: + device['laser_to_lane_mapping'] = { + int(laser): _parse_lane_string(lanes) + for laser, lanes in device['laser_to_lane_mapping'].items() + } + + def get_asic_conf_file_path(): """ Retrieves the path to the ASIC configuration file on the device diff --git a/src/sonic-py-common/tests/device_info_test.py b/src/sonic-py-common/tests/device_info_test.py index 5362da49631..4a45e678dcd 100644 --- a/src/sonic-py-common/tests/device_info_test.py +++ b/src/sonic-py-common/tests/device_info_test.py @@ -255,6 +255,107 @@ def test_get_platform_json_data(self, mock_get_platform, mock_get_path_to_platfo result = device_info.get_platform_json_data() assert result is None + @mock.patch("os.path.isfile") + @mock.patch("{}.open".format(BUILTINS)) + @mock.patch("sonic_py_common.device_info.get_path_to_platform_dir") + @mock.patch("sonic_py_common.device_info.get_path_to_hwsku_dir") + @mock.patch("sonic_py_common.device_info.get_platform") + def test_get_optical_devices_data(self, mock_get_platform, mock_get_hwsku_dir, mock_get_platform_dir, mock_open, mock_isfile): + mock_get_platform.return_value = "x86_64-vendor_cpo-r0" + mock_get_hwsku_dir.return_value = "/usr/share/sonic/device/x86_64-vendor_cpo-r0/CPO-HWSKU" + mock_get_platform_dir.return_value = "/usr/share/sonic/device/x86_64-vendor_cpo-r0" + + optical_devices_data = { + "devices": { + "OE1": { + "device_type": "optical_engine", + "max_banks": 2, + "lanes": "41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56", + "i2c_path": "/sys/bus/i2c/devices/32-0050" + }, + "ELS1": { + "device_type": "external_laser_source", + "lasers": 4, + "max_banks": 1, + "laser_to_lane_mapping": { + "1": "41,42,43,44", + "2": "45,46,47,48", + "3": "49,50,51,52", + "4": "53,54,55,56" + }, + # example vendor-specific field; must pass through verbatim. + "elsfp_sysfs_path": "/sys/bus/i2c/devices/33-0051" + } + }, + "interfaces": { + "Ethernet0": { + "associated_devices": [ + {"device_id": "OE1", "bank": 0}, + {"device_id": "ELS1", "bank": 0} + ] + }, + "Ethernet8": { + "associated_devices": [ + {"device_id": "OE1", "bank": 1}, + {"device_id": "ELS1", "bank": 0} + ] + } + } + } + + # Happy path: lane strings normalized, vendor field untouched. + mock_isfile.return_value = True + open_mocked = mock.mock_open(read_data=json.dumps(optical_devices_data)) + mock_open.side_effect = open_mocked + result = device_info.get_optical_devices_data() + assert result["devices"]["OE1"]["lanes"] == [41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56] + assert result["devices"]["ELS1"]["laser_to_lane_mapping"] == { + 1: [41, 42, 43, 44], + 2: [45, 46, 47, 48], + 3: [49, 50, 51, 52], + 4: [53, 54, 55, 56], + } + assert result["devices"]["ELS1"]["elsfp_sysfs_path"] == "/sys/bus/i2c/devices/33-0051" + assert result["interfaces"]["Ethernet0"]["associated_devices"] == [ + {"device_id": "OE1", "bank": 0}, + {"device_id": "ELS1", "bank": 0}, + ] + assert result["interfaces"]["Ethernet8"]["associated_devices"] == [ + {"device_id": "OE1", "bank": 1}, + {"device_id": "ELS1", "bank": 0}, + ] + + # hwsku file takes precedence over the platform file. + mock_open.side_effect = mock.mock_open(read_data=json.dumps(optical_devices_data)) + device_info.get_optical_devices_data() + opened_path = mock_open.call_args[0][0] + assert opened_path == "/usr/share/sonic/device/x86_64-vendor_cpo-r0/CPO-HWSKU/optical_devices.json" + + # Falls back to the platform file when no hwsku file exists. + def only_platform_file(path): + return path == "/usr/share/sonic/device/x86_64-vendor_cpo-r0/optical_devices.json" + mock_isfile.side_effect = only_platform_file + mock_open.side_effect = mock.mock_open(read_data=json.dumps(optical_devices_data)) + device_info.get_optical_devices_data() + opened_path = mock_open.call_args[0][0] + assert opened_path == "/usr/share/sonic/device/x86_64-vendor_cpo-r0/optical_devices.json" + + # Returns None when no file exists in either directory. + mock_isfile.side_effect = None + mock_isfile.return_value = False + assert device_info.get_optical_devices_data() is None + + # Returns None when platform is not set. + mock_isfile.return_value = True + mock_open.side_effect = mock.mock_open(read_data=json.dumps(optical_devices_data)) + mock_get_platform.return_value = None + assert device_info.get_optical_devices_data() is None + + # Returns None when the JSON is invalid. + mock_get_platform.return_value = "x86_64-vendor_cpo-r0" + mock_open.side_effect = mock.mock_open(read_data="invalid json") + assert device_info.get_optical_devices_data() is None + @mock.patch("sonic_py_common.device_info.get_platform_json_data") @mock.patch("sonic_py_common.device_info.get_platform") def test_is_smartswitch(self, mock_get_platform, mock_get_platform_json_data): From 7a037b8513ea575ec7499d08d9a60488f6aef372 Mon Sep 17 00:00:00 2001 From: Brian Gallagher Date: Thu, 9 Jul 2026 17:46:58 +0000 Subject: [PATCH 2/6] Review feedback Signed-off-by: Brian Gallagher --- src/sonic-py-common/sonic_py_common/device_info.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/sonic-py-common/sonic_py_common/device_info.py b/src/sonic-py-common/sonic_py_common/device_info.py index d280735ad44..4ac3277a6ea 100644 --- a/src/sonic-py-common/sonic_py_common/device_info.py +++ b/src/sonic-py-common/sonic_py_common/device_info.py @@ -25,6 +25,7 @@ # Optical devices topology file name (e.g CPO) OPTICAL_DEVICES_JSON_FILE = "optical_devices.json" + BMC_BUILD_CONFIG_FILE = '/etc/sonic/bmc_config.json' GLOBAL_BMC_DATA_FILE = '/etc/sonic/bmc.json' @@ -266,13 +267,16 @@ def _normalize_optical_devices_lanes(optical_devices_data: dict) -> None: left untouched. """ for device in optical_devices_data.get('devices', {}).values(): - if 'lanes' in device: + device_type = device['device_type'] + if device_type == 'optical_engine': device['lanes'] = _parse_lane_string(device['lanes']) - if 'laser_to_lane_mapping' in device: + elif device_type == 'external_laser_source': device['laser_to_lane_mapping'] = { int(laser): _parse_lane_string(lanes) for laser, lanes in device['laser_to_lane_mapping'].items() } + else: + raise ValueError(f'Unrecognized device_type: {device_type}') def get_asic_conf_file_path(): From 842eacc0d979be4ef4c87d0e94b0f8964f9b2579 Mon Sep 17 00:00:00 2001 From: Brian Gallagher Date: Mon, 13 Jul 2026 17:50:50 +0000 Subject: [PATCH 3/6] Rename optical_devices.json to cpo.json Signed-off-by: Brian Gallagher --- .../sonic_py_common/device_info.py | 34 +++++++++---------- src/sonic-py-common/tests/device_info_test.py | 30 ++++++++-------- 2 files changed, 32 insertions(+), 32 deletions(-) diff --git a/src/sonic-py-common/sonic_py_common/device_info.py b/src/sonic-py-common/sonic_py_common/device_info.py index 4ac3277a6ea..556f15f61b5 100644 --- a/src/sonic-py-common/sonic_py_common/device_info.py +++ b/src/sonic-py-common/sonic_py_common/device_info.py @@ -23,8 +23,8 @@ PORT_CONFIG_FILE = "port_config.ini" PLATFORM_JSON_FILE = "platform.json" -# Optical devices topology file name (e.g CPO) -OPTICAL_DEVICES_JSON_FILE = "optical_devices.json" +# CPO configuration file name +CPO_FILE = "cpo.json" BMC_BUILD_CONFIG_FILE = '/etc/sonic/bmc_config.json' GLOBAL_BMC_DATA_FILE = '/etc/sonic/bmc.json' @@ -206,9 +206,9 @@ def get_platform_json_data(): return None -def get_optical_devices_data() -> Optional[dict]: +def get_cpo_data() -> Optional[dict]: """ - Retrieve the data from the optical_devices.json file. + Retrieve the data from the cpo.json file. Locates the file using a two-stage lookup: a hwsku-specific file takes precedence over a platform-wide file. Lane fields are normalized from @@ -220,33 +220,33 @@ def get_optical_devices_data() -> Optional[dict]: if not platform: return None - optical_devices_file = _find_optical_devices_file() - if not optical_devices_file: + cpo_file = _find_cpo_file() + if not cpo_file: return None try: - with open(optical_devices_file, 'r') as f: - optical_devices_data = json.loads(f.read()) + with open(cpo_file, 'r') as f: + cpo_data = json.loads(f.read()) except (json.JSONDecodeError, IOError, TypeError, ValueError): # Handle any file reading and JSON parsing errors return None - _normalize_optical_devices_lanes(optical_devices_data) - return optical_devices_data + _normalize_cpo_data(cpo_data) + return cpo_data -def _find_optical_devices_file() -> Optional[str]: +def _find_cpo_file() -> Optional[str]: """ - Locate optical_devices.json, preferring the hwsku directory over the + Locate cpo.json, preferring the hwsku directory over the platform directory. - Returns the path to the first optical_devices.json found, or None. + Returns the path to the first cpo.json found, or None. """ try: - hwsku_file = os.path.join(get_path_to_hwsku_dir(), OPTICAL_DEVICES_JSON_FILE) + hwsku_file = os.path.join(get_path_to_hwsku_dir(), CPO_FILE) if os.path.isfile(hwsku_file): return hwsku_file - platform_file = os.path.join(get_path_to_platform_dir(), OPTICAL_DEVICES_JSON_FILE) + platform_file = os.path.join(get_path_to_platform_dir(), CPO_FILE) if os.path.isfile(platform_file): return platform_file except OSError: @@ -260,13 +260,13 @@ def _parse_lane_string(lane_string: str) -> List[int]: return [int(tok) for tok in lane_string.split(',') if tok.strip() != ''] -def _normalize_optical_devices_lanes(optical_devices_data: dict) -> None: +def _normalize_cpo_data(cpo_data: dict) -> None: """ In-place normalization of the known lane fields from comma-separated strings to lists of ints. All other fields (vendor-specific included) are left untouched. """ - for device in optical_devices_data.get('devices', {}).values(): + for device in cpo_data.get('devices', {}).values(): device_type = device['device_type'] if device_type == 'optical_engine': device['lanes'] = _parse_lane_string(device['lanes']) diff --git a/src/sonic-py-common/tests/device_info_test.py b/src/sonic-py-common/tests/device_info_test.py index 4a45e678dcd..36e6a7c8944 100644 --- a/src/sonic-py-common/tests/device_info_test.py +++ b/src/sonic-py-common/tests/device_info_test.py @@ -260,12 +260,12 @@ def test_get_platform_json_data(self, mock_get_platform, mock_get_path_to_platfo @mock.patch("sonic_py_common.device_info.get_path_to_platform_dir") @mock.patch("sonic_py_common.device_info.get_path_to_hwsku_dir") @mock.patch("sonic_py_common.device_info.get_platform") - def test_get_optical_devices_data(self, mock_get_platform, mock_get_hwsku_dir, mock_get_platform_dir, mock_open, mock_isfile): + def test_get_cpo_data(self, mock_get_platform, mock_get_hwsku_dir, mock_get_platform_dir, mock_open, mock_isfile): mock_get_platform.return_value = "x86_64-vendor_cpo-r0" mock_get_hwsku_dir.return_value = "/usr/share/sonic/device/x86_64-vendor_cpo-r0/CPO-HWSKU" mock_get_platform_dir.return_value = "/usr/share/sonic/device/x86_64-vendor_cpo-r0" - optical_devices_data = { + cpo_data = { "devices": { "OE1": { "device_type": "optical_engine", @@ -305,9 +305,9 @@ def test_get_optical_devices_data(self, mock_get_platform, mock_get_hwsku_dir, m # Happy path: lane strings normalized, vendor field untouched. mock_isfile.return_value = True - open_mocked = mock.mock_open(read_data=json.dumps(optical_devices_data)) + open_mocked = mock.mock_open(read_data=json.dumps(cpo_data)) mock_open.side_effect = open_mocked - result = device_info.get_optical_devices_data() + result = device_info.get_cpo_data() assert result["devices"]["OE1"]["lanes"] == [41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56] assert result["devices"]["ELS1"]["laser_to_lane_mapping"] == { 1: [41, 42, 43, 44], @@ -326,35 +326,35 @@ def test_get_optical_devices_data(self, mock_get_platform, mock_get_hwsku_dir, m ] # hwsku file takes precedence over the platform file. - mock_open.side_effect = mock.mock_open(read_data=json.dumps(optical_devices_data)) - device_info.get_optical_devices_data() + mock_open.side_effect = mock.mock_open(read_data=json.dumps(cpo_data)) + device_info.get_cpo_data() opened_path = mock_open.call_args[0][0] - assert opened_path == "/usr/share/sonic/device/x86_64-vendor_cpo-r0/CPO-HWSKU/optical_devices.json" + assert opened_path == "/usr/share/sonic/device/x86_64-vendor_cpo-r0/CPO-HWSKU/cpo.json" # Falls back to the platform file when no hwsku file exists. def only_platform_file(path): - return path == "/usr/share/sonic/device/x86_64-vendor_cpo-r0/optical_devices.json" + return path == "/usr/share/sonic/device/x86_64-vendor_cpo-r0/cpo.json" mock_isfile.side_effect = only_platform_file - mock_open.side_effect = mock.mock_open(read_data=json.dumps(optical_devices_data)) - device_info.get_optical_devices_data() + mock_open.side_effect = mock.mock_open(read_data=json.dumps(cpo_data)) + device_info.get_cpo_data() opened_path = mock_open.call_args[0][0] - assert opened_path == "/usr/share/sonic/device/x86_64-vendor_cpo-r0/optical_devices.json" + assert opened_path == "/usr/share/sonic/device/x86_64-vendor_cpo-r0/cpo.json" # Returns None when no file exists in either directory. mock_isfile.side_effect = None mock_isfile.return_value = False - assert device_info.get_optical_devices_data() is None + assert device_info.get_cpo_data() is None # Returns None when platform is not set. mock_isfile.return_value = True - mock_open.side_effect = mock.mock_open(read_data=json.dumps(optical_devices_data)) + mock_open.side_effect = mock.mock_open(read_data=json.dumps(cpo_data)) mock_get_platform.return_value = None - assert device_info.get_optical_devices_data() is None + assert device_info.get_cpo_data() is None # Returns None when the JSON is invalid. mock_get_platform.return_value = "x86_64-vendor_cpo-r0" mock_open.side_effect = mock.mock_open(read_data="invalid json") - assert device_info.get_optical_devices_data() is None + assert device_info.get_cpo_data() is None @mock.patch("sonic_py_common.device_info.get_platform_json_data") @mock.patch("sonic_py_common.device_info.get_platform") From 603a3338083bc57f1d48599a4c56699d3dc9b979 Mon Sep 17 00:00:00 2001 From: Brian Gallagher Date: Fri, 17 Jul 2026 22:50:39 +0000 Subject: [PATCH 4/6] Review feedback Signed-off-by: Brian Gallagher --- .../sonic_py_common/device_info.py | 42 +++++++++++++++++-- src/sonic-py-common/tests/device_info_test.py | 8 ++-- 2 files changed, 43 insertions(+), 7 deletions(-) diff --git a/src/sonic-py-common/sonic_py_common/device_info.py b/src/sonic-py-common/sonic_py_common/device_info.py index 556f15f61b5..51f02b6e8b1 100644 --- a/src/sonic-py-common/sonic_py_common/device_info.py +++ b/src/sonic-py-common/sonic_py_common/device_info.py @@ -265,15 +265,51 @@ def _normalize_cpo_data(cpo_data: dict) -> None: In-place normalization of the known lane fields from comma-separated strings to lists of ints. All other fields (vendor-specific included) are left untouched. + + Example input: + { + "devices": { + "OE1": { + "device_type": "optical_engine", + "asic_lanes": "41,42,43,44", + "i2c_path": "/sys/bus/i2c/devices/32-0050" + }, + "ELS1": { + "device_type": "external_laser_source", + "laser_to_asic_lane_mapping": { + "1": "41,42", + "2": "43,44" + } + } + } + } + + After _normalize_cpo_data(...) the same dict becomes: + { + "devices": { + "OE1": { + "device_type": "optical_engine", + "asic_lanes": [41, 42, 43, 44], + "i2c_path": "/sys/bus/i2c/devices/32-0050" + }, + "ELS1": { + "device_type": "external_laser_source", + "laser_to_asic_lane_mapping": { + 1: [41, 42], + 2: [43, 44] + } + } + } + } """ for device in cpo_data.get('devices', {}).values(): device_type = device['device_type'] if device_type == 'optical_engine': - device['lanes'] = _parse_lane_string(device['lanes']) + device['asic_lanes'] = _parse_lane_string(device['asic_lanes']) elif device_type == 'external_laser_source': - device['laser_to_lane_mapping'] = { + device['laser_to_asic_lane_mapping'] = { int(laser): _parse_lane_string(lanes) - for laser, lanes in device['laser_to_lane_mapping'].items() + for laser, lanes in device['laser_to_asic_lane_mapping'].items() } else: raise ValueError(f'Unrecognized device_type: {device_type}') diff --git a/src/sonic-py-common/tests/device_info_test.py b/src/sonic-py-common/tests/device_info_test.py index 36e6a7c8944..c717c6ba0e9 100644 --- a/src/sonic-py-common/tests/device_info_test.py +++ b/src/sonic-py-common/tests/device_info_test.py @@ -270,14 +270,14 @@ def test_get_cpo_data(self, mock_get_platform, mock_get_hwsku_dir, mock_get_plat "OE1": { "device_type": "optical_engine", "max_banks": 2, - "lanes": "41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56", + "asic_lanes": "41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56", "i2c_path": "/sys/bus/i2c/devices/32-0050" }, "ELS1": { "device_type": "external_laser_source", "lasers": 4, "max_banks": 1, - "laser_to_lane_mapping": { + "laser_to_asic_lane_mapping": { "1": "41,42,43,44", "2": "45,46,47,48", "3": "49,50,51,52", @@ -308,8 +308,8 @@ def test_get_cpo_data(self, mock_get_platform, mock_get_hwsku_dir, mock_get_plat open_mocked = mock.mock_open(read_data=json.dumps(cpo_data)) mock_open.side_effect = open_mocked result = device_info.get_cpo_data() - assert result["devices"]["OE1"]["lanes"] == [41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56] - assert result["devices"]["ELS1"]["laser_to_lane_mapping"] == { + assert result["devices"]["OE1"]["asic_lanes"] == [41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56] + assert result["devices"]["ELS1"]["laser_to_asic_lane_mapping"] == { 1: [41, 42, 43, 44], 2: [45, 46, 47, 48], 3: [49, 50, 51, 52], From 5b5fad044adb9156827aed022cea964d82ab1b7f Mon Sep 17 00:00:00 2001 From: Brian Gallagher Date: Tue, 28 Jul 2026 16:48:30 +0000 Subject: [PATCH 5/6] Copilot review feedback Signed-off-by: Brian Gallagher --- .../sonic_py_common/device_info.py | 20 +++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/src/sonic-py-common/sonic_py_common/device_info.py b/src/sonic-py-common/sonic_py_common/device_info.py index 51f02b6e8b1..4a75720c3dc 100644 --- a/src/sonic-py-common/sonic_py_common/device_info.py +++ b/src/sonic-py-common/sonic_py_common/device_info.py @@ -216,8 +216,7 @@ def get_cpo_data() -> Optional[dict]: other fields, including vendor-specific ones, are returned verbatim. None is returned if the file does not exist or cannot be parsed. """ - platform = get_platform() - if not platform: + if not get_platform(): return None cpo_file = _find_cpo_file() @@ -242,15 +241,24 @@ def _find_cpo_file() -> Optional[str]: Returns the path to the first cpo.json found, or None. """ try: - hwsku_file = os.path.join(get_path_to_hwsku_dir(), CPO_FILE) + hwsku_dir = get_path_to_hwsku_dir() + except (OSError, TypeError): + hwsku_dir = None + + if hwsku_dir: + hwsku_file = os.path.join(hwsku_dir, CPO_FILE) if os.path.isfile(hwsku_file): return hwsku_file - platform_file = os.path.join(get_path_to_platform_dir(), CPO_FILE) + try: + platform_dir = get_path_to_platform_dir() + except OSError: + platform_dir = None + + if platform_dir: + platform_file = os.path.join(platform_dir, CPO_FILE) if os.path.isfile(platform_file): return platform_file - except OSError: - pass return None From a335987d3e6abe3269088c2ab08c3f6b866df9e5 Mon Sep 17 00:00:00 2001 From: aditya-nexthop Date: Mon, 3 Aug 2026 21:09:47 +0000 Subject: [PATCH 6/6] Empty commit to trigger build Signed-off-by: aditya-nexthop