Starting refactor of tools, thank gosh
Some checks failed
Build Dusk / run-tests (push) Successful in 1m56s
Build Dusk / build-linux (push) Successful in 1m44s
Build Dusk / build-psp (push) Failing after 1m35s

This commit is contained in:
2026-01-25 21:07:31 -06:00
parent d749ac8a91
commit d788de8637
32 changed files with 120 additions and 223 deletions

View File

@@ -113,10 +113,20 @@ if(ENABLE_TESTS)
endif()
# Build assets
add_custom_target(DUSK_ASSETS_BUILT ALL
COMMAND
${Python3_EXECUTABLE} ${DUSK_TOOLS_DIR}/assettool.py
--assets ${DUSK_GAME_ASSETS_DIR}
# add_custom_target(DUSK_ASSETS_BUILT ALL
# COMMAND
# ${Python3_EXECUTABLE} ${DUSK_TOOLS_DIR}/assettool.py
# --assets ${DUSK_GAME_ASSETS_DIR}
# --output-assets ${DUSK_BUILT_ASSETS_DIR}
# --output-file ${DUSK_BUILD_DIR}/dusk.dsk
# --headers-dir ${DUSK_GENERATED_HEADERS_DIR}
# --input ${DUSK_ASSETS}
# )
# add_dependencies(${DUSK_LIBRARY_TARGET_NAME} DUSK_ASSETS_BUILT)
dusk_run_python(
DUSK_ASSETS_BUILT
tools.asset.bundle
--assets ${DUSK_ASSETS_DIR}
--output-assets ${DUSK_BUILT_ASSETS_DIR}
--output-file ${DUSK_BUILD_DIR}/dusk.dsk
--headers-dir ${DUSK_GENERATED_HEADERS_DIR}

View File

@@ -1,66 +0,0 @@
# csvtoenum(<CSV_FILE> <OUT_HEADER> <C_TYPE> <TAKE_COLUMN> <PREFIX_COLUMN>)
# Example:
# csvtoenum(myfile.csv outhead.h itemtype_t id ITEM_TYPE_)
#
# Will generate a header with:
# typedef enum {
# ITEM_TYPE_NULL,
# ITEM_TYPE_FOO,
# ...
# ITEM_TYPE_COUNT,
# } itemtype_t;
function(csvtoenum CSV_FILE OUT_HEADER C_TYPE TAKE_COLUMN PREFIX_COLUMN)
# Read the CSV file
file(READ "${CSV_FILE}" CSV_CONTENTS)
string(REPLACE "\r\n" "\n" CSV_CONTENTS "${CSV_CONTENTS}")
string(REPLACE "\r" "\n" CSV_CONTENTS "${CSV_CONTENTS}")
string(REGEX REPLACE "\n+$" "" CSV_CONTENTS "${CSV_CONTENTS}")
string(REPLACE "\n" ";" CSV_LINES "${CSV_CONTENTS}")
# Get header row and find column indices
list(GET CSV_LINES 0 HEADER_ROW)
string(REPLACE "," ";" HEADER_LIST "${HEADER_ROW}")
set(COLUMN_INDEX -1)
set(PREFIX_INDEX -1)
set(IDX 0)
foreach(COL ${HEADER_LIST})
if(COL STREQUAL "${TAKE_COLUMN}")
set(COLUMN_INDEX ${IDX})
endif()
if(COL STREQUAL "${PREFIX_COLUMN}")
set(PREFIX_INDEX ${IDX})
endif()
math(EXPR IDX "${IDX} + 1")
endforeach()
if(COLUMN_INDEX EQUAL -1)
message(FATAL_ERROR "csvtoenum: TAKE_COLUMN '${TAKE_COLUMN}' not found in header of ${CSV_FILE}")
endif()
if(PREFIX_INDEX EQUAL -1)
message(FATAL_ERROR "csvtoenum: PREFIX_COLUMN '${PREFIX_COLUMN}' not found in header of ${CSV_FILE}")
endif()
# Prepare enum entries
set(ENUM_ENTRIES " ${PREFIX_COLUMN}NULL,")
set(ROW_IDX 1)
list(LENGTH CSV_LINES NUM_LINES)
while(ROW_IDX LESS NUM_LINES)
list(GET CSV_LINES ${ROW_IDX} ROW)
string(REPLACE "," ";" ROW_LIST "${ROW}")
list(LENGTH ROW_LIST ROW_LEN)
if(ROW_LEN GREATER COLUMN_INDEX)
list(GET ROW_LIST ${COLUMN_INDEX} ENTRY)
list(GET ROW_LIST ${PREFIX_INDEX} PREFIX)
# Only add if ENTRY is not empty
string(STRIP "${ENTRY}" ENTRY)
if(NOT ENTRY STREQUAL "")
set(ENUM_ENTRIES "${ENUM_ENTRIES}\n ${PREFIX}${ENTRY},")
endif()
endif()
math(EXPR ROW_IDX "${ROW_IDX} + 1")
endwhile()
set(ENUM_ENTRIES "${ENUM_ENTRIES}\n ${PREFIX_COLUMN}COUNT,")
# Write header file
file(WRITE "${OUT_HEADER}" "/**\n * Auto-generated by csvtoenum.cmake\n * Source: ${CSV_FILE}\n */\n\ntypedef enum {\n${ENUM_ENTRIES}\n} ${C_TYPE};\n")
endfunction()

View File

@@ -3,8 +3,6 @@
# This software is released under the MIT License.
# https://opensource.org/licenses/MIT
include(cmake/modules/csvtoenum.cmake)
# Sources
target_sources(${DUSK_LIBRARY_TARGET_NAME}
PUBLIC
@@ -12,20 +10,3 @@ target_sources(${DUSK_LIBRARY_TARGET_NAME}
inventory.c
backpack.c
)
# Generate itemtype_t enum from CSV
set(ITEM_CSV "${CMAKE_CURRENT_LIST_DIR}/itemtypes.csv")
set(ITEM_HEADER "${CMAKE_CURRENT_LIST_DIR}/itemtypes2.h")
csvtoenum(${ITEM_CSV} ${ITEM_HEADER} itemtype_t id ITEM_TYPE_)
add_custom_command(
OUTPUT ${OUTPUT_FULL_PATH}
COMMAND ${CMAKE_COMMAND}
-DENV_FILE=${INPUT_FULL_PATH}
-DOUT_HEADER=${OUTPUT_FULL_PATH}
-P ${CMAKE_SOURCE_DIR}/cmake/modules/envtoh.cmake
DEPENDS ${INPUT_FULL_PATH} ${CMAKE_SOURCE_DIR}/cmake/modules/envtoh.cmake
COMMENT "Generating ${OUTPUT_NAME_RELATIVE}"
)
add_custom_target(${OUTPUT_NAME_RELATIVE}_header DEPENDS ${OUTPUT_FULL_PATH})
add_dependencies(${DUSK_LIBRARY_TARGET_NAME} ${OUTPUT_NAME_RELATIVE}_header)

View File

@@ -3,6 +3,8 @@
# This software is released under the MIT License.
# https://opensource.org/licenses/MIT
add_subdirectory(run_python)
# Function that adds an asset to be compiled
function(add_asset ASSET_TYPE ASSET_PATH)
set(FULL_ASSET_PATH "${CMAKE_CURRENT_LIST_DIR}/${ASSET_PATH}")

View File

@@ -1,10 +1,10 @@
import sys, os
from assetstool.args import args
from assetstool.processasset import processAsset
from assetstool.processpalette import processPaletteList
from assetstool.processtileset import processTilesetList
from assetstool.processlanguage import processLanguageList
from assetstool.assethelpers import getBuiltAssetsRelativePath
from tools.assetstool.args import args
from tools.assetstool.processasset import processAsset
from tools.assetstool.processpalette import processPaletteList
from tools.assetstool.processtileset import processTilesetList
from tools.assetstool.processlanguage import processLanguageList
from tools.assetstool.assethelpers import getBuiltAssetsRelativePath
import zipfile
# Parse input file args.

View File

@@ -1,5 +1,5 @@
import os
from assetstool.args import args
from tools.assetstool.args import args
def getAssetRelativePath(fullPath):
# Get the relative path to the asset

View File

@@ -1,11 +1,11 @@
import sys
# from processtileset import processTileset
from assetstool.processimage import processImage
from assetstool.processpalette import processPalette
from assetstool.processtileset import processTileset
from assetstool.processmap import processMap
from assetstool.processlanguage import processLanguage
from assetstool.processscript import processScript
from tools.assetstool.processimage import processImage
from tools.assetstool.processpalette import processPalette
from tools.assetstool.processtileset import processTileset
from tools.assetstool.processmap import processMap
from tools.assetstool.processlanguage import processLanguage
from tools.assetstool.processscript import processScript
processedAssets = []

View File

@@ -1,10 +1,10 @@
import os
import sys
from PIL import Image
from assetstool.processpalette import extractPaletteFromImage, palettes
from assetstool.args import args
from assetstool.assethelpers import getAssetRelativePath
from assetstool.assetcache import assetGetCache, assetCache
from tools.assetstool.processpalette import extractPaletteFromImage, palettes
from tools.assetstool.args import args
from tools.assetstool.assethelpers import getAssetRelativePath
from tools.assetstool.assetcache import assetGetCache, assetCache
images = []

View File

@@ -1,9 +1,9 @@
import sys
import os
from assetstool.args import args
from assetstool.assetcache import assetCache, assetGetCache
from assetstool.assethelpers import getAssetRelativePath
from dusk.defs import defs
from tools.assetstool.args import args
from tools.assetstool.assetcache import assetCache, assetGetCache
from tools.assetstool.assethelpers import getAssetRelativePath
from tools.dusk.defs import defs
import polib
import re

View File

@@ -2,12 +2,12 @@ import struct
import sys
import os
import json
from assetstool.args import args
from assetstool.assetcache import assetCache, assetGetCache
from assetstool.assethelpers import getAssetRelativePath
from dusk.defs import TILE_WIDTH, TILE_HEIGHT, TILE_DEPTH, CHUNK_WIDTH, CHUNK_HEIGHT, CHUNK_DEPTH, CHUNK_TILE_COUNT
from dusk.map import Map
from dusk.chunk import Chunk
from tools.assetstool.args import args
from tools.assetstool.assetcache import assetCache, assetGetCache
from tools.assetstool.assethelpers import getAssetRelativePath
from tools.dusk.defs import TILE_WIDTH, TILE_HEIGHT, TILE_DEPTH, CHUNK_WIDTH, CHUNK_HEIGHT, CHUNK_DEPTH, CHUNK_TILE_COUNT
from tools.dusk.map import Map
from tools.dusk.chunk import Chunk
def convertModelData(modelData):
# TLDR; Model data stores things efficiently with indices, but we buffer it

View File

@@ -1,8 +1,8 @@
import os
from PIL import Image
import datetime
from assetstool.args import args
from assetstool.assetcache import assetCache, assetGetCache
from tools.assetstool.args import args
from tools.assetstool.assetcache import assetCache, assetGetCache
palettes = []

View File

@@ -1,9 +1,9 @@
import sys
import os
from assetstool.args import args
from assetstool.assetcache import assetCache, assetGetCache
from assetstool.assethelpers import getAssetRelativePath
from dusk.defs import fileDefs
from tools.assetstool.args import args
from tools.assetstool.assetcache import assetCache, assetGetCache
from tools.assetstool.assethelpers import getAssetRelativePath
from tools.dusk.defs import fileDefs
def processScript(asset):
cache = assetGetCache(asset['path'])

View File

@@ -3,10 +3,10 @@ import sys
import os
import datetime
from xml.etree import ElementTree
from assetstool.processimage import processImage
from assetstool.assethelpers import getAssetRelativePath
from assetstool.args import args
from assetstool.assetcache import assetGetCache, assetCache
from tools.assetstool.processimage import processImage
from tools.assetstool.assethelpers import getAssetRelativePath
from tools.assetstool.args import args
from tools.assetstool.assetcache import assetGetCache, assetCache
tilesets = []

View File

@@ -1,49 +0,0 @@
#!/usr/bin/env python3
"""
csvtoenum.py: Generate a C enum header from a CSV file.
Usage:
python csvtoenum.py <csv_file> <out_header> <c_type> <take_column> <prefix_column>
"""
import sys
import csv
def main():
if len(sys.argv) != 6:
print("Usage: csvtoenum.py <csv_file> <out_header> <c_type> <take_column> <prefix_column>", file=sys.stderr)
sys.exit(1)
csv_file, out_header, c_type, take_column, prefix_column = sys.argv[1:6]
with open(csv_file, newline='') as f:
reader = csv.DictReader(f)
if take_column not in reader.fieldnames:
print(f"TAKE_COLUMN '{take_column}' not found in CSV header", file=sys.stderr)
sys.exit(2)
if prefix_column not in reader.fieldnames:
print(f"PREFIX_COLUMN '{prefix_column}' not found in CSV header", file=sys.stderr)
sys.exit(2)
entries = []
for row in reader:
entry = row[take_column].strip()
prefix = row[prefix_column].strip()
if entry:
entries.append(f" {prefix}{entry},")
# Compose enum
enum_entries = [f" {prefix_column}NULL,"]
enum_entries.extend(entries)
enum_entries.append(f" {prefix_column}COUNT,")
enum_body = "\n".join(enum_entries)
header = f"""/**
* Auto-generated by csvtoenum.py
* Source: {csv_file}
*/
typedef enum {{
{enum_body}
}} {c_type};
"""
with open(out_header, 'w') as f:
f.write(header)
if __name__ == "__main__":
main()

View File

@@ -1,11 +1,11 @@
import json
import os
from dusk.event import Event
from dusk.defs import CHUNK_WIDTH, CHUNK_HEIGHT, CHUNK_DEPTH, CHUNK_VERTEX_COUNT_MAX, TILE_SHAPE_NULL
from dusk.tile import Tile
from dusk.entity import Entity
from dusk.region import Region
from editortool.map.vertexbuffer import VertexBuffer
from tools.dusk.event import Event
from tools.dusk.defs import CHUNK_WIDTH, CHUNK_HEIGHT, CHUNK_DEPTH, CHUNK_VERTEX_COUNT_MAX, TILE_SHAPE_NULL
from tools.dusk.tile import Tile
from tools.dusk.entity import Entity
from tools.dusk.region import Region
from tools.editortool.map.vertexbuffer import VertexBuffer
from OpenGL.GL import *
class Chunk:

View File

@@ -1,5 +1,5 @@
from dusk.defs import ENTITY_TYPE_NULL, ENTITY_TYPE_NPC, CHUNK_WIDTH, CHUNK_HEIGHT, CHUNK_DEPTH, TILE_WIDTH, TILE_HEIGHT, TILE_DEPTH
from editortool.map.vertexbuffer import VertexBuffer
from tools.dusk.defs import ENTITY_TYPE_NULL, ENTITY_TYPE_NPC, CHUNK_WIDTH, CHUNK_HEIGHT, CHUNK_DEPTH, TILE_WIDTH, TILE_HEIGHT, TILE_DEPTH
from tools.editortool.map.vertexbuffer import VertexBuffer
class Entity:
def __init__(self, chunk, localX=0, localY=0, localZ=0):

View File

@@ -1,11 +1,11 @@
import json
import sys
from dusk.event import Event
from tools.dusk.event import Event
from PyQt5.QtWidgets import QFileDialog, QMessageBox
from PyQt5.QtCore import QTimer
import os
from dusk.chunk import Chunk
from dusk.defs import MAP_WIDTH, MAP_HEIGHT, MAP_DEPTH, CHUNK_WIDTH, CHUNK_HEIGHT, CHUNK_DEPTH
from tools.dusk.chunk import Chunk
from tools.dusk.defs import MAP_WIDTH, MAP_HEIGHT, MAP_DEPTH, CHUNK_WIDTH, CHUNK_HEIGHT, CHUNK_DEPTH
import traceback
MAP_DEFAULT_PATH = os.path.join(os.path.dirname(__file__), '../../assets/map/')

View File

@@ -1,5 +1,5 @@
from dusk.defs import CHUNK_WIDTH, CHUNK_HEIGHT, CHUNK_DEPTH, TILE_WIDTH, TILE_HEIGHT, TILE_DEPTH
from editortool.map.vertexbuffer import VertexBuffer
from tools.dusk.defs import CHUNK_WIDTH, CHUNK_HEIGHT, CHUNK_DEPTH, TILE_WIDTH, TILE_HEIGHT, TILE_DEPTH
from tools.editortool.map.vertexbuffer import VertexBuffer
from OpenGL.GL import *
from OpenGL.GLU import *

View File

@@ -1,5 +1,5 @@
from OpenGL.GL import *
from dusk.defs import (
from tools.dusk.defs import (
TILE_WIDTH, TILE_HEIGHT, TILE_DEPTH,
CHUNK_WIDTH, CHUNK_HEIGHT, CHUNK_DEPTH,
TILE_SHAPE_NULL, TILE_SHAPE_FLOOR,

View File

@@ -6,9 +6,9 @@ from PyQt5.QtWidgets import (
)
from OpenGL.GL import *
from OpenGL.GLU import *
from editortool.maptool import MapWindow
from editortool.langtool import LangToolWindow
from editortool.cutscenetool import CutsceneToolWindow
from tools.editortool.maptool import MapWindow
from tools.editortool.langtool import LangToolWindow
from tools.editortool.cutscenetool import CutsceneToolWindow
DEFAULT_TOOL = None
DEFAULT_TOOL = "map"

View File

@@ -1,7 +1,7 @@
from PyQt5.QtWidgets import QMainWindow, QApplication, QWidget, QVBoxLayout, QHBoxLayout, QLabel, QPushButton, QListWidget, QListWidgetItem, QMenuBar, QAction, QFileDialog, QMessageBox
from PyQt5.QtGui import QKeySequence
from editortool.cutscene.cutsceneitemeditor import CutsceneItemEditor
from editortool.cutscene.cutscenemenubar import CutsceneMenuBar
from tools.editortool.cutscene.cutsceneitemeditor import CutsceneItemEditor
from tools.editortool.cutscene.cutscenemenubar import CutsceneMenuBar
import sys
class CutsceneToolWindow(QMainWindow):

View File

@@ -2,7 +2,7 @@ import math
import time
from OpenGL.GL import *
from OpenGL.GLU import *
from dusk.defs import TILE_WIDTH, TILE_HEIGHT, TILE_DEPTH, RPG_CAMERA_PIXELS_PER_UNIT, RPG_CAMERA_Z_OFFSET, RPG_CAMERA_FOV
from tools.dusk.defs import TILE_WIDTH, TILE_HEIGHT, TILE_DEPTH, RPG_CAMERA_PIXELS_PER_UNIT, RPG_CAMERA_Z_OFFSET, RPG_CAMERA_FOV
class Camera:
def __init__(self, parent):

View File

@@ -1,5 +1,5 @@
from PyQt5.QtWidgets import QWidget, QVBoxLayout, QLabel, QGridLayout, QTreeWidget, QTreeWidgetItem, QComboBox
from dusk.defs import CHUNK_WIDTH, CHUNK_HEIGHT, CHUNK_DEPTH, TILE_SHAPES
from tools.dusk.defs import CHUNK_WIDTH, CHUNK_HEIGHT, CHUNK_DEPTH, TILE_SHAPES
class ChunkPanel(QWidget):
def __init__(self, parent):

View File

@@ -1,7 +1,7 @@
from PyQt5.QtWidgets import QWidget, QVBoxLayout, QLabel, QComboBox, QHBoxLayout, QPushButton, QLineEdit, QListWidget, QListWidgetItem
from PyQt5.QtCore import Qt
from dusk.entity import Entity
from dusk.defs import CHUNK_WIDTH, CHUNK_HEIGHT, CHUNK_DEPTH, ENTITY_TYPES, ENTITY_TYPE_NULL
from tools.dusk.entity import Entity
from tools.dusk.defs import CHUNK_WIDTH, CHUNK_HEIGHT, CHUNK_DEPTH, ENTITY_TYPES, ENTITY_TYPE_NULL
class EntityPanel(QWidget):
def __init__(self, parent):

View File

@@ -1,5 +1,5 @@
from OpenGL.GL import *
from dusk.defs import TILE_WIDTH, TILE_HEIGHT, TILE_DEPTH
from tools.dusk.defs import TILE_WIDTH, TILE_HEIGHT, TILE_DEPTH
class Grid:
def __init__(self, lines=1000):

View File

@@ -1,5 +1,5 @@
from PyQt5.QtWidgets import QWidget, QVBoxLayout, QLabel, QLineEdit, QPushButton, QHBoxLayout, QMessageBox
from dusk.defs import CHUNK_WIDTH, CHUNK_HEIGHT, CHUNK_DEPTH
from tools.dusk.defs import CHUNK_WIDTH, CHUNK_HEIGHT, CHUNK_DEPTH
class MapInfoPanel(QWidget):
def __init__(self, parent):

View File

@@ -1,7 +1,7 @@
from PyQt5.QtWidgets import QWidget, QVBoxLayout, QGridLayout, QPushButton, QTabWidget, QLabel
from editortool.map.chunkpanel import ChunkPanel
from editortool.map.entitypanel import EntityPanel
from editortool.map.regionpanel import RegionPanel
from tools.editortool.map.chunkpanel import ChunkPanel
from tools.editortool.map.entitypanel import EntityPanel
from tools.editortool.map.regionpanel import RegionPanel
class MapLeftPanel(QWidget):
def __init__(self, parent):

View File

@@ -1,7 +1,7 @@
import os
from PyQt5.QtWidgets import QAction, QMenuBar, QFileDialog
from PyQt5.QtGui import QKeySequence
from dusk.map import MAP_DEFAULT_PATH
from tools.dusk.map import MAP_DEFAULT_PATH
class MapMenubar:
def __init__(self, parent):

View File

@@ -1,7 +1,7 @@
from PyQt5.QtWidgets import QWidget, QVBoxLayout, QLabel, QComboBox, QHBoxLayout, QPushButton, QLineEdit, QListWidget, QListWidgetItem
from PyQt5.QtCore import Qt
from dusk.entity import Entity
from dusk.defs import CHUNK_WIDTH, CHUNK_HEIGHT, CHUNK_DEPTH, ENTITY_TYPES, ENTITY_TYPE_NULL
from tools.dusk.entity import Entity
from tools.dusk.defs import CHUNK_WIDTH, CHUNK_HEIGHT, CHUNK_DEPTH, ENTITY_TYPES, ENTITY_TYPE_NULL
class RegionPanel(QWidget):
def __init__(self, parent):

View File

@@ -1,7 +1,7 @@
import OpenGL.GL as gl
from dusk.defs import defs
from tools.dusk.defs import defs
import colorsys
from dusk.defs import TILE_WIDTH, TILE_HEIGHT, TILE_DEPTH
from tools.dusk.defs import TILE_WIDTH, TILE_HEIGHT, TILE_DEPTH
class SelectBox:
def __init__(self, parent):

View File

@@ -1,16 +1,16 @@
import os
from PyQt5.QtWidgets import QMainWindow, QWidget, QHBoxLayout, QMessageBox
from PyQt5.QtCore import Qt
from editortool.map.glwidget import GLWidget
from editortool.map.mapleftpanel import MapLeftPanel
from editortool.map.mapinfopanel import MapInfoPanel
from editortool.map.menubar import MapMenubar
from editortool.map.statusbar import StatusBar
from dusk.map import Map
from dusk.defs import CHUNK_WIDTH, CHUNK_HEIGHT, CHUNK_DEPTH, TILE_SHAPE_NULL, TILE_SHAPE_FLOOR
from editortool.map.selectbox import SelectBox
from editortool.map.camera import Camera
from editortool.map.grid import Grid
from tools.editortool.map.glwidget import GLWidget
from tools.editortool.map.mapleftpanel import MapLeftPanel
from tools.editortool.map.mapinfopanel import MapInfoPanel
from tools.editortool.map.menubar import MapMenubar
from tools.editortool.map.statusbar import StatusBar
from tools.dusk.map import Map
from tools.dusk.defs import CHUNK_WIDTH, CHUNK_HEIGHT, CHUNK_DEPTH, TILE_SHAPE_NULL, TILE_SHAPE_FLOOR
from tools.editortool.map.selectbox import SelectBox
from tools.editortool.map.camera import Camera
from tools.editortool.map.grid import Grid
class MapWindow(QMainWindow):
def __init__(self):

View File

@@ -0,0 +1,19 @@
# Copyright (c) 2026 Dominic Masters
#
# This software is released under the MIT License.
# https://opensource.org/licenses/MIT
# Copyright (c) 2026 Dominic Masters
#
# This software is released under the MIT License.
# https://opensource.org/licenses/MIT
function(dusk_run_python CMAKE_TARGET_NAME PYTHON_MODULE)
find_package(Python3 COMPONENTS Interpreter REQUIRED)
add_custom_target(${CMAKE_TARGET_NAME} ALL
WORKING_DIRECTORY ${DUSK_ROOT_DIR}
COMMAND
${Python3_EXECUTABLE} -m ${PYTHON_MODULE} ${ARGN}
)
endfunction()