use setuptools_scm and build_requires (#6775)

* use setuptools_scm and build_requires

* remove kwargs from setup.py

* fix tests; fetch in depth in github actions

* use build to build sdist/wheel

* try updating pip and setuptools

* update pip

* remove or unpin wheel

* bring back the version in pyinstaller

* Update scripts/build.py

Co-authored-by: Ruslan Kuprieiev <[email protected]>
This commit is contained in:
Saugat Pachhai
2021-10-12 17:58:14 +03:00
committed by GitHub
co-authored by Ruslan Kuprieiev
parent 53255c22b3
commit 8abda93e3e
15 changed files with 63 additions and 289 deletions
+13 -1
View File
@@ -11,6 +11,8 @@ jobs:
runs-on: ubuntu-18.04
steps:
- uses: actions/[email protected]
with:
fetch-depth: 0
- name: Set up Python 3.8
uses: actions/[email protected]
with:
@@ -40,6 +42,8 @@ jobs:
runs-on: ubuntu-18.04
steps:
- uses: actions/[email protected]
with:
fetch-depth: 0
- name: Set up Python 3.8
uses: actions/[email protected]
with:
@@ -69,6 +73,8 @@ jobs:
runs-on: macos-10.15
steps:
- uses: actions/[email protected]
with:
fetch-depth: 0
- name: Set up Python 3.8
uses: actions/[email protected]
with:
@@ -101,6 +107,8 @@ jobs:
runs-on: windows-2019
steps:
- uses: actions/[email protected]
with:
fetch-depth: 0
- name: Set up Python 3.8
uses: actions/[email protected]
with:
@@ -129,6 +137,8 @@ jobs:
runs-on: ubuntu-18.04
steps:
- uses: actions/[email protected]
with:
fetch-depth: 0
- name: Set up Python 3.7
uses: actions/[email protected]
with:
@@ -136,7 +146,9 @@ jobs:
- name: Install
run: pip install ".[all,tests]"
- name: Build packages
run: ./scripts/build_package.sh
run: |
pip install -U pip
./scripts/build_package.sh
- name: Publish packages to PyPI
if: github.event_name == 'release'
uses: pypa/gh-action-pypi-publish@master
+4
View File
@@ -21,6 +21,8 @@ jobs:
with:
access_token: ${{ github.token }}
- uses: actions/[email protected]
with:
fetch-depth: 0
- name: Set up Python 3.8
uses: actions/[email protected]
with:
@@ -50,6 +52,8 @@ jobs:
pyv: "3.10"
steps:
- uses: actions/[email protected]
with:
fetch-depth: 0
- name: Set up Python
uses: actions/[email protected]
with:
+1
View File
@@ -38,3 +38,4 @@ scripts/ci/gcp-creds.json
.gcp-creds.json
*~
/dvc/_version.py
+5
View File
@@ -7,6 +7,11 @@ repos:
language_version: python3
repo: https://github.com/ambv/black
rev: 21.9b0
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.0.1
hooks:
- id: check-toml
- id: check-yaml
- hooks:
- id: codespell
args:
-1
View File
@@ -1,2 +1 @@
include fastentrypoints.py
include LICENSE
+5 -73
View File
@@ -1,74 +1,6 @@
# Used in setup.py, so don't pull any additional dependencies
#
# Based on:
# - https://github.com/python/mypy/blob/master/mypy/version.py
# - https://github.com/python/mypy/blob/master/mypy/git.py
import os
import subprocess
_BASE_VERSION = "2.8.1"
def _generate_version(base_version):
"""Generate a version with information about the Git repository."""
pkg_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
if not _is_git_repo(pkg_dir) or not _have_git():
return base_version
if _is_release(pkg_dir, base_version) and not _is_dirty(pkg_dir):
return base_version
return "{base_version}+{short_sha}{dirty}".format(
base_version=base_version,
short_sha=_git_revision(pkg_dir).decode("utf-8")[0:6],
dirty=".mod" if _is_dirty(pkg_dir) else "",
)
def _is_git_repo(dir_path):
"""Is the given directory version-controlled with Git?"""
return os.path.exists(os.path.join(dir_path, ".git"))
def _have_git():
"""Can we run the git executable?"""
# pylint: disable=unused-import
try:
subprocess.check_output(["git", "--help"])
return True
except subprocess.CalledProcessError:
return False
except OSError:
return False
def _is_release(dir_path, base_version):
try:
output = subprocess.check_output(
["git", "describe", "--tags", "--exact-match"],
cwd=dir_path,
stderr=subprocess.STDOUT,
).decode("utf-8")
tag = output.strip()
return tag == base_version
except subprocess.CalledProcessError:
return False
def _git_revision(dir_path):
"""Get SHA of the HEAD of a Git repository."""
return subprocess.check_output(
["git", "rev-parse", "HEAD"], cwd=dir_path
).strip()
def _is_dirty(dir_path):
"""Check whether a git repository has uncommitted changes."""
try:
subprocess.check_call(["git", "diff", "--quiet"], cwd=dir_path)
return False
except subprocess.CalledProcessError:
return True
__version__ = _generate_version(_BASE_VERSION)
from ._version import version as __version__
from ._version import version_tuple # noqa: F401
except ImportError:
__version__ = "UNKNOWN"
-121
View File
@@ -1,121 +0,0 @@
# noqa: D300,D400
# Copyright (c) 2016, Aaron Christianson
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
# 1. Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
#
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
# PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
# TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
"""
Monkey patch setuptools to write faster console_scripts with this format:
import sys
from mymodule import entry_function
sys.exit(entry_function())
This is better.
(c) 2016, Aaron Christianson
http://github.com/ninjaaron/fast-entry_points
"""
import re
from setuptools.command import easy_install
TEMPLATE = r"""
# -*- coding: utf-8 -*-
# EASY-INSTALL-ENTRY-SCRIPT: '{3}','{4}','{5}'
__requires__ = '{3}'
import re
import sys
from {0} import {1}
if __name__ == '__main__':
sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0])
sys.exit({2}())
""".lstrip()
@classmethod
def get_args(cls, dist, header=None): # noqa: D205,D400
"""
Yield write_script() argument tuples for a distribution's
console_scripts and gui_scripts entry points.
"""
if header is None:
# pylint: disable=E1101
header = cls.get_header()
spec = str(dist.as_requirement())
for type_ in "console", "gui":
group = type_ + "_scripts"
for name, ep in dist.get_entry_map(group).items():
# ensure_safe_name
if re.search(r"[\\/]", name):
raise ValueError("Path separators not allowed in script names")
script_text = TEMPLATE.format(
ep.module_name,
ep.attrs[0],
".".join(ep.attrs),
spec,
group,
name,
)
# pylint: disable=E1101,protected-access
args = cls._get_script_args(type_, name, header, script_text)
yield from args
# pylint: disable=E1101
easy_install.ScriptWriter.get_args = get_args
def main():
import os
import shutil
import sys
dests = sys.argv[1:] or ["."]
filename = re.sub(r"\.pyc$", ".py", __file__)
for dst in dests:
shutil.copy(filename, dst)
manifest_path = os.path.join(dst, "MANIFEST.in")
setup_path = os.path.join(dst, "setup.py")
# Insert the include statement to MANIFEST.in if not present
with open(manifest_path, "a+") as manifest:
manifest.seek(0)
manifest_content = manifest.read()
if "include fastentrypoints.py" not in manifest_content:
manifest.write(
("\n" if manifest_content else "")
+ "include fastentrypoints.py"
)
# Insert the import statement to setup.py if not present
with open(setup_path, "a+") as setup:
setup.seek(0)
setup_content = setup.read()
if "import fastentrypoints" not in setup_content:
setup.seek(0)
setup.truncate()
setup.write("import fastentrypoints\n" + setup_content)
+14
View File
@@ -1,3 +1,17 @@
[build-system]
requires = [
"setuptools>=58.2",
"wheel",
"setuptools_scm[toml]>=6.3.1",
# Prevents pkg_resources import in entry point script,
# see https://github.com/ninjaaron/fast-entry_points.
# This saves about 200 ms on startup time for non-wheel installs.
"fastentrypoints>=0.12"
]
[tool.setuptools_scm]
write_to = "dvc/_version.py"
[tool.black]
line-length = 79
include = '\.pyi?$'
+2 -2
View File
@@ -10,5 +10,5 @@ fi
echo 'PKG = "pip"' >dvc/utils/build.py
python setup.py sdist
python setup.py bdist_wheel
python -m pip install -U build setuptools>=58.2
python -m build --sdist --wheel --outdir dist/
+2 -1
View File
@@ -9,5 +9,6 @@ fi
rm -rf dist/
pip install twine
python setup.py sdist bdist_wheel
python -m pip install -U build setuptools>=58.2
python -m build --sdist --wheel --outdir dist/
twine upload dist/*
+5
View File
@@ -26,10 +26,15 @@ classifiers =
Programming Language :: Python :: 3.10
[options]
setup_requires =
setuptools>=58.2
setuptools_scm[toml]>=6.3.1
fastentrypoints>=0.12
python_requires = >=3.6
zip_safe = False
packages = find:
include_package_data = True
use_scm_version = True
install_requires =
# See https://github.com/pyinstaller/pyinstaller/issues/1945
ply>=3.9
+1 -64
View File
@@ -1,66 +1,3 @@
import importlib.util
import os
from pathlib import Path
from textwrap import dedent
from setuptools import setup
from setuptools.command.build_py import build_py as _build_py
# Prevents pkg_resources import in entry point script,
# see https://github.com/ninjaaron/fast-entry_points.
# This saves about 200 ms on startup time for non-wheel installs.
try:
import fastentrypoints # noqa: F401, pylint: disable=unused-import
except ImportError:
pass # not able to import when installing through pre-commit
# Read package meta-data from version.py
# see https://packaging.python.org/guides/single-sourcing-package-version/
try:
pkg_dir = os.path.dirname(os.path.abspath(__file__))
version_path = os.path.join(pkg_dir, "dvc", "version.py")
spec = importlib.util.spec_from_file_location("dvc.version", version_path)
dvc_version = importlib.util.module_from_spec(spec)
spec.loader.exec_module(dvc_version)
version = dvc_version.__version__ # noqa: F821
except Exception as exc: # pylint: disable=broad-except
# Dependabot seem to stop working when we don't handle this except block.
# Most likely, it's because of the restrictions in execution of the module.
# We workaround it, and print the error message if it also happens on other
# installations (though this message may likely be suppressed by build
# tools, eg: pip only shows this message on `--verbose` mode).
import sys
print("Could not load version info: ", exc, file=sys.stderr)
version = "UNKNOWN"
# To achieve consistency between the build version and the one provided
# by your package during runtime, you need to **pin** the build version.
#
# This custom class will replace the version.py module with a **static**
# `__version__` that your package can read at runtime, assuring consistency.
#
# References:
# - https://docs.python.org/3.7/distutils/extending.html
# - https://github.com/python/mypy
class build_py(_build_py):
def pin_version(self):
ver_file = Path(self.build_lib) / "dvc" / "version.py"
self.mkpath(os.fspath(ver_file.parent))
ver_file.write_text(
dedent(
f"""\
# AUTOGENERATED at build time by setup.py
__version__ = "{version}"
"""
)
)
def run(self):
self.execute(self.pin_version, ())
_build_py.run(self)
setup(version=version, cmdclass={"build_py": build_py})
setup()
+6 -2
View File
@@ -1,14 +1,18 @@
import re
from dvc.main import main
from tests.unit.test_info import PYTHON_VERSION_REGEX, find_supported_remotes
from tests.unit.test_info import (
DVC_VERSION_REGEX,
PYTHON_VERSION_REGEX,
find_supported_remotes,
)
def test_(tmp_dir, dvc, scm, capsys):
assert main(["version"]) == 0
out, _ = capsys.readouterr()
assert re.search(r"DVC version: \d+\.\d+\.\d+.*", out)
assert re.search(fr"DVC version: {DVC_VERSION_REGEX}", out)
assert re.search(f"Platform: {PYTHON_VERSION_REGEX} on .*", out)
assert find_supported_remotes(out)
assert re.search(r"Cache types: .*", out)
+4 -3
View File
@@ -11,6 +11,7 @@ from dvc.info import get_dvc_info
# `patch` is more than enough for the tests.
# Refer PEP-0440 for complete regex just in-case.
PYTHON_VERSION_REGEX = r"Python \d\.\d+\.\d+\S*"
DVC_VERSION_REGEX = r"\d+\.\d+\.(\d+\.)?.*"
def find_supported_remotes(string):
@@ -50,7 +51,7 @@ def test_info_in_repo(scm_init, tmp_dir):
dvc_info = get_dvc_info()
assert re.search(r"DVC version: \d+\.\d+\.\d+.*", dvc_info)
assert re.search(fr"DVC version: {DVC_VERSION_REGEX}", dvc_info)
assert re.search(f"Platform: {PYTHON_VERSION_REGEX} on .*", dvc_info)
assert find_supported_remotes(dvc_info)
assert re.search(r"Cache types: .*", dvc_info)
@@ -125,7 +126,7 @@ def test_fs_info_in_repo(tmp_dir, dvc, caplog):
def test_info_outside_of_repo(tmp_dir, caplog):
dvc_info = get_dvc_info()
assert re.search(r"DVC version: \d+\.\d+\.\d+.*", dvc_info)
assert re.search(fr"DVC version: {DVC_VERSION_REGEX}", dvc_info)
assert re.search(f"Platform: {PYTHON_VERSION_REGEX} on .*", dvc_info)
assert find_supported_remotes(dvc_info)
assert not re.search(r"Cache types: .*", dvc_info)
@@ -134,7 +135,7 @@ def test_info_outside_of_repo(tmp_dir, caplog):
def test_fs_info_outside_of_repo(tmp_dir, caplog):
dvc_info = get_dvc_info()
assert re.search(r"DVC version: \d+\.\d+\.\d+.*", dvc_info)
assert re.search(fr"DVC version: {DVC_VERSION_REGEX}", dvc_info)
assert re.search(f"Platform: {PYTHON_VERSION_REGEX} on .*", dvc_info)
assert find_supported_remotes(dvc_info)
-20
View File
@@ -1,20 +0,0 @@
import subprocess
import dvc.version
def test_is_release(mocker):
m = mocker.patch.object(dvc.version.subprocess, "check_output")
m.side_effect = subprocess.CalledProcessError(1, "cmd")
ret = dvc.version._is_release(None, dvc.version._BASE_VERSION)
assert ret is False
m.side_effect = None
m.return_value = dvc.version._BASE_VERSION.encode("ascii")
ret = dvc.version._is_release(None, dvc.version._BASE_VERSION)
assert ret
m.return_value = b"630d1741c2d5dd89a3176bd15b63121b905d35c9"
ret = dvc.version._is_release(None, dvc.version._BASE_VERSION)
assert ret is False