summaryrefslogtreecommitdiff
path: root/setup.py
diff options
context:
space:
mode:
authorMatt Martz <matt@sivel.net>2021-10-19 14:24:57 -0500
committerGitHub <noreply@github.com>2021-10-19 14:24:57 -0500
commit66a83314b9d30c6a139de960e6da8d5554c28544 (patch)
tree88b110f697b43addcbe82de8d1a824467130ac50 /setup.py
parent43d09710c81f36113fec8d68e650a6b6d82dca05 (diff)
downloadansible-66a83314b9d30c6a139de960e6da8d5554c28544.tar.gz
Modernize install (#76021)
Co-authored-by: Matt Clay <matt@mystile.com> Co-authored-by: Matt Davis <mrd@redhat.com> Co-authored-by: Sviatoslav Sydorenko <wk.cvs.github@sydorenko.org.ua>
Diffstat (limited to 'setup.py')
-rw-r--r--setup.py403
1 files changed, 20 insertions, 383 deletions
diff --git a/setup.py b/setup.py
index 11f88c6afa..b17ae8db83 100644
--- a/setup.py
+++ b/setup.py
@@ -1,394 +1,31 @@
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
-import json
-import os
-import os.path
-import re
-import sys
-import warnings
+import pathlib
-from collections import defaultdict
+from setuptools import find_packages, setup
-try:
- from setuptools import setup, find_packages
- from setuptools.command.build_py import build_py as BuildPy
- from setuptools.command.install_lib import install_lib as InstallLib
- from setuptools.command.install_scripts import install_scripts as InstallScripts
-except ImportError:
- print("Ansible now needs setuptools in order to build. Install it using"
- " your package manager (usually python-setuptools) or via pip (pip"
- " install setuptools).", file=sys.stderr)
- sys.exit(1)
+here = pathlib.Path(__file__).parent.resolve()
-# `distutils` must be imported after `setuptools` or it will cause explosions
-# with `setuptools >=48.0.0, <49.1`.
-# Refs:
-# * https://github.com/ansible/ansible/issues/70456
-# * https://github.com/pypa/setuptools/issues/2230
-# * https://github.com/pypa/setuptools/commit/bd110264
-from distutils.command.build_scripts import build_scripts as BuildScripts
-from distutils.command.sdist import sdist as SDist
+install_requires = (here / 'requirements.txt').read_text(encoding='utf-8').splitlines()
-
-def find_package_info(*file_paths):
- try:
- with open(os.path.join(*file_paths), 'r') as f:
- info_file = f.read()
- except Exception:
- raise RuntimeError("Unable to find package info.")
-
- # The version line must have the form
- # __version__ = 'ver'
- version_match = re.search(r"^__version__ = ['\"]([^'\"]*)['\"]",
- info_file, re.M)
- author_match = re.search(r"^__author__ = ['\"]([^'\"]*)['\"]",
- info_file, re.M)
-
- if version_match and author_match:
- return version_match.group(1), author_match.group(1)
- raise RuntimeError("Unable to find package info.")
-
-
-def _validate_install_ansible_core():
- """Validate that we can install ansible-core. This checks if
- ansible<=2.9 or ansible-base>=2.10 are installed.
- """
- # Skip common commands we can ignore
- # Do NOT add bdist_wheel here, we don't ship wheels
- # and bdist_wheel is the only place we can prevent pip
- # from installing, as pip creates a wheel, and installs the wheel
- # and we have no influence over installation within a wheel
- if set(('sdist', 'egg_info')).intersection(sys.argv):
- return
-
- if os.getenv('ANSIBLE_SKIP_CONFLICT_CHECK', '') not in ('', '0'):
- return
-
- # Save these for later restoring things to pre invocation
- sys_modules = sys.modules.copy()
- sys_modules_keys = set(sys_modules)
-
- # Make sure `lib` isn't in `sys.path` that could confuse this
- sys_path = sys.path[:]
- abspath = os.path.abspath
- sys.path[:] = [p for p in sys.path if abspath(p) != abspath('lib')]
-
- try:
- from ansible.release import __version__
- except ImportError:
- pass
- else:
- version_tuple = tuple(int(v) for v in __version__.split('.')[:2])
- if version_tuple >= (2, 11):
- return
- elif version_tuple == (2, 10):
- ansible_name = 'ansible-base'
- else:
- ansible_name = 'ansible'
-
- stars = '*' * 76
- raise RuntimeError(
- '''
-
- %s
-
- Cannot install ansible-core with a pre-existing %s==%s
- installation.
-
- Installing ansible-core with ansible-2.9 or older, or ansible-base-2.10
- currently installed with pip is known to cause problems. Please uninstall
- %s and install the new version:
-
- pip uninstall %s
- pip install ansible-core
-
- If you want to skip the conflict checks and manually resolve any issues
- afterwards, set the ANSIBLE_SKIP_CONFLICT_CHECK environment variable:
-
- ANSIBLE_SKIP_CONFLICT_CHECK=1 pip install ansible-core
-
- %s
- ''' % (stars, ansible_name, __version__, ansible_name, ansible_name, stars))
- finally:
- sys.path[:] = sys_path
- for key in sys_modules_keys.symmetric_difference(sys.modules):
- sys.modules.pop(key, None)
- sys.modules.update(sys_modules)
-
-
-_validate_install_ansible_core()
-
-
-SYMLINK_CACHE = 'SYMLINK_CACHE.json'
-
-
-def _find_symlinks(topdir, extension=''):
- """Find symlinks that should be maintained
-
- Maintained symlinks exist in the bin dir or are modules which have
- aliases. Our heuristic is that they are a link in a certain path which
- point to a file in the same directory.
-
- .. warn::
-
- We want the symlinks in :file:`bin/` that link into :file:`lib/ansible/*` (currently,
- :command:`ansible`, :command:`ansible-test`, and :command:`ansible-connection`) to become
- real files on install. Updates to the heuristic here *must not* add them to the symlink
- cache.
- """
- symlinks = defaultdict(list)
- for base_path, dirs, files in os.walk(topdir):
- for filename in files:
- filepath = os.path.join(base_path, filename)
- if os.path.islink(filepath) and filename.endswith(extension):
- target = os.readlink(filepath)
- if target.startswith('/'):
- # We do not support absolute symlinks at all
- continue
-
- if os.path.dirname(target) == '':
- link = filepath[len(topdir):]
- if link.startswith('/'):
- link = link[1:]
- symlinks[os.path.basename(target)].append(link)
- else:
- # Count how many directory levels from the topdir we are
- levels_deep = os.path.dirname(filepath).count('/')
-
- # Count the number of directory levels higher we walk up the tree in target
- target_depth = 0
- for path_component in target.split('/'):
- if path_component == '..':
- target_depth += 1
- # If we walk past the topdir, then don't store
- if target_depth >= levels_deep:
- break
- else:
- target_depth -= 1
- else:
- # If we managed to stay within the tree, store the symlink
- link = filepath[len(topdir):]
- if link.startswith('/'):
- link = link[1:]
- symlinks[target].append(link)
-
- return symlinks
-
-
-def _cache_symlinks(symlink_data):
- with open(SYMLINK_CACHE, 'w') as f:
- json.dump(symlink_data, f)
-
-
-def _maintain_symlinks(symlink_type, base_path):
- """Switch a real file into a symlink"""
- try:
- # Try the cache first because going from git checkout to sdist is the
- # only time we know that we're going to cache correctly
- with open(SYMLINK_CACHE, 'r') as f:
- symlink_data = json.load(f)
- except (IOError, OSError) as e:
- # IOError on py2, OSError on py3. Both have errno
- if e.errno == 2:
- # SYMLINKS_CACHE doesn't exist. Fallback to trying to create the
- # cache now. Will work if we're running directly from a git
- # checkout or from an sdist created earlier.
- library_symlinks = _find_symlinks('lib', '.py')
- library_symlinks.update(_find_symlinks('test/lib'))
-
- symlink_data = {'script': _find_symlinks('bin'),
- 'library': library_symlinks,
- }
-
- # Sanity check that something we know should be a symlink was
- # found. We'll take that to mean that the current directory
- # structure properly reflects symlinks in the git repo
- if 'ansible-playbook' in symlink_data['script']['ansible']:
- _cache_symlinks(symlink_data)
- else:
- raise RuntimeError(
- "Pregenerated symlink list was not present and expected "
- "symlinks in ./bin were missing or broken. "
- "Perhaps this isn't a git checkout?"
- )
- else:
- raise
- symlinks = symlink_data[symlink_type]
-
- for source in symlinks:
- for dest in symlinks[source]:
- dest_path = os.path.join(base_path, dest)
- if not os.path.islink(dest_path):
- try:
- os.unlink(dest_path)
- except OSError as e:
- if e.errno == 2:
- # File does not exist which is all we wanted
- pass
- os.symlink(source, dest_path)
-
-
-class BuildPyCommand(BuildPy):
- def run(self):
- BuildPy.run(self)
- _maintain_symlinks('library', self.build_lib)
-
-
-class BuildScriptsCommand(BuildScripts):
- def run(self):
- BuildScripts.run(self)
- _maintain_symlinks('script', self.build_dir)
-
-
-class InstallLibCommand(InstallLib):
- def run(self):
- InstallLib.run(self)
- _maintain_symlinks('library', self.install_dir)
-
-
-class InstallScriptsCommand(InstallScripts):
- def run(self):
- InstallScripts.run(self)
- _maintain_symlinks('script', self.install_dir)
-
-
-class SDistCommand(SDist):
- def run(self):
- # have to generate the cache of symlinks for release as sdist is the
- # only command that has access to symlinks from the git repo
- library_symlinks = _find_symlinks('lib', '.py')
- library_symlinks.update(_find_symlinks('test/lib'))
-
- symlinks = {'script': _find_symlinks('bin'),
- 'library': library_symlinks,
- }
- _cache_symlinks(symlinks)
-
- SDist.run(self)
-
- # Print warnings at the end because no one will see warnings before all the normal status
- # output
- if os.environ.get('_ANSIBLE_SDIST_FROM_MAKEFILE', False) != '1':
- warnings.warn('When setup.py sdist is run from outside of the Makefile,'
- ' the generated tarball may be incomplete. Use `make snapshot`'
- ' to create a tarball from an arbitrary checkout or use'
- ' `cd packaging/release && make release version=[..]` for official builds.',
- RuntimeWarning)
-
-
-def read_file(file_name):
- """Read file and return its contents."""
- with open(file_name, 'r') as f:
- return f.read()
-
-
-def read_requirements(file_name):
- """Read requirements file as a list."""
- reqs = read_file(file_name).splitlines()
- if not reqs:
- raise RuntimeError(
- "Unable to read requirements from the %s file"
- "That indicates this copy of the source code is incomplete."
- % file_name
- )
- return reqs
-
-
-def get_dynamic_setup_params():
- """Add dynamically calculated setup params to static ones."""
- return {
- # Retrieve the long description from the README
- 'long_description': read_file('README.rst'),
- 'install_requires': read_requirements('requirements.txt'),
- }
-
-
-here = os.path.abspath(os.path.dirname(__file__))
-__version__, __author__ = find_package_info(here, 'lib', 'ansible', 'release.py')
-static_setup_params = dict(
- # Use the distutils SDist so that symlinks are not expanded
- # Use a custom Build for the same reason
- cmdclass={
- 'build_py': BuildPyCommand,
- 'build_scripts': BuildScriptsCommand,
- 'install_lib': InstallLibCommand,
- 'install_scripts': InstallScriptsCommand,
- 'sdist': SDistCommand,
- },
- name='ansible-core',
- version=__version__,
- description='Radically simple IT automation',
- author=__author__,
- author_email='info@ansible.com',
- url='https://ansible.com/',
- project_urls={
- 'Bug Tracker': 'https://github.com/ansible/ansible/issues',
- 'CI: Azure Pipelines': 'https://dev.azure.com/ansible/ansible/',
- 'Code of Conduct': 'https://docs.ansible.com/ansible/latest/community/code_of_conduct.html',
- 'Documentation': 'https://docs.ansible.com/ansible/',
- 'Mailing lists': 'https://docs.ansible.com/ansible/latest/community/communication.html#mailing-list-information',
- 'Source Code': 'https://github.com/ansible/ansible',
- },
- license='GPLv3+',
- # Ansible will also make use of a system copy of python-six and
- # python-selectors2 if installed but use a Bundled copy if it's not.
- python_requires='>=3.8',
+setup(
+ install_requires=install_requires,
package_dir={'': 'lib',
'ansible_test': 'test/lib/ansible_test'},
packages=find_packages('lib') + find_packages('test/lib'),
- include_package_data=True,
- classifiers=[
- 'Development Status :: 5 - Production/Stable',
- 'Environment :: Console',
- 'Intended Audience :: Developers',
- 'Intended Audience :: Information Technology',
- 'Intended Audience :: System Administrators',
- 'License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)',
- 'Natural Language :: English',
- 'Operating System :: POSIX',
- 'Programming Language :: Python :: 3',
- 'Programming Language :: Python :: 3.8',
- 'Programming Language :: Python :: 3.9',
- 'Programming Language :: Python :: 3.10',
- 'Topic :: System :: Installation/Setup',
- 'Topic :: System :: Systems Administration',
- 'Topic :: Utilities',
- ],
- scripts=[
- 'bin/ansible',
- 'bin/ansible-playbook',
- 'bin/ansible-pull',
- 'bin/ansible-doc',
- 'bin/ansible-galaxy',
- 'bin/ansible-console',
- 'bin/ansible-connection',
- 'bin/ansible-vault',
- 'bin/ansible-config',
- 'bin/ansible-inventory',
- 'bin/ansible-test',
- ],
- data_files=[],
- # Installing as zip files would break due to references to __file__
- zip_safe=False
+ entry_points={
+ 'console_scripts': [
+ 'ansible=ansible.cli.adhoc:main',
+ 'ansible-config=ansible.cli.config:main',
+ 'ansible-console=ansible.cli.console:main',
+ 'ansible-doc=ansible.cli.doc:main',
+ 'ansible-galaxy=ansible.cli.galaxy:main',
+ 'ansible-inventory=ansible.cli.inventory:main',
+ 'ansible-playbook=ansible.cli.playbook:main',
+ 'ansible-pull=ansible.cli.pull:main',
+ 'ansible-vault=ansible.cli.vault:main',
+ 'ansible-connection=ansible.cli.scripts.ansible_connection_cli_stub:main',
+ ],
+ },
)
-
-
-def main():
- """Invoke installation process using setuptools."""
- setup_params = dict(static_setup_params, **get_dynamic_setup_params())
- ignore_warning_regex = (
- r"Unknown distribution option: '(project_urls|python_requires)'"
- )
- warnings.filterwarnings(
- 'ignore',
- message=ignore_warning_regex,
- category=UserWarning,
- module='distutils.dist',
- )
- setup(**setup_params)
- warnings.resetwarnings()
-
-
-if __name__ == '__main__':
- main()