Esempio n. 1
0
    def _check_python_file(self, fname: str) -> None:
        from efrotools import get_public_license, PYVER
        with open(fname, encoding='utf-8') as infile:
            contents = infile.read()
            lines = contents.splitlines()

        # Make sure all standalone scripts are pointing to the right
        # version of python (with a few exceptions where it needs to
        # differ)
        if contents.startswith('#!/'):
            copyrightline = 1
            if fname not in ['tools/vmshell']:
                if not contents.startswith(f'#!/usr/bin/env python{PYVER}'):
                    raise CleanError(f'Incorrect shebang (first line) for '
                                     f'{fname}.')
        else:
            copyrightline = 0

        # Special case: it there's spinoff autogenerate notice there,
        # look below it.
        if (lines[copyrightline] == ''
                and 'THIS FILE IS AUTOGENERATED' in lines[copyrightline + 1]):
            copyrightline += 2

        # In all cases, look for our one-line legal notice.
        # In the public case, look for the rest of our public license too.
        if self._license_line_checks:
            public_license = get_public_license('python')
            private_license = '# ' + get_legal_notice_private()
            lnum = copyrightline
            if len(lines) < lnum + 1:
                raise RuntimeError('Not enough lines in file:', fname)

            disable_note = ('NOTE: You can disable license line'
                            ' checks by adding "license_line_checks": false\n'
                            'to the root dict in config/localconfig.json.\n'
                            'see https://ballistica.net/wiki'
                            '/Knowledge-Nuggets#'
                            'hello-world-creating-a-new-game-type')

            if self._public:
                # Check for public license only.
                if lines[lnum] != public_license:
                    raise CleanError(f'License text not found'
                                     f" at '{fname}' line {lnum+1};"
                                     f' please correct.\n'
                                     f'Expected text is: {public_license}\n'
                                     f'{disable_note}')
            else:
                # Check for public or private license.
                if (lines[lnum] != public_license
                        and lines[lnum] != private_license):
                    raise CleanError(f'License text not found'
                                     f" at '{fname}' line {lnum+1};"
                                     f' please correct.\n'
                                     f'Expected text (for public files):'
                                     f' {public_license}\n'
                                     f'Expected text (for private files):'
                                     f' {private_license}\n'
                                     f'{disable_note}')
Esempio n. 2
0
def _run_pylint(projroot: Path, pylintrc: Union[Path, str],
                cache: Optional[FileCache], dirtyfiles: List[str],
                allfiles: Optional[List[str]]) -> Dict[str, Any]:
    import time
    from pylint import lint
    from efro.error import CleanError
    from efro.terminal import Clr
    start_time = time.time()
    args = ['--rcfile', str(pylintrc), '--output-format=colorized']

    args += dirtyfiles
    name = f'{len(dirtyfiles)} file(s)'
    run = lint.Run(args, do_exit=False)
    if cache is not None:
        assert allfiles is not None
        result = _apply_pylint_run_to_cache(projroot, run, dirtyfiles,
                                            allfiles, cache)
        if result != 0:
            raise CleanError(f'Pylint failed for {result} file(s).')

        # Sanity check: when the linter fails we should always be failing too.
        # If not, it means we're probably missing something and incorrectly
        # marking a failed file as clean.
        if run.linter.msg_status != 0 and result == 0:
            raise RuntimeError('Pylint linter returned non-zero result'
                               ' but we did not; this is probably a bug.')
    else:
        if run.linter.msg_status != 0:
            raise CleanError('Pylint failed.')

    duration = time.time() - start_time
    print(f'{Clr.GRN}Pylint passed for {name}'
          f' in {duration:.1f} seconds.{Clr.RST}')
    sys.stdout.flush()
    return {'f': dirtyfiles, 't': duration}
Esempio n. 3
0
def android_archive_unstripped_libs() -> None:
    """Copy libs to a build archive."""
    import subprocess
    from pathlib import Path
    from efro.error import CleanError
    from efro.terminal import Clr
    if len(sys.argv) != 4:
        raise CleanError('Expected 2 args; src-dir and dst-dir')
    src = Path(sys.argv[2])
    dst = Path(sys.argv[3])
    if dst.exists():
        subprocess.run(['rm', '-rf', dst], check=True)
    dst.mkdir(parents=True, exist_ok=True)
    if not src.is_dir():
        raise CleanError(f"Source dir not found: '{src}'")
    libname = 'libmain'
    libext = '.so'
    for abi, abishort in [
        ('armeabi-v7a', 'arm'),
        ('arm64-v8a', 'arm64'),
        ('x86', 'x86'),
        ('x86_64', 'x86-64'),
    ]:
        srcpath = Path(src, abi, libname + libext)
        dstname = f'{libname}_{abishort}{libext}'
        dstpath = Path(dst, dstname)
        if srcpath.exists():
            print(f'Archiving unstripped library: {Clr.BLD}{dstname}{Clr.RST}')
            subprocess.run(['cp', srcpath, dstpath], check=True)
            subprocess.run(['tar', '-zcf', dstname + '.tgz', dstname],
                           cwd=dst,
                           check=True)
            subprocess.run(['rm', dstpath], check=True)
Esempio n. 4
0
def try_repeat() -> None:
    """Run a command with repeat attempts on failure.

    First arg is the number of retries; remaining args are the command.
    """
    import subprocess
    from efro.error import CleanError
    # We require one number arg and at least one command arg.
    if len(sys.argv) < 4:
        raise CleanError(
            'Expected a retry-count arg and at least one command arg')
    try:
        repeats = int(sys.argv[2])
    except Exception:
        raise CleanError('Expected int as first arg') from None
    if repeats < 0:
        raise CleanError('Retries must be >= 0')
    cmd = sys.argv[3:]
    for i in range(repeats + 1):
        result = subprocess.run(cmd, check=False)
        if result.returncode == 0:
            return
        print(f'try_repeat attempt {i + 1} of {repeats + 1} failed for {cmd}.',
              file=sys.stderr,
              flush=True)
    raise CleanError(f'Command failed {repeats + 1} time(s): {cmd}')
Esempio n. 5
0
def ensure_prefab_platform() -> None:
    """Ensure we are running on a particular prefab platform."""
    import batools.build
    from efro.error import CleanError
    if len(sys.argv) != 3:
        raise CleanError('Expected 1 platform name arg.')
    needed = sys.argv[2]
    current = batools.build.get_current_prefab_platform()
    if current != needed:
        raise CleanError(
            f'Incorrect platform: we are {current}, this requires {needed}.')
Esempio n. 6
0
def runmypy() -> None:
    """Run mypy checks on provided filenames."""
    from efro.terminal import Clr
    from efro.error import CleanError
    import efrotools.code
    if len(sys.argv) < 3:
        raise CleanError('Expected at least 1 filename arg.')
    filenames = sys.argv[2:]
    try:
        efrotools.code.runmypy(PROJROOT, filenames)
        print(f'{Clr.GRN}Mypy Passed.{Clr.RST}')
    except Exception as exc:
        raise CleanError('Mypy Failed.') from exc
Esempio n. 7
0
def stage_server_file() -> None:
    """Stage files for the server environment with some filtering."""
    import os
    import subprocess
    import batools.build
    from efro.error import CleanError
    from efrotools import replace_one
    from efrotools import PYVER
    if len(sys.argv) != 5:
        raise CleanError('Expected 3 args (mode, infile, outfile).')
    mode, infilename, outfilename = sys.argv[2], sys.argv[3], sys.argv[4]
    if mode not in ('debug', 'release'):
        raise CleanError(f"Invalid mode '{mode}'; expected debug or release.")

    print(f'Building server file: {os.path.basename(outfilename)}')

    basename = os.path.basename(infilename)
    if basename == 'config_template.yaml':
        # Inject all available config values into the config file.
        batools.build.filter_server_config(str(PROJROOT), infilename,
                                           outfilename)

    elif basename == 'ballisticacore_server.py':
        # Run Python in opt mode for release builds.
        with open(infilename) as infile:
            lines = infile.read().splitlines()
            if mode == 'release':
                lines[0] = replace_one(lines[0],
                                       f'#!/usr/bin/env python{PYVER}',
                                       f'#!/usr/bin/env -S python{PYVER} -O')
        with open(outfilename, 'w') as outfile:
            outfile.write('\n'.join(lines) + '\n')
        subprocess.run(['chmod', '+x', outfilename], check=True)
    elif basename == 'launch_ballisticacore_server.bat':
        # Run Python in opt mode for release builds.
        with open(infilename) as infile:
            lines = infile.read().splitlines()
            if mode == 'release':
                lines[1] = replace_one(
                    lines[1], ':: Python interpreter.',
                    ':: Python interpreter.'
                    ' (in opt mode so we use bundled .opt-1.pyc files)')
                lines[2] = replace_one(
                    lines[2], 'dist\\\\python.exe ballisticacore_server.py',
                    'dist\\\\python.exe -O ballisticacore_server.py')
        with open(outfilename, 'w') as outfile:
            outfile.write('\n'.join(lines) + '\n')
    else:
        raise CleanError(f"Unknown server file for staging: '{basename}'.")
Esempio n. 8
0
def wsl_path_to_win() -> None:
    """Forward escape slashes in a provided win path arg."""
    import subprocess
    import logging
    import os
    from efro.error import CleanError
    try:
        create = False
        escape = False
        if len(sys.argv) < 3:
            raise CleanError('Expected at least 1 path arg.')
        wsl_path: Optional[str] = None
        for arg in sys.argv[2:]:
            if arg == '--create':
                create = True
            elif arg == '--escape':
                escape = True
            else:
                if wsl_path is not None:
                    raise CleanError('More than one path provided.')
                wsl_path = arg
        if wsl_path is None:
            raise CleanError('No path provided.')

        # wslpath fails on nonexistent paths; make it clear when that happens.
        if create:
            os.makedirs(wsl_path, exist_ok=True)
        if not os.path.exists(wsl_path):
            raise CleanError(f'Path \'{wsl_path}\' does not exist.')

        results = subprocess.run(['wslpath', '-w', '-a', wsl_path],
                                 capture_output=True,
                                 check=True)
    except Exception:
        # This gets used in a makefile so our returncode is ignored;
        # let's try to make our failure known in other ways.
        logging.exception('wsl_to_escaped_win_path failed.')
        print('wsl_to_escaped_win_path_error_occurred', end='')
        return

    out = results.stdout.decode().strip()

    # If our input ended with a slash, match in the output.
    if wsl_path.endswith('/') and not out.endswith('\\'):
        out += '\\'

    if escape:
        out = out.replace('\\', '\\\\')
    print(out, end='')
Esempio n. 9
0
def urandom_pretty() -> None:
    """Spits out urandom bytes formatted for source files."""
    # Note; this is not especially efficient. It should probably be rewritten
    # if ever needed in a performance-sensitive context.
    import os
    from efro.error import CleanError

    if len(sys.argv) not in (3, 4):
        raise CleanError(
            'Expected one arg (count) and possibly two (line len).')
    size = int(sys.argv[2])
    linemax = 72 if len(sys.argv) < 4 else int(sys.argv[3])

    val = os.urandom(size)
    lines: list[str] = []
    line = b''

    for i in range(len(val)):
        char = val[i:i + 1]
        thislinelen = len(repr(line + char))
        if thislinelen > linemax:
            lines.append(repr(line))
            line = b''
        line += char
    if line:
        lines.append(repr(line))

    bstr = '\n'.join(str(l) for l in lines)
    print(f'({bstr})')
Esempio n. 10
0
    def run(self) -> None:
        """Do the thing."""
        self._run_cmd(self._build_cmd_args())
        assert self._returncode is not None

        # In some failure cases we may want to run a clean and try again.
        if self._returncode != 0:

            # Getting this error sometimes after xcode updates.
            if 'error: PCH file built from a different branch' in '\n'.join(
                    self._output):
                print(f'{Clr.MAG}WILL CLEAN AND'
                      f' RE-ATTEMPT XCODE BUILD{Clr.RST}')
                self._run_cmd([
                    'xcodebuild', '-project', self._project, '-scheme',
                    self._scheme, '-configuration', self._configuration,
                    'clean'
                ])
                # Now re-run the original build.
                print(f'{Clr.MAG}RE-ATTEMPTING XCODE BUILD'
                      f' AFTER CLEAN{Clr.RST}')
                self._run_cmd(self._build_cmd_args())

        if self._returncode != 0:
            raise CleanError(f'Command failed with code {self._returncode}.')
Esempio n. 11
0
def dmypy(projroot: Path) -> None:
    """Type check all of our scripts using mypy in daemon mode."""
    import time
    from efro.terminal import Clr
    from efro.error import CleanError
    filenames = get_script_filenames(projroot)

    # Special case; explicitly kill the daemon.
    if '-stop' in sys.argv:
        subprocess.run(['dmypy', 'stop'], check=False)
        return

    print('Running Mypy (daemon)...', flush=True)
    starttime = time.time()
    try:
        args = [
            'dmypy', 'run', '--timeout', '3600', '--', '--config-file',
            '.mypy.ini', '--follow-imports=error', '--pretty'
        ] + filenames
        subprocess.run(args, check=True)
    except Exception:
        raise CleanError('Mypy daemon: fail.')
    duration = time.time() - starttime
    print(f'{Clr.GRN}Mypy daemon passed in {duration:.1f} seconds.{Clr.RST}',
          flush=True)
Esempio n. 12
0
 def handle_test_message_1(self, msg: _TMessage1) -> _TResponse1:
     """Test."""
     if msg.ival == 1:
         raise CleanError('Testing Clean Error')
     if msg.ival == 2:
         raise RuntimeError('Testing Runtime Error')
     return _TResponse1(bval=True)
Esempio n. 13
0
 def lint_file(filename: str) -> None:
     result = subprocess.call(
         [f'python{PYVER}', '-m', 'cpplint', '--root=src', filename],
         env=env)
     if result != 0:
         raise CleanError(
             f'{Clr.RED}Cpplint failed for {filename}.{Clr.RST}')
Esempio n. 14
0
    def __init__(self) -> None:
        try:
            self._config = self._load_config()
        except Exception as exc:
            raise CleanError(f'Error loading config: {exc}') from exc
        self._wrapper_shutdown_desired = False
        self._done = False
        self._subprocess_commands: List[Union[str, ServerCommand]] = []
        self._subprocess_commands_lock = Lock()
        self._subprocess_force_kill_time: Optional[float] = None
        self._restart_minutes: Optional[float] = None
        self._running_interactive = False
        self._subprocess: Optional[subprocess.Popen[bytes]] = None
        self._launch_time = time.time()
        self._subprocess_launch_time: Optional[float] = None
        self._subprocess_sent_auto_restart = False
        self._subprocess_sent_clean_exit = False
        self._subprocess_sent_unclean_exit = False
        self._subprocess_thread: Optional[Thread] = None

        # If we don't have any explicit exit conditions set,
        # we run indefinitely (though we restart our subprocess
        # periodically to clear out leaks/cruft)
        if (self._config.clean_exit_minutes is None
                and self._config.unclean_exit_minutes is None
                and self._config.idle_exit_minutes is None):
            self._restart_minutes = 360.0
Esempio n. 15
0
def tool_config_install() -> None:
    """Install a tool config file (with some filtering)."""
    from efro.terminal import Clr
    from efro.error import CleanError
    if len(sys.argv) != 4:
        raise CleanError('expected 2 args')
    src = Path(sys.argv[2])
    dst = Path(sys.argv[3])

    print(f'Creating tool config: {Clr.BLD}{dst}{Clr.RST}')

    with src.open(encoding='utf-8') as infile:
        cfg = infile.read()

    # Rome substitutions, etc.
    cfg = _filter_tool_config(cfg)

    # Add an auto-generated notice.
    comment = None
    if dst.name in ['.dir-locals.el']:
        comment = ';;'
    elif dst.name in [
            '.mypy.ini', '.pycheckers', '.pylintrc', '.style.yapf',
            '.clang-format', '.editorconfig'
    ]:
        comment = '#'
    if comment is not None:
        cfg = (f'{comment} THIS FILE WAS AUTOGENERATED; DO NOT EDIT.\n'
               f'{comment} Source: {src}.\n\n' + cfg)

    with dst.open('w', encoding='utf-8') as outfile:
        outfile.write(cfg)
Esempio n. 16
0
def pytest() -> None:
    """Run pytest with project environment set up properly."""
    import os
    import platform
    import subprocess
    from efrotools import getconfig, PYTHON_BIN
    from efro.error import CleanError

    # Grab our python paths for the project and stuff them in PYTHONPATH.
    pypaths = getconfig(PROJROOT).get('python_paths')
    if pypaths is None:
        raise CleanError('python_paths not found in project config.')

    separator = ';' if platform.system() == 'Windows' else ':'
    os.environ['PYTHONPATH'] = separator.join(pypaths)

    # Also tell Python interpreters not to write __pycache__ dirs everywhere
    # which can screw up our builds.
    os.environ['PYTHONDONTWRITEBYTECODE'] = '1'

    # Do the thing.
    results = subprocess.run([PYTHON_BIN, '-m', 'pytest'] + sys.argv[2:],
                             check=False)
    if results.returncode != 0:
        sys.exit(results.returncode)
Esempio n. 17
0
 def _update_meta_makefile(self) -> None:
     try:
         subprocess.run(['tools/pcommand', 'update_meta_makefile'] +
                        self._checkarglist,
                        check=True)
     except Exception as exc:
         raise CleanError('Error checking/updating meta Makefile.') from exc
Esempio n. 18
0
def makefile_target_list() -> None:
    """Prints targets in a makefile.

    Takes a single argument: a path to a Makefile.
    """
    from dataclasses import dataclass
    from efro.error import CleanError
    from efro.terminal import Clr

    @dataclass
    class _Entry:
        kind: str
        line: int
        title: str

    if len(sys.argv) != 3:
        raise CleanError('Expected exactly one filename arg.')

    with open(sys.argv[2], encoding='utf-8') as infile:
        lines = infile.readlines()

    def _docstr(lines2: list[str], linenum: int) -> str:
        doc = ''
        j = linenum - 1
        while j >= 0 and lines2[j].startswith('#'):
            doc = lines2[j][1:].strip()
            j -= 1
        if doc != '':
            return ' - ' + doc
        return doc

    print('----------------------\n'
          'Available Make Targets\n'
          '----------------------')

    entries: list[_Entry] = []
    for i, line in enumerate(lines):

        # Targets.
        if ':' in line and line.split(':')[0].replace('-', '').replace(
                '_', '').isalnum() and not line.startswith('_'):
            entries.append(
                _Entry(kind='target', line=i, title=line.split(':')[0]))

        # Section titles.
        if (line.startswith('#  ') and line.endswith('  #\n')
                and len(line.split()) > 2):
            entries.append(
                _Entry(kind='section', line=i, title=line[1:-2].strip()))

    for i, entry in enumerate(entries):
        if entry.kind == 'section':
            # Don't print headers for empty sections.
            if i + 1 >= len(entries) or entries[i + 1].kind == 'section':
                continue
            print('\n' + entry.title + '\n' + '-' * len(entry.title))
        elif entry.kind == 'target':
            print(Clr.MAG + entry.title + Clr.BLU +
                  _docstr(lines, entry.line) + Clr.RST)
Esempio n. 19
0
def standard_message_receiver_gen_pcommand(
    projroot: Path,
    basename: str,
    source_module: str,
    is_async: bool,
    get_protocol_call: str = 'get_protocol',
    embedded: bool = False,
) -> None:
    """Used by pcommands generating efro.message receiver modules."""
    # pylint: disable=too-many-locals

    import efro.message
    from efro.terminal import Clr
    from efro.error import CleanError

    if len(sys.argv) != 3:
        raise CleanError('Expected 1 arg: out-path.')

    dst = sys.argv[2]

    # Use wrapping-friendly form for long call names.
    get_protocol_import = (f'({get_protocol_call})' if
                           len(get_protocol_call) >= 14 else get_protocol_call)

    # In embedded situations we have to pass different code to import
    # the protocol at build time than we do in our runtime code (where
    # there is only a dummy import for type-checking purposes)
    protocol_module_level_import_code: Optional[str]
    build_time_protocol_create_code: Optional[str]
    if embedded:
        protocol_module_level_import_code = (
            f'\n# Dummy import for type-checking purposes.\n'
            f'if bool(False):\n'
            f'    from {source_module} import {get_protocol_import}')
        protocol_create_code = f'protocol = {get_protocol_call}()'
        build_time_protocol_create_code = (
            f'from {source_module} import {get_protocol_import}\n'
            f'protocol = {get_protocol_call}()')
    else:
        protocol_module_level_import_code = None
        protocol_create_code = (
            f'from {source_module} import {get_protocol_import}\n'
            f'protocol = {get_protocol_call}()')
        build_time_protocol_create_code = None

    module_code = efro.message.create_receiver_module(
        basename,
        protocol_create_code=protocol_create_code,
        protocol_module_level_import_code=protocol_module_level_import_code,
        build_time_protocol_create_code=build_time_protocol_create_code,
        is_async=is_async,
    )

    out = format_yapf_str(projroot, module_code)

    print(f'Meta-building {Clr.BLD}{dst}{Clr.RST}')
    Path(dst).parent.mkdir(parents=True, exist_ok=True)
    with open(dst, 'w', encoding='utf-8') as outfile:
        outfile.write(out)
Esempio n. 20
0
def lazybuild() -> None:
    """Run a build command only if an input has changed."""
    import subprocess
    import batools.build
    from efro.error import CleanError
    if len(sys.argv) < 5:
        raise CleanError('Expected at least 3 args')
    try:
        category = batools.build.LazyBuildCategory(sys.argv[2])
    except ValueError as exc:
        raise CleanError(exc) from exc
    target = sys.argv[3]
    command = ' '.join(sys.argv[4:])
    try:
        batools.build.lazybuild(target, category, command)
    except subprocess.CalledProcessError as exc:
        raise CleanError(exc) from exc
Esempio n. 21
0
def checkenv() -> None:
    """Check for tools necessary to build and run the app."""
    import batools.build
    from efro.error import CleanError
    try:
        batools.build.checkenv()
    except RuntimeError as exc:
        raise CleanError(exc)
Esempio n. 22
0
def stage_server_file() -> None:
    """Stage files for the server environment with some filtering."""
    from efro.error import CleanError
    import batools.assetstaging
    if len(sys.argv) != 5:
        raise CleanError('Expected 3 args (mode, infile, outfile).')
    mode, infilename, outfilename = sys.argv[2], sys.argv[3], sys.argv[4]
    batools.assetstaging.stage_server_file(str(PROJROOT), mode, infilename,
                                           outfilename)
Esempio n. 23
0
def gen_binding_code() -> None:
    """Generate binding.inc file."""
    from efro.error import CleanError
    import batools.meta
    if len(sys.argv) != 4:
        raise CleanError('Expected 2 args (srcfile, dstfile)')
    inpath = sys.argv[2]
    outpath = sys.argv[3]
    batools.meta.gen_binding_code(str(PROJROOT), inpath, outpath)
Esempio n. 24
0
def ensure_prefab_platform() -> None:
    """Ensure we are running on a particular prefab platform.

    Note that prefab platform may not exactly match hardware/os.
    For example, when running in Linux under a WSL environment,
    the prefab platform may be Windows; not Linux. Also, a 64-bit
    os may be targeting a 32-bit platform.
    """
    import batools.build
    from efro.error import CleanError

    if len(sys.argv) != 3:
        raise CleanError('Expected 1 platform name arg.')
    needed = sys.argv[2]
    current = batools.build.get_current_prefab_platform()
    if current != needed:
        raise CleanError(
            f'Incorrect platform: we are {current}, this requires {needed}.')
Esempio n. 25
0
def gen_flat_data_code() -> None:
    """Generate a C++ include file from a Python file."""
    from efro.error import CleanError
    import batools.meta
    if len(sys.argv) != 5:
        raise CleanError('Expected 3 args (srcfile, dstfile, varname)')
    inpath = sys.argv[2]
    outpath = sys.argv[3]
    varname = sys.argv[4]
    batools.meta.gen_flat_data_code(str(PROJROOT), inpath, outpath, varname)
Esempio n. 26
0
 def handle_test_message_1(self, msg: _TMsg1) -> _TResp1:
     """Test."""
     if msg.ival == 1:
         raise CleanError('Testing Clean Error')
     if msg.ival == 2:
         raise RuntimeError('Testing Runtime Error')
     out = _TResp1(bval=True)
     if self.test_sidecar:
         setattr(out, '_sidecar_data', getattr(msg, '_sidecar_data'))
     return out
Esempio n. 27
0
def runpylint() -> None:
    """Run pylint checks on provided filenames."""
    from efro.terminal import Clr
    from efro.error import CleanError
    import efrotools.code
    if len(sys.argv) < 3:
        raise CleanError('Expected at least 1 filename arg.')
    filenames = sys.argv[2:]
    efrotools.code.runpylint(PROJROOT, filenames)
    print(f'{Clr.GRN}Pylint Passed.{Clr.RST}')
Esempio n. 28
0
def wsl_build_check_win_drive() -> None:
    """Make sure we're building on a windows drive."""
    import os
    import subprocess
    import textwrap
    from efro.error import CleanError

    if subprocess.run(['which', 'wslpath'], check=False,
                      capture_output=True).returncode != 0:
        raise CleanError('wslpath not found; you must run'
                         ' this from a WSL environment')

    if os.environ.get('WSL_BUILD_CHECK_WIN_DRIVE_IGNORE') == '1':
        return

    # Get a windows path to the current dir.
    path = subprocess.run(
        ['wslpath', '-w', '-a', os.getcwd()], capture_output=True,
        check=True).stdout.decode().strip()

    # If we're sitting under the linux filesystem, our path
    # will start with \\wsl$; fail in that case and explain why.
    if not path.startswith('\\\\wsl$'):
        return

    def _wrap(txt: str) -> str:
        return textwrap.fill(txt, 76)

    raise CleanError('\n\n'.join([
        _wrap('ERROR: This project appears to live on the Linux filesystem.'),
        _wrap('Visual Studio compiles will error here for reasons related'
              ' to Linux filesystem case-sensitivity, and thus are'
              ' disallowed.'
              ' Clone the repo to a location that maps to a native'
              ' Windows drive such as \'/mnt/c/ballistica\' and try again.'),
        _wrap('Note that WSL2 filesystem performance is poor when accessing'
              ' native Windows drives, so if Visual Studio builds are not'
              ' needed it may be best to keep things on the Linux filesystem.'
              ' This behavior may differ under WSL1 (untested).'),
        _wrap('Set env-var WSL_BUILD_CHECK_WIN_DRIVE_IGNORE=1 to skip'
              ' this check.')
    ]))
Esempio n. 29
0
def sync_all() -> None:
    """Runs full syncs between all efrotools projects.

    This list is defined in the EFROTOOLS_SYNC_PROJECTS env var.
    This assumes that there is a 'sync-full' and 'sync-list' Makefile target
    under each project.
    """
    import os
    import subprocess
    import concurrent.futures
    from efro.error import CleanError
    from efro.terminal import Clr
    print(f'{Clr.BLD}Updating formatting for all projects...{Clr.RST}')
    projects_str = os.environ.get('EFROTOOLS_SYNC_PROJECTS')
    if projects_str is None:
        raise CleanError('EFROTOOL_SYNC_PROJECTS is not defined.')
    projects = projects_str.split(':')

    def _format_project(fproject: str) -> None:
        fcmd = f'cd "{fproject}" && make format'
        print(fcmd)
        subprocess.run(fcmd, shell=True, check=True)

    # No matter what we're doing (even if just listing), run formatting
    # in all projects before beginning. Otherwise if we do a sync and then
    # a preflight we'll often wind up getting out-of-sync errors due to
    # formatting changing after the sync.
    with concurrent.futures.ThreadPoolExecutor(
            max_workers=len(projects)) as executor:
        # Converting this to a list will propagate any errors.
        list(executor.map(_format_project, projects))

    if len(sys.argv) > 2 and sys.argv[2] == 'list':
        # List mode
        for project in projects_str.split(':'):
            cmd = f'cd "{project}" && make sync-list'
            print(cmd)
            subprocess.run(cmd, shell=True, check=True)

    else:
        # Real mode
        for i in range(2):
            if i == 0:
                print(Clr.BLD + 'Running sync pass 1:'
                      ' (ensures all changes at dsts are pushed to src)' +
                      Clr.RST)
            else:
                print(Clr.BLD + 'Running sync pass 2:'
                      ' (ensures latest src is pulled to all dsts)' + Clr.RST)
            for project in projects_str.split(':'):
                cmd = f'cd "{project}" && make sync-full'
                print(cmd)
                subprocess.run(cmd, shell=True, check=True)
        print(Clr.BLD + 'Sync-all successful!' + Clr.RST)
Esempio n. 30
0
 def _update_dummy_module(self) -> None:
     # Update our dummy _ba module.
     # Note: This should happen near the end because it may run the cmake
     # build so its success may depend on the cmake build files having
     # already been updated.
     try:
         subprocess.run(['tools/pcommand', 'update_dummy_module'] +
                        self._checkarglist,
                        check=True)
     except Exception as exc:
         raise CleanError('Error checking/updating dummy module.') from exc