VxSDK - 0.12.0-27 : fix configuration file + preparation for CMake build-system
@update > [common] ¦ rename all log.warning() in log.warn() > [core/build] ¦ [compile] support explicit extra environment variables setup ¦ [rules] support explicit extra environment variables setup @fix > [__main__] ¦ fix error log print > [core/config] ¦ fix exception raising if the configuration file does not exists
This commit is contained in:
commit
b708cf6cc1
|
@ -0,0 +1,71 @@
|
|||
# vxSDK
|
||||
|
||||
## Description
|
||||
|
||||
vxSDK is a tool which aims to help the user to work well by using multiple commands.
|
||||
|
||||
### Usage
|
||||
|
||||
vxsdk [+toolchain] [OPTIONS] [SUBCOMMAND]
|
||||
|
||||
### Options
|
||||
-v, --version Print version info and exit
|
||||
--list List installed command
|
||||
-h, --help Print helps information
|
||||
|
||||
Default sub-commands used:
|
||||
p, project project abstraction
|
||||
g, gitea package manager for Vhex's forge Gitea
|
||||
l, link USB abstraction
|
||||
d, doctor vxSDK inspector
|
||||
|
||||
# Commands
|
||||
|
||||
## vxsdk project
|
||||
|
||||
### Description
|
||||
|
||||
Abstract project manipulation
|
||||
|
||||
### Usage
|
||||
|
||||
vxsdk project <COMMAND> [OPTIONS]
|
||||
|
||||
### Options
|
||||
|
||||
--list List installed command
|
||||
-h, --help Print helps information
|
||||
|
||||
### Common used commands:
|
||||
|
||||
n, new Create a new project
|
||||
b, build Build a project
|
||||
d, doctor Display all project information
|
||||
a, add Add project dependency
|
||||
|
||||
## vxsdk gitea
|
||||
|
||||
### Description
|
||||
|
||||
Package manager for the Vhex's forge Gitea
|
||||
|
||||
### Usage
|
||||
|
||||
vxsdk gitea [OPTIONS]
|
||||
|
||||
### Common used commands:
|
||||
|
||||
s, search [NAME] Search project
|
||||
i, install [NAME] Try to install a project
|
||||
r, remove <NAME> Remove a project
|
||||
u, update [NAME] Try to update a project
|
||||
|
||||
## vxsdk link
|
||||
|
||||
### Description
|
||||
|
||||
### Usage
|
||||
|
||||
### Options
|
||||
|
||||
### Common used commands:
|
|
@ -0,0 +1,13 @@
|
|||
# Build files
|
||||
/build-fx
|
||||
/build-cg
|
||||
/*.g1a
|
||||
/*.g3a
|
||||
|
||||
# Python bytecode
|
||||
__pycache__/
|
||||
|
||||
# Common IDE files
|
||||
*.sublime-project
|
||||
*.sublime-workspace
|
||||
.vscode
|
|
@ -0,0 +1,92 @@
|
|||
#! /usr/bin/make -f
|
||||
|
||||
|
||||
#
|
||||
# variable definition
|
||||
#
|
||||
|
||||
# color definition, for swagg :D
|
||||
red := \033[1;31m
|
||||
green := \033[1;32m
|
||||
blue := \033[1;34m
|
||||
white := \033[1;37m
|
||||
nocolor := \033[1;0m
|
||||
|
||||
src := $(foreach path,\
|
||||
$(shell find src -not -path "*/\.*" -type d), \
|
||||
$(wildcard $(path)/*.c) \
|
||||
$(wildcard $(path)/*.S) \
|
||||
$(wildcard $(path)/*.s))
|
||||
|
||||
obj := $(patsubst src_%,$(VXSDK_PREFIX_BUILD)/%.o,$(subst /,_,$(src)))
|
||||
obj += $(patsubst \
|
||||
$(VXSDK_ASSETS_SRC)/%,\
|
||||
$(VXSDK_ASSETS_BUILD)/%.o,\
|
||||
$(wildcard $(VXSDK_ASSETS_SRC)/*.c) \
|
||||
)
|
||||
|
||||
cflags := -ffreestanding -nostdlib -m4-nofpu -fPIE -O1
|
||||
cflags += -mb -fstrict-volatile-bitfields
|
||||
cflags += $(VXSDK_CFLAGS_INCLUDE) -I.. -Iinclude
|
||||
|
||||
# debug vars
|
||||
VERBOSE ?= false
|
||||
|
||||
#
|
||||
# build rules
|
||||
#
|
||||
|
||||
vxaddin: $(obj)
|
||||
@ printf "$(blue)Create $(red)$@$(nocolor)\n"
|
||||
sh-elf-vhex-gcc \
|
||||
-T $(VXSDK_PREFIX_LIB)/fxcg50-dynamic.ld -Wl,-q -Wl,-M \
|
||||
$(VXSDK_CFLAGS_LINK) \
|
||||
-o $@ $^ \
|
||||
-lvhex-fxcg50 -lc -lgcc \
|
||||
> $(VXSDK_PREFIX_BUILD)/map.txt
|
||||
vxsdk conv addin -b $@ -n vxaddin -o /tmp/vxaddin
|
||||
|
||||
version:
|
||||
@echo "$(VXSDK_PKG_VERSION)"
|
||||
|
||||
help:
|
||||
@ echo 'Rules listing:'
|
||||
@ echo '... all the default, if no target is provided'
|
||||
@ echo '... clean remove build object'
|
||||
@ echo '... fclean remove all generated object'
|
||||
@ echo '... re same as `make fclean all`'
|
||||
@ echo '... version display version'
|
||||
@ echo '... install install the library'
|
||||
@ echo '... uninstall uninstall the library'
|
||||
|
||||
.PHONY: help version
|
||||
|
||||
#
|
||||
# Object rules
|
||||
#
|
||||
|
||||
$(VXSDK_PREFIX_BUILD)%.o:
|
||||
ifeq ($(VERBOSE),true)
|
||||
@ mkdir -p $(dir $@)
|
||||
sh-elf-vhex-gcc \
|
||||
$(cflags) -D FXCG50 \
|
||||
-o $@ \
|
||||
-c $(addprefix src/,$(subst _,/,$(notdir $(basename $@))))
|
||||
else
|
||||
@ mkdir -p $(dir $@)
|
||||
@ printf "$(green)>$(nocolor) $(white)$@$(nocolor)\n"
|
||||
@ sh-elf-vhex-gcc \
|
||||
$(cflags) -D FXCG50 \
|
||||
-o $@ \
|
||||
-c $(addprefix src/,$(subst _,/,$(notdir $(basename $@))))
|
||||
endif
|
||||
|
||||
$(VXSDK_ASSETS_BUILD)%.o: $(VXSDK_ASSETS_SRC)/%
|
||||
ifeq ($(VERBOSE),true)
|
||||
@ mkdir -p $(dir $@)
|
||||
sh-elf-vhex-gcc $(cflags) -D FXCG50 -o $@ -c $<
|
||||
else
|
||||
@ mkdir -p $(dir $@)
|
||||
@ printf "$(green)>$(nocolor) $(white)$@$(nocolor)\n"
|
||||
@ sh-elf-vhex-gcc $(cflags) -D FXCG50 -o $@ -c $<
|
||||
endif
|
|
@ -0,0 +1,14 @@
|
|||
#include <vhex/display.h>
|
||||
#include <vhex/keyboard.h>
|
||||
|
||||
int main(void)
|
||||
{
|
||||
dclear(C_WHITE);
|
||||
dtext(1, 1, C_BLACK, "Sample fxSDK add-in.");
|
||||
dupdate();
|
||||
|
||||
while (1) { __asm__("sleep"); }
|
||||
|
||||
//getkey();
|
||||
return 1;
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
[project]
|
||||
name = 'vxaddin'
|
||||
|
||||
[dependencies]
|
||||
vxKernel = 'dev'
|
||||
|
||||
[build]
|
||||
build = 'make'
|
|
@ -0,0 +1,140 @@
|
|||
#! /usr/bin/env bash
|
||||
|
||||
prefix="$HOME/.local"
|
||||
VERSION='0.12.0'
|
||||
|
||||
#
|
||||
# Help screen
|
||||
#
|
||||
help() {
|
||||
cat << OEF
|
||||
Script used to manipulate the VxSDK, a set of tools for the Vhex Operating
|
||||
System project.
|
||||
|
||||
Usage $0 [ACTION]
|
||||
|
||||
Actions:
|
||||
bootstrap Try to bootstrap the vxSDK itself [default]
|
||||
install Try to install the VsSDK
|
||||
uninstall Try to uninstall the VsSDK
|
||||
|
||||
Options:
|
||||
--prefix=<PREFIX> Installation prefix (default is ~/.local)
|
||||
-h, --help Display this help
|
||||
OEF
|
||||
exit 0
|
||||
}
|
||||
|
||||
|
||||
|
||||
#
|
||||
# Parse arguments
|
||||
#
|
||||
|
||||
target='install'
|
||||
|
||||
for arg; do case "$arg" in
|
||||
--help | -h)
|
||||
help;;
|
||||
-v | --version)
|
||||
echo "$VERSION"
|
||||
exit 0;;
|
||||
|
||||
install)
|
||||
target='install';;
|
||||
update)
|
||||
target='update';;
|
||||
uninstall)
|
||||
target='uninstall';;
|
||||
*)
|
||||
echo "error: unreconized argument '$arg', giving up." >&2
|
||||
exit 1
|
||||
esac; done
|
||||
|
||||
|
||||
|
||||
#
|
||||
# Process
|
||||
#
|
||||
|
||||
if [[ "$target" = "install" ]]
|
||||
then
|
||||
if [[ -d "$prefix/lib/vxsdk/vxsdk" ]]; then
|
||||
echo 'warning : vxsdk is already installed !' >&2
|
||||
read -n 1 -p 'Do you whant to re-install the vxSDK (package will not be removed) [y/N] ? ' reinstall
|
||||
[[ "$reinstall" != 'y' ]] && exit 1
|
||||
echo ''
|
||||
./install.sh uninstall
|
||||
fi
|
||||
|
||||
install -d $prefix/lib/vxsdk/vxsdk
|
||||
cp -r requirements.txt assets vxsdk $prefix/lib/vxsdk/vxsdk
|
||||
|
||||
install -d $prefix/bin
|
||||
echo '#! /usr/bin/env bash' > $prefix/bin/vxsdk
|
||||
echo '' >> $prefix/bin/vxsdk
|
||||
echo "source $prefix/lib/vxsdk/vxsdk/venv/bin/activate" >> $prefix/bin/vxsdk
|
||||
echo "python3 $prefix/lib/vxsdk/vxsdk/vxsdk \$@" >> $prefix/bin/vxsdk
|
||||
echo 'deactivate' >> $prefix/bin/vxsdk
|
||||
chmod +x $prefix/bin/vxsdk
|
||||
|
||||
build_date=$(date '+%Y-%m-%d')
|
||||
build_hash=$(git rev-parse --short HEAD)
|
||||
f="$prefix/lib/vxsdk/vxsdk/vxsdk/__main__.py"
|
||||
sed -e "s*%VERSION%*$VERSION*; s*%BUILD_HASH%*$build_hash*; s*%BUILD_DATE%*$build_date*" vxsdk/__main__.py > $f
|
||||
|
||||
mkdir -p $prefix/share/vxsdk
|
||||
|
||||
cd $prefix/lib/vxsdk/vxsdk
|
||||
python3 -m venv venv
|
||||
source venv/bin/activate
|
||||
pip install --upgrade pip 2>&1 > /dev/null
|
||||
pip install -r requirements.txt
|
||||
deactivate
|
||||
exit 0
|
||||
fi
|
||||
|
||||
|
||||
|
||||
if [[ "$target" = "update" ]]
|
||||
then
|
||||
|
||||
git clone git@github.com:Vhex-org/vxSDK.git --depth=1 /tmp/vxSDK > /dev/null 2>&1 || exit 84
|
||||
cd /tmp/vxSDK
|
||||
|
||||
if [[ "$(./install.sh --version)" == "$VERSION" ]]
|
||||
then
|
||||
rm -rf /tmp/vxSDK
|
||||
echo 'already up to date !'
|
||||
exit 0
|
||||
fi
|
||||
|
||||
_check=$(echo -e "$(./install.sh --version)\n$VERSION" | sort -V | head -n1)
|
||||
|
||||
if [[ "$_check" != "$VERSION" ]]; then
|
||||
rm -rf /tmp/vxSDK
|
||||
echo 'already up to date !'
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "update $VERSION -> $(./install.sh --version)"
|
||||
|
||||
./install.sh uninstall
|
||||
./install.sh install
|
||||
|
||||
rm -rf /tmp/vxSDK
|
||||
|
||||
fi
|
||||
|
||||
|
||||
|
||||
if [[ "$target" = "uninstall" ]]
|
||||
then
|
||||
|
||||
rm $prefix/bin/vxsdk
|
||||
rm -rf $prefix/lib/vxsdk
|
||||
rmdir $prefix/share/vxsdk 2>/dev/null || exit 0
|
||||
echo 'note: repositories cloned by vxSDK have not been removed'
|
||||
exit 0
|
||||
|
||||
fi
|
|
@ -0,0 +1,3 @@
|
|||
requests
|
||||
toml
|
||||
pillow
|
|
@ -0,0 +1,102 @@
|
|||
"""
|
||||
vxSDK is a suitable of tools used in conjunction with the Vhex Operating System
|
||||
to develop game, add-in, abstract build step with dependencies resolver and
|
||||
more.
|
||||
"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
|
||||
from core.logger import log
|
||||
|
||||
|
||||
# Program version (inserted at compile-time)
|
||||
__VXSDK_VERSION__ = "%VERSION%"
|
||||
__VXSDK_BUILD_HASH__ = "%BUILD_HASH%"
|
||||
__VXSDK_BUILD_DATE__ = "%BUILD_DATE%"
|
||||
|
||||
|
||||
__VXSDK_HELP__ = r"""
|
||||
Vhex' Software Developement Kit
|
||||
|
||||
USAGE:
|
||||
vxsdk [+toolchain] [OPTIONS] [SUBCOMMAND]
|
||||
|
||||
OPTIONS:
|
||||
-v, --version Print version info and exit
|
||||
--list List installed command
|
||||
--update Try to update the ymtools
|
||||
-h, --help Print helps information
|
||||
|
||||
Default sub-commands used:
|
||||
project Project abstraction
|
||||
pkg Package manager
|
||||
build Build abstraction
|
||||
|
||||
See `vxsdk <sub-command> --help` for more information on a specific command
|
||||
""".strip()
|
||||
|
||||
def _list_modules():
|
||||
for name in os.listdir(f"{os.path.dirname(__file__)}/cli"):
|
||||
try:
|
||||
mod = __import__(f"cli.{name.split('.')[0]}", fromlist=[
|
||||
'__VXSDK_MODULE_META__',
|
||||
'cli_parse'
|
||||
])
|
||||
if not hasattr(mod, 'cli_validate'):
|
||||
continue
|
||||
if not hasattr(mod, 'cli_parse'):
|
||||
continue
|
||||
if not hasattr(mod, '__VXSDK_MODULE_META__'):
|
||||
continue
|
||||
yield mod
|
||||
except ImportError as err:
|
||||
log.warn(f"[vxsdk] module '{name}' cannot be imported")
|
||||
log.warn(f"{err}")
|
||||
|
||||
def _subcommand_parse(argv):
|
||||
for mod in _list_modules():
|
||||
if mod.cli_validate(argv[0]):
|
||||
return mod.cli_parse(argv)
|
||||
log.emergency(f"vxsdk: '{argv[0]}' command not found :(")
|
||||
return 84
|
||||
|
||||
def _subcommand_list():
|
||||
for mod in _list_modules():
|
||||
mod_info = mod.__VXSDK_MODULE_META__[1]
|
||||
args = str(mod.__VXSDK_MODULE_META__[0]).strip('[]')
|
||||
log.user(f" {args}".ljust(32) + f"{mod_info}")
|
||||
|
||||
def _main(argv):
|
||||
if not argv:
|
||||
log.error(__VXSDK_HELP__)
|
||||
sys.exit(84)
|
||||
|
||||
if argv[0] in ['-h', '--help']:
|
||||
log.user(__VXSDK_HELP__)
|
||||
sys.exit(0)
|
||||
|
||||
if argv[0] == '--version':
|
||||
_ver = __VXSDK_VERSION__
|
||||
_hash = __VXSDK_BUILD_HASH__
|
||||
_date = __VXSDK_BUILD_DATE__
|
||||
log.user(f"vxSDK {_ver} ({_hash} {_date})")
|
||||
sys.exit(0)
|
||||
|
||||
if argv[0] == '--update':
|
||||
return os.system(
|
||||
os.path.dirname(__file__) + '/../install.sh update'
|
||||
)
|
||||
|
||||
if argv[0] in ['-v', '-vv', '-vvv']:
|
||||
log.level += len(argv[0]) - 1
|
||||
argv = argv[1:]
|
||||
if not argv:
|
||||
log.error(__VXSDK_HELP__)
|
||||
sys.exit(84)
|
||||
|
||||
if len(argv) == 1 and argv[0] == '--list':
|
||||
return _subcommand_list()
|
||||
return _subcommand_parse(argv)
|
||||
|
||||
_main(sys.argv[1:])
|
|
@ -0,0 +1,156 @@
|
|||
"""vxsdk-build modules
|
||||
|
||||
This module provides package abstraction and build core function for the Vhex
|
||||
Operating system project.
|
||||
"""
|
||||
from core.logger import log
|
||||
|
||||
from cli.build.default import build_default_cli
|
||||
from cli.build.doctor import build_doctor_cli
|
||||
|
||||
|
||||
__all__ = [
|
||||
'__VXSDK_MODULE_META__',
|
||||
'cli_validate',
|
||||
'cli_parse',
|
||||
]
|
||||
|
||||
|
||||
__VXSDK_MODULE_META__ = (
|
||||
['build'],
|
||||
"Build a project",
|
||||
r"""vxsdk-build
|
||||
Build System Abstraction
|
||||
|
||||
USAGE:
|
||||
vxsdk build(-<platform>) [OPTIONS]
|
||||
|
||||
DESCRIPTION:
|
||||
Compile Vhex project.
|
||||
|
||||
NOTES:
|
||||
The Vex build system is extremely powerful and polyvalent. It allows the
|
||||
user to totally ignore the build part and the dependencies management.
|
||||
|
||||
All Vhex projects use a <.vxsdk.toml> file wich should be stored at the root
|
||||
of the project directory (you cam generate a project template using the
|
||||
`vxsdk project new <project>`). This file uses the TOML language to control
|
||||
the build step of a project.
|
||||
|
||||
```
|
||||
# Default meta-data information for the pacakge manager.
|
||||
[project]
|
||||
name = 'myaddin' # required
|
||||
version = '1.0.0' # required
|
||||
type = 'addin' # optional ('addin' or 'app')
|
||||
description = ''' # optional
|
||||
An old-school demo-scene !
|
||||
Written by Yatis :)
|
||||
'''
|
||||
|
||||
# Dependencies information for the build manager. (optional)
|
||||
#
|
||||
# The version can target <tag> or branch name.
|
||||
#
|
||||
# Note that you can use a powerfull operations for managing version of
|
||||
# dependencies if the 'version' is detected to respect the correct
|
||||
# semantic versioning like caret (^), tilde (~) and wildcard (*):
|
||||
#
|
||||
# Caret requirements (^)
|
||||
# ^1.2.3 := >=1.2.3, <2.0.0
|
||||
# ^1.2 := >=1.2.0, <2.0.0
|
||||
# ^1 := >=1.0.0, <2.0.0
|
||||
# ^0.2.3 := >=0.2.3, <0.3.0
|
||||
# ^0.2 := >=0.2.0, <0.3.0
|
||||
# ^0.0.3 := >=0.0.3, <0.0.4
|
||||
# ^0.0 := >=0.0.0, <0.1.0
|
||||
# ^0 := >=0.0.0, <1.0.0
|
||||
#
|
||||
# Tilde requirements (~)
|
||||
# ~1.2.3 := >=1.2.3, <1.3.0
|
||||
# ~1.2 := >=1.2.0, <1.3.0
|
||||
# ~1 := >=1.0.0, <2.0.0
|
||||
#
|
||||
# Wildcard requirements (*)
|
||||
# * := >=0.0.0
|
||||
# 1.* := >=1.0.0, <2.0.0
|
||||
# 1.2.* := >=1.2.0, <1.3.0
|
||||
#
|
||||
# Note that it's possible that a package can have two same versions (one
|
||||
# for branch name and another for a tag), by default, the tag is always
|
||||
# selected, but here the display information will explicitly describe if
|
||||
# the version is a tag and / or a branch.
|
||||
[dependencies]
|
||||
vxkernel = 'master' # recommanded
|
||||
sh-elf-vhex = 'master' # recommanded
|
||||
custom-lib = '^1.5' # exemple of carret (^) operation
|
||||
|
||||
# Manual build indication (option)
|
||||
#
|
||||
# Note that if this section is specified then only this build indication
|
||||
# will be executed, no default step will be used
|
||||
#
|
||||
# All building steop are, in order:
|
||||
# > configure
|
||||
# > build
|
||||
# > install
|
||||
# And during all of theses building step, the vxSDk setup some
|
||||
# environment variable:
|
||||
# > VXSDK_PKG_NAME - project name
|
||||
# > VXSDK_PKG_VERSION - project version
|
||||
# > VXSDK_PREFIX_BUILD - project build prefix (for object files)
|
||||
# > VXSDK_PREFIX_INSTALL - project installation prefix
|
||||
# > VXSDK_PREFIX_LIB - prefix for all stored librairy
|
||||
# > VXSDK_CFLAGS_INCLUDE - Include flags for GCC
|
||||
# > VXSDK_CFLAGS_LINK - Linker flags for GCC
|
||||
# > VXSDK_ASSETS_SRC - Assets sources file directory
|
||||
# > VXSDK_ASSETS_BUILD - Assets build directory (for object)
|
||||
[build]
|
||||
configure = 'mkdir -p build && cd build && ../configure --verbose'
|
||||
build = 'cd build && make'
|
||||
|
||||
# Dependencies Hook (optional)
|
||||
#
|
||||
# You need to create a section wich is named like [extra.<package_name>]
|
||||
# and you can "hook" some step of particular dependencies (here
|
||||
# vxkernel for exemple).
|
||||
#
|
||||
# A hook is simply additional string that will be send to the
|
||||
# appropriate step of the build.
|
||||
[extra.vxkernel]
|
||||
configure = '--static --verbose'
|
||||
```
|
||||
|
||||
Above is a exemple of a defaut project configuration file that can be used
|
||||
for a new project.
|
||||
|
||||
|
||||
OPTIONS:
|
||||
-v, --verbose Disable threading and display log information
|
||||
-r, --rebuild Force rebuild the project
|
||||
-u, --update Update dependencies
|
||||
--extra-conf [ARG]... Add extra configuration flags
|
||||
--extra-build [ARG]... Add extra build flags
|
||||
|
||||
ACTIONS:
|
||||
doctor Display all information about one package
|
||||
"""
|
||||
)
|
||||
|
||||
#---
|
||||
# Public
|
||||
#---
|
||||
|
||||
def cli_validate(name):
|
||||
""" validate the module name """
|
||||
return name.find('build') == 0
|
||||
|
||||
def cli_parse(argv):
|
||||
""" Build subcommand entry """
|
||||
if len(argv) > 2:
|
||||
if '--help' in argv or '-h' in argv:
|
||||
log.user(__VXSDK_MODULE_META__[2])
|
||||
return 0
|
||||
if argv[1] == 'doctor':
|
||||
return build_doctor_cli(argv[2:])
|
||||
return build_default_cli(argv)
|
|
@ -0,0 +1,49 @@
|
|||
"""
|
||||
Vhex default build management
|
||||
"""
|
||||
import sys
|
||||
|
||||
from core.build.project import VxProject
|
||||
from core.logger import log
|
||||
|
||||
__all__ = [
|
||||
'build_default_cli'
|
||||
]
|
||||
|
||||
def build_default_cli(argv):
|
||||
"""Parse CLI arguments"""
|
||||
board_target = None
|
||||
if argv[0].find('build-') == 0:
|
||||
board_target = argv[0][6:]
|
||||
|
||||
path = None
|
||||
verbose = False
|
||||
extra_conf = {
|
||||
'configure' : '',
|
||||
'build' : '',
|
||||
}
|
||||
for arg in argv[1:]:
|
||||
# if arg in ['-r', '--rebuild']:
|
||||
# force_rebuild = True
|
||||
# continue
|
||||
# if arg in ['-u', '--update']:
|
||||
# update_dependencies = True
|
||||
# continue
|
||||
if arg in ['-v', '--verbose']:
|
||||
verbose = True
|
||||
continue
|
||||
if arg.find('--extra-conf=') == 0:
|
||||
extra_conf['configure'] += ' ' + arg[13:]
|
||||
continue
|
||||
if arg.find('--extra-build=') == 0:
|
||||
extra_conf['build'] += ' ' + arg[14:]
|
||||
continue
|
||||
if path:
|
||||
log.error(f"argument '{arg}' not recognized")
|
||||
sys.exit(85)
|
||||
path = arg
|
||||
|
||||
return VxProject(path, extra_conf=extra_conf).build(
|
||||
board_target,
|
||||
verbose
|
||||
)
|
|
@ -0,0 +1,20 @@
|
|||
"""
|
||||
Display pacakge build information
|
||||
"""
|
||||
|
||||
from core.logger import log
|
||||
from core.build.project import VxProject
|
||||
|
||||
__all__ = [
|
||||
'build_doctor_cli'
|
||||
]
|
||||
|
||||
def build_doctor_cli(argv):
|
||||
r""" Package doctor
|
||||
|
||||
This function will display all package information (path, name, ...) and it
|
||||
will try to display the dependencies information (if dependencies have been
|
||||
found, dependencies graph, ...).
|
||||
"""
|
||||
log.user(VxProject(None if len(argv) <= 0 else argv[0]))
|
||||
return 0
|
|
@ -0,0 +1,46 @@
|
|||
from core.config import config_set, config_get
|
||||
|
||||
__all__ = [
|
||||
'__VXSDK_MODULE_META__',
|
||||
'cli_validate',
|
||||
'cli_parse',
|
||||
]
|
||||
|
||||
__VXSDK_MODULE_META__ = (
|
||||
['config'],
|
||||
"vxSDK configuration module",
|
||||
r"""vxsdk-configuration
|
||||
VxSDK configuration and options
|
||||
|
||||
USAGE:
|
||||
vxsdk config [<options>]
|
||||
|
||||
DESCRIPTION:
|
||||
Change / customise the vxSDK behaviour
|
||||
"""
|
||||
)
|
||||
|
||||
# TODO: list all key available
|
||||
# TODO: level selection : 'local', 'global'
|
||||
|
||||
def cli_validate(name):
|
||||
return name in __VXSDK_MODULE_META__[0]
|
||||
|
||||
def cli_parse(argv):
|
||||
""" Config subcommand entry """
|
||||
|
||||
if '--help' in argv or '-h' in argv:
|
||||
logger(LOG_USER, __VXSDK_MODULE_META__[2])
|
||||
return 0
|
||||
|
||||
if len(argv) == 2:
|
||||
logger(LOG_USER, config_get(argv[1]))
|
||||
return 0
|
||||
|
||||
if len(argv) == 3:
|
||||
if old := config_set(argv[1], argv[2]):
|
||||
logger(LOG_USER, f"previous value = {old}")
|
||||
return 0
|
||||
|
||||
logger(LOG_EMERG, __VXSDK_MODULE_META__[2])
|
||||
return 84
|
|
@ -0,0 +1,61 @@
|
|||
"""vxsdk-converter modules
|
||||
|
||||
This package provides conversion abstraction (image -> source code, ELF ->
|
||||
addin, ...) for the Vhex project.
|
||||
"""
|
||||
|
||||
from core.logger import log
|
||||
|
||||
from cli.conv.doctor import conv_doctor_cli_parse
|
||||
from cli.conv.asset import conv_asset_cli_parse
|
||||
from cli.conv.addin import conv_addin_cli_parse
|
||||
|
||||
|
||||
__all__ = [
|
||||
'__VXSDK_MODULE_META__',
|
||||
'cli_validate',
|
||||
'cli_parse',
|
||||
]
|
||||
|
||||
__VXSDK_MODULE_META__ = (
|
||||
['conv'],
|
||||
'assets converter',
|
||||
r"""vxsdk-conv
|
||||
Project assets conv
|
||||
|
||||
USAGE:
|
||||
vxsdk conv(-<ACTION>) [OPTIONS] ...
|
||||
|
||||
DESCRIPTION:
|
||||
Convert vhex project assets (or binary) into various form. By default, if no
|
||||
action is specified, the "asset" conversion is selected.
|
||||
|
||||
ACTIONS:
|
||||
doctor try to display assets and addin information (debug)
|
||||
asset convert asset into source file or binary file
|
||||
addin convert binary into addin file for vxOS
|
||||
|
||||
See `vxsdk conv <action> --help` for more information on a specific action
|
||||
"""
|
||||
)
|
||||
|
||||
def cli_validate(name):
|
||||
""" validate the module name """
|
||||
return name.find('conv') == 0
|
||||
|
||||
def cli_parse(argv):
|
||||
""" Build subcommand entry """
|
||||
if '--help' in argv or '-h' in argv:
|
||||
log.user(__VXSDK_MODULE_META__[2])
|
||||
return 0
|
||||
if argv[0].find('conv-') != 0:
|
||||
argv[0] = 'conv-asset'
|
||||
action = argv[0][5:]
|
||||
if action == 'doctor':
|
||||
return conv_doctor_cli_parse(argv[1:])
|
||||
if action == 'asset':
|
||||
return conv_asset_cli_parse(argv[1:])
|
||||
if action == 'addin':
|
||||
return conv_addin_cli_parse(argv[1:])
|
||||
log.error(f"unable to find action '{action}'")
|
||||
return 84
|
|
@ -0,0 +1,47 @@
|
|||
from core.conv.addin import generate_addin
|
||||
|
||||
__all__ = [
|
||||
'conv_addin_cli_parse'
|
||||
]
|
||||
|
||||
__HELP__ = r"""vxsdk-converter-addin
|
||||
Converte binary file into Vhex OS addin.
|
||||
|
||||
USAGE:
|
||||
vxsdk conv addin -b BINARY ...
|
||||
|
||||
DESCRIPTION:
|
||||
Convert a binary file into an application for the Vhex operating system.
|
||||
|
||||
OPTIONS:
|
||||
-b <binary path> ELF binary file (no check is performed in this file)
|
||||
-i <icon path> 92x62 pixel image path
|
||||
-o <output path> output path for the generated addin
|
||||
-n <internal name> internal addin name
|
||||
-v <internal version> internal addin version
|
||||
"""
|
||||
|
||||
def conv_addin_cli_parse(argv):
|
||||
"""Process CLI arguments"""
|
||||
if '-h' in argv or '--help' in argv:
|
||||
logger(LOG_USER, __HELP__, exit=0)
|
||||
|
||||
action = None
|
||||
info = [None, None, None, None, None]
|
||||
for arg in argv:
|
||||
if action == '-b': info[0] = arg
|
||||
if action == '-i': info[1] = arg
|
||||
if action == '-n': info[2] = arg
|
||||
if action == '-o': info[3] = arg
|
||||
if action == '-v': info[4] = arg
|
||||
if action:
|
||||
action = None
|
||||
continue
|
||||
if arg in ['-b', '-i', '-n', '-o', '-v']:
|
||||
action = arg
|
||||
continue
|
||||
|
||||
if info[0] == None:
|
||||
logger(LOG_ERR, 'converter: need binary path !', exit=84)
|
||||
|
||||
return generate_addin(info[0], info[1], info[2], info[3], info[4])
|
|
@ -0,0 +1,108 @@
|
|||
"""
|
||||
Vhex asset converter user interface
|
||||
"""
|
||||
import os
|
||||
|
||||
from core.logger import log
|
||||
from core.conv import assets_generate
|
||||
|
||||
|
||||
__all__ = [
|
||||
'conv_asset_cli_parse'
|
||||
]
|
||||
|
||||
|
||||
__HELP__ = r"""vxsdk-converter-asset
|
||||
Convert all assets file in the project directory.
|
||||
|
||||
USAGE:
|
||||
vxsdk conv-asset [project path] [OPTIONS]
|
||||
|
||||
DESCRIPTION:
|
||||
Convert all assets file in the asset directory. This part of the converter
|
||||
module will scan the provided folder (or the current working directory) and
|
||||
will try to find all `vxconv.txt` file, which describe all assets that
|
||||
should be converted.
|
||||
|
||||
If no argument is provided, then the current working directory is used as
|
||||
asset prefix and a storag for all generated source file. You can modify this
|
||||
behaviour using OPTIONS.
|
||||
|
||||
The vxconv.txt file is structured like basic key/value file:
|
||||
|
||||
```
|
||||
<exposed_symbols_name>:
|
||||
type: <image type> (font, bitmap) - required
|
||||
path: <image path> - required
|
||||
...
|
||||
|
||||
<next_exposed_symbols_name>:
|
||||
...
|
||||
```
|
||||
|
||||
Each asset file description should have at least type and name information,
|
||||
and each type have potentially its own requierements.
|
||||
|
||||
type = bitmap:
|
||||
================================== =========================================
|
||||
Keys name and value type Description
|
||||
================================== =========================================
|
||||
profile: <name> Select the bitmap pixel profile
|
||||
| rgb4 | RGB 4 (indexed)
|
||||
| rgb4a | RGBA 4 (indexed)
|
||||
| rgb8 | RGB 8 (indexed)
|
||||
| rgb8a | RGBA 8 (indexed)
|
||||
| rgb16 | RGB 16 (5:R, 6:G, 5:B)
|
||||
| rgb16a | RGBA 16 (5:R, 5:G, 5:B, 1:A)
|
||||
================================== =========================================
|
||||
|
||||
type = font:
|
||||
================================== =========================================
|
||||
Keys name and value type Description
|
||||
================================== =========================================
|
||||
grid_size: 8x9 (widthxheight) caracter size in pixel
|
||||
grid_padding: <pixel> space between caracter
|
||||
grig_border: <pixel> space around grid
|
||||
proportional: <true,false> caracter are cropped
|
||||
line_height: <pixel> caracter line alignement
|
||||
charset: <default,unicode> charset specification
|
||||
char_spacing <pixel> space between character
|
||||
================================== =========================================
|
||||
|
||||
OPTIONS:
|
||||
-o <output prefix> The prefix for source file that will be generated
|
||||
-h, --help Display this help
|
||||
"""
|
||||
|
||||
def conv_asset_cli_parse(argv):
|
||||
"""Process CLI arguments"""
|
||||
# check obvious flags
|
||||
if '-h' in argv or '--help' in argv:
|
||||
log.user(__HELP__)
|
||||
return 0
|
||||
|
||||
# fetch user indication
|
||||
manual_output = False
|
||||
prefix_output = None
|
||||
prefix_asset = None
|
||||
for arg in argv:
|
||||
if arg == '-o':
|
||||
manual_output = True
|
||||
continue
|
||||
if manual_output:
|
||||
prefix_output = arg
|
||||
continue
|
||||
if prefix_asset:
|
||||
log.warn(f"warning: previous path ({prefix_asset}) dropped")
|
||||
prefix_asset = arg
|
||||
|
||||
# check indication
|
||||
if not prefix_asset:
|
||||
prefix_asset = os.getcwd()
|
||||
if not prefix_output:
|
||||
prefix_output = os.getcwd()
|
||||
prefix_asset = os.path.abspath(prefix_asset)
|
||||
prefix_output = os.path.abspath(prefix_output)
|
||||
|
||||
# generate asset information
|
||||
return assets_generate(prefix_asset, prefix_output)
|
|
@ -0,0 +1,13 @@
|
|||
__all__ = [
|
||||
'conv_doctor_cli_parse'
|
||||
]
|
||||
|
||||
def conv_doctor_cli_parse(argv):
|
||||
"""Process CLI handling
|
||||
|
||||
TODO:
|
||||
> give asset file description to check error
|
||||
> try to display asset and addin information based on the project type
|
||||
"""
|
||||
logger(LOG_WARN, 'conv: doctor action not implemented yet')
|
||||
return 0
|
|
@ -0,0 +1,50 @@
|
|||
"""vxsdk-pkg modules
|
||||
|
||||
This module provides package management utilities that track installed Vhex
|
||||
packages. It features : dependency support, package groups, install and
|
||||
uninstall scripts, and syncs your local Vhex instance with remote repositories
|
||||
to automatically upgrade packages.
|
||||
"""
|
||||
from core.logger import log
|
||||
|
||||
from cli.pkg.search import pkg_search_cli_parse
|
||||
from cli.pkg.update import pkg_update_cli_parse
|
||||
from cli.pkg.clone import pkg_clone_cli_parse
|
||||
|
||||
|
||||
__VXSDK_MODULE_META__ = (
|
||||
['pkg'],
|
||||
"package manager for Vhex's project",
|
||||
r"""vxsdk package
|
||||
Package manager for Vhex
|
||||
|
||||
SYNOPSIS:
|
||||
vxsdk pkg [ COMMAND ] [ OPTIONS ]... [ ARGS ]...
|
||||
|
||||
COMMAND:
|
||||
search [ ARGS ]... Search project
|
||||
clone [ ARGS ]... Try to clone project
|
||||
update [ ARGS ]... Try to update project
|
||||
|
||||
For more information about a specific command, try 'vxsdk pkg <command> -h'
|
||||
"""
|
||||
)
|
||||
|
||||
def cli_validate(name):
|
||||
""" validate or not the subcommand name """
|
||||
return name in __VXSDK_MODULE_META__[0]
|
||||
|
||||
def cli_parse(argv):
|
||||
""" Vhex pacakge CLI parser entry """
|
||||
if len(argv) > 2:
|
||||
if argv[1] == 'search':
|
||||
return pkg_search_cli_parse(argv[2:])
|
||||
if argv[1] == 'clone':
|
||||
return pkg_clone_cli_parse(argv[2:])
|
||||
if argv[1] == 'update':
|
||||
return pkg_update_cli_parse(argv[2:])
|
||||
if '-h' in argv or '--help' in argv:
|
||||
log.user(__VXSDK_MODULE_META__[2])
|
||||
return 0
|
||||
log.error(__VXSDK_MODULE_META__[2])
|
||||
return 84
|
|
@ -0,0 +1,89 @@
|
|||
"""
|
||||
Vhex package cloning user interface
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
|
||||
from core.logger import log
|
||||
import core.pkg
|
||||
|
||||
|
||||
__all__ = [
|
||||
'pkg_clone_cli_parse'
|
||||
]
|
||||
|
||||
|
||||
__HELP__ = r"""
|
||||
vxsdk-pkg-clone
|
||||
Package manager cloning part
|
||||
|
||||
USAGE:
|
||||
vxsdk pkg clone [ OPTIONS ] [ targets[, ...]]
|
||||
|
||||
DESCRIPTION:
|
||||
The installation part is simple, you juste need to refer the repository
|
||||
name and, optionally, the version information. Like this:
|
||||
|
||||
<NAME@version> (ex: vxKernel@1.2.3)
|
||||
|
||||
This will allow the searching to try to match package with specific version
|
||||
information. This part is very powerfull because If the 'version' is
|
||||
detected to respect the correct semantic versioning, you can perform version
|
||||
operations in the version target:
|
||||
|
||||
Caret requirements (^)
|
||||
^1.2.3 := >=1.2.3, <2.0.0
|
||||
^1.2 := >=1.2.0, <2.0.0
|
||||
^1 := >=1.0.0, <2.0.0
|
||||
^0.2.3 := >=0.2.3, <0.3.0
|
||||
^0.2 := >=0.2.0, <0.3.0
|
||||
^0.0.3 := >=0.0.3, <0.0.4
|
||||
^0.0 := >=0.0.0, <0.1.0
|
||||
^0 := >=0.0.0, <1.0.0
|
||||
|
||||
Tilde requirements (~)
|
||||
~1.2.3 := >=1.2.3, <1.3.0
|
||||
~1.2 := >=1.2.0, <1.3.0
|
||||
~1 := >=1.0.0, <2.0.0
|
||||
|
||||
Wildcard requirements (*)
|
||||
* := >=0.0.0
|
||||
1.* := >=1.0.0, <2.0.0
|
||||
1.2.* := >=1.2.0, <1.3.0
|
||||
|
||||
Note that it's possible that a package can have two same version (one for
|
||||
branch name and another for a tag), by default, the tag is always selected,
|
||||
but here the display information will explicitly describe if the version is
|
||||
a tag and / or a branch.
|
||||
|
||||
OPTIONS:
|
||||
-y, --yes Do not ask for interactive confirmation
|
||||
-n, --no-build Do not build the project, just clone
|
||||
-h, --help Print this help and exit
|
||||
""".strip()
|
||||
|
||||
#---
|
||||
# Entry point of the module
|
||||
#---
|
||||
|
||||
def pkg_clone_cli_parse(argv):
|
||||
""" Clone a particular package """
|
||||
if not argv:
|
||||
log.error(__HELP__)
|
||||
sys.exit(84)
|
||||
if '-h' in argv or '--help' in argv:
|
||||
log.user(__HELP__)
|
||||
sys.exit(0)
|
||||
|
||||
confirm = True
|
||||
for arg in argv:
|
||||
if arg in ['-y', '--yes', '-c', '--confirm']:
|
||||
confirm = arg in ['-c', '--confirm']
|
||||
continue
|
||||
core.pkg.clone(
|
||||
arg.split('@')[0],
|
||||
None if len(arg.split('@')) != 2 else arg.split('@')[1],
|
||||
os.getcwd(),
|
||||
confirm
|
||||
)
|
||||
return 0
|
|
@ -0,0 +1,161 @@
|
|||
"""
|
||||
Vhex package searching user interface
|
||||
"""
|
||||
import sys
|
||||
|
||||
from core.logger import log
|
||||
import core.pkg
|
||||
|
||||
|
||||
__all__ = [
|
||||
'pkg_search_cli_parse'
|
||||
]
|
||||
|
||||
|
||||
__HELP__ = r"""
|
||||
vxsdk-pkg-search
|
||||
Package manager : search commands
|
||||
|
||||
SYNOPSIS:
|
||||
vxsdk pkg search [ OPTIONS ] [ targets[, ...] ]
|
||||
|
||||
DESCRIPTION:
|
||||
This part of the vxSDK will performs search queries in remote and local
|
||||
arena (see <OPTIONS> to change this default behaviour). Each "target" can
|
||||
be written specificaly like this:
|
||||
|
||||
<NAME@version> (ex: vxKernel@1.2.3)
|
||||
|
||||
This will allow the searching to try to match package with specific version
|
||||
information. This part is very powerfull because If the 'version' is
|
||||
detected to respect the correct semantic versioning, you can perform version
|
||||
operations in the version target:
|
||||
|
||||
Caret requirements (^)
|
||||
^1.2.3 := >=1.2.3, <2.0.0
|
||||
^1.2 := >=1.2.0, <2.0.0
|
||||
^1 := >=1.0.0, <2.0.0
|
||||
^0.2.3 := >=0.2.3, <0.3.0
|
||||
^0.2 := >=0.2.0, <0.3.0
|
||||
^0.0.3 := >=0.0.3, <0.0.4
|
||||
^0.0 := >=0.0.0, <0.1.0
|
||||
^0 := >=0.0.0, <1.0.0
|
||||
|
||||
Tilde requirements (~)
|
||||
~1.2.3 := >=1.2.3, <1.3.0
|
||||
~1.2 := >=1.2.0, <1.3.0
|
||||
~1 := >=1.0.0, <2.0.0
|
||||
|
||||
Wildcard requirements (*)
|
||||
* := >=0.0.0
|
||||
1.* := >=1.0.0, <2.0.0
|
||||
1.2.* := >=1.2.0, <1.3.0
|
||||
|
||||
Note that it's possible that a package can have two same version (one for
|
||||
branch name and another for a tag), by default, the tag is always selected,
|
||||
but here the display information will explicitly describe if the version is
|
||||
a tag and / or a branch.
|
||||
|
||||
OPTIONS:
|
||||
-a, --all Display all package found
|
||||
-l, --local Performs search in "local" and local
|
||||
-L, --local-only Performs search only in "local"
|
||||
-r, --remote Performs search only in "remote"
|
||||
-i, --info Print extra information for each repositories
|
||||
-s, --short Print short information for each repositories (default)
|
||||
-h, --help Print this help and exit
|
||||
""".strip()
|
||||
|
||||
#---
|
||||
# Internals
|
||||
#---
|
||||
|
||||
def _pkg_list_display(pkg_list, version, display_extra_info=False):
|
||||
for pkg in pkg_list:
|
||||
indent = ' '
|
||||
log.user(f"{pkg['full_name']}")
|
||||
|
||||
# handle special version request
|
||||
version_available = pkg['versions']
|
||||
if version:
|
||||
version_available = []
|
||||
for ver in pkg['versions']:
|
||||
if ver.validate(version):
|
||||
version_available.append(ver)
|
||||
|
||||
if display_extra_info:
|
||||
log.user(
|
||||
f"- Description : {pkg['description']}\n"
|
||||
f"- Created at {pkg['created']}\n"
|
||||
f"- Updated at {pkg['updated']}\n"
|
||||
f"- Authors : {pkg['author']}\n"
|
||||
f"- URL : {pkg['url']}\n"
|
||||
"- Versions:"
|
||||
)
|
||||
|
||||
if not version_available:
|
||||
log.user(f"{indent}No version available for '{version}'")
|
||||
return
|
||||
|
||||
for ver in version_available:
|
||||
ind = ' '
|
||||
if len(ver.sources) == 1:
|
||||
ind = '(r-) ' if ver.sources[0] == 'remote' else '(-l) '
|
||||
if len(ver.sources) > 1:
|
||||
ind = '(rl) '
|
||||
content = f"{indent}{ind}{pkg['name']}@{ver.name}".ljust(32)
|
||||
content += f"({ver.type})"
|
||||
log.user(content)
|
||||
log.user(" ")
|
||||
|
||||
#---
|
||||
# Public
|
||||
#---
|
||||
|
||||
def pkg_search_cli_parse(argv):
|
||||
""" Search command handling """
|
||||
if not argv:
|
||||
log.notice(__HELP__)
|
||||
sys.exit(84)
|
||||
if '-h' in argv or '--help' in argv:
|
||||
log.user(__HELP__)
|
||||
sys.exit(0)
|
||||
|
||||
local = True
|
||||
remote = True
|
||||
display_extra_info = False
|
||||
for arg in argv:
|
||||
# handle search arena
|
||||
if arg in ['-l', '--local', '-r', '--remote']:
|
||||
local = arg in ['-l', '--local']
|
||||
remote = arg in ['-r', '--remote']
|
||||
continue
|
||||
# handle display mode
|
||||
if arg in ['-i', '--info', '-s', '--short']:
|
||||
display_extra_info = arg in ['-i', '--info']
|
||||
continue
|
||||
|
||||
# performs searching operation
|
||||
# @note
|
||||
# - handle search exception when all package a requested
|
||||
if arg in ['-a', '--all']:
|
||||
pkg_list = core.pkg.find(None, None, local, remote)
|
||||
else:
|
||||
pkg_list = core.pkg.find(
|
||||
arg.split('@')[0],
|
||||
None,
|
||||
local,
|
||||
remote
|
||||
)
|
||||
if not pkg_list:
|
||||
log.warn(f"{arg}: package not found, skipped")
|
||||
continue
|
||||
|
||||
# display package information
|
||||
_pkg_list_display(
|
||||
pkg_list,
|
||||
None if len(arg.split('@')) != 2 else arg.split('@')[1],
|
||||
display_extra_info
|
||||
)
|
||||
|
||||
return 0
|
|
@ -0,0 +1,15 @@
|
|||
"""
|
||||
Vhex's packages updater subcommand
|
||||
"""
|
||||
import sys
|
||||
|
||||
from core.logger import log
|
||||
|
||||
__all__ = [
|
||||
'pkg_update_cli_parse'
|
||||
]
|
||||
|
||||
def pkg_update_cli_parse(_):
|
||||
""" Vhex pacakge CLI parser entry """
|
||||
log.critical("pacakge updater not implemented yet o(x_x)o")
|
||||
sys.exit(85)
|
|
@ -0,0 +1,34 @@
|
|||
import sys
|
||||
|
||||
from core.project import project_new
|
||||
|
||||
__VXSDK_MODULE_META__ = (
|
||||
['p', 'project'],
|
||||
"project abstraction",
|
||||
r"""vxsdk project
|
||||
Abstract project manipulation
|
||||
|
||||
USAGE:
|
||||
vxsdk project <COMMAND> [OPTIONS]
|
||||
|
||||
OPTIONS:
|
||||
--list List installed command
|
||||
-h, --help Print helps information
|
||||
|
||||
Common used commands:
|
||||
n, new Create a new project
|
||||
|
||||
See `vxsdk project help <action>` for more information on a specific command
|
||||
"""
|
||||
)
|
||||
|
||||
def cli_parse(_, argv):
|
||||
if argv:
|
||||
if argv[0] == 'n' or argv[0] == 'new':
|
||||
for path in argv[1:]:
|
||||
project_new(path)
|
||||
sys.exit(0)
|
||||
if '-h' in argv or '--help' in argv:
|
||||
logger(LOG_USER, __VXSDK_MODULE_META__[2])
|
||||
sys.exit(0)
|
||||
logger(LOG_EMERG, __VXSDK_MODULE_META__[2])
|
|
@ -0,0 +1,140 @@
|
|||
"""
|
||||
Compilation hadling using the dependency DAG graph
|
||||
"""
|
||||
|
||||
from core.logger import log
|
||||
from core.build.rules import project_rules_exec
|
||||
import core.conv
|
||||
|
||||
__all__ = [
|
||||
'project_compile'
|
||||
]
|
||||
|
||||
#---
|
||||
# Internals
|
||||
#---
|
||||
|
||||
def __dep_generate_assets(dep_info, _, __):
|
||||
log.user(f"[{dep_info['meta'].name}] generate assets...")
|
||||
core.conv.assets_generate(
|
||||
f"{dep_info['meta'].path}/assets/",
|
||||
f"{dep_info['meta'].parent_path}/.vxsdk/converter/{dep_info['meta'].name}/src"
|
||||
)
|
||||
return 0
|
||||
|
||||
def __dep_build_sources(dep_info, env_extra, verbose):
|
||||
log.user(f"[{dep_info['meta'].name}] build sources...")
|
||||
return project_rules_exec(
|
||||
dep_info['meta'],
|
||||
dep_info['target'],
|
||||
['configure', 'build'],
|
||||
verbose,
|
||||
env_extra
|
||||
)
|
||||
|
||||
def __dep_install(dep_info, env_extra, verbose):
|
||||
log.user(f"[{dep_info['meta'].name}] install...")
|
||||
return project_rules_exec(
|
||||
dep_info['meta'],
|
||||
dep_info['target'],
|
||||
['intall'],
|
||||
verbose,
|
||||
env_extra
|
||||
)
|
||||
|
||||
def __compile_dependency(dep, env_extra, verbose):
|
||||
""" Compile dependency
|
||||
|
||||
@args
|
||||
> dep (dict) - dependency information
|
||||
> verbose (bool) - display extra verbose information
|
||||
|
||||
@return
|
||||
> 0 on success, negative value otherwise
|
||||
"""
|
||||
if __dep_generate_assets(dep['info'], env_extra, verbose) != 0:
|
||||
log.error(f"[{dep['info']['meta'].name}] error during asset generation")
|
||||
return -1
|
||||
if __dep_build_sources(dep['info'], env_extra, verbose) != 0:
|
||||
log.error(f"[{dep['info']['meta'].name}] error during source build")
|
||||
return -2
|
||||
if __dep_install(dep['info'], env_extra, verbose) != 0:
|
||||
log.error(f"[{dep['info']['meta'].name}] error during installation")
|
||||
return -3
|
||||
return 0
|
||||
|
||||
def __env_extra_fetch(dep_graph):
|
||||
""" Generate extra environement information
|
||||
|
||||
@args
|
||||
> dep_graph (list) : DAG graph
|
||||
|
||||
@return
|
||||
> a dictionary with all common env export
|
||||
"""
|
||||
env_extra = {}
|
||||
for dep in dep_graph:
|
||||
dep_meta = dep['info']['meta']
|
||||
dep_env_extra = dep_meta.get_env_extra(dep['info']['target'])
|
||||
for key in dep_env_extra:
|
||||
if key.upper() != key:
|
||||
log.warn(f"[{dep_meta.name}] : {key} : env key must be upper")
|
||||
if key in env_extra:
|
||||
log.warn(f"[{dep_meta.name}] : {key} : already set, overrided")
|
||||
env_extra[key] = dep_env_extra[key]
|
||||
return env_extra
|
||||
|
||||
#---
|
||||
# Public
|
||||
#---
|
||||
|
||||
def project_compile(dep_graph, verbose=False):
|
||||
r""" Build the entire project
|
||||
|
||||
@args
|
||||
> dep_graph (list) : DAG graph
|
||||
> verbose (bool) : enable or disable verbose mode during build steps
|
||||
|
||||
Return:
|
||||
> 0 for succes, negative vale otherwise
|
||||
"""
|
||||
# generate "extra" environement configuration
|
||||
env_extra = __env_extra_fetch(dep_graph)
|
||||
|
||||
# main build loop
|
||||
while True:
|
||||
completed = True
|
||||
for dep in dep_graph:
|
||||
# check if the package as been completed
|
||||
if dep['completed']:
|
||||
continue
|
||||
completed = False
|
||||
|
||||
# check that all of its dependencies as been completed
|
||||
can_run = True
|
||||
for dep_idx in dep['dependencies']:
|
||||
if not dep_graph[dep_idx]['completed']:
|
||||
can_run = False
|
||||
break
|
||||
if not can_run:
|
||||
continue
|
||||
|
||||
# handle verbosity during its building
|
||||
enable_verbose = verbose
|
||||
if dep['info']['meta'].type == 'app':
|
||||
enable_verbose = True
|
||||
|
||||
# build the package
|
||||
error = __compile_dependency(dep, env_extra, enable_verbose)
|
||||
if error != 0:
|
||||
return error
|
||||
|
||||
# mark as completed
|
||||
dep['completed'] = True
|
||||
|
||||
# if all package as been completed, quit
|
||||
if completed:
|
||||
break
|
||||
|
||||
# no error, exit
|
||||
return 0
|
|
@ -0,0 +1,150 @@
|
|||
"""
|
||||
Dependencies resolver
|
||||
"""
|
||||
import os
|
||||
|
||||
from core.logger import log
|
||||
from core.build.meta import VxProjectMeta
|
||||
import core.pkg
|
||||
|
||||
__all__ = [
|
||||
'project_dependency_clone'
|
||||
]
|
||||
|
||||
#---
|
||||
# Internals
|
||||
#---
|
||||
|
||||
def __dep_graph_update(dep_graph, dep_parent_id, pkg_info):
|
||||
"""
|
||||
Update dependency graph
|
||||
"""
|
||||
# inject new pkg information and gues its ID
|
||||
dep_id = len(dep_graph)
|
||||
dep_graph.append({
|
||||
'info' : pkg_info,
|
||||
'completed' : False,
|
||||
'dependencies' : []
|
||||
})
|
||||
|
||||
# update internal parent ID
|
||||
if dep_parent_id >= 0:
|
||||
dep_graph[dep_parent_id]['dependencies'].append(dep_id)
|
||||
return dep_id
|
||||
|
||||
def __recurs_clone(parent_path, dep_info, pkg_info, prefix, dep_stack):
|
||||
"""Clone all dependency and generate dependency graph
|
||||
|
||||
@args
|
||||
> dep_graph (list) - list dependencies
|
||||
> dep_parent_id (int) - parent index in `dep_graph`
|
||||
> pkg_info (dict) - package information
|
||||
> prefix (str) - prefix for package cloning
|
||||
|
||||
@return
|
||||
> 0 if success, negative value otherwise
|
||||
"""
|
||||
# fetch info
|
||||
dep_graph = dep_info[0]
|
||||
dep_parent_id = dep_info[1]
|
||||
|
||||
# check circular dependency and update the stack
|
||||
for dep in dep_stack:
|
||||
if dep != pkg_info:
|
||||
continue
|
||||
log.error(f"circular dependency with '{pkg_info['name']}' detected")
|
||||
return -1
|
||||
dep_stack.append(pkg_info)
|
||||
|
||||
# try to clone the package
|
||||
pkg_path = core.pkg.clone(
|
||||
pkg_info['name'],
|
||||
pkg_info['version'],
|
||||
prefix
|
||||
)
|
||||
|
||||
# check pacakge validity
|
||||
# @todo
|
||||
# Find a way to not be dependent of VxProjectMeta to avoid spaghetti code
|
||||
target = pkg_info['target']
|
||||
pkg_meta = VxProjectMeta(pkg_path, parent_path, pkg_info['extra_conf'])
|
||||
if target not in pkg_meta.target_support:
|
||||
log.error(f"[{pkg_meta.name}] target '{target}' not supported")
|
||||
return -2
|
||||
|
||||
# generate dependency information
|
||||
pkg_dep_id = __dep_graph_update(
|
||||
dep_graph,
|
||||
dep_parent_id,
|
||||
{
|
||||
'meta' : pkg_meta,
|
||||
'target' : pkg_info['target']
|
||||
}
|
||||
)
|
||||
for dep in pkg_meta.get_dependencies(target):
|
||||
__recurs_clone(
|
||||
parent_path,
|
||||
(dep_graph, pkg_dep_id),
|
||||
dep,
|
||||
prefix,
|
||||
dep_stack.copy()
|
||||
)
|
||||
return 0
|
||||
|
||||
#---
|
||||
# Public
|
||||
#---
|
||||
|
||||
def project_dependency_clone(pkg, target):
|
||||
r""" Clone dependencies of package and generate a DAG graph
|
||||
|
||||
This function will clone all dependency of a package optimised for a
|
||||
particular target a `Direct Acyclic Graph` (DAG) graph will be generated to
|
||||
facilitate project compilation later.
|
||||
|
||||
@args
|
||||
> pkg (dict) - first pacakge information
|
||||
> target (str) - build target
|
||||
|
||||
@return
|
||||
> A list with package and dependency information : [
|
||||
{
|
||||
'meta' : <:obj:VxProjectMeta>
|
||||
'extra_conf' : <extra configuration information>
|
||||
'target' : build target
|
||||
},
|
||||
...
|
||||
]
|
||||
"""
|
||||
# create the package "storage" folder
|
||||
# @note
|
||||
# All dependencies will be cloned in "global" path, only symbolic link will
|
||||
# be generated here
|
||||
prefix = f"{pkg.parent_path}/.vxsdk/dependencies"
|
||||
if not os.path.exists(prefix):
|
||||
os.makedirs(prefix)
|
||||
|
||||
# clone all dependencies and generate a DAG graph
|
||||
# @note
|
||||
# we need to manualy bootstrap the current project as a dependency to
|
||||
# facilitate graph generation with a unified data structure
|
||||
dep_graph = []
|
||||
dep_origin_id = __dep_graph_update(
|
||||
dep_graph,
|
||||
-1,
|
||||
{
|
||||
'meta' : pkg,
|
||||
'target' : target,
|
||||
}
|
||||
)
|
||||
for dep in pkg.get_dependencies(target):
|
||||
__recurs_clone(
|
||||
pkg.parent_path,
|
||||
(dep_graph, dep_origin_id),
|
||||
dep,
|
||||
prefix,
|
||||
[]
|
||||
)
|
||||
|
||||
# return the DAG
|
||||
return dep_graph
|
|
@ -0,0 +1,288 @@
|
|||
"""
|
||||
vxsdk.toml document parser
|
||||
"""
|
||||
import os
|
||||
import toml
|
||||
|
||||
from core.logger import log
|
||||
from core.pkg.version import version_get
|
||||
|
||||
__all__ = [
|
||||
'VxProjectMeta'
|
||||
]
|
||||
|
||||
class VxProjectMeta():
|
||||
r""" Represente the project meta-information
|
||||
|
||||
All meta-information is stored in a file named `vxsdk.toml` at the root
|
||||
of the project directory.
|
||||
|
||||
This class exposes:
|
||||
|
||||
================================== =========================================
|
||||
Property Description
|
||||
================================== =========================================
|
||||
name (str) Project name
|
||||
version (str) Project versions
|
||||
path (str) Project path
|
||||
description (str) Project description
|
||||
type (str) Project type
|
||||
target_support (list,str) List of all supported target
|
||||
================================== =========================================
|
||||
Methods Description
|
||||
================================== =========================================
|
||||
get_dependencies (list,dict) List of all dependencies information
|
||||
get_build_rules (dict) Building rules information
|
||||
================================== =========================================
|
||||
|
||||
For the file to be valid, it should expose *at least* the 'project' section
|
||||
with the name of the project. Other information like the project type have
|
||||
default value:
|
||||
|
||||
================================== =======================================
|
||||
Information Default value
|
||||
================================== =======================================
|
||||
name (str) Required.
|
||||
version (str) auto. (see core.pkg.version.version_get)
|
||||
type (str) 'bin'
|
||||
description (str) Project description
|
||||
target_support (list,str) [<empty>] (support all)
|
||||
================================== =======================================
|
||||
|
||||
Also, this class will perform any vxSDK configuration needed described in
|
||||
the "special" section 'config' which can expose "key/value" which will be
|
||||
added (if not exist) in the vxSDK's configuration file
|
||||
(~/.config/vxsdk/config.toml).
|
||||
|
||||
Example of a valid 'vxsdk.toml' file:
|
||||
```
|
||||
[project]
|
||||
name = 'project'
|
||||
type = 'addin'
|
||||
|
||||
[dependencies]
|
||||
vxkernel = '^1.0.0'
|
||||
|
||||
[vxkernel.extra]
|
||||
build = '--static --verbose'
|
||||
|
||||
[config]
|
||||
'superh.path.sysroot' = '{path.sysroot}/superh'
|
||||
'superh.path.sysroot_test' = '{superh.path.sysroot}/test/yes'
|
||||
```
|
||||
"""
|
||||
def __init__(self, path, parent_path=None, extra_conf=None):
|
||||
""" Open and parse the vxsdk.toml file
|
||||
|
||||
@args
|
||||
> path (str) : project path which we can find `vxsdk.toml` file
|
||||
> parent_path (str) : project parent path
|
||||
> extra_conf (dict) : extra flags information about particular steps
|
||||
|
||||
@todo
|
||||
> proper handle exception
|
||||
"""
|
||||
# try to open the file
|
||||
self._path = os.path.abspath(path)
|
||||
with open(self._path + '/vxsdk.toml', encoding='utf-8') as file:
|
||||
self._toml = toml.loads(file.read())
|
||||
|
||||
# check mandatory information
|
||||
# @notes
|
||||
# - 'project' section
|
||||
# - project name information
|
||||
if 'project' not in self._toml or 'name' not in self._toml['project']:
|
||||
raise Exception(f"'{self._path}': missing project name information")
|
||||
|
||||
# setup information and cache
|
||||
self._type = None
|
||||
self._target_support = None
|
||||
self._parent_path = parent_path
|
||||
self._extra_conf = extra_conf
|
||||
|
||||
def __str__(self):
|
||||
""" Display project information """
|
||||
content = f"project '{self.name}' - {self.version}\n"
|
||||
content += '\n'
|
||||
content += 'project meta\n'
|
||||
content += ' - path:'.ljust(16) + f'{self.path}\n'
|
||||
content += ' - parent:'.ljust(16) + f'{self.parent_path}\n'
|
||||
content += ' - type:'.ljust(16) + f'{self.type}\n'
|
||||
content += '\n'
|
||||
content += 'Build information\n'
|
||||
#if not self.build:
|
||||
# content += ' default building rules used\n'
|
||||
# content += '\n'
|
||||
#else:
|
||||
# rule_list = self.get_build_rules()
|
||||
# for rule in rule_list:
|
||||
# content += ' - '
|
||||
# content += f'{rule}:'.ljust(16)
|
||||
# content += f'{rule_list[rule]}\n'
|
||||
# content += '\n'
|
||||
#content += 'Dependencies list\n'
|
||||
#if not self.dependencies:
|
||||
# content += ' No dependencies for this project\n'
|
||||
#else:
|
||||
# for dep in self.dependencies:
|
||||
# content += ' - ' + f'{dep.name}'.ljust(16) + f'({dep.version})\n'
|
||||
return content
|
||||
|
||||
def __repr__(self):
|
||||
return f'{self.name}<{self.version}>({self.type})'
|
||||
|
||||
#---
|
||||
# Properties
|
||||
#---
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""<property> get project name"""
|
||||
return self._toml['project']['name']
|
||||
|
||||
@property
|
||||
def path(self):
|
||||
"""<property> get project path"""
|
||||
return self._path
|
||||
|
||||
@property
|
||||
def description(self):
|
||||
"""<property> get project description"""
|
||||
if 'description' in self._toml['project']:
|
||||
return self._toml['project']['description']
|
||||
return ''
|
||||
|
||||
@property
|
||||
def parent_path(self):
|
||||
"""<property> Return project parent path """
|
||||
return self._parent_path if self._parent_path else self._path
|
||||
|
||||
@property
|
||||
def is_original(self):
|
||||
"""<property> Return if the project is the target or a dependency"""
|
||||
return not self._parent_path
|
||||
|
||||
@property
|
||||
def extra_conf(self):
|
||||
"""<property> Return extra pacakge information """
|
||||
return self._extra_conf
|
||||
|
||||
@property
|
||||
def type(self):
|
||||
"""<property> get project build rules"""
|
||||
if self._type:
|
||||
return self._type
|
||||
if 'type' not in self._toml['project']:
|
||||
return 'bin'
|
||||
self._type = self._toml['project']['type']
|
||||
if self._type not in ['app', 'bin', 'lib', 'addin']:
|
||||
log.warn(
|
||||
f"{self.path}: project type '{self._type}' is unknown !"
|
||||
f" Handled like a 'bin' type project"
|
||||
)
|
||||
return self._type
|
||||
|
||||
@property
|
||||
def target_support(self):
|
||||
"""<property> get supported target list"""
|
||||
if not self._target_support:
|
||||
self._target_support = []
|
||||
if 'target' in self._toml['project']:
|
||||
self._target_support = self._toml['project']['target']
|
||||
return self._target_support
|
||||
|
||||
@property
|
||||
def version(self):
|
||||
"""<property> get current package version"""
|
||||
return version_get(self.path)
|
||||
|
||||
#---
|
||||
# Methods
|
||||
#---
|
||||
|
||||
def get_dependencies(self, target=None):
|
||||
""" Get project dependency list for a particular board target
|
||||
|
||||
@args
|
||||
> target (str) - target name
|
||||
|
||||
@return
|
||||
> Return a list of dependencies information : [
|
||||
{
|
||||
'name' : <dep name>,
|
||||
'version' : <dep target version>,
|
||||
'target' : <dep build target>,
|
||||
'extra_conf' : <dep extra configuration>
|
||||
},
|
||||
...
|
||||
]
|
||||
"""
|
||||
# check mandatory target requirement
|
||||
if self.target_support:
|
||||
if not target or target not in self.target_support:
|
||||
log.warn(f"[{self.name}] target '{target}' not supported")
|
||||
return []
|
||||
|
||||
# help particular section dump
|
||||
def section_dep_fetch(section, target):
|
||||
if 'dependencies' not in section:
|
||||
return []
|
||||
dep_list = []
|
||||
for dep_name in section['dependencies']:
|
||||
extra = None
|
||||
if 'extra' in section and dep_name in section['extra']:
|
||||
extra = section['extra'][dep_name]
|
||||
pkg_target = target
|
||||
pkg_version = section['dependencies'][dep_name].split('@')
|
||||
if len(pkg_version) >= 2:
|
||||
pkg_target = pkg_version[1]
|
||||
dep_list.append({
|
||||
'name' : dep_name,
|
||||
'version' : pkg_version[0],
|
||||
'target' : pkg_target,
|
||||
'extra_conf' : extra
|
||||
})
|
||||
return dep_list
|
||||
|
||||
# fetch dependencies information in common and target-specific section
|
||||
dep_list = section_dep_fetch(self._toml, target)
|
||||
if target and target in self._toml:
|
||||
dep_list += section_dep_fetch(self._toml[target], target)
|
||||
return dep_list
|
||||
|
||||
def get_build_rules(self, target=None):
|
||||
""" Get project building rules
|
||||
|
||||
@args
|
||||
> target (str) - target name
|
||||
|
||||
@return
|
||||
> a dictionary with all available building step : {
|
||||
'configure' : <configure rule string>,
|
||||
'build' : <build rule string>,
|
||||
'install' : <install rule string>,
|
||||
'uninstall' : <uninstall rule string>,
|
||||
}
|
||||
"""
|
||||
def section_rules_fetch(section):
|
||||
if 'build' not in section:
|
||||
log.warn(f"[{self.name}] no building rules")
|
||||
return {}
|
||||
rules = {}
|
||||
for rule in section['build']:
|
||||
if rule in ['configure', 'build', 'install', 'uninstall']:
|
||||
rules[rule] = section['build'][rule]
|
||||
continue
|
||||
log.warn(
|
||||
f"[{self.name}] building rule '{rule}' doesn't exists"
|
||||
)
|
||||
return rules
|
||||
|
||||
# check if we need to fetch common rules or target-specific one
|
||||
if target:
|
||||
if self.target_support and target not in self.target_support:
|
||||
log.error(f"[{self.name}] not supported target '{target}'")
|
||||
return {}
|
||||
if target in self._toml:
|
||||
return section_rules_fetch(self._toml[target])
|
||||
return section_rules_fetch(self._toml)
|
|
@ -0,0 +1,195 @@
|
|||
r""" build.core - Core building function
|
||||
|
||||
This module exports:
|
||||
|
||||
VxProject - a class representing a "Vhex" project
|
||||
"""
|
||||
import os
|
||||
|
||||
from core.logger import log
|
||||
from core.build.meta import VxProjectMeta
|
||||
from core.build.dependency import project_dependency_clone
|
||||
from core.build.compile import project_compile
|
||||
from core.build.rules import project_rules_exec
|
||||
|
||||
__all__ = [
|
||||
'VxProject'
|
||||
]
|
||||
|
||||
|
||||
#---
|
||||
# Public object
|
||||
#---
|
||||
|
||||
class VxProject(VxProjectMeta):
|
||||
r"""Represent a Vhex project
|
||||
|
||||
A Vhex project is a folder which contains a 'vxsdk.toml' file. This file
|
||||
describe information about the project, like its name, version, description,
|
||||
and many other information.
|
||||
|
||||
This class will handle this particular file, and will abstract some complex
|
||||
tasks like build abstraction, assets conversion, manage project dependencies
|
||||
and more.
|
||||
|
||||
All project information and file generation (file objects, build logs,
|
||||
assets conversion, ...) will be stored in a '.vxsdk' folder at the root of
|
||||
the project folder.
|
||||
|
||||
The VxProject exposes:
|
||||
|
||||
================================== =========================================
|
||||
Property VxprojectMeta Description
|
||||
================================== =========================================
|
||||
name (str) * Holds name
|
||||
type (str) * Holds type (lib, app or addin)
|
||||
path (str) * Holds project path
|
||||
dependencies (list) * Holds project dependencies
|
||||
description (str) * Holds description
|
||||
build_rules (dict) * Holds custom build information
|
||||
board_support (list,str) * List of all supported board (None if all)
|
||||
extra_conf (dict) Holds extra rules information
|
||||
parent (str) Holds project parent path
|
||||
version (str) Holds version
|
||||
assets (list) Holds project assets paths
|
||||
is_original (bool) Return True if its the original package
|
||||
================================== =========================================
|
||||
|
||||
================================== =========================================
|
||||
Method Description
|
||||
================================== =========================================
|
||||
update() Try to update project's dependencies
|
||||
install() Install the project
|
||||
uninstall() Uninstall the project
|
||||
build() Build the entiere project (dep, src, ...)
|
||||
rebuild() Rebuild the entiere project
|
||||
================================== =========================================
|
||||
"""
|
||||
def __init__(self, path=None, parent_path=None, extra_conf=None):
|
||||
r""" Try to read the TOML project file of a project
|
||||
|
||||
This constructor will simply try to read the vxsdk.toml file stored at
|
||||
the root of the project directory . The project path can be provided by
|
||||
`path` but if the path is not provided, the vxSDK will select the
|
||||
current working directory.
|
||||
|
||||
The `parent` path is used to know if the project is the original one or
|
||||
a dependency. If it a dependency, its build genereted files will be done
|
||||
in the `.vxsdk/*` directory of the original one.
|
||||
|
||||
Extra information for each building steps can be provided in the form of
|
||||
dictionary. Like this:
|
||||
|
||||
```
|
||||
{
|
||||
'configure' : '--static --enable-xana',
|
||||
'build': '--verbose'
|
||||
}
|
||||
```
|
||||
|
||||
@args
|
||||
> path (str) - the project path
|
||||
> parent_path (str) - the project parent path
|
||||
> extra (dict) - extra flags information about particular steps
|
||||
|
||||
@raise
|
||||
> Exception - if the project file cannot be loaded
|
||||
"""
|
||||
super().__init__(
|
||||
os.path.abspath(path) if path else os.getcwd(),
|
||||
parent_path,
|
||||
extra_conf
|
||||
)
|
||||
|
||||
#---
|
||||
# Public methods
|
||||
#---
|
||||
|
||||
def update(self):
|
||||
r"""Update project's dependencies.
|
||||
|
||||
This method will try to bump all dependencies to the possible newest
|
||||
version, always validating the dependencies versioning match
|
||||
|
||||
TODO:
|
||||
> display dependency name and tag and "old version -> new version"
|
||||
> try to update and build the new dependencies before override
|
||||
"""
|
||||
log.error('dependencies update not implemented yet')
|
||||
return -1
|
||||
|
||||
def install(self, target, verbose=False):
|
||||
r""" Install the project.
|
||||
|
||||
@args
|
||||
> target (str) : targeted board for operation (fxcg50, SDL, ...)
|
||||
> verbose (bool) : display project logs or just strict minimum
|
||||
|
||||
@return
|
||||
> True if no error, False otherwise
|
||||
"""
|
||||
if target and self.target_support and target not in self.target_support:
|
||||
log.error(f"[{self.name}] target '{target}' not supported")
|
||||
return -1
|
||||
return project_rules_exec(
|
||||
self,
|
||||
target,
|
||||
['install'],
|
||||
verbose
|
||||
)
|
||||
|
||||
def uninstall(self, target, verbose=False):
|
||||
""" Uninstall the project
|
||||
|
||||
@args
|
||||
> target (str) : targted board for operation (fxcg50, SDL, ...)
|
||||
> verbose (bool) : display project logs or just strict minimum
|
||||
|
||||
@return
|
||||
> True if no error, False otherwise
|
||||
"""
|
||||
if target and self.target_support and target not in self.target_support:
|
||||
log.error(f"[{self.name}] target '{target}' not supported")
|
||||
return -1
|
||||
return project_rules_exec(
|
||||
self,
|
||||
target,
|
||||
['uninstall'],
|
||||
verbose
|
||||
)
|
||||
|
||||
def build(self, target=None, verbose=False):
|
||||
"""Build the entire project
|
||||
|
||||
Args:
|
||||
> target (str) : target build operation
|
||||
> verbose (bool) : display project logs or just strict minimum
|
||||
|
||||
Return:
|
||||
> True if success, False otherwise
|
||||
"""
|
||||
log.user(f"[{self.name}] start building package...")
|
||||
|
||||
# check target availability
|
||||
if target and self.target_support and target not in self.target_support:
|
||||
log.error(f"[{self.name}] target '{target}' not supported")
|
||||
return -1
|
||||
|
||||
# check if the project is compatible with the board
|
||||
build_rules = self.get_build_rules(target)
|
||||
if not 'config' in build_rules \
|
||||
and not 'build' in build_rules \
|
||||
and not 'install' in build_rules:
|
||||
log.error(f"[{self.name}] project not compatible for '{target}'")
|
||||
return -2
|
||||
|
||||
# clone dependencies
|
||||
log.debug(f"target test = {target}")
|
||||
dep_graph = project_dependency_clone(self, target)
|
||||
if not dep_graph:
|
||||
log.error(f"[{self.name}] unable to perform dependeincy relovation")
|
||||
return -3
|
||||
|
||||
# compile the entire project
|
||||
log.debug(f"dep_graph = {dep_graph}")
|
||||
return project_compile(dep_graph, verbose)
|
|
@ -0,0 +1,160 @@
|
|||
"""
|
||||
Vhex build shell command abstraction
|
||||
"""
|
||||
import os
|
||||
import subprocess
|
||||
|
||||
from core.logger import log
|
||||
from core.config import config_get
|
||||
|
||||
__all__ = [
|
||||
'project_rules_exec'
|
||||
]
|
||||
|
||||
#---
|
||||
# Internals
|
||||
#---
|
||||
|
||||
def __project_generate_env(env_extra, pkg_meta, target):
|
||||
r""" Generate environment variables
|
||||
|
||||
================================ =======================================
|
||||
Type Description
|
||||
================================ =======================================
|
||||
VXSDK_PKG_NAME project name
|
||||
VXSDK_PKG_VERSION project version
|
||||
VXSDK_PKG_IS_ORIGINAL 'true' if the package is the original
|
||||
VXSDK_PREFIX_BUILD project build prefix (for object files)
|
||||
VXSDK_PREFIX_INSTALL project installation prefix
|
||||
VXSDK_PREFIX_LIB prefix for all stored library
|
||||
VXSDK_GCC_CFLAGS include flags for GCC
|
||||
VXSDK_ASSETS_SRC assets sources file directory
|
||||
VXSDK_ASSETS_BUILD assets build directory
|
||||
VXSDK_PATH_SYSROOT_LIB sysroot library path
|
||||
VXSDK_PATH_SYSROOT_INCLUDE sysroot include path
|
||||
================================ =======================================
|
||||
|
||||
@arg
|
||||
> board_target (str) - targeted board
|
||||
"""
|
||||
prefix = f"{pkg_meta.parent_path}/.vxsdk"
|
||||
|
||||
# generate VXSDK_PREFIX_BUILD information
|
||||
pkg_prefix_build = f'{prefix}/build/{pkg_meta.name}'
|
||||
if not os.path.exists(pkg_prefix_build):
|
||||
os.makedirs(pkg_prefix_build)
|
||||
|
||||
# generate VXSDK_PREFIX_INSTALL information
|
||||
pkg_prefix_install = f'{prefix}/lib'
|
||||
if pkg_meta.type == 'app':
|
||||
pkg_prefix_install = os.path.expanduser(config_get('path.bin'))
|
||||
if not os.path.exists(pkg_prefix_install):
|
||||
os.makedirs(pkg_prefix_install)
|
||||
|
||||
# generate VXSDK_PREFIX_LIB information
|
||||
pkg_prefix_lib = f'{prefix}/lib'
|
||||
if not os.path.exists(pkg_prefix_lib):
|
||||
os.makedirs(pkg_prefix_lib)
|
||||
|
||||
# generate VXSDK_GCC_CFLAGS information
|
||||
#@todo : compiler specific !
|
||||
#pkg_gcc_cflags = f'-L. -Llib/ -L{prefix}/lib/'
|
||||
#pkg_gcc_cflags += f' -I. -Iinclude/ -I{prefix}/lib/include/'
|
||||
|
||||
# generate VXSDK_ASSETS_* information
|
||||
pkg_assets_src = f'{prefix}/converter/{pkg_meta.name}/src'
|
||||
pkg_assets_obj = f'{prefix}/converter/{pkg_meta.name}/obj'
|
||||
|
||||
# generate VXSDK_PKG_* information
|
||||
pkg_name = pkg_meta.name
|
||||
pkg_is_original = str(pkg_meta.is_original)
|
||||
pkg_version = pkg_meta.version
|
||||
pkg_target = target
|
||||
|
||||
# generate "dependence-specific" env
|
||||
envp = {
|
||||
'VXSDK_PKG_NAME' : pkg_name,
|
||||
'VXSDK_PKG_TARGET' : pkg_target,
|
||||
'VXSDK_PKG_VERSION' : pkg_version,
|
||||
'VXSDK_PKG_IS_ORIGINAL' : pkg_is_original,
|
||||
'VXSDK_PREFIX_BUILD' : pkg_prefix_build,
|
||||
'VXSDK_PREFIX_INSTALL' : pkg_prefix_install,
|
||||
'VXSDK_PREFIX_LIB' : pkg_prefix_lib,
|
||||
'VXSDK_ASSETS_SRC' : pkg_assets_src,
|
||||
'VXSDK_ASSETS_BUILD' : pkg_assets_obj,
|
||||
'VXSDK_CURRENT_SOURCE_DIR' : pkg_meta.path
|
||||
}
|
||||
|
||||
# merge extra env configuration
|
||||
for key in env_extra:
|
||||
if key in envp:
|
||||
log.warn(f"[{pkg_name}] extra env key '{key}' already exist")
|
||||
envp[key] += f' {env_extra[key]}'
|
||||
|
||||
# update env
|
||||
log.debug(f"{envp}")
|
||||
os.environ.update(envp)
|
||||
|
||||
#---
|
||||
# Public
|
||||
#---
|
||||
|
||||
def project_rules_exec(pkg_meta, target, rule_list, env_extra, verbose):
|
||||
""" Walk through project rules and performs target operation if needed
|
||||
|
||||
This method will build the project source using the custom build
|
||||
information set by the author in the TOML description.
|
||||
|
||||
Sometimes, we need to pass extra flags information in some steps. You
|
||||
can pass this information using the 'rules_extra' property of this class
|
||||
(see more information at 'VxProject.extra')
|
||||
|
||||
@args
|
||||
> board_target (str) : targeted board for operation (fxcg50, SDL, ...)
|
||||
> rules (array,str) : list of wanted operations to performs
|
||||
> extra (dict,str) : user extra information about operations
|
||||
> verbose (bool) : capture (verbose=False) or not the operation print
|
||||
|
||||
@return
|
||||
> 0 if success, negative value otherwise
|
||||
"""
|
||||
# save env information
|
||||
saved_pwd = os.getcwd()
|
||||
saved_env = os.environ.copy()
|
||||
|
||||
# move to the project path
|
||||
os.chdir(pkg_meta.path)
|
||||
|
||||
# fetch target specific rules
|
||||
project_rules = pkg_meta.get_build_rules(target)
|
||||
|
||||
# main loop. Fetch operation, check if available, generate env
|
||||
# information, and perform said operation
|
||||
ret = 0
|
||||
verbose = not verbose
|
||||
for rule in rule_list:
|
||||
if rule not in project_rules:
|
||||
continue
|
||||
cmd = project_rules[rule].strip()
|
||||
if pkg_meta.extra_conf and rule in pkg_meta.extra_conf:
|
||||
cmd += ' ' + pkg_meta.extra_conf[rule]
|
||||
__project_generate_env(env_extra, pkg_meta, target)
|
||||
log.debug(f"[{pkg_meta.name}] rule : {rule} -> cmd : ${cmd}$")
|
||||
ret = subprocess.run(
|
||||
cmd.split(),
|
||||
capture_output=verbose,
|
||||
check=False
|
||||
)
|
||||
if ret.returncode != 0:
|
||||
if ret.stdout:
|
||||
log.error(ret.stdout.decode('utf8'))
|
||||
if ret.stderr:
|
||||
log.error(ret.stderr.decode('utf8'))
|
||||
ret = ret.returncode
|
||||
break
|
||||
ret = 0
|
||||
|
||||
# restore env information
|
||||
os.environ.update(saved_env)
|
||||
os.chdir(saved_pwd)
|
||||
return ret
|
|
@ -0,0 +1,168 @@
|
|||
"""
|
||||
Configuration file wrapper
|
||||
"""
|
||||
import os
|
||||
import toml
|
||||
|
||||
from core.logger import log
|
||||
|
||||
__all__ = [
|
||||
'config_get',
|
||||
'config_set',
|
||||
'config_set_default'
|
||||
]
|
||||
|
||||
DEFAULT_CONFIG_KEYVAL = [
|
||||
('path.config', '~/.config/vxsdk/config.toml'),
|
||||
('path.sysroot', '~/.local/share/vxsdk/sysroot'),
|
||||
('path.packages', '~/.local/share/vxsdk/packages'),
|
||||
('path.assets', '~/.local/share/vxsdk/assets'),
|
||||
('path.bin', '~/.local/bin'),
|
||||
|
||||
('build.default_target', 'superh'),
|
||||
|
||||
('pkg.backend.name', 'gitea'),
|
||||
('pkg.backend.url', 'https://gitea.planet-casio.com'),
|
||||
('pkg.local_storage', '{path.packages}')
|
||||
]
|
||||
|
||||
__CACHED_CONFIG_FILE = None
|
||||
__CACHED_CONFIG_PATH = None
|
||||
|
||||
#---
|
||||
# Internals
|
||||
#---
|
||||
|
||||
def __setitem_dots(dictionary, key, value, path=""):
|
||||
if "." not in key:
|
||||
old = dictionary[key] if key in dictionary else None
|
||||
dictionary[key] = value
|
||||
return old
|
||||
group, key = key.split(".", 1)
|
||||
if group in dictionary and not isinstance(dictionary[group], dict):
|
||||
raise ValueError(f"cannot assign {value} into value {path+group}")
|
||||
if group not in dictionary:
|
||||
dictionary[group] = {}
|
||||
return __setitem_dots(dictionary[group], key, value, path + group + ".")
|
||||
|
||||
|
||||
def __config_control(name, value=None):
|
||||
""" Common configuration file manipulation
|
||||
|
||||
If the `value` parameter is not set, only read operation will be performed,
|
||||
otherwise the complete file will be updated.
|
||||
|
||||
@args
|
||||
> name (str) - dot-separated ker (ex : `default.board.name`)
|
||||
> value (str) - value for the key
|
||||
"""
|
||||
global __CACHED_CONFIG_PATH
|
||||
global __CACHED_CONFIG_FILE
|
||||
|
||||
# check if config file information are cached
|
||||
# @notes
|
||||
# - create the configuration file folder if needed
|
||||
# - load the TOML content
|
||||
# - cache pathname to avoid path manipulation
|
||||
if not __CACHED_CONFIG_PATH:
|
||||
__CACHED_CONFIG_PATH = os.path.expanduser('~/.config/vxsdk/config.toml')
|
||||
if not __CACHED_CONFIG_FILE:
|
||||
cache_basename = os.path.basename(__CACHED_CONFIG_PATH)
|
||||
if not os.path.exists(cache_basename):
|
||||
os.makedirs(cache_basename)
|
||||
with open(__CACHED_CONFIG_PATH, 'r+', encoding='utf-8') as file:
|
||||
__CACHED_CONFIG_FILE = toml.loads(file.read())
|
||||
|
||||
# check "read-only" request (just fetch value)
|
||||
if not value:
|
||||
conf = __CACHED_CONFIG_FILE
|
||||
targets = name.split('.')
|
||||
while targets:
|
||||
if not targets[0] in conf:
|
||||
log.debug(f"[config] unable to find target '{name}'")
|
||||
return None
|
||||
conf = conf[targets[0]]
|
||||
targets = targets[1:]
|
||||
return conf
|
||||
|
||||
# perform "write-only" request (update the configuration file)
|
||||
old = __setitem_dots(__CACHED_CONFIG_FILE, name, value)
|
||||
with open(__CACHED_CONFIG_PATH, "w", encoding='utf-8') as file:
|
||||
file.write(toml.dumps(__CACHED_CONFIG_FILE))
|
||||
|
||||
# return the previous information of the update field (if available)
|
||||
return old
|
||||
|
||||
|
||||
def _generate_value(name, val):
|
||||
if not val:
|
||||
return None
|
||||
while val.find('{') >= 0:
|
||||
if val.find('}') < 0:
|
||||
break
|
||||
key = val[val.find('{') + 1: val.find('}')]
|
||||
res = __config_control(key)
|
||||
if not res:
|
||||
log.warn(f"[config] {name} = {val} : unable to find '{key}'")
|
||||
return None
|
||||
val = val[:val.find('{')] + res + val[val.find('}') + 1:]
|
||||
return val
|
||||
|
||||
#---
|
||||
# Public functions
|
||||
#---
|
||||
|
||||
def config_get(key: str, default_value: str = None) -> str:
|
||||
""" Get configuration key/value
|
||||
|
||||
This function will try to find the key value of `key`. If the key doest not
|
||||
exists then None will be returned. You can specify a default value if the
|
||||
key doest not exist.
|
||||
|
||||
@args
|
||||
> key: string - the key name
|
||||
> default_value: string - the key default value if not found
|
||||
|
||||
@return
|
||||
> return the key value or None if not found nor default value set
|
||||
"""
|
||||
if ret := __config_control(key):
|
||||
return _generate_value(key, ret)
|
||||
default_value = _generate_value(key, default_value)
|
||||
if default_value:
|
||||
__config_control(key, default_value)
|
||||
return default_value
|
||||
|
||||
def config_set(key: str, value: str) -> str:
|
||||
""" Set configuration key = value
|
||||
|
||||
This function will try to update the user vxSDK configuration file to add
|
||||
key / value information. Note that the `value` can have placeholder in its
|
||||
content like `{path.sysroot}/superh` wich will fetch the 'path.sysroot'
|
||||
configuration key.
|
||||
|
||||
@args
|
||||
> key: string - the key name
|
||||
> name: string - the key value
|
||||
|
||||
@return
|
||||
> the old key value or None if new
|
||||
"""
|
||||
return __config_control(key, value)
|
||||
|
||||
def config_set_default(list_of_keyval: list):
|
||||
""" Set default key / value
|
||||
|
||||
This function will setup all default key value if the key doest not exists.
|
||||
This is usefull to centralise all default user configuration information in
|
||||
this file instead of in all project files.
|
||||
|
||||
@arg
|
||||
> list_of_keyval: list of tuple - [(key, value), ...]
|
||||
"""
|
||||
for key, value in list_of_keyval:
|
||||
if not __config_control(key):
|
||||
__config_control(key, value)
|
||||
|
||||
# workaround
|
||||
config_set_default(DEFAULT_CONFIG_KEYVAL)
|
|
@ -0,0 +1,68 @@
|
|||
"""
|
||||
Vhex converter module
|
||||
"""
|
||||
|
||||
from core.conv.asset import conv_assets_generate
|
||||
|
||||
__all__ = [
|
||||
'assets_generate'
|
||||
]
|
||||
|
||||
def assets_generate(prefix_assets, prefix_src):
|
||||
r"""Generate Vhex assets.
|
||||
|
||||
This function abstract the asset convertion for the Vhex Operating System.
|
||||
It will walk througt the `<source_prefix>` folder and will try to find some
|
||||
files named 'vxconv.txt' wich discribe assets information of a potential
|
||||
project. Then it will use them to convert assets into an appropriate source
|
||||
file in C without using any Vhex-specific function (so, you can use this
|
||||
converter for other project).
|
||||
|
||||
The vxconv.txt file is structured like basic key/value file:
|
||||
|
||||
```
|
||||
<exposed_symbols_name>:
|
||||
type: <image type> (font, bitmap) - required
|
||||
path: <image path> - required
|
||||
...
|
||||
|
||||
<next_exposed_symbols_name>:
|
||||
...
|
||||
```
|
||||
|
||||
Each asset file description should have at least type and name information,
|
||||
and each type have potentially its own requierements.
|
||||
|
||||
type = bitmap:
|
||||
================================== =========================================
|
||||
Keys name and value type Description
|
||||
================================== =========================================
|
||||
profile: <name> Select the bitmap pixel profile
|
||||
| rgb4 | RGB 4 (indexed)
|
||||
| rgb4a | RGBA 4 (indexed)
|
||||
| rgb8 | RGB 8 (indexed)
|
||||
| rgb8a | RGBA 8 (indexed)
|
||||
| rgb16 | RGB 16 (5:R, 6:G, 5:B)
|
||||
| rgb16a | RGBA 16 (5:R, 5:G, 5:B, 1:A)
|
||||
================================== =========================================
|
||||
|
||||
type = font:
|
||||
================================== =========================================
|
||||
Keys name and value type Description
|
||||
================================== =========================================
|
||||
grid.size: 8x9 (widthxheight) caracter size in pixel
|
||||
grid.padding: <pixel> space between caracter
|
||||
grig.border: <pixel> space around grid
|
||||
proportional: <true,false> caracter are cropped
|
||||
line_height: <pixel> caracter line alignement
|
||||
charset: <print,unicode> charset specification
|
||||
================================== =========================================
|
||||
|
||||
@args:
|
||||
> path (str) - the path to find assets
|
||||
> source_prefix (str) - the path to generate image source file
|
||||
|
||||
@return:
|
||||
> a list of string which represents all assets sources files path
|
||||
"""
|
||||
return conv_assets_generate(prefix_assets, prefix_src)
|
|
@ -0,0 +1,67 @@
|
|||
from core.conv.pixel import rgba8conv
|
||||
|
||||
from PIL import Image
|
||||
import os
|
||||
|
||||
__all__ = [
|
||||
'generate_addin'
|
||||
]
|
||||
|
||||
|
||||
def generate_addin(binary, icon=None, name=None, output=None, version=None):
|
||||
r"""Generate an addin for the Vhex Operating System.
|
||||
|
||||
The addin name (passed through the `name` argument) is optional here. In
|
||||
this case, the name will use the internal name...which can be guessed using
|
||||
the binary name (e.i '/path/to/the/super_addin.elf' -> internal name =
|
||||
'super_addin' -> output name = '/path/to/the/super_addin').
|
||||
|
||||
The output path for the generated file is, by defautl, the same path that
|
||||
the binary but the suffix '.vxos' will be added.
|
||||
|
||||
if the icon is not specified, a default blank icon will be used.
|
||||
|
||||
Args:
|
||||
> binary (str) - binary path
|
||||
> icon (str) - addin icon path (optional)
|
||||
> name (str) - addin name (displayed in the menu) (optional)
|
||||
> output (str) - output path for the generated addin (optional)
|
||||
|
||||
FIXME:
|
||||
> generate default internal name
|
||||
> change 8-bits icon into rgb565
|
||||
> add internal addin version in the header
|
||||
"""
|
||||
if not os.path.exists(binary):
|
||||
logger(LOG_ERR, 'binary path is invalid')
|
||||
sys.exit(84)
|
||||
if icon and not os.path.exists(icon):
|
||||
logger(LOG_WARN, f'{icon}: icon does not exists, ignored')
|
||||
icon = None
|
||||
if not name:
|
||||
name = ''
|
||||
if not output:
|
||||
output = binary + '.vxos'
|
||||
|
||||
if icon:
|
||||
bitmap = Image.open(icon)
|
||||
if bitmap.size != (92, 64):
|
||||
logger(
|
||||
LOG_ERR,
|
||||
f'{icon}:icon size does not match {bitmap.size} != (92, 64)',
|
||||
exit=84
|
||||
)
|
||||
|
||||
with open(binary, 'rb') as b:
|
||||
with open(output, 'wb') as a:
|
||||
a.write(b'VHEX')
|
||||
a.write(name.encode('utf8'))
|
||||
a.write(b'\x00')
|
||||
if icon:
|
||||
for pixel in bitmap.getdata():
|
||||
a.write(rgba8conv(pixel).to_bytes(1, 'big'))
|
||||
else:
|
||||
a.write(bytes(92*64))
|
||||
a.write(b.read())
|
||||
|
||||
return 0
|
|
@ -0,0 +1,126 @@
|
|||
"""
|
||||
Vhex assets converter
|
||||
"""
|
||||
import os
|
||||
import toml
|
||||
|
||||
from core.logger import log
|
||||
from core.conv.type.font import conv_font_generate
|
||||
from core.conv.type.image import conv_image_generate
|
||||
|
||||
|
||||
__all__ = [
|
||||
'conv_assets_generate'
|
||||
]
|
||||
|
||||
|
||||
#---
|
||||
# Private
|
||||
#---
|
||||
|
||||
class _VxAsset():
|
||||
"""Represent a asset object
|
||||
|
||||
This is an internal class which represents assets information with some
|
||||
methods to abstract conversion and file type manipulation (for asset type
|
||||
font and asset type bitmap).
|
||||
|
||||
Also note that this class is private because we use a tricky optimization to
|
||||
parse the `vxconv.txt` file, this is why we have no "private" property with
|
||||
setter and getter, and why this class is "hidden".
|
||||
|
||||
Some important methods to note:
|
||||
|
||||
================================== =========================================
|
||||
Name Description
|
||||
================================== =========================================
|
||||
generate() Generate the source file (C)
|
||||
================================== =========================================
|
||||
|
||||
"""
|
||||
def __init__(self, prefix, name, meta):
|
||||
if 'path' not in meta:
|
||||
raise Exception(f"[{name}] missing required path information")
|
||||
if 'type' not in meta:
|
||||
raise Exception(f"[{name}] missing required type information")
|
||||
if meta['type'] not in ['font', 'image']:
|
||||
raise Exception(f"asset type '{meta[type]}' is not known")
|
||||
|
||||
self._name = name
|
||||
self._meta = meta
|
||||
self._type = meta['type']
|
||||
self._path = prefix + '/' + meta['path']
|
||||
if not os.path.exists(self.path):
|
||||
raise Exception("asset path '{self._path}' cannot be openned")
|
||||
|
||||
def __repr__(self):
|
||||
return f'<_VxAssetObj, {self.name}>'
|
||||
|
||||
def __str__(self):
|
||||
content = f"[{self.name}]\n"
|
||||
content += f" - type: {self.type}\n"
|
||||
content += f" - path: {self.path}\n"
|
||||
return content
|
||||
|
||||
#---
|
||||
# Getter
|
||||
#---
|
||||
|
||||
@property
|
||||
def path(self):
|
||||
"""<property> path"""
|
||||
return self._path
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""<property> name"""
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def type(self):
|
||||
"""<property> type"""
|
||||
return self._type
|
||||
|
||||
@property
|
||||
def meta(self):
|
||||
"""<property> meta"""
|
||||
return self._meta
|
||||
|
||||
#---
|
||||
# Public method
|
||||
#---
|
||||
|
||||
def generate_source_file(self, prefix_output):
|
||||
"""generate source file """
|
||||
if self.type == 'font':
|
||||
return conv_font_generate(self, prefix_output)
|
||||
return conv_image_generate(self, prefix_output)
|
||||
|
||||
|
||||
#---
|
||||
# Public
|
||||
#---
|
||||
|
||||
def conv_assets_generate(prefix_assets, prefix_output):
|
||||
""" Walk through the assets prefix and generate all source file
|
||||
|
||||
@args
|
||||
> prefix_asset (str) - prefix used for recursivly search for asset info
|
||||
> prefix_output (str) - prefix used for the output of generated file
|
||||
|
||||
@return
|
||||
> a list of all generated sources pathname
|
||||
"""
|
||||
if not os.path.exists(prefix_output):
|
||||
os.makedirs(prefix_output)
|
||||
generated = []
|
||||
for root, _, files in os.walk(prefix_assets):
|
||||
if not 'vxconv.toml' in files:
|
||||
continue
|
||||
with open(root + '/vxconv.toml', "r", encoding='utf-8') as inf:
|
||||
content = toml.loads(inf.read())
|
||||
for asset_name in content:
|
||||
log.user(f"converting {asset_name}...")
|
||||
asset = _VxAsset(root, asset_name, content[asset_name])
|
||||
generated += asset.generate_source_file(prefix_output)
|
||||
return generated
|
|
@ -0,0 +1,45 @@
|
|||
""" Pixel converter utilities
|
||||
|
||||
This file expose many 32 bits RGBA into various pixel format
|
||||
"""
|
||||
|
||||
__all__ = [
|
||||
'rgb1conv',
|
||||
'rgb8conv',
|
||||
'rgba8conv',
|
||||
'rgb16conv',
|
||||
'rgba16conv'
|
||||
]
|
||||
|
||||
def rgb24to16(rgb):
|
||||
_r = (rgb[0] & 0xff) >> 3
|
||||
_g = (rgb[1] & 0xff) >> 2
|
||||
_b = (rgb[2] & 0xff) >> 3
|
||||
return (_r << 11) | (_g << 5) | _b
|
||||
|
||||
|
||||
|
||||
def rgb1conv(pixel):
|
||||
return pixel == (0, 0, 0)
|
||||
|
||||
def rgb8conv(pixel):
|
||||
return int((pixel[0] * 7) / 255) << 5 \
|
||||
| int((pixel[1] * 4) / 255) << 3 \
|
||||
| int((pixel[2] * 7) / 255) << 0
|
||||
|
||||
def rgba8conv(pixel):
|
||||
return int((pixel[0] * 4) / 256) << 6 \
|
||||
| int((pixel[1] * 8) / 256) << 3 \
|
||||
| int((pixel[2] * 4) / 256) << 1 \
|
||||
| (len(pixel) >= 4 and pixel[3] == 0)
|
||||
|
||||
def rgb16conv(pixel):
|
||||
return int((pixel[0] * 31) / 255) << 11 \
|
||||
| int((pixel[1] * 63) / 255) << 5 \
|
||||
| int((pixel[2] * 31) / 255) << 0
|
||||
|
||||
def rgba16conv(pixel):
|
||||
return int((pixel[0] * 31) / 255) << 11 \
|
||||
| int((pixel[1] * 63) / 255) << 6 \
|
||||
| int((pixel[2] * 31) / 255) << 1 \
|
||||
| (pixel[3] != 0)
|
|
@ -0,0 +1,466 @@
|
|||
"""
|
||||
Vhex font converter
|
||||
"""
|
||||
|
||||
from PIL import Image
|
||||
|
||||
from core.logger import log
|
||||
|
||||
__all__ = [
|
||||
'conv_font_generate'
|
||||
]
|
||||
|
||||
#---
|
||||
# Internal vxconv.tmol handling routines
|
||||
#---
|
||||
|
||||
def __font_fetch_info(asset):
|
||||
""" Check and fetch font information
|
||||
|
||||
@arg
|
||||
> asset (VxAsset) - asset information
|
||||
|
||||
@return
|
||||
> dictionary with font information
|
||||
"""
|
||||
# generate font default information
|
||||
font_info = {
|
||||
# user can customise
|
||||
'charset' : 'normal',
|
||||
'grid_size_x' : 0,
|
||||
'grid_size_y' : 0,
|
||||
'grid_padding' : 1,
|
||||
'grid_border' : 1,
|
||||
'is_proportional' : False,
|
||||
'line_height' : 0,
|
||||
'char_spacing' : 1,
|
||||
|
||||
# generated "on-the-fly" by the conversion step
|
||||
# @notes
|
||||
# This is mainly to provide cache for the Vhex operating system to
|
||||
# speed-up render calculation by avoiding recurent caculation.
|
||||
'glyph_size' : 0,
|
||||
'glyph_height' : 0,
|
||||
'font_size' : 0,
|
||||
'data' : []
|
||||
}
|
||||
|
||||
# handle user meta-indication
|
||||
if 'charset' in asset.meta:
|
||||
if asset.meta['charset'] not in ['default', 'unicode']:
|
||||
log.error(f"Unknown charset '{asset.meta['charset']}', abord")
|
||||
return None
|
||||
font_info['charset'] = asset.meta['charset']
|
||||
if 'grid_size' not in asset.meta:
|
||||
log.error("Missing critical grid size information, abord")
|
||||
return None
|
||||
grid_size = asset.meta['grid_size'].split('x')
|
||||
font_info['grid_size_x'] = int(grid_size[0])
|
||||
font_info['grid_size_y'] = int(grid_size[1])
|
||||
if 'grid_padding' in asset.meta:
|
||||
font_info['grid_padding'] = int(asset.meta['grid_padding'])
|
||||
if 'grid_border' in asset.meta:
|
||||
font_info['grid_border'] = int(asset.meta['grid_border'])
|
||||
if 'proportional' in asset.meta:
|
||||
font_info['is_proportional'] = asset.meta['proportional']
|
||||
font_info['line_height'] = font_info['grid_size_y']
|
||||
if 'line_height' in asset.meta:
|
||||
font_info['line_height'] = asset.meta['line_height']
|
||||
if 'char_spacing' in asset.meta:
|
||||
font_info['char_spacing'] = asset.meta['char_spacing']
|
||||
font_info['glyph_height'] = font_info['grid_size_y']
|
||||
|
||||
# return font information
|
||||
return font_info
|
||||
|
||||
#---
|
||||
# Internal glyph routines
|
||||
#---
|
||||
|
||||
def __glyph_get_wgeometry(geometry_info, img_raw, img_size, pos, grid_size):
|
||||
""" Generate glyph width geometry information
|
||||
|
||||
@args
|
||||
> geometry_info (dict) - geometry information
|
||||
> img_raw (list) - list of all pixel of the image
|
||||
> img_size (tuple) - image width and image height
|
||||
> pos (tuple) - glyph position information (X and Y in pixel)
|
||||
> grid_size (tuple) - glyph grid size information (width and height)
|
||||
|
||||
@return
|
||||
> Nothing
|
||||
"""
|
||||
|
||||
geometry_info['wstart'] = -1
|
||||
geometry_info['wend'] = -1
|
||||
|
||||
_px = pos[0]
|
||||
_py = pos[1]
|
||||
log.debug(f'[geometry] X:{pos[0]} Y:{int(pos[1]/img_size[0])}')
|
||||
log.debug(f' - grid_size = {grid_size}')
|
||||
for _ in range(0, grid_size[1]):
|
||||
for offx in range(0, grid_size[0]):
|
||||
if img_raw[_py + (_px + offx)][:3] == (255, 255, 255):
|
||||
continue
|
||||
if geometry_info['wstart'] < 0 or offx < geometry_info['wstart']:
|
||||
geometry_info['wstart'] = offx
|
||||
if geometry_info['wstart'] < 0 or offx > geometry_info['wend']:
|
||||
geometry_info['wend'] = offx
|
||||
_py += img_size[0]
|
||||
geometry_info['wend'] += 1
|
||||
log.debug(f' - geometry = {geometry_info}')
|
||||
|
||||
def __glyph_encode(data_info, img_info, geometry, posx, posy):
|
||||
""" Encode glyph bitmap
|
||||
|
||||
@args
|
||||
> data_info (dict) - internal data information (list, index and shift)
|
||||
> img_info (dict) - image-related information (object and raw content)
|
||||
> geometry (dict) - geometry information
|
||||
> posx (int) - X-axis position in pixel
|
||||
> posy (int) - Y-axis position in pixel
|
||||
|
||||
@return
|
||||
> Nothing
|
||||
"""
|
||||
# fetch information
|
||||
img = img_info['obj']
|
||||
img_raw = img_info['raw']
|
||||
data = data_info['table']
|
||||
data_idx = data_info['idx']
|
||||
data_shift = data_info['shift']
|
||||
wstart = geometry['wstart']
|
||||
wend = geometry['wend']
|
||||
|
||||
# encode the glyph
|
||||
yoff = 0
|
||||
log.debug(f'[encode] X:{posx} Y:{int(posy/img.size[0])}')
|
||||
for _h in range(geometry['hstart'], geometry['hend']):
|
||||
for _w in range(wstart, wend):
|
||||
if img_raw[(posy + yoff) + (posx + _w)][:3] == (0, 0, 0):
|
||||
log.debug('#', end='')
|
||||
data[data_idx] |= 0x80000000 >> data_shift
|
||||
else:
|
||||
log.debug('.', end='')
|
||||
data[data_idx] &= ~(0x80000000 >> data_shift)
|
||||
if (data_shift := data_shift + 1) >= 32:
|
||||
data_shift = 0
|
||||
data_idx += 1
|
||||
log.debug('')
|
||||
yoff += img.size[0]
|
||||
|
||||
# commit modification
|
||||
data_info['idx'] = data_idx
|
||||
data_info['shift'] = data_shift
|
||||
|
||||
#---
|
||||
# Intenal font conversion
|
||||
#---
|
||||
|
||||
def __font_convert_proportional(packed_info):
|
||||
""" Generate proportional font
|
||||
|
||||
Proportional font means that each character have its own width size (but
|
||||
have a common height). We need to performs more complexe handling than the
|
||||
monospaced one.
|
||||
|
||||
@args
|
||||
> asset (VxAsset) - asset information
|
||||
> font_information (dict) - font indication
|
||||
|
||||
@return
|
||||
> 0 if success, negative value otherwise
|
||||
"""
|
||||
# unpack information
|
||||
font_info = packed_info[0]
|
||||
img_info = packed_info[1]
|
||||
glyph_info = packed_info[2]
|
||||
data_info = packed_info[4]
|
||||
geometry_info = packed_info[5]
|
||||
|
||||
# isolate needed information
|
||||
img = img_info['obj']
|
||||
img_raw = img_info['raw']
|
||||
nb_col = packed_info[3][0]
|
||||
nb_row = packed_info[3][1]
|
||||
gwidth = glyph_info[0]
|
||||
gheight = glyph_info[1]
|
||||
|
||||
# main loop, walk glyph per glyph
|
||||
_py = (font_info['grid_border'] + font_info['grid_padding']) * img.size[0]
|
||||
for _ in range(0, nb_row):
|
||||
_px = font_info['grid_border'] + font_info['grid_padding']
|
||||
for _ in range(0, nb_col):
|
||||
# generate width geometry information
|
||||
__glyph_get_wgeometry(
|
||||
geometry_info,
|
||||
img_raw,
|
||||
img.size,
|
||||
(_px, _py),
|
||||
(font_info['grid_size_x'], font_info['grid_size_y'])
|
||||
)
|
||||
|
||||
# save critical glyph geometry information that will be encoded in
|
||||
# the final C source file
|
||||
font_info['glyph_props'].append((
|
||||
geometry_info['wend'] - geometry_info['wstart'],
|
||||
data_info['idx'],
|
||||
data_info['shift']
|
||||
))
|
||||
|
||||
# encode glyph information
|
||||
__glyph_encode(data_info, img_info, geometry_info, _px, _py)
|
||||
|
||||
# update loop information
|
||||
font_info['glyph_count'] += 1
|
||||
_px += gwidth
|
||||
_py += gheight * img.size[0]
|
||||
return 0
|
||||
|
||||
def __font_convert_monospaced(packed_info):
|
||||
""" Generate proportional font
|
||||
|
||||
Proportional font means that each character have its own width size (but
|
||||
have a common height). We need to performs more complexe handling than the
|
||||
monospaced one.
|
||||
|
||||
@args
|
||||
> asset (VxAsset) - asset information
|
||||
> font_information (dict) - font indication
|
||||
|
||||
@return
|
||||
> 0 if success, negative value otherwise
|
||||
"""
|
||||
# unpack information
|
||||
font_info = packed_info[0]
|
||||
img_info = packed_info[1]
|
||||
glyph_info = packed_info[2]
|
||||
grid_info = packed_info[3]
|
||||
data_info = packed_info[4]
|
||||
geometry_info = packed_info[5]
|
||||
|
||||
# isolate needed information
|
||||
img = img_info['obj']
|
||||
nb_row = grid_info[1]
|
||||
nb_col = grid_info[0]
|
||||
gwidth = glyph_info[0]
|
||||
gheight = glyph_info[1]
|
||||
|
||||
# main loop, walk glyph per glyph
|
||||
_py = (font_info['grid_border'] + font_info['grid_padding']) * img.size[0]
|
||||
for _ in range(0, nb_row):
|
||||
_px = font_info['grid_border'] + font_info['grid_padding']
|
||||
for _ in range(0, nb_col):
|
||||
__glyph_encode(data_info, img_info, geometry_info, _px, _py)
|
||||
font_info['glyph_count'] += 1
|
||||
_px += gwidth
|
||||
_py += gheight * img.size[0]
|
||||
return 0
|
||||
|
||||
def __font_convert(asset, font_info):
|
||||
""" Generate font information
|
||||
|
||||
@args
|
||||
> asset (VxAsset) - asset information
|
||||
> font_info (dict) - font information
|
||||
|
||||
@return
|
||||
> 0 if success, negative value otherwise
|
||||
"""
|
||||
# generate image information
|
||||
img = Image.open(asset.path)
|
||||
img_raw = img.getdata()
|
||||
img_info = {
|
||||
'obj' : img,
|
||||
'raw' : img_raw
|
||||
}
|
||||
|
||||
# pre-calculate the "real" glyph width and height using padding information
|
||||
glyph_info = [0, 0]
|
||||
glyph_info[0] = font_info['grid_size_x'] + font_info['grid_padding']
|
||||
glyph_info[1] = font_info['grid_size_y'] + font_info['grid_padding']
|
||||
gheight = glyph_info[1]
|
||||
gwidth = glyph_info[0]
|
||||
log.debug(f"gwidth = {gwidth} && gheight = {gheight}")
|
||||
|
||||
# pre-calculate the number of row and column of the font
|
||||
grid_info = [0, 0]
|
||||
grid_info[0] = int((img.size[0] - (font_info['grid_border'] * 2)) / gwidth)
|
||||
grid_info[1] = int((img.size[1] - (font_info['grid_border'] * 2)) / gheight)
|
||||
nb_col = grid_info[0]
|
||||
nb_row = grid_info[1]
|
||||
log.debug(f"nb_row = {nb_row} && nb_col = {nb_col}")
|
||||
|
||||
# pre-calculate and prepare per-glyph information
|
||||
# @note
|
||||
# The generated data is designed for 4-alignement padding. This to have
|
||||
# speed-up on drawing function.
|
||||
font_info['glyph_size'] = font_info['grid_size_x'] * font_info['grid_size_y']
|
||||
font_info['font_size'] = font_info['glyph_size'] * nb_row * nb_col
|
||||
font_info['glyph_count'] = 0
|
||||
font_info['glyph_props'] = []
|
||||
font_info['data'] = [0] * int((font_info['font_size'] + 31) / 32)
|
||||
log.debug(f"data original = {id(font_info['data'])}")
|
||||
|
||||
# generate data information
|
||||
data_info = {
|
||||
'table' : font_info['data'],
|
||||
'idx' : 0,
|
||||
'shift' : 0
|
||||
}
|
||||
log.debug(f"data packed = {id(data_info['table'])}")
|
||||
|
||||
# generate geometry information
|
||||
geometry_info = {
|
||||
'hstart' : 0,
|
||||
'hend' : font_info['grid_size_y'],
|
||||
'wstart' : 0,
|
||||
'wend' : font_info['grid_size_x'],
|
||||
}
|
||||
|
||||
# select the converter
|
||||
converter = __font_convert_monospaced
|
||||
if font_info['is_proportional']:
|
||||
converter = __font_convert_proportional
|
||||
|
||||
# convert font
|
||||
converter((
|
||||
font_info,
|
||||
img_info,
|
||||
glyph_info,
|
||||
grid_info,
|
||||
data_info,
|
||||
geometry_info
|
||||
))
|
||||
|
||||
log.debug(f"data packed end = {id(data_info['table'])}")
|
||||
return 0
|
||||
|
||||
#---
|
||||
# Source file generation
|
||||
#---
|
||||
|
||||
def __font_generate_unicode_source(_):
|
||||
"""Unicode special chaset directory"""
|
||||
log.error("unicode conversion not implemented yet o(x_x)o")
|
||||
return ''
|
||||
|
||||
def __font_generate_normal_source(font_info):
|
||||
"""Print chaset is a image file
|
||||
|
||||
"""
|
||||
content = "\t.glyph = {\n"
|
||||
content += f"\t\t.height = {font_info['glyph_height']},\n"
|
||||
content += f"\t\t.line_height = {font_info['line_height']},\n"
|
||||
|
||||
# encode font bitmap
|
||||
line = 0
|
||||
log.debug(f"data = {font_info['data']}")
|
||||
content += "\t\t.data = (uint32_t[]){\n"
|
||||
for pixel in font_info['data']:
|
||||
if line == 0:
|
||||
content += '\t\t\t'
|
||||
if line >= 1:
|
||||
content += ' '
|
||||
content += f"{pixel:#010x},"
|
||||
if (line := line + 1) == 4:
|
||||
content += '\n'
|
||||
line = 0
|
||||
if line != 0:
|
||||
content += '\n'
|
||||
content += '\t\t},\n'
|
||||
|
||||
# indicate the number of glyph in the bitmap
|
||||
content += f"\t\t.count = {font_info['glyph_count']},\n"
|
||||
|
||||
# encode proportional information if needed
|
||||
if font_info['is_proportional']:
|
||||
content += '\t\t.prop = (struct __workaround[]){\n'
|
||||
for prop in font_info['glyph_props']:
|
||||
content += "\t\t\t{\n"
|
||||
content += f"\t\t\t\t.width = {prop[0]},\n"
|
||||
content += f"\t\t\t\t.index = {prop[1]},\n"
|
||||
content += f"\t\t\t\t.shift = {prop[2]},\n"
|
||||
content += "\t\t\t},\n"
|
||||
else:
|
||||
content += "\t\t.mono = {,\n"
|
||||
content += f"\t\t\t.width = {font_info['glyph_width']},\n"
|
||||
content += f"\t\t\t.size = {font_info['glyph_size']},\n"
|
||||
content += "\t\t},\n"
|
||||
content += "\t},\n"
|
||||
|
||||
# skip unicode struct
|
||||
content += "\t.unicode = {\n"
|
||||
content += "\t\t.blocks = NULL,\n"
|
||||
content += "\t\t.block_count = 0,\n"
|
||||
content += "\t}\n"
|
||||
return content
|
||||
|
||||
def __font_generate_source_file(asset, font_info):
|
||||
"""Generate font source file content
|
||||
|
||||
@args
|
||||
> asset (VxAsset) - asset information
|
||||
> info (dict) - hold font information
|
||||
|
||||
@return
|
||||
> file C content string
|
||||
"""
|
||||
# generate basic header
|
||||
content = "#include <vhex/display/font.h>\n"
|
||||
content += "\n"
|
||||
content += f"/* {asset.name} - Vhex asset\n"
|
||||
content += " This object has been converted by using the vxSDK "
|
||||
content += "converter */\n"
|
||||
content += f"struct font const {asset.name} = " + "{\n"
|
||||
content += f"\t.name = \"{asset.name}\",\n"
|
||||
|
||||
# shape information
|
||||
content += "\t.shape = {\n"
|
||||
content += "\t\t.bold = 0,\n"
|
||||
content += "\t\t.italic = 0,\n"
|
||||
content += "\t\t.serif = 0,\n"
|
||||
content += "\t\t.mono = 0,\n"
|
||||
content += f"\t\t.prop = {int(font_info['is_proportional'])},\n"
|
||||
content += "\t},\n"
|
||||
|
||||
# manage display indication
|
||||
content += f"\t.char_spacing = {font_info['char_spacing']},\n"
|
||||
|
||||
# handle special charset behaviour
|
||||
if font_info['charset'] == 'unicode':
|
||||
content += __font_generate_unicode_source(font_info)
|
||||
else:
|
||||
content += __font_generate_normal_source(font_info)
|
||||
|
||||
# closure and return
|
||||
content += '};\n'
|
||||
return content
|
||||
|
||||
#---
|
||||
# Public
|
||||
#---
|
||||
|
||||
def conv_font_generate(asset, prefix_output):
|
||||
""" Convert an image asset to a C source file
|
||||
|
||||
@args
|
||||
> asset (_VxAsset) - minimal asset information
|
||||
> prefix_output (str) - prefix for source file generation
|
||||
|
||||
@return
|
||||
> pathname of the generated file
|
||||
"""
|
||||
# generate font information
|
||||
if not (font_info := __font_fetch_info(asset)):
|
||||
return ''
|
||||
if __font_convert(asset, font_info) != 0:
|
||||
return ''
|
||||
content = __font_generate_source_file(asset, font_info)
|
||||
|
||||
# create the source file
|
||||
asset_src = f'{prefix_output}/{asset.name}_vxfont.c'
|
||||
with open(asset_src, "w", encoding='utf8') as file:
|
||||
file.write(content)
|
||||
log.debug(f"source file generated at {asset_src}")
|
||||
return asset_src
|
|
@ -0,0 +1,397 @@
|
|||
"""
|
||||
Vhex image converter
|
||||
"""
|
||||
|
||||
from PIL import Image
|
||||
|
||||
from core.logger import log
|
||||
from core.conv.pixel import rgb24to16
|
||||
|
||||
__all__ = [
|
||||
'conv_image_generate'
|
||||
]
|
||||
|
||||
#---
|
||||
# Private profile color management
|
||||
#---
|
||||
|
||||
def __profile_gen(profile, name, palette=None, alpha=None):
|
||||
r""" Internal image profile class
|
||||
|
||||
================================== =========================================
|
||||
Property Description
|
||||
================================== =========================================
|
||||
id (int) profile ID
|
||||
names (array of str) list all profile names
|
||||
format (str) profile format name (vhex API)
|
||||
has_alpha (bool) indicate if the profil has alpha
|
||||
alpha (int) alpha index in the palette (or mask)
|
||||
is_indexed (bool) indicate if the profile should be indexed
|
||||
palette_base (int) indicate base index for color inserting
|
||||
palette_color_count (int) indicate the number of color (palette)
|
||||
palette_trim (bool) indicate if the palette should be trimed
|
||||
================================== =========================================
|
||||
"""
|
||||
profile = {
|
||||
'profile' : profile,
|
||||
'name' : name,
|
||||
'has_alpha' : (alpha is not None),
|
||||
'alpha' : alpha,
|
||||
'is_indexed': (palette is not None),
|
||||
'palette' : None
|
||||
}
|
||||
if palette is not None:
|
||||
profile['palette_base'] = palette[0]
|
||||
profile['palette_color_count'] = palette[1]
|
||||
profile['palette_trim'] = palette[2]
|
||||
return profile
|
||||
|
||||
# all supported profile information
|
||||
VX_PROFILES = [
|
||||
__profile_gen('IMAGE_RGB565', "p16"),
|
||||
__profile_gen('IMAGE_RGB565A', "p16a", alpha=0x0001),
|
||||
__profile_gen('IMAGE_P8_RGB565', "p8", palette=(0,256,True)),
|
||||
__profile_gen('IMAGE_P8_RGB565A', "p8a", palette=(1,256,True), alpha=0),
|
||||
__profile_gen('IMAGE_P4_RGB565', "p4", palette=(0,16,False)),
|
||||
__profile_gen('IMAGE_P4_RGB565A', "p4a", palette=(1,16,False), alpha=0),
|
||||
]
|
||||
|
||||
def __profile_find(name):
|
||||
"""Find a profile by name."""
|
||||
for profile in VX_PROFILES:
|
||||
if name == profile['name']:
|
||||
return profile
|
||||
return None
|
||||
|
||||
#---
|
||||
# Private image manipulation
|
||||
#---
|
||||
|
||||
def __image_isolate_alpha(info):
|
||||
""" Isolate alpha corlor of the image
|
||||
|
||||
Vhex use a particular handling for alpha color and this information should
|
||||
use a strict encoding way. Things that Pillow don't do properly. So, lets
|
||||
manually setup our alpha isolation and patch Pillow alpha palette handling.
|
||||
|
||||
@args
|
||||
> info (dict) - contains all needed information (image, data, ...)
|
||||
|
||||
@return
|
||||
> Nothing
|
||||
"""
|
||||
# fetch needed information
|
||||
img = info['img']
|
||||
profile = info['profile']
|
||||
|
||||
# Save the alpha channel and make it 1-bit. We need to do this because
|
||||
# the alpha value is handled specialy in Vhex and the image conversion
|
||||
# to palette-oriented image is weird : the alpha colors is also converted
|
||||
# in the palette
|
||||
if profile['has_alpha']:
|
||||
alpha_channel = img.getchannel("A").convert("1", dither=Image.NONE)
|
||||
else:
|
||||
alpha_channel = Image.new("1", img.size, 1)
|
||||
|
||||
alpha_pixels = alpha_channel.load()
|
||||
img = img.convert("RGB")
|
||||
|
||||
# Transparent pixels have random values on the RGB channels, causing
|
||||
# them to use up palette entries during quantization. To avoid that, set
|
||||
# their RGB data to a color used somewhere else in the image.
|
||||
pixels = img.load()
|
||||
bg_color = next(
|
||||
(
|
||||
pixels[x,y]
|
||||
for x in range(img.width)
|
||||
for y in range(img.height)
|
||||
if alpha_pixels[x,y] > 0
|
||||
),
|
||||
(0,0,0)
|
||||
)
|
||||
for _y in range(img.height):
|
||||
for _x in range(img.width):
|
||||
if alpha_pixels[_x, _y] == 0:
|
||||
pixels[_x, _y] = bg_color
|
||||
|
||||
# update external information
|
||||
info['img'] = img
|
||||
info['img_pixel_list_alpha'] = alpha_pixels
|
||||
info['img_pixel_list_clean'] = pixels
|
||||
|
||||
def __image_encode_palette(info):
|
||||
""" Generate palette information
|
||||
|
||||
This routine is involved only if the targeted profile is indexed. We need to
|
||||
generate (and isolate) color palette.
|
||||
|
||||
@args
|
||||
> info (dict) - contains all needed information (image, data, ...)
|
||||
|
||||
@return
|
||||
> Nothing
|
||||
"""
|
||||
# fetch needed information
|
||||
img = info['img']
|
||||
profile = info['profile']
|
||||
|
||||
# convert image into palette format
|
||||
# note: we remove one color slot in the palette for the alpha one
|
||||
color_count = profile['palette_color_count'] - int(profile['has_alpha'])
|
||||
img = img.convert(
|
||||
'P',
|
||||
dither=Image.NONE,
|
||||
palette=Image.ADAPTIVE,
|
||||
colors=color_count
|
||||
)
|
||||
|
||||
# The palette format is a list of N triplets ([r, g, b, ...]). But,
|
||||
# sometimes, colors after img.convert() are not numbered 0 to
|
||||
# `color_count`, because the palette don't need to be that big. So,
|
||||
# we calculate the "palette size" by walking throuth the bitmap and
|
||||
# by saving the biggest index used.
|
||||
pixels = img.load()
|
||||
nb_triplet = 1 + max(
|
||||
pixels[x,y]
|
||||
for y in range(img.height)
|
||||
for x in range(img.width)
|
||||
)
|
||||
palette = img.getpalette()[:3 * nb_triplet]
|
||||
palette = list(zip(palette[::3], palette[1::3], palette[2::3]))
|
||||
|
||||
# For formats with transparency, add an "unused" palette slot which
|
||||
# will used has pink/purple in case of a bad application try to use
|
||||
# this value anyway
|
||||
if profile['has_alpha']:
|
||||
palette = [(255, 0, 255)] + palette
|
||||
nb_triplet += 1
|
||||
|
||||
# Also keep track of how to remap indices from the values generated
|
||||
# by img.convert() into the palette, which is shifted by 1 due to
|
||||
# alpha and also starts at profile.palette_base.
|
||||
#
|
||||
# Note: profile.palette_base already starts 1 value later for
|
||||
# formats with alpha.
|
||||
palette_map = [
|
||||
(profile['palette_base'] + i) % profile['palette_color_count']
|
||||
for i in range(nb_triplet)
|
||||
]
|
||||
|
||||
# Encode the palette
|
||||
palette_color_count = nb_triplet
|
||||
if not profile['palette_trim']:
|
||||
palette_color_count = profile['palette_color_count']
|
||||
|
||||
palette_data = [0] * palette_color_count
|
||||
for i, rgb24 in enumerate(palette):
|
||||
palette_data[i] = rgb24to16(rgb24)
|
||||
|
||||
# update internal information
|
||||
info['palette_map'] = palette_map
|
||||
info['palette_data'] = palette_data
|
||||
info['palette_color_count'] = palette_color_count
|
||||
info['nb_triplet'] = nb_triplet
|
||||
info['img_pixel_list_clean'] = pixels
|
||||
|
||||
def __image_encode_bitmap(info):
|
||||
""" Encode the bitmap
|
||||
|
||||
This routine will generate the main data list which will contains the bitmap
|
||||
using Vhex-specific encoding.
|
||||
|
||||
@args
|
||||
> info (dict) - contains all needed information (image, data, ...)
|
||||
|
||||
@return
|
||||
> Nothing
|
||||
"""
|
||||
# fetch needed information
|
||||
img = info['img']
|
||||
profile = info['profile']
|
||||
alpha_pixels = info['img_pixel_list_alpha']
|
||||
pixels = info['img_pixel_list_clean']
|
||||
palette_map = info['palette_map']
|
||||
|
||||
# generate profile-specific geometry information
|
||||
if profile['name'] in ['p16', 'p16a']:
|
||||
# Preserve alignment between rows by padding to 4 bytes
|
||||
nb_stride = ((img.width + 1) // 2) * 4
|
||||
data_size = (nb_stride * img.height) * 2
|
||||
elif profile['name'] in ['p8', 'p8a']:
|
||||
nb_stride = img.width
|
||||
data_size = img.width * img.height
|
||||
else:
|
||||
# Pad whole bytes
|
||||
nb_stride = (img.width + 1) // 2
|
||||
data_size = nb_stride * img.height
|
||||
|
||||
# Generate the real data map
|
||||
data = [0] * data_size
|
||||
|
||||
# encode the bitmap
|
||||
for _y in range(img.height):
|
||||
for _x in range(img.width):
|
||||
# get alpha information about this pixel
|
||||
_a = alpha_pixels[_x, _y]
|
||||
|
||||
if profile['name'] in ['p16', 'p16a']:
|
||||
# If c lands on the alpha value, flip its lowest bit to avoid
|
||||
# ambiguity with alpha
|
||||
_c = profile['alpha'] if _a else rgb24to16(pixels[_x, _y]) & ~1
|
||||
data[(img.width * _y) + _x] = _c
|
||||
|
||||
elif profile['name'] in ['p8', 'p8a']:
|
||||
_c = palette_map[pixels[_x, _y]] if _a > 0 else profile['alpha']
|
||||
data[(img.width * _y) + _x] = _c
|
||||
|
||||
else:
|
||||
_c = palette_map[pixels[_x, _y]] if _a > 0 else profile['alpha']
|
||||
offset = (nb_stride * _y) + (_x // 2)
|
||||
if _x % 2 == 0:
|
||||
data[offset] |= (_c << 4)
|
||||
else:
|
||||
data[offset] |= _c
|
||||
|
||||
# update external information
|
||||
info['data'] = data
|
||||
info['data_size'] = data_size
|
||||
info['nb_stride'] = nb_stride
|
||||
info['data_size'] = data_size
|
||||
|
||||
def __image_convert(asset, profile_name):
|
||||
""" Image asset convertion
|
||||
|
||||
@args
|
||||
> asset (_VxAsset) - asset information
|
||||
> profile_name (str) - profile name information
|
||||
|
||||
@return
|
||||
> a dictionary with all image information
|
||||
"""
|
||||
# generate critical information and check posible error
|
||||
img_info = {
|
||||
'img' : Image.open(asset.path),
|
||||
'profile' : __profile_find(profile_name)
|
||||
}
|
||||
if not img_info['img']:
|
||||
log.error(f"unable to open the asset '{asset.path}', abord")
|
||||
return None
|
||||
if not img_info['profile']:
|
||||
log.error(f"unable to find the color profile '{profile_name}', abord")
|
||||
return None
|
||||
|
||||
# convert the bitmap and generate critical information
|
||||
__image_isolate_alpha(img_info)
|
||||
if img_info['profile']['is_indexed']:
|
||||
__image_encode_palette(img_info)
|
||||
__image_encode_bitmap(img_info)
|
||||
|
||||
# return generated information
|
||||
return img_info
|
||||
|
||||
#---
|
||||
# Internal source file content generation
|
||||
#---
|
||||
|
||||
def __display_array(array, prefix='\t\t'):
|
||||
""" Display array information (only for p16* profile) """
|
||||
line = 0
|
||||
content = ''
|
||||
for pixels in array:
|
||||
if line == 0:
|
||||
content += prefix
|
||||
if line >= 1:
|
||||
content += ' '
|
||||
content += f'{pixels:#06x},'
|
||||
if (line := line + 1) >= 8:
|
||||
content += '\n'
|
||||
line = 0
|
||||
if line != 0:
|
||||
content += '\n'
|
||||
return content
|
||||
|
||||
def __image_generate_source_file(asset, info):
|
||||
"""Generate image source file
|
||||
|
||||
@args
|
||||
> asset (VxAsset) - asset information
|
||||
> info (dict) - hold image information
|
||||
|
||||
@return
|
||||
> file C content string
|
||||
"""
|
||||
img = info['img']
|
||||
profile = info['profile']
|
||||
|
||||
# generate basic header
|
||||
content = "#include <vhex/display/image/types.h>\n"
|
||||
content += "\n"
|
||||
content += f"/* {asset.name} - Vhex asset\n"
|
||||
content += " This object has been converted by using the vxSDK "
|
||||
content += "converter */\n"
|
||||
content += "const image_t " + f"{asset.name} = " + "{\n"
|
||||
content += f"\t.format = {profile['profile']},\n"
|
||||
content += "\t.flags = IMAGE_FLAGS_RO | IMAGE_FLAGS_OWN,\n"
|
||||
content += f"\t.color_count = {profile['palette_color_count']},\n"
|
||||
content += f"\t.width = {img.width},\n"
|
||||
content += f"\t.height = {img.height},\n"
|
||||
content += f"\t.stride = {info['nb_stride']},\n"
|
||||
|
||||
# encode bitmap table
|
||||
encode = 16 if profile['profile'] in ['p16', 'p16a'] else 8
|
||||
content += f"\t.data = (void*)(const uint{encode}_t [])" + "{\n"
|
||||
for _y in range(img.height):
|
||||
content += '\t\t'
|
||||
for _x in range(info['nb_stride']):
|
||||
pixel = info['data'][(_y * info['nb_stride']) + _x]
|
||||
if profile['profile'] in ['p16', 'p16a']:
|
||||
content += f'{pixel:#06x},'
|
||||
elif profile['profile'] in ['p8', 'p8a']:
|
||||
content += f'{pixel:#04x},'
|
||||
else:
|
||||
content += f'{pixel:3},'
|
||||
content += '\n'
|
||||
|
||||
content += '\t},\n'
|
||||
|
||||
# add palette information
|
||||
if 'palette_data' in info:
|
||||
content += "\t.palette = (void*)(const uint16_t []){\n"
|
||||
content += __display_array(info['palette_data'])
|
||||
content += "\t},\n"
|
||||
else:
|
||||
content += "\t.palette = NULL,\n"
|
||||
|
||||
# closure and return
|
||||
content += '};'
|
||||
return content
|
||||
|
||||
#---
|
||||
# Public
|
||||
#---
|
||||
|
||||
def conv_image_generate(asset, prefix_output):
|
||||
""" Convert an image asset to a C source file
|
||||
|
||||
@args
|
||||
> asset (_VxAsset) - minimal asset information
|
||||
> prefix_output (str) - prefix for source file generation
|
||||
|
||||
@return
|
||||
> pathname of the generated file
|
||||
"""
|
||||
# check critical requirement
|
||||
if 'profile' not in asset.meta:
|
||||
log.error(f"[{asset.name}] missing profile information!")
|
||||
return ''
|
||||
|
||||
#generate the source file content
|
||||
if not (img_info := __image_convert(asset, asset.meta['profile'])):
|
||||
return ''
|
||||
content = __image_generate_source_file(asset, img_info)
|
||||
|
||||
# generate the source file
|
||||
asset_src = f'{prefix_output}/{asset.name}_vximage.c'
|
||||
with open(asset_src, "w", encoding='utf8') as file:
|
||||
file.write(content)
|
||||
return asset_src
|
|
@ -0,0 +1,115 @@
|
|||
"""
|
||||
Log wrapper
|
||||
"""
|
||||
import sys
|
||||
|
||||
__all__ = [
|
||||
'log'
|
||||
]
|
||||
|
||||
LOG_DEBUG = 7
|
||||
LOG_INFO = 6
|
||||
LOG_NOTICE = 5
|
||||
LOG_USER = 4
|
||||
LOG_WARN = 3
|
||||
LOG_ERR = 2
|
||||
LOG_CRIT = 1
|
||||
LOG_EMERG = 0
|
||||
|
||||
#---
|
||||
# Internals
|
||||
#---
|
||||
|
||||
class _VxLogger():
|
||||
def __init__(self, logfile=None):
|
||||
self._logfile = logfile
|
||||
self._level = LOG_USER
|
||||
self._indent = 0
|
||||
|
||||
#---
|
||||
# Internals
|
||||
#---
|
||||
|
||||
def _print(self, level, text, skip_indent, fileno):
|
||||
if self._level < level:
|
||||
return 0
|
||||
if not skip_indent and self._level == LOG_DEBUG and self._indent > 0:
|
||||
text = ('>>> ' * self._indent) + text
|
||||
print(text, file=fileno, end='', flush=True)
|
||||
return len(text) + 1
|
||||
|
||||
#---
|
||||
# Public properties
|
||||
#---
|
||||
|
||||
@property
|
||||
def level(self):
|
||||
""" <property> handle print level """
|
||||
return self._level
|
||||
|
||||
@level.setter
|
||||
def level(self, level):
|
||||
""" <property> handle print level """
|
||||
if level < LOG_EMERG or level > LOG_DEBUG:
|
||||
print(f"[log] level update to {level} is not possible, ignored")
|
||||
return
|
||||
self._level = level
|
||||
|
||||
@property
|
||||
def indent(self):
|
||||
""" <property> handle indentation level for LOG_DEBUG """
|
||||
return self._indent
|
||||
|
||||
@indent.setter
|
||||
def indent(self, indent):
|
||||
""" <property> handle indentation level for LOG_DEBUG """
|
||||
if indent < 0:
|
||||
print(f"[log] indent update to {indent} is not possible, ignored")
|
||||
return
|
||||
self._indent = indent
|
||||
|
||||
#---
|
||||
# Public methods
|
||||
#---
|
||||
|
||||
def debug(self, text, end='\n', skip_indent=False):
|
||||
""" print debug log """
|
||||
return self._print(LOG_DEBUG, text + end, skip_indent, sys.stdout)
|
||||
|
||||
def info(self, text, end='\n', skip_indent=False):
|
||||
""" print info log """
|
||||
return self._print(LOG_INFO, text + end, skip_indent, sys.stdout)
|
||||
|
||||
def notice(self, text, end='\n', skip_indent=False):
|
||||
""" print notice log """
|
||||
return self._print(LOG_NOTICE, text + end, skip_indent, sys.stdout)
|
||||
|
||||
def user(self, text, end='\n', skip_indent=False):
|
||||
""" print user log """
|
||||
return self._print(LOG_USER, text + end, skip_indent, sys.stdout)
|
||||
|
||||
def warn(self, text, end='\n', skip_indent=False):
|
||||
""" print warning log """
|
||||
return self._print(LOG_WARN, text + end, skip_indent, sys.stderr)
|
||||
|
||||
def error(self, text, end='\n', skip_indent=False):
|
||||
""" print error log """
|
||||
return self._print(LOG_ERR, text + end, skip_indent, sys.stderr)
|
||||
|
||||
def critical(self, text, end='\n', skip_indent=False):
|
||||
""" print critical log """
|
||||
return self._print(LOG_CRIT, text + end, skip_indent, sys.stderr)
|
||||
|
||||
def emergency(self, text, end='\n', skip_indent=False):
|
||||
""" print emergency log """
|
||||
return self._print(LOG_EMERG, text + end, skip_indent, sys.stderr)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
#---
|
||||
# Public functions
|
||||
#---
|
||||
|
||||
log = _VxLogger()
|
|
@ -0,0 +1,59 @@
|
|||
"""
|
||||
Provide package primitives (mainly for syntax sugar)
|
||||
"""
|
||||
from core.pkg.find import pkg_find
|
||||
from core.pkg.clone import pkg_clone
|
||||
|
||||
__all__ = [
|
||||
'find',
|
||||
'clone',
|
||||
]
|
||||
|
||||
#---
|
||||
# Public
|
||||
#---
|
||||
|
||||
def find(name, version=None, local=True, remote=True):
|
||||
r"""Try to find a particular package.
|
||||
|
||||
@args
|
||||
> name (str) - exact valid package name
|
||||
> version (str) - version query string
|
||||
> local (bool) - enable local search
|
||||
> remote (bool) - enable remote search
|
||||
|
||||
@return
|
||||
> a list of all matched package dictionnary :
|
||||
[
|
||||
{
|
||||
'name' : <package name>,
|
||||
'version' : [
|
||||
<list of all available (or matched) version>
|
||||
...
|
||||
]
|
||||
]
|
||||
> None if error
|
||||
"""
|
||||
return pkg_find(name, version, local, remote)
|
||||
|
||||
def clone(name, version=None, prefix=None, confirm=False):
|
||||
r""" Clone package with appropriate version
|
||||
|
||||
This function will try to find the wanted package with the appropriate
|
||||
version then clone it into <`prefix`> if provided or in the internal
|
||||
<config.pkg.home_prefix> configuration key otherwise.
|
||||
|
||||
The `name` should be a valid package name. However, the `version` argument
|
||||
can contains version operation like carret (^), tilde (~) and start (*) as
|
||||
described in <vxsdk/core/pkg/version.py>
|
||||
|
||||
@args
|
||||
> prefix (str) - clone path prefix
|
||||
> name (str) - exact valid package name
|
||||
> version (str) - version query string
|
||||
> confirm (bool) - display user input to confirm the clone
|
||||
|
||||
@return
|
||||
> the package path if successfully cloned, None otherwise
|
||||
"""
|
||||
return pkg_clone(name, version, prefix, confirm)
|
|
@ -0,0 +1,57 @@
|
|||
"""Remote backend constructor
|
||||
|
||||
This package will exposes the major important object for the package core remote
|
||||
part of the vxsdk.
|
||||
|
||||
=========================== ============================================
|
||||
Object name Description
|
||||
=========================== ============================================
|
||||
PKG_CORE_BACKEND_REMOTE Remote ackend object
|
||||
PKG_CORE_BACKEND_LOCAL Local backend object
|
||||
VxRemoteBackend Abstract class for backend implementation
|
||||
=========================== ======================================
|
||||
|
||||
This part of the vxsdk can be manually configured using a TOML configuration
|
||||
which need to be located at <~/.config/vxsdk/configure.toml>. A special
|
||||
section named 'package.remote' can be added to specify backend information:
|
||||
|
||||
[pkg]
|
||||
backend.name = 'gitea'
|
||||
backend.url = 'https://personal.gitea.instance.gaming'
|
||||
|
||||
This is the only configuration that you can set here.
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
|
||||
from core.pkg.backend.local import VxBackendLocal
|
||||
from core.logger import log
|
||||
from core.config import config_get
|
||||
|
||||
__all__ = [
|
||||
'PKG_CORE_BACKEND_REMOTE',
|
||||
'PKG_CORE_BACKEND_LOCAL',
|
||||
]
|
||||
|
||||
backend_remote_name = config_get('pkg.backend.name')
|
||||
backend_remote_url = config_get('pkg.backend.url')
|
||||
backend_local_url = os.path.expanduser(config_get('pkg.local_storage'))
|
||||
|
||||
PKG_CORE_BACKEND_REMOTE = None
|
||||
PKG_CORE_BACKEND_LOCAL = None
|
||||
try:
|
||||
mod = __import__(
|
||||
f'core.pkg.backend.{backend_remote_name}',
|
||||
fromlist=['VxBackendRemote']
|
||||
)
|
||||
if not hasattr(mod, 'VxBackendRemote'):
|
||||
raise Exception(
|
||||
f"backend '{backend_remote_name}' doesn't expose "
|
||||
"VxBackendRemote class"
|
||||
)
|
||||
PKG_CORE_BACKEND_REMOTE = mod.VxBackendRemote(backend_remote_url)
|
||||
PKG_CORE_BACKEND_LOCAL = VxBackendLocal(backend_local_url)
|
||||
except ImportError as err:
|
||||
log.emergency("[backend] unable to load remote backend, abord")
|
||||
log.emergency(err)
|
||||
sys.exit(84)
|
|
@ -0,0 +1,134 @@
|
|||
"""
|
||||
Vhex package core abstract class
|
||||
"""
|
||||
import abc
|
||||
|
||||
from core.logger import log
|
||||
|
||||
__all__ = [
|
||||
'VxRemoteBackendCore'
|
||||
]
|
||||
|
||||
class VxRemoteBackendCore(abc.ABC):
|
||||
r"""Represent a remote backend 'core' class.
|
||||
|
||||
This class is a simple abstract class that should be used by all internal
|
||||
'backend' wich should expose some common methods and property:
|
||||
|
||||
================================== =========================================
|
||||
Property Description
|
||||
================================== =========================================
|
||||
name (str) Backend name (for debug)
|
||||
url (str) Backend URL (for internal use)
|
||||
================================== =========================================
|
||||
|
||||
================================== =========================================
|
||||
Method Description
|
||||
================================== =========================================
|
||||
find() find packages
|
||||
clone() clone a package
|
||||
================================== =========================================
|
||||
"""
|
||||
def __init__(self, url):
|
||||
self._url = url
|
||||
self._pkg_list = []
|
||||
|
||||
@property
|
||||
@abc.abstractmethod
|
||||
def name(self):
|
||||
"""<property> hold pacakge name"""
|
||||
|
||||
@property
|
||||
@abc.abstractmethod
|
||||
def url(self):
|
||||
"""<property> hold pacakge URL (or path for local package)"""
|
||||
|
||||
@property
|
||||
@abc.abstractmethod
|
||||
def package_list(self):
|
||||
"""<property> return the pacakge list (cached)"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def package_fetch_versions(self, pkg):
|
||||
"""<method> pacakge version fetch (cache)"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def package_clone(self, pkg, prefix=None):
|
||||
"""<method> package primtive """
|
||||
|
||||
def package_find(self, name, version=None):
|
||||
r"""Try to find package
|
||||
|
||||
This method will try to find a particular package with a specific
|
||||
version if the `version` argument is provided. Otherwise, the version
|
||||
argument can holds special version requirement if the package use a
|
||||
versionning sementic (more information in <vxsdk/core/pkg/version.py>)
|
||||
|
||||
If name is empty, all package will be returned
|
||||
|
||||
@args
|
||||
> names (str) - package name
|
||||
> versions (str) - version pattern
|
||||
|
||||
Return:
|
||||
> A list of dictionary with (at least):
|
||||
[
|
||||
{
|
||||
'name' (str) : package name
|
||||
'full_name' (str) : full named package (author+name)
|
||||
'description' (str) : package description
|
||||
'versions' (list) : [
|
||||
List of :obj:VxVersion that match the `version` argument
|
||||
]
|
||||
},
|
||||
...
|
||||
]
|
||||
"""
|
||||
# check if we need to return the complet list
|
||||
if not name:
|
||||
ret = []
|
||||
for pkg in self.package_list:
|
||||
if not self.package_fetch_versions(pkg):
|
||||
continue
|
||||
pkg_cpy = pkg.copy()
|
||||
pkg_cpy['version'] = None
|
||||
ret.append(pkg_cpy)
|
||||
return ret
|
||||
|
||||
# handle package name and version verification
|
||||
# @note
|
||||
# - return on the first matched package
|
||||
for pkg in self.package_list:
|
||||
# check package name
|
||||
if pkg['name'] != name:
|
||||
continue
|
||||
|
||||
# fetch version information
|
||||
if not self.package_fetch_versions(pkg):
|
||||
log.warn("[pkg] '{pkg['name']}' unable to fetch versions")
|
||||
continue
|
||||
|
||||
# if the version is None, we just need to fetch available
|
||||
# versions then return
|
||||
if not version:
|
||||
pkg_cpy = pkg.copy()
|
||||
pkg_cpy['version'] = None
|
||||
return [pkg_cpy]
|
||||
|
||||
# perform version checking and select the best fit
|
||||
ver_found = None
|
||||
for ver in pkg['versions']:
|
||||
if not ver.validate(version):
|
||||
continue
|
||||
if not ver_found or ver.compare(ver_found.name) < 0:
|
||||
ver_found = ver
|
||||
if not ver_found:
|
||||
return []
|
||||
|
||||
# isolate the best version
|
||||
pkg_cpy = pkg.copy()
|
||||
pkg_cpy['version'] = ver_found
|
||||
return [pkg_cpy]
|
||||
|
||||
# not requested package found, return
|
||||
return None
|
|
@ -0,0 +1,170 @@
|
|||
"""
|
||||
Vhex core backend for Gitea instance
|
||||
"""
|
||||
import os
|
||||
import subprocess
|
||||
import requests
|
||||
|
||||
from core.pkg.backend.core import VxRemoteBackendCore
|
||||
from core.pkg.version import VxVersion
|
||||
from core.logger import log
|
||||
from core.config import config_get
|
||||
|
||||
|
||||
__all__ = [
|
||||
'VxBackendRemote'
|
||||
]
|
||||
|
||||
class VxBackendRemote(VxRemoteBackendCore):
|
||||
"""
|
||||
Vhex Gitea backend class
|
||||
"""
|
||||
|
||||
#---
|
||||
# Public properties
|
||||
#---
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
return 'gitea'
|
||||
|
||||
@property
|
||||
def url(self):
|
||||
return self._url
|
||||
|
||||
@property
|
||||
def package_list(self):
|
||||
""" Return the list of all repository find in the remote instance.
|
||||
|
||||
This property is used to cache request's information and avoid big HTTP
|
||||
quesring to the remote instance of Gitea.
|
||||
"""
|
||||
# if cached, avoid quering operation
|
||||
if self._pkg_list:
|
||||
return self._pkg_list
|
||||
|
||||
# raw HTTP request
|
||||
resp = requests.get(
|
||||
f'{self.url}/api/v1/repos/search',
|
||||
params={
|
||||
'q' : 'vxsdk',
|
||||
'topic': 'True'
|
||||
}
|
||||
)
|
||||
if not resp.ok:
|
||||
log.warn(
|
||||
f'[backend] gitea: remote package requests failed\n'
|
||||
f'>>> {resp.status_code}'
|
||||
)
|
||||
self._pkg_list = []
|
||||
return []
|
||||
|
||||
# generate the pacakge list
|
||||
# @note
|
||||
# - hide private repositories
|
||||
self._pkg_list = []
|
||||
for pkg in resp.json()['data']:
|
||||
if pkg['private']:
|
||||
continue
|
||||
self._pkg_list.append({
|
||||
'name' : pkg['name'],
|
||||
'full_name' : pkg['full_name'],
|
||||
'description' : pkg['description'],
|
||||
'url' : pkg['clone_url'],
|
||||
'created' : pkg['created_at'].split('T')[0],
|
||||
'updated' : pkg['updated_at'].split('T')[0],
|
||||
'author' : pkg['owner']['login'],
|
||||
'default_branch' : pkg['default_branch'],
|
||||
'versions' : []
|
||||
})
|
||||
return self._pkg_list
|
||||
|
||||
#---
|
||||
# Private methods
|
||||
#---
|
||||
|
||||
def package_fetch_versions(self, pkg):
|
||||
""" Fetch package version information.
|
||||
|
||||
Same as the `package_list` property, we using cache to avoid too many
|
||||
HTTP request.
|
||||
"""
|
||||
# if cached information, return it
|
||||
if pkg['versions']:
|
||||
return pkg['versions']
|
||||
|
||||
# request branches information
|
||||
pkg['versions'] = []
|
||||
url = f"{self.url}/api/v1/repos/{pkg['full_name']}"
|
||||
resp = requests.get(f'{url}/branches')
|
||||
if not resp.ok:
|
||||
log.warn(
|
||||
f'[pkg]: backend: gitea: branches requests error\n'
|
||||
f'>>> url = {url}/branches\n'
|
||||
f'>>> status = {resp.status_code}'
|
||||
)
|
||||
else:
|
||||
for ver in resp.json():
|
||||
pkg['versions'].append(
|
||||
VxVersion(ver['name'], 'branch', 'remote')
|
||||
)
|
||||
|
||||
# request tag information
|
||||
resp = requests.get(f'{url}/tags')
|
||||
if not resp.ok:
|
||||
log.warn(
|
||||
f'[pkg]: backend: gitea: tags requests error\n'
|
||||
f'>>> url = {url}/tags\n'
|
||||
f'>>> status = {resp.status_code}'
|
||||
)
|
||||
else:
|
||||
for ver in resp.json():
|
||||
pkg['versions'].append(VxVersion(ver['name'], 'tag', 'remote'))
|
||||
return pkg['versions']
|
||||
|
||||
#---
|
||||
# Public methods
|
||||
#---
|
||||
|
||||
def package_clone(self, pkg, _=None):
|
||||
""" Clone the package in global storage
|
||||
|
||||
@args
|
||||
> pkg (dict) - package information returned by package_find()
|
||||
|
||||
@return
|
||||
> Complet path for the package (str), or None if error
|
||||
"""
|
||||
# fetch global storage prefix
|
||||
# @notes
|
||||
# - create it if its doesn't exists
|
||||
prefix = os.path.expanduser(config_get('path.packages'))
|
||||
if not os.path.exists(prefix):
|
||||
os.makedirs(prefix)
|
||||
|
||||
# generate clone information
|
||||
# @note
|
||||
# - create clone folder if not exists
|
||||
pkg_ver = pkg['version']
|
||||
pkg_name = f"{pkg['author']}@{pkg['name']}@{pkg_ver.name}@{pkg_ver.type}"
|
||||
pkg_path = f"{prefix}/{pkg_name}"
|
||||
if os.path.exists(pkg_path):
|
||||
log.warn(f"[clone]: {pkg_name} already exists, skipped")
|
||||
return pkg_path
|
||||
|
||||
# perform high-level git clone
|
||||
cmd = [
|
||||
'git',
|
||||
'-c', 'advice.detachedHead=false',
|
||||
'clone', '--branch', pkg_ver.name,
|
||||
pkg['url'], pkg_path,
|
||||
'--depth=1'
|
||||
]
|
||||
log.debug(f"[gitea] {cmd}")
|
||||
status = subprocess.run(cmd, capture_output=True, check=False)
|
||||
if status.returncode != 0:
|
||||
log.error(f"[clone] : unable to clone {pkg_name}, abord")
|
||||
return []
|
||||
|
||||
# return the package path
|
||||
return pkg_path
|
|
@ -0,0 +1,89 @@
|
|||
"""
|
||||
Vhex core backend for local package
|
||||
"""
|
||||
import os
|
||||
|
||||
from core.logger import log
|
||||
from core.pkg.backend.core import VxRemoteBackendCore
|
||||
from core.pkg.version import VxVersion
|
||||
|
||||
__all__ = [
|
||||
'VxBackendLocal'
|
||||
]
|
||||
|
||||
class VxBackendLocal(VxRemoteBackendCore):
|
||||
"""
|
||||
Vhex backend local package class
|
||||
"""
|
||||
|
||||
#---
|
||||
# Public properties
|
||||
#---
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
return 'local'
|
||||
|
||||
@property
|
||||
def url(self):
|
||||
return self._url
|
||||
|
||||
@property
|
||||
def package_list(self):
|
||||
if self._pkg_list:
|
||||
return self._pkg_list
|
||||
|
||||
if not os.path.exists(self._url):
|
||||
os.makedirs(self._url)
|
||||
return []
|
||||
|
||||
self._pkg_list = []
|
||||
for file in os.listdir(self._url):
|
||||
if not os.path.isdir(f"{self._url}/{file}"):
|
||||
continue
|
||||
exists = False
|
||||
for pkg in self._pkg_list:
|
||||
if pkg['name'] == file.split('@')[1]:
|
||||
exists = True
|
||||
break
|
||||
if exists:
|
||||
continue
|
||||
self._pkg_list.append({
|
||||
'name' : file.split('@')[1],
|
||||
'full_name' : f"{self._url}/{file.split('@')[1]}",
|
||||
'description' : None,
|
||||
'url' : f"{self._url}/{file}",
|
||||
'created' : None,
|
||||
'updated' : None,
|
||||
'author' : file.split('@')[0],
|
||||
'default_branch' : None,
|
||||
'versions' : [
|
||||
VxVersion(file.split('@')[2], file.split('@')[3], 'local')
|
||||
]
|
||||
})
|
||||
return self._pkg_list
|
||||
|
||||
#---
|
||||
# Private methods
|
||||
#---
|
||||
|
||||
def package_fetch_versions(self, pkg):
|
||||
return pkg['versions']
|
||||
|
||||
#---
|
||||
# Public methods
|
||||
#---
|
||||
|
||||
def package_clone(self, pkg, prefix=None):
|
||||
if not prefix:
|
||||
prefix = os.getcwd()
|
||||
if not os.path.exists(prefix):
|
||||
os.makedirs(prefix)
|
||||
if not os.path.exists(f"{prefix}/{pkg['name']}"):
|
||||
log.debug(f"[local] link '{pkg['url']}' > '{prefix}/{pkg['name']}'")
|
||||
os.symlink(
|
||||
pkg['url'],
|
||||
f"{prefix}/{pkg['name']}",
|
||||
target_is_directory=True
|
||||
)
|
||||
return f"{prefix}/{pkg['name']}"
|
|
@ -0,0 +1,89 @@
|
|||
"""
|
||||
Package clone backend abstraction
|
||||
"""
|
||||
|
||||
from core.pkg.backend import PKG_CORE_BACKEND_REMOTE, PKG_CORE_BACKEND_LOCAL
|
||||
from core.pkg.version import VxVersion
|
||||
from core.pkg.find import pkg_find
|
||||
from core.logger import log
|
||||
|
||||
__all__ = [
|
||||
'pkg_clone'
|
||||
]
|
||||
|
||||
#---
|
||||
# Internals
|
||||
#---
|
||||
|
||||
def _pkg_clone_core(pkg, prefix):
|
||||
"""
|
||||
|
||||
@args
|
||||
> pkg (dict) - package information
|
||||
|
||||
@return
|
||||
> the package path if successfully cloned, None otherwise
|
||||
"""
|
||||
version = pkg['version']
|
||||
|
||||
# be sure that the package target is in the global storage
|
||||
log.user(f"cloning package {pkg['name']}...")
|
||||
pkg_path = PKG_CORE_BACKEND_REMOTE.package_clone(pkg)
|
||||
if not pkg_path:
|
||||
log.error(
|
||||
f"{pkg['name']}@{version.name}: unable to clone the package, abord",
|
||||
)
|
||||
return None
|
||||
|
||||
# "clone" the package (create a symbolic link)
|
||||
pkg['url'] = pkg_path
|
||||
return PKG_CORE_BACKEND_LOCAL.package_clone(pkg, prefix)
|
||||
|
||||
#---
|
||||
# Public
|
||||
#---
|
||||
|
||||
def pkg_clone(name, version, prefix, confirm=False):
|
||||
r""" Clone the package
|
||||
|
||||
This function will try to clone the package with the exact selected
|
||||
version, with all of its dependencies. See <core/pkg/backend> for more
|
||||
information about the process.
|
||||
|
||||
@args
|
||||
> name (str) - exact valid package name
|
||||
> version (str) - version query string
|
||||
> prefix (str) - clone path prefix
|
||||
> confirm (bool) - display user input to confirm the clone
|
||||
|
||||
@return
|
||||
> the package path if successfully cloned, None otherwise
|
||||
"""
|
||||
# try to find the package anywhere that the vxSDK allow
|
||||
pkg_list = pkg_find(name, version, local=True, remote=True)
|
||||
if not pkg_list:
|
||||
log.error("[pkg] pacakge find error")
|
||||
return None
|
||||
if len(pkg_list) != 1:
|
||||
log.warn("[pkg] multiple package found, other will be ignored")
|
||||
|
||||
# check version information
|
||||
pkg_info = pkg_list[0]
|
||||
if not pkg_info['version']:
|
||||
if version:
|
||||
log.error(f"{name}@{version}: unable to find the version")
|
||||
return None
|
||||
pkg_info['version'] = VxVersion(pkg_info['default_branch'], 'branch')
|
||||
|
||||
# wait user interaction if needed
|
||||
if confirm and not 'local' in pkg_info['version'].sources:
|
||||
log.user(
|
||||
f"Do you want to install '{pkg_info['full_name']}'? (Y/n) ",
|
||||
end = ''
|
||||
)
|
||||
valid = input()
|
||||
if valid and not valid in ['Y', 'y', 'yes', 'Yes']:
|
||||
return None
|
||||
|
||||
# "real" clone the package
|
||||
return _pkg_clone_core(pkg_info, prefix)
|
|
@ -0,0 +1,126 @@
|
|||
"""
|
||||
Package find backend abstraction
|
||||
"""
|
||||
|
||||
from core.logger import log
|
||||
from core.pkg.backend import PKG_CORE_BACKEND_REMOTE, PKG_CORE_BACKEND_LOCAL
|
||||
|
||||
__all__ = [
|
||||
'pkg_find'
|
||||
]
|
||||
|
||||
#---
|
||||
# Internals
|
||||
#---
|
||||
|
||||
def _pkg_find_merge_result(pkg_list_remote, pkg_list_local):
|
||||
""" Merge two custom package information list
|
||||
@arg
|
||||
> pkg_list_remote (list,dict) - package list
|
||||
> pkg_list_local (list,dict) - package list
|
||||
|
||||
@return
|
||||
> The first list modified
|
||||
"""
|
||||
for pkg_local in pkg_list_local:
|
||||
pkg_found = False
|
||||
for pkg_remote in pkg_list_remote:
|
||||
if pkg_remote['name'] != pkg_local['name']:
|
||||
continue
|
||||
pkg_found = True
|
||||
for pkg_ver_local in pkg_local['versions']:
|
||||
version_found = False
|
||||
for pkg_ver_remote in pkg_remote['versions']:
|
||||
if pkg_ver_remote.name != pkg_ver_local.name:
|
||||
continue
|
||||
pkg_ver_remote.addSource('local')
|
||||
version_found = True
|
||||
break
|
||||
if not version_found:
|
||||
pkg_remote['versions'].append(pkg_ver_local)
|
||||
if not pkg_found:
|
||||
pkg_list_remote.append(pkg_local)
|
||||
return pkg_list_remote
|
||||
|
||||
def _pkg_find_select_best(pkg_remote, pkg_local):
|
||||
""" Select the best version
|
||||
|
||||
@arg
|
||||
> pkg_remote (dict) - pacakge information
|
||||
> pkg_local (dict) - pacakge information
|
||||
|
||||
@return
|
||||
> a list with the best pacakge information version
|
||||
"""
|
||||
if not 'version' in pkg_remote or not 'version' in pkg_local:
|
||||
log.warn("[log] misformed package search result, silently ignored")
|
||||
return []
|
||||
if pkg_remote['version'].compare(pkg_local['version'].name) > 0:
|
||||
pkg_remote['version'] = pkg_local['version']
|
||||
return [pkg_remote]
|
||||
|
||||
#---
|
||||
# Public
|
||||
#---
|
||||
|
||||
def pkg_find(name, version=None, local=False, remote=True):
|
||||
r""" Find the most appropriate package information
|
||||
|
||||
This function will request to the remote backend the list of all version of
|
||||
the pacakge, which is basically all tags and branches. Then, we will check
|
||||
all of these until a version match with the `version` query.
|
||||
|
||||
Note that the version query can contains special operation as described in
|
||||
<core/pkg/version>
|
||||
|
||||
@args
|
||||
> name (str) - exact valid package name
|
||||
> version (str) - version query string
|
||||
> local (bool) - enable local search
|
||||
> remote (bool) - enable remote search
|
||||
|
||||
@return
|
||||
> a list of all matched package dictionnary :
|
||||
[
|
||||
{
|
||||
'name' : <package name>,
|
||||
'path' : <paclage path or URL>
|
||||
'versions' : [
|
||||
<list of all available (or matched) version (VxVersion)>
|
||||
...
|
||||
]
|
||||
]
|
||||
> None if error
|
||||
"""
|
||||
if not local and not remote:
|
||||
return []
|
||||
|
||||
# perform explicit request for each backend
|
||||
pkg_list_local = []
|
||||
pkg_list_remote = []
|
||||
if remote:
|
||||
pkg_list_remote = PKG_CORE_BACKEND_REMOTE.package_find(name, version)
|
||||
if local:
|
||||
pkg_list_local = PKG_CORE_BACKEND_LOCAL.package_find(name, version)
|
||||
|
||||
|
||||
# verify useless checking and merging operation
|
||||
if not pkg_list_remote:
|
||||
return pkg_list_local
|
||||
if not pkg_list_local:
|
||||
return pkg_list_remote
|
||||
|
||||
|
||||
# if a version is provided, each backend will return only one dictionary
|
||||
# with the best match in local and remote one. We will select the best
|
||||
# between these two.
|
||||
if version:
|
||||
return _pkg_find_select_best(
|
||||
pkg_list_remote[0],
|
||||
pkg_list_local[0],
|
||||
)
|
||||
|
||||
# if no version information as been specified each `pkg_list_remote` and
|
||||
# `pkg_list_local` will contains a list of all matched package information.
|
||||
# So, we need to merge these two list.
|
||||
return _pkg_find_merge_result(pkg_list_remote, pkg_list_local)
|
|
@ -0,0 +1,216 @@
|
|||
import os
|
||||
import re
|
||||
import subprocess
|
||||
|
||||
__all__ = [
|
||||
'VxVersion',
|
||||
'version_is_valid',
|
||||
'version_get'
|
||||
]
|
||||
|
||||
class VxVersion(object):
|
||||
r"""Represent a Version object.
|
||||
|
||||
This version mecanism is a strong part of the package managing because If
|
||||
the 'version' is detected to respect the correct semantic versioning, you
|
||||
can perform version operations: caret (^), tilde (~) and wildcard (*)
|
||||
|
||||
Caret requirements (^)
|
||||
^1.2.3 := >=1.2.3, <2.0.0
|
||||
^1.2 := >=1.2.0, <2.0.0
|
||||
^1 := >=1.0.0, <2.0.0
|
||||
^0.2.3 := >=0.2.3, <0.3.0
|
||||
^0.2 := >=0.2.0, <0.3.0
|
||||
^0.0.3 := >=0.0.3, <0.0.4
|
||||
^0.0 := >=0.0.0, <0.1.0
|
||||
^0 := >=0.0.0, <1.0.0
|
||||
|
||||
Tilde requirements (~)
|
||||
~1.2.3 := >=1.2.3, <1.3.0
|
||||
~1.2 := >=1.2.0, <1.3.0
|
||||
~1 := >=1.0.0, <2.0.0
|
||||
|
||||
Wildcard requirements (*)
|
||||
* := >=0.0.0
|
||||
1.* := >=1.0.0, <2.0.0
|
||||
1.2.* := >=1.2.0, <1.3.0
|
||||
|
||||
Note that it's possible that a package can have two same versions (one
|
||||
for branch name and another for a tag), by default, the tag is always
|
||||
selected, but here the display information will explicitly describe if
|
||||
the version is a tag and / or a branch.
|
||||
|
||||
|
||||
TODO:
|
||||
> Use 'releases' section too ?
|
||||
> Define explicitely version type (tag, branch, releases ?)
|
||||
"""
|
||||
def __init__(self, name, type=None, source=None):
|
||||
self._type = type
|
||||
self._name = name
|
||||
self._source_list = [source]
|
||||
|
||||
def __repr__(self):
|
||||
return '<' + self._name + ',(' + self._type + ')>'
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
return self._name
|
||||
@property
|
||||
def type(self):
|
||||
return self._type
|
||||
@property
|
||||
def sources(self):
|
||||
return self._source_list
|
||||
|
||||
|
||||
def validate(self, pattern):
|
||||
r""" Try to check if the version validate the pattern operation.
|
||||
|
||||
As explainned in the class docstring, we can have 3 type of operation,
|
||||
caret (^), tilde (~) and wildcard (*) and the pattern should follow the
|
||||
semantic requirement for each operator.
|
||||
|
||||
Note that the pattern can be None, in this case, the validation step is
|
||||
always success. Beside, the pattern can be a non-valid version semantic
|
||||
request, in this case the version name should match the entiere pattern.
|
||||
|
||||
Args:
|
||||
> pattern (None, str) - pattern used to check if the version is valid
|
||||
|
||||
Return:
|
||||
> True if the version validate the pattern, False otherwise
|
||||
"""
|
||||
return version_is_valid(self.name, pattern)
|
||||
|
||||
def compare(self, version):
|
||||
r"""Compare two version
|
||||
|
||||
This methods will compare versionning inforation and return an interger
|
||||
indicating the result of the comparison, as follow:
|
||||
|
||||
> 0 - the two version match
|
||||
> a negative value - self is less than 'version'
|
||||
> a positive value - self is greater than 'version'
|
||||
"""
|
||||
try:
|
||||
s1 = tuple([int(d) for d in self.name.split(".") if d] + [0,0,0])[:3]
|
||||
s2 = tuple([int(d) for d in version.name.split(".") if d] + [0,0,0])[:3]
|
||||
|
||||
if s1[0] - s2[0] != 0: return s1[0] - s2[0]
|
||||
if s1[1] - s2[1] != 0: return s1[1] - s2[1]
|
||||
return s1[2] - s2[2]
|
||||
except Exception as _:
|
||||
return 0
|
||||
|
||||
def addSource(self, source):
|
||||
self._source_list.append(source)
|
||||
|
||||
|
||||
def version_is_valid(version, pattern):
|
||||
r"""Check if the version validate a pattern
|
||||
|
||||
If the 'version' is detected to respect the correct semantic versioning, you
|
||||
can perform version operations: caret (^), tilde (~) and wildcard (*)
|
||||
|
||||
Caret requirements (^)
|
||||
^1.2.3 := >=1.2.3, <2.0.0
|
||||
^1.2 := >=1.2.0, <2.0.0
|
||||
^1 := >=1.0.0, <2.0.0
|
||||
^0.2.3 := >=0.2.3, <0.3.0
|
||||
^0.2 := >=0.2.0, <0.3.0
|
||||
^0.0.3 := >=0.0.3, <0.0.4
|
||||
^0.0 := >=0.0.0, <0.1.0
|
||||
^0 := >=0.0.0, <1.0.0
|
||||
|
||||
Tilde requirements (~)
|
||||
~1.2.3 := >=1.2.3, <1.3.0
|
||||
~1.2 := >=1.2.0, <1.3.0
|
||||
~1 := >=1.0.0, <2.0.0
|
||||
|
||||
Wildcard requirements (*)
|
||||
* := >=0.0.0
|
||||
1.* := >=1.0.0, <2.0.0
|
||||
1.2.* := >=1.2.0, <1.3.0
|
||||
|
||||
Note that if the pattern argument is None, then the version is always
|
||||
validate and the function will return True
|
||||
|
||||
Args:
|
||||
> version (str) - version to check
|
||||
> pattern (str, None) - version pattern
|
||||
|
||||
Return:
|
||||
> True if the version validate the pattern, False otherwise
|
||||
"""
|
||||
if not pattern:
|
||||
return True
|
||||
|
||||
# Parse a version string of the form "M", "M.m", or "M.m.p" into a triplet
|
||||
def _parse_version(version_string):
|
||||
digits = [int(d) for d in version_string.split(".") if d]
|
||||
return tuple(digits + [0,0,0])[:3]
|
||||
|
||||
# Index of first nonzero component
|
||||
def _first_nonzero(v, default):
|
||||
return next((i for i, value in enumerate(v) if value), default)
|
||||
|
||||
# Increment at specified position
|
||||
def _increment_at(v, position):
|
||||
padding = len(v) - position - 1
|
||||
return tuple(list(v[:position]) + [v[position]+1] + [0] * padding)
|
||||
|
||||
# Parse a spec like ^1.2.3, ~1.2 or 1.2.* into a min/max pair
|
||||
def _parse_spec(spec):
|
||||
regex = re.compile(r'[~^]?(\d+\.){,2}\d+|(\d+\.){,2}\*')
|
||||
if not regex.fullmatch(spec):
|
||||
return None
|
||||
|
||||
spec_length = spec.count(".") + 1
|
||||
v = _parse_version(
|
||||
spec.replace("^","").replace("~","").replace("*","")
|
||||
)
|
||||
M, m, p = v
|
||||
|
||||
if spec[0] == '^':
|
||||
return v, _increment_at(v, _first_nonzero(v, spec_length-1))
|
||||
elif spec[0] == '~':
|
||||
return v, _increment_at(v, min(spec_length-1, 1))
|
||||
elif spec == "*":
|
||||
return ((0, 0, 0), None)
|
||||
elif "*" in spec:
|
||||
return v, _increment_at(v, spec_length-2)
|
||||
else:
|
||||
return (M,m,p), (M,m,p+1)
|
||||
|
||||
# Check if version is between requested bounds
|
||||
def _version_is_suitable(version, min_version, max_version):
|
||||
return (min_version is None or min_version <= version) \
|
||||
and (max_version is None or version < max_version)
|
||||
|
||||
# function entry
|
||||
try:
|
||||
pair = _parse_spec(pattern)
|
||||
if not pair:
|
||||
return version == pattern
|
||||
fmt_version = _parse_version(version)
|
||||
return _version_is_suitable(fmt_version, pair[0], pair[1])
|
||||
except:
|
||||
return False
|
||||
|
||||
def version_get(path):
|
||||
saved_pwd = os.getcwd()
|
||||
os.chdir(path)
|
||||
ret = subprocess.run(
|
||||
['git', 'describe', '--tags', '--exact-match'],
|
||||
capture_output=True, check=False
|
||||
)
|
||||
if ret.returncode != 0:
|
||||
ret = subprocess.run(
|
||||
['git', 'branch', '--show-current'],
|
||||
capture_output=True, check=False
|
||||
)
|
||||
os.chdir(saved_pwd)
|
||||
if ret.returncode != 0:
|
||||
return 'unknown'
|
||||
return ret.stdout.decode("utf8").strip()
|
|
@ -0,0 +1,20 @@
|
|||
import os
|
||||
import shutil
|
||||
|
||||
from core.logger import log
|
||||
|
||||
__all__ = [
|
||||
'project_new'
|
||||
]
|
||||
|
||||
#TODO: change internal project name
|
||||
def project_new(project_path):
|
||||
if os.path.exists(project_path):
|
||||
logger(LOG_WARN, f'The path {project_path} already exists !')
|
||||
return True
|
||||
origin_path = os.path.dirname(__file__)
|
||||
shutil.copytree(
|
||||
origin_path + '/../../assets/project/',
|
||||
project_path
|
||||
)
|
||||
logger(LOG_USER, f"project '{project_path}' successfully created !")
|
Loading…
Reference in New Issue