OpenCorePkg/AppleModels/update_generated.py
2022-08-18 15:04:17 +03:00

277 lines
11 KiB
Python
Executable File

#!/usr/bin/env python3
# pylint: disable=redefined-outer-name,too-many-branches
'''
Update autogenerated source files from yaml database.
Copyright (c) 2019, vit9696
'''
import fnmatch
import json
import operator
import os
import sys
import unicodedata
import yaml
import update_products
def remove_accents(input_str):
nfkd_form = unicodedata.normalize('NFKD', input_str)
return ''.join([c for c in nfkd_form if not unicodedata.combining(c)])
def load_db(dbpath):
'''
Load yaml database and return in a list.
'''
if not os.path.exists(dbpath):
print(f'Cannot find {dbpath} directory, rerun from AppleModels directory!')
sys.exit(1)
db = []
for root, _, files in os.walk(dbpath):
for file in fnmatch.filter(files, '*.yaml'):
path = os.path.join(root, file)
with open(path, 'r', encoding='utf-8') as fh:
try:
r = yaml.safe_load(fh)
if r.get('SystemProductName', None) is None:
print(f'WARN: Missing SystemProductName in {path}, skipping!')
continue
db.append(r)
except yaml.YAMLError as e:
print(f'Failed to parse file {path} - {e}')
sys.exit(1)
if len(db) == 0:
print('Empty database!')
sys.exit(1)
# Sorting is required for fast lookup.
return sorted(db, key=operator.itemgetter('SystemProductName'))
def gather_products(db, ptype='AppleModelCode', empty_valid=False, shared_valid=False, fatal=True):
'''
Obtain all product codes from the database
'''
products = []
for info in db:
pp = info.get(ptype, None)
if pp is None:
continue
for p in pp:
if p == '':
if not empty_valid:
print(f"ERROR: {info['SystemProductName']} in contains empty {ptype}, skipping!")
if fatal:
sys.exit(1)
continue
if p in ['000', '0000']:
print(f"WARN: {info['SystemProductName']} in contains zero {ptype}, skipping!")
continue
if p in products:
if not shared_valid:
print(f"ERROR: {info['SystemProductName']} shares {ptype} {p} with other model!")
if fatal:
sys.exit(1)
continue
products.append(p)
return products
def validate_products(db, dbpd):
usedproducts = gather_products(db)
# FIXME: Empty is not valid, but we let it be for now.
gather_products(db, 'AppleBoardCode', True, True, False)
knownproducts = dbpd
for product in usedproducts:
if knownproducts.get(product, None) is None:
print(f'ERROR: Model {product} is used in DataBase but not present in Products!')
sys.exit(1)
if knownproducts[product][update_products.KEY_STATUS] != update_products.STATUS_OK:
print(f'ERROR: Model {product} is used in DataBase but not valid in Products!')
sys.exit(1)
to_add = {}
for product in knownproducts:
if knownproducts[product][update_products.KEY_STATUS] != update_products.STATUS_OK:
continue
name = knownproducts[product][update_products.KEY_NAME]
if name.find('Mac') < 0 and name.find('Xserve') < 0:
continue
if name.find('M1') >= 0:
continue
if len(product) > 3 and product not in usedproducts:
print(f'WARN: Model {product} ({name}) is known but is not used in DataBase!')
if to_add.get(name, None) is None:
to_add[name] = []
to_add[name].append(product)
continue
if len(to_add) > 0:
for sysname in to_add.items():
for info in db:
if sysname in info['Specifications']['SystemReportName']:
print(f"New AppleModelCode for {info['SystemProductName']}:")
for model in to_add[sysname]:
print(f" - \"{model}\"")
def export_db_macinfolib(db, path, year=0):
'''
Export yaml database to MacInfoLib format.
TODO: use jinja2?
'''
with open(path, 'w', encoding='utf-8') as fh:
print('// DO NOT EDIT! This is an autogenerated file.', file=fh)
print('#include "MacInfoInternal.h"', file=fh)
print('CONST MAC_INFO_INTERNAL_ENTRY gMacInfoModels[] = {', file=fh)
for info in db:
if max(info['AppleModelYear']) < year:
continue
sb_model = info.get('AppleModelId')
sb_model = f'"{sb_model.lower()}"' if sb_model else 'NULL'
print(
' {\n'
f''' .SystemProductName = "{info['SystemProductName']}",\n'''
f''' .BoardProduct = "{info['BoardProduct'][0] if isinstance(info['BoardProduct'], list) else info['BoardProduct']}",\n'''
f''' .BoardRevision = {f"0x{info['BoardRevision']:X}" if 'BoardRevision' in info else 'MAC_INFO_BOARD_REVISION_MISSING'},\n'''
f''' .SmcRevision = {{{', '.join(map(str, info.get('SmcRevision', [0x00])))}}},\n'''
f''' .SmcBranch = {{{', '.join(map(str, info.get('SmcBranch', [0x00])))}}},\n'''
f''' .SmcPlatform = {{{', '.join(map(str, info.get('SmcPlatform', [0x00])))}}},\n'''
f''' .BIOSVersion = "{info['BIOSVersion']}",\n'''
f''' .BIOSReleaseDate = "{info['BIOSReleaseDate']}",\n'''
f''' .SystemVersion = "{info['SystemVersion']}",\n'''
f''' .SystemSKUNumber = "{info['SystemSKUNumber']}",\n'''
f''' .SystemFamily = "{info['SystemFamily']}",\n'''
f''' .BoardVersion = "{info['BoardVersion']}",\n'''
f''' .BoardAssetTag = "{info['BoardAssetTag']}",\n'''
f''' .BoardLocationInChassis = "{info['BoardLocationInChassis']}",\n'''
f''' .SmcGeneration = 0x{info['SmcGeneration']:X},\n'''
f''' .BoardType = 0x{info['BoardType']:X},\n'''
f''' .ChassisType = 0x{info['ChassisType']:X},\n'''
f''' .MemoryFormFactor = 0x{info['MemoryFormFactor']:X},\n'''
f''' .PlatformFeature = {f"0x{info['PlatformFeature']:X}" if 'PlatformFeature' in info else 'MAC_INFO_PLATFORM_FEATURE_MISSING'},\n'''
f''' .ChassisAssetTag = "{info['ChassisAssetTag']}",\n'''
f''' .FirmwareFeatures = 0x{info.get('ExtendedFirmwareFeatures', info.get('FirmwareFeatures', 0)):X}ULL,\n'''
f''' .FirmwareFeaturesMask = 0x{info.get('ExtendedFirmwareFeaturesMask', info.get('FirmwareFeaturesMask', 0)):X}ULL,\n'''
f' .SecureBootModel = {sb_model},\n'
f' }},', file=fh)
print('};', file=fh)
print('CONST UINTN gMacInfoModelCount = ARRAY_SIZE (gMacInfoModels);', file=fh)
print('CONST UINTN gMacInfoDefaultModel = 0;', file=fh)
def export_db_macserial(db, dbpd, path, year=0):
'''
Export yaml database to macserial format.
TODO: use jinja2?
'''
with open(path, 'w', encoding='utf-8') as fh:
print('#ifndef GENSERIAL_MODELINFO_AUTOGEN_H', file=fh)
print('#define GENSERIAL_MODELINFO_AUTOGEN_H\n', file=fh)
print('// DO NOT EDIT! This is an autogenerated file.\n', file=fh)
print('#include "macserial.h"\n', file=fh)
print('typedef enum {', file=fh)
for info in db:
print(f" {info['SystemProductName'].replace(',', '_')}, // {info['Specifications']['CPU'][0]}", file=fh)
print('} AppleModel;\n', file=fh)
print(f'#define APPLE_MODEL_MAX {len(db)}\n', file=fh)
print('static PLATFORMDATA ApplePlatformData[] = {', file=fh)
for info in db:
print(f''' {{ "{info['SystemProductName']}", "{info['SystemSerialNumber']}" }},''', file=fh)
print('};\n', file=fh)
print(f"#define APPLE_MODEL_CODE_MAX {max(len(info['AppleModelCode']) for info in db)}", file=fh)
print('static const char *AppleModelCode[][APPLE_MODEL_CODE_MAX] = {', file=fh)
for info in db:
print(f""" /* {info['SystemProductName']:14} */ {{"{'", "'.join(info['AppleModelCode'])}"}},""", file=fh)
print('};\n', file=fh)
print(f"#define APPLE_BOARD_CODE_MAX {max(len(info['AppleBoardCode']) for info in db)}", file=fh)
print('static const char *AppleBoardCode[][APPLE_BOARD_CODE_MAX] = {', file=fh)
for info in db:
print(f""" /* {info['SystemProductName']:14} */ {{"{'", "'.join(info['AppleBoardCode'])}"}},""", file=fh)
print('};\n', file=fh)
print(f"#define APPLE_MODEL_YEAR_MAX {max(len(info['AppleModelYear']) for info in db)}", file=fh)
print('static uint32_t AppleModelYear[][APPLE_MODEL_YEAR_MAX] = {', file=fh)
for info in db:
print(f" /* {info['SystemProductName']:14} */ {{{', '.join(str(year) for year in info['AppleModelYear'])}}},", file=fh)
print('};\n', file=fh)
print('static uint32_t ApplePreferredModelYear[] = {', file=fh)
for info in db:
print(f" /* {info['SystemProductName']:14} */ {info.get('MacserialModelYear', 0)},", file=fh)
print('};\n', file=fh)
print('static APPLE_MODEL_DESC AppleModelDesc[] = {', file=fh)
models = sorted(dbpd.keys())
models.sort(key=len)
for model in models:
if dbpd[model][update_products.KEY_STATUS] == update_products.STATUS_OK:
print(f""" {{"{model}", "{remove_accents(dbpd[model][update_products.KEY_NAME])}"}},""", file=fh)
print('};\n', file=fh)
print('#endif // GENSERIAL_MODELINFO_AUTOGEN_H', file=fh)
def export_mlb_boards(db, boards):
mlb = {}
for info in db:
if len(info['SystemSerialNumber']) == 12:
models = [info['BoardProduct']] if not isinstance(info['BoardProduct'], list) else info['BoardProduct']
for model in models:
mlb[model] = 'latest' if not info['MaximumOSVersion'] else info['MaximumOSVersion']
with open(boards, 'w', encoding='utf-8') as fh:
json.dump(mlb, fh, indent=1)
if __name__ == '__main__':
db = load_db('DataBase')
dbpd = update_products.load_products()
# Run test phase to validate the library
validate_products(db, dbpd)
export_db_macinfolib(db, os.devnull)
export_db_macserial(db, dbpd, os.devnull)
# Export new models
export_db_macinfolib(db, '../Library/OcMacInfoLib/AutoGenerated.c')
export_db_macserial(db, dbpd, '../Utilities/macserial/modelinfo_autogen.h')
# Export MLB models
export_mlb_boards(db, '../Utilities/macrecovery/boards.json')