1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
|
#!/usr/bin/env python3
#
# InspIRCd -- Internet Relay Chat Daemon
#
# Copyright (C) 2026 Sadie Powell <sadie@witchery.services>
#
# This file is part of InspIRCd. InspIRCd 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, version 2.
#
# 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, see <http://www.gnu.org/licenses/>.
#
import argparse
import functools
import json
import os
import pathlib
import re
import shutil
import subprocess
import sys
import textwrap
from src.python import common, console
PROGRAM = pathlib.Path(__file__).name
CONFIG_CACHE = common.ROOT / "configure.json"
BUILD_DIR = common.ROOT / "build"
BUILD_DIR_REL = BUILD_DIR.relative_to(pathlib.Path.cwd())
RUN_DIR = common.ROOT / "run"
RUN_DIR_REL = RUN_DIR.relative_to(pathlib.Path.cwd())
def cmake_define(name, value):
return f"-D {name}={value}"
def print_wrapped(text, **kwargs):
dedented_text = textwrap.dedent(text)
left_pad, message, right_pad = re.match(r"(\s*)(.*?)(\s*)$", dedented_text, re.DOTALL).groups()
wrapped_messages = [textwrap.fill(dm, width=80) for dm in message.split("\n\n")]
final_message = "\n\n".join(wrapped_messages)
print(f"{left_pad}{final_message}{right_pad}", **kwargs)
parser = argparse.ArgumentParser(
formatter_class=functools.partial(argparse.HelpFormatter, max_help_position=50)
)
parser.add_argument("--binary-dir", type=str, metavar="DIR", help="The directory to install binary files to")
parser.add_argument("--config-dir", type=str, metavar="DIR", help="The directory to read config files from")
parser.add_argument("--data-dir", type=str, metavar="DIR", help="The directory to read and write data files to")
parser.add_argument("--example-dir", type=str, metavar="DIR", help="The directory to install example config files to")
parser.add_argument("--log-dir", type=str, metavar="DIR", help="The directory to write log files to")
parser.add_argument("--manual-dir", type=str, metavar="DIR", help="The directory to install manual files to")
parser.add_argument("--module-dir", type=str, metavar="DIR", help="The directory to install module files to")
parser.add_argument("--prefix", type=str, metavar="DIR", help="The directory to install to")
parser.add_argument("--runtime-dir", type=str, metavar="DIR", help="The directory to write runtime files to")
parser.add_argument("--script-dir", type=str, metavar="DIR", help="The directory to install script files to")
parser.add_argument("--clean", action="store_true", help=f"Don't load {CONFIG_CACHE} in interactive mode")
parser.add_argument("--development", action="store_true", help="Preconfigure for a development build")
parser.add_argument("--disable-auto-extras", action="store_true", help="Disable automatically enabling extra modules")
parser.add_argument("--disable-interactive", action="store_true", help="Disable interactive mode")
parser.add_argument("--disable-ownership", action="store_true", help="Disable file ownership")
parser.add_argument("--portable", action="store_true", help="Preconfigure paths for a portable install")
parser.add_argument("--system", action="store_true", help="Preconfigure paths for a system install")
parser.add_argument("--update", action="store_true", help=f"Regenerate the CMake build config from {CONFIG_CACHE}")
parser.add_argument("--build-tool", type=str, metavar="TOOL", help="The build tool to generate build files for")
parser.add_argument("--distribution-label", type=str, metavar="LABEL", help="Specify a distribution label")
parser.add_argument("--gid", type=str, metavar="ID", help="Specify the group to run as")
parser.add_argument("--socketengine", type=str, metavar="ENGINE", help="Specify the socket engine to build with")
parser.add_argument("--uid", type=str, metavar="ID", help="Specify the user to run as")
options = parser.parse_args()
CMAKE = os.getenv("CMAKE", "cmake")
if not shutil.which(CMAKE):
console.error(f"CMake ({CMAKE}) must be installed to build InspIRCd!")
interactive = console.interactive() and not any(
[
options.binary_dir,
options.config_dir,
options.data_dir,
options.example_dir,
options.log_dir,
options.manual_dir,
options.module_dir,
options.prefix,
options.runtime_dir,
options.script_dir,
options.development,
options.disable_auto_extras,
options.disable_interactive,
options.disable_ownership,
options.portable,
options.system,
options.build_tool,
options.distribution_label,
options.gid,
options.socketengine,
options.uid,
]
)
if not interactive:
console.warning(f"Using {PROGRAM} in non-interactive mode is deprecated, please switch to CMake.")
if not options.clean and interactive:
try:
if not options.clean:
with open(CONFIG_CACHE, 'r', encoding='utf-8') as file:
option_vars = vars(options)
for k, v in json.load(file).items():
if not option_vars.get(k):
option_vars[k] = v
except FileNotFoundError:
pass # The script is probably being run for the first time.
except json.JSONDecodeError as e:
console.warning(f"Unable to load previous config: {e}")
with open(common.ROOT / "docs" / "BANNER.txt") as fh:
print(textwrap.indent(fh.read(), " " * 10))
version = common.version()
print(f"v{version['FULL']}".rjust(70))
if interactive and not options.update:
print_wrapped(
f"""
This script will help you build InspIRCd, a high-performance Internet Relay Chat (IRCv3)
server for UNIX-like and Windows systems.
If you are upgrading from an older version and just want to re-run CMake with your previous
configuration you can run `{PROGRAM} --update`. Otherwise, please answer the following
questions to create the build configuration.
"""
)
if version["LABEL"] and not options.development and not options.update:
print("-" * 80)
print_wrapped(
"""
You are building a development version. This contains code which has not been tested as much
and may contain faults that could seriously affect the running of your server. It is
recommended that you use a stable version instead.
You can obtain the latest stable version from https://www.inspircd.org/ or by running
`git checkout $(git describe --abbrev=0 --tags insp4)` if you are installing from Git.
"""
)
exit_now = True
if interactive:
print_wrapped("I understand this warning and want to continue anyway.")
print()
exit_now = not console.prompt_boolean(False)
else:
print_wrapped("If you understand this warning and still want to continue pass the --development flag.")
if exit_now:
sys.exit(1)
if interactive and not options.update:
print("-" * 80)
print_wrapped(
f"""
InspIRCd supports three kinds of installation:
* A "source" installation installs into `{RUN_DIR_REL}` with short paths selected for users
who are building in their home directory. This is the recommended install type.
* A "system" installation installs into `/` with FHS paths as you might expect for a program
installed from your system package manager.
* A "portable" installation is similar to a source installation but it uses relative paths
in the build configuration allowing you to move it between directories. If you pick this
build type you will need to run InspIRCd from `{RUN_DIR_REL}` so it knows where to find
files.
""",
)
while True:
kind_default = "system" if options.system else "portable" if options.portable else "source"
match kind := console.prompt_string(kind_default).lower():
case "source":
options.portable = False
options.system = False
break
case "system":
options.portable = False
options.system = True
break
case "portable":
options.portable = True
options.system = False
break
case _:
console.warning(f'"{kind}" is not one of "source", "system", or "portable". Please try again.')
print("-" * 80)
print_wrapped(
"""
By default InspIRCd will build all extra modules for which the dependencies exist. If you
prefer you can manually select the extra modules to enable.
Would you like to manually enable extra modules?
"""
)
options.disable_auto_extras = console.prompt_boolean(options.disable_auto_extras)
if options.disable_auto_extras:
subprocess.run([f"{common.ROOT}/modulemanager", "extra", "interactive"])
print()
if any(shutil.which(n) for n in ["ninja", "ninja-build", "samu"]):
print("-" * 80)
print_wrapped(
"""
You have Ninja installed. Building InspIRCd with this is a lot faster than other build
tools. Do you want to use it?
"""
)
tool_default = not options.build_tool or options.build_tool == "Ninja"
if console.prompt_boolean(tool_default):
options.build_tool = "Ninja";
else:
options.build_tool = "Unix Makefiles";
print("-" * 80)
print_wrapped(
"""
You are now ready to configure InspIRCd. When you press Enter your build configuration will
be saved and CMake will be invoked to create the build files.
"""
)
input()
try:
set_options = {k: v for k, v in vars(options).items() if v}
with open(CONFIG_CACHE, "w", encoding="utf-8") as file:
json.dump(set_options, file, indent=4)
except OSError as e:
console.warning(f"Unable to save config: {e}")
# Synthesize a CMake command
cmake_args = [
f"-B {BUILD_DIR}",
f"-S {common.ROOT}",
]
if options.prefix:
cmake_args.append(cmake_define("CMAKE_INSTALL_PREFIX", options.prefix))
if options.binary_dir:
cmake_args.append(cmake_define("BINARY_DIR", options.binary_dir))
if options.config_dir:
cmake_args.append(cmake_define("CONFIG_DIR", options.config_dir))
if options.data_dir:
cmake_args.append(cmake_define("DATA_DIR", options.data_dir))
if options.example_dir:
cmake_args.append(cmake_define("EXAMPLE_DIR", options.example_dir))
if options.log_dir:
cmake_args.append(cmake_define("LOG_DIR", options.log_dir))
if options.manual_dir:
cmake_args.append(cmake_define("MANUAL_DIR", options.manual_dir))
if options.module_dir:
cmake_args.append(cmake_define("MODULE_DIR", options.module_dir))
if options.runtime_dir:
cmake_args.append(cmake_define("RUNTIME_DIR", options.runtime_dir))
if options.script_dir:
cmake_args.append(cmake_define("SCRIPT_DIR", options.script_dir))
if options.development:
cmake_args.append(cmake_define("CMAKE_BUILD_TYPE", "Debug"))
cmake_args.append("-Wdev")
cmake_args.append("-Wdeprecated")
if options.disable_auto_extras:
cmake_args.append(cmake_define("AUTO_ENABLE_EXTRAS", "OFF"))
if options.disable_ownership:
cmake_args.append(cmake_define("DISABLE_OWNERSHIP", "OFF"))
if options.portable:
cmake_args.append(cmake_define("PORTABLE", "ON"))
if options.system:
cmake_args.append(cmake_define("SYSTEM", "ON"))
if options.build_tool:
cmake_args.append(f"-G {options.build_tool}")
if options.distribution_label:
cmake_args.append(cmake_define("DISTRIBUTION_LABEL", options.distribution_label))
if options.gid:
cmake_args.append(cmake_define("GROUP", options.gid))
if options.socketengine:
cmake_args.append(cmake_define("SOCKETENGINE", options.socketengine))
if options.uid:
cmake_args.append(cmake_define("USER", options.uid))
cmake_args.sort()
try:
shutil.rmtree(BUILD_DIR, ignore_errors=True)
arg_indent = " " * (len(CMAKE) + 3)
cmake_args_str = f"\n{arg_indent}".join(sorted(cmake_args))
print(console.color(f"$ {CMAKE} {cmake_args_str}", console.BOLD))
subprocess.run([CMAKE, *cmake_args], check=True)
except subprocess.CalledProcessError as e:
console.error(f"CMake failed with error code {e.returncode}")
print_wrapped(
f"""
You can now run `{CMAKE} --build {BUILD_DIR_REL} --target install` to build and install InspIRCd.
"""
)
|