diff --git a/.idea/dictionaries/ericf.xml b/.idea/dictionaries/ericf.xml
index 5e46b2e7..fcb5af3b 100644
--- a/.idea/dictionaries/ericf.xml
+++ b/.idea/dictionaries/ericf.xml
@@ -54,6 +54,7 @@
anroid
antigravity
apichanges
+ apis
apks
appcfg
appconfig
@@ -934,6 +935,7 @@
minigames
minusbutton
minval
+ minver
mios
mipmap
mipmaps
@@ -1689,6 +1691,7 @@
vmshell
vmware
vmwarevm
+ vnums
vobj
voffs
vorbis
@@ -1696,6 +1699,7 @@
vrmode
vrtesting
vscode
+ vstr
vsync
vsyncs
vval
diff --git a/Makefile b/Makefile
index 039aeb9f..80509e66 100644
--- a/Makefile
+++ b/Makefile
@@ -36,7 +36,7 @@ DOCPREFIX = "ballisticacore_"
################################################################################
# List targets in this Makefile and basic descriptions for them.
-help: list
+help:
@tools/snippets makefile_target_list Makefile
# Prerequisites that should be in place before running most any other build;
@@ -448,7 +448,7 @@ TOOL_CFG_SRC = tools/efrotools/snippets.py config/config.json
.pycheckers: config/toolconfigsrc/pycheckers ${TOOL_CFG_SRC}
${TOOL_CFG_INST} $< $@
-.cache/checkenv:
+.cache/checkenv: tools/snippets
@tools/snippets checkenv
@mkdir -p .cache
@touch .cache/checkenv
diff --git a/tools/efrotools/efrocache.py b/tools/efrotools/efrocache.py
index c111e68b..acf55684 100644
--- a/tools/efrotools/efrocache.py
+++ b/tools/efrotools/efrocache.py
@@ -210,7 +210,7 @@ def update_cache(makefile_dirs: List[str]) -> None:
# Push what we just wrote to the staging server
print('Pushing cache to staging...', flush=True)
- run('rsync --recursive build/efrocache/'
+ run('rsync --progress --recursive build/efrocache/'
' ubuntu@ballistica.net:files.ballistica.net/cache/ba1/')
print(f'Cache update successful!')
diff --git a/tools/snippets b/tools/snippets
index 633dde22..1cf147eb 100755
--- a/tools/snippets
+++ b/tools/snippets
@@ -49,7 +49,7 @@ from efrotools.snippets import ( # pylint: disable=unused-import
compile_python_files)
if TYPE_CHECKING:
- from typing import Optional, List
+ from typing import Optional, List, Sequence
# Parts of full-tests suite we only run on particular days.
# (This runs in listed order so should be randomized by hand to avoid
@@ -640,11 +640,63 @@ def warm_start_asset_build() -> None:
check=True)
+def _vstr(nums: Sequence[int]) -> str:
+ return '.'.join(str(i) for i in nums)
+
+
def checkenv() -> None:
"""Check for tools necessary to build and run the app."""
- if subprocess.run(['which', 'python3.7'], check=False,
+ print('Checking environment...', flush=True)
+
+ python_bin = 'python3.7'
+ pylint_min_ver = [2, 4, 3]
+ mypy_min_ver = [0, 740]
+
+ # Make sure they've got our target python version.
+ if subprocess.run(['which', python_bin], check=False,
capture_output=True).returncode != 0:
- raise CleanError('python3.7 is required for these builds.')
+ raise CleanError(f'{python_bin} is required.')
+
+ # Make sure they've got pip for that python version.
+ if subprocess.run(f"{python_bin} -m pip --version",
+ shell=True,
+ check=False,
+ capture_output=True).returncode != 0:
+ raise CleanError('pip (for {python_bin}) is required.')
+
+ # Check for some required python modules.
+ for modname, minver in [
+ ('pylint', pylint_min_ver),
+ ('mypy', mypy_min_ver),
+ ('typing_extensions', None),
+ ('pytz', None),
+ ]:
+
+ if minver is not None:
+ results = subprocess.run(f'{python_bin} -m {modname} --version',
+ shell=True,
+ check=False,
+ capture_output=True)
+ else:
+ results = subprocess.run(f'{python_bin} -c "import {modname}"',
+ shell=True,
+ check=False,
+ capture_output=True)
+ if results.returncode != 0:
+ raise CleanError(
+ f'{modname} (for {python_bin}) is required.\n'
+ f'To install it, try: "{python_bin} -m pip install {modname}"')
+ if minver is not None:
+ ver_line = results.stdout.decode().splitlines()[0]
+ assert modname in ver_line
+ vnums = [int(x) for x in ver_line.split()[-1].split('.')]
+ assert len(vnums) == len(minver)
+ if vnums < minver:
+ raise CleanError(
+ f'{modname} ver. {_vstr(minver)} or newer required;'
+ f' found {_vstr(vnums)}')
+
+ print('Environment ok.', flush=True)
def make_prefab() -> None: