261 строка
8.9 KiB
Python
Executable File
261 строка
8.9 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
|
|
import ast
|
|
import sys
|
|
import os
|
|
import re
|
|
import subprocess
|
|
import tempfile
|
|
from typing import List, Optional, Pattern
|
|
|
|
RELEASE_PATTERN = re.compile(r"release_[0-9]+(_docs)*")
|
|
# This matches the various ways to invoke pip: "pip", "pip3", "python -m pip"
|
|
# It matches "mlagents" and "mlagents_envs", accessible as group "package"
|
|
# and optionally matches the version, e.g. "==1.2.3"
|
|
PIP_INSTALL_PATTERN = re.compile(
|
|
r"(python -m )?pip3* install (?P<quiet>-q )?(?P<package>mlagents(_envs)?)(==[0-9]+\.[0-9]+\.[0-9]+(\.dev[0-9]+)?)?"
|
|
)
|
|
TRAINER_INIT_FILE = "ml-agents/mlagents/trainers/__init__.py"
|
|
|
|
MATCH_ANY = re.compile(r"(?s).*")
|
|
# Filename -> regex list to allow specific lines.
|
|
# To allow everything in the file (effectively skipping it), use MATCH_ANY for the value
|
|
ALLOW_LIST = {
|
|
# Previous release table
|
|
"docs/Python-PettingZoo-API.md": re.compile(
|
|
r"\*\*(Verified Package ([0-9]\.?)*|Release [0-9]+)\*\*"
|
|
),
|
|
"docs/Versioning.md": MATCH_ANY,
|
|
"com.unity.ml-agents/CHANGELOG.md": MATCH_ANY,
|
|
"utils/make_readme_table.py": MATCH_ANY,
|
|
"utils/validate_release_links.py": MATCH_ANY,
|
|
}
|
|
|
|
|
|
def test_release_pattern():
|
|
# Just some sanity check that the regex works as expected.
|
|
for s, expected in [
|
|
(
|
|
"https://github.com/Unity-Technologies/ml-agents/blob/release_4_docs/Food.md",
|
|
True,
|
|
),
|
|
("https://github.com/Unity-Technologies/ml-agents/blob/release_4/Foo.md", True),
|
|
(
|
|
"git clone --branch release_4 https://github.com/Unity-Technologies/ml-agents.git",
|
|
True,
|
|
),
|
|
(
|
|
"https://github.com/Unity-Technologies/ml-agents/blob/release_123_docs/Foo.md",
|
|
True,
|
|
),
|
|
(
|
|
"https://github.com/Unity-Technologies/ml-agents/blob/release_123/Foo.md",
|
|
True,
|
|
),
|
|
(
|
|
"https://github.com/Unity-Technologies/ml-agents/blob/latest_release/docs/Foo.md",
|
|
False,
|
|
),
|
|
]:
|
|
assert bool(RELEASE_PATTERN.search(s)) is expected
|
|
|
|
print("release tests OK!")
|
|
|
|
|
|
def test_pip_pattern():
|
|
# Just some sanity check that the regex works as expected.
|
|
for s, expected in [
|
|
("pip install mlagents", True),
|
|
("pip3 install -q mlagents", True),
|
|
("python -m pip install mlagents", True),
|
|
("python -m pip install mlagents==1.2.3", True),
|
|
("python -m pip install mlagents_envs==1.2.3", True),
|
|
("python -m pip install mlagents==11.222.3333", True),
|
|
("python -m pip install mlagents_envs==11.222.3333", True),
|
|
]:
|
|
assert bool(PIP_INSTALL_PATTERN.search(s)) is expected
|
|
|
|
sub_expected = "Try running rm -rf / to install"
|
|
assert sub_expected == PIP_INSTALL_PATTERN.sub(
|
|
"rm -rf /", "Try running python -m pip install mlagents==1.2.3 to install"
|
|
)
|
|
|
|
print("pip tests OK!")
|
|
|
|
|
|
def update_pip_install_line(line, package_verion):
|
|
match = PIP_INSTALL_PATTERN.search(line)
|
|
if match is not None: # if there is a pip install line
|
|
package_name = match.group("package")
|
|
quiet_option = match.group("quiet") or ""
|
|
replacement_version = (
|
|
f"python -m pip install {quiet_option}{package_name}=={package_verion}"
|
|
)
|
|
updated = PIP_INSTALL_PATTERN.sub(replacement_version, line)
|
|
return updated
|
|
else: # Don't do anything
|
|
return line
|
|
|
|
|
|
def git_ls_files() -> List[str]:
|
|
"""
|
|
Run "git ls-files" and return a list with one entry per line.
|
|
This returns the list of all files tracked by git.
|
|
"""
|
|
return subprocess.check_output(["git", "ls-files"], universal_newlines=True).split(
|
|
"\n"
|
|
)
|
|
|
|
|
|
def get_release_tag() -> Optional[str]:
|
|
"""
|
|
Returns the release tag for the mlagents python package.
|
|
This will be None on the main branch.
|
|
:return:
|
|
"""
|
|
with open(TRAINER_INIT_FILE) as f:
|
|
for line in f:
|
|
if "__release_tag__" in line:
|
|
lhs, equals_string, rhs = line.strip().partition(" = ")
|
|
# Evaluate the right hand side of the expression
|
|
return ast.literal_eval(rhs)
|
|
# If we couldn't find the release tag, raise an exception
|
|
# (since we can't return None here)
|
|
raise RuntimeError("Can't determine release tag")
|
|
|
|
|
|
def get_python_package_version() -> str:
|
|
"""
|
|
Returns the mlagents python package.
|
|
:return:
|
|
"""
|
|
with open(TRAINER_INIT_FILE) as f:
|
|
for line in f:
|
|
if "__version__" in line:
|
|
lhs, equals_string, rhs = line.strip().partition(" = ")
|
|
# Evaluate the right hand side of the expression
|
|
return ast.literal_eval(rhs)
|
|
# If we couldn't find the release tag, raise an exception
|
|
# (since we can't return None here)
|
|
raise RuntimeError("Can't determine python package version")
|
|
|
|
|
|
def check_file(
|
|
filename: str,
|
|
release_tag_pattern: Pattern,
|
|
release_tag: str,
|
|
pip_allow_pattern: Pattern,
|
|
package_version: str,
|
|
) -> List[str]:
|
|
"""
|
|
Validate a single file and return any offending lines.
|
|
"""
|
|
bad_lines = []
|
|
with tempfile.TemporaryDirectory() as tempdir:
|
|
if not os.path.exists(tempdir):
|
|
os.makedirs(tempdir)
|
|
new_file_name = os.path.join(tempdir, os.path.basename(filename))
|
|
with open(new_file_name, "w+") as new_file:
|
|
# default to match everything if there is nothing in the ALLOW_LIST
|
|
allow_list_pattern = ALLOW_LIST.get(filename, None)
|
|
with open(filename) as f:
|
|
for line in f:
|
|
# Does it contain anything of the form release_123
|
|
has_release_pattern = RELEASE_PATTERN.search(line) is not None
|
|
# Does it contain this particular release, e.g. release_42 or release_42_docs
|
|
has_release_tag_pattern = (
|
|
release_tag_pattern.search(line) is not None
|
|
)
|
|
# Does it contain the allow list pattern for the file (if there is one)
|
|
has_allow_list_pattern = (
|
|
allow_list_pattern
|
|
and allow_list_pattern.search(line) is not None
|
|
)
|
|
|
|
pip_install_ok = (
|
|
has_allow_list_pattern
|
|
or PIP_INSTALL_PATTERN.search(line) is None
|
|
or pip_allow_pattern.search(line) is not None
|
|
)
|
|
|
|
release_tag_ok = (
|
|
not has_release_pattern
|
|
or has_release_tag_pattern
|
|
or has_allow_list_pattern
|
|
)
|
|
|
|
if release_tag_ok and pip_install_ok:
|
|
new_file.write(line)
|
|
else:
|
|
bad_lines.append(f"{filename}: {line}")
|
|
new_line = re.sub(r"release_[0-9]+", rf"{release_tag}", line)
|
|
new_line = update_pip_install_line(new_line, package_version)
|
|
new_file.write(new_line)
|
|
if bad_lines:
|
|
if os.path.exists(filename):
|
|
os.remove(filename)
|
|
os.rename(new_file_name, filename)
|
|
|
|
return bad_lines
|
|
|
|
|
|
def check_all_files(
|
|
release_allow_pattern: Pattern,
|
|
release_tag: str,
|
|
pip_allow_pattern: Pattern,
|
|
package_version: str,
|
|
) -> List[str]:
|
|
"""
|
|
Validate all files tracked by git.
|
|
:param release_allow_pattern:
|
|
"""
|
|
bad_lines = []
|
|
file_types = {".py", ".md", ".cs", ".ipynb"}
|
|
for file_name in git_ls_files():
|
|
if "localized" in file_name or os.path.splitext(file_name)[1] not in file_types:
|
|
continue
|
|
bad_lines += check_file(
|
|
file_name,
|
|
release_allow_pattern,
|
|
release_tag,
|
|
pip_allow_pattern,
|
|
package_version,
|
|
)
|
|
return bad_lines
|
|
|
|
|
|
def main():
|
|
release_tag = get_release_tag()
|
|
if not release_tag:
|
|
print("Release tag is None, exiting")
|
|
sys.exit(0)
|
|
|
|
package_version = get_python_package_version()
|
|
print(f"Release tag: {release_tag}")
|
|
print(f"Python package version: {package_version}")
|
|
release_allow_pattern = re.compile(f"{release_tag}(_docs)?")
|
|
pip_allow_pattern = re.compile(
|
|
rf"python -m pip install (-q )?mlagents(_envs)?=={package_version}"
|
|
)
|
|
bad_lines = check_all_files(
|
|
release_allow_pattern, release_tag, pip_allow_pattern, package_version
|
|
)
|
|
if bad_lines:
|
|
for line in bad_lines:
|
|
print(line)
|
|
|
|
print("*************************************************************")
|
|
print(
|
|
"This script attempted to fix the above errors. Please double "
|
|
+ "check them to make sure the replacements were done correctly"
|
|
)
|
|
|
|
sys.exit(1 if bad_lines else 0)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
if "--test" in sys.argv:
|
|
test_release_pattern()
|
|
test_pip_pattern()
|
|
main()
|