ccbfdec334
* excludes scenario guides from core docs, splits porting guides and roadmaps, symlinks indices to create index.html pages, and adds .gitignore entries for conf.py and the toplevel index.rst files generated by the docs build This solution builds three types of docs: * ansible-2.10 and earlier: all the docs. Handle this via `make webdocs ANSIBLE_VERSION=2.10` * ansible-3 and later: a subset of the docs for the ansible package. Handle this via `make webdocs ANSIBLE_VERSION=3` (change the ANSIBLE_VERSION to match the version being built for. * ansible-core: a subset of the docs for the ansible-core package. Handle this via `make coredocs`. * `make webdocs` now always builds all the collection docs * Use `make coredocs` to limit it to core plugins only * The user specifies the desired version. If no ANSIBLE_VERSION is specified, build plugins for the latest release of ansible Co-authored-by: Toshio Kuratomi <a.badger@gmail.com> Co-authored-by: Matt Clay <matt@mystile.com>
379 lines
13 KiB
Python
Executable file
379 lines
13 KiB
Python
Executable file
#!/usr/bin/env python
|
|
from __future__ import (absolute_import, division, print_function)
|
|
__metaclass__ = type
|
|
|
|
import contextlib
|
|
import fnmatch
|
|
import glob
|
|
import os
|
|
import re
|
|
import shutil
|
|
import subprocess
|
|
import sys
|
|
import tarfile
|
|
import tempfile
|
|
|
|
|
|
def assemble_files_to_ship(complete_file_list):
|
|
"""
|
|
This looks for all files which should be shipped in the sdist
|
|
"""
|
|
# All files which are in the repository except these:
|
|
ignore_patterns = (
|
|
# Developer-only tools
|
|
'.azure-pipelines/*',
|
|
'.github/*',
|
|
'.github/*/*',
|
|
'changelogs/fragments/*',
|
|
'hacking/backport/*',
|
|
'hacking/shippable/*',
|
|
'hacking/tests/*',
|
|
'hacking/ticket_stubs/*',
|
|
'test/sanity/code-smell/botmeta.*',
|
|
'test/utils/*',
|
|
'test/utils/*/*',
|
|
'test/utils/*/*/*',
|
|
'.git*',
|
|
)
|
|
ignore_files = frozenset((
|
|
# Developer-only tools
|
|
'changelogs/config.yaml',
|
|
'hacking/README.md',
|
|
'hacking/ansible-profile',
|
|
'hacking/cgroup_perf_recap_graph.py',
|
|
'hacking/create_deprecated_issues.py',
|
|
'hacking/deprecated_issue_template.md',
|
|
'hacking/fix_test_syntax.py',
|
|
'hacking/get_library.py',
|
|
'hacking/metadata-tool.py',
|
|
'hacking/report.py',
|
|
'hacking/return_skeleton_generator.py',
|
|
'hacking/test-module',
|
|
'hacking/test-module.py',
|
|
'test/support/README.md',
|
|
'.cherry_picker.toml',
|
|
'.mailmap',
|
|
# Generated as part of a build step
|
|
'docs/docsite/rst/conf.py',
|
|
'docs/docsite/rst/index.rst',
|
|
# Possibly should be included
|
|
'examples/scripts/uptime.py',
|
|
'examples/scripts/my_test.py',
|
|
'examples/scripts/my_test_info.py',
|
|
'examples/scripts/my_test_facts.py',
|
|
'examples/DOCUMENTATION.yml',
|
|
'examples/play.yml',
|
|
'examples/hosts.yaml',
|
|
'examples/hosts.yml',
|
|
'examples/inventory_script_schema.json',
|
|
'examples/plugin_filters.yml',
|
|
'hacking/env-setup',
|
|
'hacking/env-setup.fish',
|
|
'MANIFEST',
|
|
))
|
|
|
|
# These files are generated and then intentionally added to the sdist
|
|
|
|
# Manpages
|
|
manpages = ['docs/man/man1/ansible.1']
|
|
for dirname, dummy, files in os.walk('bin'):
|
|
for filename in files:
|
|
path = os.path.join(dirname, filename)
|
|
if os.path.islink(path):
|
|
if os.readlink(path) == 'ansible':
|
|
manpages.append('docs/man/man1/%s.1' % filename)
|
|
|
|
# Misc
|
|
misc_generated_files = [
|
|
'SYMLINK_CACHE.json',
|
|
'PKG-INFO',
|
|
]
|
|
|
|
shipped_files = manpages + misc_generated_files
|
|
|
|
for path in complete_file_list:
|
|
if path not in ignore_files:
|
|
for ignore in ignore_patterns:
|
|
if fnmatch.fnmatch(path, ignore):
|
|
break
|
|
else:
|
|
shipped_files.append(path)
|
|
|
|
return shipped_files
|
|
|
|
|
|
def assemble_files_to_install(complete_file_list):
|
|
"""
|
|
This looks for all of the files which should show up in an installation of ansible
|
|
"""
|
|
ignore_patterns = tuple()
|
|
|
|
pkg_data_files = []
|
|
for path in complete_file_list:
|
|
|
|
if path.startswith("lib/ansible"):
|
|
prefix = 'lib'
|
|
elif path.startswith("test/lib/ansible_test"):
|
|
prefix = 'test/lib'
|
|
else:
|
|
continue
|
|
|
|
for ignore in ignore_patterns:
|
|
if fnmatch.fnmatch(path, ignore):
|
|
break
|
|
else:
|
|
pkg_data_files.append(os.path.relpath(path, prefix))
|
|
|
|
return pkg_data_files
|
|
|
|
|
|
@contextlib.contextmanager
|
|
def clean_repository(file_list):
|
|
"""Copy the repository to clean it of artifacts"""
|
|
# Create a tempdir that will be the clean repo
|
|
with tempfile.TemporaryDirectory() as repo_root:
|
|
directories = set((repo_root + os.path.sep,))
|
|
|
|
for filename in file_list:
|
|
# Determine if we need to create the directory
|
|
directory = os.path.dirname(filename)
|
|
dest_dir = os.path.join(repo_root, directory)
|
|
if dest_dir not in directories:
|
|
os.makedirs(dest_dir)
|
|
|
|
# Keep track of all the directories that now exist
|
|
path_components = directory.split(os.path.sep)
|
|
path = repo_root
|
|
for component in path_components:
|
|
path = os.path.join(path, component)
|
|
if path not in directories:
|
|
directories.add(path)
|
|
|
|
# Copy the file
|
|
shutil.copy2(filename, dest_dir, follow_symlinks=False)
|
|
|
|
yield repo_root
|
|
|
|
|
|
def create_sdist(tmp_dir):
|
|
"""Create an sdist in the repository"""
|
|
create = subprocess.Popen(
|
|
['make', 'snapshot', 'SDIST_DIR=%s' % tmp_dir],
|
|
stdout=subprocess.PIPE,
|
|
stderr=subprocess.PIPE,
|
|
universal_newlines=True,
|
|
)
|
|
|
|
stderr = create.communicate()[1]
|
|
|
|
if create.returncode != 0:
|
|
raise Exception('make snapshot failed:\n%s' % stderr)
|
|
|
|
# Determine path to sdist
|
|
tmp_dir_files = os.listdir(tmp_dir)
|
|
|
|
if not tmp_dir_files:
|
|
raise Exception('sdist was not created in the temp dir')
|
|
elif len(tmp_dir_files) > 1:
|
|
raise Exception('Unexpected extra files in the temp dir')
|
|
|
|
return os.path.join(tmp_dir, tmp_dir_files[0])
|
|
|
|
|
|
def extract_sdist(sdist_path, tmp_dir):
|
|
"""Untar the sdist"""
|
|
# Untar the sdist from the tmp_dir
|
|
with tarfile.open(os.path.join(tmp_dir, sdist_path), 'r|*') as sdist:
|
|
sdist.extractall(path=tmp_dir)
|
|
|
|
# Determine the sdist directory name
|
|
sdist_filename = os.path.basename(sdist_path)
|
|
tmp_dir_files = os.listdir(tmp_dir)
|
|
try:
|
|
tmp_dir_files.remove(sdist_filename)
|
|
except ValueError:
|
|
# Unexpected could not find original sdist in temp dir
|
|
raise
|
|
|
|
if len(tmp_dir_files) > 1:
|
|
raise Exception('Unexpected extra files in the temp dir')
|
|
elif len(tmp_dir_files) < 1:
|
|
raise Exception('sdist extraction did not occur i nthe temp dir')
|
|
|
|
return os.path.join(tmp_dir, tmp_dir_files[0])
|
|
|
|
|
|
def install_sdist(tmp_dir, sdist_dir):
|
|
"""Install the extracted sdist into the temporary directory"""
|
|
install = subprocess.Popen(
|
|
['python', 'setup.py', 'install', '--root=%s' % tmp_dir],
|
|
stdout=subprocess.PIPE,
|
|
stderr=subprocess.PIPE,
|
|
universal_newlines=True,
|
|
cwd=os.path.join(tmp_dir, sdist_dir),
|
|
)
|
|
|
|
stdout, stderr = install.communicate()
|
|
|
|
if install.returncode != 0:
|
|
raise Exception('sdist install failed:\n%s' % stderr)
|
|
|
|
# Determine the prefix for the installed files
|
|
match = re.search('^creating (%s/.*?/(?:site|dist)-packages)/ansible$' %
|
|
tmp_dir, stdout, flags=re.M)
|
|
return match.group(1)
|
|
|
|
|
|
def check_sdist_contains_expected(sdist_dir, to_ship_files):
|
|
"""Check that the files we expect to ship are present in the sdist"""
|
|
results = []
|
|
for filename in to_ship_files:
|
|
path = os.path.join(sdist_dir, filename)
|
|
if not os.path.exists(path):
|
|
results.append('%s: File was not added to sdist' % filename)
|
|
|
|
# Also changelog
|
|
changelog_files = glob.glob(os.path.join(sdist_dir, 'changelogs/CHANGELOG-v2.[0-9]*.rst'))
|
|
if not changelog_files:
|
|
results.append('changelogs/CHANGELOG-v2.*.rst: Changelog file was not added to the sdist')
|
|
elif len(changelog_files) > 1:
|
|
results.append('changelogs/CHANGELOG-v2.*.rst: Too many changelog files: %s'
|
|
% changelog_files)
|
|
|
|
return results
|
|
|
|
|
|
def check_sdist_files_are_wanted(sdist_dir, to_ship_files):
|
|
"""Check that all files in the sdist are desired"""
|
|
results = []
|
|
for dirname, dummy, files in os.walk(sdist_dir):
|
|
dirname = os.path.relpath(dirname, start=sdist_dir)
|
|
if dirname == '.':
|
|
dirname = ''
|
|
|
|
for filename in files:
|
|
path = os.path.join(dirname, filename)
|
|
if path not in to_ship_files:
|
|
if fnmatch.fnmatch(path, 'changelogs/CHANGELOG-v2.[0-9]*.rst'):
|
|
# changelog files are expected
|
|
continue
|
|
|
|
# FIXME: ansible-test doesn't pass the paths of symlinks to us so we aren't
|
|
# checking those
|
|
if not os.path.islink(os.path.join(sdist_dir, path)):
|
|
results.append('%s: File in sdist was not in the repository' % path)
|
|
|
|
return results
|
|
|
|
|
|
def check_installed_contains_expected(install_dir, to_install_files):
|
|
"""Check that all the files we expect to be installed are"""
|
|
results = []
|
|
for filename in to_install_files:
|
|
path = os.path.join(install_dir, filename)
|
|
if not os.path.exists(path):
|
|
results.append('%s: File not installed' % os.path.join('lib', filename))
|
|
|
|
return results
|
|
|
|
|
|
EGG_RE = re.compile('ansible[^/]+\\.egg-info/(PKG-INFO|SOURCES.txt|'
|
|
'dependency_links.txt|not-zip-safe|requires.txt|top_level.txt)$')
|
|
|
|
|
|
def check_installed_files_are_wanted(install_dir, to_install_files):
|
|
"""Check that all installed files were desired"""
|
|
results = []
|
|
|
|
for dirname, dummy, files in os.walk(install_dir):
|
|
dirname = os.path.relpath(dirname, start=install_dir)
|
|
if dirname == '.':
|
|
dirname = ''
|
|
|
|
for filename in files:
|
|
# If this is a byte code cache, look for the python file's name
|
|
directory = dirname
|
|
if filename.endswith('.pyc') or filename.endswith('.pyo'):
|
|
# Remove the trailing "o" or c"
|
|
filename = filename[:-1]
|
|
|
|
if directory.endswith('%s__pycache__' % os.path.sep):
|
|
# Python3 byte code cache, look for the basename of
|
|
# __pycache__/__init__.cpython-36.py
|
|
segments = filename.rsplit('.', 2)
|
|
if len(segments) >= 3:
|
|
filename = '.'.join((segments[0], segments[2]))
|
|
directory = os.path.dirname(directory)
|
|
|
|
path = os.path.join(directory, filename)
|
|
|
|
# Test that the file was listed for installation
|
|
if path not in to_install_files:
|
|
# FIXME: ansible-test doesn't pass the paths of symlinks to us so we
|
|
# aren't checking those
|
|
if not os.path.islink(os.path.join(install_dir, path)):
|
|
if not EGG_RE.match(path):
|
|
results.append('%s: File was installed but was not supposed to be' % path)
|
|
|
|
return results
|
|
|
|
|
|
def _find_symlinks():
|
|
symlink_list = []
|
|
for dirname, directories, filenames in os.walk('.'):
|
|
for filename in filenames:
|
|
path = os.path.join(dirname, filename)
|
|
# Strip off "./" from the front
|
|
path = path[2:]
|
|
if os.path.islink(path):
|
|
symlink_list.append(path)
|
|
|
|
return symlink_list
|
|
|
|
|
|
def main():
|
|
"""All of the files in the repository"""
|
|
complete_file_list = []
|
|
for path in sys.argv[1:] or sys.stdin.read().splitlines():
|
|
complete_file_list.append(path)
|
|
|
|
# ansible-test isn't currently passing symlinks to us so construct those ourselves for now
|
|
for filename in _find_symlinks():
|
|
if filename not in complete_file_list:
|
|
# For some reason ansible-test is passing us lib/ansible/module_utils/ansible_release.py
|
|
# which is a symlink even though it doesn't pass any others
|
|
complete_file_list.append(filename)
|
|
|
|
# We may run this after docs sanity tests so get a clean repository to run in
|
|
with clean_repository(complete_file_list) as clean_repo_dir:
|
|
os.chdir(clean_repo_dir)
|
|
|
|
to_ship_files = assemble_files_to_ship(complete_file_list)
|
|
to_install_files = assemble_files_to_install(complete_file_list)
|
|
|
|
results = []
|
|
with tempfile.TemporaryDirectory() as tmp_dir:
|
|
sdist_path = create_sdist(tmp_dir)
|
|
sdist_dir = extract_sdist(sdist_path, tmp_dir)
|
|
|
|
# Check that the files that are supposed to be in the sdist are there
|
|
results.extend(check_sdist_contains_expected(sdist_dir, to_ship_files))
|
|
|
|
# Check that the files that are in the sdist are in the repository
|
|
results.extend(check_sdist_files_are_wanted(sdist_dir, to_ship_files))
|
|
|
|
# install the sdist
|
|
install_dir = install_sdist(tmp_dir, sdist_dir)
|
|
|
|
# Check that the files that are supposed to be installed are there
|
|
results.extend(check_installed_contains_expected(install_dir, to_install_files))
|
|
|
|
# Check that the files that are installed are supposed to be installed
|
|
results.extend(check_installed_files_are_wanted(install_dir, to_install_files))
|
|
|
|
for message in results:
|
|
print(message)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|