Initial commit
This commit is contained in:
19
buildroot/share/PlatformIO/scripts/SAMD21_minitronics20.py
Normal file
19
buildroot/share/PlatformIO/scripts/SAMD21_minitronics20.py
Normal file
@ -0,0 +1,19 @@
|
||||
#
|
||||
# SAMD21_minitronics20.py
|
||||
# Customizations for env:SAMD21_minitronics20
|
||||
#
|
||||
import pioutil
|
||||
if pioutil.is_pio_build():
|
||||
from os.path import join, isfile
|
||||
import shutil
|
||||
|
||||
Import("env")
|
||||
|
||||
mf = env["MARLIN_FEATURES"]
|
||||
rxBuf = mf["RX_BUFFER_SIZE"] if "RX_BUFFER_SIZE" in mf else "0"
|
||||
txBuf = mf["TX_BUFFER_SIZE"] if "TX_BUFFER_SIZE" in mf else "0"
|
||||
|
||||
serialBuf = str(max(int(rxBuf), int(txBuf), 350))
|
||||
|
||||
build_flags = env.get('BUILD_FLAGS')
|
||||
env.Replace(BUILD_FLAGS=build_flags)
|
20
buildroot/share/PlatformIO/scripts/SAMD51_grandcentral_m4.py
Normal file
20
buildroot/share/PlatformIO/scripts/SAMD51_grandcentral_m4.py
Normal file
@ -0,0 +1,20 @@
|
||||
#
|
||||
# SAMD51_grandcentral_m4.py
|
||||
# Customizations for env:SAMD51_grandcentral_m4
|
||||
#
|
||||
import pioutil
|
||||
if pioutil.is_pio_build():
|
||||
from os.path import join, isfile
|
||||
import shutil
|
||||
|
||||
Import("env")
|
||||
|
||||
mf = env["MARLIN_FEATURES"]
|
||||
rxBuf = mf["RX_BUFFER_SIZE"] if "RX_BUFFER_SIZE" in mf else "0"
|
||||
txBuf = mf["TX_BUFFER_SIZE"] if "TX_BUFFER_SIZE" in mf else "0"
|
||||
|
||||
serialBuf = str(max(int(rxBuf), int(txBuf), 350))
|
||||
|
||||
build_flags = env.get('BUILD_FLAGS')
|
||||
build_flags.append("-DSERIAL_BUFFER_SIZE=" + serialBuf)
|
||||
env.Replace(BUILD_FLAGS=build_flags)
|
19
buildroot/share/PlatformIO/scripts/STM32F103RC_MEEB_3DP.py
Normal file
19
buildroot/share/PlatformIO/scripts/STM32F103RC_MEEB_3DP.py
Normal file
@ -0,0 +1,19 @@
|
||||
#
|
||||
# STM32F103RC_MEEB_3DP.py
|
||||
#
|
||||
import pioutil
|
||||
if pioutil.is_pio_build():
|
||||
|
||||
Import("env", "projenv")
|
||||
|
||||
flash_size = 0
|
||||
vect_tab_addr = 0
|
||||
|
||||
for define in env['CPPDEFINES']:
|
||||
if define[0] == "VECT_TAB_ADDR":
|
||||
vect_tab_addr = define[1]
|
||||
if define[0] == "STM32_FLASH_SIZE":
|
||||
flash_size = define[1]
|
||||
|
||||
print('Use the {0:s} address as the marlin app entry point.'.format(vect_tab_addr))
|
||||
print('Use the {0:d}KB flash version of stm32f103rct6 chip.'.format(flash_size))
|
27
buildroot/share/PlatformIO/scripts/STM32F103RC_fysetc.py
Normal file
27
buildroot/share/PlatformIO/scripts/STM32F103RC_fysetc.py
Normal file
@ -0,0 +1,27 @@
|
||||
#
|
||||
# STM32F103RC_fysetc.py
|
||||
#
|
||||
import pioutil
|
||||
if pioutil.is_pio_build():
|
||||
from os.path import join
|
||||
from os.path import expandvars
|
||||
Import("env")
|
||||
|
||||
# Custom HEX from ELF
|
||||
env.AddPostAction(
|
||||
join("$BUILD_DIR", "${PROGNAME}.elf"),
|
||||
env.VerboseAction(" ".join([
|
||||
"$OBJCOPY", "-O ihex", "$TARGET",
|
||||
"\"" + join("$BUILD_DIR", "${PROGNAME}.hex") + "\"", # Note: $BUILD_DIR is a full path
|
||||
]), "Building $TARGET"))
|
||||
|
||||
# In-line command with arguments
|
||||
UPLOAD_TOOL="stm32flash"
|
||||
platform = env.PioPlatform()
|
||||
if platform.get_package_dir("tool-stm32duino") != None:
|
||||
UPLOAD_TOOL=expandvars("\"" + join(platform.get_package_dir("tool-stm32duino"),"stm32flash","stm32flash") + "\"")
|
||||
|
||||
env.Replace(
|
||||
UPLOADER=UPLOAD_TOOL,
|
||||
UPLOADCMD=expandvars(UPLOAD_TOOL + " -v -i rts,-dtr,dtr -R -b 115200 -g 0x8000000 -w \"" + join("$BUILD_DIR","${PROGNAME}.hex")+"\"" + " $UPLOAD_PORT")
|
||||
)
|
31
buildroot/share/PlatformIO/scripts/STM32F1_create_variant.py
Normal file
31
buildroot/share/PlatformIO/scripts/STM32F1_create_variant.py
Normal file
@ -0,0 +1,31 @@
|
||||
#
|
||||
# STM32F1_create_variant.py
|
||||
#
|
||||
import pioutil
|
||||
if pioutil.is_pio_build():
|
||||
import shutil,marlin
|
||||
from pathlib import Path
|
||||
|
||||
Import("env")
|
||||
platform = env.PioPlatform()
|
||||
board = env.BoardConfig()
|
||||
|
||||
FRAMEWORK_DIR = Path(platform.get_package_dir("framework-arduinoststm32-maple"))
|
||||
assert FRAMEWORK_DIR.is_dir()
|
||||
|
||||
source_root = Path("buildroot/share/PlatformIO/variants")
|
||||
assert source_root.is_dir()
|
||||
|
||||
variant = board.get("build.variant")
|
||||
variant_dir = FRAMEWORK_DIR / "STM32F1/variants" / variant
|
||||
|
||||
source_dir = source_root / variant
|
||||
assert source_dir.is_dir()
|
||||
|
||||
if variant_dir.is_dir():
|
||||
shutil.rmtree(variant_dir)
|
||||
|
||||
if not variant_dir.is_dir():
|
||||
variant_dir.mkdir()
|
||||
|
||||
marlin.copytree(source_dir, variant_dir)
|
0
buildroot/share/PlatformIO/scripts/__init__.py
Normal file
0
buildroot/share/PlatformIO/scripts/__init__.py
Normal file
6
buildroot/share/PlatformIO/scripts/add_nanolib.py
Normal file
6
buildroot/share/PlatformIO/scripts/add_nanolib.py
Normal file
@ -0,0 +1,6 @@
|
||||
#
|
||||
# add_nanolib.py
|
||||
#
|
||||
Import("env")
|
||||
|
||||
env.Append(LINKFLAGS=["--specs=nano.specs"])
|
126
buildroot/share/PlatformIO/scripts/chitu_crypt.py
Normal file
126
buildroot/share/PlatformIO/scripts/chitu_crypt.py
Normal file
@ -0,0 +1,126 @@
|
||||
#
|
||||
# chitu_crypt.py
|
||||
# Customizations for Chitu boards
|
||||
#
|
||||
import pioutil
|
||||
if pioutil.is_pio_build():
|
||||
import struct,uuid,marlin
|
||||
|
||||
board = marlin.env.BoardConfig()
|
||||
|
||||
def calculate_crc(contents, seed):
|
||||
accumulating_xor_value = seed
|
||||
|
||||
for i in range(0, len(contents), 4):
|
||||
value = struct.unpack('<I', contents[ i : i + 4])[0]
|
||||
accumulating_xor_value = accumulating_xor_value ^ value
|
||||
return accumulating_xor_value
|
||||
|
||||
def xor_block(r0, r1, block_number, block_size, file_key):
|
||||
# This is the loop counter
|
||||
loop_counter = 0x0
|
||||
|
||||
# This is the key length
|
||||
key_length = 0x18
|
||||
|
||||
# This is an initial seed
|
||||
xor_seed = 0x4BAD
|
||||
|
||||
# This is the block counter
|
||||
block_number = xor_seed * block_number
|
||||
|
||||
#load the xor key from the file
|
||||
r7 = file_key
|
||||
|
||||
for loop_counter in range(0, block_size):
|
||||
# meant to make sure different bits of the key are used.
|
||||
xor_seed = int(loop_counter / key_length)
|
||||
|
||||
# IP is a scratch register / R12
|
||||
ip = loop_counter - (key_length * xor_seed)
|
||||
|
||||
# xor_seed = (loop_counter * loop_counter) + block_number
|
||||
xor_seed = (loop_counter * loop_counter) + block_number
|
||||
|
||||
# shift the xor_seed left by the bits in IP.
|
||||
xor_seed = xor_seed >> ip
|
||||
|
||||
# load a byte into IP
|
||||
ip = r0[loop_counter]
|
||||
|
||||
# XOR the seed with r7
|
||||
xor_seed = xor_seed ^ r7
|
||||
|
||||
# and then with IP
|
||||
xor_seed = xor_seed ^ ip
|
||||
|
||||
#Now store the byte back
|
||||
r1[loop_counter] = xor_seed & 0xFF
|
||||
|
||||
#increment the loop_counter
|
||||
loop_counter = loop_counter + 1
|
||||
|
||||
def encrypt_file(input, output_file, file_length):
|
||||
input_file = bytearray(input.read())
|
||||
block_size = 0x800
|
||||
key_length = 0x18
|
||||
|
||||
uid_value = uuid.uuid4()
|
||||
file_key = int(uid_value.hex[0:8], 16)
|
||||
|
||||
xor_crc = 0xEF3D4323
|
||||
|
||||
# the input file is exepcted to be in chunks of 0x800
|
||||
# so round the size
|
||||
while len(input_file) % block_size != 0:
|
||||
input_file.extend(b'0x0')
|
||||
|
||||
# write the file header
|
||||
output_file.write(struct.pack(">I", 0x443D2D3F))
|
||||
# encrypt the contents using a known file header key
|
||||
|
||||
# write the file_key
|
||||
output_file.write(struct.pack("<I", file_key))
|
||||
|
||||
#TODO - how to enforce that the firmware aligns to block boundaries?
|
||||
block_count = int(len(input_file) / block_size)
|
||||
print ("Block Count is ", block_count)
|
||||
for block_number in range(0, block_count):
|
||||
block_offset = (block_number * block_size)
|
||||
block_end = block_offset + block_size
|
||||
block_array = bytearray(input_file[block_offset: block_end])
|
||||
xor_block(block_array, block_array, block_number, block_size, file_key)
|
||||
for n in range (0, block_size):
|
||||
input_file[block_offset + n] = block_array[n]
|
||||
|
||||
# update the expected CRC value.
|
||||
xor_crc = calculate_crc(block_array, xor_crc)
|
||||
|
||||
# write CRC
|
||||
output_file.write(struct.pack("<I", xor_crc))
|
||||
|
||||
# finally, append the encrypted results.
|
||||
output_file.write(input_file)
|
||||
return
|
||||
|
||||
# Encrypt ${PROGNAME}.bin and save it as 'update.cbd'
|
||||
def encrypt(source, target, env):
|
||||
from pathlib import Path
|
||||
|
||||
fwpath = Path(target[0].path)
|
||||
fwsize = fwpath.stat().st_size
|
||||
|
||||
enname = board.get("build.crypt_chitu")
|
||||
enpath = Path(target[0].dir.path)
|
||||
|
||||
fwfile = fwpath.open("rb")
|
||||
enfile = (enpath / enname).open("wb")
|
||||
|
||||
print(f"Encrypting {fwpath} to {enname}")
|
||||
encrypt_file(fwfile, enfile, fwsize)
|
||||
fwfile.close()
|
||||
enfile.close()
|
||||
fwpath.unlink()
|
||||
|
||||
marlin.relocate_firmware("0x08008800")
|
||||
marlin.add_post_action(encrypt)
|
46
buildroot/share/PlatformIO/scripts/common-cxxflags.py
Normal file
46
buildroot/share/PlatformIO/scripts/common-cxxflags.py
Normal file
@ -0,0 +1,46 @@
|
||||
#
|
||||
# common-cxxflags.py
|
||||
# Convenience script to apply customizations to CPP flags
|
||||
#
|
||||
|
||||
import pioutil
|
||||
if pioutil.is_pio_build():
|
||||
Import("env")
|
||||
|
||||
cxxflags = [
|
||||
# "-Wno-incompatible-pointer-types",
|
||||
# "-Wno-unused-const-variable",
|
||||
# "-Wno-maybe-uninitialized",
|
||||
# "-Wno-sign-compare"
|
||||
]
|
||||
if "teensy" not in env["PIOENV"]:
|
||||
cxxflags += ["-Wno-register"]
|
||||
env.Append(CXXFLAGS=cxxflags)
|
||||
|
||||
#
|
||||
# Add CPU frequency as a compile time constant instead of a runtime variable
|
||||
#
|
||||
def add_cpu_freq():
|
||||
if "BOARD_F_CPU" in env:
|
||||
env["BUILD_FLAGS"].append("-DBOARD_F_CPU=" + env["BOARD_F_CPU"])
|
||||
|
||||
# Useful for JTAG debugging
|
||||
#
|
||||
# It will separate release and debug build folders.
|
||||
# It useful to keep two live versions: a debug version for debugging and another for
|
||||
# release, for flashing when upload is not done automatically by jlink/stlink.
|
||||
# Without this, PIO needs to recompile everything twice for any small change.
|
||||
if env.GetBuildType() == "debug" and env.get("UPLOAD_PROTOCOL") not in ["jlink", "stlink", "custom"]:
|
||||
env["BUILD_DIR"] = "$PROJECT_BUILD_DIR/$PIOENV/debug"
|
||||
|
||||
def on_program_ready(source, target, env):
|
||||
import shutil
|
||||
shutil.copy(target[0].get_abspath(), env.subst("$PROJECT_BUILD_DIR/$PIOENV"))
|
||||
|
||||
env.AddPostAction("$PROGPATH", on_program_ready)
|
||||
|
||||
# On some platform, F_CPU is a runtime variable. Since it's used to convert from ns
|
||||
# to CPU cycles, this adds overhead preventing small delay (in the order of less than
|
||||
# 30 cycles) to be generated correctly. By using a compile time constant instead
|
||||
# the compiler will perform the computation and this overhead will be avoided
|
||||
add_cpu_freq()
|
@ -0,0 +1,16 @@
|
||||
#
|
||||
# post:common-dependencies-post.py
|
||||
# Convenience script to add build flags for Marlin Enabled Features
|
||||
#
|
||||
import pioutil
|
||||
if pioutil.is_pio_build():
|
||||
Import("env", "projenv")
|
||||
|
||||
def apply_board_build_flags():
|
||||
if not 'BOARD_CUSTOM_BUILD_FLAGS' in env['MARLIN_FEATURES']:
|
||||
return
|
||||
projenv.Append(CCFLAGS=env['MARLIN_FEATURES']['BOARD_CUSTOM_BUILD_FLAGS'].split())
|
||||
|
||||
# We need to add the board build flags in a post script
|
||||
# so the platform build script doesn't overwrite the custom CCFLAGS
|
||||
apply_board_build_flags()
|
107
buildroot/share/PlatformIO/scripts/common-dependencies.h
Normal file
107
buildroot/share/PlatformIO/scripts/common-dependencies.h
Normal file
@ -0,0 +1,107 @@
|
||||
/**
|
||||
* Marlin 3D Printer Firmware
|
||||
* Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
|
||||
*
|
||||
* Based on Sprinter and grbl.
|
||||
* Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* The purpose of this file is just include Marlin Configuration files,
|
||||
* to discover which FEATURES are enabled, without any HAL include.
|
||||
* Used by common-dependencies.py
|
||||
*/
|
||||
|
||||
#include "../../../../Marlin/src/inc/MarlinConfig.h"
|
||||
|
||||
//
|
||||
// Conditionals only used for [features]
|
||||
//
|
||||
#if ENABLED(SR_LCD_3W_NL)
|
||||
// Feature checks for SR_LCD_3W_NL
|
||||
#elif ANY(LCD_I2C_TYPE_MCP23017, LCD_I2C_TYPE_MCP23008)
|
||||
#define USES_LIQUIDTWI2
|
||||
#elif ENABLED(LCD_I2C_TYPE_PCA8574)
|
||||
#define USES_LIQUIDCRYSTAL_I2C
|
||||
#elif ANY(HAS_MARLINUI_HD44780, LCD_I2C_TYPE_PCF8575, SR_LCD_2W_NL, LCM1602)
|
||||
#define USES_LIQUIDCRYSTAL
|
||||
#endif
|
||||
|
||||
#if SAVED_POSITIONS
|
||||
#define HAS_SAVED_POSITIONS
|
||||
#endif
|
||||
|
||||
#if ENABLED(DUET_SMART_EFFECTOR) && PIN_EXISTS(SMART_EFFECTOR_MOD)
|
||||
#define HAS_SMART_EFF_MOD
|
||||
#endif
|
||||
|
||||
#if HAS_MARLINUI_MENU
|
||||
#if ENABLED(BACKLASH_GCODE)
|
||||
#define HAS_MENU_BACKLASH
|
||||
#endif
|
||||
#if ENABLED(LCD_BED_TRAMMING)
|
||||
#define HAS_MENU_BED_TRAMMING
|
||||
#endif
|
||||
#if ENABLED(CANCEL_OBJECTS)
|
||||
#define HAS_MENU_CANCELOBJECT
|
||||
#endif
|
||||
#if ANY(DELTA_CALIBRATION_MENU, DELTA_AUTO_CALIBRATION)
|
||||
#define HAS_MENU_DELTA_CALIBRATE
|
||||
#endif
|
||||
#if ANY(LED_CONTROL_MENU, CASE_LIGHT_MENU)
|
||||
#define HAS_MENU_LED
|
||||
#endif
|
||||
#if ENABLED(ADVANCED_PAUSE_FEATURE)
|
||||
#define HAS_MENU_FILAMENT
|
||||
#endif
|
||||
#if HAS_MEDIA
|
||||
#define HAS_MENU_MEDIA
|
||||
#endif
|
||||
#if ENABLED(MIXING_EXTRUDER)
|
||||
#define HAS_MENU_MIXER
|
||||
#endif
|
||||
#if ENABLED(POWER_LOSS_RECOVERY)
|
||||
#define HAS_MENU_JOB_RECOVERY
|
||||
#endif
|
||||
#if HAS_POWER_MONITOR
|
||||
#define HAS_MENU_POWER_MONITOR
|
||||
#endif
|
||||
#if HAS_CUTTER
|
||||
#define HAS_MENU_CUTTER
|
||||
#endif
|
||||
#if HAS_TEMPERATURE
|
||||
#define HAS_MENU_TEMPERATURE
|
||||
#endif
|
||||
#if ENABLED(MMU2_MENUS)
|
||||
#define HAS_MENU_MMU2
|
||||
#endif
|
||||
#if ENABLED(PASSWORD_FEATURE)
|
||||
#define HAS_MENU_PASSWORD
|
||||
#endif
|
||||
#if HAS_TRINAMIC_CONFIG
|
||||
#define HAS_MENU_TMC
|
||||
#endif
|
||||
#if ENABLED(TOUCH_SCREEN_CALIBRATION)
|
||||
#define HAS_MENU_TOUCH_SCREEN
|
||||
#endif
|
||||
#if ENABLED(ASSISTED_TRAMMING_WIZARD)
|
||||
#define HAS_MENU_TRAMMING_WIZARD
|
||||
#endif
|
||||
#if ENABLED(AUTO_BED_LEVELING_UBL)
|
||||
#define HAS_MENU_UBL
|
||||
#endif
|
||||
#endif
|
322
buildroot/share/PlatformIO/scripts/common-dependencies.py
Normal file
322
buildroot/share/PlatformIO/scripts/common-dependencies.py
Normal file
@ -0,0 +1,322 @@
|
||||
#
|
||||
# common-dependencies.py
|
||||
# Convenience script to check dependencies and add libs and sources for Marlin Enabled Features
|
||||
#
|
||||
import pioutil
|
||||
if pioutil.is_pio_build():
|
||||
|
||||
import subprocess,os,re,fnmatch,glob
|
||||
srcfilepattern = re.compile(r".*[.](cpp|c)$")
|
||||
marlinbasedir = os.path.join(os.getcwd(), "Marlin/")
|
||||
Import("env")
|
||||
|
||||
from platformio.package.meta import PackageSpec
|
||||
from platformio.project.config import ProjectConfig
|
||||
|
||||
verbose = 0
|
||||
FEATURE_CONFIG = {}
|
||||
|
||||
def validate_pio():
|
||||
PIO_VERSION_MIN = (6, 0, 1)
|
||||
try:
|
||||
from platformio import VERSION as PIO_VERSION
|
||||
weights = (1000, 100, 1)
|
||||
version_min = sum([x[0] * float(re.sub(r'[^0-9]', '.', str(x[1]))) for x in zip(weights, PIO_VERSION_MIN)])
|
||||
version_cur = sum([x[0] * float(re.sub(r'[^0-9]', '.', str(x[1]))) for x in zip(weights, PIO_VERSION)])
|
||||
if version_cur < version_min:
|
||||
print()
|
||||
print("**************************************************")
|
||||
print("****** An update to PlatformIO is ******")
|
||||
print("****** required to build Marlin Firmware. ******")
|
||||
print("****** ******")
|
||||
print("****** Minimum version: ", PIO_VERSION_MIN, " ******")
|
||||
print("****** Current Version: ", PIO_VERSION, " ******")
|
||||
print("****** ******")
|
||||
print("****** Update PlatformIO and try again. ******")
|
||||
print("**************************************************")
|
||||
print()
|
||||
exit(1)
|
||||
except SystemExit:
|
||||
exit(1)
|
||||
except:
|
||||
print("Can't detect PlatformIO Version")
|
||||
|
||||
def blab(str,level=1):
|
||||
if verbose >= level:
|
||||
print("[deps] %s" % str)
|
||||
|
||||
def add_to_feat_cnf(feature, flines):
|
||||
|
||||
try:
|
||||
feat = FEATURE_CONFIG[feature]
|
||||
except:
|
||||
FEATURE_CONFIG[feature] = {}
|
||||
|
||||
# Get a reference to the FEATURE_CONFIG under construction
|
||||
feat = FEATURE_CONFIG[feature]
|
||||
|
||||
# Split up passed lines on commas or newlines and iterate.
|
||||
# Take care to convert Windows '\' paths to Unix-style '/'.
|
||||
# Add common options to the features config under construction.
|
||||
# For lib_deps replace a previous instance of the same library.
|
||||
atoms = re.sub(r',\s*', '\n', flines.replace('\\', '/')).strip().split('\n')
|
||||
for line in atoms:
|
||||
parts = line.split('=')
|
||||
name = parts.pop(0)
|
||||
if name in ['build_flags', 'extra_scripts', 'build_src_filter', 'lib_ignore']:
|
||||
feat[name] = '='.join(parts)
|
||||
blab("[%s] %s=%s" % (feature, name, feat[name]), 3)
|
||||
else:
|
||||
for dep in re.split(r',\s*', line):
|
||||
lib_name = re.sub(r'@([~^]|[<>]=?)?[\d.]+', '', dep.strip()).split('=').pop(0)
|
||||
lib_re = re.compile('(?!^' + lib_name + '\\b)')
|
||||
if not 'lib_deps' in feat: feat['lib_deps'] = {}
|
||||
feat['lib_deps'] = list(filter(lib_re.match, feat['lib_deps'])) + [dep]
|
||||
blab("[%s] lib_deps = %s" % (feature, dep), 3)
|
||||
|
||||
def load_features():
|
||||
blab("========== Gather [features] entries...")
|
||||
for key in ProjectConfig().items('features'):
|
||||
feature = key[0].upper()
|
||||
if not feature in FEATURE_CONFIG:
|
||||
FEATURE_CONFIG[feature] = { 'lib_deps': [] }
|
||||
add_to_feat_cnf(feature, key[1])
|
||||
|
||||
# Add options matching custom_marlin.MY_OPTION to the pile
|
||||
blab("========== Gather custom_marlin entries...")
|
||||
for n in env.GetProjectOptions():
|
||||
key = n[0]
|
||||
mat = re.match(r'custom_marlin\.(.+)', key)
|
||||
if mat:
|
||||
try:
|
||||
val = env.GetProjectOption(key)
|
||||
except:
|
||||
val = None
|
||||
if val:
|
||||
opt = mat[1].upper()
|
||||
blab("%s.custom_marlin.%s = '%s'" % ( env['PIOENV'], opt, val ), 2)
|
||||
add_to_feat_cnf(opt, val)
|
||||
|
||||
def get_all_known_libs():
|
||||
known_libs = []
|
||||
for feature in FEATURE_CONFIG:
|
||||
feat = FEATURE_CONFIG[feature]
|
||||
if not 'lib_deps' in feat:
|
||||
continue
|
||||
for dep in feat['lib_deps']:
|
||||
known_libs.append(PackageSpec(dep).name)
|
||||
return known_libs
|
||||
|
||||
def get_all_env_libs():
|
||||
env_libs = []
|
||||
lib_deps = env.GetProjectOption('lib_deps')
|
||||
for dep in lib_deps:
|
||||
env_libs.append(PackageSpec(dep).name)
|
||||
return env_libs
|
||||
|
||||
def set_env_field(field, value):
|
||||
proj = env.GetProjectConfig()
|
||||
proj.set("env:" + env['PIOENV'], field, value)
|
||||
|
||||
# All unused libs should be ignored so that if a library
|
||||
# exists in .pio/lib_deps it will not break compilation.
|
||||
def force_ignore_unused_libs():
|
||||
env_libs = get_all_env_libs()
|
||||
known_libs = get_all_known_libs()
|
||||
diff = (list(set(known_libs) - set(env_libs)))
|
||||
lib_ignore = env.GetProjectOption('lib_ignore') + diff
|
||||
blab("Ignore libraries: %s" % lib_ignore)
|
||||
set_env_field('lib_ignore', lib_ignore)
|
||||
|
||||
def apply_features_config():
|
||||
load_features()
|
||||
blab("========== Apply enabled features...")
|
||||
build_filters = ' '.join(env.GetProjectOption('build_src_filter'))
|
||||
for feature in FEATURE_CONFIG:
|
||||
if not env.MarlinHas(feature):
|
||||
continue
|
||||
|
||||
feat = FEATURE_CONFIG[feature]
|
||||
|
||||
if 'lib_deps' in feat and len(feat['lib_deps']):
|
||||
blab("========== Adding lib_deps for %s... " % feature, 2)
|
||||
|
||||
# feat to add
|
||||
deps_to_add = {}
|
||||
for dep in feat['lib_deps']:
|
||||
deps_to_add[PackageSpec(dep).name] = dep
|
||||
blab("==================== %s... " % dep, 2)
|
||||
|
||||
# Does the env already have the dependency?
|
||||
deps = env.GetProjectOption('lib_deps')
|
||||
for dep in deps:
|
||||
name = PackageSpec(dep).name
|
||||
if name in deps_to_add:
|
||||
del deps_to_add[name]
|
||||
|
||||
# Are there any libraries that should be ignored?
|
||||
lib_ignore = env.GetProjectOption('lib_ignore')
|
||||
for dep in deps:
|
||||
name = PackageSpec(dep).name
|
||||
if name in deps_to_add:
|
||||
del deps_to_add[name]
|
||||
|
||||
# Is there anything left?
|
||||
if len(deps_to_add) > 0:
|
||||
# Only add the missing dependencies
|
||||
set_env_field('lib_deps', deps + list(deps_to_add.values()))
|
||||
|
||||
if 'build_flags' in feat:
|
||||
f = feat['build_flags']
|
||||
blab("========== Adding build_flags for %s: %s" % (feature, f), 2)
|
||||
new_flags = env.GetProjectOption('build_flags') + [ f ]
|
||||
env.Replace(BUILD_FLAGS=new_flags)
|
||||
|
||||
if 'extra_scripts' in feat:
|
||||
blab("Running extra_scripts for %s... " % feature, 2)
|
||||
env.SConscript(feat['extra_scripts'], exports="env")
|
||||
|
||||
if 'build_src_filter' in feat:
|
||||
blab("========== Adding build_src_filter for %s... " % feature, 2)
|
||||
build_filters = build_filters + ' ' + feat['build_src_filter']
|
||||
# Just append the filter in the order that the build environment specifies.
|
||||
# Important here is the order of entries in the "features.ini" file.
|
||||
|
||||
if 'lib_ignore' in feat:
|
||||
blab("========== Adding lib_ignore for %s... " % feature, 2)
|
||||
lib_ignore = env.GetProjectOption('lib_ignore') + [feat['lib_ignore']]
|
||||
set_env_field('lib_ignore', lib_ignore)
|
||||
|
||||
build_src_filter = ""
|
||||
if True:
|
||||
# Build the actual equivalent build_src_filter list based on the inclusions by the features.
|
||||
# PlatformIO doesn't do it this way, but maybe in the future....
|
||||
cur_srcs = set()
|
||||
# Remove the references to the same folder
|
||||
my_srcs = re.findall(r'([+-]<.*?>)', build_filters)
|
||||
for d in my_srcs:
|
||||
# Assume normalized relative paths
|
||||
plain = d[2:-1]
|
||||
if d[0] == '+':
|
||||
def addentry(fullpath, info=None):
|
||||
relp = os.path.relpath(fullpath, marlinbasedir)
|
||||
if srcfilepattern.match(relp):
|
||||
if info:
|
||||
blab("Added src file %s (%s)" % (relp, str(info)), 3)
|
||||
else:
|
||||
blab("Added src file %s " % relp, 3)
|
||||
cur_srcs.add(relp)
|
||||
# Special rule: If a direct folder is specified add all files within.
|
||||
fullplain = os.path.join(marlinbasedir, plain)
|
||||
if os.path.isdir(fullplain):
|
||||
blab("Directory content addition for %s " % plain, 3)
|
||||
gpattern = os.path.join(fullplain, "**")
|
||||
for fname in glob.glob(gpattern, recursive=True):
|
||||
addentry(fname, "dca")
|
||||
else:
|
||||
# Add all the things from the pattern by GLOB.
|
||||
def srepl(matchi):
|
||||
g0 = matchi.group(0)
|
||||
return r"**" + g0[1:]
|
||||
gpattern = re.sub(r'[*]($|[^*])', srepl, plain)
|
||||
gpattern = os.path.join(marlinbasedir, gpattern)
|
||||
|
||||
for fname in glob.glob(gpattern, recursive=True):
|
||||
addentry(fname)
|
||||
else:
|
||||
# Special rule: If a direct folder is specified then remove all files within.
|
||||
def onremove(relp, info=None):
|
||||
if info:
|
||||
blab("Removed src file %s (%s)" % (relp, str(info)), 3)
|
||||
else:
|
||||
blab("Removed src file %s " % relp, 3)
|
||||
fullplain = os.path.join(marlinbasedir, plain)
|
||||
if os.path.isdir(fullplain):
|
||||
blab("Directory content removal for %s " % plain, 2)
|
||||
def filt(x):
|
||||
common = os.path.commonpath([plain, x])
|
||||
if not common == os.path.normpath(plain): return True
|
||||
onremove(x, "dcr")
|
||||
return False
|
||||
cur_srcs = set(filter(filt, cur_srcs))
|
||||
else:
|
||||
# Remove matching source entries.
|
||||
def filt(x):
|
||||
if not fnmatch.fnmatch(x, plain): return True
|
||||
onremove(x)
|
||||
return False
|
||||
cur_srcs = set(filter(filt, cur_srcs))
|
||||
# Transform the resulting set into a string.
|
||||
for x in cur_srcs:
|
||||
if build_src_filter != "": build_src_filter += ' '
|
||||
build_src_filter += "+<" + x + ">"
|
||||
|
||||
#blab("Final build_src_filter: " + build_src_filter, 3)
|
||||
else:
|
||||
build_src_filter = build_filters
|
||||
|
||||
# Update in PlatformIO
|
||||
set_env_field('build_src_filter', [build_src_filter])
|
||||
env.Replace(SRC_FILTER=build_src_filter)
|
||||
|
||||
#
|
||||
# Use the compiler to get a list of all enabled features
|
||||
#
|
||||
def load_marlin_features():
|
||||
if 'MARLIN_FEATURES' in env:
|
||||
return
|
||||
|
||||
# Process defines
|
||||
from preprocessor import run_preprocessor
|
||||
define_list = run_preprocessor(env)
|
||||
marlin_features = {}
|
||||
for define in define_list:
|
||||
feature = define[8:].strip().decode().split(' ')
|
||||
feature, definition = feature[0], ' '.join(feature[1:])
|
||||
marlin_features[feature] = definition
|
||||
env['MARLIN_FEATURES'] = marlin_features
|
||||
|
||||
#
|
||||
# Return True if a matching feature is enabled
|
||||
#
|
||||
def MarlinHas(env, feature):
|
||||
load_marlin_features()
|
||||
r = re.compile('^' + feature + '$', re.IGNORECASE)
|
||||
found = list(filter(r.match, env['MARLIN_FEATURES']))
|
||||
|
||||
# Defines could still be 'false' or '0', so check
|
||||
some_on = False
|
||||
if len(found):
|
||||
for f in found:
|
||||
val = env['MARLIN_FEATURES'][f]
|
||||
if val in [ '', '1', 'true' ]:
|
||||
some_on = True
|
||||
elif val in env['MARLIN_FEATURES']:
|
||||
some_on = env.MarlinHas(val)
|
||||
|
||||
#blab("%s is %s" % (feature, str(some_on)), 2)
|
||||
|
||||
return some_on
|
||||
|
||||
validate_pio()
|
||||
|
||||
try:
|
||||
verbose = int(env.GetProjectOption('custom_verbose'))
|
||||
except:
|
||||
pass
|
||||
|
||||
#
|
||||
# Add a method for other PIO scripts to query enabled features
|
||||
#
|
||||
env.AddMethod(MarlinHas)
|
||||
|
||||
#
|
||||
# Add dependencies for enabled Marlin features
|
||||
#
|
||||
apply_features_config()
|
||||
force_ignore_unused_libs()
|
||||
|
||||
#print(env.Dump())
|
||||
|
||||
from signature import compute_build_signature
|
||||
compute_build_signature(env)
|
291
buildroot/share/PlatformIO/scripts/configuration.py
Executable file
291
buildroot/share/PlatformIO/scripts/configuration.py
Executable file
@ -0,0 +1,291 @@
|
||||
#!/usr/bin/env python3
|
||||
#
|
||||
# configuration.py
|
||||
# Apply options from config.ini to the existing Configuration headers
|
||||
#
|
||||
import re, shutil, configparser, datetime
|
||||
from pathlib import Path
|
||||
|
||||
verbose = 0
|
||||
def blab(str,level=1):
|
||||
if verbose >= level: print(f"[config] {str}")
|
||||
|
||||
def config_path(cpath):
|
||||
return Path("Marlin", cpath, encoding='utf-8')
|
||||
|
||||
# Apply a single name = on/off ; name = value ; etc.
|
||||
# TODO: Limit to the given (optional) configuration
|
||||
def apply_opt(name, val, conf=None):
|
||||
if name == "lcd": name, val = val, "on"
|
||||
|
||||
# Create a regex to match the option and capture parts of the line
|
||||
# 1: Indentation
|
||||
# 2: Comment
|
||||
# 3: #define and whitespace
|
||||
# 4: Option name
|
||||
# 5: First space after name
|
||||
# 6: Remaining spaces between name and value
|
||||
# 7: Option value
|
||||
# 8: Whitespace after value
|
||||
# 9: End comment
|
||||
regex = re.compile(rf'^(\s*)(//\s*)?(#define\s+)({name}\b)(\s?)(\s*)(.*?)(\s*)(//.*)?$', re.IGNORECASE)
|
||||
|
||||
# Find and enable and/or update all matches
|
||||
for file in ("Configuration.h", "Configuration_adv.h"):
|
||||
fullpath = config_path(file)
|
||||
lines = fullpath.read_text(encoding='utf-8').split('\n')
|
||||
found = False
|
||||
for i in range(len(lines)):
|
||||
line = lines[i]
|
||||
match = regex.match(line)
|
||||
if match and match[4].upper() == name.upper():
|
||||
found = True
|
||||
# For boolean options un/comment the define
|
||||
if val in ("on", "", None):
|
||||
newline = re.sub(r'^(\s*)//+\s*(#define)(\s{1,3})?(\s*)', r'\1\2 \4', line)
|
||||
elif val == "off":
|
||||
# TODO: Comment more lines in a multi-line define with \ continuation
|
||||
newline = re.sub(r'^(\s*)(#define)(\s{1,3})?(\s*)', r'\1//\2 \4', line)
|
||||
else:
|
||||
# For options with values, enable and set the value
|
||||
addsp = '' if match[5] else ' '
|
||||
newline = match[1] + match[3] + match[4] + match[5] + addsp + val + match[6]
|
||||
if match[9]:
|
||||
sp = match[8] if match[8] else ' '
|
||||
newline += sp + match[9]
|
||||
lines[i] = newline
|
||||
blab(f"Set {name} to {val}")
|
||||
|
||||
# If the option was found, write the modified lines
|
||||
if found:
|
||||
fullpath.write_text('\n'.join(lines), encoding='utf-8')
|
||||
break
|
||||
|
||||
# If the option didn't appear in either config file, add it
|
||||
if not found:
|
||||
# OFF options are added as disabled items so they appear
|
||||
# in config dumps. Useful for custom settings.
|
||||
prefix = ""
|
||||
if val == "off":
|
||||
prefix, val = "//", "" # Item doesn't appear in config dump
|
||||
#val = "false" # Item appears in config dump
|
||||
|
||||
# Uppercase the option unless already mixed/uppercase
|
||||
added = name.upper() if name.islower() else name
|
||||
|
||||
# Add the provided value after the name
|
||||
if val != "on" and val != "" and val is not None:
|
||||
added += " " + val
|
||||
|
||||
# Prepend the new option after the first set of #define lines
|
||||
fullpath = config_path("Configuration.h")
|
||||
with fullpath.open(encoding='utf-8') as f:
|
||||
lines = f.readlines()
|
||||
linenum = 0
|
||||
gotdef = False
|
||||
for line in lines:
|
||||
isdef = line.startswith("#define")
|
||||
if not gotdef:
|
||||
gotdef = isdef
|
||||
elif not isdef:
|
||||
break
|
||||
linenum += 1
|
||||
currtime = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||||
lines.insert(linenum, f"{prefix}#define {added:30} // Added by config.ini {currtime}\n")
|
||||
fullpath.write_text(''.join(lines), encoding='utf-8')
|
||||
|
||||
# Disable all (most) defined options in the configuration files.
|
||||
# Everything in the named sections. Section hint for exceptions may be added.
|
||||
def disable_all_options():
|
||||
# Create a regex to match the option and capture parts of the line
|
||||
regex = re.compile(r'^(\s*)(#define\s+)([A-Z0-9_]+\b)(\s?)(\s*)(.*?)(\s*)(//.*)?$', re.IGNORECASE)
|
||||
|
||||
# Disable all enabled options in both Config files
|
||||
for file in ("Configuration.h", "Configuration_adv.h"):
|
||||
fullpath = config_path(file)
|
||||
lines = fullpath.read_text(encoding='utf-8').split('\n')
|
||||
found = False
|
||||
for i in range(len(lines)):
|
||||
line = lines[i]
|
||||
match = regex.match(line)
|
||||
if match:
|
||||
name = match[3].upper()
|
||||
if name in ('CONFIGURATION_H_VERSION', 'CONFIGURATION_ADV_H_VERSION'): continue
|
||||
if name.startswith('_'): continue
|
||||
found = True
|
||||
# Comment out the define
|
||||
# TODO: Comment more lines in a multi-line define with \ continuation
|
||||
lines[i] = re.sub(r'^(\s*)(#define)(\s{1,3})?(\s*)', r'\1//\2 \4', line)
|
||||
blab(f"Disable {name}")
|
||||
|
||||
# If the option was found, write the modified lines
|
||||
if found:
|
||||
fullpath.write_text('\n'.join(lines), encoding='utf-8')
|
||||
|
||||
# Fetch configuration files from GitHub given the path.
|
||||
# Return True if any files were fetched.
|
||||
def fetch_example(url):
|
||||
if url.endswith("/"): url = url[:-1]
|
||||
if not url.startswith('http'):
|
||||
brch = "bugfix-2.1.x"
|
||||
if '@' in url: url, brch = map(str.strip, url.split('@'))
|
||||
if url == 'examples/default': url = 'default'
|
||||
url = f"https://raw.githubusercontent.com/MarlinFirmware/Configurations/{brch}/config/{url}"
|
||||
url = url.replace("%", "%25").replace(" ", "%20")
|
||||
|
||||
# Find a suitable fetch command
|
||||
if shutil.which("curl") is not None:
|
||||
fetch = "curl -L -s -S -f -o"
|
||||
elif shutil.which("wget") is not None:
|
||||
fetch = "wget -q -O"
|
||||
else:
|
||||
blab("Couldn't find curl or wget", -1)
|
||||
return False
|
||||
|
||||
import os
|
||||
|
||||
# Reset configurations to default
|
||||
os.system("git checkout HEAD Marlin/*.h")
|
||||
|
||||
# Try to fetch the remote files
|
||||
gotfile = False
|
||||
for fn in ("Configuration.h", "Configuration_adv.h", "_Bootscreen.h", "_Statusscreen.h"):
|
||||
if os.system(f"{fetch} wgot {url}/{fn} >/dev/null 2>&1") == 0:
|
||||
shutil.move('wgot', config_path(fn))
|
||||
gotfile = True
|
||||
|
||||
if Path('wgot').exists(): shutil.rmtree('wgot')
|
||||
|
||||
return gotfile
|
||||
|
||||
def section_items(cp, sectkey):
|
||||
return cp.items(sectkey) if sectkey in cp.sections() else []
|
||||
|
||||
# Apply all items from a config section. Ignore ini_ items outside of config:base and config:root.
|
||||
def apply_ini_by_name(cp, sect):
|
||||
iniok = True
|
||||
if sect in ('config:base', 'config:root'):
|
||||
iniok = False
|
||||
items = section_items(cp, 'config:base') + section_items(cp, 'config:root')
|
||||
else:
|
||||
items = section_items(cp, sect)
|
||||
|
||||
for item in items:
|
||||
if iniok or not item[0].startswith('ini_'):
|
||||
apply_opt(item[0], item[1])
|
||||
|
||||
# Apply all config sections from a parsed file
|
||||
def apply_all_sections(cp):
|
||||
for sect in cp.sections():
|
||||
if sect.startswith('config:'):
|
||||
apply_ini_by_name(cp, sect)
|
||||
|
||||
# Apply certain config sections from a parsed file
|
||||
def apply_sections(cp, ckey='all'):
|
||||
blab(f"Apply section key: {ckey}")
|
||||
if ckey == 'all':
|
||||
apply_all_sections(cp)
|
||||
else:
|
||||
# Apply the base/root config.ini settings after external files are done
|
||||
if ckey in ('base', 'root'):
|
||||
apply_ini_by_name(cp, 'config:base')
|
||||
|
||||
# Apply historically 'Configuration.h' settings everywhere
|
||||
if ckey == 'basic':
|
||||
apply_ini_by_name(cp, 'config:basic')
|
||||
|
||||
# Apply historically Configuration_adv.h settings everywhere
|
||||
# (Some of which rely on defines in 'Conditionals_LCD.h')
|
||||
elif ckey in ('adv', 'advanced'):
|
||||
apply_ini_by_name(cp, 'config:advanced')
|
||||
|
||||
# Apply a specific config:<name> section directly
|
||||
elif ckey.startswith('config:'):
|
||||
apply_ini_by_name(cp, ckey)
|
||||
|
||||
# Apply settings from a top level config.ini
|
||||
def apply_config_ini(cp):
|
||||
blab("=" * 20 + " Gather 'config.ini' entries...")
|
||||
|
||||
# Pre-scan for ini_use_config to get config_keys
|
||||
base_items = section_items(cp, 'config:base') + section_items(cp, 'config:root')
|
||||
config_keys = ['base']
|
||||
for ikey, ival in base_items:
|
||||
if ikey == 'ini_use_config':
|
||||
config_keys = map(str.strip, ival.split(','))
|
||||
|
||||
# For each ini_use_config item perform an action
|
||||
for ckey in config_keys:
|
||||
addbase = False
|
||||
|
||||
# For a key ending in .ini load and parse another .ini file
|
||||
if ckey.endswith('.ini'):
|
||||
sect = 'base'
|
||||
if '@' in ckey: sect, ckey = map(str.strip, ckey.split('@'))
|
||||
cp2 = configparser.ConfigParser()
|
||||
cp2.read(config_path(ckey))
|
||||
apply_sections(cp2, sect)
|
||||
ckey = 'base'
|
||||
|
||||
# (Allow 'example/' as a shortcut for 'examples/')
|
||||
elif ckey.startswith('example/'):
|
||||
ckey = 'examples' + ckey[7:]
|
||||
|
||||
# For 'examples/<path>' fetch an example set from GitHub.
|
||||
# For https?:// do a direct fetch of the URL.
|
||||
if ckey.startswith('examples/') or ckey.startswith('http'):
|
||||
fetch_example(ckey)
|
||||
ckey = 'base'
|
||||
|
||||
#
|
||||
# [flatten] Write out Configuration.h and Configuration_adv.h files with
|
||||
# just the enabled options and all other content removed.
|
||||
#
|
||||
#if ckey == '[flatten]':
|
||||
# write_flat_configs()
|
||||
|
||||
if ckey == '[disable]':
|
||||
disable_all_options()
|
||||
|
||||
elif ckey == 'all':
|
||||
apply_sections(cp)
|
||||
|
||||
else:
|
||||
# Apply keyed sections after external files are done
|
||||
apply_sections(cp, 'config:' + ckey)
|
||||
|
||||
if __name__ == "__main__":
|
||||
#
|
||||
# From command line use the given file name
|
||||
#
|
||||
import sys
|
||||
args = sys.argv[1:]
|
||||
if len(args) > 0:
|
||||
if args[0].endswith('.ini'):
|
||||
ini_file = args[0]
|
||||
else:
|
||||
print("Usage: %s <.ini file>" % sys.argv[0])
|
||||
else:
|
||||
ini_file = config_path('config.ini')
|
||||
|
||||
if ini_file:
|
||||
user_ini = configparser.ConfigParser()
|
||||
user_ini.read(ini_file)
|
||||
apply_config_ini(user_ini)
|
||||
|
||||
else:
|
||||
#
|
||||
# From within PlatformIO use the loaded INI file
|
||||
#
|
||||
import pioutil
|
||||
if pioutil.is_pio_build():
|
||||
|
||||
Import("env")
|
||||
|
||||
try:
|
||||
verbose = int(env.GetProjectOption('custom_verbose'))
|
||||
except:
|
||||
pass
|
||||
|
||||
from platformio.project.config import ProjectConfig
|
||||
apply_config_ini(ProjectConfig())
|
18
buildroot/share/PlatformIO/scripts/custom_board.py
Normal file
18
buildroot/share/PlatformIO/scripts/custom_board.py
Normal file
@ -0,0 +1,18 @@
|
||||
#
|
||||
# custom_board.py
|
||||
#
|
||||
# - For build.address replace VECT_TAB_ADDR to relocate the firmware
|
||||
# - For build.ldscript use one of the linker scripts in buildroot/share/PlatformIO/ldscripts
|
||||
#
|
||||
import pioutil
|
||||
if pioutil.is_pio_build():
|
||||
import marlin
|
||||
board = marlin.env.BoardConfig()
|
||||
|
||||
address = board.get("build.address", "")
|
||||
if address:
|
||||
marlin.relocate_firmware(address)
|
||||
|
||||
ldscript = board.get("build.ldscript", "")
|
||||
if ldscript:
|
||||
marlin.custom_ld_script(ldscript)
|
53
buildroot/share/PlatformIO/scripts/download_mks_assets.py
Normal file
53
buildroot/share/PlatformIO/scripts/download_mks_assets.py
Normal file
@ -0,0 +1,53 @@
|
||||
#
|
||||
# download_mks_assets.py
|
||||
# Added by HAS_TFT_LVGL_UI to download assets from Makerbase repo
|
||||
#
|
||||
import pioutil
|
||||
if pioutil.is_pio_build():
|
||||
Import("env")
|
||||
import requests,zipfile,tempfile,shutil
|
||||
from pathlib import Path
|
||||
|
||||
url = "https://github.com/makerbase-mks/Mks-Robin-Nano-Marlin2.0-Firmware/archive/0263cdaccf.zip"
|
||||
deps_path = Path(env.Dictionary("PROJECT_LIBDEPS_DIR"))
|
||||
zip_path = deps_path / "mks-assets.zip"
|
||||
assets_path = Path(env.Dictionary("PROJECT_BUILD_DIR"), env.Dictionary("PIOENV"), "assets")
|
||||
|
||||
def download_mks_assets():
|
||||
print("Downloading MKS Assets for TFT_LVGL_UI")
|
||||
r = requests.get(url, stream=True)
|
||||
# the user may have a very clean workspace,
|
||||
# so create the PROJECT_LIBDEPS_DIR directory if not exits
|
||||
if not deps_path.exists():
|
||||
deps_path.mkdir()
|
||||
with zip_path.open('wb') as fd:
|
||||
for chunk in r.iter_content(chunk_size=128):
|
||||
fd.write(chunk)
|
||||
|
||||
def copy_mks_assets():
|
||||
print("Copying MKS Assets for TFT_LVGL_UI")
|
||||
output_path = Path(tempfile.mkdtemp())
|
||||
zip_obj = zipfile.ZipFile(zip_path, 'r')
|
||||
zip_obj.extractall(output_path)
|
||||
zip_obj.close()
|
||||
if assets_path.exists() and not assets_path.is_dir():
|
||||
assets_path.unlink()
|
||||
if not assets_path.exists():
|
||||
assets_path.mkdir()
|
||||
base_path = ''
|
||||
for filename in output_path.iterdir():
|
||||
base_path = filename
|
||||
fw_path = (output_path / base_path / 'Firmware')
|
||||
font_path = fw_path / 'mks_font'
|
||||
for filename in font_path.iterdir():
|
||||
shutil.copy(font_path / filename, assets_path)
|
||||
pic_path = fw_path / 'mks_pic'
|
||||
for filename in pic_path.iterdir():
|
||||
shutil.copy(pic_path / filename, assets_path)
|
||||
shutil.rmtree(output_path, ignore_errors=True)
|
||||
|
||||
if not zip_path.exists():
|
||||
download_mks_assets()
|
||||
|
||||
if not assets_path.exists():
|
||||
copy_mks_assets()
|
104
buildroot/share/PlatformIO/scripts/exc.S
Normal file
104
buildroot/share/PlatformIO/scripts/exc.S
Normal file
@ -0,0 +1,104 @@
|
||||
/* *****************************************************************************
|
||||
* The MIT License
|
||||
*
|
||||
* Copyright (c) 2010 Perry Hung.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
* ****************************************************************************/
|
||||
|
||||
# On an exception, push a fake stack thread mode stack frame and redirect
|
||||
# thread execution to a thread mode error handler
|
||||
|
||||
# From RM008:
|
||||
# The SP is decremented by eight words by the completion of the stack push.
|
||||
# Figure 5-1 shows the contents of the stack after an exception pre-empts the
|
||||
# current program flow.
|
||||
#
|
||||
# Old SP--> <previous>
|
||||
# xPSR
|
||||
# PC
|
||||
# LR
|
||||
# r12
|
||||
# r3
|
||||
# r2
|
||||
# r1
|
||||
# SP--> r0
|
||||
|
||||
.text
|
||||
.globl __exc_nmi
|
||||
.weak __exc_nmi
|
||||
.globl __exc_hardfault
|
||||
.weak __exc_hardfault
|
||||
.globl __exc_memmanage
|
||||
.weak __exc_memmanage
|
||||
.globl __exc_busfault
|
||||
.weak __exc_busfault
|
||||
.globl __exc_usagefault
|
||||
.weak __exc_usagefault
|
||||
|
||||
.code 16
|
||||
.thumb_func
|
||||
__exc_nmi:
|
||||
mov r0, #1
|
||||
b __default_exc
|
||||
|
||||
.thumb_func
|
||||
__exc_hardfault:
|
||||
mov r0, #2
|
||||
b __default_exc
|
||||
|
||||
.thumb_func
|
||||
__exc_memmanage:
|
||||
mov r0, #3
|
||||
b __default_exc
|
||||
|
||||
.thumb_func
|
||||
__exc_busfault:
|
||||
mov r0, #4
|
||||
b __default_exc
|
||||
|
||||
.thumb_func
|
||||
__exc_usagefault:
|
||||
mov r0, #5
|
||||
b __default_exc
|
||||
|
||||
.thumb_func
|
||||
__default_exc:
|
||||
ldr r2, NVIC_CCR @ Enable returning to thread mode even if there are
|
||||
mov r1 ,#1 @ pending exceptions. See flag NONEBASETHRDENA.
|
||||
str r1, [r2]
|
||||
cpsid i @ Disable global interrupts
|
||||
ldr r2, SYSTICK_CSR @ Disable systick handler
|
||||
mov r1, #0
|
||||
str r1, [r2]
|
||||
ldr r1, CPSR_MASK @ Set default CPSR
|
||||
push {r1}
|
||||
ldr r1, TARGET_PC @ Set target pc
|
||||
push {r1}
|
||||
sub sp, sp, #24 @ Don't care
|
||||
ldr r1, EXC_RETURN @ Return to thread mode
|
||||
mov lr, r1
|
||||
bx lr @ Exception exit
|
||||
|
||||
.align 4
|
||||
CPSR_MASK: .word 0x61000000
|
||||
EXC_RETURN: .word 0xFFFFFFF9
|
||||
TARGET_PC: .word __error
|
||||
NVIC_CCR: .word 0xE000ED14 @ NVIC configuration control register
|
||||
SYSTICK_CSR: .word 0xE000E010 @ Systick control register
|
35
buildroot/share/PlatformIO/scripts/fix_framework_weakness.py
Normal file
35
buildroot/share/PlatformIO/scripts/fix_framework_weakness.py
Normal file
@ -0,0 +1,35 @@
|
||||
#
|
||||
# fix_framework_weakness.py
|
||||
#
|
||||
import pioutil
|
||||
if pioutil.is_pio_build():
|
||||
|
||||
import shutil
|
||||
from os.path import join, isfile
|
||||
from pprint import pprint
|
||||
|
||||
Import("env")
|
||||
|
||||
if env.MarlinHas("POSTMORTEM_DEBUGGING"):
|
||||
FRAMEWORK_DIR = env.PioPlatform().get_package_dir("framework-arduinoststm32-maple")
|
||||
patchflag_path = join(FRAMEWORK_DIR, ".exc-patching-done")
|
||||
|
||||
# patch file only if we didn't do it before
|
||||
if not isfile(patchflag_path):
|
||||
print("Patching libmaple exception handlers")
|
||||
original_file = join(FRAMEWORK_DIR, "STM32F1", "cores", "maple", "libmaple", "exc.S")
|
||||
backup_file = join(FRAMEWORK_DIR, "STM32F1", "cores", "maple", "libmaple", "exc.S.bak")
|
||||
src_file = join("buildroot", "share", "PlatformIO", "scripts", "exc.S")
|
||||
|
||||
assert isfile(original_file) and isfile(src_file)
|
||||
shutil.copyfile(original_file, backup_file)
|
||||
shutil.copyfile(src_file, original_file)
|
||||
|
||||
def _touch(path):
|
||||
with open(path, "w") as fp:
|
||||
fp.write("")
|
||||
|
||||
env.Execute(lambda *args, **kwargs: _touch(patchflag_path))
|
||||
print("Done patching exception handler")
|
||||
|
||||
print("Libmaple modified and ready for post mortem debugging")
|
65
buildroot/share/PlatformIO/scripts/generic_create_variant.py
Normal file
65
buildroot/share/PlatformIO/scripts/generic_create_variant.py
Normal file
@ -0,0 +1,65 @@
|
||||
#
|
||||
# generic_create_variant.py
|
||||
#
|
||||
# Copy one of the variants from buildroot/platformio/variants into
|
||||
# the appropriate framework variants folder, so that its contents
|
||||
# will be picked up by PlatformIO just like any other variant.
|
||||
#
|
||||
import pioutil, re
|
||||
marlin_variant_pattern = re.compile("marlin_.*")
|
||||
if pioutil.is_pio_build():
|
||||
import shutil,marlin
|
||||
from pathlib import Path
|
||||
|
||||
#
|
||||
# Get the platform name from the 'platform_packages' option,
|
||||
# or look it up by the platform.class.name.
|
||||
#
|
||||
env = marlin.env
|
||||
platform = env.PioPlatform()
|
||||
|
||||
from platformio.package.meta import PackageSpec
|
||||
platform_packages = env.GetProjectOption('platform_packages')
|
||||
|
||||
# Remove all tool items from platform_packages
|
||||
platform_packages = [x for x in platform_packages if not x.startswith("platformio/tool-")]
|
||||
|
||||
if len(platform_packages) == 0:
|
||||
framewords = {
|
||||
"Ststm32Platform": "framework-arduinoststm32",
|
||||
"AtmelavrPlatform": "framework-arduino-avr"
|
||||
}
|
||||
platform_name = framewords[platform.__class__.__name__]
|
||||
else:
|
||||
spec = PackageSpec(platform_packages[0])
|
||||
if spec.uri and '@' in spec.uri:
|
||||
platform_name = re.sub(r'@.+', '', spec.uri)
|
||||
else:
|
||||
platform_name = spec.name
|
||||
|
||||
FRAMEWORK_DIR = Path(platform.get_package_dir(platform_name))
|
||||
assert FRAMEWORK_DIR.is_dir()
|
||||
|
||||
board = env.BoardConfig()
|
||||
|
||||
#mcu_type = board.get("build.mcu")[:-2]
|
||||
variant = board.get("build.variant")
|
||||
#series = mcu_type[:7].upper() + "xx"
|
||||
|
||||
# Only prepare a new variant if the PlatformIO configuration provides it (board_build.variant).
|
||||
# This check is important to avoid deleting official board config variants.
|
||||
if marlin_variant_pattern.match(str(variant).lower()):
|
||||
# Prepare a new empty folder at the destination
|
||||
variant_dir = FRAMEWORK_DIR / "variants" / variant
|
||||
if variant_dir.is_dir():
|
||||
shutil.rmtree(variant_dir)
|
||||
if not variant_dir.is_dir():
|
||||
variant_dir.mkdir()
|
||||
|
||||
# Source dir is a local variant sub-folder
|
||||
source_dir = Path("buildroot/share/PlatformIO/variants", variant)
|
||||
assert source_dir.is_dir()
|
||||
|
||||
print("Copying variant " + str(variant) + " to framework directory...")
|
||||
|
||||
marlin.copytree(source_dir, variant_dir)
|
@ -0,0 +1,35 @@
|
||||
#
|
||||
# jgaurora_a5s_a1_with_bootloader.py
|
||||
# Customizations for env:jgaurora_a5s_a1
|
||||
#
|
||||
import pioutil
|
||||
if pioutil.is_pio_build():
|
||||
|
||||
# Append ${PROGNAME}.bin firmware after bootloader and save it as 'jgaurora_firmware.bin'
|
||||
def addboot(source, target, env):
|
||||
from pathlib import Path
|
||||
|
||||
fw_path = Path(target[0].path)
|
||||
fwb_path = fw_path.parent / 'firmware_with_bootloader.bin'
|
||||
with fwb_path.open("wb") as fwb_file:
|
||||
bl_path = Path("buildroot/share/PlatformIO/scripts/jgaurora_bootloader.bin")
|
||||
bl_file = bl_path.open("rb")
|
||||
while True:
|
||||
b = bl_file.read(1)
|
||||
if b == b'': break
|
||||
else: fwb_file.write(b)
|
||||
|
||||
with fw_path.open("rb") as fw_file:
|
||||
while True:
|
||||
b = fw_file.read(1)
|
||||
if b == b'': break
|
||||
else: fwb_file.write(b)
|
||||
|
||||
fws_path = Path(target[0].dir.path, 'firmware_for_sd_upload.bin')
|
||||
if fws_path.exists():
|
||||
fws_path.unlink()
|
||||
|
||||
fw_path.rename(fws_path)
|
||||
|
||||
import marlin
|
||||
marlin.add_post_action(addboot)
|
BIN
buildroot/share/PlatformIO/scripts/jgaurora_bootloader.bin
Normal file
BIN
buildroot/share/PlatformIO/scripts/jgaurora_bootloader.bin
Normal file
Binary file not shown.
47
buildroot/share/PlatformIO/scripts/lerdge.py
Normal file
47
buildroot/share/PlatformIO/scripts/lerdge.py
Normal file
@ -0,0 +1,47 @@
|
||||
#
|
||||
# lerdge.py
|
||||
# Customizations for Lerdge build environments:
|
||||
# env:LERDGEX env:LERDGEX_usb_flash_drive
|
||||
# env:LERDGES env:LERDGES_usb_flash_drive
|
||||
# env:LERDGEK env:LERDGEK_usb_flash_drive
|
||||
#
|
||||
import pioutil
|
||||
if pioutil.is_pio_build():
|
||||
import os,marlin
|
||||
|
||||
board = marlin.env.BoardConfig()
|
||||
|
||||
def encryptByte(byte):
|
||||
byte = 0xFF & ((byte << 6) | (byte >> 2))
|
||||
i = 0x58 + byte
|
||||
j = 0x05 + byte + (i >> 8)
|
||||
byte = (0xF8 & i) | (0x07 & j)
|
||||
return byte
|
||||
|
||||
def encrypt_file(input, output_file, file_length):
|
||||
input_file = bytearray(input.read())
|
||||
for i in range(len(input_file)):
|
||||
input_file[i] = encryptByte(input_file[i])
|
||||
output_file.write(input_file)
|
||||
|
||||
# Encrypt ${PROGNAME}.bin and save it with the name given in build.crypt_lerdge
|
||||
def encrypt(source, target, env):
|
||||
fwpath = target[0].path
|
||||
enname = board.get("build.crypt_lerdge")
|
||||
print("Encrypting %s to %s" % (fwpath, enname))
|
||||
fwfile = open(fwpath, "rb")
|
||||
enfile = open(target[0].dir.path + "/" + enname, "wb")
|
||||
length = os.path.getsize(fwpath)
|
||||
|
||||
encrypt_file(fwfile, enfile, length)
|
||||
|
||||
fwfile.close()
|
||||
enfile.close()
|
||||
os.remove(fwpath)
|
||||
|
||||
if 'crypt_lerdge' in board.get("build").keys():
|
||||
if board.get("build.crypt_lerdge") != "":
|
||||
marlin.add_post_action(encrypt)
|
||||
else:
|
||||
print("LERDGE builds require output file via board_build.crypt_lerdge = 'filename' parameter")
|
||||
exit(1)
|
73
buildroot/share/PlatformIO/scripts/marlin.py
Normal file
73
buildroot/share/PlatformIO/scripts/marlin.py
Normal file
@ -0,0 +1,73 @@
|
||||
#
|
||||
# marlin.py
|
||||
# Helper module with some commonly-used functions
|
||||
#
|
||||
import shutil
|
||||
from pathlib import Path
|
||||
|
||||
from SCons.Script import DefaultEnvironment
|
||||
env = DefaultEnvironment()
|
||||
|
||||
def copytree(src, dst, symlinks=False, ignore=None):
|
||||
for item in src.iterdir():
|
||||
if item.is_dir():
|
||||
shutil.copytree(item, dst / item.name, symlinks, ignore)
|
||||
else:
|
||||
shutil.copy2(item, dst / item.name)
|
||||
|
||||
def replace_define(field, value):
|
||||
envdefs = env['CPPDEFINES'].copy()
|
||||
for define in envdefs:
|
||||
if define[0] == field:
|
||||
env['CPPDEFINES'].remove(define)
|
||||
env['CPPDEFINES'].append((field, value))
|
||||
|
||||
# Relocate the firmware to a new address, such as "0x08005000"
|
||||
def relocate_firmware(address):
|
||||
replace_define("VECT_TAB_ADDR", address)
|
||||
|
||||
# Relocate the vector table with a new offset
|
||||
def relocate_vtab(address):
|
||||
replace_define("VECT_TAB_OFFSET", address)
|
||||
|
||||
# Replace the existing -Wl,-T with the given ldscript path
|
||||
def custom_ld_script(ldname):
|
||||
apath = str(Path("buildroot/share/PlatformIO/ldscripts", ldname).resolve())
|
||||
for i, flag in enumerate(env["LINKFLAGS"]):
|
||||
if "-Wl,-T" in flag:
|
||||
env["LINKFLAGS"][i] = "-Wl,-T" + apath
|
||||
elif flag == "-T":
|
||||
env["LINKFLAGS"][i + 1] = apath
|
||||
|
||||
# Encrypt ${PROGNAME}.bin and save it with a new name. This applies (mostly) to MKS boards
|
||||
# This PostAction is set up by offset_and_rename.py for envs with 'build.encrypt_mks'.
|
||||
def encrypt_mks(source, target, env, new_name):
|
||||
import sys
|
||||
|
||||
key = [0xA3, 0xBD, 0xAD, 0x0D, 0x41, 0x11, 0xBB, 0x8D, 0xDC, 0x80, 0x2D, 0xD0, 0xD2, 0xC4, 0x9B, 0x1E, 0x26, 0xEB, 0xE3, 0x33, 0x4A, 0x15, 0xE4, 0x0A, 0xB3, 0xB1, 0x3C, 0x93, 0xBB, 0xAF, 0xF7, 0x3E]
|
||||
|
||||
# If FIRMWARE_BIN is defined by config, override all
|
||||
mf = env["MARLIN_FEATURES"]
|
||||
if "FIRMWARE_BIN" in mf: new_name = mf["FIRMWARE_BIN"]
|
||||
|
||||
fwpath = Path(target[0].path)
|
||||
fwfile = fwpath.open("rb")
|
||||
enfile = Path(target[0].dir.path, new_name).open("wb")
|
||||
length = fwpath.stat().st_size
|
||||
position = 0
|
||||
try:
|
||||
while position < length:
|
||||
byte = fwfile.read(1)
|
||||
if 320 <= position < 31040:
|
||||
byte = chr(ord(byte) ^ key[position & 31])
|
||||
if sys.version_info[0] > 2:
|
||||
byte = bytes(byte, 'latin1')
|
||||
enfile.write(byte)
|
||||
position += 1
|
||||
finally:
|
||||
fwfile.close()
|
||||
enfile.close()
|
||||
fwpath.unlink()
|
||||
|
||||
def add_post_action(action):
|
||||
env.AddPostAction(str(Path("$BUILD_DIR", "${PROGNAME}.bin")), action)
|
68
buildroot/share/PlatformIO/scripts/mc-apply.py
Executable file
68
buildroot/share/PlatformIO/scripts/mc-apply.py
Executable file
@ -0,0 +1,68 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# Create a Configuration from marlin_config.json
|
||||
#
|
||||
import json
|
||||
import sys
|
||||
import shutil
|
||||
|
||||
opt_output = '--opt' in sys.argv
|
||||
output_suffix = '.sh' if opt_output else '' if '--bare-output' in sys.argv else '.gen'
|
||||
|
||||
try:
|
||||
with open('marlin_config.json', 'r') as infile:
|
||||
conf = json.load(infile)
|
||||
for key in conf:
|
||||
# We don't care about the hash when restoring here
|
||||
if key == '__INITIAL_HASH':
|
||||
continue
|
||||
if key == 'VERSION':
|
||||
for k, v in sorted(conf[key].items()):
|
||||
print(k + ': ' + v)
|
||||
continue
|
||||
# The key is the file name, so let's build it now
|
||||
outfile = open('Marlin/' + key + output_suffix, 'w')
|
||||
for k, v in sorted(conf[key].items()):
|
||||
# Make define line now
|
||||
if opt_output:
|
||||
if v != '':
|
||||
if '"' in v:
|
||||
v = "'%s'" % v
|
||||
elif ' ' in v:
|
||||
v = '"%s"' % v
|
||||
define = 'opt_set ' + k + ' ' + v + '\n'
|
||||
else:
|
||||
define = 'opt_enable ' + k + '\n'
|
||||
else:
|
||||
define = '#define ' + k + ' ' + v + '\n'
|
||||
outfile.write(define)
|
||||
outfile.close()
|
||||
|
||||
# Try to apply changes to the actual configuration file (in order to keep useful comments)
|
||||
if output_suffix != '':
|
||||
# Move the existing configuration so it doesn't interfere
|
||||
shutil.move('Marlin/' + key, 'Marlin/' + key + '.orig')
|
||||
infile_lines = open('Marlin/' + key + '.orig', 'r').read().split('\n')
|
||||
outfile = open('Marlin/' + key, 'w')
|
||||
for line in infile_lines:
|
||||
sline = line.strip(" \t\n\r")
|
||||
if sline[:7] == "#define":
|
||||
# Extract the key here (we don't care about the value)
|
||||
kv = sline[8:].strip().split(' ')
|
||||
if kv[0] in conf[key]:
|
||||
outfile.write('#define ' + kv[0] + ' ' + conf[key][kv[0]] + '\n')
|
||||
# Remove the key from the dict, so we can still write all missing keys at the end of the file
|
||||
del conf[key][kv[0]]
|
||||
else:
|
||||
outfile.write(line + '\n')
|
||||
else:
|
||||
outfile.write(line + '\n')
|
||||
# Process any remaining defines here
|
||||
for k, v in sorted(conf[key].items()):
|
||||
define = '#define ' + k + ' ' + v + '\n'
|
||||
outfile.write(define)
|
||||
outfile.close()
|
||||
|
||||
print('Output configuration written to: ' + 'Marlin/' + key + output_suffix)
|
||||
except:
|
||||
print('No marlin_config.json found.')
|
69
buildroot/share/PlatformIO/scripts/offset_and_rename.py
Normal file
69
buildroot/share/PlatformIO/scripts/offset_and_rename.py
Normal file
@ -0,0 +1,69 @@
|
||||
#
|
||||
# offset_and_rename.py
|
||||
#
|
||||
# - If 'board_build.offset' is provided, either by JSON or by the environment...
|
||||
# - Set linker flag LD_FLASH_OFFSET and relocate the VTAB based on 'build.offset'.
|
||||
# - Set linker flag LD_MAX_DATA_SIZE based on 'build.maximum_ram_size'.
|
||||
# - Define STM32_FLASH_SIZE from 'upload.maximum_size' for use by Flash-based EEPROM emulation.
|
||||
#
|
||||
# - For 'board_build.rename' add a post-action to rename the firmware file.
|
||||
#
|
||||
import pioutil
|
||||
if pioutil.is_pio_build():
|
||||
import marlin
|
||||
|
||||
env = marlin.env
|
||||
board = env.BoardConfig()
|
||||
board_keys = board.get("build").keys()
|
||||
|
||||
#
|
||||
# For build.offset define LD_FLASH_OFFSET, used by ldscript.ld
|
||||
#
|
||||
if 'offset' in board_keys:
|
||||
LD_FLASH_OFFSET = board.get("build.offset")
|
||||
marlin.relocate_vtab(LD_FLASH_OFFSET)
|
||||
|
||||
# Flash size
|
||||
maximum_flash_size = int(board.get("upload.maximum_size") / 1024)
|
||||
marlin.replace_define('STM32_FLASH_SIZE', maximum_flash_size)
|
||||
|
||||
# Get upload.maximum_ram_size (defined by /buildroot/share/PlatformIO/boards/VARIOUS.json)
|
||||
maximum_ram_size = board.get("upload.maximum_ram_size")
|
||||
|
||||
for i, flag in enumerate(env["LINKFLAGS"]):
|
||||
if "-Wl,--defsym=LD_FLASH_OFFSET" in flag:
|
||||
env["LINKFLAGS"][i] = "-Wl,--defsym=LD_FLASH_OFFSET=" + LD_FLASH_OFFSET
|
||||
if "-Wl,--defsym=LD_MAX_DATA_SIZE" in flag:
|
||||
env["LINKFLAGS"][i] = "-Wl,--defsym=LD_MAX_DATA_SIZE=" + str(maximum_ram_size - 40)
|
||||
|
||||
#
|
||||
# For build.encrypt_mks rename and encode the firmware file.
|
||||
#
|
||||
if 'encrypt_mks' in board_keys:
|
||||
|
||||
# Encrypt ${PROGNAME}.bin and save it with the name given in build.encrypt_mks
|
||||
def encrypt(source, target, env):
|
||||
marlin.encrypt_mks(source, target, env, board.get("build.encrypt_mks"))
|
||||
|
||||
if board.get("build.encrypt_mks") != "":
|
||||
marlin.add_post_action(encrypt)
|
||||
|
||||
#
|
||||
# For build.rename simply rename the firmware file.
|
||||
#
|
||||
if 'rename' in board_keys:
|
||||
|
||||
# If FIRMWARE_BIN is defined by config, override all
|
||||
mf = env["MARLIN_FEATURES"]
|
||||
if "FIRMWARE_BIN" in mf: new_name = mf["FIRMWARE_BIN"]
|
||||
else: new_name = board.get("build.rename")
|
||||
|
||||
def rename_target(source, target, env):
|
||||
from pathlib import Path
|
||||
from datetime import datetime
|
||||
from os import path
|
||||
_newpath = Path(target[0].dir.path, datetime.now().strftime(new_name.replace('{date}', '%Y%m%d').replace('{time}', '%H%M%S')))
|
||||
Path(target[0].path).replace(_newpath)
|
||||
env['PROGNAME'] = path.splitext(_newpath)[0]
|
||||
|
||||
marlin.add_post_action(rename_target)
|
19
buildroot/share/PlatformIO/scripts/openblt.py
Normal file
19
buildroot/share/PlatformIO/scripts/openblt.py
Normal file
@ -0,0 +1,19 @@
|
||||
#
|
||||
# Convert the ELF to an SREC file suitable for some bootloaders
|
||||
#
|
||||
import pioutil
|
||||
if pioutil.is_pio_build():
|
||||
from os.path import join
|
||||
|
||||
Import("env")
|
||||
|
||||
board = env.BoardConfig()
|
||||
board_keys = board.get("build").keys()
|
||||
if 'encode' in board_keys:
|
||||
env.AddPostAction(
|
||||
join("$BUILD_DIR", "${PROGNAME}.bin"),
|
||||
env.VerboseAction(" ".join([
|
||||
"$OBJCOPY", "-O", "srec",
|
||||
"\"$BUILD_DIR/${PROGNAME}.elf\"", "\"" + join("$BUILD_DIR", board.get("build.encode")) + "\""
|
||||
]), "Building " + board.get("build.encode"))
|
||||
)
|
14
buildroot/share/PlatformIO/scripts/pioutil.py
Normal file
14
buildroot/share/PlatformIO/scripts/pioutil.py
Normal file
@ -0,0 +1,14 @@
|
||||
#
|
||||
# pioutil.py
|
||||
#
|
||||
|
||||
# Make sure 'vscode init' is not the current command
|
||||
def is_pio_build():
|
||||
from SCons.Script import DefaultEnvironment
|
||||
env = DefaultEnvironment()
|
||||
if "IsCleanTarget" in dir(env) and env.IsCleanTarget(): return False
|
||||
return not env.IsIntegrationDump()
|
||||
|
||||
def get_pio_version():
|
||||
from platformio import util
|
||||
return util.pioversion_to_intstr()
|
138
buildroot/share/PlatformIO/scripts/preflight-checks.py
Normal file
138
buildroot/share/PlatformIO/scripts/preflight-checks.py
Normal file
@ -0,0 +1,138 @@
|
||||
#
|
||||
# preflight-checks.py
|
||||
# Check for common issues prior to compiling
|
||||
#
|
||||
import pioutil
|
||||
if pioutil.is_pio_build():
|
||||
|
||||
import re,sys
|
||||
from pathlib import Path
|
||||
Import("env")
|
||||
|
||||
def get_envs_for_board(board):
|
||||
ppath = Path("Marlin/src/pins/pins.h")
|
||||
with ppath.open() as file:
|
||||
|
||||
if sys.platform == 'win32':
|
||||
envregex = r"(?:env|win):"
|
||||
elif sys.platform == 'darwin':
|
||||
envregex = r"(?:env|mac|uni):"
|
||||
elif sys.platform == 'linux':
|
||||
envregex = r"(?:env|lin|uni):"
|
||||
else:
|
||||
envregex = r"(?:env):"
|
||||
|
||||
r = re.compile(r"if\s+MB\((.+)\)")
|
||||
if board.startswith("BOARD_"):
|
||||
board = board[6:]
|
||||
|
||||
for line in file:
|
||||
mbs = r.findall(line)
|
||||
if mbs and board in re.split(r",\s*", mbs[0]):
|
||||
line = file.readline()
|
||||
found_envs = re.match(r"\s*#include .+" + envregex, line)
|
||||
if found_envs:
|
||||
envlist = re.findall(envregex + r"(\w+)", line)
|
||||
return [ "env:"+s for s in envlist ]
|
||||
return []
|
||||
|
||||
def check_envs(build_env, board_envs, config):
|
||||
if build_env in board_envs:
|
||||
return True
|
||||
ext = config.get(build_env, 'extends', default=None)
|
||||
if ext:
|
||||
if isinstance(ext, str):
|
||||
return check_envs(ext, board_envs, config)
|
||||
elif isinstance(ext, list):
|
||||
for ext_env in ext:
|
||||
if check_envs(ext_env, board_envs, config):
|
||||
return True
|
||||
return False
|
||||
|
||||
def sanity_check_target():
|
||||
# Sanity checks:
|
||||
if 'PIOENV' not in env:
|
||||
raise SystemExit("Error: PIOENV is not defined. This script is intended to be used with PlatformIO")
|
||||
|
||||
# Require PlatformIO 6.1.1 or later
|
||||
vers = pioutil.get_pio_version()
|
||||
if vers < [6, 1, 1]:
|
||||
raise SystemExit("Error: Marlin requires PlatformIO >= 6.1.1. Use 'pio upgrade' to get a newer version.")
|
||||
|
||||
if 'MARLIN_FEATURES' not in env:
|
||||
raise SystemExit("Error: this script should be used after common Marlin scripts.")
|
||||
|
||||
if len(env['MARLIN_FEATURES']) == 0:
|
||||
raise SystemExit("Error: Failed to parse Marlin features. See previous error messages.")
|
||||
|
||||
build_env = env['PIOENV']
|
||||
motherboard = env['MARLIN_FEATURES']['MOTHERBOARD']
|
||||
board_envs = get_envs_for_board(motherboard)
|
||||
config = env.GetProjectConfig()
|
||||
result = check_envs("env:"+build_env, board_envs, config)
|
||||
|
||||
if not result:
|
||||
err = "Error: Build environment '%s' is incompatible with %s. Use one of these environments: %s" % \
|
||||
( build_env, motherboard, ", ".join([ e[4:] for e in board_envs if e.startswith("env:") ]) )
|
||||
raise SystemExit(err)
|
||||
|
||||
#
|
||||
# Check for Config files in two common incorrect places
|
||||
#
|
||||
epath = Path(env['PROJECT_DIR'])
|
||||
for p in [ epath, epath / "config" ]:
|
||||
for f in ("Configuration.h", "Configuration_adv.h"):
|
||||
if (p / f).is_file():
|
||||
err = "ERROR: Config files found in directory %s. Please move them into the Marlin subfolder." % p
|
||||
raise SystemExit(err)
|
||||
|
||||
#
|
||||
# Find the name.cpp.o or name.o and remove it
|
||||
#
|
||||
def rm_ofile(subdir, name):
|
||||
build_dir = Path(env['PROJECT_BUILD_DIR'], build_env)
|
||||
for outdir in (build_dir, build_dir / "debug"):
|
||||
for ext in (".cpp.o", ".o"):
|
||||
fpath = outdir / "src/src" / subdir / (name + ext)
|
||||
if fpath.exists():
|
||||
fpath.unlink()
|
||||
|
||||
#
|
||||
# Give warnings on every build
|
||||
#
|
||||
rm_ofile("inc", "Warnings")
|
||||
|
||||
#
|
||||
# Rebuild 'settings.cpp' for EEPROM_INIT_NOW
|
||||
#
|
||||
if 'EEPROM_INIT_NOW' in env['MARLIN_FEATURES']:
|
||||
rm_ofile("module", "settings")
|
||||
|
||||
#
|
||||
# Check for old files indicating an entangled Marlin (mixing old and new code)
|
||||
#
|
||||
mixedin = []
|
||||
p = Path(env['PROJECT_DIR'], "Marlin/src/lcd/dogm")
|
||||
for f in [ "ultralcd_DOGM.cpp", "ultralcd_DOGM.h" ]:
|
||||
if (p / f).is_file():
|
||||
mixedin += [ f ]
|
||||
p = Path(env['PROJECT_DIR'], "Marlin/src/feature/bedlevel/abl")
|
||||
for f in [ "abl.cpp", "abl.h" ]:
|
||||
if (p / f).is_file():
|
||||
mixedin += [ f ]
|
||||
if mixedin:
|
||||
err = "ERROR: Old files fell into your Marlin folder. Remove %s and try again" % ", ".join(mixedin)
|
||||
raise SystemExit(err)
|
||||
|
||||
#
|
||||
# Check FILAMENT_RUNOUT_SCRIPT has a %c parammeter when required
|
||||
#
|
||||
if 'FILAMENT_RUNOUT_SENSOR' in env['MARLIN_FEATURES'] and 'NUM_RUNOUT_SENSORS' in env['MARLIN_FEATURES']:
|
||||
if env['MARLIN_FEATURES']['NUM_RUNOUT_SENSORS'].isdigit() and int(env['MARLIN_FEATURES']['NUM_RUNOUT_SENSORS']) > 1:
|
||||
if 'FILAMENT_RUNOUT_SCRIPT' in env['MARLIN_FEATURES']:
|
||||
frs = env['MARLIN_FEATURES']['FILAMENT_RUNOUT_SCRIPT']
|
||||
if "M600" in frs and "%c" not in frs:
|
||||
err = "ERROR: FILAMENT_RUNOUT_SCRIPT needs a %c parameter (e.g., \"M600 T%c\") when NUM_RUNOUT_SENSORS is > 1"
|
||||
raise SystemExit(err)
|
||||
|
||||
sanity_check_target()
|
94
buildroot/share/PlatformIO/scripts/preprocessor.py
Normal file
94
buildroot/share/PlatformIO/scripts/preprocessor.py
Normal file
@ -0,0 +1,94 @@
|
||||
#
|
||||
# preprocessor.py
|
||||
#
|
||||
import subprocess
|
||||
|
||||
nocache = 1
|
||||
verbose = 0
|
||||
|
||||
def blab(str):
|
||||
if verbose:
|
||||
print(str)
|
||||
|
||||
################################################################################
|
||||
#
|
||||
# Invoke GCC to run the preprocessor and extract enabled features
|
||||
#
|
||||
preprocessor_cache = {}
|
||||
def run_preprocessor(env, fn=None):
|
||||
filename = fn or 'buildroot/share/PlatformIO/scripts/common-dependencies.h'
|
||||
if filename in preprocessor_cache:
|
||||
return preprocessor_cache[filename]
|
||||
|
||||
# Process defines
|
||||
build_flags = env.get('BUILD_FLAGS')
|
||||
build_flags = env.ParseFlagsExtended(build_flags)
|
||||
|
||||
cxx = search_compiler(env)
|
||||
cmd = ['"' + cxx + '"']
|
||||
|
||||
# Build flags from board.json
|
||||
#if 'BOARD' in env:
|
||||
# cmd += [env.BoardConfig().get("build.extra_flags")]
|
||||
for s in build_flags['CPPDEFINES']:
|
||||
if isinstance(s, tuple):
|
||||
cmd += ['-D' + s[0] + '=' + str(s[1])]
|
||||
else:
|
||||
cmd += ['-D' + s]
|
||||
|
||||
cmd += ['-D__MARLIN_DEPS__ -w -dM -E -x c++']
|
||||
depcmd = cmd + [ filename ]
|
||||
cmd = ' '.join(depcmd)
|
||||
blab(cmd)
|
||||
try:
|
||||
define_list = subprocess.check_output(cmd, shell=True).splitlines()
|
||||
except:
|
||||
define_list = {}
|
||||
preprocessor_cache[filename] = define_list
|
||||
return define_list
|
||||
|
||||
|
||||
################################################################################
|
||||
#
|
||||
# Find a compiler, considering the OS
|
||||
#
|
||||
def search_compiler(env):
|
||||
|
||||
from pathlib import Path, PurePath
|
||||
|
||||
ENV_BUILD_PATH = Path(env['PROJECT_BUILD_DIR'], env['PIOENV'])
|
||||
GCC_PATH_CACHE = ENV_BUILD_PATH / ".gcc_path"
|
||||
|
||||
try:
|
||||
gccpath = env.GetProjectOption('custom_gcc')
|
||||
blab("Getting compiler from env")
|
||||
return gccpath
|
||||
except:
|
||||
pass
|
||||
|
||||
# Warning: The cached .gcc_path will obscure a newly-installed toolkit
|
||||
if not nocache and GCC_PATH_CACHE.exists():
|
||||
blab("Getting g++ path from cache")
|
||||
return GCC_PATH_CACHE.read_text()
|
||||
|
||||
# Use any item in $PATH corresponding to a platformio toolchain bin folder
|
||||
path_separator = ':'
|
||||
gcc_exe = '*g++'
|
||||
if env['PLATFORM'] == 'win32':
|
||||
path_separator = ';'
|
||||
gcc_exe += ".exe"
|
||||
|
||||
# Search for the compiler in PATH
|
||||
for ppath in map(Path, env['ENV']['PATH'].split(path_separator)):
|
||||
if ppath.match(env['PROJECT_PACKAGES_DIR'] + "/**/bin"):
|
||||
for gpath in ppath.glob(gcc_exe):
|
||||
gccpath = str(gpath.resolve())
|
||||
# Cache the g++ path to no search always
|
||||
if not nocache and ENV_BUILD_PATH.exists():
|
||||
blab("Caching g++ for current env")
|
||||
GCC_PATH_CACHE.write_text(gccpath)
|
||||
return gccpath
|
||||
|
||||
gccpath = env.get('CXX')
|
||||
blab("Couldn't find a compiler! Fallback to %s" % gccpath)
|
||||
return gccpath
|
478
buildroot/share/PlatformIO/scripts/schema.py
Executable file
478
buildroot/share/PlatformIO/scripts/schema.py
Executable file
@ -0,0 +1,478 @@
|
||||
#!/usr/bin/env python3
|
||||
#
|
||||
# schema.py
|
||||
#
|
||||
# Used by signature.py via common-dependencies.py to generate a schema file during the PlatformIO build
|
||||
# when CONFIG_EXPORT is defined in the configuration.
|
||||
#
|
||||
# This script can also be run standalone from within the Marlin repo to generate JSON and YAML schema files.
|
||||
#
|
||||
# This script is a companion to abm/js/schema.js in the MarlinFirmware/AutoBuildMarlin project, which has
|
||||
# been extended to evaluate conditions and can determine what options are actually enabled, not just which
|
||||
# options are uncommented. That will be migrated to this script for standalone migration.
|
||||
#
|
||||
import re,json
|
||||
from pathlib import Path
|
||||
|
||||
def extend_dict(d:dict, k:tuple):
|
||||
if len(k) >= 1 and k[0] not in d:
|
||||
d[k[0]] = {}
|
||||
if len(k) >= 2 and k[1] not in d[k[0]]:
|
||||
d[k[0]][k[1]] = {}
|
||||
if len(k) >= 3 and k[2] not in d[k[0]][k[1]]:
|
||||
d[k[0]][k[1]][k[2]] = {}
|
||||
|
||||
grouping_patterns = [
|
||||
re.compile(r'^([XYZIJKUVW]|[XYZ]2|Z[34]|E[0-7])$'),
|
||||
re.compile(r'^AXIS\d$'),
|
||||
re.compile(r'^(MIN|MAX)$'),
|
||||
re.compile(r'^[0-8]$'),
|
||||
re.compile(r'^HOTEND[0-7]$'),
|
||||
re.compile(r'^(HOTENDS|BED|PROBE|COOLER)$'),
|
||||
re.compile(r'^[XYZIJKUVW]M(IN|AX)$')
|
||||
]
|
||||
# If the indexed part of the option name matches a pattern
|
||||
# then add it to the dictionary.
|
||||
def find_grouping(gdict, filekey, sectkey, optkey, pindex):
|
||||
optparts = optkey.split('_')
|
||||
if 1 < len(optparts) > pindex:
|
||||
for patt in grouping_patterns:
|
||||
if patt.match(optparts[pindex]):
|
||||
subkey = optparts[pindex]
|
||||
modkey = '_'.join(optparts)
|
||||
optparts[pindex] = '*'
|
||||
wildkey = '_'.join(optparts)
|
||||
kkey = f'{filekey}|{sectkey}|{wildkey}'
|
||||
if kkey not in gdict: gdict[kkey] = []
|
||||
gdict[kkey].append((subkey, modkey))
|
||||
|
||||
# Build a list of potential groups. Only those with multiple items will be grouped.
|
||||
def group_options(schema):
|
||||
for pindex in range(10, -1, -1):
|
||||
found_groups = {}
|
||||
for filekey, f in schema.items():
|
||||
for sectkey, s in f.items():
|
||||
for optkey in s:
|
||||
find_grouping(found_groups, filekey, sectkey, optkey, pindex)
|
||||
|
||||
fkeys = [ k for k in found_groups.keys() ]
|
||||
for kkey in fkeys:
|
||||
items = found_groups[kkey]
|
||||
if len(items) > 1:
|
||||
f, s, w = kkey.split('|')
|
||||
extend_dict(schema, (f, s, w)) # Add wildcard group to schema
|
||||
for subkey, optkey in items: # Add all items to wildcard group
|
||||
schema[f][s][w][subkey] = schema[f][s][optkey] # Move non-wildcard item to wildcard group
|
||||
del schema[f][s][optkey]
|
||||
del found_groups[kkey]
|
||||
|
||||
# Extract all board names from boards.h
|
||||
def load_boards():
|
||||
bpath = Path("Marlin/src/core/boards.h")
|
||||
if bpath.is_file():
|
||||
with bpath.open() as bfile:
|
||||
boards = []
|
||||
for line in bfile:
|
||||
if line.startswith("#define BOARD_"):
|
||||
bname = line.split()[1]
|
||||
if bname != "BOARD_UNKNOWN": boards.append(bname)
|
||||
return "['" + "','".join(boards) + "']"
|
||||
return ''
|
||||
|
||||
#
|
||||
# Extract the current configuration files in the form of a structured schema.
|
||||
# Contains the full schema for the configuration files, not just the enabled options,
|
||||
# Contains the current values of the options, not just data structure, so "schema" is a slight misnomer.
|
||||
#
|
||||
# The returned object is a nested dictionary with the following indexing:
|
||||
#
|
||||
# - schema[filekey][section][define_name] = define_info
|
||||
#
|
||||
# Where the define_info contains the following keyed fields:
|
||||
# - section = The @section the define is in
|
||||
# - name = The name of the define
|
||||
# - enabled = True if the define is enabled (not commented out)
|
||||
# - line = The line number of the define
|
||||
# - sid = A serial ID for the define
|
||||
# - value = The value of the define, if it has one
|
||||
# - type = The type of the define, if it has one
|
||||
# - requires = The conditions that must be met for the define to be enabled
|
||||
# - comment = The comment for the define, if it has one
|
||||
# - units = The units for the define, if it has one
|
||||
# - options = The options for the define, if it has one
|
||||
#
|
||||
def extract():
|
||||
# Load board names from boards.h
|
||||
boards = load_boards()
|
||||
|
||||
# Parsing states
|
||||
class Parse:
|
||||
NORMAL = 0 # No condition yet
|
||||
BLOCK_COMMENT = 1 # Looking for the end of the block comment
|
||||
EOL_COMMENT = 2 # EOL comment started, maybe add the next comment?
|
||||
SLASH_COMMENT = 3 # Block-like comment, starting with aligned //
|
||||
GET_SENSORS = 4 # Gathering temperature sensor options
|
||||
ERROR = 9 # Syntax error
|
||||
|
||||
# List of files to process, with shorthand
|
||||
filekey = { 'Configuration.h':'basic', 'Configuration_adv.h':'advanced' }
|
||||
# A JSON object to store the data
|
||||
sch_out = { 'basic':{}, 'advanced':{} }
|
||||
# Regex for #define NAME [VALUE] [COMMENT] with sanitized line
|
||||
defgrep = re.compile(r'^(//)?\s*(#define)\s+([A-Za-z0-9_]+)\s*(.*?)\s*(//.+)?$')
|
||||
# Pattern to match a float value
|
||||
flt = r'[-+]?\s*(\d+\.|\d*\.\d+)([eE][-+]?\d+)?[fF]?'
|
||||
# Defines to ignore
|
||||
ignore = ('CONFIGURATION_H_VERSION', 'CONFIGURATION_ADV_H_VERSION', 'CONFIG_EXAMPLES_DIR', 'CONFIG_EXPORT')
|
||||
# Start with unknown state
|
||||
state = Parse.NORMAL
|
||||
# Serial ID
|
||||
sid = 0
|
||||
# Loop through files and parse them line by line
|
||||
for fn, fk in filekey.items():
|
||||
with Path("Marlin", fn).open() as fileobj:
|
||||
section = 'none' # Current Settings section
|
||||
line_number = 0 # Counter for the line number of the file
|
||||
conditions = [] # Create a condition stack for the current file
|
||||
comment_buff = [] # A temporary buffer for comments
|
||||
prev_comment = '' # Copy before reset for an EOL comment
|
||||
options_json = '' # A buffer for the most recent options JSON found
|
||||
eol_options = False # The options came from end of line, so only apply once
|
||||
join_line = False # A flag that the line should be joined with the previous one
|
||||
line = '' # A line buffer to handle \ continuation
|
||||
last_added_ref = None # Reference to the last added item
|
||||
# Loop through the lines in the file
|
||||
for the_line in fileobj.readlines():
|
||||
line_number += 1
|
||||
|
||||
# Clean the line for easier parsing
|
||||
the_line = the_line.strip()
|
||||
|
||||
if join_line: # A previous line is being made longer
|
||||
line += (' ' if line else '') + the_line
|
||||
else: # Otherwise, start the line anew
|
||||
line, line_start = the_line, line_number
|
||||
|
||||
# If the resulting line ends with a \, don't process now.
|
||||
# Strip the end off. The next line will be joined with it.
|
||||
join_line = line.endswith("\\")
|
||||
if join_line:
|
||||
line = line[:-1].strip()
|
||||
continue
|
||||
else:
|
||||
line_end = line_number
|
||||
|
||||
defmatch = defgrep.match(line)
|
||||
|
||||
# Special handling for EOL comments after a #define.
|
||||
# At this point the #define is already digested and inserted,
|
||||
# so we have to extend it
|
||||
if state == Parse.EOL_COMMENT:
|
||||
# If the line is not a comment, we're done with the EOL comment
|
||||
if not defmatch and the_line.startswith('//'):
|
||||
comment_buff.append(the_line[2:].strip())
|
||||
else:
|
||||
state = Parse.NORMAL
|
||||
cline = ' '.join(comment_buff)
|
||||
comment_buff = []
|
||||
if cline != '':
|
||||
# A (block or slash) comment was already added
|
||||
cfield = 'notes' if 'comment' in last_added_ref else 'comment'
|
||||
last_added_ref[cfield] = cline
|
||||
|
||||
def use_comment(c, opt, sec, bufref):
|
||||
if c.startswith(':'): # If the comment starts with : then it has magic JSON
|
||||
d = c[1:].strip() # Strip the leading :
|
||||
cbr = c.rindex('}') if d.startswith('{') else c.rindex(']') if d.startswith('[') else 0
|
||||
if cbr:
|
||||
opt, cmt = c[1:cbr+1].strip(), c[cbr+1:].strip()
|
||||
if cmt != '': bufref.append(cmt)
|
||||
else:
|
||||
opt = c[1:].strip()
|
||||
elif c.startswith('@section'): # Start a new section
|
||||
sec = c[8:].strip()
|
||||
elif not c.startswith('========'):
|
||||
bufref.append(c)
|
||||
return opt, sec
|
||||
|
||||
# For slash comments, capture consecutive slash comments.
|
||||
# The comment will be applied to the next #define.
|
||||
if state == Parse.SLASH_COMMENT:
|
||||
if not defmatch and the_line.startswith('//'):
|
||||
use_comment(the_line[2:].strip(), options_json, section, comment_buff)
|
||||
continue
|
||||
else:
|
||||
state = Parse.NORMAL
|
||||
|
||||
# In a block comment, capture lines up to the end of the comment.
|
||||
# Assume nothing follows the comment closure.
|
||||
if state in (Parse.BLOCK_COMMENT, Parse.GET_SENSORS):
|
||||
endpos = line.find('*/')
|
||||
if endpos < 0:
|
||||
cline = line
|
||||
else:
|
||||
cline, line = line[:endpos].strip(), line[endpos+2:].strip()
|
||||
|
||||
# Temperature sensors are done
|
||||
if state == Parse.GET_SENSORS:
|
||||
options_json = f'[ {options_json[:-2]} ]'
|
||||
|
||||
state = Parse.NORMAL
|
||||
|
||||
# Strip the leading '*' from block comments
|
||||
cline = re.sub(r'^\* ?', '', cline)
|
||||
|
||||
# Collect temperature sensors
|
||||
if state == Parse.GET_SENSORS:
|
||||
sens = re.match(r'^(-?\d+)\s*:\s*(.+)$', cline)
|
||||
if sens:
|
||||
s2 = sens[2].replace("'","''")
|
||||
options_json += f"{sens[1]}:'{sens[1]} - {s2}', "
|
||||
|
||||
elif state == Parse.BLOCK_COMMENT:
|
||||
|
||||
# Look for temperature sensors
|
||||
if re.match(r'temperature sensors.*:', cline, re.IGNORECASE):
|
||||
state, cline = Parse.GET_SENSORS, "Temperature Sensors"
|
||||
|
||||
options_json, section = use_comment(cline, options_json, section, comment_buff)
|
||||
|
||||
# For the normal state we're looking for any non-blank line
|
||||
elif state == Parse.NORMAL:
|
||||
# Skip a commented define when evaluating comment opening
|
||||
st = 2 if re.match(r'^//\s*#define', line) else 0
|
||||
cpos1 = line.find('/*') # Start a block comment on the line?
|
||||
cpos2 = line.find('//', st) # Start an end of line comment on the line?
|
||||
|
||||
# Only the first comment starter gets evaluated
|
||||
cpos = -1
|
||||
if cpos1 != -1 and (cpos1 < cpos2 or cpos2 == -1):
|
||||
cpos = cpos1
|
||||
comment_buff = []
|
||||
state = Parse.BLOCK_COMMENT
|
||||
eol_options = False
|
||||
|
||||
elif cpos2 != -1 and (cpos2 < cpos1 or cpos1 == -1):
|
||||
cpos = cpos2
|
||||
|
||||
# Comment after a define may be continued on the following lines
|
||||
if defmatch != None and cpos > 10:
|
||||
state = Parse.EOL_COMMENT
|
||||
prev_comment = '\n'.join(comment_buff)
|
||||
comment_buff = []
|
||||
else:
|
||||
state = Parse.SLASH_COMMENT
|
||||
|
||||
# Process the start of a new comment
|
||||
if cpos != -1:
|
||||
comment_buff = []
|
||||
cline, line = line[cpos+2:].strip(), line[:cpos].strip()
|
||||
|
||||
if state == Parse.BLOCK_COMMENT:
|
||||
# Strip leading '*' from block comments
|
||||
cline = re.sub(r'^\* ?', '', cline)
|
||||
else:
|
||||
# Expire end-of-line options after first use
|
||||
if cline.startswith(':'): eol_options = True
|
||||
|
||||
# Buffer a non-empty comment start
|
||||
if cline != '':
|
||||
options_json, section = use_comment(cline, options_json, section, comment_buff)
|
||||
|
||||
# If the line has nothing before the comment, go to the next line
|
||||
if line == '':
|
||||
options_json = ''
|
||||
continue
|
||||
|
||||
# Parenthesize the given expression if needed
|
||||
def atomize(s):
|
||||
if s == '' \
|
||||
or re.match(r'^[A-Za-z0-9_]*(\([^)]+\))?$', s) \
|
||||
or re.match(r'^[A-Za-z0-9_]+ == \d+?$', s):
|
||||
return s
|
||||
return f'({s})'
|
||||
|
||||
#
|
||||
# The conditions stack is an array containing condition-arrays.
|
||||
# Each condition-array lists the conditions for the current block.
|
||||
# IF/N/DEF adds a new condition-array to the stack.
|
||||
# ELSE/ELIF/ENDIF pop the condition-array.
|
||||
# ELSE/ELIF negate the last item in the popped condition-array.
|
||||
# ELIF adds a new condition to the end of the array.
|
||||
# ELSE/ELIF re-push the condition-array.
|
||||
#
|
||||
cparts = line.split()
|
||||
iselif, iselse = cparts[0] == '#elif', cparts[0] == '#else'
|
||||
if iselif or iselse or cparts[0] == '#endif':
|
||||
if len(conditions) == 0:
|
||||
raise Exception(f'no #if block at line {line_number}')
|
||||
|
||||
# Pop the last condition-array from the stack
|
||||
prev = conditions.pop()
|
||||
|
||||
if iselif or iselse:
|
||||
prev[-1] = '!' + prev[-1] # Invert the last condition
|
||||
if iselif: prev.append(atomize(line[5:].strip()))
|
||||
conditions.append(prev)
|
||||
|
||||
elif cparts[0] == '#if':
|
||||
conditions.append([ atomize(line[3:].strip()) ])
|
||||
elif cparts[0] == '#ifdef':
|
||||
conditions.append([ f'defined({line[6:].strip()})' ])
|
||||
elif cparts[0] == '#ifndef':
|
||||
conditions.append([ f'!defined({line[7:].strip()})' ])
|
||||
|
||||
# Handle a complete #define line
|
||||
elif defmatch != None:
|
||||
|
||||
# Get the match groups into vars
|
||||
enabled, define_name, val = defmatch[1] == None, defmatch[3], defmatch[4]
|
||||
|
||||
# Increment the serial ID
|
||||
sid += 1
|
||||
|
||||
# Create a new dictionary for the current #define
|
||||
define_info = {
|
||||
'section': section,
|
||||
'name': define_name,
|
||||
'enabled': enabled,
|
||||
'line': line_start,
|
||||
'sid': sid
|
||||
}
|
||||
|
||||
# Type is based on the value
|
||||
value_type = \
|
||||
'switch' if val == '' \
|
||||
else 'bool' if re.match(r'^(true|false)$', val) \
|
||||
else 'int' if re.match(r'^[-+]?\s*\d+$', val) \
|
||||
else 'ints' if re.match(r'^([-+]?\s*\d+)(\s*,\s*[-+]?\s*\d+)+$', val) \
|
||||
else 'floats' if re.match(rf'({flt}(\s*,\s*{flt})+)', val) \
|
||||
else 'float' if re.match(f'^({flt})$', val) \
|
||||
else 'string' if val[0] == '"' \
|
||||
else 'char' if val[0] == "'" \
|
||||
else 'state' if re.match(r'^(LOW|HIGH)$', val) \
|
||||
else 'enum' if re.match(r'^[A-Za-z0-9_]{3,}$', val) \
|
||||
else 'int[]' if re.match(r'^{\s*[-+]?\s*\d+(\s*,\s*[-+]?\s*\d+)*\s*}$', val) \
|
||||
else 'float[]' if re.match(r'^{{\s*{flt}(\s*,\s*{flt})*\s*}}$', val) \
|
||||
else 'array' if val[0] == '{' \
|
||||
else ''
|
||||
|
||||
val = (val == 'true') if value_type == 'bool' \
|
||||
else int(val) if value_type == 'int' \
|
||||
else val.replace('f','') if value_type == 'floats' \
|
||||
else float(val.replace('f','')) if value_type == 'float' \
|
||||
else val
|
||||
|
||||
if val != '': define_info['value'] = val
|
||||
if value_type != '': define_info['type'] = value_type
|
||||
|
||||
# Join up accumulated conditions with &&
|
||||
if conditions: define_info['requires'] = '(' + ') && ('.join(sum(conditions, [])) + ')'
|
||||
|
||||
# If the comment_buff is not empty, add the comment to the info
|
||||
if comment_buff:
|
||||
full_comment = '\n'.join(comment_buff)
|
||||
|
||||
# An EOL comment will be added later
|
||||
# The handling could go here instead of above
|
||||
if state == Parse.EOL_COMMENT:
|
||||
define_info['comment'] = ''
|
||||
else:
|
||||
define_info['comment'] = full_comment
|
||||
comment_buff = []
|
||||
|
||||
# If the comment specifies units, add that to the info
|
||||
units = re.match(r'^\(([^)]+)\)', full_comment)
|
||||
if units:
|
||||
units = units[1]
|
||||
if units == 's' or units == 'sec': units = 'seconds'
|
||||
define_info['units'] = units
|
||||
|
||||
# Set the options for the current #define
|
||||
if define_name == "MOTHERBOARD" and boards != '':
|
||||
define_info['options'] = boards
|
||||
elif options_json != '':
|
||||
define_info['options'] = options_json
|
||||
if eol_options: options_json = ''
|
||||
|
||||
# Create section dict if it doesn't exist yet
|
||||
if section not in sch_out[fk]: sch_out[fk][section] = {}
|
||||
|
||||
# If define has already been seen...
|
||||
if define_name in sch_out[fk][section]:
|
||||
info = sch_out[fk][section][define_name]
|
||||
if isinstance(info, dict): info = [ info ] # Convert a single dict into a list
|
||||
info.append(define_info) # Add to the list
|
||||
else:
|
||||
# Add the define dict with name as key
|
||||
sch_out[fk][section][define_name] = define_info
|
||||
|
||||
if state == Parse.EOL_COMMENT:
|
||||
last_added_ref = define_info
|
||||
|
||||
return sch_out
|
||||
|
||||
def dump_json(schema:dict, jpath:Path):
|
||||
with jpath.open('w') as jfile:
|
||||
json.dump(schema, jfile, ensure_ascii=False, indent=2)
|
||||
|
||||
def dump_yaml(schema:dict, ypath:Path):
|
||||
import yaml
|
||||
with ypath.open('w') as yfile:
|
||||
yaml.dump(schema, yfile, default_flow_style=False, width=120, indent=2)
|
||||
|
||||
def main():
|
||||
try:
|
||||
schema = extract()
|
||||
except Exception as exc:
|
||||
print("Error: " + str(exc))
|
||||
schema = None
|
||||
|
||||
if schema:
|
||||
|
||||
# Get the command line arguments after the script name
|
||||
import sys
|
||||
args = sys.argv[1:]
|
||||
if len(args) == 0: args = ['some']
|
||||
|
||||
# Does the given array intersect at all with args?
|
||||
def inargs(c): return len(set(args) & set(c)) > 0
|
||||
|
||||
# Help / Unknown option
|
||||
unk = not inargs(['some','json','jsons','group','yml','yaml'])
|
||||
if (unk): print(f"Unknown option: '{args[0]}'")
|
||||
if inargs(['-h', '--help']) or unk:
|
||||
print("Usage: schema.py [some|json|jsons|group|yml|yaml]...")
|
||||
print(" some = json + yml")
|
||||
print(" jsons = json + group")
|
||||
return
|
||||
|
||||
# JSON schema
|
||||
if inargs(['some', 'json', 'jsons']):
|
||||
print("Generating JSON ...")
|
||||
dump_json(schema, Path('schema.json'))
|
||||
|
||||
# JSON schema (wildcard names)
|
||||
if inargs(['group', 'jsons']):
|
||||
group_options(schema)
|
||||
dump_json(schema, Path('schema_grouped.json'))
|
||||
|
||||
# YAML
|
||||
if inargs(['some', 'yml', 'yaml']):
|
||||
try:
|
||||
import yaml
|
||||
except ImportError:
|
||||
print("Installing YAML module ...")
|
||||
import subprocess
|
||||
try:
|
||||
subprocess.run(['python3', '-m', 'pip', 'install', 'pyyaml'])
|
||||
import yaml
|
||||
except:
|
||||
print("Failed to install YAML module")
|
||||
return
|
||||
|
||||
print("Generating YML ...")
|
||||
dump_yaml(schema, Path('schema.yml'))
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
420
buildroot/share/PlatformIO/scripts/signature.py
Executable file
420
buildroot/share/PlatformIO/scripts/signature.py
Executable file
@ -0,0 +1,420 @@
|
||||
#!/usr/bin/env python3
|
||||
#
|
||||
# signature.py
|
||||
#
|
||||
import schema
|
||||
|
||||
import subprocess,re,json,hashlib
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
|
||||
def enabled_defines(filepath):
|
||||
'''
|
||||
Return all enabled #define items from a given C header file in a dictionary.
|
||||
A "#define" in a multi-line comment could produce a false positive if it's not
|
||||
preceded by a non-space character (like * in a multi-line comment).
|
||||
|
||||
Output:
|
||||
Each entry is a dictionary with a 'name' and a 'section' key. We end up with:
|
||||
{ MOTHERBOARD: { name: "MOTHERBOARD", section: "hardware" }, ... }
|
||||
|
||||
TODO: Drop the 'name' key as redundant. For now it's useful for debugging.
|
||||
|
||||
This list is only used to filter config-defined options from those defined elsewhere.
|
||||
|
||||
Because the option names are the keys, only the last occurrence is retained.
|
||||
This means the actual used value might not be reflected by this function.
|
||||
The Schema class does more complete parsing for a more accurate list of options.
|
||||
|
||||
While the Schema class parses the configurations on its own, this script will
|
||||
get the preprocessor output and get the intersection of the enabled options from
|
||||
our crude scraping method and the actual compiler output.
|
||||
We end up with the actual configured state,
|
||||
better than what the config files say. You can then use the
|
||||
a decent reflection of all enabled options that (probably) came from
|
||||
resulting config.ini to produce more exact configuration files.
|
||||
'''
|
||||
outdict = {}
|
||||
section = "user"
|
||||
spatt = re.compile(r".*@section +([-a-zA-Z0-9_\s]+)$") # must match @section ...
|
||||
|
||||
f = open(filepath, encoding="utf8").read().split("\n")
|
||||
|
||||
# Get the full contents of the file and remove all block comments.
|
||||
# This will avoid false positives from #defines in comments
|
||||
f = re.sub(r'/\*.*?\*/', '', '\n'.join(f), flags=re.DOTALL).split("\n")
|
||||
|
||||
for line in f:
|
||||
sline = line.strip()
|
||||
m = re.match(spatt, sline) # @section ...
|
||||
if m: section = m.group(1).strip() ; continue
|
||||
if sline[:7] == "#define":
|
||||
# Extract the key here (we don't care about the value)
|
||||
kv = sline[8:].strip().split()
|
||||
outdict[kv[0]] = { 'name':kv[0], 'section': section }
|
||||
return outdict
|
||||
|
||||
# Compute the SHA256 hash of a file
|
||||
def get_file_sha256sum(filepath):
|
||||
sha256_hash = hashlib.sha256()
|
||||
with open(filepath,"rb") as f:
|
||||
# Read and update hash string value in blocks of 4K
|
||||
for byte_block in iter(lambda: f.read(4096),b""):
|
||||
sha256_hash.update(byte_block)
|
||||
return sha256_hash.hexdigest()
|
||||
|
||||
#
|
||||
# Compress a JSON file into a zip file
|
||||
#
|
||||
import zipfile
|
||||
def compress_file(filepath, storedname, outpath):
|
||||
with zipfile.ZipFile(outpath, 'w', compression=zipfile.ZIP_BZIP2, compresslevel=9) as zipf:
|
||||
zipf.write(filepath, arcname=storedname, compress_type=zipfile.ZIP_BZIP2, compresslevel=9)
|
||||
|
||||
def compute_build_signature(env):
|
||||
'''
|
||||
Compute the build signature by extracting all configuration settings and
|
||||
building a unique reversible signature that can be included in the binary.
|
||||
The signature can be reversed to get a 1:1 equivalent configuration file.
|
||||
Used by common-dependencies.py after filtering build files by feature.
|
||||
'''
|
||||
if 'BUILD_SIGNATURE' in env: return
|
||||
env.Append(BUILD_SIGNATURE=1)
|
||||
|
||||
build_path = Path(env['PROJECT_BUILD_DIR'], env['PIOENV'])
|
||||
marlin_json = build_path / 'marlin_config.json'
|
||||
marlin_zip = build_path / 'mc.zip'
|
||||
|
||||
# Definitions from these files will be kept
|
||||
header_paths = [ 'Marlin/Configuration.h', 'Marlin/Configuration_adv.h' ]
|
||||
|
||||
# Check if we can skip processing
|
||||
hashes = ''
|
||||
for header in header_paths:
|
||||
hashes += get_file_sha256sum(header)[0:10]
|
||||
|
||||
# Read a previously exported JSON file
|
||||
# Same configuration, skip recomputing the build signature
|
||||
same_hash = False
|
||||
try:
|
||||
with marlin_json.open() as infile:
|
||||
conf = json.load(infile)
|
||||
same_hash = conf['__INITIAL_HASH'] == hashes
|
||||
if same_hash:
|
||||
compress_file(marlin_json, 'marlin_config.json', marlin_zip)
|
||||
except:
|
||||
pass
|
||||
|
||||
# Extract "enabled" #define lines by scraping the configuration files.
|
||||
# This data also contains the @section for each option.
|
||||
conf_defines = {}
|
||||
conf_names = []
|
||||
for hpath in header_paths:
|
||||
# Get defines in the form of { name: { name:..., section:... }, ... }
|
||||
defines = enabled_defines(hpath)
|
||||
# Get all unique define names into a flat array
|
||||
conf_names += defines.keys()
|
||||
# Remember which file these defines came from
|
||||
conf_defines[hpath.split('/')[-1]] = defines
|
||||
|
||||
# Get enabled config options based on running GCC to preprocess the config files.
|
||||
# The result is a list of line strings, each starting with '#define'.
|
||||
from preprocessor import run_preprocessor
|
||||
build_output = run_preprocessor(env)
|
||||
|
||||
# Dumb regex to filter out some dumb macros
|
||||
r = re.compile(r"\(+(\s*-*\s*_.*)\)+")
|
||||
|
||||
# Extract all the #define lines in the build output as key/value pairs
|
||||
build_defines = {}
|
||||
for line in build_output:
|
||||
# Split the define from the value.
|
||||
key_val = line[8:].strip().decode().split(' ')
|
||||
key, value = key_val[0], ' '.join(key_val[1:])
|
||||
# Ignore values starting with two underscore, since it's low level
|
||||
if len(key) > 2 and key[0:2] == "__": continue
|
||||
# Ignore values containing parentheses (likely a function macro)
|
||||
if '(' in key and ')' in key: continue
|
||||
# Then filter dumb values
|
||||
if r.match(value): continue
|
||||
|
||||
build_defines[key] = value if len(value) else ""
|
||||
|
||||
#
|
||||
# Continue to gather data for CONFIGURATION_EMBEDDING or CONFIG_EXPORT
|
||||
#
|
||||
if not ('CONFIGURATION_EMBEDDING' in build_defines or 'CONFIG_EXPORT' in build_defines):
|
||||
return
|
||||
|
||||
# Filter out useless macros from the output
|
||||
cleaned_build_defines = {}
|
||||
for key in build_defines:
|
||||
# Remove all boards now
|
||||
if key.startswith("BOARD_") and key != "BOARD_INFO_NAME": continue
|
||||
# Remove all keys ending by "_T_DECLARED" as it's a copy of extraneous system stuff
|
||||
if key.endswith("_T_DECLARED"): continue
|
||||
# Remove keys that are not in the #define list in the Configuration list
|
||||
if key not in conf_names + [ 'DETAILED_BUILD_VERSION', 'STRING_DISTRIBUTION_DATE' ]: continue
|
||||
# Add to a new dictionary for simplicity
|
||||
cleaned_build_defines[key] = build_defines[key]
|
||||
|
||||
# And we only care about defines that (most likely) came from the config files
|
||||
# Build a dictionary of dictionaries with keys: 'name', 'section', 'value'
|
||||
# { 'file1': { 'option': { 'name':'option', 'section':..., 'value':... }, ... }, 'file2': { ... } }
|
||||
real_config = {}
|
||||
for header in conf_defines:
|
||||
real_config[header] = {}
|
||||
for key in cleaned_build_defines:
|
||||
if key in conf_defines[header]:
|
||||
if key[0:2] == '__': continue
|
||||
val = cleaned_build_defines[key]
|
||||
real_config[header][key] = { 'file':header, 'name': key, 'value': val, 'section': conf_defines[header][key]['section']}
|
||||
|
||||
def tryint(key):
|
||||
try: return int(build_defines[key])
|
||||
except: return 0
|
||||
|
||||
# Get the CONFIG_EXPORT value and do an extended dump if > 100
|
||||
# For example, CONFIG_EXPORT 102 will make a 'config.ini' with a [config:] group for each schema @section
|
||||
config_dump = tryint('CONFIG_EXPORT')
|
||||
extended_dump = config_dump > 100
|
||||
if extended_dump: config_dump -= 100
|
||||
|
||||
#
|
||||
# Produce an INI file if CONFIG_EXPORT == 2
|
||||
#
|
||||
if config_dump == 2:
|
||||
print("Generating config.ini ...")
|
||||
|
||||
ini_fmt = '{0:40} = {1}'
|
||||
ext_fmt = '{0:40} {1}'
|
||||
ignore = ('CONFIGURATION_H_VERSION', 'CONFIGURATION_ADV_H_VERSION', 'CONFIG_EXPORT')
|
||||
|
||||
if extended_dump:
|
||||
# Extended export will dump config options by section
|
||||
|
||||
# We'll use Schema class to get the sections
|
||||
try:
|
||||
conf_schema = schema.extract()
|
||||
except Exception as exc:
|
||||
print("Error: " + str(exc))
|
||||
exit(1)
|
||||
|
||||
# Then group options by schema @section
|
||||
sections = {}
|
||||
for header in real_config:
|
||||
for name in real_config[header]:
|
||||
#print(f" name: {name}")
|
||||
if name not in ignore:
|
||||
ddict = real_config[header][name]
|
||||
#print(f" real_config[{header}][{name}]:", ddict)
|
||||
sect = ddict['section']
|
||||
if sect not in sections: sections[sect] = {}
|
||||
sections[sect][name] = ddict
|
||||
|
||||
# Get all sections as a list of strings, with spaces and dashes replaced by underscores
|
||||
long_list = [ re.sub(r'[- ]+', '_', x).lower() for x in sections.keys() ]
|
||||
# Make comma-separated lists of sections with 64 characters or less
|
||||
sec_lines = []
|
||||
while len(long_list):
|
||||
line = long_list.pop(0) + ', '
|
||||
while len(long_list) and len(line) + len(long_list[0]) < 64 - 1:
|
||||
line += long_list.pop(0) + ', '
|
||||
sec_lines.append(line.strip())
|
||||
sec_lines[-1] = sec_lines[-1][:-1] # Remove the last comma
|
||||
|
||||
else:
|
||||
sec_lines = ['all']
|
||||
|
||||
# Build the ini_use_config item
|
||||
sec_list = ini_fmt.format('ini_use_config', sec_lines[0])
|
||||
for line in sec_lines[1:]: sec_list += '\n' + ext_fmt.format('', line)
|
||||
|
||||
config_ini = build_path / 'config.ini'
|
||||
with config_ini.open('w') as outfile:
|
||||
filegrp = { 'Configuration.h':'config:basic', 'Configuration_adv.h':'config:advanced' }
|
||||
vers = build_defines["CONFIGURATION_H_VERSION"]
|
||||
dt_string = datetime.now().strftime("%Y-%m-%d at %H:%M:%S")
|
||||
|
||||
outfile.write(
|
||||
f'''#
|
||||
# Marlin Firmware
|
||||
# config.ini - Options to apply before the build
|
||||
#
|
||||
# Generated by Marlin build on {dt_string}
|
||||
#
|
||||
[config:base]
|
||||
#
|
||||
# ini_use_config - A comma-separated list of actions to apply to the Configuration files.
|
||||
# The actions will be applied in the listed order.
|
||||
# - none
|
||||
# Ignore this file and don't apply any configuration options
|
||||
#
|
||||
# - base
|
||||
# Just apply the options in config:base to the configuration
|
||||
#
|
||||
# - minimal
|
||||
# Just apply the options in config:minimal to the configuration
|
||||
#
|
||||
# - all
|
||||
# Apply all 'config:*' sections in this file to the configuration
|
||||
#
|
||||
# - another.ini
|
||||
# Load another INI file with a path relative to this config.ini file (i.e., within Marlin/)
|
||||
#
|
||||
# - https://me.myserver.com/path/to/configs
|
||||
# Fetch configurations from any URL.
|
||||
#
|
||||
# - example/Creality/Ender-5 Plus @ bugfix-2.1.x
|
||||
# Fetch example configuration files from the MarlinFirmware/Configurations repository
|
||||
# https://raw.githubusercontent.com/MarlinFirmware/Configurations/bugfix-2.1.x/config/examples/Creality/Ender-5%20Plus/
|
||||
#
|
||||
# - example/default @ release-2.0.9.7
|
||||
# Fetch default configuration files from the MarlinFirmware/Configurations repository
|
||||
# https://raw.githubusercontent.com/MarlinFirmware/Configurations/release-2.0.9.7/config/default/
|
||||
#
|
||||
# - [disable]
|
||||
# Comment out all #defines in both Configuration.h and Configuration_adv.h. This is useful
|
||||
# to start with a clean slate before applying any config: options, so only the options explicitly
|
||||
# set in config.ini will be enabled in the configuration.
|
||||
#
|
||||
# - [flatten] (Not yet implemented)
|
||||
# Produce a flattened set of Configuration.h and Configuration_adv.h files with only the enabled
|
||||
# #defines and no comments. A clean look, but context-free.
|
||||
#
|
||||
{sec_list}
|
||||
{ini_fmt.format('ini_config_vers', vers)}
|
||||
''' )
|
||||
|
||||
if extended_dump:
|
||||
|
||||
# Loop through the sections
|
||||
for skey in sorted(sections):
|
||||
#print(f" skey: {skey}")
|
||||
sani = re.sub(r'[- ]+', '_', skey).lower()
|
||||
outfile.write(f"\n[config:{sani}]\n")
|
||||
opts = sections[skey]
|
||||
for name in sorted(opts):
|
||||
val = opts[name]['value']
|
||||
if val == '': val = 'on'
|
||||
#print(f" {name} = {val}")
|
||||
outfile.write(ini_fmt.format(name.lower(), val) + '\n')
|
||||
|
||||
else:
|
||||
|
||||
# Standard export just dumps config:basic and config:advanced sections
|
||||
for header in real_config:
|
||||
outfile.write(f'\n[{filegrp[header]}]\n')
|
||||
for name in sorted(real_config[header]):
|
||||
if name not in ignore:
|
||||
val = real_config[header][name]['value']
|
||||
if val == '': val = 'on'
|
||||
outfile.write(ini_fmt.format(name.lower(), val) + '\n')
|
||||
|
||||
#
|
||||
# CONFIG_EXPORT 3 = schema.json, 4 = schema.yml
|
||||
#
|
||||
if config_dump >= 3:
|
||||
try:
|
||||
conf_schema = schema.extract()
|
||||
except Exception as exc:
|
||||
print("Error: " + str(exc))
|
||||
conf_schema = None
|
||||
|
||||
if conf_schema:
|
||||
#
|
||||
# 3 = schema.json
|
||||
#
|
||||
if config_dump in (3, 13):
|
||||
print("Generating schema.json ...")
|
||||
schema.dump_json(conf_schema, build_path / 'schema.json')
|
||||
if config_dump == 13:
|
||||
schema.group_options(conf_schema)
|
||||
schema.dump_json(conf_schema, build_path / 'schema_grouped.json')
|
||||
|
||||
#
|
||||
# 4 = schema.yml
|
||||
#
|
||||
elif config_dump == 4:
|
||||
print("Generating schema.yml ...")
|
||||
try:
|
||||
import yaml
|
||||
except ImportError:
|
||||
env.Execute(env.VerboseAction(
|
||||
'$PYTHONEXE -m pip install "pyyaml"',
|
||||
"Installing YAML for schema.yml export",
|
||||
))
|
||||
import yaml
|
||||
schema.dump_yaml(conf_schema, build_path / 'schema.yml')
|
||||
|
||||
#
|
||||
# Produce a JSON file for CONFIGURATION_EMBEDDING or CONFIG_EXPORT == 1
|
||||
# Skip if an identical JSON file was already present.
|
||||
#
|
||||
if not same_hash and (config_dump == 1 or 'CONFIGURATION_EMBEDDING' in build_defines):
|
||||
with marlin_json.open('w') as outfile:
|
||||
|
||||
json_data = {}
|
||||
if extended_dump:
|
||||
print("Extended dump ...")
|
||||
for header in real_config:
|
||||
confs = real_config[header]
|
||||
json_data[header] = {}
|
||||
for name in confs:
|
||||
c = confs[name]
|
||||
s = c['section']
|
||||
if s not in json_data[header]: json_data[header][s] = {}
|
||||
json_data[header][s][name] = c['value']
|
||||
else:
|
||||
for header in real_config:
|
||||
conf = real_config[header]
|
||||
#print(f"real_config[{header}]", conf)
|
||||
for name in conf:
|
||||
json_data[name] = conf[name]['value']
|
||||
|
||||
json_data['__INITIAL_HASH'] = hashes
|
||||
|
||||
# Append the source code version and date
|
||||
json_data['VERSION'] = {
|
||||
'DETAILED_BUILD_VERSION': cleaned_build_defines['DETAILED_BUILD_VERSION'],
|
||||
'STRING_DISTRIBUTION_DATE': cleaned_build_defines['STRING_DISTRIBUTION_DATE']
|
||||
}
|
||||
try:
|
||||
curver = subprocess.check_output(["git", "describe", "--match=NeVeRmAtCh", "--always"]).strip()
|
||||
json_data['VERSION']['GIT_REF'] = curver.decode()
|
||||
except:
|
||||
pass
|
||||
|
||||
json.dump(json_data, outfile, separators=(',', ':'))
|
||||
|
||||
#
|
||||
# The rest only applies to CONFIGURATION_EMBEDDING
|
||||
#
|
||||
if not 'CONFIGURATION_EMBEDDING' in build_defines:
|
||||
(build_path / 'mc.zip').unlink(missing_ok=True)
|
||||
return
|
||||
|
||||
# Compress the JSON file as much as we can
|
||||
if not same_hash:
|
||||
compress_file(marlin_json, 'marlin_config.json', marlin_zip)
|
||||
|
||||
# Generate a C source file containing the entire ZIP file as an array
|
||||
with open('Marlin/src/mczip.h','wb') as result_file:
|
||||
result_file.write(
|
||||
b'#ifndef NO_CONFIGURATION_EMBEDDING_WARNING\n'
|
||||
+ b' #warning "Generated file \'mc.zip\' is embedded (Define NO_CONFIGURATION_EMBEDDING_WARNING to suppress this warning.)"\n'
|
||||
+ b'#endif\n'
|
||||
+ b'const unsigned char mc_zip[] PROGMEM = {\n '
|
||||
)
|
||||
count = 0
|
||||
for b in (build_path / 'mc.zip').open('rb').read():
|
||||
result_file.write(b' 0x%02X,' % b)
|
||||
count += 1
|
||||
if count % 16 == 0: result_file.write(b'\n ')
|
||||
if count % 16: result_file.write(b'\n')
|
||||
result_file.write(b'};\n')
|
||||
|
||||
if __name__ == "__main__":
|
||||
# Build required. From command line just explain usage.
|
||||
print("Use schema.py to export JSON and YAML from the command-line.")
|
||||
print("Build Marlin with CONFIG_EXPORT 2 to export 'config.ini'.")
|
53
buildroot/share/PlatformIO/scripts/simulator.py
Normal file
53
buildroot/share/PlatformIO/scripts/simulator.py
Normal file
@ -0,0 +1,53 @@
|
||||
#
|
||||
# simulator.py
|
||||
# PlatformIO pre: script for simulator builds
|
||||
#
|
||||
|
||||
import pioutil
|
||||
if pioutil.is_pio_build():
|
||||
# Get the environment thus far for the build
|
||||
Import("env")
|
||||
|
||||
#print(env.Dump())
|
||||
|
||||
#
|
||||
# Give the binary a distinctive name
|
||||
#
|
||||
|
||||
env['PROGNAME'] = "MarlinSimulator"
|
||||
|
||||
#
|
||||
# If Xcode is installed add the path to its Frameworks folder,
|
||||
# or if Mesa is installed try to use its GL/gl.h.
|
||||
#
|
||||
|
||||
import sys
|
||||
if sys.platform == 'darwin':
|
||||
|
||||
#
|
||||
# Silence half of the ranlib warnings. (No equivalent for 'ARFLAGS')
|
||||
#
|
||||
env['RANLIBFLAGS'] += [ "-no_warning_for_no_symbols" ]
|
||||
|
||||
# Default paths for Xcode and a lucky GL/gl.h dropped by Mesa
|
||||
xcode_path = "/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/System/Library/Frameworks"
|
||||
mesa_path = "/opt/local/include/GL/gl.h"
|
||||
|
||||
import os.path
|
||||
|
||||
if os.path.exists(xcode_path):
|
||||
|
||||
env['BUILD_FLAGS'] += [ "-F" + xcode_path ]
|
||||
print("Using OpenGL framework headers from Xcode.app")
|
||||
|
||||
elif os.path.exists(mesa_path):
|
||||
|
||||
env['BUILD_FLAGS'] += [ '-D__MESA__' ]
|
||||
print("Using OpenGL header from", mesa_path)
|
||||
|
||||
else:
|
||||
|
||||
print("\n\nNo OpenGL headers found. Install Xcode for matching headers, or use 'sudo port install mesa' to get a GL/gl.h.\n\n")
|
||||
|
||||
# Break out of the PIO build immediately
|
||||
sys.exit(1)
|
61
buildroot/share/PlatformIO/scripts/stm32_serialbuffer.py
Normal file
61
buildroot/share/PlatformIO/scripts/stm32_serialbuffer.py
Normal file
@ -0,0 +1,61 @@
|
||||
#
|
||||
# stm32_serialbuffer.py
|
||||
#
|
||||
import pioutil
|
||||
if pioutil.is_pio_build():
|
||||
Import("env")
|
||||
|
||||
# Get a build flag's value or None
|
||||
def getBuildFlagValue(name):
|
||||
for flag in build_flags:
|
||||
if isinstance(flag, list) and flag[0] == name:
|
||||
return flag[1]
|
||||
|
||||
return None
|
||||
|
||||
# Get an overriding buffer size for RX or TX from the build flags
|
||||
def getInternalSize(side):
|
||||
return getBuildFlagValue(f"MF_{side}_BUFFER_SIZE") or \
|
||||
getBuildFlagValue(f"SERIAL_{side}_BUFFER_SIZE") or \
|
||||
getBuildFlagValue(f"USART_{side}_BUF_SIZE")
|
||||
|
||||
# Get the largest defined buffer size for RX or TX
|
||||
def getBufferSize(side, default):
|
||||
# Get a build flag value or fall back to the given default
|
||||
internal = int(getInternalSize(side) or default)
|
||||
flag = side + "_BUFFER_SIZE"
|
||||
# Return the largest value
|
||||
return max(int(mf[flag]), internal) if flag in mf else internal
|
||||
|
||||
# Add a build flag if it's not already defined
|
||||
def tryAddFlag(name, value):
|
||||
if getBuildFlagValue(name) is None:
|
||||
env.Append(BUILD_FLAGS=[f"-D{name}={value}"])
|
||||
|
||||
# Marlin uses the `RX_BUFFER_SIZE` \ `TX_BUFFER_SIZE` options to
|
||||
# configure buffer sizes for receiving \ transmitting serial data.
|
||||
# Stm32duino uses another set of defines for the same purpose, so this
|
||||
# script gets the values from the configuration and uses them to define
|
||||
# `SERIAL_RX_BUFFER_SIZE` and `SERIAL_TX_BUFFER_SIZE` as global build
|
||||
# flags so they are available for use by the platform.
|
||||
#
|
||||
# The script will set the value as the default one (64 bytes)
|
||||
# or the user-configured one, whichever is higher.
|
||||
#
|
||||
# Marlin's default buffer sizes are 128 for RX and 32 for TX.
|
||||
# The highest value is taken (128/64).
|
||||
#
|
||||
# If MF_*_BUFFER_SIZE, SERIAL_*_BUFFER_SIZE, USART_*_BUF_SIZE, are
|
||||
# defined, the first of these values will be used as the minimum.
|
||||
build_flags = env.ParseFlags(env.get('BUILD_FLAGS'))["CPPDEFINES"]
|
||||
mf = env["MARLIN_FEATURES"]
|
||||
|
||||
# Get the largest defined buffer sizes for RX or TX, using defaults for undefined
|
||||
rxBuf = getBufferSize("RX", 128)
|
||||
txBuf = getBufferSize("TX", 64)
|
||||
|
||||
# Provide serial buffer sizes to the stm32duino platform
|
||||
tryAddFlag("SERIAL_RX_BUFFER_SIZE", rxBuf)
|
||||
tryAddFlag("SERIAL_TX_BUFFER_SIZE", txBuf)
|
||||
tryAddFlag("USART_RX_BUF_SIZE", rxBuf)
|
||||
tryAddFlag("USART_TX_BUF_SIZE", txBuf)
|
Reference in New Issue
Block a user