Repository: facebookresearch/hydra

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.


Discussion:
hi @mdraw thanks for the PR and for maintaining hydra in conda. we will take a look at this!

Resolving patch:
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 finalize_options(self) -> None:
 
 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 @@ def run(self) -> None:
                 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)
 


Test patch:
diff --git a/tests/test_completion.py b/tests/test_completion.py
--- a/tests/test_completion.py
+++ b/tests/test_completion.py
@@ -1,7 +1,7 @@
 # Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved
-import distutils.spawn
 import os
 import re
+import shutil
 import subprocess
 import sys
 from pathlib import Path
@@ -20,11 +20,11 @@
 
 
 def is_expect_exists() -> bool:
-    return distutils.spawn.find_executable("expect") is not None
+    return shutil.which("expect") is not None
 
 
 def is_fish_supported() -> bool:
-    if distutils.spawn.find_executable("fish") is None:
+    if shutil.which("fish") is None:
         return False
 
     proc = subprocess.run(
@@ -46,7 +46,7 @@ def is_fish_supported() -> bool:
 
 
 def is_zsh_supported() -> bool:
-    if distutils.spawn.find_executable("zsh") is None:
+    if shutil.which("zsh") is None:
         return False
 
     proc = subprocess.run(