Skip to content

Commit 35bc6b9

Browse files
committed
Make possible to do a portable build of XQF
Make possible to do a portable build of XQF. - USE_RELATIVE_PREFIX: Make the executable looks around for its own files. - USE_FHS: Install files following the File Hierarchy Standard. This is how one can do a portable installation where the binaries are stored at the root of the install folder and the required data is searched next to them at run time: cmake -DUSE_RELATIVE_PREFIX=ON -DUSE_FHS=OFF It makes possible to build and distribute compiled binaries in a tarball. This relies on some code under the BSD license I copied from the Dæmon engine¹. I place the glue around under the CC0 1.0 license (public domain like). ¹ https://github.com/DaemonEngine/Daemon
1 parent 0fe084c commit 35bc6b9

6 files changed

Lines changed: 259 additions & 20 deletions

File tree

CMakeLists.txt

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -110,8 +110,9 @@ macro(try_c_cxx_flag FLAG PROP)
110110
endmacro()
111111

112112
# Paths
113-
set(PACKAGE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/share/xqf")
114-
set(LOCALEDIR "${CMAKE_INSTALL_PREFIX}/share/locale")
113+
set(SHARE_DIR "${CMAKE_INSTALL_PREFIX}/share")
114+
set(PACKAGE_DATA_DIR "${SHARE_DIR}/xqf")
115+
set(LOCALEDIR "${SHARE_DIR}/locale")
115116

116117
if (NOT MAN_ENTRY_PATH)
117118
set(MAN_ENTRY_PATH "${CMAKE_INSTALL_PREFIX}/share/man")
@@ -121,6 +122,7 @@ if (NOT PIXMAPS_ENTRY_PATH)
121122
set(PIXMAPS_ENTRY_PATH "${CMAKE_INSTALL_PREFIX}/share/icons/hicolor")
122123
endif()
123124

125+
124126
# Definitions
125127
add_definitions(-DPACKAGE="${PROJECT_NAME}" -DPACKAGE_VERSION="${VERSION}" -DXQF_VERSION="${VERSION}" -DDOMAIN="${DOMAIN}" -DLOCALEDIR="${LOCALEDIR}" -DPACKAGE_DATA_DIR="${PACKAGE_DATA_DIR}")
126128

@@ -153,6 +155,21 @@ if (CMAKE_BUILD_TYPE MATCHES "DEBUG")
153155
add_definitions(-DDEBUG)
154156
endif()
155157

158+
option(USE_RELATIVE_PREFIX "Make the executable look around for its own files." OFF)
159+
160+
if (USE_RELATIVE_PREFIX)
161+
add_definitions(-DUSE_RELATIVE_PREFIX)
162+
endif()
163+
164+
option(USE_FHS "Install files following the File Hierarchy Standard." ON)
165+
166+
if (USE_FHS)
167+
set(XQF_BIN_DIR "/bin")
168+
add_definitions(-DUSE_FHS)
169+
else()
170+
set(XQF_BIN_DIR "")
171+
endif()
172+
156173
# Compiler flags
157174
set_c_cxx_flag("-g3" DEBUG)
158175
set_c_cxx_flag("-g3" RELWITHDEBINFO)
@@ -214,6 +231,7 @@ set(xqf_SOURCES
214231
${CMAKE_SOURCE_DIR}/src/srv-prop.c
215232
${CMAKE_SOURCE_DIR}/src/stat.c
216233
${CMAKE_SOURCE_DIR}/src/statistics.c
234+
${CMAKE_SOURCE_DIR}/src/system.cpp
217235
${CMAKE_SOURCE_DIR}/src/utils.c
218236
${CMAKE_SOURCE_DIR}/src/xqf.c
219237
${CMAKE_SOURCE_DIR}/src/xqf-ui.c
@@ -259,6 +277,7 @@ set(xqf_SOURCES
259277
${CMAKE_SOURCE_DIR}/src/srv-prop.h
260278
${CMAKE_SOURCE_DIR}/src/stat.h
261279
${CMAKE_SOURCE_DIR}/src/statistics.h
280+
${CMAKE_SOURCE_DIR}/src/system.h
262281
${CMAKE_SOURCE_DIR}/src/utils.h
263282
${CMAKE_SOURCE_DIR}/src/xqf-ui.h
264283
${CMAKE_SOURCE_DIR}/src/xqf.h
@@ -280,11 +299,13 @@ set(rcon_SOURCES
280299
${CMAKE_SOURCE_DIR}/src/debug.c
281300
${CMAKE_SOURCE_DIR}/src/huffman.c
282301
${CMAKE_SOURCE_DIR}/src/rcon.c
302+
${CMAKE_SOURCE_DIR}/src/system.cpp
283303
${CMAKE_SOURCE_DIR}/src/utils.c
284304

285305
${CMAKE_SOURCE_DIR}/src/debug.h
286306
${CMAKE_SOURCE_DIR}/src/huffman.h
287307
${CMAKE_SOURCE_DIR}/src/rcon.h
308+
${CMAKE_SOURCE_DIR}/src/system.h
288309
${CMAKE_SOURCE_DIR}/src/utils.h
289310
)
290311

@@ -681,7 +702,7 @@ if (BUILD_XQF)
681702
# Installation
682703
# Executable
683704
install(TARGETS xqf
684-
DESTINATION "${CMAKE_INSTALL_PREFIX}/bin")
705+
DESTINATION "${CMAKE_INSTALL_PREFIX}${XQF_BIN_DIR}")
685706

686707
# UI
687708
install(FILES ${CMAKE_SOURCE_DIR}/src/xqf-gtk${GTK_TARGET}.ui
@@ -746,5 +767,5 @@ if (BUILD_RCON)
746767
# Installation
747768
# Executable
748769
install(TARGETS rcon
749-
DESTINATION "${CMAKE_INSTALL_PREFIX}/bin")
770+
DESTINATION "${CMAKE_INSTALL_PREFIX}${XQF_BIN_DIR}")
750771
endif()

src/rcon.c

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929

3030
#include <sys/time.h> // select
3131

32+
#include "system.h"
3233
#include "utils.h"
3334
#include "rcon.h"
3435

@@ -727,8 +728,12 @@ int main(int argc, char* argv[]) {
727728
char* buf = NULL;
728729
int res;
729730

731+
#if defined(USE_RELATIVE_PREFIX)
732+
setDefaultDirs();
733+
#endif
734+
730735
setlocale(LC_ALL, "");
731-
bindtextdomain(PACKAGE, LOCALEDIR);
736+
bindtextdomain(PACKAGE, xqf_LOCALEDIR);
732737
textdomain(PACKAGE);
733738

734739
rcon_servertype = Q3_SERVER;

src/system.cpp

Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
/* XQF - Quake server browser and launcher
2+
What is not explicitly stated to be covered by another license is
3+
distributed under the CC0 1.0 license.
4+
See: https://creativecommons.org/public-domain/cc0/ */
5+
6+
/* XQF CC0 Source Code.
7+
Copyright (C) 2025 XQF Team - https://xqf.github.io
8+
No rights reserved. */
9+
10+
#if defined(USE_RELATIVE_PREFIX)
11+
#include <algorithm>
12+
#include <memory>
13+
#include <string>
14+
15+
#include <cstring>
16+
#include <unistd.h>
17+
18+
#include "system.h"
19+
#endif
20+
21+
#if defined(USE_RELATIVE_PREFIX)
22+
char xqf_PACKAGE_DATA_DIR[ XQF_MAX_PATH ] = {};
23+
char xqf_LOCALEDIR[ XQF_MAX_PATH ] = {};
24+
#else
25+
const char* xqf_PACKAGE_DATA_DIR = PACKAGE_DATA_DIR;
26+
const char* xqf_LOCALEDIR = LOCALEDIR;
27+
#endif
28+
29+
#if defined(USE_RELATIVE_PREFIX)
30+
namespace FS {
31+
std::string DefaultLibPath();
32+
}
33+
34+
void setDefaultDirs()
35+
{
36+
#ifdef _WIN32
37+
std::string sep = "\\";
38+
#else
39+
std::string sep = "/";
40+
#endif
41+
42+
std::string exe_dir = FS::DefaultLibPath();
43+
#if defined(USE_FHS)
44+
std::string share_dir = exe_dir + sep + ".." + sep + "share";
45+
#else
46+
std::string share_dir = exe_dir + sep + "share";
47+
#endif
48+
std::string data_dir = share_dir + sep + "xqf";
49+
std::string locale_dir = share_dir + sep + "locale";
50+
51+
strncpy(xqf_PACKAGE_DATA_DIR, data_dir.c_str(), std::min(data_dir.size(), size_t(XQF_MAX_PATH)));
52+
strncpy(xqf_LOCALEDIR, locale_dir.c_str(), std::min(locale_dir.size(), size_t(XQF_MAX_PATH)));
53+
}
54+
55+
// The following is copied from https://github.com/DaemonEngine/Daemon
56+
57+
/*
58+
===========================================================================
59+
Daemon BSD Source Code
60+
Copyright (c) 2013-2025, Daemon Developers
61+
All rights reserved.
62+
63+
Redistribution and use in source and binary forms, with or without
64+
modification, are permitted provided that the following conditions are met:
65+
* Redistributions of source code must retain the above copyright
66+
notice, this list of conditions and the following disclaimer.
67+
* Redistributions in binary form must reproduce the above copyright
68+
notice, this list of conditions and the following disclaimer in the
69+
documentation and/or other materials provided with the distribution.
70+
* Neither the name of the Daemon developers nor the
71+
names of its contributors may be used to endorse or promote products
72+
derived from this software without specific prior written permission.
73+
74+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
75+
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
76+
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
77+
DISCLAIMED. IN NO EVENT SHALL DAEMON DEVELOPERS BE LIABLE FOR ANY
78+
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
79+
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
80+
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
81+
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
82+
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
83+
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
84+
===========================================================================
85+
*/
86+
87+
#ifdef _WIN32
88+
namespace Str
89+
{
90+
// Copied from Daemon/src/common/String.h
91+
template<typename T> class BasicStringRef {
92+
public:
93+
const T* begin() const
94+
{
95+
return ptr;
96+
}
97+
const T* end() const
98+
{
99+
return ptr + len;
100+
}
101+
private:
102+
const T* ptr;
103+
size_t len;
104+
};
105+
}
106+
107+
// Copied from Daemon/src/common/String.cpp
108+
std::string UTF16To8(Str::BasicStringRef<wchar_t> str)
109+
{
110+
std::string out;
111+
auto it = str.begin();
112+
auto end = str.end();
113+
114+
while (it != end) {
115+
uint16_t ch = *it++;
116+
if (ch >= UNICODE_SURROGATE_HEAD_START && ch <= UNICODE_SURROGATE_HEAD_END) {
117+
if (it == end)
118+
out.append(UNICODE_REPLACEMENT_CHAR_UTF8);
119+
else {
120+
uint16_t tail = *it++;
121+
if (tail >= UNICODE_SURROGATE_TAIL_START && tail <= UNICODE_SURROGATE_TAIL_END)
122+
UTF8_Append(out, ((ch & UNICODE_SURROGATE_MASK) << 10) | (tail & UNICODE_SURROGATE_MASK));
123+
else
124+
out.append(UNICODE_REPLACEMENT_CHAR_UTF8);
125+
}
126+
} else if (ch >= UNICODE_SURROGATE_TAIL_START && ch <= UNICODE_SURROGATE_TAIL_END)
127+
out.append(UNICODE_REPLACEMENT_CHAR_UTF8);
128+
else
129+
UTF8_Append(out, ch);
130+
}
131+
return out;
132+
}
133+
}
134+
#endif
135+
136+
namespace FS
137+
{
138+
// Copied from Daemon/src/common/FileSystem.cpp
139+
std::string DefaultLibPath()
140+
{
141+
#ifdef _WIN32
142+
wchar_t buffer[MAX_PATH];
143+
DWORD len = GetModuleFileNameW(nullptr, buffer, MAX_PATH);
144+
if (len == 0 || len >= MAX_PATH)
145+
return "";
146+
147+
wchar_t* p = wcsrchr(buffer, L'\\');
148+
if (!p)
149+
return empty_string;
150+
*p = L'\0';
151+
152+
return Str::UTF16To8(buffer);
153+
#elif defined(__linux__) || defined(__FreeBSD__)
154+
ssize_t len = 64;
155+
while (true) {
156+
std::unique_ptr<char[]> out(new char[len]);
157+
#if defined(__linux__)
158+
const char* proc_file = "/proc/self/exe";
159+
#elif defined(__FreeBSD__)
160+
const char* proc_file = "/proc/curproc/file";
161+
#endif
162+
ssize_t result = readlink(proc_file, out.get(), len);
163+
if (result == -1)
164+
return "";
165+
if (result < len) {
166+
out[result] = '\0';
167+
char* p = strrchr(out.get(), '/');
168+
if (!p)
169+
return "";
170+
*p = '\0';
171+
return out.get();
172+
}
173+
len *= 2;
174+
}
175+
#elif defined(__APPLE__)
176+
uint32_t bufsize = 0;
177+
_NSGetExecutablePath(nullptr, &bufsize);
178+
179+
std::unique_ptr<char[]> out(new char[bufsize]);
180+
_NSGetExecutablePath(out.get(), &bufsize);
181+
182+
char* p = strrchr(out.get(), '/');
183+
if (!p)
184+
return "";
185+
*p = '\0';
186+
187+
return out.get();
188+
#endif
189+
}
190+
}
191+
#endif // USE_RELATIVE_PREFIX

src/system.h

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
/* XQF - Quake server browser and launcher
2+
What is not explicitly stated to be covered by another license is
3+
distributed under the CC0 1.0 license.
4+
See: https://creativecommons.org/public-domain/cc0/ */
5+
6+
/* XQF CC0 Source Code.
7+
Copyright (C) 2025 XQF Team - https://xqf.github.io
8+
No rights reserved. */
9+
10+
#ifndef __SYSTEM_H__
11+
#define __SYSTEM_H__
12+
13+
#ifdef __cplusplus
14+
#define EXTERNC extern "C"
15+
#else
16+
#define EXTERNC
17+
#endif
18+
19+
#if defined(USE_RELATIVE_PREFIX)
20+
#define XQF_MAX_PATH 1024
21+
extern char xqf_PACKAGE_DATA_DIR[ XQF_MAX_PATH ];
22+
extern char xqf_LOCALEDIR[ XQF_MAX_PATH ];
23+
#else
24+
extern const char* xqf_PACKAGE_DATA_DIR;
25+
extern const char* xqf_LOCALEDIR;
26+
#endif
27+
28+
#if defined(USE_RELATIVE_PREFIX)
29+
EXTERNC void setDefaultDirs();
30+
#endif
31+
32+
#undef EXTERNC
33+
#endif // __SYSTEM_H__

src/xqf.c

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@
6969
#include "addserver.h"
7070
#include "addmaster.h"
7171
#include "sort.h"
72+
#include "system.h"
7273
#include "config.h"
7374
#include "debug.h"
7475
#include "redial.h"
@@ -90,9 +91,6 @@ int dontlaunch = 0;
9091

9192
int event_type = 0;
9293

93-
char* xqf_PACKAGE_DATA_DIR = PACKAGE_DATA_DIR;
94-
char* xqf_LOCALEDIR = LOCALEDIR;
95-
9694
char* qstat_configfile = NULL;
9795

9896
GtkBuilder *builder = NULL;
@@ -2640,15 +2638,9 @@ int main (int argc, char *argv[]) {
26402638

26412639
redialserver = 0;
26422640

2643-
var = getenv ("xqf_PACKAGE_DATA_DIR");
2644-
if (var) {
2645-
xqf_PACKAGE_DATA_DIR = var;
2646-
}
2647-
2648-
var = getenv ("xqf_LOCALEDIR");
2649-
if (var) {
2650-
xqf_LOCALEDIR = var;
2651-
}
2641+
#if defined(USE_RELATIVE_PREFIX)
2642+
setDefaultDirs();
2643+
#endif
26522644

26532645
setlocale (LC_ALL, "");
26542646
bindtextdomain (PACKAGE, xqf_LOCALEDIR);

src/xqf.h

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -54,9 +54,6 @@ void master_set_qstat_option(struct master* m, const char* opt);
5454
char* master_to_url(QFMaster* m);
5555

5656
extern time_t xqf_start_time;
57-
extern char* xqf_PACKAGE_DATA_DIR;
58-
extern char* xqf_LOCALEDIR;
59-
extern char* xqf_PIXMAPSDIR;
6057

6158
extern char* qstat_configfile;
6259

0 commit comments

Comments
 (0)