diff --git a/pre_commit/languages/gn.py b/pre_commit/languages/gn.py new file mode 100644 index 00000000..e626bad7 --- /dev/null +++ b/pre_commit/languages/gn.py @@ -0,0 +1,101 @@ +from __future__ import annotations + +import contextlib +import functools +import os.path +import platform +import shutil +import tempfile +import urllib.error +import urllib.request +import zipfile +from collections.abc import Generator +from collections.abc import Sequence +from typing import ContextManager +from typing import IO + +import pre_commit.constants as C +from pre_commit import lang_base +from pre_commit.envcontext import envcontext +from pre_commit.envcontext import PatchesT +from pre_commit.envcontext import Var +from pre_commit.prefix import Prefix +from pre_commit.util import make_executable + +ENVIRONMENT_DIR = 'gn_env' +health_check = lang_base.basic_health_check +run_hook = lang_base.basic_run_hook + +_ARCH_ALIASES = { + 'x86_64': 'amd64', + 'aarch64': 'arm64', + 'armv8': 'arm64', +} +_ARCH = platform.machine().lower() +_ARCH = _ARCH_ALIASES.get(_ARCH, _ARCH) + + +def _open_archive(bio: IO[bytes]) -> ContextManager[zipfile.ZipFile]: + return zipfile.ZipFile(bio) + + +def _get_gn_url() -> str: + os_name = platform.system().lower() + if os_name == 'linux': + os_name = 'linux-' + _ARCH + elif os_name == 'darwin': + os_name = 'mac-' + _ARCH + elif os_name == 'windows': + os_name = 'windows-' + _ARCH + return 'https://chrome-infra-packages.appspot.com/dl/gn/gn/' + \ + f'{os_name}/+/latest' + + +def _install_gn(dest: str) -> None: + try: + resp = urllib.request.urlopen(_get_gn_url()) + except urllib.error.HTTPError as e: # pragma: no cover + os_name = platform.system().lower() + raise ValueError( + f'Could not find GN for your system (os={os_name}; arch={_ARCH})', + ) from e + else: + with tempfile.TemporaryFile() as f: + shutil.copyfileobj(resp, f) + f.seek(0) + with _open_archive(f) as archive: + archive.extractall(dest) + make_executable(f'{dest}/gn') + + +@functools.lru_cache(maxsize=1) +def get_default_version() -> str: + if lang_base.exe_exists('gn'): + return 'system' + else: + return C.DEFAULT + + +def get_env_patch(venv: str, version: str) -> PatchesT: + if version == 'system': + return () + + return (('PATH', (venv, os.pathsep, Var('PATH'))),) + + +@contextlib.contextmanager +def in_env(prefix: Prefix, version: str) -> Generator[None, None, None]: + env_dir = lang_base.environment_dir(prefix, ENVIRONMENT_DIR, version) + with envcontext(get_env_patch(env_dir, version)): + yield + + +def install_environment( + prefix: Prefix, + version: str, + additional_dependencies: Sequence[str], +) -> None: + if version == 'system': + return + env_dir = lang_base.environment_dir(prefix, ENVIRONMENT_DIR, version) + _install_gn(env_dir) diff --git a/tests/languages/gn_test.py b/tests/languages/gn_test.py new file mode 100644 index 00000000..eb5e6f2e --- /dev/null +++ b/tests/languages/gn_test.py @@ -0,0 +1,37 @@ +from __future__ import annotations + +import re + +from pre_commit.languages import gn +from testing.language_helpers import run_language + + +def test_gn_format(tmp_path): + # Create a GN file with content that needs formatting + gn_file_content = """\ +source_set("hello_world") { + sources = [ "hello_world.cc", ] +} +""" + gn_file = tmp_path.joinpath('test.gn') + gn_file.write_text(gn_file_content) + + # Run gn format on the created file + ret, out = run_language(tmp_path, gn, f'gn format {gn_file}') + + # Read the formatted file + formatted_content = gn_file.read_text() + + assert ret == 0 + assert gn_file_content != formatted_content + + +def test_gn_version(tmp_path): + ret, out = run_language( + tmp_path, + gn, + 'gn --version', + ) + assert ret == 0 + version_regex = re.compile(rb'\d+ \([a-f0-9]+\)\n') + assert version_regex.search(out) is not None