(layout())->insertWidget(layout()->count() - 2, button);
}
void RadioPage::clear()
{
- while(layout()->count() > 1) // remove all but first (=text label)
+ for(auto* button : m_group->buttons())
{
- auto* item = layout()->itemAt(layout()->count() - 1);
- if(item->widget())
- {
- delete item->widget();
- }
- layout()->removeItem(item);
+ delete button;
}
}
+
+void RadioPage::setBottomText(const QString& text)
+{
+ m_bottomText->setText(text);
+ m_bottomText->setVisible(!text.isEmpty());
+}
diff --git a/client/src/wizard/page/radiopage.hpp b/client/src/wizard/page/radiopage.hpp
index 13cc175d..eb92f7f4 100644
--- a/client/src/wizard/page/radiopage.hpp
+++ b/client/src/wizard/page/radiopage.hpp
@@ -31,14 +31,17 @@ class RadioPage : public TextPage
{
protected:
QButtonGroup* m_group;
+ QLabel* m_bottomText;
public:
explicit RadioPage(QWidget* parent = nullptr);
int currentIndex() const;
- void addItem(const QString& label, bool disabled = false);
+ void addItem(const QString& label, bool checked = false, bool disabled = false);
void clear();
+
+ void setBottomText(const QString& text);
};
#endif
diff --git a/manual/buildluadoc.py b/manual/buildluadoc.py
index 309dab46..4603c1fc 100755
--- a/manual/buildluadoc.py
+++ b/manual/buildluadoc.py
@@ -20,6 +20,7 @@ class LuaDoc:
DEFAULT_LANGUAGE = 'en-us'
FILENAME_INDEX = 'index.html'
FILENAME_GLOBALS = 'globals.html'
+ FILENAME_PV = 'pv.html'
FILENAME_ENUM = 'enum.html'
FILENAME_SET = 'set.html'
FILENAME_OBJECT = 'object.html'
@@ -30,6 +31,7 @@ class LuaDoc:
version = None
def __init__(self, project_root: str) -> None:
+ self._project_root = project_root
self._globals = LuaDoc._find_globals(project_root)
self._enums = LuaDoc._find_enums(project_root)
self._sets = LuaDoc._find_sets(project_root)
@@ -52,6 +54,7 @@ class LuaDoc:
definition = self._terms[term]
definition = re.sub(r'`(.+?)`', r'\1', definition)
+ definition = re.sub(r'\[([^\]]+)]\(([^\)]+)\)', r'\1', definition)
definition = re.sub(r'{ref:([a-z0-9_\.]+?)(|#[a-z0-9_]+)(|\|.+?)}', self._ref_link, definition)
return definition
@@ -100,6 +103,16 @@ class LuaDoc:
for object in self._objects:
if object['lua_name'] == id:
return '' + (self._get_term(object['name']) if title == '' else title) + ''
+ elif id == 'globals':
+ return '' + (self._get_term('globals:title') if title == '' else title) + ''
+ elif id == 'enum':
+ return '' + (self._get_term('enum:title') if title == '' else title) + ''
+ elif id == 'set':
+ return '' + (self._get_term('set:title') if title == '' else title) + ''
+ elif id == 'object':
+ return '' + (self._get_term('object:title') if title == '' else title) + ''
+ elif id == 'pv':
+ return '' + (self._get_term('pv:title') if title == '' else title) + ''
return '' + m.group(0) + ''
@@ -347,7 +360,7 @@ class LuaDoc:
hpp = LuaDoc._read_file(filename_hpp)
cpp = LuaDoc._read_file(filename_cpp) if os.path.exists(filename_cpp) else hpp
for cpp_type, cpp_template_type, cpp_item_name in re.findall(r'(Property|VectorProperty|ObjectProperty|ObjectVectorProperty|Method|Event)<(.*?)>\s+([A-Za-z0-9_]+);', hpp):
- m = re.search(cpp_item_name + r'({|\()\s*[\*]?this\s*,\s*"([a-z0-9_]+)"[^}]*(PropertyFlags::ScriptReadOnly|PropertyFlags::ScriptReadWrite|MethodFlags::ScriptCallable|EventFlags::Scriptable)[^}]*}', cpp)
+ m = re.search(cpp_item_name + r'({|\()\s*[\*]?this\s*,\s*"([a-z0-9_]+)".*?(PropertyFlags::ScriptReadOnly|PropertyFlags::ScriptReadWrite|MethodFlags::ScriptCallable|EventFlags::Scriptable)[^}]*}', cpp)
if m is None:
continue
@@ -446,6 +459,7 @@ class LuaDoc:
self._build_index(output_dir)
self._build_globals(output_dir, nav)
+ self._build_pv(output_dir, nav)
self._build_enums(output_dir, nav)
self._build_sets(output_dir, nav)
for _, lib in self._libs.items():
@@ -648,6 +662,30 @@ class LuaDoc:
html += self._build_items_html(self._globals, 'globals.')
LuaDoc._write_file(os.path.join(output_dir, LuaDoc.FILENAME_GLOBALS), self._add_toc(html))
+ def _build_pv(self, output_dir: str, nav: list) -> None:
+ title = self._get_term('pv:title')
+ html = self._get_header(title, nav + [{'title': title, 'href': LuaDoc.FILENAME_PV}])
+ html += '' + self._get_term('pv:paragraph_1') + '
' + os.linesep
+ html += '' + self._get_term('pv:paragraph_2') + '
' + os.linesep
+
+ html += '' + self._get_term('pv.storing:title') + '
' + os.linesep
+ html += '' + self._get_term('pv.storing:paragraph_1') + '
' + os.linesep
+ html += '' + highlight_lua(LuaDoc._read_file(os.path.join(self._project_root, 'manual', 'luadoc', 'example', 'pv', 'storingpersistentdata.lua'))) + '
'
+
+ html += '' + self._get_term('pv.retrieving:title') + '
' + os.linesep
+ html += '' + self._get_term('pv.retrieving:paragraph_1') + '
' + os.linesep
+ html += '' + highlight_lua(LuaDoc._read_file(os.path.join(self._project_root, 'manual', 'luadoc', 'example', 'pv', 'retrievingpersistentdata.lua'))) + '
'
+
+ html += '' + self._get_term('pv.deleting:title') + '
' + os.linesep
+ html += '' + self._get_term('pv.deleting:paragraph_1') + '
' + os.linesep
+ html += '' + highlight_lua(LuaDoc._read_file(os.path.join(self._project_root, 'manual', 'luadoc', 'example', 'pv', 'deletingpersistentdata.lua'))) + '
'
+
+ html += '' + self._get_term('pv.checking:title') + '
' + os.linesep
+ html += '' + self._get_term('pv.checking:paragraph_1') + '
' + os.linesep
+ html += '' + highlight_lua(LuaDoc._read_file(os.path.join(self._project_root, 'manual', 'luadoc', 'example', 'pv', 'checkingforpersistentdata.lua'))) + '
'
+
+ LuaDoc._write_file(os.path.join(output_dir, LuaDoc.FILENAME_PV), self._add_toc(html))
+
def _build_enums(self, output_dir: str, nav: list) -> None:
title = self._get_term('enum:title')
nav_enums = nav + [{'title': title, 'href': LuaDoc.FILENAME_ENUM}]
@@ -708,7 +746,6 @@ class LuaDoc:
category['title'] = self._get_term('object.category.' + key + ':title')
category['items'] = []
for object in category['objects']:
- print(object)
for item in items:
if item['id'] == object:
category['items'].append(item)
@@ -854,6 +891,7 @@ class LuaDoc:
def _get_header(self, title: str, nav: list) -> str:
menu = ' ' + self._get_term('globals:title') + '' + os.linesep
+ menu += ' ' + self._get_term('pv:title') + '' + os.linesep
for k in sorted(list(self._libs.keys()) + ['enum', 'set']):
if k == 'enum':
menu += ' ' + self._get_term('enum:title') + '' + os.linesep
diff --git a/manual/luadoc/example/pv/checkingforpersistentdata.lua b/manual/luadoc/example/pv/checkingforpersistentdata.lua
new file mode 100644
index 00000000..df83b85e
--- /dev/null
+++ b/manual/luadoc/example/pv/checkingforpersistentdata.lua
@@ -0,0 +1,6 @@
+if pv.freight_car_1 == nil then
+ pv.freight_car_1 = {
+ cargo = 'none',
+ destination = 'unset'
+ }
+end
diff --git a/manual/luadoc/example/pv/deletingpersistentdata.lua b/manual/luadoc/example/pv/deletingpersistentdata.lua
new file mode 100644
index 00000000..5e94ee05
--- /dev/null
+++ b/manual/luadoc/example/pv/deletingpersistentdata.lua
@@ -0,0 +1,4 @@
+pv.number = nil
+pv.title = nil
+pv.very_cool = nil
+pv.freight_car_1 = nil
diff --git a/manual/luadoc/example/pv/retrievingpersistentdata.lua b/manual/luadoc/example/pv/retrievingpersistentdata.lua
new file mode 100644
index 00000000..31d1f011
--- /dev/null
+++ b/manual/luadoc/example/pv/retrievingpersistentdata.lua
@@ -0,0 +1,9 @@
+log.debug(pv.number)
+log.debug(pv.title)
+log.debug(pv.very_cool)
+
+log.debug(pv.freight_car_1.cargo)
+
+for k, v in pairs(pv['freight_car_1']) do
+ log.debug(k, v)
+end
diff --git a/manual/luadoc/example/pv/storingpersistentdata.lua b/manual/luadoc/example/pv/storingpersistentdata.lua
new file mode 100644
index 00000000..014bf024
--- /dev/null
+++ b/manual/luadoc/example/pv/storingpersistentdata.lua
@@ -0,0 +1,8 @@
+pv.number = 42
+pv.title = 'Traintastic is awesome!'
+pv.very_cool = true
+
+pv['freight_car_1'] = {
+ cargo = 'grain',
+ destination = 'upper yard'
+}
diff --git a/manual/luadoc/globals.json b/manual/luadoc/globals.json
index ee67139c..920a80c2 100644
--- a/manual/luadoc/globals.json
+++ b/manual/luadoc/globals.json
@@ -67,6 +67,10 @@
"type": "constant",
"since": "0.1"
},
+ "pv": {
+ "type": "object",
+ "since": "0.3"
+ },
"world": {
"type": "object",
"since": "0.1"
@@ -102,4 +106,4 @@
"type": "library",
"since": "0.1"
}
-}
\ No newline at end of file
+}
diff --git a/manual/luadoc/object/blockrailtile.json b/manual/luadoc/object/blockrailtile.json
index c0413441..523fc76c 100644
--- a/manual/luadoc/object/blockrailtile.json
+++ b/manual/luadoc/object/blockrailtile.json
@@ -2,6 +2,7 @@
"name": {
"since": "0.3"
},
+ "trains": {},
"on_train_assigned": {
"parameters": [
{
@@ -64,4 +65,4 @@
],
"since": "0.3"
}
-}
\ No newline at end of file
+}
diff --git a/manual/luadoc/object/railvehicle.json b/manual/luadoc/object/railvehicle.json
index c4454edd..0214173e 100644
--- a/manual/luadoc/object/railvehicle.json
+++ b/manual/luadoc/object/railvehicle.json
@@ -1,3 +1,4 @@
{
- "active_train": {}
-}
\ No newline at end of file
+ "active_train": {},
+ "trains": {}
+}
diff --git a/manual/luadoc/object/train.json b/manual/luadoc/object/train.json
index c2f9ebec..f32d7b5e 100644
--- a/manual/luadoc/object/train.json
+++ b/manual/luadoc/object/train.json
@@ -6,6 +6,7 @@
"powered": {},
"active": {},
"mode": {},
+ "blocks": {},
"on_block_assigned": {
"parameters": [
{
diff --git a/manual/luadoc/object/turnoutsliprailtile.json b/manual/luadoc/object/turnoutsliprailtile.json
new file mode 100644
index 00000000..a0b9173d
--- /dev/null
+++ b/manual/luadoc/object/turnoutsliprailtile.json
@@ -0,0 +1,3 @@
+{
+ "dual_motor": {}
+}
diff --git a/manual/luadoc/terms/en-us.json b/manual/luadoc/terms/en-us.json
index bbfb2c53..8c2774c5 100644
--- a/manual/luadoc/terms/en-us.json
+++ b/manual/luadoc/terms/en-us.json
@@ -2006,5 +2006,77 @@
{
"term": "object.train.on_block_reserved.parameter.direction:description",
"definition": "Train direction from the block perspective, a {ref:enum.block_train_direction} value."
+ },
+ {
+ "term": "object.turnoutsliprailtile.dual_motor:description",
+ "definition": "`true` if the slip turnout has two motors/coils, `false` if it has one motor/coil."
+ },
+ {
+ "term": "object.turnoutrailtile.name:description",
+ "definition": "Turnout name."
+ },
+ {
+ "term": "object.turnoutrailtile.position:description",
+ "definition": "Current turnout position, a {ref:enum.turnout_position} value. To change the turnout position call [`set_position`](#set_position)."
+ },
+ {
+ "term": "object.turnoutrailtile.set_position:description",
+ "definition": "Change turnout position."
+ },
+ {
+ "term": "object.turnoutrailtile.set_position.parameter.position:description",
+ "definition": "Requested turnout position, a {ref:enum.turnout_position} value."
+ },
+ {
+ "term": "object.turnoutrailtile.set_position:return_values",
+ "definition": "`true` if the position is changed, `false` if position value is invalid or turnout is locked e.g. due to a reserved path."
+ },
+ {
+ "term": "globals.pv:description",
+ "definition": "The {ref:pv|persistent variable} table."
+ },
+ {
+ "term": "pv:title",
+ "definition": "Persistent variables"
+ },
+ {
+ "term": "pv:paragraph_1",
+ "definition": "Persistent variables allow you to store and retrieve data that remains available across multiple executions of the Lua script. This can be particularly useful for maintaining state information that needs to be retained beyond the current script's lifetime."
+ },
+ {
+ "term": "pv:paragraph_2",
+ "definition": "The {ref:globals#pv|`pv`} global provides a simple and efficient interface for interacting with persistent data. Any values stored in {ref:globals#pv|`pv`} are saved across script executions and world save and loads. Below is a detailed breakdown of how to use the {ref:globals#pv|`pv`} global."
+ },
+ {
+ "term": "pv.storing:title",
+ "definition": "Storing persistent data"
+ },
+ {
+ "term": "pv.storing:paragraph_1",
+ "definition": "You can store data in {ref:globals#pv|`pv`} just like you would with a regular Lua table. Supported data types are numbers, strings, booleans, tables, {ref:enum|enums}, {ref:set|sets}, {ref:object|objects} and object methods."
+ },
+ {
+ "term": "pv.retrieving:title",
+ "definition": "Retrieving persistent data"
+ },
+ {
+ "term": "pv.retrieving:paragraph_1",
+ "definition": "To retrieve a previously stored value, including tables, access the corresponding key in the {ref:globals#pv|`pv`} global:"
+ },
+ {
+ "term": "pv.deleting:title",
+ "definition": "Deleting persistent data"
+ },
+ {
+ "term": "pv.deleting:paragraph_1",
+ "definition": "To delete a stored persistent value, including tables, simply assign `nil` to the desired key:"
+ },
+ {
+ "term": "pv.checking:title",
+ "definition": "Checking for persistent data"
+ },
+ {
+ "term": "pv.checking:paragraph_1",
+ "definition": "To determine if a persistent variable has been set, use an `if` statement with `nil` checks. Variables in {ref:globals#pv|`pv`} that haven't been initialized or have been deleted will return `nil`. This pattern is useful for initializing default values or handling cases where the persistent variables are cleared."
}
]
diff --git a/manual/traintasticmanual/css/traintasticmanual.css b/manual/traintasticmanual/css/traintasticmanual.css
index bae21a8d..789974bc 100644
--- a/manual/traintasticmanual/css/traintasticmanual.css
+++ b/manual/traintasticmanual/css/traintasticmanual.css
@@ -320,7 +320,8 @@ ul.index-az-nav li a
/** Lua **********************************************************************/
-pre[lang="lua"]
+pre[lang="lua"],
+pre[lang="bash"]
{
border: solid 1px darkgray;
background-color: #f8f8f8;
@@ -423,7 +424,8 @@ pre[lang="lua"] code span.function
border-left-color: blue;
}
- pre[lang="lua"]
+ pre[lang="lua"],
+ pre[lang="bash"]
{
border-color: gray;
background-color: #222;
diff --git a/manual/traintasticmanual/en-us/gettingstarted/start-client.md b/manual/traintasticmanual/en-us/gettingstarted/start-client.md
index 7a49dce9..fd5670fd 100644
--- a/manual/traintasticmanual/en-us/gettingstarted/start-client.md
+++ b/manual/traintasticmanual/en-us/gettingstarted/start-client.md
@@ -4,7 +4,7 @@ When Traintastic server is running the Traintastic client can be started.
**Windows:** The Traintastic client can be started using the desktop icon (if installed) or by selecting *Traintastic* -> *Traintastic client* from the Windows start menu.
-**Linux:** TODO
+**Linux:** To start Traintastic client, open your desktop environment's application launcher, search for "Traintastic Client," and select it from the list.
## Connect to the server
diff --git a/manual/traintasticmanual/en-us/gettingstarted/start-server.md b/manual/traintasticmanual/en-us/gettingstarted/start-server.md
index 82bc6519..bf60644b 100644
--- a/manual/traintasticmanual/en-us/gettingstarted/start-server.md
+++ b/manual/traintasticmanual/en-us/gettingstarted/start-server.md
@@ -2,13 +2,36 @@
When running Traintastic, the server should be started first.
-**Windows:** The Traintastic server can be started using the desktop icon (if installed) or by selecting *Traintastic* -> *Traintastic server* from the Windows start menu.
+## Windows
+The Traintastic server can be started using the desktop icon (if installed) or by selecting *Traintastic* -> *Traintastic server* from the Windows start menu.
Traintastic server runs as background process, a Traintastic icon will appear in the system tray next to the clock.
A Windows notification is displayed when it is running in the background.
Traintastic server can be quit by clicking on the Traintastic icon and selecting *Quit* from the popup menu.

-**Linux:** TODO
+## Linux
+When installing Traintastic server using a Debian package it is installed as systemd service.
-When Traintastic server is running proceed to [start Traintastic client](start-client.md).
+To start the Traintastic server using systemd, open a terminal and run the command:
+```bash
+sudo systemctl start traintastic-server.service
+```
+
+To stop the service, use the command:
+```bash
+sudo systemctl stop traintastic-server.service
+```
+
+Ensure you have the necessary permissions (typically root) to manage systemd services.
+
+### Auto start on system boot
+To enable Traintastic server to start automatically at boot, run the following command in a terminal:
+```bash
+sudo systemctl enable traintastic-server.service
+```
+
+To disable automatic start at boot, run the following command in a terminal:
+```bash
+sudo systemctl disable traintastic-server.service
+```
diff --git a/manual/traintasticmanualbuilder/utils.py b/manual/traintasticmanualbuilder/utils.py
index 4db7acf3..11a6f5a7 100644
--- a/manual/traintasticmanualbuilder/utils.py
+++ b/manual/traintasticmanualbuilder/utils.py
@@ -25,7 +25,7 @@ def highlight_replace(code: str, css_class: str, clickable_links: bool = False)
def highlight_lua(code: str) -> str:
- code = re.sub(r'\b(math|table|string|class|enum|set|log|world)\b', r'\1', code) # globals
+ code = re.sub(r'\b(math|table|string|class|enum|set|log|world|pv)\b', r'\1', code) # globals
code = re.sub(r'\b([A-Z_][A-Z0-9_]*)\b', r'\1', code) # CONSTANTS
code = re.sub(r'\b(and|break|do|else|elseif|end|false|for|function|goto|if|in|local|nil|not|or|repeat|return|then|true|until|while)\b', r'\1', code) # keywords
code = re.sub(r'\b((|-|\+)[0-9]+(\\.[0-9]*|)((e|E)(|-|\+)[0-9]+|))\b', r'\1', code) # numbers: infloat, decimal
diff --git a/package/innosetup/traintastic.iss b/package/innosetup/traintastic.iss
index 23082a2f..60a05102 100644
--- a/package/innosetup/traintastic.iss
+++ b/package/innosetup/traintastic.iss
@@ -54,7 +54,7 @@ Name: "firewall_wlanmaus"; Description: "{cm:firewall_allow_wlanmaus_z21}"; Grou
[Files]
; Server
Source: "..\..\server\build\{#ServerExeName}"; DestDir: "{app}\server"; Flags: ignoreversion; Check: InstallServer
-Source: "..\..\server\thirdparty\lua5.3\bin\win64\lua53.dll"; DestDir: "{app}\server"; Flags: ignoreversion; Check: InstallServer
+Source: "..\..\server\thirdparty\lua5.4\bin\win64\lua54.dll"; DestDir: "{app}\server"; Flags: ignoreversion; Check: InstallServer
Source: "..\..\server\thirdparty\libarchive\bin\archive.dll"; DestDir: "{app}\server"; Flags: ignoreversion; Check: InstallServer
Source: "..\..\server\thirdparty\zlib\bin\zlib1.dll"; DestDir: "{app}\server"; Flags: ignoreversion; Check: InstallServer
; Client
@@ -107,7 +107,7 @@ Root: HKLM; Subkey: "{#CompanySubKey}"; Flags: uninsdeletekeyifempty
Root: HKLM; Subkey: "{#AppSubKey}"; Flags: uninsdeletekey
[INI]
-Filename: {commonappdata}\traintastic\traintastic-client.ini; Section: general_; Key: language; String: {code:GetTraintasticClientLanguage}; Flags: uninsdeleteentry uninsdeletesectionifempty;
+Filename: {commonappdata}\traintastic\traintastic-client.ini; Section: general_; Key: language; String: {code:GetTraintasticLanguage}; Flags: uninsdeleteentry uninsdeletesectionifempty;
[Code]
const
@@ -179,6 +179,7 @@ begin
ClientAndServerRadioButton.Checked := (Components = 'ClientAndServer');
ClientAndServerRadioButton.Font.Style := [fsBold];
ClientAndServerRadioButton.Height := ScaleY(23);
+ ClientAndServerRadioButton.Width := ComponentsPage.SurfaceWidth;
ClientAndServerRadioButton.Parent := ComponentsPage.Surface;
ClientAndServerRadioButton.OnClick := @ComponentRadioButtonClick;
@@ -195,6 +196,7 @@ begin
ClientOnlyRadioButton.Font.Style := [fsBold];
ClientOnlyRadioButton.Top := Lbl.Top + Lbl.Height + ScaleY(10);
ClientOnlyRadioButton.Height := ScaleY(23);
+ ClientOnlyRadioButton.Width := ComponentsPage.SurfaceWidth;
ClientOnlyRadioButton.Parent := ComponentsPage.Surface;
ClientOnlyRadioButton.OnClick := @ComponentRadioButtonClick;
@@ -206,7 +208,7 @@ begin
Lbl.Parent := ComponentsPage.Surface;
end;
-function GetTraintasticClientLanguage(Param: String) : String;
+function GetTraintasticLanguage(Param: String) : String;
begin
case ActiveLanguage of
'nl': Result := 'nl-nl';
@@ -219,6 +221,19 @@ begin
end;
end;
+procedure CurStepChanged(CurStep: TSetupStep);
+var
+ ServerSettingsFile: String;
+begin
+ if CurStep = ssPostInstall then begin
+ // Server: only write language if there is no setting file yet:
+ ServerSettingsFile := ExpandConstant('{localappdata}\traintastic\server\settings.json');
+ if not FileExists(ServerSettingsFile) then begin
+ SaveStringToFile(ServerSettingsFile, '{"language":"' + GetTraintasticLanguage('') + '"}', False);
+ end;
+ end
+end;
+
function VC2019RedistNeedsInstall: Boolean;
var
Version: String;
diff --git a/server/CMakeLists.txt b/server/CMakeLists.txt
index 0126a335..4536b3ff 100644
--- a/server/CMakeLists.txt
+++ b/server/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 3.9)
+cmake_minimum_required(VERSION 3.15)
include(../shared/traintastic.cmake)
project(traintastic-server VERSION ${TRAINTASTIC_VERSION} DESCRIPTION "Traintastic server")
include(GNUInstallDirs)
@@ -32,16 +32,25 @@ target_include_directories(traintastic-server SYSTEM PRIVATE
thirdparty)
if(BUILD_TESTING)
+ add_subdirectory(thirdparty/catch2)
+ set_target_properties(Catch2 PROPERTIES
+ CXX_STANDARD 20
+ CXX_CLANG_TIDY ""
+ )
add_executable(traintastic-server-test test/main.cpp)
add_dependencies(traintastic-server-test traintastic-lang)
target_compile_definitions(traintastic-server-test PRIVATE -DTRAINTASTIC_TEST)
- set_target_properties(traintastic-server-test PROPERTIES CXX_STANDARD 20)
+ set_target_properties(traintastic-server-test PROPERTIES
+ CXX_STANDARD 20
+ CXX_CLANG_TIDY ""
+ )
target_include_directories(traintastic-server-test PRIVATE
${CMAKE_CURRENT_BINARY_DIR}
../shared/src)
target_include_directories(traintastic-server-test SYSTEM PRIVATE
../shared/thirdparty
thirdparty)
+ target_link_libraries(traintastic-server-test PRIVATE Catch2::Catch2WithMain)
endif()
file(GLOB SOURCES
@@ -55,6 +64,7 @@ file(GLOB SOURCES
"src/board/nx/*.cpp"
"src/board/tile/*.hpp"
"src/board/tile/*.cpp"
+ "src/board/tile/hidden/*.cpp"
"src/board/tile/misc/*.hpp"
"src/board/tile/misc/*.cpp"
"src/board/tile/rail/*.hpp"
@@ -184,6 +194,30 @@ file(GLOB TEST_SOURCES
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -DENABLE_LOG_DEBUG")
+### VCPKG
+if(DEFINED ENV{VCPKG_ROOT})
+ message(STATUS "Using VCPKG (VCPKG_ROOT=$ENV{VCPKG_ROOT})")
+ if(WIN32)
+ set(VCPKG_TARGET_TRIPLET "x64-windows-static-md")
+ endif()
+ include($ENV{VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake)
+endif()
+
+### RESOURCES ###
+
+include(cmake/add-resource.cmake)
+
+add_resource(resource-shared
+ BASE_DIR ../
+ FILES
+ shared/gfx/appicon.ico
+)
+
+add_dependencies(traintastic-server resource-shared)
+if(BUILD_TESTING)
+ add_dependencies(traintastic-server-test resource-shared)
+endif()
+
### OPTIONS ###
if(NO_LOCALHOST_ONLY_SETTING)
@@ -215,6 +249,9 @@ if(LINUX)
if(BUILD_TESTING)
target_link_libraries(traintastic-server-test PRIVATE PkgConfig::LIBSYSTEMD)
endif()
+ else()
+ # Use inotify for monitoring serial ports:
+ list(APPEND SOURCES "src/os/linux/serialportlistimplinotify.hpp" "src/os/linux/serialportlistimplinotify.cpp")
endif()
else()
# socket CAN is only available on linux:
@@ -272,47 +309,12 @@ if(WIN32 AND NOT MSVC)
endif()
# boost
-if(LINUX)
- find_package(Boost 1.71 REQUIRED COMPONENTS program_options)
-
- target_include_directories(traintastic-server SYSTEM PRIVATE ${Boost_INCLUDE_DIRS})
- target_link_libraries(traintastic-server PRIVATE ${Boost_LIBRARIES})
- if(BUILD_TESTING)
- target_include_directories(traintastic-server-test SYSTEM PRIVATE ${Boost_INCLUDE_DIRS})
- target_link_libraries(traintastic-server-test PRIVATE ${Boost_LIBRARIES})
- endif()
-else()
- add_definitions(
- -DBOOST_ALL_NO_LIB
- -DBOOST_ERROR_CODE_HEADER_ONLY
- -DBOOST_CHRONO_HEADER_ONLY
- -DBOOST_ASIO_HEADER_ONLY
- -DBOOST_SYSTEM_NO_DEPRECATED)
-
- if(NOT MSVC)
- set_source_files_properties(
- thirdparty/boost/libs/program_options/src/cmdline.cpp
- thirdparty/boost/libs/program_options/src/config_file.cpp
- thirdparty/boost/libs/program_options/src/convert.cpp
- thirdparty/boost/libs/program_options/src/options_description.cpp
- thirdparty/boost/libs/program_options/src/parsers.cpp
- thirdparty/boost/libs/program_options/src/positional_options.cpp
- thirdparty/boost/libs/program_options/src/split.cpp
- thirdparty/boost/libs/program_options/src/utf8_codecvt_facet.cpp
- thirdparty/boost/libs/program_options/src/value_semantic.cpp
- thirdparty/boost/libs/program_options/src/variables_map.cpp
- thirdparty/boost/libs/program_options/src/winmain.cpp
- PROPERTIES
- COMPILE_FLAGS -Wno-shadow)
- endif()
-
- target_include_directories(traintastic-server SYSTEM PRIVATE thirdparty/boost)
- if(BUILD_TESTING)
- target_include_directories(traintastic-server-test SYSTEM PRIVATE thirdparty/boost)
- endif()
-
- file(GLOB SOURCES_BOOST "thirdparty/boost/libs/program_options/src/*.cpp")
- list(APPEND SOURCES ${SOURCES_BOOST})
+find_package(Boost 1.81 REQUIRED COMPONENTS program_options)
+target_include_directories(traintastic-server SYSTEM PRIVATE ${Boost_INCLUDE_DIRS})
+target_link_libraries(traintastic-server PRIVATE ${Boost_LIBRARIES})
+if(BUILD_TESTING)
+ target_include_directories(traintastic-server-test SYSTEM PRIVATE ${Boost_INCLUDE_DIRS})
+ target_link_libraries(traintastic-server-test PRIVATE ${Boost_LIBRARIES})
endif()
# zlib
@@ -384,41 +386,41 @@ if(BUILD_TESTING)
target_link_libraries(traintastic-server-test PRIVATE ${LibArchive_LIBRARIES})
endif()
-# liblua5.3
+# liblua5.4
if(WIN32)
add_definitions(-DLUA_BUILD_AS_DLL)
- set(LUA_INCLUDE_DIR "thirdparty/lua5.3/include")
+ set(LUA_INCLUDE_DIR "thirdparty/lua5.4/include")
if(MSVC)
- set(LUA_LIBRARIES lua53)
+ set(LUA_LIBRARIES lua54)
add_custom_command(TARGET traintastic-server PRE_LINK
- COMMAND lib "/def:${PROJECT_SOURCE_DIR}/thirdparty/lua5.3/bin/win64/lua53.def" /out:lua53.lib /machine:x64)
+ COMMAND lib "/def:${PROJECT_SOURCE_DIR}/thirdparty/lua5.4/bin/win64/lua54.def" /out:lua54.lib /machine:x64)
add_custom_command(TARGET traintastic-server-test PRE_LINK
- COMMAND lib "/def:${PROJECT_SOURCE_DIR}/thirdparty/lua5.3/bin/win64/lua53.def" /out:lua53.lib /machine:x64)
+ COMMAND lib "/def:${PROJECT_SOURCE_DIR}/thirdparty/lua5.4/bin/win64/lua54.def" /out:lua54.lib /machine:x64)
else()
# MinGW can directly link .dll without import lib
- set(LUA_LIBRARIES "${PROJECT_SOURCE_DIR}/thirdparty/lua5.3/bin/win64/lua53.dll")
+ set(LUA_LIBRARIES "${PROJECT_SOURCE_DIR}/thirdparty/lua5.4/bin/win64/lua54.dll")
endif()
- # copy lua53.dll to build directory, to be able to run the tests:
+ # copy lua54.dll to build directory, to be able to run the tests:
add_custom_command(TARGET traintastic-server-test POST_BUILD
- COMMAND ${CMAKE_COMMAND} -E copy "${PROJECT_SOURCE_DIR}/thirdparty/lua5.3/bin/win64/lua53.dll" .)
+ COMMAND ${CMAKE_COMMAND} -E copy "${PROJECT_SOURCE_DIR}/thirdparty/lua5.4/bin/win64/lua54.dll" .)
elseif(APPLE)
find_path(LUA_INCLUDE_DIR
NAMES lua.h
PATHS
- "/usr/local/opt/lua@5.3/include/lua" # x86_64
- "/opt/homebrew/opt/lua@5.3/include/lua" # arm64
+ "/usr/local/opt/lua@5.4/include/lua" # x86_64
+ "/opt/homebrew/opt/lua@5.4/include/lua" # arm64
)
find_library(LUA_LIBRARIES
- NAMES lua5.3 liblua5.3
+ NAMES lua5.4 liblua5.4
PATHS
- "/usr/local/opt/lua@5.3/lib" # x86_64
- "/opt/homebrew/opt/lua@5.3/lib" # arm64
+ "/usr/local/opt/lua@5.4/lib" # x86_64
+ "/opt/homebrew/opt/lua@5.4/lib" # arm64
)
else()
- find_package(Lua 5.3 REQUIRED)
+ find_package(Lua 5.4 REQUIRED)
endif()
target_include_directories(traintastic-server PRIVATE ${LUA_INCLUDE_DIR})
target_link_libraries(traintastic-server PRIVATE ${LUA_LIBRARIES})
@@ -457,8 +459,7 @@ endif()
if(BUILD_TESTING)
include(Catch)
- target_include_directories(traintastic-server-test PRIVATE thirdparty/catch2)
- catch_discover_tests(traintastic-server-test)
+ catch_discover_tests(traintastic-server-test DISCOVERY_MODE PRE_TEST)
endif()
### Doxygen ###
diff --git a/server/FindLua.cmake b/server/FindLua.cmake
index dc85e8e2..782f648d 100644
--- a/server/FindLua.cmake
+++ b/server/FindLua.cmake
@@ -17,6 +17,7 @@
# Copyright 2007-2009 Kitware, Inc.
# Modified to support Lua 5.2 by LuaDist 2012
# Modified to support Lua 5.3 by Reinder Feenstra 2019
+# Modified to support Lua 5.4 by Reinder Feenstra 2024
#
# Distributed under the OSI-approved BSD License (the "License");
# see accompanying file Copyright.txt for details.
@@ -41,7 +42,7 @@ SET(_POSSIBLE_LUA_LIBRARY lua)
IF(Lua_FIND_VERSION_MAJOR AND Lua_FIND_VERSION_MINOR)
SET(_POSSIBLE_SUFFIXES "${Lua_FIND_VERSION_MAJOR}${Lua_FIND_VERSION_MINOR}" "${Lua_FIND_VERSION_MAJOR}.${Lua_FIND_VERSION_MINOR}" "-${Lua_FIND_VERSION_MAJOR}.${Lua_FIND_VERSION_MINOR}")
ELSE(Lua_FIND_VERSION_MAJOR AND Lua_FIND_VERSION_MINOR)
- SET(_POSSIBLE_SUFFIXES "53" "5.3" "-5.3" "52" "5.2" "-5.2" "51" "5.1" "-5.1")
+ SET(_POSSIBLE_SUFFIXES "54" "5.4" "-5.4" "53" "5.3" "-5.3" "52" "5.2" "-5.2" "51" "5.1" "-5.1")
ENDIF(Lua_FIND_VERSION_MAJOR AND Lua_FIND_VERSION_MINOR)
# Set up possible search names and locations
diff --git a/server/cmake/Catch.cmake b/server/cmake/Catch.cmake
deleted file mode 100644
index a3885162..00000000
--- a/server/cmake/Catch.cmake
+++ /dev/null
@@ -1,206 +0,0 @@
-# Distributed under the OSI-approved BSD 3-Clause License. See accompanying
-# file Copyright.txt or https://cmake.org/licensing for details.
-
-#[=======================================================================[.rst:
-Catch
------
-
-This module defines a function to help use the Catch test framework.
-
-The :command:`catch_discover_tests` discovers tests by asking the compiled test
-executable to enumerate its tests. This does not require CMake to be re-run
-when tests change. However, it may not work in a cross-compiling environment,
-and setting test properties is less convenient.
-
-This command is intended to replace use of :command:`add_test` to register
-tests, and will create a separate CTest test for each Catch test case. Note
-that this is in some cases less efficient, as common set-up and tear-down logic
-cannot be shared by multiple test cases executing in the same instance.
-However, it provides more fine-grained pass/fail information to CTest, which is
-usually considered as more beneficial. By default, the CTest test name is the
-same as the Catch name; see also ``TEST_PREFIX`` and ``TEST_SUFFIX``.
-
-.. command:: catch_discover_tests
-
- Automatically add tests with CTest by querying the compiled test executable
- for available tests::
-
- catch_discover_tests(target
- [TEST_SPEC arg1...]
- [EXTRA_ARGS arg1...]
- [WORKING_DIRECTORY dir]
- [TEST_PREFIX prefix]
- [TEST_SUFFIX suffix]
- [PROPERTIES name1 value1...]
- [TEST_LIST var]
- [REPORTER reporter]
- [OUTPUT_DIR dir]
- [OUTPUT_PREFIX prefix}
- [OUTPUT_SUFFIX suffix]
- )
-
- ``catch_discover_tests`` sets up a post-build command on the test executable
- that generates the list of tests by parsing the output from running the test
- with the ``--list-test-names-only`` argument. This ensures that the full
- list of tests is obtained. Since test discovery occurs at build time, it is
- not necessary to re-run CMake when the list of tests changes.
- However, it requires that :prop_tgt:`CROSSCOMPILING_EMULATOR` is properly set
- in order to function in a cross-compiling environment.
-
- Additionally, setting properties on tests is somewhat less convenient, since
- the tests are not available at CMake time. Additional test properties may be
- assigned to the set of tests as a whole using the ``PROPERTIES`` option. If
- more fine-grained test control is needed, custom content may be provided
- through an external CTest script using the :prop_dir:`TEST_INCLUDE_FILES`
- directory property. The set of discovered tests is made accessible to such a
- script via the ``_TESTS`` variable.
-
- The options are:
-
- ``target``
- Specifies the Catch executable, which must be a known CMake executable
- target. CMake will substitute the location of the built executable when
- running the test.
-
- ``TEST_SPEC arg1...``
- Specifies test cases, wildcarded test cases, tags and tag expressions to
- pass to the Catch executable with the ``--list-test-names-only`` argument.
-
- ``EXTRA_ARGS arg1...``
- Any extra arguments to pass on the command line to each test case.
-
- ``WORKING_DIRECTORY dir``
- Specifies the directory in which to run the discovered test cases. If this
- option is not provided, the current binary directory is used.
-
- ``TEST_PREFIX prefix``
- Specifies a ``prefix`` to be prepended to the name of each discovered test
- case. This can be useful when the same test executable is being used in
- multiple calls to ``catch_discover_tests()`` but with different
- ``TEST_SPEC`` or ``EXTRA_ARGS``.
-
- ``TEST_SUFFIX suffix``
- Similar to ``TEST_PREFIX`` except the ``suffix`` is appended to the name of
- every discovered test case. Both ``TEST_PREFIX`` and ``TEST_SUFFIX`` may
- be specified.
-
- ``PROPERTIES name1 value1...``
- Specifies additional properties to be set on all tests discovered by this
- invocation of ``catch_discover_tests``.
-
- ``TEST_LIST var``
- Make the list of tests available in the variable ``var``, rather than the
- default ``_TESTS``. This can be useful when the same test
- executable is being used in multiple calls to ``catch_discover_tests()``.
- Note that this variable is only available in CTest.
-
- ``REPORTER reporter``
- Use the specified reporter when running the test case. The reporter will
- be passed to the Catch executable as ``--reporter reporter``.
-
- ``OUTPUT_DIR dir``
- If specified, the parameter is passed along as
- ``--out dir/`` to Catch executable. The actual file name is the
- same as the test name. This should be used instead of
- ``EXTRA_ARGS --out foo`` to avoid race conditions writing the result output
- when using parallel test execution.
-
- ``OUTPUT_PREFIX prefix``
- May be used in conjunction with ``OUTPUT_DIR``.
- If specified, ``prefix`` is added to each output file name, like so
- ``--out dir/prefix``.
-
- ``OUTPUT_SUFFIX suffix``
- May be used in conjunction with ``OUTPUT_DIR``.
- If specified, ``suffix`` is added to each output file name, like so
- ``--out dir/suffix``. This can be used to add a file extension to
- the output e.g. ".xml".
-
-#]=======================================================================]
-
-#------------------------------------------------------------------------------
-function(catch_discover_tests TARGET)
- cmake_parse_arguments(
- ""
- ""
- "TEST_PREFIX;TEST_SUFFIX;WORKING_DIRECTORY;TEST_LIST;REPORTER;OUTPUT_DIR;OUTPUT_PREFIX;OUTPUT_SUFFIX"
- "TEST_SPEC;EXTRA_ARGS;PROPERTIES"
- ${ARGN}
- )
-
- if(NOT _WORKING_DIRECTORY)
- set(_WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}")
- endif()
- if(NOT _TEST_LIST)
- set(_TEST_LIST ${TARGET}_TESTS)
- endif()
-
- ## Generate a unique name based on the extra arguments
- string(SHA1 args_hash "${_TEST_SPEC} ${_EXTRA_ARGS} ${_REPORTER} ${_OUTPUT_DIR} ${_OUTPUT_PREFIX} ${_OUTPUT_SUFFIX}")
- string(SUBSTRING ${args_hash} 0 7 args_hash)
-
- # Define rule to generate test list for aforementioned test executable
- set(ctest_include_file "${CMAKE_CURRENT_BINARY_DIR}/${TARGET}_include-${args_hash}.cmake")
- set(ctest_tests_file "${CMAKE_CURRENT_BINARY_DIR}/${TARGET}_tests-${args_hash}.cmake")
- get_property(crosscompiling_emulator
- TARGET ${TARGET}
- PROPERTY CROSSCOMPILING_EMULATOR
- )
- add_custom_command(
- TARGET ${TARGET} POST_BUILD
- BYPRODUCTS "${ctest_tests_file}"
- COMMAND "${CMAKE_COMMAND}"
- -D "TEST_TARGET=${TARGET}"
- -D "TEST_EXECUTABLE=$"
- -D "TEST_EXECUTOR=${crosscompiling_emulator}"
- -D "TEST_WORKING_DIR=${_WORKING_DIRECTORY}"
- -D "TEST_SPEC=${_TEST_SPEC}"
- -D "TEST_EXTRA_ARGS=${_EXTRA_ARGS}"
- -D "TEST_PROPERTIES=${_PROPERTIES}"
- -D "TEST_PREFIX=${_TEST_PREFIX}"
- -D "TEST_SUFFIX=${_TEST_SUFFIX}"
- -D "TEST_LIST=${_TEST_LIST}"
- -D "TEST_REPORTER=${_REPORTER}"
- -D "TEST_OUTPUT_DIR=${_OUTPUT_DIR}"
- -D "TEST_OUTPUT_PREFIX=${_OUTPUT_PREFIX}"
- -D "TEST_OUTPUT_SUFFIX=${_OUTPUT_SUFFIX}"
- -D "CTEST_FILE=${ctest_tests_file}"
- -P "${_CATCH_DISCOVER_TESTS_SCRIPT}"
- VERBATIM
- )
-
- file(WRITE "${ctest_include_file}"
- "if(EXISTS \"${ctest_tests_file}\")\n"
- " include(\"${ctest_tests_file}\")\n"
- "else()\n"
- " add_test(${TARGET}_NOT_BUILT-${args_hash} ${TARGET}_NOT_BUILT-${args_hash})\n"
- "endif()\n"
- )
-
- if(NOT ${CMAKE_VERSION} VERSION_LESS "3.10.0")
- # Add discovered tests to directory TEST_INCLUDE_FILES
- set_property(DIRECTORY
- APPEND PROPERTY TEST_INCLUDE_FILES "${ctest_include_file}"
- )
- else()
- # Add discovered tests as directory TEST_INCLUDE_FILE if possible
- get_property(test_include_file_set DIRECTORY PROPERTY TEST_INCLUDE_FILE SET)
- if (NOT ${test_include_file_set})
- set_property(DIRECTORY
- PROPERTY TEST_INCLUDE_FILE "${ctest_include_file}"
- )
- else()
- message(FATAL_ERROR
- "Cannot set more than one TEST_INCLUDE_FILE"
- )
- endif()
- endif()
-
-endfunction()
-
-###############################################################################
-
-set(_CATCH_DISCOVER_TESTS_SCRIPT
- ${CMAKE_CURRENT_LIST_DIR}/CatchAddTests.cmake
- CACHE INTERNAL "Catch2 full path to CatchAddTests.cmake helper file"
-)
diff --git a/server/cmake/CatchAddTests.cmake b/server/cmake/CatchAddTests.cmake
deleted file mode 100644
index 18286b71..00000000
--- a/server/cmake/CatchAddTests.cmake
+++ /dev/null
@@ -1,132 +0,0 @@
-# Distributed under the OSI-approved BSD 3-Clause License. See accompanying
-# file Copyright.txt or https://cmake.org/licensing for details.
-
-set(prefix "${TEST_PREFIX}")
-set(suffix "${TEST_SUFFIX}")
-set(spec ${TEST_SPEC})
-set(extra_args ${TEST_EXTRA_ARGS})
-set(properties ${TEST_PROPERTIES})
-set(reporter ${TEST_REPORTER})
-set(output_dir ${TEST_OUTPUT_DIR})
-set(output_prefix ${TEST_OUTPUT_PREFIX})
-set(output_suffix ${TEST_OUTPUT_SUFFIX})
-set(script)
-set(suite)
-set(tests)
-
-function(add_command NAME)
- set(_args "")
- foreach(_arg ${ARGN})
- if(_arg MATCHES "[^-./:a-zA-Z0-9_]")
- set(_args "${_args} [==[${_arg}]==]") # form a bracket_argument
- else()
- set(_args "${_args} ${_arg}")
- endif()
- endforeach()
- set(script "${script}${NAME}(${_args})\n" PARENT_SCOPE)
-endfunction()
-
-# Run test executable to get list of available tests
-if(NOT EXISTS "${TEST_EXECUTABLE}")
- message(FATAL_ERROR
- "Specified test executable '${TEST_EXECUTABLE}' does not exist"
- )
-endif()
-execute_process(
- COMMAND ${TEST_EXECUTOR} "${TEST_EXECUTABLE}" ${spec} --list-test-names-only
- OUTPUT_VARIABLE output
- RESULT_VARIABLE result
- WORKING_DIRECTORY "${TEST_WORKING_DIR}"
-)
-# Catch --list-test-names-only reports the number of tests, so 0 is... surprising
-if(${result} EQUAL 0)
- message(WARNING
- "Test executable '${TEST_EXECUTABLE}' contains no tests!\n"
- )
-elseif(${result} LESS 0)
- message(FATAL_ERROR
- "Error running test executable '${TEST_EXECUTABLE}':\n"
- " Result: ${result}\n"
- " Output: ${output}\n"
- )
-endif()
-
-string(REPLACE "\n" ";" output "${output}")
-
-# Run test executable to get list of available reporters
-execute_process(
- COMMAND ${TEST_EXECUTOR} "${TEST_EXECUTABLE}" ${spec} --list-reporters
- OUTPUT_VARIABLE reporters_output
- RESULT_VARIABLE reporters_result
- WORKING_DIRECTORY "${TEST_WORKING_DIR}"
-)
-if(${reporters_result} EQUAL 0)
- message(WARNING
- "Test executable '${TEST_EXECUTABLE}' contains no reporters!\n"
- )
-elseif(${reporters_result} LESS 0)
- message(FATAL_ERROR
- "Error running test executable '${TEST_EXECUTABLE}':\n"
- " Result: ${reporters_result}\n"
- " Output: ${reporters_output}\n"
- )
-endif()
-string(FIND "${reporters_output}" "${reporter}" reporter_is_valid)
-if(reporter AND ${reporter_is_valid} EQUAL -1)
- message(FATAL_ERROR
- "\"${reporter}\" is not a valid reporter!\n"
- )
-endif()
-
-# Prepare reporter
-if(reporter)
- set(reporter_arg "--reporter ${reporter}")
-endif()
-
-# Prepare output dir
-if(output_dir AND NOT IS_ABSOLUTE ${output_dir})
- set(output_dir "${TEST_WORKING_DIR}/${output_dir}")
- if(NOT EXISTS ${output_dir})
- file(MAKE_DIRECTORY ${output_dir})
- endif()
-endif()
-
-# Parse output
-foreach(line ${output})
- set(test ${line})
- # Escape characters in test case names that would be parsed by Catch2
- set(test_name ${test})
- foreach(char , [ ])
- string(REPLACE ${char} "\\${char}" test_name ${test_name})
- endforeach(char)
- # ...add output dir
- if(output_dir)
- string(REGEX REPLACE "[^A-Za-z0-9_]" "_" test_name_clean ${test_name})
- set(output_dir_arg "--out ${output_dir}/${output_prefix}${test_name_clean}${output_suffix}")
- endif()
-
- # ...and add to script
- add_command(add_test
- "${prefix}${test}${suffix}"
- ${TEST_EXECUTOR}
- "${TEST_EXECUTABLE}"
- "${test_name}"
- ${extra_args}
- "${reporter_arg}"
- "${output_dir_arg}"
- )
- add_command(set_tests_properties
- "${prefix}${test}${suffix}"
- PROPERTIES
- WORKING_DIRECTORY "${TEST_WORKING_DIR}"
- ${properties}
- )
- list(APPEND tests "${prefix}${test}${suffix}")
-endforeach()
-
-# Create a list of all discovered tests, which users may use to e.g. set
-# properties on the tests
-add_command(set ${TEST_LIST} ${tests})
-
-# Write CTest script
-file(WRITE "${CTEST_FILE}" "${script}")
diff --git a/server/cmake/add-resource.cmake b/server/cmake/add-resource.cmake
new file mode 100644
index 00000000..2b8a294f
--- /dev/null
+++ b/server/cmake/add-resource.cmake
@@ -0,0 +1,40 @@
+#
+# This file is part of the traintastic source code.
+# See .
+#
+# Copyright (C) 2024 Reinder Feenstra
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+
+function(add_resource TARGET_NAME)
+ cmake_parse_arguments(PARSE_ARG "" "BASE_DIR" "FILES" ${ARGN})
+ if(PARSE_ARG_BASE_DIR)
+ set(PARSE_ARG_BASE_DIR "${CMAKE_SOURCE_DIR}/${PARSE_ARG_BASE_DIR}")
+ else()
+ set(PARSE_ARG_BASE_DIR "${CMAKE_SOURCE_DIR}")
+ endif()
+ foreach(INPUT_FILE ${PARSE_ARG_FILES})
+ set(OUTPUT_FILE ${CMAKE_BINARY_DIR}/resource/${INPUT_FILE}.hpp)
+ add_custom_command(
+ OUTPUT ${OUTPUT_FILE}
+ COMMAND Python3::Interpreter ${CMAKE_SOURCE_DIR}/cmake/generateresourceheader.py ${PARSE_ARG_BASE_DIR} ${INPUT_FILE} ${OUTPUT_FILE}
+ DEPENDS ${CMAKE_SOURCE_DIR}/cmake/generateresourceheader.py ${PARSE_ARG_BASE_DIR}/${INPUT_FILE}
+ COMMENT "Generating resource header resource/${INPUT_FILE}.hpp"
+ )
+ list(APPEND OUTPUT_HEADERS ${OUTPUT_FILE})
+ endforeach()
+ add_custom_target(${TARGET_NAME} ALL DEPENDS ${OUTPUT_HEADERS})
+endfunction()
diff --git a/server/cmake/code-coverage.cmake b/server/cmake/code-coverage.cmake
index 601e58a1..334b8d68 100644
--- a/server/cmake/code-coverage.cmake
+++ b/server/cmake/code-coverage.cmake
@@ -380,7 +380,7 @@ function(target_code_coverage TARGET_NAME)
COMMAND ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --zerocounters
COMMAND $ ${target_code_coverage_ARGS}
COMMAND
- ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --base-directory
+ ${LCOV_PATH} --ignore-errors mismatch --directory ${CMAKE_BINARY_DIR} --base-directory
${CMAKE_SOURCE_DIR} --capture ${EXTERNAL_OPTION} --output-file
${COVERAGE_INFO}
COMMAND ${EXCLUDE_COMMAND}
diff --git a/server/cmake/generateresourceheader.py b/server/cmake/generateresourceheader.py
new file mode 100644
index 00000000..ccd2e8e9
--- /dev/null
+++ b/server/cmake/generateresourceheader.py
@@ -0,0 +1,86 @@
+#
+# This file is part of the traintastic source code.
+# See .
+#
+# Copyright (C) 2024 Reinder Feenstra
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+
+import sys
+import os
+import re
+import textwrap
+
+if len(sys.argv) != 4:
+ print(f"Usage: {sys.argv[0]}