Prepare parts of the build system

extracted and slightly modified from Buddy-FW, still doesn't work
vintagepc/version-and-build
D.R.racer 2021-04-13 08:24:43 +02:00 committed by DRracer
parent 3f7e32f6cf
commit 257d0ec340
21 changed files with 1667 additions and 0 deletions

126
.clang-format Normal file
View File

@ -0,0 +1,126 @@
---
Language: Cpp
# BasedOnStyle: WebKit
AccessModifierOffset: -4
AlignAfterOpenBracket: DontAlign
AlignConsecutiveMacros: true
AlignConsecutiveAssignments: false
AlignConsecutiveDeclarations: false
AlignEscapedNewlines: Right
AlignOperands: false
AlignTrailingComments: true
AllowAllArgumentsOnNextLine: true
AllowAllConstructorInitializersOnNextLine: true
AllowAllParametersOfDeclarationOnNextLine: true
AllowShortBlocksOnASingleLine: false
AllowShortCaseLabelsOnASingleLine: false
AllowShortFunctionsOnASingleLine: All
AllowShortLambdasOnASingleLine: All
AllowShortIfStatementsOnASingleLine: Never
AllowShortLoopsOnASingleLine: false
AlwaysBreakAfterDefinitionReturnType: None
AlwaysBreakAfterReturnType: None
AlwaysBreakBeforeMultilineStrings: false
AlwaysBreakTemplateDeclarations: MultiLine
BinPackArguments: true
BinPackParameters: true
BraceWrapping:
AfterCaseLabel: false
AfterClass: false
AfterControlStatement: false
AfterEnum: false
AfterFunction: false
AfterNamespace: false
AfterObjCDeclaration: false
AfterStruct: false
AfterUnion: false
AfterExternBlock: false
BeforeCatch: false
BeforeElse: false
IndentBraces: false
SplitEmptyFunction: true
SplitEmptyRecord: true
SplitEmptyNamespace: true
BreakBeforeBinaryOperators: All
BreakBeforeBraces: Custom
BreakBeforeInheritanceComma: false
BreakInheritanceList: BeforeColon
BreakBeforeTernaryOperators: true
BreakConstructorInitializersBeforeComma: false
BreakConstructorInitializers: BeforeComma
BreakAfterJavaFieldAnnotations: false
BreakStringLiterals: true
ColumnLimit: 0
CommentPragmas: '^ IWYU pragma:'
CompactNamespaces: false
ConstructorInitializerAllOnOneLineOrOnePerLine: false
ConstructorInitializerIndentWidth: 4
ContinuationIndentWidth: 4
Cpp11BracedListStyle: false
DerivePointerAlignment: false
DisableFormat: false
ExperimentalAutoDetectBinPacking: false
FixNamespaceComments: false
ForEachMacros:
- foreach
- Q_FOREACH
- BOOST_FOREACH
IncludeBlocks: Preserve
IncludeCategories:
- Regex: '^"(llvm|llvm-c|clang|clang-c)/'
Priority: 2
- Regex: '^(<|"(gtest|gmock|isl|json)/)'
Priority: 3
- Regex: '.*'
Priority: 1
IncludeIsMainRegex: '(Test)?$'
IndentCaseLabels: false
IndentPPDirectives: BeforeHash
IndentWidth: 4
IndentWrappedFunctionNames: false
JavaScriptQuotes: Leave
JavaScriptWrapImports: true
KeepEmptyLinesAtTheStartOfBlocks: true
MacroBlockBegin: ''
MacroBlockEnd: ''
MaxEmptyLinesToKeep: 1
NamespaceIndentation: Inner
ObjCBinPackProtocolList: Auto
ObjCBlockIndentWidth: 4
ObjCSpaceAfterProperty: true
ObjCSpaceBeforeProtocolList: true
PenaltyBreakAssignment: 2
PenaltyBreakBeforeFirstCallParameter: 19
PenaltyBreakComment: 300
PenaltyBreakFirstLessLess: 120
PenaltyBreakString: 1000
PenaltyBreakTemplateDeclaration: 10
PenaltyExcessCharacter: 1000000
PenaltyReturnTypeOnItsOwnLine: 60
PointerAlignment: Right
ReflowComments: true
SortIncludes: false
SortUsingDeclarations: true
SpaceAfterCStyleCast: false
SpaceAfterLogicalNot: false
SpaceAfterTemplateKeyword: true
SpaceBeforeAssignmentOperators: true
SpaceBeforeCpp11BracedList: true
SpaceBeforeCtorInitializerColon: true
SpaceBeforeInheritanceColon: true
SpaceBeforeParens: ControlStatements
SpaceBeforeRangeBasedForLoopColon: true
SpaceInEmptyParentheses: false
SpacesBeforeTrailingComments: 1
SpacesInAngles: false
SpacesInContainerLiterals: true
SpacesInCStyleCastParentheses: false
SpacesInParentheses: false
SpacesInSquareBrackets: false
Standard: Cpp11
StatementMacros:
- Q_UNUSED
- QT_REQUIRE_VERSION
TabWidth: 8
UseTab: Never
...

21
.cmake-format.py Normal file
View File

@ -0,0 +1,21 @@
# If a statement is wrapped to more than one line, than dangle the closing
# parenthesis on it's own line.
dangle_parens = True
dangle_align = 'child'
# If true, the parsers may infer whether or not an argument list is sortable
# (without annotation).
autosort = True
# How wide to allow formatted cmake files
line_width = 100
additional_commands = {
"target_sources": {
"kwargs": {
"PUBLIC": "*",
"PRIVATE": "*",
"INTERFACE": "*",
}
},
}

15
.gitignore vendored Normal file
View File

@ -0,0 +1,15 @@
.vs
/build*
.cproject
.project
.settings
.dependencies
.DS_Store
/CMakeLists.txt.user
.ccls-cache
.idea
compile_commands.json
/.vscode/*.peripherals.state.json
/.vscode/*.registers.state.json
Makefile
/doc/html/

39
.pre-commit-config.yaml Normal file
View File

@ -0,0 +1,39 @@
# See https://pre-commit.com for more information
# See https://pre-commit.com/hooks.html for more hooks
repos:
- repo: https://github.com/dragomirecky/cmake-format-pre-commit-hook
rev: 'v0.6.0'
hooks:
- id: cmake-format # cmake formatter
files: (CMakeLists.*|.*\.cmake|.*\.cmake.in)
- repo: https://github.com/pre-commit/mirrors-yapf
rev: 'v0.27.0'
hooks:
- id: yapf # python formatter
- repo: local
hooks:
- id: clang-format
name: clang-format
description: This hook automatically checks and reformats changed files using clang-format formatter.
entry: './.dependencies/clang-format-9.0.0-noext/clang-format'
language: script
files: \.(h\+\+|h|hh|hxx|hpp|cuh|c|cc|cpp|cu|c\+\+|cxx|tpp|txx)$
args: ['-i', '-style=file']
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: 'v2.4.0'
hooks:
- id: trailing-whitespace
args: [--markdown-linebreak-ext=md]
- id: end-of-file-fixer
- id: mixed-line-ending
exclude: |
(?x)(
^tests/unit/lang/translator/(keys|cs|es|fr|de|pl|it)\.txt$|
^lib/inih/|
^lib/Marlin/|
^lib/Prusa-Error-Codes/|
^lib/TMCStepper/|
^lib/Middlewares/Third_Party/LwIP/|
^lib/jsmn/|
^lib/Catch2/
)

199
CMakeLists.txt Normal file
View File

@ -0,0 +1,199 @@
cmake_minimum_required(VERSION 3.15)
include(cmake/Utilities.cmake)
include(cmake/GetGitRevisionDescription.cmake)
include(cmake/ProjectVersion.cmake)
project(
MMU
LANGUAGES C CXX ASM
VERSION ${PROJECT_VERSION}
)
if(NOT CMAKE_CROSSCOMPILING)
#
# If we are not crosscompiling, include `utils` with host tools.
#
add_subdirectory(utils)
endif()
#
# Command Line Options
#
# You should specify those options when invoking CMake. Example:
# ~~~
# cmake .. <other options> -DPRINTER=MMU
# ~~~
set(PRINTER_VALID_OPTS "MMU")
set(PRINTER
"MMU"
CACHE
STRING
"Select the MMU unit for which you want to compile the project (valid values are ${PRINTER_VALID_OPTS})."
)
set(PROJECT_VERSION_SUFFIX
"<auto>"
CACHE
STRING
"Full version suffix to be shown on the info screen in settings (e.g. full_version=4.0.3-BETA+1035.PR111.B4, suffix=-BETA+1035.PR111.B4). Defaults to '+<commit sha>.<dirty?>.<debug?>' if set to '<auto>'."
)
set(PROJECT_VERSION_SUFFIX_SHORT
"<auto>"
CACHE
STRING
"Short version suffix to be shown on splash screen. Defaults to '+<BUILD_NUMBER>' if set to '<auto>'."
)
set(BUILD_NUMBER
""
CACHE STRING "Build number of the firmware. Resolved automatically if not specified."
)
set(CUSTOM_COMPILE_OPTIONS
""
CACHE STRING "Allows adding custom C/C++ flags"
)
# Validate options
foreach(OPTION "PRINTER")
if(NOT ${OPTION} IN_LIST ${OPTION}_VALID_OPTS)
message(FATAL_ERROR "Invalid ${OPTION} ${${OPTION}}: Valid values are ${${OPTION}_VALID_OPTS}")
endif()
endforeach()
# Resolve BUILD_NUMBER and PROJECT_VERSION_* variables
resolve_version_variables()
# Check GCC Version
get_recommended_gcc_version(RECOMMENDED_TOOLCHAIN_VERSION)
if(CMAKE_CROSSCOMPILING AND NOT CMAKE_CXX_COMPILER_VERSION VERSION_EQUAL
${RECOMMENDED_TOOLCHAIN_VERSION}
)
message(WARNING "Recommended AVR toolchain is ${RECOMMENDED_TOOLCHAIN_VERSION}"
", but you have ${CMAKE_CXX_COMPILER_VERSION}"
)
elseif(NOT CMAKE_CROSSCOMPILING AND NOT CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
message(
WARNING
"Recommended compiler for host tools and unittests is GCC, you have ${CMAKE_CXX_COMPILER_ID}."
)
endif()
# Inform user about the resolved settings
message(STATUS "Project version: ${PROJECT_VERSION}")
message(STATUS "Project version with full suffix: ${PROJECT_VERSION_FULL}")
message(
STATUS "Project version with short suffix: ${PROJECT_VERSION}${PROJECT_VERSION_SUFFIX_SHORT}"
)
message(STATUS "Using toolchain file: ${CMAKE_TOOLCHAIN_FILE}.")
message(STATUS "Printer: ${PRINTER}")
# eclipse sets those variables, so lets just use them so we don't get a warning about unused
# variables
set(unused "${CMAKE_VERBOSE_MAKEFILE} ${CMAKE_RULE_MESSAGES}")
# append custom C/C++ flags
if(CUSTOM_COMPILE_OPTIONS)
string(REPLACE " " ";" CUSTOM_COMPILE_OPTIONS "${CUSTOM_COMPILE_OPTIONS}")
add_compile_options(${CUSTOM_COMPILE_OPTIONS})
endif()
#
# MMUHeaders
#
# add_library(MMUHeaders INTERFACE) target_include_directories( MMUHeaders INTERFACE include
# include/stm32f4_hal include/usb_host include/usb_device include/marlin include/freertos )
# target_link_libraries(A3idesHeaders INTERFACE STM32F4::HAL FreeRTOS::FreeRTOS)
# target_compile_definitions( A3idesHeaders INTERFACE PRINTER_TYPE=PRINTER_PRUSA_${PRINTER} )
#
# Global Compiler & Linker Configuration
#
# include symbols
add_compile_options(-g)
# optimizations
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
add_compile_options(-Og)
else()
add_compile_options(-Os)
endif()
if(CMAKE_CROSSCOMPILING)
# mcu related settings set(MCU_FLAGS -mthumb -mcpu=cortex-m4 -mfloat-abi=hard -mfpu=fpv4-sp-d16)
# add_compile_options(${MCU_FLAGS}) add_link_options(${MCU_FLAGS})
# split and gc sections
add_compile_options(-ffunction-sections -fdata-sections)
add_link_options(-Wl,--gc-sections)
# disable exceptions and related metadata
add_compile_options(-fno-exceptions -fno-unwind-tables)
add_link_options(-Wl,--defsym,__exidx_start=0,--defsym,__exidx_end=0)
endif()
# enable all warnings (well, not all, but some)
add_compile_options(-Wall -Wsign-compare)
add_compile_options($<$<COMPILE_LANGUAGE:CXX>:-Wno-register> $<$<COMPILE_LANGUAGE:CXX>:-std=c++14>)
# support _DEBUG macro (some code uses to recognize debug builds)
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
add_compile_definitions(_DEBUG)
endif()
# if(CMAKE_CROSSCOMPILING) # configure linker script set(LINKER_SCRIPT
# "${CMAKE_CURRENT_SOURCE_DIR}/src/STM32F407VG_FLASH.ld")
# add_link_options("-Wl,-T,${LINKER_SCRIPT}") endif()
#
# Import definitions of all libraries
#
# add_subdirectory(lib)
#
# MMU firmware
#
add_executable(firmware)
set_target_properties(firmware PROPERTIES CXX_STANDARD 14)
# generate firmware.bin file
objcopy(firmware "binary" ".bin")
# generate linker map file
target_link_options(firmware PUBLIC -Wl,-Map=firmware.map)
# inform about the firmware's size in terminal
report_size(firmware)
# add_link_dependency(firmware "${LINKER_SCRIPT}")
target_include_directories(firmware PRIVATE include src)
target_compile_options(firmware PRIVATE -Wdouble-promotion)
# target_link_libraries( firmware PRIVATE A3idesHeaders )
target_sources(firmware PRIVATE src/main.cpp)
set_property(
SOURCE src/version.c
APPEND
PROPERTY COMPILE_DEFINITIONS
FW_BUILD_NUMBER=${BUILD_NUMBER}
FW_VERSION_FULL=${PROJECT_VERSION_FULL}
FW_VERSION=${PROJECT_VERSION}
FW_VERSION_SUFFIX=${PROJECT_VERSION_SUFFIX}
FW_VERSION_SUFFIX_SHORT=${PROJECT_VERSION_SUFFIX_SHORT}
)
if(NOT CMAKE_CROSSCOMPILING)
enable_testing()
add_subdirectory(tests)
endif()

View File

@ -0,0 +1,99 @@
get_filename_component(PROJECT_CMAKE_DIR "${CMAKE_CURRENT_LIST_FILE}" DIRECTORY)
include("${PROJECT_CMAKE_DIR}/Utilities.cmake")
set(CMAKE_SYSTEM_NAME Generic)
set(CMAKE_SYSTEM_PROCESSOR ARM)
#
# Utilities
if(MINGW
OR CYGWIN
OR WIN32
)
set(UTIL_SEARCH_CMD where)
set(EXECUTABLE_SUFFIX ".exe")
elseif(UNIX OR APPLE)
set(UTIL_SEARCH_CMD which)
set(EXECUTABLE_SUFFIX "")
endif()
set(TOOLCHAIN_PREFIX arm-none-eabi-)
#
# Looking up the toolchain
#
if(ARM_TOOLCHAIN_DIR)
# using toolchain set by gcc-arm-none-eabi.cmake (locked version)
set(BINUTILS_PATH "${ARM_TOOLCHAIN_DIR}/bin")
else()
# search for ANY arm-none-eabi-gcc toolchain
execute_process(
COMMAND ${UTIL_SEARCH_CMD} ${TOOLCHAIN_PREFIX}gcc
OUTPUT_VARIABLE ARM_NONE_EABI_GCC_PATH
OUTPUT_STRIP_TRAILING_WHITESPACE
RESULT_VARIABLE FIND_RESULT
)
# found?
if(NOT "${FIND_RESULT}" STREQUAL "0")
message(FATAL_ERROR "arm-none-eabi-gcc not found")
endif()
get_filename_component(BINUTILS_PATH "${ARM_NONE_EABI_GCC_PATH}" DIRECTORY)
get_filename_component(ARM_TOOLCHAIN_DIR ${BINUTILS_PATH} DIRECTORY)
endif()
#
# Setup CMake
#
# Without that flag CMake is not able to pass test compilation check
set(CMAKE_TRY_COMPILE_TARGET_TYPE STATIC_LIBRARY)
set(triple armv7m-none-eabi)
set(CMAKE_C_COMPILER
"${BINUTILS_PATH}/${TOOLCHAIN_PREFIX}gcc${EXECUTABLE_SUFFIX}"
CACHE FILEPATH "" FORCE
)
set(CMAKE_C_COMPILER_TARGET
${triple}
CACHE STRING "" FORCE
)
set(CMAKE_ASM_COMPILER
"${BINUTILS_PATH}/${TOOLCHAIN_PREFIX}gcc${EXECUTABLE_SUFFIX}"
CACHE FILEPATH "" FORCE
)
set(CMAKE_ASM_COMPILER_TARGET
${triple}
CACHE STRING "" FORCE
)
set(CMAKE_CXX_COMPILER
"${BINUTILS_PATH}/${TOOLCHAIN_PREFIX}g++${EXECUTABLE_SUFFIX}"
CACHE FILEPATH "" FORCE
)
set(CMAKE_CXX_COMPILER_TARGET
${triple}
CACHE STRING "" FORCE
)
set(CMAKE_EXE_LINKER_FLAGS_INIT
"--specs=nosys.specs"
CACHE STRING "" FORCE
)
set(CMAKE_ASM_COMPILE_OBJECT
"<CMAKE_ASM_COMPILER> <DEFINES> <FLAGS> -o <OBJECT> -c <SOURCE>"
CACHE STRING "" FORCE
)
set(CMAKE_OBJCOPY
"${BINUTILS_PATH}/${TOOLCHAIN_PREFIX}objcopy${EXECUTABLE_SUFFIX}"
CACHE INTERNAL "objcopy tool"
)
set(CMAKE_SIZE_UTIL
"${BINUTILS_PATH}/${TOOLCHAIN_PREFIX}size${EXECUTABLE_SUFFIX}"
CACHE INTERNAL "size tool"
)
set(CMAKE_FIND_ROOT_PATH "${ARM_TOOLCHAIN_DIR}")
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)

View File

@ -0,0 +1,22 @@
# getlocked version
get_filename_component(PROJECT_CMAKE_DIR "${CMAKE_CURRENT_LIST_FILE}" DIRECTORY)
get_filename_component(PROJECT_ROOT_DIR "${PROJECT_CMAKE_DIR}" DIRECTORY)
include("${PROJECT_CMAKE_DIR}/Utilities.cmake")
get_recommended_gcc_version(RECOMMENDED_TOOLCHAIN_VERSION)
set(RECOMMENDED_TOOLCHAIN_BINUTILS
"${PROJECT_ROOT_DIR}/.dependencies/gcc-arm-none-eabi-${RECOMMENDED_TOOLCHAIN_VERSION}/bin"
)
# check that the locked version of gcc-arm-none-eabi is present
if(NOT EXISTS "${RECOMMENDED_TOOLCHAIN_BINUTILS}")
message(
FATAL_ERROR
"arm-none-eabi-gcc (version ${RECOMMENDED_TOOLCHAIN_VERSION}) not found. Run the command below to download it.\n"
"${PROJECT_ROOT_DIR}/utils/bootstrap.sh\n"
)
endif()
# include any-gcc-arm-none-eabi toolchain and pass in ARM_TOOLCHAIN_DIR
get_filename_component(ARM_TOOLCHAIN_DIR "${RECOMMENDED_TOOLCHAIN_BINUTILS}" DIRECTORY)
include("${PROJECT_ROOT_DIR}/cmake/AnyGccArmNoneEabi.cmake")

View File

@ -0,0 +1,232 @@
# * Returns a version string from Git
#
# These functions force a re-configure on each git commit so that you can trust the values of the
# variables in your build system.
#
# get_git_head_revision(<refspecvar> <hashvar> [<additional arguments to git describe> ...])
#
# Returns the refspec and sha hash of the current head revision
#
# git_describe(<var> [<additional arguments to git describe> ...])
#
# Returns the results of git describe on the source tree, and adjusting the output so that it tests
# false if an error occurs.
#
# git_get_exact_tag(<var> [<additional arguments to git describe> ...])
#
# Returns the results of git describe --exact-match on the source tree, and adjusting the output so
# that it tests false if there was no exact matching tag.
#
# git_local_changes(<var>)
#
# Returns either "CLEAN" or "DIRTY" with respect to uncommitted changes. Uses the return code of
# "git diff-index --quiet HEAD --". Does not regard untracked files.
#
# git_count_parent_commits(<var>)
#
# Returns number of commits preceeding current commit -1 if git rev-list --count HEAD failed or
# "GIT-NOTFOUND" if git executable was not found or "HEAD-HASH-NOTFOUND" if head hash was not found.
# I don't know if get_git_head_revision() must be called internally or not, as reason of calling it
# is not clear for me also in git_local_changes().
#
# Requires CMake 2.6 or newer (uses the 'function' command)
#
# Original Author: 2009-2010 Ryan Pavlik <rpavlik@iastate.edu> <abiryan@ryand.net>
# http://academic.cleardefinition.com Iowa State University HCI Graduate Program/VRAC
#
# Copyright Iowa State University 2009-2010. Distributed under the Boost Software License, Version
# 1.0. (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
if(__get_git_revision_description)
return()
endif()
set(__get_git_revision_description YES)
# We must run the following at "include" time, not at function call time, to find the path to this
# module rather than the path to a calling list file
get_filename_component(_gitdescmoddir ${CMAKE_CURRENT_LIST_FILE} PATH)
function(get_git_head_revision _refspecvar _hashvar)
set(GIT_PARENT_DIR "${CMAKE_CURRENT_SOURCE_DIR}")
set(GIT_DIR "${GIT_PARENT_DIR}/.git")
while(NOT EXISTS "${GIT_DIR}") # .git dir not found, search parent directories
set(GIT_PREVIOUS_PARENT "${GIT_PARENT_DIR}")
get_filename_component(GIT_PARENT_DIR ${GIT_PARENT_DIR} PATH)
if(GIT_PARENT_DIR STREQUAL GIT_PREVIOUS_PARENT)
# We have reached the root directory, we are not in git
set(${_refspecvar}
"GITDIR-NOTFOUND"
PARENT_SCOPE
)
set(${_hashvar}
"GITDIR-NOTFOUND"
PARENT_SCOPE
)
return()
endif()
set(GIT_DIR "${GIT_PARENT_DIR}/.git")
endwhile()
# check if this is a submodule
if(NOT IS_DIRECTORY ${GIT_DIR})
file(READ ${GIT_DIR} submodule)
string(REGEX REPLACE "gitdir: (.*)\n$" "\\1" GIT_DIR_RELATIVE ${submodule})
get_filename_component(SUBMODULE_DIR ${GIT_DIR} PATH)
get_filename_component(GIT_DIR ${SUBMODULE_DIR}/${GIT_DIR_RELATIVE} ABSOLUTE)
endif()
set(GIT_DATA "${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/git-data")
if(NOT EXISTS "${GIT_DATA}")
file(MAKE_DIRECTORY "${GIT_DATA}")
endif()
if(NOT EXISTS "${GIT_DIR}/HEAD")
return()
endif()
set(HEAD_FILE "${GIT_DATA}/HEAD")
configure_file("${GIT_DIR}/HEAD" "${HEAD_FILE}" COPYONLY)
configure_file(
"${_gitdescmoddir}/GetGitRevisionDescription.cmake.in" "${GIT_DATA}/grabRef.cmake" @ONLY
)
include("${GIT_DATA}/grabRef.cmake")
set(${_refspecvar}
"${HEAD_REF}"
PARENT_SCOPE
)
set(${_hashvar}
"${HEAD_HASH}"
PARENT_SCOPE
)
endfunction()
function(git_describe _var)
if(NOT GIT_FOUND)
find_package(Git QUIET)
endif()
get_git_head_revision(refspec hash)
if(NOT GIT_FOUND)
set(${_var}
"GIT-NOTFOUND"
PARENT_SCOPE
)
return()
endif()
if(NOT hash)
set(${_var}
"HEAD-HASH-NOTFOUND"
PARENT_SCOPE
)
return()
endif()
# TODO sanitize if((${ARGN}" MATCHES "&&") OR (ARGN MATCHES "||") OR (ARGN MATCHES "\\;"))
# message("Please report the following error to the project!") message(FATAL_ERROR "Looks like
# someone's doing something nefarious with git_describe! Passed arguments ${ARGN}") endif()
# message(STATUS "Arguments to execute_process: ${ARGN}")
execute_process(
COMMAND "${GIT_EXECUTABLE}" describe ${hash} ${ARGN}
WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}"
RESULT_VARIABLE res
OUTPUT_VARIABLE out
ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE
)
if(NOT res EQUAL 0)
set(out "${out}-${res}-NOTFOUND")
endif()
set(${_var}
"${out}"
PARENT_SCOPE
)
endfunction()
function(git_get_exact_tag _var)
git_describe(out --exact-match ${ARGN})
set(${_var}
"${out}"
PARENT_SCOPE
)
endfunction()
function(git_local_changes _var)
if(NOT GIT_FOUND)
find_package(Git QUIET)
endif()
get_git_head_revision(refspec hash)
if(NOT GIT_FOUND)
set(${_var}
"GIT-NOTFOUND"
PARENT_SCOPE
)
return()
endif()
if(NOT hash)
set(${_var}
"HEAD-HASH-NOTFOUND"
PARENT_SCOPE
)
return()
endif()
execute_process(
COMMAND "${GIT_EXECUTABLE}" diff-index --quiet HEAD --
WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}"
RESULT_VARIABLE res
OUTPUT_VARIABLE out
ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE
)
if(res EQUAL 0)
set(${_var}
"CLEAN"
PARENT_SCOPE
)
else()
set(${_var}
"DIRTY"
PARENT_SCOPE
)
endif()
endfunction()
function(git_count_parent_commits _var)
if(NOT GIT_FOUND)
find_package(Git QUIET)
endif()
get_git_head_revision(refspec hash)
if(NOT GIT_FOUND)
set(${_var}
"GIT-NOTFOUND"
PARENT_SCOPE
)
return()
endif()
if(NOT hash)
set(${_var}
"HEAD-HASH-NOTFOUND"
PARENT_SCOPE
)
return()
endif()
execute_process(
COMMAND "${GIT_EXECUTABLE}" rev-list --count HEAD
WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}"
RESULT_VARIABLE res
OUTPUT_VARIABLE out
ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE
)
if(res EQUAL 0)
set(${_var}
"${out}"
PARENT_SCOPE
)
else()
set(${_var}
"-1"
PARENT_SCOPE
)
endif()
endfunction()

View File

@ -0,0 +1,37 @@
#
# Internal file for GetGitRevisionDescription.cmake
#
# Requires CMake 2.6 or newer (uses the 'function' command)
#
# Original Author: 2009-2010 Ryan Pavlik <rpavlik@iastate.edu> <abiryan@ryand.net>
# http://academic.cleardefinition.com Iowa State University HCI Graduate Program/VRAC
#
# Copyright Iowa State University 2009-2010. Distributed under the Boost Software License, Version
# 1.0. (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
set(HEAD_HASH)
file(READ "@HEAD_FILE@" HEAD_CONTENTS LIMIT 1024)
string(STRIP "${HEAD_CONTENTS}" HEAD_CONTENTS)
if(HEAD_CONTENTS MATCHES "ref")
# named branch
string(REPLACE "ref: " "" HEAD_REF "${HEAD_CONTENTS}")
if(EXISTS "@GIT_DIR@/${HEAD_REF}")
configure_file("@GIT_DIR@/${HEAD_REF}" "@GIT_DATA@/head-ref" COPYONLY)
else()
configure_file("@GIT_DIR@/packed-refs" "@GIT_DATA@/packed-refs" COPYONLY)
file(READ "@GIT_DATA@/packed-refs" PACKED_REFS)
if(${PACKED_REFS} MATCHES "([0-9a-z]*) ${HEAD_REF}")
set(HEAD_HASH "${CMAKE_MATCH_1}")
endif()
endif()
else()
# detached HEAD
configure_file("@GIT_DIR@/HEAD" "@GIT_DATA@/head-ref" COPYONLY)
endif()
if(NOT HEAD_HASH)
file(READ "@GIT_DATA@/head-ref" HEAD_HASH LIMIT 1024)
string(STRIP "${HEAD_HASH}" HEAD_HASH)
endif()

View File

@ -0,0 +1,62 @@
#
# This file is responsible for setting the following variables:
#
# ~~~
# BUILD_NUMBER (1035)
# PROJECT_VERSION (4.0.3)
# PROJECT_VERSION_FULL (4.0.3-BETA+1035.PR111.B4)
# PROJECT_VERSION_SUFFIX (-BETA+1035.PR111.B4)
# PROJECT_VERSION_SUFFIX_SHORT (+1035)
#
# The `PROJECT_VERSION` variable is set as soon as the file is included.
# To set the rest, the function `resolve_version_variables` has to be called.
#
# ~~~
# PROJECT_VERSION
file(READ "${CMAKE_SOURCE_DIR}/version.txt" content)
string(REGEX MATCH "([0-9]+)\.([0-9]+)\.([0-9]+)" result "${content}")
if(NOT result)
message(FATAL_ERROR "Failed to read version info from ${version_file}")
endif()
set(PROJECT_VERSION ${CMAKE_MATCH_0})
function(resolve_version_variables)
# BUILD_NUMBER
if(NOT BUILD_NUMBER)
git_count_parent_commits(BUILD_NUMBER)
set(ERRORS "GIT-NOTFOUND" "HEAD-HASH-NOTFOUND")
if(BUILD_NUMBER IN_LIST ERRORS)
message(WARNING "Failed to resolve build number: ${BUILD_NUMBER}. Setting to zero.")
set(BUILD_NUMBER "0")
endif()
set(BUILD_NUMBER
${BUILD_NUMBER}
PARENT_SCOPE
)
endif()
# PROJECT_VERSION_SUFFIX
if(PROJECT_VERSION_SUFFIX STREQUAL "<auto>")
# TODO: set to +<sha>.dirty?.debug?
set(PROJECT_VERSION_SUFFIX "+${BUILD_NUMBER}.LOCAL")
set(PROJECT_VERSION_SUFFIX
"+${BUILD_NUMBER}.LOCAL"
PARENT_SCOPE
)
endif()
# PROJECT_VERSION_SUFFIX_SHORT
if(PROJECT_VERSION_SUFFIX_SHORT STREQUAL "<auto>")
set(PROJECT_VERSION_SUFFIX_SHORT
"+${BUILD_NUMBER}"
PARENT_SCOPE
)
endif()
# PROJECT_VERSION_FULL
set(PROJECT_VERSION_FULL
"${PROJECT_VERSION}${PROJECT_VERSION_SUFFIX}"
PARENT_SCOPE
)
endfunction()

122
cmake/Utilities.cmake Normal file
View File

@ -0,0 +1,122 @@
get_filename_component(PROJECT_CMAKE_DIR "${CMAKE_CURRENT_LIST_FILE}" DIRECTORY)
get_filename_component(PROJECT_ROOT_DIR "${PROJECT_CMAKE_DIR}" DIRECTORY)
find_package(Python3 COMPONENTS Interpreter)
if(NOT Python3_FOUND)
message(FATAL_ERROR "Python3 not found.")
endif()
function(get_recommended_gcc_version var)
execute_process(
COMMAND "${Python3_EXECUTABLE}" "${PROJECT_ROOT_DIR}/utils/bootstrap.py"
"--print-dependency-version" "gcc-arm-none-eabi"
OUTPUT_VARIABLE RECOMMENDED_VERSION
OUTPUT_STRIP_TRAILING_WHITESPACE
RESULT_VARIABLE RETVAL
)
if(NOT "${RETVAL}" STREQUAL "0")
message(FATAL_ERROR "Failed to obtain recommended gcc version from utils/bootstrap.py")
endif()
set(${var}
${RECOMMENDED_VERSION}
PARENT_SCOPE
)
endfunction()
function(get_dependency_directory dependency var)
execute_process(
COMMAND "${Python3_EXECUTABLE}" "${PROJECT_ROOT_DIR}/utils/bootstrap.py"
"--print-dependency-directory" "${dependency}"
OUTPUT_VARIABLE DEPENDENCY_DIRECTORY
OUTPUT_STRIP_TRAILING_WHITESPACE
RESULT_VARIABLE RETVAL
)
if(NOT "${RETVAL}" STREQUAL "0")
message(FATAL_ERROR "Failed to find directory with ${dependency}")
endif()
set(${var}
${DEPENDENCY_DIRECTORY}
PARENT_SCOPE
)
endfunction()
function(objcopy target format suffix)
add_custom_command(
TARGET ${target} POST_BUILD
COMMAND "${CMAKE_OBJCOPY}" -O ${format} -S "$<TARGET_FILE:${target}>"
"${CMAKE_CURRENT_BINARY_DIR}/${target}${suffix}"
COMMENT "Generating ${format} from ${target}..."
)
endfunction()
function(report_size target)
add_custom_command(
TARGET ${target} POST_BUILD
COMMAND echo "" # visually separate the output
COMMAND "${CMAKE_SIZE_UTIL}" -B "$<TARGET_FILE:${target}>"
USES_TERMINAL
)
endfunction()
function(pack_firmware target fw_version build_number printer_type signing_key)
set(bin_firmware_path "${CMAKE_CURRENT_BINARY_DIR}/${target}.bin")
if(SIGNING_KEY)
set(sign_opts "--key" "${signing_key}")
else()
set(sign_opts "--no-sign")
endif()
add_custom_command(
TARGET ${target} POST_BUILD
COMMAND "${CMAKE_OBJCOPY}" -O binary -S "$<TARGET_FILE:${target}>" "${bin_firmware_path}"
COMMAND echo "" # visually separate the output
COMMAND
"${Python3_EXECUTABLE}" "${CMAKE_SOURCE_DIR}/utils/pack_fw.py" --version="${fw_version}"
--printer-type "${printer_type}" --printer-version "1" ${sign_opts} "${bin_firmware_path}"
--build-number "${build_number}"
)
endfunction()
function(create_dfu)
set(options)
set(one_value_args OUTPUT TARGET)
set(multi_value_args INPUT)
cmake_parse_arguments(CREATE_DFU "${options}" "${one_value_args}" "${multi_value_args}" ${ARGN})
add_custom_command(
TARGET "${CREATE_DFU_TARGET}" POST_BUILD
COMMAND "${Python3_EXECUTABLE}" "${CMAKE_SOURCE_DIR}/utils/dfu.py" create ${CREATE_DFU_INPUT}
"${CREATE_DFU_OUTPUT}"
)
endfunction()
function(add_link_dependency target file_path)
get_target_property(link_deps ${target} LINK_DEPENDS)
if(link_deps STREQUAL "link_deps-NOTFOUND")
set(link_deps "")
endif()
list(APPEND link_deps "${file_path}")
set_target_properties(${target} PROPERTIES LINK_DEPENDS "${link_deps}")
endfunction()
function(rfc1123_datetime var)
set(cmd
"from email.utils import formatdate; print(formatdate(timeval=None, localtime=False, usegmt=True))"
)
execute_process(
COMMAND "${Python3_EXECUTABLE}" -c "${cmd}"
OUTPUT_VARIABLE RFC1123_DATETIME
OUTPUT_STRIP_TRAILING_WHITESPACE
RESULT_VARIABLE RETVAL
)
if(NOT "${RETVAL}" STREQUAL "0")
message(FATAL_ERROR "Failed to obtain rfc1123 date time from Python")
endif()
set(${var}
${RFC1123_DATETIME}
PARENT_SCOPE
)
endfunction()

3
src/main.cpp Normal file
View File

@ -0,0 +1,3 @@
int main() {
return 0;
}

21
src/version.c Normal file
View File

@ -0,0 +1,21 @@
#include "version.h"
#include "config.h"
#define _STR(x) #x
#define STR(x) _STR(x)
const char project_version[] = STR(FW_VERSION);
const char project_version_full[] = STR(FW_VERSION_FULL);
const char project_version_suffix[] = STR(FW_VERSION_SUFFIX);
const char project_version_suffix_short[] = STR(FW_VERSION_SUFFIX_SHORT);
const int project_build_number = FW_BUILD_NUMBER;
#if (PRINTER_TYPE == PRINTER_PRUSA_MINI)
const char project_firmware_name[] = "Buddy_MINI";
#else
#error "unknown printer type"
#endif

27
src/version.h Normal file
View File

@ -0,0 +1,27 @@
#pragma once
#ifdef __cplusplus
extern "C" {
#endif //__cplusplus
/// Project's version (4.0.2)
extern const char project_version[];
/// Full project's version (4.0.3-BETA+1035.PR111.B4)
extern const char project_version_full[];
/// Project's version suffix (-BETA+1035.PR111.B4)
extern const char project_version_suffix[];
/// Project's short version suffix (+1035)
extern const char project_version_suffix_short[];
/// Project's build number (number of commits in a branch)
extern const int project_build_number;
/// Firmware name
extern const char project_firmware_name[];
#ifdef __cplusplus
}
#endif //__cplusplus

1
tests/CMakeLists.txt Normal file
View File

@ -0,0 +1 @@
#

View File

@ -0,0 +1 @@
#

View File

@ -0,0 +1 @@
#

0
utils/CMakeLists.txt Normal file
View File

197
utils/bootstrap.py Executable file
View File

@ -0,0 +1,197 @@
#!/usr/bin/env python3
#
# Bootstrap Script
#
# This script
# 1) records the recommended versions of dependencies, and
# 2) when run, checks that all of them are present and downloads
# them if they are not.
#
# pylint: disable=line-too-long
import json
import os
import platform
import shutil
import stat
import subprocess
import sys
import tarfile
import zipfile
from argparse import ArgumentParser
from pathlib import Path
from urllib.request import urlretrieve
project_root_dir = Path(__file__).resolve().parent.parent
dependencies_dir = project_root_dir / '.dependencies'
# All dependencies of this project.
#
# yapf: disable
dependencies = {
'ninja': {
'version': '1.9.0',
'url': {
'Linux': 'https://github.com/ninja-build/ninja/releases/download/v1.9.0/ninja-linux.zip',
'Windows': 'https://github.com/ninja-build/ninja/releases/download/v1.9.0/ninja-win.zip',
'Darwin': 'https://github.com/ninja-build/ninja/releases/download/v1.9.0/ninja-mac.zip',
},
},
'cmake': {
'version': '3.15.5',
'url': {
'Linux': 'https://github.com/Kitware/CMake/releases/download/v3.15.5/cmake-3.15.5-Linux-x86_64.tar.gz',
'Windows': 'https://github.com/Kitware/CMake/releases/download/v3.15.5/cmake-3.15.5-win64-x64.zip',
'Darwin': 'https://github.com/Kitware/CMake/releases/download/v3.15.5/cmake-3.15.5-Darwin-x86_64.tar.gz',
},
},
'avr8-gnu-toolchain': {
'version': '5.4.0',
'url': {
'Linux': 'https://xxxarmkeil.blob.core.windows.net/developer/Files/downloads/gnu-rm/7-2018q2/gcc-arm-none-eabi-7-2018-q2-update-linux.tar.bz2',
'Windows': 'https://xxxarmkeil.blob.core.windows.net/developer/Files/downloads/gnu-rm/7-2018q2/gcc-arm-none-eabi-7-2018-q2-update-win32.zip',
'Darwin': 'https://xxxarmkeil.blob.core.windows.net/developer/Files/downloads/gnu-rm/7-2018q2/gcc-arm-none-eabi-7-2018-q2-update-mac.tar.bz2',
}
},
'clang-format': {
'version': '9.0.0-noext',
'url': {
'Linux': 'https://prusa-buddy-firmware-dependencies.s3.eu-central-1.amazonaws.com/clang-format-9.0.0-linux.zip',
'Windows': 'https://prusa-buddy-firmware-dependencies.s3.eu-central-1.amazonaws.com/clang-format-9.0.0-noext-win.zip',
'Darwin': 'https://prusa-buddy-firmware-dependencies.s3.eu-central-1.amazonaws.com/clang-format-9.0.0-darwin.zip',
}
},
}
pip_dependencies = ['ecdsa', 'polib']
# yapf: enable
def directory_for_dependency(dependency, version):
return dependencies_dir / (dependency + '-' + version)
def find_single_subdir(path: Path):
members = list(path.iterdir())
if path.is_dir() and len(members) > 1:
return path
elif path.is_dir() and len(members) == 1:
return find_single_subdir(members[0]) if members[0].is_dir() else path
else:
raise RuntimeError
def download_and_unzip(url: str, directory: Path):
"""Download a compressed file and extract it at `directory`."""
extract_dir = directory.with_suffix('.temp')
shutil.rmtree(directory, ignore_errors=True)
shutil.rmtree(extract_dir, ignore_errors=True)
print('Downloading ' + directory.name)
f, _ = urlretrieve(url, filename=None)
print('Extracting ' + directory.name)
if '.tar.bz2' in url or '.tar.gz' in url or '.tar.xz' in url:
obj = tarfile.open(f)
else:
obj = zipfile.ZipFile(f, 'r')
obj.extractall(path=str(extract_dir))
subdir = find_single_subdir(extract_dir)
shutil.move(str(subdir), str(directory))
shutil.rmtree(extract_dir, ignore_errors=True)
def run(*cmd):
process = subprocess.run([str(a) for a in cmd],
stdout=subprocess.PIPE,
check=True,
encoding='utf-8')
return process.stdout.strip()
def fix_executable_permissions(dependency, installation_directory):
to_fix = ('ninja', 'clang-format')
if dependency not in to_fix:
return
for fpath in installation_directory.iterdir():
if fpath.is_file and fpath.with_suffix('').name in to_fix:
st = os.stat(fpath)
os.chmod(fpath, st.st_mode | stat.S_IEXEC)
def recommended_version_is_available(dependency):
version = dependencies[dependency]['version']
directory = directory_for_dependency(dependency, version)
return directory.exists() and directory.is_dir()
def get_installed_pip_packages():
result = run(sys.executable, '-m', 'pip', 'list',
'--disable-pip-version-check', '--format', 'json')
data = json.loads(result)
return [(pkg['name'].lower(), pkg['version']) for pkg in data]
def install_dependency(dependency):
specs = dependencies[dependency]
installation_directory = directory_for_dependency(dependency,
specs['version'])
url = specs['url']
if isinstance(url, dict):
url = url[platform.system()]
download_and_unzip(url=url, directory=installation_directory)
fix_executable_permissions(dependency, installation_directory)
def main() -> int:
parser = ArgumentParser()
# yapf: disable
parser.add_argument(
'--print-dependency-version', type=str,
help='Prints recommended version of given dependency and exits.')
parser.add_argument(
'--print-dependency-directory', type=str,
help='Prints installation directory of given dependency and exits.')
args = parser.parse_args(sys.argv[1:])
# yapf: enable
if args.print_dependency_version:
try:
version = dependencies[args.print_dependency_version]['version']
print(version)
return 0
except KeyError:
print('Unknown dependency "%s"' % args.print_dependency_version)
return 1
if args.print_dependency_directory:
try:
dependency = args.print_dependency_directory
version = dependencies[dependency]['version']
install_dir = directory_for_dependency(dependency, version)
print(install_dir)
return 0
except KeyError:
print('Unknown dependency "%s"' % args.print_dependency_directory)
return 1
# if no argument present, check and install dependencies
for dependency in dependencies:
if recommended_version_is_available(dependency):
continue
install_dependency(dependency)
# also, install pip packages
installed_pip_packages = get_installed_pip_packages()
for package in pip_dependencies:
is_installed = any(installed[0] == package
for installed in installed_pip_packages)
if is_installed:
continue
print('Installing Python package %s' % package)
run(sys.executable, '-m', 'pip', 'install', package,
'--disable-pip-version-check')
return 0
if __name__ == "__main__":
sys.exit(main())

441
utils/build.py Executable file
View File

@ -0,0 +1,441 @@
#!/usr/bin/env python3
import argparse
import os
import platform
import random
import re
import shutil
import subprocess
import sys
import xml.etree.ElementTree as ET
from abc import ABC, abstractmethod, abstractproperty
from copy import deepcopy
from enum import Enum
from functools import lru_cache
from pathlib import Path
from typing import Dict, List, Optional
from uuid import uuid4
try:
from tqdm import tqdm
except ModuleNotFoundError:
def tqdm(iterable, *args, **kwargs):
return iterable
if os.isatty(sys.stdout.fileno()) and random.randint(0, 10) <= 1:
print('TIP: run `pip install -m tqdm` to get a nice progress bar')
project_root = Path(__file__).resolve().parent.parent
dependencies_dir = project_root / '.dependencies'
def bootstrap(*args, interactive=False, check=False):
"""Run the bootstrap script."""
bootstrap_py = project_root / 'utils' / 'bootstrap.py'
result = subprocess.run([sys.executable, str(bootstrap_py)] + list(args),
check=False,
encoding='utf-8',
stdout=None if interactive else subprocess.PIPE,
stderr=None if interactive else subprocess.PIPE)
return result
def project_version():
"""Return current project version (e. g. "4.0.3")"""
with open(project_root / 'version.txt', 'r') as f:
return f.read().strip()
@lru_cache()
def get_dependency(name):
install_dir = Path(
bootstrap('--print-dependency-directory', name,
check=True).stdout.strip())
suffix = '.exe' if platform.system() == 'Windows' else ''
if name == 'ninja':
return install_dir / ('ninja' + suffix)
elif name == 'cmake':
return install_dir / 'bin' / ('cmake' + suffix)
else:
return install_dir
class Printer(Enum):
"""Represents the -DPRINTER CMake option."""
MMU = 'MMU'
#class Bootloader(Enum):
# """Represents the -DBOOTLOADER CMake option."""
#
# NO = 'NO'
# EMPTY = 'EMPTY'
# YES = 'YES'
#
# @property
# def file_component(self):
# if self == Bootloader.NO:
# return 'NOBOOT'
# elif self == Bootloader.EMPTY:
# return 'EMPTYBOOT'
# elif self == Bootloader.YES:
# return 'BOOT'
# else:
# raise NotImplementedError
class BuildType(Enum):
"""Represents the -DCONFIG CMake option."""
DEBUG = 'DEBUG'
RELEASE = 'RELEASE'
#class HostTool(Enum):
# """Known host tools."""
#
# png2font = "png2font"
# bin2cc = "bin2cc"
# hex2dfu = "hex2dfu"
# makefsdata = "makefsdata"
class BuildConfiguration(ABC):
@abstractmethod
def get_cmake_cache_entries(self):
"""Convert the build configuration to CMake cache entries."""
@abstractmethod
def get_cmake_flags(self, build_dir: Path) -> List[str]:
"""Return all CMake command-line flags required to build this configuration."""
@abstractproperty
def name(self):
"""Name of the configuration."""
def __hash__(self):
return hash(self.name)
class FirmwareBuildConfiguration(BuildConfiguration):
def __init__(self,
printer: Printer,
build_type: BuildType,
toolchain: Path = None,
generator: str = None,
version_suffix: str = None,
version_suffix_short: str = None,
custom_entries: List[str] = None):
self.printer = printer
self.build_type = build_type
self.toolchain = toolchain or FirmwareBuildConfiguration.default_toolchain(
)
self.generator = generator
self.version_suffix = version_suffix
self.version_suffix_short = version_suffix_short
self.custom_entries = custom_entries or []
@staticmethod
def default_toolchain() -> Path:
return Path(
__file__).resolve().parent.parent / 'cmake/GccArmNoneEabi.cmake'
def get_cmake_cache_entries(self):
entries = []
if self.generator.lower() == 'ninja':
entries.append(('CMAKE_MAKE_PROGRAM', 'FILEPATH',
str(get_dependency('ninja'))))
entries.extend([
('CMAKE_MAKE_PROGRAM', 'FILEPATH', str(get_dependency('ninja'))),
('PRINTER', 'STRING', self.printer.value),
('CMAKE_TOOLCHAIN_FILE', 'FILEPATH', str(self.toolchain)),
('CMAKE_BUILD_TYPE', 'STRING', self.build_type.value.title()),
('PROJECT_VERSION_SUFFIX', 'STRING', self.version_suffix or ''),
('PROJECT_VERSION_SUFFIX_SHORT', 'STRING',
self.version_suffix_short or ''),
])
entries.extend(self.custom_entries)
return entries
def get_cmake_flags(self, build_dir: Path) -> List[str]:
cache_entries = self.get_cmake_cache_entries()
flags = ['-D{}:{}={}'.format(*entry) for entry in cache_entries]
flags += ['-G', self.generator or 'Ninja']
flags += ['-S', str(Path(__file__).resolve().parent.parent)]
flags += ['-B', str(build_dir)]
return flags
@property
def name(self):
components = [
self.printer.name,
self.build_type.value,
]
return '_'.join(components)
class BuildResult:
"""Represents a result of an attempt to build the project."""
def __init__(self, config_returncode: int, build_returncode: Optional[int],
stdout: Path, stderr: Path, products: List[Path]):
self.config_returncode = config_returncode
self.build_returncode = build_returncode
self.stdout = stdout
self.stderr = stderr
self.products = products
@property
def configuration_failed(self):
return self.config_returncode != 0
@property
def build_failed(self):
return self.build_returncode != 0 and self.build_returncode is not None
@property
def is_failure(self):
return self.configuration_failed or self.build_failed
def __str__(self):
return '<BuildResult config={self.config_returncode} build={self.build_returncode}>'.format(
self=self)
def build(configuration: BuildConfiguration,
build_dir: Path,
configure_only=False,
output_to_file=True) -> BuildResult:
"""Build a project with a single configuration."""
flags = configuration.get_cmake_flags(build_dir=build_dir)
# create the build directory
build_dir.mkdir(parents=True, exist_ok=True)
products = []
if output_to_file:
# stdout and stderr are saved to a file in the build directory
stdout_path = build_dir / 'stdout.txt'
stderr_path = build_dir / 'stderr.txt'
stdout = open(stdout_path, 'w')
stderr = open(stderr_path, 'w')
else:
stdout_path, stderr_path = None, None
stdout, stderr = None, None
# prepare the build
config_process = subprocess.run([str(get_dependency('cmake'))] + flags,
stdout=stdout,
stderr=stderr,
check=False)
if not configure_only and config_process.returncode == 0:
cmd = [
str(get_dependency('cmake')), '--build',
str(build_dir), '--config',
configuration.build_type.value.lower()
]
build_process = subprocess.run(cmd,
stdout=stdout,
stderr=stderr,
check=False)
build_returncode = build_process.returncode
products.extend(build_dir / fname for fname in [
'firmware', 'firmware.bin', 'firmware.bbf', 'firmware.dfu',
'firmware.map'
] if (build_dir / fname).exists())
else:
build_returncode = None
if stdout:
stdout.close()
if stderr:
stderr.close()
# collect the result and return
return BuildResult(config_returncode=config_process.returncode,
build_returncode=build_returncode,
stdout=stdout_path,
stderr=stderr_path,
products=products)
def store_products(products: List[Path], build_config: BuildConfiguration,
products_dir: Path):
"""Copy build products to a shared products directory."""
products_dir.mkdir(parents=True, exist_ok=True)
for product in products:
is_firmware = isinstance(build_config, FirmwareBuildConfiguration)
has_custom_suffix = is_firmware and (build_config.version_suffix !=
'<auto>')
if has_custom_suffix:
version = project_version()
name = build_config.name.lower(
) + '_' + version + build_config.version_suffix
else:
name = build_config.name.lower()
destination = products_dir / (name + product.suffix)
shutil.copy(product, destination)
def list_of(EnumType):
"""Create an argument-parser for comma-separated list of values of some Enum subclass."""
def convert(val):
if val == '':
return []
values = [p.lower() for p in val.split(',')]
if 'all' in values:
return list(EnumType)
else:
return [EnumType(v.upper()) for v in values]
convert.__name__ = EnumType.__name__
return convert
def cmake_cache_entry(arg):
match = re.fullmatch(r'(.*):(.*)=(.*)', arg)
if not match:
raise ValueError('invalid cmake entry; must be <NAME>:<TYPE>=<VALUE>')
return (match.group(1), match.group(2), match.group(3))
def main():
parser = argparse.ArgumentParser()
# yapf: disable
parser.add_argument(
'--printer',
type=list_of(Printer),
default=list(Printer),
help='Printer type (default: {default}).'.format(
default=','.join(str(p.value.lower()) for p in Printer)))
parser.add_argument(
'--build-type',
type=list_of(BuildType),
default='release',
help=('Build type (debug or release; default: release; '
'default for --generate-cproject: debug,release).'))
parser.add_argument(
'--version-suffix',
type=str,
default='<auto>',
help='Version suffix (e.g. -BETA+1035.PR111.B4)')
parser.add_argument(
'--version-suffix-short',
type=str,
default='<auto>',
help='Version suffix (e.g. +1035)')
parser.add_argument(
'--final',
action='store_true',
help='Set\'s --version-suffix and --version-suffix-short to empty string.')
parser.add_argument(
'--build-dir',
type=Path,
help='Specify a custom build directory to be used.')
parser.add_argument(
'--products-dir',
type=Path,
help='Directory to store built firmware (default: <build-dir>/products).')
parser.add_argument(
'-G', '--generator',
type=str,
default='Ninja',
help='Generator to be used by CMake (default=Ninja).')
parser.add_argument(
'--toolchain',
type=Path,
help='Path to a CMake toolchain file to be used.')
parser.add_argument(
'--host-tools',
action='store_true',
help=('Build host tools (png2font and others). '
'Turned on by default with --generate-cproject only.')
)
parser.add_argument(
'--no-build',
action='store_true',
help='Do not build, configure the build only.'
)
parser.add_argument(
'--no-store-output',
action='store_false',
help='Do not write build output to files - print it to console instead.'
)
parser.add_argument(
'-D', '--cmake-def',
action='append', type=cmake_cache_entry,
help='Custom CMake cache entries (e.g. -DCUSTOM_COMPILE_OPTIONS:STRING=-Werror)'
)
args = parser.parse_args(sys.argv[1:])
# yapf: enable
build_dir_root = args.build_dir or Path(
__file__).resolve().parent.parent / 'build'
products_dir_root = args.products_dir or (build_dir_root / 'products')
if args.final:
args.version_suffix = ''
args.version_suffix_short = ''
# Check all dependencis are installed
if bootstrap(interactive=True).returncode != 0:
print('bootstrap.py failed.')
sys.exit(1)
# prepare configurations
configurations = [
FirmwareBuildConfiguration(
printer=printer,
build_type=build_type,
version_suffix=args.version_suffix,
version_suffix_short=args.version_suffix_short,
generator=args.generator,
custom_entries=args.cmake_def) for printer in args.printer
for build_type in args.build_type
]
# build everything
configurations_iter = tqdm(configurations)
results: Dict[BuildConfiguration, BuildResult] = dict()
for configuration in configurations_iter:
build_dir = build_dir_root / configuration.name.lower()
description = 'Building ' + configuration.name.lower()
if hasattr(configurations_iter, 'set_description'):
configurations_iter.set_description(description)
else:
print(description)
result = build(configuration,
build_dir=build_dir,
configure_only=args.no_build,
output_to_file=args.no_store_output is not False)
store_products(result.products, configuration, products_dir_root)
results[configuration] = result
# print results
print()
print('Building finished: {} success, {} failure(s).'.format(
sum(1 for result in results.values() if not result.is_failure),
sum(1 for result in results.values() if result.is_failure)))
failure = False
max_configname_len = max(len(config.name) for config in results)
for config, result in results.items():
if result.configuration_failed:
status = 'project configuration FAILED'
failure = True
elif result.build_failed:
status = 'build FAILED'
failure = True
else:
status = 'SUCCESS'
print(' {} {}'.format(
config.name.lower().ljust(max_configname_len, ' '), status))
if failure:
sys.exit(1)
if __name__ == "__main__":
main()

1
version.txt Normal file
View File

@ -0,0 +1 @@
2.0.0