-
Notifications
You must be signed in to change notification settings - Fork 7
/
drupal.py
379 lines (328 loc) · 11.2 KB
/
drupal.py
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
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
"""
Drupal codebase template main module
"""
import json
import os
import pkgutil
import shutil
import subprocess
import sys
import click
from . import gitlab
from . import lando
from . import util
DEFAULT_CORE_VERSION = "^8.9.0"
@click.command()
@click.argument("name")
@click.option(
"--directory",
help="Directory where the files should be set up (e.g., drupal). "
+ "The directory will be emptied.",
type=click.Path(exists=False, file_okay=False),
default="drupal",
show_default=True,
)
@click.option("--description", help="Description of the package", default="")
@click.option(
"--core-package",
"-core",
"core_package",
help="Select the core package",
type=click.Choice(["core", "recommended"], case_sensitive=False),
show_default=True,
)
@click.option(
"--core",
"core_package",
help="Select the drupal/core package",
flag_value="core",
default=True,
)
@click.option(
"--recommended",
"core_package",
help="Select the drupal/core-recommended package",
flag_value="recommended",
)
@click.option(
"--core-version",
help="Drupal core version",
default=DEFAULT_CORE_VERSION,
show_default=True,
)
@click.option(
"--docroot", help="The document root", type=click.Path(exists=False), default="web"
)
@click.option(
"--no-install", help="Do not run composer install", is_flag=True, default=False
)
@click.option(
"--cache",
help="Add a cache service",
type=click.Choice(["redis", "memcache"], case_sensitive=False),
)
@click.option("--lando", "add_lando", help="Add Lando support", is_flag=True)
@click.option("--gitlab", "add_gitlab", help="Add GitLab support", is_flag=True)
@click.option(
"--force",
"-f",
help="Force delete the target directory if it exists",
is_flag=True,
)
def main(
name,
directory,
description,
core_package,
core_version,
docroot,
no_install,
cache,
add_lando,
add_gitlab,
force,
):
"""
Scaffold a Drupal site template
Create a Drupal site template with NAME.
Where NAME is the name of your application package (e.g., axelerant/site)
"""
if "/" not in name:
util.write_error(
"The name argument should consist of vendor name and project name, separated by /."
)
if not no_install:
ensure_memory_limit()
prepare_base_directory(directory, force)
os.chdir(directory)
os.system('git init; git commit --allow-empty -m "Initial commit"')
generate_drupal_files(
name=name,
description=description,
core=core_package,
core_version=core_version,
docroot=docroot,
cache_service=cache,
)
settings_file = ensure_settings_file(docroot)
modify_settings_file(
settings_file, "$settings['config_sync_directory'] = '../config/sync';",
)
write_settings_env(docroot)
if add_lando:
name = name.split("/")
name = name[1] if len(name) == 2 else name[0]
util.write_info("Adding Lando support...")
lando.generate_lando_files(name, docroot, cache)
if add_gitlab:
util.write_info("Adding GitLab support...")
gitlab.generate_gitlab_files(docroot)
if not no_install:
run_composer_install()
else:
util.write_info("Remember to run 'composer install' manually.")
os.chdir("..")
return 0
def ensure_memory_limit():
"""
Make sure we have enough memory for composer to work.
"""
if shutil.which("php") is None:
util.write_error("Cannot find php.")
util.write_error("Run with --no-install flag if you don't need composer.")
sys.exit(3)
mem_limit = os.getenv("COMPOSER_MEMORY_LIMIT")
if mem_limit == "-1":
return
php_mem_limit = subprocess.check_output(
["php", "-r", "echo ini_get('memory_limit');"]
).decode("utf-8")
if php_mem_limit == "-1":
return
util.write_error(
"Composer typically requires a lot of memory, especially with Drupal"
)
util.write_error(f"Current PHP value configured: {php_mem_limit}")
util.write_error(f"Current composer value configured: {mem_limit}")
util.write_error(
"Consider setting the limit to -1 with either of the below methods"
)
util.write_error("- PHP settings - memory_limit option")
util.write_error("- COMPOSER_MEMORY_LIMIT environment variable")
util.write_warning(
"Read {} for more details".format(
"https://getcomposer.org/doc/articles/troubleshooting.md#memory-limit-errors"
)
)
should_continue = click.prompt(
"Do you want to continue anyway?", "yes", show_default=True
)
if should_continue == "yes":
return
sys.exit(3)
def prepare_base_directory(directory, force):
"""
Create the base directory while deleting it if forced.
We need the directory to be empty.
"""
if os.path.isdir(directory):
if not force:
util.write_error(
f'The "{directory}" directory already exists.'
+ "Please delete it before running or use the -f option."
)
sys.exit(2)
util.write_warning(f'Removing "{directory}" directory...')
try:
shutil.rmtree(directory)
except OSError as err:
util.write_error(f'Failed deleting the "{directory}" directory')
util.write_error(str(err))
sys.exit(2)
os.mkdir(directory)
def run_composer_install():
"""
Run composer install and handle errors
"""
if shutil.which("composer") is None:
util.write_error("Cannot find composer. Aborting...")
sys.exit(4)
composer_run = subprocess.run("composer install -o", shell=True, check=False)
if composer_run.returncode != 0:
util.write_error("Error when running 'composer install'. Aborting...")
util.write_error("Make sure you have set an adequate PHP memory limit.")
util.write_warning(
"Read {} for more details".format(
"https://getcomposer.org/doc/articles/troubleshooting.md#memory-limit-errors"
)
)
sys.exit(4)
def generate_drupal_files(
name,
description="",
core="core",
core_version=DEFAULT_CORE_VERSION,
docroot="web",
cache_service="",
):
"""
Generate Drupal files based on the given options
"""
composer = get_composer_template(
name=name,
description=description,
core=core,
core_version=core_version,
docroot=docroot,
cache_service=cache_service,
)
composer = sort_composer_packages(composer)
with open("composer.json", "w") as composer_file:
json.dump(composer, composer_file, indent=4)
util.write_file(".gitignore", get_gitignore(docroot))
util.copy_package_file("files/drupal/load.environment.php", "load.environment.php")
util.copy_package_file("files/drupal/.env.example", ".env.example")
os.makedirs("drush/sites", mode=0o755, exist_ok=True)
util.copy_package_file("files/drupal/drush.yml", "drush/drush.yml")
util.copy_package_file("files/drupal/self.site.yml", "drush/sites/self.site.yml")
os.makedirs("config/sync", mode=0o755, exist_ok=True)
util.write_file("config/sync/.gitkeep", "")
util.copy_package_file("files/drupal/renovate.json", "renovate.json")
def get_composer_template(
name, description, core, core_version, docroot, cache_service
):
"""
Get the composer template from package and modify it as per given options
"""
composer = json.loads(pkgutil.get_data(__name__, "files/drupal/composer.json"))
composer["name"] = name
composer["description"] = description
composer["extra"]["drupal-scaffold"]["locations"]["web-root"] = docroot + "/"
composer["extra"]["installer-paths"] = {
docroot + "/core": ["type:drupal-core"],
docroot + "/libraries/{$name}": ["type:drupal-library"],
docroot + "/modules/contrib/{$name}": ["type:drupal-module"],
docroot + "/profiles/contrib/{$name}": ["type:drupal-profile"],
docroot + "/themes/contrib/{$name}": ["type:drupal-theme"],
"drush/Commands/contrib/{$name}": ["type:drupal-drush"],
docroot + "/modules/custom/{$name}": ["type:drupal-custom-module"],
docroot + "/themes/custom/{$name}": ["type:drupal-custom-theme"],
}
if core == "core":
composer["require"]["drupal/core"] = core_version
if core == "recommended":
composer["require"]["drupal/core-recommended"] = core_version
if cache_service == "redis":
composer["require"]["drupal/redis"] = "^1.4"
if cache_service == "memcache":
composer["require"]["drupal/memcache"] = "^2.0"
composer["require"]["drupal/core-composer-scaffold"] = core_version
return composer
def sort_composer_packages(composer):
"""
Sort the packages in a composer array
"""
composer["require"] = sort_dictionary_by_keys(composer["require"])
composer["require-dev"] = sort_dictionary_by_keys(composer["require-dev"])
return composer
def sort_dictionary_by_keys(input_dict):
"""
Sort the dictionary by keys in alphabetical order
"""
sorted_dict = {}
for key in sorted(input_dict.keys()):
sorted_dict[key] = input_dict[key]
return sorted_dict
def get_gitignore(docroot):
"""
Get gitignore template from the package
"""
gitignore = pkgutil.get_data(__name__, "files/drupal/.gitignore.template").decode()
gitignore = gitignore.replace("{docroot}", docroot)
return gitignore
def ensure_settings_file(docroot):
"""
Make sure the settings.php file exists
"""
settings_path = f"{docroot}/sites/default"
settings_file = f"{settings_path}/settings.php"
if not os.path.exists(settings_path):
os.makedirs(settings_path, exist_ok=True)
settings_source = f"{docroot}/sites/default/default.settings.php"
if not os.path.exists(settings_source):
return False
if not os.path.exists(settings_file):
util.write_info("Copying settings.php...")
shutil.copyfile(settings_source, settings_file)
return settings_file
def write_settings_env(docroot):
"""
Write settings.env.php file in correct location
"""
settings_file = ensure_settings_file(docroot)
util.copy_package_file(
"files/drupal/settings.env.php", docroot + "/sites/default/settings.env.php"
)
modify_settings_file(
settings_file, "include $app_root . '/' . $site_path . '/settings.env.php';"
)
util.copy_package_file(
"files/lando/settings.lando.php", docroot + "/sites/default/settings.lando.php"
)
modify_settings_file(
settings_file, "include $app_root . '/' . $site_path . '/settings.lando.php';"
)
def modify_settings_file(settings_file, line):
"""
Add a line to the settings.php file if present, else show a warning
"""
if settings_file and os.path.exists(settings_file):
settings = util.read_file(settings_file)
if settings.find(line) == -1:
settings += "\n" + line + "\n"
util.write_file(settings_file, settings)
else:
util.write_warning(
"Could not write to settings.php. Remember to add this line after installation."
)
util.write_warning(line)