-
Notifications
You must be signed in to change notification settings - Fork 535
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Migrate build scripts to python with single script to orchestrate dur…
…ing make up (#23011) * Migrate build scripts to python with single script to orchestrate during make up - Replaced the locale compilation script from a shell script to a Python script (compile_locales.py) for better error handling and parallel processing. - Updated the update_assets target in Makefile-docker to use the new update_assets.py script. - Removed the obsolete compile-mo.sh script. - Introduced sync_host_files.py to streamline dependency updates and asset synchronization. * TMP: add tests
- Loading branch information
Showing
11 changed files
with
354 additions
and
54 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
#!/usr/bin/env python3 | ||
|
||
import os | ||
import subprocess | ||
from concurrent.futures import ThreadPoolExecutor | ||
from pathlib import Path | ||
|
||
|
||
def process_po_file(pofile, attempt=1): | ||
"""Process a single .po file, creating corresponding .mo file.""" | ||
pofile_path = Path(pofile) | ||
print('processing', pofile_path.as_posix()) | ||
mo_path = pofile_path.with_suffix('.mo') | ||
|
||
mo_path.touch() | ||
|
||
try: | ||
# Run dennis-cmd lint | ||
subprocess.run( | ||
['dennis-cmd', 'lint', '--errorsonly', pofile], | ||
capture_output=True, | ||
check=False, | ||
) | ||
# If lint passes, run msgfmt | ||
subprocess.run(['msgfmt', '-o', mo_path, pofile], check=True) | ||
return | ||
except subprocess.CalledProcessError as e: | ||
if attempt < 3: | ||
print(f'Failed attempt {attempt} for {pofile}, retrying...') | ||
return process_po_file(pofile, attempt=attempt + 1) | ||
raise e | ||
|
||
|
||
def compile_locales(): | ||
# Ensure 'dennis' is installed | ||
import dennis as _dennis # type: ignore # noqa: F401 | ||
|
||
HOME = os.environ.get('HOME') | ||
|
||
locale_dir = Path(HOME) / 'locale' | ||
|
||
print(f'Compiling locales in {locale_dir}') | ||
|
||
# Collect all files first | ||
django_files = [] | ||
djangojs_files = [] | ||
for root, _, files in locale_dir.walk(): | ||
for file in files: | ||
if file == 'django.po': | ||
django_files.append(root / file) | ||
elif file == 'djangojs.po': | ||
djangojs_files.append(root / file) | ||
|
||
# Process django.po files in parallel | ||
with ThreadPoolExecutor() as executor: | ||
executor.map(process_po_file, django_files + djangojs_files) | ||
|
||
|
||
if __name__ == '__main__': | ||
compile_locales() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
#!/usr/bin/env python3 | ||
|
||
import json | ||
import os | ||
import subprocess | ||
|
||
|
||
def sync_host_files(): | ||
BUILD_INFO = os.environ.get('BUILD_INFO') | ||
|
||
subprocess.run(['make', 'update_deps'], check=True) | ||
|
||
with open(BUILD_INFO, 'r') as f: | ||
build_info = json.load(f) | ||
|
||
if build_info.get('target') == 'production': | ||
subprocess.run(['make', 'compile_locales'], check=True) | ||
subprocess.run(['make', 'update_assets'], check=True) | ||
|
||
|
||
if __name__ == '__main__': | ||
sync_host_files() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
#!/usr/bin/env python3 | ||
|
||
import argparse | ||
import os | ||
import shutil | ||
import subprocess | ||
from pathlib import Path | ||
|
||
|
||
def clean_static_dirs(verbose: bool = False): | ||
HOME = os.environ.get('HOME') | ||
STATIC_DIRS = ['static-build', 'site-static'] | ||
|
||
for directory in STATIC_DIRS: | ||
path = Path(HOME) / directory | ||
path.mkdir(parents=True, exist_ok=True) | ||
for entry in path.iterdir(): | ||
entry_path = entry.as_posix() | ||
if verbose: | ||
print(f'Removing {entry_path}') | ||
if entry.is_dir(): | ||
shutil.rmtree(entry_path) | ||
else: | ||
os.remove(entry_path) | ||
|
||
|
||
def update_assets(verbose: bool = False): | ||
clean_static_dirs(verbose) | ||
|
||
script_prefix = ['python3', 'manage.py'] | ||
|
||
environment = os.environ.copy() | ||
# Always run in production mode without any development settings | ||
environment['DJANGO_SETTINGS_MODULE'] = 'olympia.lib.settings_base' | ||
|
||
subprocess.run( | ||
script_prefix + ['compress_assets'], | ||
check=True, | ||
env=environment, | ||
) | ||
subprocess.run( | ||
script_prefix + ['generate_jsi18n_files'], | ||
check=True, | ||
env=environment, | ||
) | ||
subprocess.run( | ||
script_prefix + ['collectstatic', '--noinput'], | ||
check=True, | ||
env=environment, | ||
) | ||
|
||
|
||
if __name__ == '__main__': | ||
parser = argparse.ArgumentParser() | ||
parser.add_argument('--verbose', action='store_true') | ||
args = parser.parse_args() | ||
update_assets(args.verbose) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
import subprocess | ||
import sys | ||
import tempfile | ||
from pathlib import Path | ||
from unittest import TestCase, mock | ||
from unittest.mock import Mock, patch | ||
|
||
import pytest | ||
|
||
from scripts.compile_locales import compile_locales, process_po_file | ||
from tests import override_env | ||
|
||
|
||
@pytest.mark.needs_locales_compilation | ||
class TestCompileLocales(TestCase): | ||
def setUp(self): | ||
self.home_dir = Path(tempfile.mkdtemp()) | ||
self.locale_dir = (self.home_dir / 'locale').mkdir() | ||
|
||
@patch.dict(sys.modules, {'dennis': None}) | ||
def test_dennis_not_installed(self): | ||
"""Test that the script raises when dennis is not installed""" | ||
self.assertRaises(ImportError, compile_locales) | ||
|
||
@patch.dict(sys.modules, {'dennis': Mock()}) | ||
@patch('scripts.compile_locales.ThreadPoolExecutor') | ||
def test_process_po_file(self, mock_executor): | ||
"""Test that the script processes po files""" | ||
# Create po files | ||
django_po = self.home_dir / 'locale' / 'django.po' | ||
django_po.touch() | ||
djangojs_po = self.home_dir / 'locale' / 'djangojs.po' | ||
djangojs_po.touch() | ||
|
||
# Setup ThreadPoolExecutor mock | ||
mock_executor_instance = Mock() | ||
mock_executor.return_value.__enter__.return_value = mock_executor_instance | ||
|
||
with override_env(HOME=self.home_dir.as_posix()): | ||
compile_locales() | ||
|
||
# Get the actual arguments passed to map | ||
actual_args = mock_executor_instance.map.call_args[0] | ||
self.assertEqual(actual_args[0], process_po_file) | ||
self.assertEqual(list(actual_args[1]), [django_po, djangojs_po]) | ||
|
||
|
||
class TestProcessPoFile(TestCase): | ||
def setUp(self): | ||
self.pofile = Path(tempfile.mkdtemp()) / 'django.po' | ||
|
||
mock_subprocess = patch('scripts.compile_locales.subprocess.run') | ||
self.mock_subprocess = mock_subprocess.start() | ||
self.addCleanup(mock_subprocess.stop) | ||
|
||
def test_process_po_file(self): | ||
process_po_file(self.pofile.as_posix()) | ||
self.assertTrue(self.pofile.with_suffix('.mo').exists()) | ||
|
||
assert self.mock_subprocess.call_args_list == [ | ||
mock.call( | ||
['dennis-cmd', 'lint', '--errorsonly', self.pofile.as_posix()], | ||
capture_output=True, | ||
check=False, | ||
), | ||
mock.call( | ||
[ | ||
'msgfmt', | ||
'-o', | ||
self.pofile.with_suffix('.mo'), | ||
self.pofile.as_posix(), | ||
], | ||
check=True, | ||
), | ||
] | ||
|
||
def test_process_po_file_retries(self): | ||
self.mock_subprocess.side_effect = subprocess.CalledProcessError( | ||
returncode=1, | ||
cmd=['dennis-cmd', 'lint', '--errorsonly', self.pofile.as_posix()], | ||
) | ||
|
||
with self.assertRaises(subprocess.CalledProcessError): | ||
process_po_file(self.pofile.as_posix()) | ||
|
||
self.assertTrue(self.pofile.with_suffix('.mo').exists()) | ||
|
||
# We expect 3 attempts to process the file | ||
self.assertEqual(self.mock_subprocess.call_count, 3) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
import json | ||
import tempfile | ||
from pathlib import Path | ||
from unittest import TestCase, mock | ||
|
||
from scripts.sync_host_files import sync_host_files | ||
from tests import override_env | ||
|
||
|
||
@mock.patch('scripts.sync_host_files.subprocess.run') | ||
class TestSyncHostFiles(TestCase): | ||
def test_sync_host_files(self, mock_subprocess): | ||
sync_host_files() | ||
|
||
mock_subprocess.assert_has_calls( | ||
[ | ||
mock.call(['make', 'update_deps'], check=True), | ||
# mock.call(['make', 'compile_locales'], check=True), | ||
# mock.call(['make', 'update_assets'], check=True), | ||
] | ||
) | ||
|
||
def test_sync_host_files_production(self, mock_subprocess): | ||
mock_build = Path(tempfile.mktemp()) | ||
mock_build.write_text(json.dumps({'target': 'production'})) | ||
|
||
with override_env(BUILD_INFO=mock_build.as_posix()): | ||
sync_host_files() | ||
|
||
mock_subprocess.assert_has_calls( | ||
[ | ||
mock.call(['make', 'update_deps'], check=True), | ||
mock.call(['make', 'compile_locales'], check=True), | ||
mock.call(['make', 'update_assets'], check=True), | ||
] | ||
) |
Oops, something went wrong.