Repository: pypa/setuptools_scm

Issue:
document painless git usage in buildkit docker usage
For future reference (building only works in recent docker versions with buildkit support) you can avoid having `.git` in one of your layers by temporarily bind-mounting it during build time [[docs]](https://github.com/moby/buildkit/blob/v0.8.3/frontend/dockerfile/docs/syntax.md):

```dockerfile
RUN --mount=source=.git,target=.git,type=bind \
    pip install --no-cache-dir -e .
```

_Originally posted by @ddelange in https://github.com/pypa/setuptools_scm/issues/77#issuecomment-844927695_

Comments:

Patch:
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 6f40899..d6a2932 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -1,6 +1,6 @@
 repos:
 -   repo: https://github.com/psf/black
-    rev: 21.9b0
+    rev: 21.12b0
     hooks:
     - id: black
       args: [--safe, --quiet]
@@ -10,7 +10,7 @@ repos:
     -   id: reorder-python-imports
         args: [ "--application-directories=.:src" , --py3-plus]
 -   repo: https://github.com/pre-commit/pre-commit-hooks
-    rev: v4.0.1
+    rev: v4.1.0
     hooks:
     -   id: trailing-whitespace
     -   id: end-of-file-fixer
@@ -21,16 +21,16 @@ repos:
     hooks:
     -   id: flake8
 -   repo: https://github.com/asottile/pyupgrade
-    rev: v2.29.0
+    rev: v2.29.1
     hooks:
     - id: pyupgrade
       args: [--py36-plus]
 -   repo: https://github.com/asottile/setup-cfg-fmt
-    rev: v1.18.0
+    rev: v1.20.0
     hooks:
     -   id: setup-cfg-fmt
 -   repo: https://github.com/pre-commit/mirrors-mypy
-    rev: 'v0.910-1'
+    rev: 'v0.930'
     hooks:
     -   id: mypy
         additional_dependencies:
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index f4dd514..9704a01 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -1,3 +1,8 @@
+6.3.4
+======
+
+* compatibility adjustments for setuptools >58
+
 6.3.3
 ======
 
@@ -277,8 +282,8 @@ v3.0.0
 * require parse results to be ScmVersion or None (breaking change)
 * fix #266 by requiring the prefix word to be a word again
   (breaking change as the bug allowed arbitrary prefixes while the original feature only allowed words")
-* introduce a internal config object to allow the configruation fo tag parsing and prefixes
-  (thanks to @punkadiddle for introducing it and passing it trough)
+* introduce an internal config object to allow the configuration for tag parsing and prefixes
+  (thanks to @punkadiddle for introducing it and passing it through)
 
 v2.1.0
 ======
@@ -295,7 +300,7 @@ v2.0.0
 * fix #237 - correct imports in code examples
 * improve mercurial commit detection (thanks Aaron)
 * breaking change: remove support for setuptools before parsed versions
-* reintroduce manifest as the travis deploy cant use the file finder
+* reintroduce manifest as the travis deploy can't use the file finder
 * reconfigure flake8 for future compatibility with black
 * introduce support for branch name in version metadata and support a opt-in simplified semver version scheme
 
@@ -325,8 +330,8 @@ v1.16.0
 =======
 
 * drop support for eol python versions
-* #214 - fix missuse in surogate-escape api
-* add the node-and-timestamp local version sheme
+* #214 - fix misuse in surogate-escape api
+* add the node-and-timestamp local version scheme
 * respect git export ignores
 * avoid shlex.split on windows
 * fix #218 - better handling of mercurial edge-cases with tag commits
@@ -355,7 +360,7 @@ v1.15.5
 v1.15.4
 =======
 
-* fix issue #164: iterate all found entry points to avoid erros when pip remakes egg-info
+* fix issue #164: iterate all found entry points to avoid errors when pip remakes egg-info
 * enhance self-use to enable pip install from github again
 
 v1.15.3
@@ -386,7 +391,7 @@ v1.15.0
   when considering distance in commits
   (thanks Petre Mierlutiu)
 * fix issue #114: stop trying to be smart for the sdist
-  and ensure its always correctly usign itself
+  and ensure its always correctly using itself
 * update trove classifiers
 * fix issue #84: document using the installed package metadata for sphinx
 * fix issue #81: fail more gracious when git/hg are missing
@@ -401,7 +406,7 @@ v1.14.1
             don't consider untracked file
             (this was a regression due to #86 in v1.13.1)
 * consider the distance 0 when the git node is unknown
-  (happens when you haven't commited anything)
+  (happens when you haven't committed anything)
 
 v1.14.0
 =======
@@ -419,7 +424,7 @@ v1.13.0
 
 * fix regression caused by the fix of #101
   * assert types for version dumping
-  * strictly pass all versions trough parsed version metadata
+  * strictly pass all versions through parsed version metadata
 
 v1.12.0
 =======
@@ -457,7 +462,7 @@ v1.10.0
 * add support for overriding the version number via the
   environment variable SETUPTOOLS_SCM_PRETEND_VERSION
 
-* fix isssue #63 by adding the --match parameter to the git describe call
+* fix issue #63 by adding the --match parameter to the git describe call
   and prepare the possibility of passing more options to scm backends
 
 * fix issue #70 and #71 by introducing the parse keyword
@@ -501,7 +506,7 @@ v1.6.0
   before we would let the setup stay at version 0.0,
   now there is a ValueError
 
-* propperly raise errors on write_to missuse (thanks Te-jé Rodgers)
+* properly raise errors on write_to misuse (thanks Te-jé Rodgers)
 
 v1.5.5
 ======
@@ -538,7 +543,7 @@ v1.5.0
 v1.4.0
 ======
 
-* propper handling for sdist
+* proper handling for sdist
 * fix file-finder failure from windows
 * resuffle docs
 
diff --git a/README.rst b/README.rst
index fbfdb4c..20220d4 100644
--- a/README.rst
+++ b/README.rst
@@ -209,6 +209,39 @@ The underlying reason is, that services like *Read the Docs* sometimes change
 the working directory for good reasons and using the installed metadata
 prevents using needless volatile data there.
 
+Usage from Docker
+-----------------
+
+By default, docker will not copy the ``.git``  folder into your container.
+Therefore, builds with version inference might fail.
+Consequently, you can use the following snipped to infer the version from
+the host os without copying the entire ``.git`` folder to your Dockerfile.
+
+.. code:: dockerfile
+
+    RUN --mount=source=.git,target=.git,type=bind \
+        pip install --no-cache-dir -e .
+
+However, this build step introduces a dependency to the state of your local
+.git folder the build cache and triggers the long-running pip install process on every build.
+To optimize build caching, one can use an environment variable to pretend a pseudo
+version that is used to cache the results of the pip install process:
+
+.. code:: dockerfile
+
+    FROM python
+    COPY pyproject.toml
+    ARG PSEUDO_VERSION=1
+    RUN SETUPTOOLS_SCM_PRETEND_VERSION=${PSEUDO_VERSION} pip install -e .[test]
+    RUN --mount=source=.git,target=.git,type=bind pip install -e .
+
+Note that running this Dockerfile requires docker with BuildKit enabled
+`[docs] <https://github.com/moby/buildkit/blob/v0.8.3/frontend/dockerfile/docs/syntax.md>`_.
+
+To avoid BuildKit and mounting of the .git folder altogether, one can also pass the desired
+version as a build argument. Note that ``SETUPTOOLS_SCM_PRETEND_VERSION_FOR_${UPPERCASED_DIST_NAME}``
+is preferred over ``SETUPTOOLS_SCM_PRETEND_VERSION``.
+
 Notable Plugins
 ---------------
 
diff --git a/src/setuptools_scm/__main__.py b/src/setuptools_scm/__main__.py
index f3377b0..ef64819 100644
--- a/src/setuptools_scm/__main__.py
+++ b/src/setuptools_scm/__main__.py
@@ -1,15 +1,68 @@
-import sys
+import argparse
+import os
+import warnings
 
-from setuptools_scm import get_version
+from setuptools_scm import _get_version
+from setuptools_scm.config import Configuration
+from setuptools_scm.discover import walk_potential_roots
 from setuptools_scm.integration import find_files
 
 
 def main() -> None:
-    print("Guessed Version", get_version())
-    if "ls" in sys.argv:
-        for fname in find_files("."):
+    opts = _get_cli_opts()
+    root = opts.root or "."
+
+    try:
+        pyproject = opts.config or _find_pyproject(root)
+        root = opts.root or os.path.relpath(os.path.dirname(pyproject))
+        config = Configuration.from_file(pyproject)
+        config.root = root
+    except (LookupError, FileNotFoundError) as ex:
+        # no pyproject.toml OR no [tool.setuptools_scm]
+        warnings.warn(f"{ex}. Using default configuration.")
+        config = Configuration(root)
+
+    print(_get_version(config))
+
+    if opts.command == "ls":
+        for fname in find_files(config.root):
             print(fname)
 
 
+def _get_cli_opts():
+    prog = "python -m setuptools_scm"
+    desc = "Print project version according to SCM metadata"
+    parser = argparse.ArgumentParser(prog, description=desc)
+    # By default, help for `--help` starts with lower case, so we keep the pattern:
+    parser.add_argument(
+        "-r",
+        "--root",
+        default=None,
+        help='directory managed by the SCM, default: inferred from config file, or "."',
+    )
+    parser.add_argument(
+        "-c",
+        "--config",
+        default=None,
+        metavar="PATH",
+        help="path to 'pyproject.toml' with setuptools_scm config, "
+        "default: looked up in the current or parent directories",
+    )
+    sub = parser.add_subparsers(title="extra commands", dest="command", metavar="")
+    # We avoid `metavar` to prevent printing repetitive information
+    desc = "List files managed by the SCM"
+    sub.add_parser("ls", help=desc[0].lower() + desc[1:], description=desc)
+    return parser.parse_args()
+
+
+def _find_pyproject(parent):
+    for directory in walk_potential_roots(os.path.abspath(parent)):
+        pyproject = os.path.join(directory, "pyproject.toml")
+        if os.path.exists(pyproject):
+            return pyproject
+
+    raise FileNotFoundError("'pyproject.toml' was not found")
+
+
 if __name__ == "__main__":
     main()
diff --git a/src/setuptools_scm/_overrides.py b/src/setuptools_scm/_overrides.py
index 292936c..644e87d 100644
--- a/src/setuptools_scm/_overrides.py
+++ b/src/setuptools_scm/_overrides.py
@@ -12,7 +12,7 @@ PRETEND_KEY_NAMED = PRETEND_KEY + "_FOR_{name}"
 
 
 def _read_pretended_version_for(config: Configuration) -> Optional[ScmVersion]:
-    """read a a overriden version from the environment
+    """read a a overridden version from the environment
 
     tries ``SETUPTOOLS_SCM_PRETEND_VERSION``
     and ``SETUPTOOLS_SCM_PRETEND_VERSION_FOR_$UPPERCASE_DIST_NAME``
diff --git a/src/setuptools_scm/hg_git.py b/src/setuptools_scm/hg_git.py
index b871a39..323cdcb 100644
--- a/src/setuptools_scm/hg_git.py
+++ b/src/setuptools_scm/hg_git.py
@@ -73,7 +73,7 @@ class GitWorkdirHgClient(GitWorkdir, HgWorkdir):
                 trace("Cannot get git node so we use hg node", hg_node)
 
                 if hg_node == "0" * len(hg_node):
-                    # mimick Git behavior
+                    # mimic Git behavior
                     return None
 
                 return hg_node
diff --git a/tox.ini b/tox.ini
index 3f77ea9..e356153 100644
--- a/tox.ini
+++ b/tox.ini
@@ -8,7 +8,7 @@ filterwarnings=
     ignore:.*tool\.setuptools_scm.*
 markers=
     issue(id): reference to github issue
-    skip_commit: allows to skip commiting in the helpers
+    skip_commit: allows to skip committing in the helpers
 # disable unraisable until investigated
 addopts = -p no:unraisableexception
 


Test patch:
diff --git a/testing/test_main.py b/testing/test_main.py
index 97ea05e..ea1373f 100644
--- a/testing/test_main.py
+++ b/testing/test_main.py
@@ -1,4 +1,8 @@
 import os.path
+import sys
+import textwrap
+
+import pytest
 
 
 def test_main():
@@ -8,3 +12,51 @@ def test_main():
     with open(mainfile) as f:
         code = compile(f.read(), "__main__.py", "exec")
         exec(code)
+
+
+@pytest.fixture
+def repo(wd):
+    wd("git init")
+    wd("git config user.email user@host")
+    wd("git config user.name user")
+    wd.add_command = "git add ."
+    wd.commit_command = "git commit -m test-{reason}"
+
+    wd.write("README.rst", "My example")
+    wd.add_and_commit()
+    wd("git tag v0.1.0")
+
+    wd.write("file.txt", "file.txt")
+    wd.add_and_commit()
+
+    return wd
+
+
+def test_repo_with_config(repo):
+    pyproject = """\
+    [tool.setuptools_scm]
+    version_scheme = "no-guess-dev"
+
+    [project]
+    name = "example"
+    """
+    repo.write("pyproject.toml", textwrap.dedent(pyproject))
+    repo.add_and_commit()
+    res = repo((sys.executable, "-m", "setuptools_scm"))
+    assert res.startswith("0.1.0.post1.dev2")
+
+
+def test_repo_without_config(repo):
+    res = repo((sys.executable, "-m", "setuptools_scm"))
+    assert res.startswith("0.1.1.dev1")
+
+
+def test_repo_with_pyproject_missing_setuptools_scm(repo):
+    pyproject = """\
+    [project]
+    name = "example"
+    """
+    repo.write("pyproject.toml", textwrap.dedent(pyproject))
+    repo.add_and_commit()
+    res = repo((sys.executable, "-m", "setuptools_scm"))
+    assert res.startswith("0.1.1.dev2")
diff --git a/testing/test_mercurial.py b/testing/test_mercurial.py
index 2352bf9..3276802 100644
--- a/testing/test_mercurial.py
+++ b/testing/test_mercurial.py
@@ -82,7 +82,7 @@ def test_version_from_hg_id(wd):
     wd("hg up v0.1")
     assert wd.version == "0.1"
 
-    # commit originating from the taged revision
+    # commit originating from the tagged revision
     # that is not a actual tag
     wd.commit_testfile()
     assert wd.version.startswith("0.2.dev1+")
@@ -96,7 +96,7 @@ def test_version_from_hg_id(wd):
 
 def test_version_from_archival(wd):
     # entrypoints are unordered,
-    # cleaning the wd ensure this test wont break randomly
+    # cleaning the wd ensure this test won't break randomly
     wd.cwd.joinpath(".hg").rename(wd.cwd / ".nothg")
     wd.write(".hg_archival.txt", "node: 000000000000\n" "tag: 0.1\n")
     assert wd.version == "0.1"
@@ -172,7 +172,7 @@ def test_version_bump_from_commit_including_hgtag_mods(wd):
 @pytest.mark.usefixtures("version_1_0")
 def test_latest_tag_detection(wd):
     """Tests that tags not containing a "." are ignored, the same as for git.
-    Note that will be superceded by the fix for pypa/setuptools_scm/issues/235
+    Note that will be superseded by the fix for pypa/setuptools_scm/issues/235
     """
     wd('hg tag some-random-tag -u test -d "0 0"')
     assert wd.version == "1.0.0"
diff --git a/testing/test_setuptools_support.py b/testing/test_setuptools_support.py
index d6243db..b0c7230 100644
--- a/testing/test_setuptools_support.py
+++ b/testing/test_setuptools_support.py
@@ -7,9 +7,21 @@ import subprocess
 import sys
 
 import pytest
-from virtualenv.run import cli_run
 
-pytestmark = pytest.mark.filterwarnings(r"ignore:.*tool\.setuptools_scm.*")
+
+def cli_run(*k, **kw):
+    """this defers the virtualenv import
+    it helps to avoid warnings from the furthermore imported setuptools
+    """
+    global cli_run
+    from virtualenv.run import cli_run
+
+    return cli_run(*k, **kw)
+
+
+pytestmark = pytest.mark.filterwarnings(
+    r"ignore:.*tool\.setuptools_scm.*", r"always:.*setup.py install is deprecated.*"
+)
 
 
 ROOT = pathlib.Path(__file__).parent.parent
@@ -88,7 +100,7 @@ def check(venv, expected_version, **env):
 
 
 @pytest.mark.skipif(
-    sys.version_info[:2] >= (3, 10), reason="old setuptools wont work on python 3.10"
+    sys.version_info[:2] >= (3, 10), reason="old setuptools won't work on python 3.10"
 )
 def test_distlib_setuptools_works(venv_maker):
     venv = venv_maker.get_venv(setuptools="45.0.0", pip="9.0", python="3.6")
@@ -137,7 +149,7 @@ def prepare_setup_py_config(pkg: pathlib.Path):
 
 
 @pytest.mark.skipif(
-    sys.version_info[:2] >= (3, 10), reason="old setuptools wont work on python 3.10"
+    sys.version_info[:2] >= (3, 10), reason="old setuptools won't work on python 3.10"
 )
 @pytest.mark.parametrize("setuptools", [f"{v}.0" for v in range(31, 45)])
 @pytest.mark.parametrize(