We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
[Bug] distutils deprecation warning makes test_completion.py fail on Python 3.10
# 🐛 Bug
## Description
<!-- A clear and concise description of what the bug is. -->

distutils is deprecated in Python 3.10 and its import in https://github.com/facebookresearch/hydra/blob/8b9ce30802165c1b24dbf4e513eb78d8ca8cacd6/tests/test_completion.py#L2 therefore emits a warning that makes the pytest run fail.

## Checklist
- [x] I checked on the latest version of Hydra
- [x] I created a minimal repro (See [this](https://stackoverflow.com/help/minimal-reproducible-example) for tips).

## To reproduce

** Minimal Code/Config snippet to reproduce **

- Run pytest with a Python 3.10 interpreter.

See conda build meta.yaml here: https://github.com/conda-forge/hydra-core-feedstock/blob/e62d99bd0afdcca166a731941f230b848015cbbe/recipe/meta.yaml
and associated CI failure here: https://dev.azure.com/conda-forge/feedstock-builds/_build/results?buildId=529074&view=logs&j=656edd35-690f-5c53-9ba3-09c10d0bea97&t=e5c8ab1d-8ff9-5cae-b332-e15ae582ed2d

(ignoring the warning per `-W` flag didn't work here)

**Stack trace/error message**
```
==================================== ERRORS ====================================
__________________ ERROR collecting tests/test_completion.py ___________________
tests/test_completion.py:2: in <module>
    import distutils.spawn
../_test_env_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_place/lib/python3.10/distutils/__init__.py:19: in <module>
    warnings.warn(_DEPRECATION_MESSAGE,
E   DeprecationWarning: The distutils package is deprecated and slated for removal in Python 3.12. Use setuptools or check PEP 632 for potential alternatives
=========================== short test summary info ============================
ERROR tests/test_completion.py - DeprecationWarning: The distutils package is...
!!!!!!!!!!!!!!!!!!!! Interrupted: 1 error during collection !!!!!!!!!!!!!!!!!!!!
=============================== 1 error in 1.18s ===============================
Tests failed for hydra-core-1.2.0-pyhd8ed1ab_0.tar.bz2 - moving package to /home/conda/feedstock_root/build_artifacts/broken
WARNING:conda_build.build:Tests failed for hydra-core-1.2.0-pyhd8ed1ab_0.tar.bz2 - moving package to /home/conda/feedstock_root/build_artifacts/broken
TESTS FAILED: hydra-core-1.2.0-pyhd8ed1ab_0.tar.bz2
```

## Expected Behavior
<!-- A clear and concise description of what you expected to happen. -->

Tests pass

## System information
- **Hydra Version** :  1.2.0
- **Python version** : 3.10.5
- **Virtual environment type and version** : conda env
- **Operating system** : Linux

## Additional context

~~This currently blocks the conda-forge feedstock update at https://github.com/conda-forge/hydra-core-feedstock/pull/19.
Workaround suggestions for this PR (preferably without file changes) are welcome.~~
Update: Got the tests to pass by adding the `-W ignore::DeprecationWarning` flag to the pytest args. With this workaround I have completed the feedstock update and hydra-core 1.2 should be available from conda-forge in a few minutes.

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `build_helpers/__init__.py`
Content:
```
1 # Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved
2 import setuptools  # isort:skip # noqa
3 import distutils  # isort:skip # noqa
4
```

Path: `build_helpers/build_helpers.py`
Content:
```
1 # Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved
2 import codecs
3 import distutils.log
4 import errno
5 import os
6 import re
7 import shutil
8 import subprocess
9 from os.path import abspath, basename, dirname, exists, isdir, join
10 from typing import List, Optional
11 
12 from setuptools import Command
13 from setuptools.command import build_py, develop, sdist
14 
15 
16 def find_version(*file_paths: str) -> str:
17     with codecs.open(os.path.join(*file_paths), "r") as fp:
18         version_file = fp.read()
19     version_match = re.search(r"^__version__ = ['\"]([^'\"]*)['\"]", version_file, re.M)
20     if version_match:
21         return version_match.group(1)
22     raise RuntimeError("Unable to find version string.")
23 
24 
25 def matches(patterns: List[str], string: str) -> bool:
26     string = string.replace("\\", "/")
27     for pattern in patterns:
28         if re.match(pattern, string):
29             return True
30     return False
31 
32 
33 def find_(
34     root: str,
35     rbase: str,
36     include_files: List[str],
37     include_dirs: List[str],
38     excludes: List[str],
39     scan_exclude: List[str],
40 ) -> List[str]:
41     files = []
42     scan_root = os.path.join(root, rbase)
43     with os.scandir(scan_root) as it:
44         for entry in it:
45             path = os.path.join(rbase, entry.name)
46             if matches(scan_exclude, path):
47                 continue
48 
49             if entry.is_dir():
50                 if matches(include_dirs, path):
51                     if not matches(excludes, path):
52                         files.append(path)
53                 else:
54                     ret = find_(
55                         root=root,
56                         rbase=path,
57                         include_files=include_files,
58                         include_dirs=include_dirs,
59                         excludes=excludes,
60                         scan_exclude=scan_exclude,
61                     )
62                     files.extend(ret)
63             else:
64                 if matches(include_files, path) and not matches(excludes, path):
65                     files.append(path)
66 
67     return files
68 
69 
70 def find(
71     root: str,
72     include_files: List[str],
73     include_dirs: List[str],
74     excludes: List[str],
75     scan_exclude: Optional[List[str]] = None,
76 ) -> List[str]:
77     if scan_exclude is None:
78         scan_exclude = []
79     return find_(
80         root=root,
81         rbase="",
82         include_files=include_files,
83         include_dirs=include_dirs,
84         excludes=excludes,
85         scan_exclude=scan_exclude,
86     )
87 
88 
89 class CleanCommand(Command):  # type: ignore
90     """
91     Our custom command to clean out junk files.
92     """
93 
94     description = "Cleans out generated and junk files we don't want in the repo"
95     dry_run: bool
96     user_options: List[str] = []
97 
98     def run(self) -> None:
99         files = find(
100             ".",
101             include_files=["^hydra/grammar/gen/.*"],
102             include_dirs=[
103                 "\\.egg-info$",
104                 "^.pytest_cache$",
105                 ".*/__pycache__$",
106                 ".*/multirun$",
107                 ".*/outputs$",
108                 "^build$",
109             ],
110             scan_exclude=["^.git$", "^.nox/.*$", "^website/.*$"],
111             excludes=[".*\\.gitignore$"],
112         )
113 
114         if self.dry_run:
115             print("Would clean up the following files and dirs")
116             print("\n".join(files))
117         else:
118             for f in files:
119                 if exists(f):
120                     if isdir(f):
121                         shutil.rmtree(f, ignore_errors=True)
122                     else:
123                         os.unlink(f)
124 
125     def initialize_options(self) -> None:
126         pass
127 
128     def finalize_options(self) -> None:
129         pass
130 
131 
132 def run_antlr(cmd: Command) -> None:
133     try:
134         cmd.announce("Generating parsers with antlr4", level=distutils.log.INFO)
135         cmd.run_command("antlr")
136     except OSError as e:
137         if e.errno == errno.ENOENT:
138             msg = f"| Unable to generate parsers: {e} |"
139             msg = "=" * len(msg) + "\n" + msg + "\n" + "=" * len(msg)
140             cmd.announce(f"{msg}", level=distutils.log.FATAL)
141             exit(1)
142         else:
143             raise
144 
145 
146 class BuildPyCommand(build_py.build_py):
147     def run(self) -> None:
148         if not self.dry_run:
149             self.run_command("clean")
150             run_antlr(self)
151         build_py.build_py.run(self)
152 
153 
154 class Develop(develop.develop):
155     def run(self) -> None:  # type: ignore
156         if not self.dry_run:
157             run_antlr(self)
158         develop.develop.run(self)
159 
160 
161 class SDistCommand(sdist.sdist):
162     def run(self) -> None:
163         if not self.dry_run:  # type: ignore
164             self.run_command("clean")
165             run_antlr(self)
166         sdist.sdist.run(self)
167 
168 
169 class ANTLRCommand(Command):  # type: ignore
170     """Generate parsers using ANTLR."""
171 
172     description = "Run ANTLR"
173     user_options: List[str] = []
174 
175     def run(self) -> None:
176         """Run command."""
177         root_dir = abspath(dirname(__file__))
178         project_root = abspath(dirname(basename(__file__)))
179         for grammar in [
180             "hydra/grammar/OverrideLexer.g4",
181             "hydra/grammar/OverrideParser.g4",
182         ]:
183             command = [
184                 "java",
185                 "-jar",
186                 join(root_dir, "bin/antlr-4.9.3-complete.jar"),
187                 "-Dlanguage=Python3",
188                 "-o",
189                 join(project_root, "hydra/grammar/gen/"),
190                 "-Xexact-output-dir",
191                 "-visitor",
192                 join(project_root, grammar),
193             ]
194 
195             self.announce(
196                 f"Generating parser for Python3: {command}", level=distutils.log.INFO
197             )
198 
199             subprocess.check_call(command)
200 
201     def initialize_options(self) -> None:
202         pass
203 
204     def finalize_options(self) -> None:
205         pass
206
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/build_helpers/__init__.py b/build_helpers/__init__.py
--- a/build_helpers/__init__.py
+++ b/build_helpers/__init__.py
@@ -1,3 +1,2 @@
 # Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved
 import setuptools  # isort:skip # noqa
-import distutils  # isort:skip # noqa
diff --git a/build_helpers/build_helpers.py b/build_helpers/build_helpers.py
--- a/build_helpers/build_helpers.py
+++ b/build_helpers/build_helpers.py
@@ -1,7 +1,7 @@
 # Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved
 import codecs
-import distutils.log
 import errno
+import logging
 import os
 import re
 import shutil
@@ -12,6 +12,8 @@
 from setuptools import Command
 from setuptools.command import build_py, develop, sdist
 
+log = logging.getLogger(__name__)
+
 
 def find_version(*file_paths: str) -> str:
     with codecs.open(os.path.join(*file_paths), "r") as fp:
@@ -131,13 +133,13 @@
 
 def run_antlr(cmd: Command) -> None:
     try:
-        cmd.announce("Generating parsers with antlr4", level=distutils.log.INFO)
+        log.info("Generating parsers with antlr4")
         cmd.run_command("antlr")
     except OSError as e:
         if e.errno == errno.ENOENT:
             msg = f"| Unable to generate parsers: {e} |"
             msg = "=" * len(msg) + "\n" + msg + "\n" + "=" * len(msg)
-            cmd.announce(f"{msg}", level=distutils.log.FATAL)
+            log.critical(f"{msg}")
             exit(1)
         else:
             raise
@@ -192,9 +194,7 @@
                 join(project_root, grammar),
             ]
 
-            self.announce(
-                f"Generating parser for Python3: {command}", level=distutils.log.INFO
-            )
+            log.info(f"Generating parser for Python3: {command}")
 
             subprocess.check_call(command)

{"golden_diff": "diff --git a/build_helpers/__init__.py b/build_helpers/__init__.py\n--- a/build_helpers/__init__.py\n+++ b/build_helpers/__init__.py\n@@ -1,3 +1,2 @@\n # Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved\n import setuptools  # isort:skip # noqa\n-import distutils  # isort:skip # noqa\ndiff --git a/build_helpers/build_helpers.py b/build_helpers/build_helpers.py\n--- a/build_helpers/build_helpers.py\n+++ b/build_helpers/build_helpers.py\n@@ -1,7 +1,7 @@\n # Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved\n import codecs\n-import distutils.log\n import errno\n+import logging\n import os\n import re\n import shutil\n@@ -12,6 +12,8 @@\n from setuptools import Command\n from setuptools.command import build_py, develop, sdist\n \n+log = logging.getLogger(__name__)\n+\n \n def find_version(*file_paths: str) -> str:\n     with codecs.open(os.path.join(*file_paths), \"r\") as fp:\n@@ -131,13 +133,13 @@\n \n def run_antlr(cmd: Command) -> None:\n     try:\n-        cmd.announce(\"Generating parsers with antlr4\", level=distutils.log.INFO)\n+        log.info(\"Generating parsers with antlr4\")\n         cmd.run_command(\"antlr\")\n     except OSError as e:\n         if e.errno == errno.ENOENT:\n             msg = f\"| Unable to generate parsers: {e} |\"\n             msg = \"=\" * len(msg) + \"\\n\" + msg + \"\\n\" + \"=\" * len(msg)\n-            cmd.announce(f\"{msg}\", level=distutils.log.FATAL)\n+            log.critical(f\"{msg}\")\n             exit(1)\n         else:\n             raise\n@@ -192,9 +194,7 @@\n                 join(project_root, grammar),\n             ]\n \n-            self.announce(\n-                f\"Generating parser for Python3: {command}\", level=distutils.log.INFO\n-            )\n+            log.info(f\"Generating parser for Python3: {command}\")\n \n             subprocess.check_call(command)\n", "issue": "[Bug] distutils deprecation warning makes test_completion.py fail on Python 3.10\n# \ud83d\udc1b Bug\r\n## Description\r\n<!-- A clear and concise description of what the bug is. -->\r\n\r\ndistutils is deprecated in Python 3.10 and its import in https://github.com/facebookresearch/hydra/blob/8b9ce30802165c1b24dbf4e513eb78d8ca8cacd6/tests/test_completion.py#L2 therefore emits a warning that makes the pytest run fail.\r\n\r\n## Checklist\r\n- [x] I checked on the latest version of Hydra\r\n- [x] I created a minimal repro (See [this](https://stackoverflow.com/help/minimal-reproducible-example) for tips).\r\n\r\n## To reproduce\r\n\r\n** Minimal Code/Config snippet to reproduce **\r\n\r\n- Run pytest with a Python 3.10 interpreter.\r\n\r\nSee conda build meta.yaml here: https://github.com/conda-forge/hydra-core-feedstock/blob/e62d99bd0afdcca166a731941f230b848015cbbe/recipe/meta.yaml\r\nand associated CI failure here: https://dev.azure.com/conda-forge/feedstock-builds/_build/results?buildId=529074&view=logs&j=656edd35-690f-5c53-9ba3-09c10d0bea97&t=e5c8ab1d-8ff9-5cae-b332-e15ae582ed2d\r\n\r\n(ignoring the warning per `-W` flag didn't work here)\r\n\r\n**Stack trace/error message**\r\n```\r\n==================================== ERRORS ====================================\r\n__________________ ERROR collecting tests/test_completion.py ___________________\r\ntests/test_completion.py:2: in <module>\r\n    import distutils.spawn\r\n../_test_env_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_place/lib/python3.10/distutils/__init__.py:19: in <module>\r\n    warnings.warn(_DEPRECATION_MESSAGE,\r\nE   DeprecationWarning: The distutils package is deprecated and slated for removal in Python 3.12. Use setuptools or check PEP 632 for potential alternatives\r\n=========================== short test summary info ============================\r\nERROR tests/test_completion.py - DeprecationWarning: The distutils package is...\r\n!!!!!!!!!!!!!!!!!!!! Interrupted: 1 error during collection !!!!!!!!!!!!!!!!!!!!\r\n=============================== 1 error in 1.18s ===============================\r\nTests failed for hydra-core-1.2.0-pyhd8ed1ab_0.tar.bz2 - moving package to /home/conda/feedstock_root/build_artifacts/broken\r\nWARNING:conda_build.build:Tests failed for hydra-core-1.2.0-pyhd8ed1ab_0.tar.bz2 - moving package to /home/conda/feedstock_root/build_artifacts/broken\r\nTESTS FAILED: hydra-core-1.2.0-pyhd8ed1ab_0.tar.bz2\r\n```\r\n\r\n## Expected Behavior\r\n<!-- A clear and concise description of what you expected to happen. -->\r\n\r\nTests pass\r\n\r\n## System information\r\n- **Hydra Version** :  1.2.0\r\n- **Python version** : 3.10.5\r\n- **Virtual environment type and version** : conda env\r\n- **Operating system** : Linux\r\n\r\n## Additional context\r\n\r\n~~This currently blocks the conda-forge feedstock update at https://github.com/conda-forge/hydra-core-feedstock/pull/19.\r\nWorkaround suggestions for this PR (preferably without file changes) are welcome.~~\r\nUpdate: Got the tests to pass by adding the `-W ignore::DeprecationWarning` flag to the pytest args. With this workaround I have completed the feedstock update and hydra-core 1.2 should be available from conda-forge in a few minutes.\n", "before_files": [{"content": "# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved\nimport setuptools  # isort:skip # noqa\nimport distutils  # isort:skip # noqa\n", "path": "build_helpers/__init__.py"}, {"content": "# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved\nimport codecs\nimport distutils.log\nimport errno\nimport os\nimport re\nimport shutil\nimport subprocess\nfrom os.path import abspath, basename, dirname, exists, isdir, join\nfrom typing import List, Optional\n\nfrom setuptools import Command\nfrom setuptools.command import build_py, develop, sdist\n\n\ndef find_version(*file_paths: str) -> str:\n    with codecs.open(os.path.join(*file_paths), \"r\") as fp:\n        version_file = fp.read()\n    version_match = re.search(r\"^__version__ = ['\\\"]([^'\\\"]*)['\\\"]\", version_file, re.M)\n    if version_match:\n        return version_match.group(1)\n    raise RuntimeError(\"Unable to find version string.\")\n\n\ndef matches(patterns: List[str], string: str) -> bool:\n    string = string.replace(\"\\\\\", \"/\")\n    for pattern in patterns:\n        if re.match(pattern, string):\n            return True\n    return False\n\n\ndef find_(\n    root: str,\n    rbase: str,\n    include_files: List[str],\n    include_dirs: List[str],\n    excludes: List[str],\n    scan_exclude: List[str],\n) -> List[str]:\n    files = []\n    scan_root = os.path.join(root, rbase)\n    with os.scandir(scan_root) as it:\n        for entry in it:\n            path = os.path.join(rbase, entry.name)\n            if matches(scan_exclude, path):\n                continue\n\n            if entry.is_dir():\n                if matches(include_dirs, path):\n                    if not matches(excludes, path):\n                        files.append(path)\n                else:\n                    ret = find_(\n                        root=root,\n                        rbase=path,\n                        include_files=include_files,\n                        include_dirs=include_dirs,\n                        excludes=excludes,\n                        scan_exclude=scan_exclude,\n                    )\n                    files.extend(ret)\n            else:\n                if matches(include_files, path) and not matches(excludes, path):\n                    files.append(path)\n\n    return files\n\n\ndef find(\n    root: str,\n    include_files: List[str],\n    include_dirs: List[str],\n    excludes: List[str],\n    scan_exclude: Optional[List[str]] = None,\n) -> List[str]:\n    if scan_exclude is None:\n        scan_exclude = []\n    return find_(\n        root=root,\n        rbase=\"\",\n        include_files=include_files,\n        include_dirs=include_dirs,\n        excludes=excludes,\n        scan_exclude=scan_exclude,\n    )\n\n\nclass CleanCommand(Command):  # type: ignore\n    \"\"\"\n    Our custom command to clean out junk files.\n    \"\"\"\n\n    description = \"Cleans out generated and junk files we don't want in the repo\"\n    dry_run: bool\n    user_options: List[str] = []\n\n    def run(self) -> None:\n        files = find(\n            \".\",\n            include_files=[\"^hydra/grammar/gen/.*\"],\n            include_dirs=[\n                \"\\\\.egg-info$\",\n                \"^.pytest_cache$\",\n                \".*/__pycache__$\",\n                \".*/multirun$\",\n                \".*/outputs$\",\n                \"^build$\",\n            ],\n            scan_exclude=[\"^.git$\", \"^.nox/.*$\", \"^website/.*$\"],\n            excludes=[\".*\\\\.gitignore$\"],\n        )\n\n        if self.dry_run:\n            print(\"Would clean up the following files and dirs\")\n            print(\"\\n\".join(files))\n        else:\n            for f in files:\n                if exists(f):\n                    if isdir(f):\n                        shutil.rmtree(f, ignore_errors=True)\n                    else:\n                        os.unlink(f)\n\n    def initialize_options(self) -> None:\n        pass\n\n    def finalize_options(self) -> None:\n        pass\n\n\ndef run_antlr(cmd: Command) -> None:\n    try:\n        cmd.announce(\"Generating parsers with antlr4\", level=distutils.log.INFO)\n        cmd.run_command(\"antlr\")\n    except OSError as e:\n        if e.errno == errno.ENOENT:\n            msg = f\"| Unable to generate parsers: {e} |\"\n            msg = \"=\" * len(msg) + \"\\n\" + msg + \"\\n\" + \"=\" * len(msg)\n            cmd.announce(f\"{msg}\", level=distutils.log.FATAL)\n            exit(1)\n        else:\n            raise\n\n\nclass BuildPyCommand(build_py.build_py):\n    def run(self) -> None:\n        if not self.dry_run:\n            self.run_command(\"clean\")\n            run_antlr(self)\n        build_py.build_py.run(self)\n\n\nclass Develop(develop.develop):\n    def run(self) -> None:  # type: ignore\n        if not self.dry_run:\n            run_antlr(self)\n        develop.develop.run(self)\n\n\nclass SDistCommand(sdist.sdist):\n    def run(self) -> None:\n        if not self.dry_run:  # type: ignore\n            self.run_command(\"clean\")\n            run_antlr(self)\n        sdist.sdist.run(self)\n\n\nclass ANTLRCommand(Command):  # type: ignore\n    \"\"\"Generate parsers using ANTLR.\"\"\"\n\n    description = \"Run ANTLR\"\n    user_options: List[str] = []\n\n    def run(self) -> None:\n        \"\"\"Run command.\"\"\"\n        root_dir = abspath(dirname(__file__))\n        project_root = abspath(dirname(basename(__file__)))\n        for grammar in [\n            \"hydra/grammar/OverrideLexer.g4\",\n            \"hydra/grammar/OverrideParser.g4\",\n        ]:\n            command = [\n                \"java\",\n                \"-jar\",\n                join(root_dir, \"bin/antlr-4.9.3-complete.jar\"),\n                \"-Dlanguage=Python3\",\n                \"-o\",\n                join(project_root, \"hydra/grammar/gen/\"),\n                \"-Xexact-output-dir\",\n                \"-visitor\",\n                join(project_root, grammar),\n            ]\n\n            self.announce(\n                f\"Generating parser for Python3: {command}\", level=distutils.log.INFO\n            )\n\n            subprocess.check_call(command)\n\n    def initialize_options(self) -> None:\n        pass\n\n    def finalize_options(self) -> None:\n        pass\n", "path": "build_helpers/build_helpers.py"}], "after_files": [{"content": "# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved\nimport setuptools  # isort:skip # noqa\n", "path": "build_helpers/__init__.py"}, {"content": "# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved\nimport codecs\nimport errno\nimport logging\nimport os\nimport re\nimport shutil\nimport subprocess\nfrom os.path import abspath, basename, dirname, exists, isdir, join\nfrom typing import List, Optional\n\nfrom setuptools import Command\nfrom setuptools.command import build_py, develop, sdist\n\nlog = logging.getLogger(__name__)\n\n\ndef find_version(*file_paths: str) -> str:\n    with codecs.open(os.path.join(*file_paths), \"r\") as fp:\n        version_file = fp.read()\n    version_match = re.search(r\"^__version__ = ['\\\"]([^'\\\"]*)['\\\"]\", version_file, re.M)\n    if version_match:\n        return version_match.group(1)\n    raise RuntimeError(\"Unable to find version string.\")\n\n\ndef matches(patterns: List[str], string: str) -> bool:\n    string = string.replace(\"\\\\\", \"/\")\n    for pattern in patterns:\n        if re.match(pattern, string):\n            return True\n    return False\n\n\ndef find_(\n    root: str,\n    rbase: str,\n    include_files: List[str],\n    include_dirs: List[str],\n    excludes: List[str],\n    scan_exclude: List[str],\n) -> List[str]:\n    files = []\n    scan_root = os.path.join(root, rbase)\n    with os.scandir(scan_root) as it:\n        for entry in it:\n            path = os.path.join(rbase, entry.name)\n            if matches(scan_exclude, path):\n                continue\n\n            if entry.is_dir():\n                if matches(include_dirs, path):\n                    if not matches(excludes, path):\n                        files.append(path)\n                else:\n                    ret = find_(\n                        root=root,\n                        rbase=path,\n                        include_files=include_files,\n                        include_dirs=include_dirs,\n                        excludes=excludes,\n                        scan_exclude=scan_exclude,\n                    )\n                    files.extend(ret)\n            else:\n                if matches(include_files, path) and not matches(excludes, path):\n                    files.append(path)\n\n    return files\n\n\ndef find(\n    root: str,\n    include_files: List[str],\n    include_dirs: List[str],\n    excludes: List[str],\n    scan_exclude: Optional[List[str]] = None,\n) -> List[str]:\n    if scan_exclude is None:\n        scan_exclude = []\n    return find_(\n        root=root,\n        rbase=\"\",\n        include_files=include_files,\n        include_dirs=include_dirs,\n        excludes=excludes,\n        scan_exclude=scan_exclude,\n    )\n\n\nclass CleanCommand(Command):  # type: ignore\n    \"\"\"\n    Our custom command to clean out junk files.\n    \"\"\"\n\n    description = \"Cleans out generated and junk files we don't want in the repo\"\n    dry_run: bool\n    user_options: List[str] = []\n\n    def run(self) -> None:\n        files = find(\n            \".\",\n            include_files=[\"^hydra/grammar/gen/.*\"],\n            include_dirs=[\n                \"\\\\.egg-info$\",\n                \"^.pytest_cache$\",\n                \".*/__pycache__$\",\n                \".*/multirun$\",\n                \".*/outputs$\",\n                \"^build$\",\n            ],\n            scan_exclude=[\"^.git$\", \"^.nox/.*$\", \"^website/.*$\"],\n            excludes=[\".*\\\\.gitignore$\"],\n        )\n\n        if self.dry_run:\n            print(\"Would clean up the following files and dirs\")\n            print(\"\\n\".join(files))\n        else:\n            for f in files:\n                if exists(f):\n                    if isdir(f):\n                        shutil.rmtree(f, ignore_errors=True)\n                    else:\n                        os.unlink(f)\n\n    def initialize_options(self) -> None:\n        pass\n\n    def finalize_options(self) -> None:\n        pass\n\n\ndef run_antlr(cmd: Command) -> None:\n    try:\n        log.info(\"Generating parsers with antlr4\")\n        cmd.run_command(\"antlr\")\n    except OSError as e:\n        if e.errno == errno.ENOENT:\n            msg = f\"| Unable to generate parsers: {e} |\"\n            msg = \"=\" * len(msg) + \"\\n\" + msg + \"\\n\" + \"=\" * len(msg)\n            log.critical(f\"{msg}\")\n            exit(1)\n        else:\n            raise\n\n\nclass BuildPyCommand(build_py.build_py):\n    def run(self) -> None:\n        if not self.dry_run:\n            self.run_command(\"clean\")\n            run_antlr(self)\n        build_py.build_py.run(self)\n\n\nclass Develop(develop.develop):\n    def run(self) -> None:  # type: ignore\n        if not self.dry_run:\n            run_antlr(self)\n        develop.develop.run(self)\n\n\nclass SDistCommand(sdist.sdist):\n    def run(self) -> None:\n        if not self.dry_run:  # type: ignore\n            self.run_command(\"clean\")\n            run_antlr(self)\n        sdist.sdist.run(self)\n\n\nclass ANTLRCommand(Command):  # type: ignore\n    \"\"\"Generate parsers using ANTLR.\"\"\"\n\n    description = \"Run ANTLR\"\n    user_options: List[str] = []\n\n    def run(self) -> None:\n        \"\"\"Run command.\"\"\"\n        root_dir = abspath(dirname(__file__))\n        project_root = abspath(dirname(basename(__file__)))\n        for grammar in [\n            \"hydra/grammar/OverrideLexer.g4\",\n            \"hydra/grammar/OverrideParser.g4\",\n        ]:\n            command = [\n                \"java\",\n                \"-jar\",\n                join(root_dir, \"bin/antlr-4.9.3-complete.jar\"),\n                \"-Dlanguage=Python3\",\n                \"-o\",\n                join(project_root, \"hydra/grammar/gen/\"),\n                \"-Xexact-output-dir\",\n                \"-visitor\",\n                join(project_root, grammar),\n            ]\n\n            log.info(f\"Generating parser for Python3: {command}\")\n\n            subprocess.check_call(command)\n\n    def initialize_options(self) -> None:\n        pass\n\n    def finalize_options(self) -> None:\n        pass\n", "path": "build_helpers/build_helpers.py"}]}