@ -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 ( )