From a7641df1f7f6183741da6ba22f2d582d7eec7322 Mon Sep 17 00:00:00 2001 From: John Sirois Date: Tue, 13 Feb 2024 19:53:54 -0800 Subject: [PATCH] Re-work Pex documentation. This change moves Pex off RTD and on to GitHub Pages. Instead of versioned docs out on RTD, only the latest docs will be available via https://docs.pex-tool.org and versioned docs will be available via GitHub Releases (for the PDF) and inside the Pex wheel and Pex PEX themselves via the new `pex3 docs` command that serves the appropriate docs locally, offline, with full functionality. With this switch, docs are also now built and linkchecked in CI. There should be no more RTD-style misconfiguration production outages. --- .github/workflows/ci.yml | 20 +- .github/workflows/doc-site.yml | 42 +++ .github/workflows/release.yml | 12 +- .gitignore | 2 +- CHANGES.md | 23 +- CONTRIBUTING.md | 18 +- README.rst | 2 +- assets/download.svg | 4 + assets/github.svg | 4 + assets/pdf.svg | 4 + assets/pex-full-dark.png | Bin 0 -> 34315 bytes assets/pex-full-light.png | Bin 0 -> 35372 bytes assets/pex-full.svg | 73 ++++ assets/pex-icon-512.png | Bin 0 -> 25424 bytes assets/pex-icon-512x512.png | Bin 0 -> 25312 bytes assets/pex.ico | Bin 0 -> 47040 bytes assets/python.svg | 4 + build-backend/pex_build/__init__.py | 9 + build-backend/pex_build/hatchling/build.py | 24 +- .../pex_build/hatchling/build_hook.py | 38 ++ .../hatchling/dynamic_requires_python.py | 27 -- build-backend/pex_build/hatchling/hooks.py | 19 +- .../pex_build/hatchling/metadata_hook.py | 61 ++++ docs-requirements.txt | 5 + docs/_ext/sphinx_pex/__init__.py | 71 ++++ docs/_ext/{ => sphinx_pex}/vars.py | 22 +- docs/_static/pex-cover.png | 1 + docs/_static/pex-full-dark.png | 1 + docs/_static/pex-full-light.png | 1 + docs/_static/pex.ico | 1 + docs/_templates/search.html | 54 +++ docs/api/vars.md | 5 + docs/api/vars.rst | 4 - docs/buildingpex.rst | 4 +- docs/conf.py | 341 +++++------------- pex/cli/commands/__init__.py | 3 +- pex/cli/commands/docs.py | 220 +++++++++++ pex/docs/__init__.py | 18 + pex/platforms.py | 2 +- pex/variables.py | 4 +- pex/venv/installer_options.py | 2 +- pex/version.py | 2 +- pyproject.toml | 13 +- scripts/build_docs.py | 179 +++++++++ scripts/format.py | 15 +- scripts/lint.py | 1 + scripts/package.py | 66 +++- scripts/typecheck.py | 5 + tox.ini | 13 +- 49 files changed, 1082 insertions(+), 357 deletions(-) create mode 100644 .github/workflows/doc-site.yml create mode 100644 assets/download.svg create mode 100644 assets/github.svg create mode 100644 assets/pdf.svg create mode 100644 assets/pex-full-dark.png create mode 100644 assets/pex-full-light.png create mode 100644 assets/pex-full.svg create mode 100644 assets/pex-icon-512.png create mode 100644 assets/pex-icon-512x512.png create mode 100644 assets/pex.ico create mode 100644 assets/python.svg create mode 100644 build-backend/pex_build/hatchling/build_hook.py delete mode 100644 build-backend/pex_build/hatchling/dynamic_requires_python.py create mode 100644 build-backend/pex_build/hatchling/metadata_hook.py create mode 100644 docs-requirements.txt create mode 100644 docs/_ext/sphinx_pex/__init__.py rename docs/_ext/{ => sphinx_pex}/vars.py (78%) create mode 120000 docs/_static/pex-cover.png create mode 120000 docs/_static/pex-full-dark.png create mode 120000 docs/_static/pex-full-light.png create mode 120000 docs/_static/pex.ico create mode 100644 docs/_templates/search.html create mode 100644 docs/api/vars.md delete mode 100644 docs/api/vars.rst create mode 100644 pex/cli/commands/docs.py create mode 100644 pex/docs/__init__.py create mode 100644 scripts/build_docs.py diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0e98ae308..9d28ed8d2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -24,7 +24,7 @@ jobs: - name: Noop run: "true" checks: - name: tox -e format-check,lint-check,typecheck,vendor-check,package + name: tox -e format-check,lint-check,typecheck,vendor-check,package,docs needs: org-check runs-on: ubuntu-22.04 steps: @@ -39,10 +39,23 @@ jobs: with: # We need to keep Python 3.8 for consistent vendoring with tox. python-version: "3.8" - - name: Check Formatting, Lints, Types, Vendoring and Packaging + - name: Check Formatting, Lints and Types uses: pantsbuild/actions/run-tox@b16b9cf47cd566acfe217b1dafc5b452e27e6fd7 with: - tox-env: format-check,lint-check,typecheck,vendor-check,package -- --additional-format sdist --additional-format wheel + tox-env: format-check,lint-check,typecheck + - name: Check Vendoring + uses: pantsbuild/actions/run-tox@b16b9cf47cd566acfe217b1dafc5b452e27e6fd7 + with: + tox-env: vendor-check + - name: Check Packaging + uses: pantsbuild/actions/run-tox@b16b9cf47cd566acfe217b1dafc5b452e27e6fd7 + with: + tox-env: package -- --additional-format sdist --additional-format wheel --embed-docs + - name: Check Docs + uses: pantsbuild/actions/run-tox@b16b9cf47cd566acfe217b1dafc5b452e27e6fd7 + with: + tox-env: docs -- --linkcheck --pdf --clean-html + # N.B.: The name of this job key (linux-tests) is depended on by scrips/build_cache_image.py. In # particular, the tox-env matrix list is used to ensure the cache covers all Linux CI jobs. linux-tests: @@ -189,6 +202,7 @@ jobs: name: Gather Final Status needs: - checks + - docs - linux-tests - mac-tests runs-on: ubuntu-22.04 diff --git a/.github/workflows/doc-site.yml b/.github/workflows/doc-site.yml new file mode 100644 index 000000000..50bca6813 --- /dev/null +++ b/.github/workflows/doc-site.yml @@ -0,0 +1,42 @@ +name: Deploy Doc Site +on: + push: + tags: + - v[0-9]+.[0-9]+.[0-9]+ + workflow_dispatch: + +permissions: + contents: read + pages: write + id-token: write + +concurrency: + group: "pages" + cancel-in-progress: false + +jobs: + deploy: + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + runs-on: ubuntu-22.04 + steps: + - name: Checkout Pex + uses: actions/checkout@v3 + - name: Setup Python 3.11 + uses: actions/setup-python@v4 + with: + python-version: "3.11" + - name: Build Doc Site + uses: pantsbuild/actions/run-tox@b16b9cf47cd566acfe217b1dafc5b452e27e6fd7 + with: + tox-env: docs -- --linkcheck --pdf --clean-html + - name: Setup Pages + uses: actions/configure-pages@v3 + - name: Upload artifact + uses: actions/upload-pages-artifact@v1 + with: + path: "dist/docs/html/" + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v2 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 302aee84a..e70eb2e94 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -58,7 +58,7 @@ jobs: - name: Build sdist and wheel uses: pantsbuild/actions/run-tox@b16b9cf47cd566acfe217b1dafc5b452e27e6fd7 with: - tox-env: package -- --no-pex --additional-format sdist --additional-format wheel + tox-env: package -- --no-pex --additional-format sdist --additional-format wheel --embed-docs - name: Publish Pex ${{ needs.determine-tag.outputs.release-tag }} uses: pypa/gh-action-pypi-publish@release/v1 with: @@ -83,7 +83,11 @@ jobs: - name: Package Pex ${{ needs.determine-tag.outputs.release-tag }} PEX uses: pantsbuild/actions/run-tox@b16b9cf47cd566acfe217b1dafc5b452e27e6fd7 with: - tox-env: package + tox-env: package -- --embed-docs + - name: Generate Pex ${{ needs.determine-tag.outputs.release-tag }} PDF + uses: pantsbuild/actions/run-tox@b16b9cf47cd566acfe217b1dafc5b452e27e6fd7 + with: + tox-env: docs -- --no-html --pdf - name: Prepare Changelog id: prepare-changelog uses: a-scie/actions/changelog@v1.5 @@ -100,6 +104,8 @@ jobs: body_path: ${{ steps.prepare-changelog.outputs.changelog-file }} draft: false prerelease: false - files: dist/pex + files: | + dist/pex + dist/docs/pdf/pex.pdf fail_on_unmatched_files: true discussion_category_name: Announcements diff --git a/.gitignore b/.gitignore index 96f732fee..c7a905086 100644 --- a/.gitignore +++ b/.gitignore @@ -5,5 +5,5 @@ /.mypy_cache/ /.tox/ /dist/ -/docs/_build +/docs/_static_dynamic/ diff --git a/CHANGES.md b/CHANGES.md index 827ba5da9..f04414489 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,18 @@ # Release Notes +## 2.1.164 + +This release moves Pex documentation from https://pex.readthedocs.io to +https://docs.pex-tool.org. While legacy versioned docs will remain +available at RTD in perpetuity, going forward only the latest Pex +release docs will be available online at the https://docs.pex-tool.org +site. If you want to see the Pex docs for the version you are currently +using, Pex now supports the `pex3 docs` command which will serve the +docs for your Pex version locally, offline, but with full functionality, +including search. + +* Re-work Pex documentation. (#2362) + ## 2.1.163 This release fixes Pex to work in certain OS / SSL environments where it @@ -567,7 +580,7 @@ In addition, you can now "inject" runtime environment variables and arguments into PEX files such that, when run, the PEX runtime ensures those environment variables and command line arguments are passed to the PEXed application. See [PEX Recipes]( -https://pex.readthedocs.io/en/latest/recipes.html#uvicorn-and-other-customizable-application-servers +https://docs.pex-tool.org/recipes.html#uvicorn-and-other-customizable-application-servers ) for more information. @@ -1021,7 +1034,7 @@ PEX venvs (More on additional data files as well as a new venv install `--scope` that can be used to create fully optimized container images with PEXed applications (See how to use this feature -[here](https://pex.readthedocs.io/en/latest/recipes.html#pex-app-in-a-container)). +[here](https://docs.pex-tool.org/recipes.html#pex-app-in-a-container)). * Support splitting venv creation into deps & srcs. (#1634) * Fix handling of data files when creating venvs. (#1632) @@ -1052,7 +1065,7 @@ pre-built wheels are available for that foreign platform. Additionally, PEXes now know how to set a usable process name when the PEX contains the `setproctitle` distribution. See -[here](https://pex.readthedocs.io/en/v2.1.66/recipes.html#long-running-pex-applications-and-daemons) +[here](https://docs.pex-tool.org/recipes.html#long-running-pex-applications-and-daemons) for more information. * Add support for `--complete-platform`. (#1609) @@ -1148,7 +1161,7 @@ creating optimized container images from PEX files. ## 2.1.55 This release brings official support for Python 3.10 as well as fixing - doc generation and fixing help for + doc generation and fixing help for `pex-tools` / `PEX_TOOLS=1 ./my.pex` pex tools invocations that have too few arguments. @@ -1547,7 +1560,7 @@ This release improves interpreter discovery to prefer more recent patch versions, e.g. preferring Python 3.6.10 over 3.6.8. We recently regained access to the docsite, and - is now up-to-date. + is now up-to-date. * Prefer more recent patch versions in interpreter discovery. (#1088) * Fix `--pex-python` when it's the same as the current interpreter. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 0ad6da2d6..f1eb6bdc5 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -34,7 +34,12 @@ lead to a better end result. Before sending off changes you should run `tox -e fmt,lint,check`. This formats, lints and type checks the code. -In addition you should run tests, which are divided into integration tests (those under +If you've made `docs/` changes, you should run `tox -e docs -- --linkcheck --pdf --serve` which +will build the full doc site including its downloadable PDF version as well as the search index. +You can browse to the URL printed out at the end of the output to view the site on your local +machine. + +In addition, you should run tests, which are divided into integration tests (those under `tests/integration/`) and unit tests (those under `tests/` but not under `tests/integration/`). Unit tests have a tox environment name that matches the desired interpreter to test against. So, to run unit tests against CPython 3.11 (which you must have installed), use `tox -e py311`. For @@ -56,3 +61,14 @@ without having to actually install PyPy 2.7 on your machine. When you're ready to get additional eyes on your changes, submit a [pull request]( https://github.com/pex-tool/pex/pulls). +If you've made documentation changes you can render the site in the fork you used for the pull +request by navigating to the "Deploy Doc Site" action in your fork and running the workflow +manually. You do this using the "Run workflow" widget in the top row of the workflow run list, +selecting your PR branch and clicking "Run workflow". This will fail your first time doing this due +to a branch protection rule the "Deploy Doc Site" action automatically establishes to restrict doc +site deployments to the main branch. To fix this, navigate to "Environments" in your fork settings +and edit the "github-pages" branch protection rule, changing "Deployment Branches" from +"Selected branches" to "All branches" and then save the protection rules. You can now re-run the +workflow and should be able to browse to https://.github.io/pex to browse the +deployed site with your changes incorporated. N.B.: The site will be destroyed when you delete your +PR branch. diff --git a/README.rst b/README.rst index 836a21f78..95da09d62 100644 --- a/README.rst +++ b/README.rst @@ -138,7 +138,7 @@ Documentation ============= More documentation about Pex, building .pex files, and how .pex files work -is available at https://pex.readthedocs.io. +is available at https://docs.pex-tool.org. Development diff --git a/assets/download.svg b/assets/download.svg new file mode 100644 index 000000000..f06ce1fc4 --- /dev/null +++ b/assets/download.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/github.svg b/assets/github.svg new file mode 100644 index 000000000..5e7b2e0a9 --- /dev/null +++ b/assets/github.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/pdf.svg b/assets/pdf.svg new file mode 100644 index 000000000..54a4d78ad --- /dev/null +++ b/assets/pdf.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/pex-full-dark.png b/assets/pex-full-dark.png new file mode 100644 index 0000000000000000000000000000000000000000..d3a4bd0dd0539b80d41ed4c1fef804d51957e527 GIT binary patch literal 34315 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 literal 0 HcmV?d00001 diff --git a/assets/pex-full-light.png b/assets/pex-full-light.png new file mode 100644 index 0000000000000000000000000000000000000000..539c2fe82ac0853d736a361d2592d647bf6ecfa1 GIT binary patch literal 35372 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

X66`` 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 + + + + + + + ex + + + + diff --git a/assets/pex-icon-512.png b/assets/pex-icon-512.png new file mode 100644 index 0000000000000000000000000000000000000000..98a164557d54caa4240bd4faf63f6c127219f6fb GIT binary patch literal 25424 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%0AS{vDmO7pP&wi-M;Tve}tc}pIN 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

{{ _("Search") }}

+ +{% endblock %} + +{% block scripts -%} +{{ super() }} + + +{%- endblock scripts %} + +{% block extra_styles -%} +{{ super() }} + +{%- endblock extra_styles %} + diff --git a/docs/api/vars.md b/docs/api/vars.md new file mode 100644 index 000000000..f3092cb0e --- /dev/null +++ b/docs/api/vars.md @@ -0,0 +1,5 @@ +(vars)= +# PEX runtime environment variables + +```{vars} +``` diff --git a/docs/api/vars.rst b/docs/api/vars.rst deleted file mode 100644 index c4d5e5f50..000000000 --- a/docs/api/vars.rst +++ /dev/null @@ -1,4 +0,0 @@ -PEX runtime environment variables -================================= - -.. vars:: diff --git a/docs/buildingpex.rst b/docs/buildingpex.rst index 10682462e..40a11103e 100644 --- a/docs/buildingpex.rst +++ b/docs/buildingpex.rst @@ -435,9 +435,7 @@ Tailoring PEX execution at runtime ---------------------------------- Tailoring of PEX execution can be done at runtime by setting various environment variables. -The source of truth for these environment variables can be found in the -`pex.variables API `_. - +See :ref:`vars`. Using ``bdist_pex`` =================== diff --git a/docs/conf.py b/docs/conf.py index 779bff35b..c6462cf92 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -1,279 +1,110 @@ -# -*- coding: utf-8 -*- -# -# pex documentation build configuration file, created by -# sphinx-quickstart on Fri Jul 25 15:16:37 2014. -# -# This file is execfile()d with the current directory set to its -# containing dir. -# -# Note that not all possible configuration values are present in this -# autogenerated file. +# Copyright 2024 Pex project contributors. +# Licensed under the Apache License, Version 2.0 (see LICENSE). + +# Configuration file for the Sphinx documentation builder. # -# All configuration values have a default; values that are commented out -# serve to show the default. +# For the full list of built-in configuration values, see the documentation: +# https://www.sphinx-doc.org/en/master/usage/configuration.html -import os import sys from datetime import datetime +from pathlib import PurePath + +sys.path.insert(0, str(PurePath(__file__).parent.parent)) -sys.path.insert(0, os.path.abspath("..")) -sys.path.append(os.path.abspath("./_ext")) +# -- Project information ----------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information # Note: must come after the sys.path manipulation above. from pex.version import __version__ as PEX_VERSION # isort:skip +project = "pex" +version = ".".join(PEX_VERSION.split(".")[:2]) +release = PEX_VERSION +copyright = f"{datetime.now().year}, Pex project contributors" +author = "Pex project contributors" -# -- General configuration ------------------------------------------------ - -# If your documentation needs a minimal Sphinx version, state it here. -# needs_sphinx = '1.0' +# -- General configuration --------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration -# Add any Sphinx extension module names here, as strings. They can be -# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom -# ones. +# Note: the vars extension is housed in _ext. +sys.path.insert(0, str(PurePath(__file__).parent / "_ext")) extensions = [ - "sphinx.ext.autodoc", - "sphinx.ext.viewcode", - "vars", + "myst_parser", + "sphinx_pex", ] -# Add any paths that contain templates here, relative to this directory. -templates_path = ["_templates"] - -# The suffix of source filenames. -source_suffix = ".rst" - -# The encoding of source files. -# source_encoding = 'utf-8-sig' - -# The master toctree document. -master_doc = "index" - -# General information about the project. -project = u"pex" -copyright = u"%s, Pants project contributors" % datetime.now().year - -# The version info for the project you're documenting, acts as replacement for -# |version| and |release|, also used in various other places throughout the -# built documents. -# -# The short X.Y version. -version = ".".join(PEX_VERSION.split(".")[0:2]) - -# The full version, including alpha/beta/rc tags. -release = PEX_VERSION - -# The language for content autogenerated by Sphinx. Refer to documentation -# for a list of supported languages. -# language = None - -# There are two options for replacing |today|: either, you set today to some -# non-false value, then it is used: -# today = '' -# Else, today_fmt is used as the format for a strftime call. -# today_fmt = '%B %d, %Y' - -# List of patterns, relative to source directory, that match files and -# directories to ignore when looking for source files. -exclude_patterns = ["_build"] - -# The reST default role (used for this markup: `text`) to use for all -# documents. -# default_role = None - -# If true, '()' will be appended to :func: etc. cross-reference text. -# add_function_parentheses = True - -# If true, the current module name will be prepended to all description -# unit titles (such as .. function::). -# add_module_names = True - -# If true, sectionauthor and moduleauthor directives will be shown in the -# output. They are ignored by default. -# show_authors = False - -# The name of the Pygments (syntax highlighting) style to use. -pygments_style = "sphinx" - -# A list of ignored prefixes for module index sorting. -# modindex_common_prefix = [] - -# If true, keep warnings as "system message" paragraphs in the built documents. -# keep_warnings = False - - -# -- Options for HTML output ---------------------------------------------- - -# The theme to use for HTML and HTML Help pages. See the documentation for -# a list of builtin themes. - -try: - import sphinx_rtd_theme - - html_theme = "sphinx_rtd_theme" - html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] -except ImportError: - html_theme = "default" - sys.stderr.write("Failed to import sphinx_rtd_theme!") - - -html_domain_indices = True -html_show_sourcelink = True - - -# Theme options are theme-specific and customize the look and feel of a theme -# further. For a list of options available for each theme, see the -# documentation. -# html_theme_options = {} - -# Add any paths that contain custom themes here, relative to this directory. -# html_theme_path = [] - -# The name for this set of Sphinx documents. If None, it defaults to -# " v documentation". -# html_title = None - -# A shorter title for the navigation bar. Default is the same as html_title. -# html_short_title = None - -# The name of an image file (relative to this directory) to place at the top -# of the sidebar. -# html_logo = None - -# The name of an image file (within the static path) to use as favicon of the -# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 -# pixels large. -# html_favicon = None - -# Add any paths that contain custom static files (such as style sheets) here, -# relative to this directory. They are copied after the builtin static files, -# so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ["_static"] - -# Add any extra paths that contain custom files (such as robots.txt or -# .htaccess) here, relative to this directory. These files are copied -# directly to the root of the documentation. -# html_extra_path = [] - -# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, -# using the given strftime format. -# html_last_updated_fmt = '%b %d, %Y' - -# If true, SmartyPants will be used to convert quotes and dashes to -# typographically correct entities. -# html_use_smartypants = True - -# Custom sidebar templates, maps document names to template names. -# html_sidebars = {} - -# Additional templates that should be rendered to pages, maps page names to -# template names. -# html_additional_pages = {} - -# If false, no module index is generated. -# html_domain_indices = True - -# If false, no index is generated. -# html_use_index = True - -# If true, the index is split into individual pages for each letter. -# html_split_index = False - -# If true, links to the reST sources are added to the pages. -# html_show_sourcelink = True - -# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. -# html_show_sphinx = True - -# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. -# html_show_copyright = True - -# If true, an OpenSearch description file will be output, and all pages will -# contain a tag referring to it. The value of this option must be the -# base URL from which the finished HTML is served. -# html_use_opensearch = '' - -# This is the file name suffix for HTML files (e.g. ".xhtml"). -# html_file_suffix = None - -# Output file base name for HTML help builder. -htmlhelp_basename = "pexdoc" - - -# -- Options for LaTeX output --------------------------------------------- - -latex_elements = { - # The paper size ('letterpaper' or 'a4paper'). - #'papersize': 'letterpaper', - # The font size ('10pt', '11pt' or '12pt'). - #'pointsize': '10pt', - # Additional stuff for the LaTeX preamble. - #'preamble': '', +source_suffix = { + ".md": "markdown", + ".rst": "restructuredtext", } -# Grouping the document tree into LaTeX files. List of tuples -# (source start file, target name, title, -# author, documentclass [howto, manual, or own class]). -latex_documents = [ - ("index", "pex.tex", u"pex Documentation", u"Brian Wickman", "manual"), +suppress_warnings = [ + # Otherwise epub warns (and we treat warinings as errors) when it finds .doctrees/ files, which it should not + # consider anyhow. + "epub.unknown_project_files" ] -# The name of an image file (relative to this directory) to place at the top of -# the title page. -# latex_logo = None - -# For "manual" documents, if this is true, then toplevel headings are parts, -# not chapters. -# latex_use_parts = False - -# If true, show page references after internal links. -# latex_show_pagerefs = False - -# If true, show URL addresses after external links. -# latex_show_urls = False - -# Documents to append as an appendix to all manuals. -# latex_appendices = [] - -# If false, no module index is generated. -# latex_domain_indices = True - - -# -- Options for manual page output --------------------------------------- - -# One entry per manual page. List of tuples -# (source start file, name, description, authors, manual section). -man_pages = [("index", "pex", u"pex Documentation", [u"Brian Wickman"], 1)] - -# If true, show URL addresses after external links. -# man_show_urls = False - +templates_path = [ + "_templates", +] -# -- Options for Texinfo output ------------------------------------------- +# -- Options for HTML output ------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output +# https://myst-parser.readthedocs.io/en/latest/configuration.html +# https://pradyunsg.me/furo/customisation/ -# Grouping the document tree into Texinfo files. List of tuples -# (source start file, target name, title, author, -# dir menu entry, description, category) -texinfo_documents = [ - ( - "index", - "pex", - u"pex Documentation", - u"Brian Wickman", - "pex", - "One line description of project.", - "Miscellaneous", - ), +myst_enable_extensions = [ + "linkify", ] -# Documents to append as an appendix to all manuals. -# texinfo_appendices = [] - -# If false, no module index is generated. -# texinfo_domain_indices = True +import sphinx_pex +from sphinx_pex import SVGIcon + +html_title = f"Pex Docs (v{release})" +html_theme = "furo" +html_favicon = "_static/pex.ico" +html_static_path = sphinx_pex.html_static_path() + + +html_theme_options = { + "light_logo": "pex-full-light.png", + "dark_logo": "pex-full-dark.png", + "sidebar_hide_name": True, + "source_repository": "https://github.com/pex-tool/pex/", + "source_branch": "main", + "source_directory": "docs/", + "footer_icons": [ + icon.as_furo_footer_icon() + for icon in [ + SVGIcon.load_if_static_asset_exists( + name="PDF", icon_asset=PurePath("pdf.svg"), static_asset=PurePath("pex.pdf") + ), + SVGIcon.load( + name="PyPI", + icon_asset=PurePath("python.svg"), + url=f"https://pypi.org/project/pex/{PEX_VERSION}/", + ), + SVGIcon.load( + name="Download", + icon_asset=PurePath("download.svg"), + url=f"https://github.com/pex-tool/pex/releases/download/v{PEX_VERSION}/pex", + ), + SVGIcon.load( + name="Source", + icon_asset=PurePath("github.svg"), + url="https://github.com/pex-tool/pex", + ), + ] + if icon + ], +} -# How to display URL addresses: 'footnote', 'no', or 'inline'. -# texinfo_show_urls = 'footnote' +# -- Options for Sphinx-SimplePDF output ------------------------------------------------- +# https://sphinx-simplepdf.readthedocs.io/en/latest/configuration.html -# If true, do not generate a @detailmenu in the "Top" node's menu. -# texinfo_no_detailmenu = False +simplepdf_vars = { + "primary": "#ffcc00", + "cover": "black", + "cover-bg": "url(pex-cover.png) no-repeat center", +} diff --git a/pex/cli/commands/__init__.py b/pex/cli/commands/__init__.py index 9ea8b48ad..25e40b32c 100644 --- a/pex/cli/commands/__init__.py +++ b/pex/cli/commands/__init__.py @@ -2,6 +2,7 @@ # Licensed under the Apache License, Version 2.0 (see LICENSE). from pex.cli.command import BuildTimeCommand +from pex.cli.commands.docs import Docs from pex.cli.commands.interpreter import Interpreter from pex.cli.commands.lock import Lock from pex.cli.commands.venv import Venv @@ -13,4 +14,4 @@ def all_commands(): # type: () -> Iterable[Type[BuildTimeCommand]] - return Interpreter, Lock, Venv + return Docs, Interpreter, Lock, Venv diff --git a/pex/cli/commands/docs.py b/pex/cli/commands/docs.py new file mode 100644 index 000000000..89b9e1df0 --- /dev/null +++ b/pex/cli/commands/docs.py @@ -0,0 +1,220 @@ +# Copyright 2024 Pex project contributors. +# Licensed under the Apache License, Version 2.0 (see LICENSE). + +import errno +import json +import logging +import os +import re +import signal +import subprocess +import sys +import time +from textwrap import dedent + +from pex import docs +from pex.cli.command import BuildTimeCommand +from pex.commands.command import try_open_file +from pex.common import safe_open +from pex.result import Error, Result +from pex.typing import TYPE_CHECKING +from pex.variables import ENV +from pex.version import __version__ + +if TYPE_CHECKING: + from typing import Optional, Union + + import attr # vendor:skip +else: + from pex.third_party import attr + + +logger = logging.getLogger(__name__) + + +SERVER_NAME = "Pex v{version} docs HTTP server".format(version=__version__) +SERVER_DIR = os.path.join(ENV.PEX_ROOT, "docs", "server", __version__) + + +@attr.s(frozen=True) +class Pidfile(object): + _PIDFILE = os.path.join(SERVER_DIR, "pidfile") + + @classmethod + def load(cls): + # type: () -> Optional[Pidfile] + try: + with open(cls._PIDFILE) as fp: + data = json.load(fp) + return cls(url=data["url"], pid=data["pid"]) + except (OSError, IOError, ValueError, KeyError) as e: + logger.warning( + "Failed to load {server} pid file from {path}: {err}".format( + server=SERVER_NAME, path=cls._PIDFILE, err=e + ) + ) + return None + + @staticmethod + def _read_url( + server_log, # type: str + timeout, # type: float + ): + # type: (...) -> Optional[str] + + # The permutations of Python versions, simple http server module and the output it provides: + # 2.7: Serving HTTP on 0.0.0.0 port 46399 ... -mSimpleHttpServer + # 3.5: Serving HTTP on 0.0.0.0 port 45577 ... -mhttp.server + # 3.6+: Serving HTTP on 0.0.0.0 port 33539 (http://0.0.0.0:33539/) ... -mhttp.server + + start = time.time() + while time.time() - start < timeout: + with open(server_log) as fp: + for line in fp: + if line.endswith(os.linesep): + match = re.search(r"Serving HTTP on 0.0.0.0 port (?P\d+)", line) + if match: + port = match.group("port") + return "http://localhost:{port}".format(port=port) + return None + + @classmethod + def record( + cls, + server_log, # type: str + pid, # type: int + timeout=5.0, # type: float + ): + # type: (...) -> Optional[Pidfile] + url = cls._read_url(server_log, timeout) + if not url: + return None + + with safe_open(cls._PIDFILE, "w") as fp: + json.dump(dict(url=url, pid=pid), fp, indent=2, sort_keys=True) + return cls(url=url, pid=pid) + + url = attr.ib() # type: str + pid = attr.ib() # type: int + + def alive(self): + # type: () -> bool + # TODO(John Sirois): Handle pid rollover + try: + os.kill(self.pid, 0) + return True + except OSError as e: + if e.errno == errno.ESRCH: # No such process. + return False + raise + + def kill(self): + # type: () -> None + os.kill(self.pid, signal.SIGTERM) + + +@attr.s(frozen=True) +class LaunchResult(object): + url = attr.ib() # type: str + already_running = attr.ib() # type: bool + + +def launch_docs_server( + document_root, # type: str + port, # type: int + timeout=5.0, # type: float +): + # type: (...) -> Union[str, LaunchResult] + + pidfile = Pidfile.load() + if pidfile and pidfile.alive(): + return LaunchResult(url=pidfile.url, already_running=True) + + # Not proper daemonization, but good enough. + log = os.path.join(SERVER_DIR, "log.txt") + http_server_module = "http.server" if sys.version_info[0] == 3 else "SimpleHttpServer" + env = os.environ.copy() + # N.B.: We set up line buffering for the process pipes as well as the underlying Python running + # the http server to ensure we can observe the `Serving HTTP on ...` line we need to grab the + # ephemeral port chosen. + env.update(PYTHONUNBUFFERED="1") + with safe_open(log, "w") as fp: + process = subprocess.Popen( + args=[sys.executable, "-m", http_server_module, str(port)], + env=env, + cwd=document_root, + preexec_fn=os.setsid, + bufsize=1, + stdout=fp.fileno(), + stderr=subprocess.STDOUT, + ) + + pidfile = Pidfile.record(server_log=log, pid=process.pid, timeout=timeout) + if not pidfile: + try: + os.kill(process.pid, signal.SIGKILL) + except OSError as e: + if e.errno != errno.ESRCH: # No such process. + raise + return log + + return LaunchResult(url=pidfile.url, already_running=False) + + +def shutdown_docs_server(): + # type: () -> bool + + pidfile = Pidfile.load() + if not pidfile: + return False + + logger.info( + "Killing {server} {url} @ {pid}".format( + server=SERVER_NAME, url=pidfile.url, pid=pidfile.pid + ) + ) + pidfile.kill() + return True + + +class Docs(BuildTimeCommand): + """Interact with the Pex documentation.""" + + def run(self): + # type: () -> Result + html_docs = docs.root(doc_type="html") + if not html_docs: + # TODO(John Sirois): Evaluate if this should fall back to opening latest html docs instead of just + # displaying links. + return Error( + dedent( + """\ + This Pex distribution does not include embedded docs. + + You can find the latest docs here: + HTML: https://docs.pex-tool.org + PDF: https://github.com/pex-tool/pex/releases/latest/download/pex.pdf + """ + ).rstrip() + ) + + # TODO(John Sirois): Consider trying a standard pex docs port, then fall back to ephemeral if: + # 2.7: socket.error: [Errno 98] Address already in use + # 3.x: OSError: [Errno 98] Address already in use + # This would allow for cookie stickiness for light / dark mode although the furo theme default of detecting + # the system default mode works well in practice to get you what you probably wanted anyhow. + result = launch_docs_server(html_docs, port=0) + if isinstance(result, str): + with open(result) as fp: + for line in fp: + logger.log(logging.ERROR, line.rstrip()) + return Error("Failed to launch {server}.".format(server=SERVER_NAME)) + + logger.info( + ( + "{server} already running at {url}" + if result.already_running + else "Launched {server} at {url}" + ).format(server=SERVER_NAME, url=result.url) + ) + return try_open_file(result.url) diff --git a/pex/docs/__init__.py b/pex/docs/__init__.py new file mode 100644 index 000000000..9fc271824 --- /dev/null +++ b/pex/docs/__init__.py @@ -0,0 +1,18 @@ +# Copyright 2024 Pex project contributors. +# Licensed under the Apache License, Version 2.0 (see LICENSE). + +from __future__ import absolute_import + +import os.path + +from pex.typing import TYPE_CHECKING + +if TYPE_CHECKING: + from typing import Optional + + +def root(doc_type="html"): + # type: (str) -> Optional[str] + + doc_root = os.path.join(os.path.dirname(__file__), doc_type) + return doc_root if os.path.isdir(doc_root) else None diff --git a/pex/platforms.py b/pex/platforms.py index 961f8d592..f43950e87 100644 --- a/pex/platforms.py +++ b/pex/platforms.py @@ -82,7 +82,7 @@ def create(cls, platform, cause=None): You may be forced to specify this form when resolves encounter environment markers that use `python_full_version`. See the `--complete-platform` help as well as: - + https://pex.readthedocs.io/en/latest/buildingpex.html#complete-platform + + https://docs.pex-tool.org/buildingpex.html#complete-platform + https://www.python.org/dev/peps/pep-0508/#environment-markers """ ) diff --git a/pex/variables.py b/pex/variables.py index 3cea85320..41ff3b287 100644 --- a/pex/variables.py +++ b/pex/variables.py @@ -337,7 +337,9 @@ def _maybe_get_path_tuple( def strip(self): # type: () -> Variables stripped_environ = { - k: v for k, v in self.copy().items() if not k.startswith(("PEX_", "__PEX_")) + k: v + for k, v in self.copy().items() + if k.startswith("__PEX_BUILD_") or not k.startswith(("PEX_", "__PEX_")) } return Variables(environ=stripped_environ) diff --git a/pex/venv/installer_options.py b/pex/venv/installer_options.py index c178cf32c..a5201de91 100644 --- a/pex/venv/installer_options.py +++ b/pex/venv/installer_options.py @@ -27,7 +27,7 @@ def register( "situations it's beneficial to split the venv installation into {deps} and " "{sources} steps. This is particularly useful when installing a PEX in a container " "image. See " - "https://pex.readthedocs.io/en/latest/recipes.html#pex-app-in-a-container for more " + "https://docs.pex-tool.org/recipes.html#pex-app-in-a-container for more " "information.".format( all=InstallScope.ALL, deps=InstallScope.DEPS_ONLY, diff --git a/pex/version.py b/pex/version.py index 2188eae44..2ae99045d 100644 --- a/pex/version.py +++ b/pex/version.py @@ -1,4 +1,4 @@ # Copyright 2015 Pex project contributors. # Licensed under the Apache License, Version 2.0 (see LICENSE). -__version__ = "2.1.163" +__version__ = "2.1.164" diff --git a/pyproject.toml b/pyproject.toml index 3fe90a7c8..1b47ea741 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,7 +7,10 @@ backend-path = ["build-backend"] build-backend = "pex_build.hatchling.build" requires = ["hatchling"] -[tool.hatch.metadata.hooks.pex-dynamic-requires-python] +[tool.hatch.metadata.hooks.pex-adjust-metadata] +expand = {"pex_version" = "version"} + +[tool.hatch.build.targets.wheel.hooks.pex-adjust-build] # We need this empty table to enable our hook. [project] @@ -62,11 +65,11 @@ pex-tools = "pex.tools.main:main" bdist_pex = "pex.distutils.commands.bdist_pex:bdist_pex" [project.urls] +Changelog = "https://github.com/pex-tool/pex/blob/v{pex_version}/CHANGES.md" +Documentation = "https://docs.pex-tool.org/" +Download = "https://github.com/pex-tool/pex/releases/download/v{pex_version}/pex" Homepage = "https://github.com/pex-tool/pex" -Download = "https://github.com/pex-tool/pex/releases/latest/download/pex" -Changelog = "https://github.com/pex-tool/pex/blob/main/CHANGES.md" -Documentation = "https://pex.readthedocs.io/en/latest/" -Source = "https://github.com/pex-tool/pex" +Source = "https://github.com/pex-tool/pex/tree/v{pex_version}" [tool.hatch.version] path = "pex/version.py" diff --git a/scripts/build_docs.py b/scripts/build_docs.py new file mode 100644 index 000000000..4b867e9b9 --- /dev/null +++ b/scripts/build_docs.py @@ -0,0 +1,179 @@ +#!/usr/bin/env python3 + +from __future__ import annotations + +import argparse +import atexit +import os.path +import platform +import shutil +import subprocess +import sys +import tarfile +import tempfile +from enum import Enum +from pathlib import Path, PurePath +from typing import Iterable, Optional +from urllib.parse import unquote, urlparse + +import httpx + +PEX_DEV_DIR = Path("~/.pex_dev").expanduser() + +PAGEFIND_NAME = "pagefind" +PAGEFIND_VERSION = "1.0.4" + + +class Platform(Enum): + Linux_aarch64 = "aarch64-unknown-linux-musl" + Linux_x86_64 = "x86_64-unknown-linux-musl" + Macos_aarch64 = "aarch64-apple-darwin" + Macos_x86_64 = "x86_64-apple-darwin" + Windows_x86_64 = "x86_64-pc-windows-msvc" + + @classmethod + def parse(cls, value: str) -> Platform: + return Platform.current() if "current" == value else Platform(value) + + @classmethod + def current(cls) -> Platform: + system = platform.system().lower() + machine = platform.machine().lower() + if system == "linux": + if machine in ("aarch64", "arm64"): + return cls.Linux_aarch64 + elif machine in ("amd64", "x86_64"): + return cls.Linux_x86_64 + elif system == "darwin": + if machine in ("aarch64", "arm64"): + return cls.Macos_aarch64 + elif machine in ("amd64", "x86_64"): + return cls.Macos_x86_64 + elif system == "windows" and machine in ("amd64", "x86_64"): + return cls.Windows_x86_64 + + raise ValueError( + f"The current operating system / machine pair is not supported!: {system} / {machine}" + ) + + @property + def extension(self): + return ".exe" if self is self.Windows_x86_64 else "" + + def binary_name(self, binary_name: str) -> str: + return f"{binary_name}{self.extension}" + + +CURRENT_PLATFORM = Platform.current() + + +def target_triple() -> str: + return CURRENT_PLATFORM.value + + +def pagefind_executable() -> str: + return CURRENT_PLATFORM.binary_name( + binary_name="{name}-{version}".format(name=PAGEFIND_NAME, version=PAGEFIND_VERSION) + ) + + +def ensure_pagefind() -> PurePath: + pagefind_exe = pagefind_executable() + pagefind_exe_path = PEX_DEV_DIR / pagefind_exe + if pagefind_exe_path.is_file() and os.access(pagefind_exe_path, os.R_OK | os.X_OK): + return pagefind_exe_path + + tmp_dir = PEX_DEV_DIR / ".tmp" + tmp_dir.mkdir(parents=True, exist_ok=True) + download_dir = Path(tempfile.mktemp(prefix="download-pagefind.", dir=tmp_dir)) + atexit.register(shutil.rmtree, str(download_dir), ignore_errors=True) + + tarball_url = ( + f"https://github.com/CloudCannon/pagefind/releases/download/v{PAGEFIND_VERSION}/" + f"{PAGEFIND_NAME}-v{PAGEFIND_VERSION}-{target_triple()}.tar.gz" + ) + out_path = download_dir / PurePath(unquote(urlparse(tarball_url).path)).name + out_path.parent.mkdir(parents=True, exist_ok=True) + with httpx.stream("GET", tarball_url, follow_redirects=True) as response, out_path.open( + "wb" + ) as out_fp: + for chunk in response.iter_bytes(): + out_fp.write(chunk) + with tarfile.open(out_path) as tf: + tf.extract(PAGEFIND_NAME, path=str(download_dir)) + (download_dir / PAGEFIND_NAME).rename(pagefind_exe_path) + + return pagefind_exe_path + + +def execute_pagefind(args: Iterable[str]) -> None: + subprocess.run(args=[str(ensure_pagefind()), *args], check=True) + + +def execute_sphinx_build(out_base_dir: Path, builder: str, out_dir: Optional[str] = None) -> Path: + gen_dir = (out_base_dir / (out_dir or builder)).absolute() + shutil.rmtree(gen_dir, ignore_errors=True) + subprocess.run( + args=[sys.executable, "-m", "sphinx", "-b", builder, "-aEW", "docs", str(gen_dir)], + check=True, + ) + return gen_dir + + +def main( + out_dir: Path, + linkcheck: bool = False, + pdf: bool = False, + html: bool = True, + clean_html: bool = False, + serve: bool = False, +) -> None: + static_dynamic_dir = (Path("docs") / "_static_dynamic").absolute() + + def clean_static_dynmaic_dir() -> None: + shutil.rmtree(static_dynamic_dir, ignore_errors=True) + + clean_static_dynmaic_dir() + static_dynamic_dir.mkdir(parents=True, exist_ok=True) + atexit.register(clean_static_dynmaic_dir) + + if linkcheck: + execute_sphinx_build(out_dir, "linkcheck") + + if pdf: + pdf_dir = execute_sphinx_build(out_dir, "simplepdf", out_dir="pdf") + (static_dynamic_dir / "pex.pdf").symlink_to(pdf_dir / "pex.pdf") + + if html: + html_dir = execute_sphinx_build(out_dir, "html") + if clean_html: + shutil.rmtree(html_dir / ".doctrees", ignore_errors=True) + (html_dir / ".buildinfo").unlink(missing_ok=True) + (html_dir / "objects.inv").unlink(missing_ok=True) + + page_find_args = ["--site", str(html_dir), "--output-subdir", "_pagefind"] + if serve: + page_find_args.append("--serve") + execute_pagefind(page_find_args) + + +if __name__ == "__main__": + parser = argparse.ArgumentParser() + parser.add_argument("--linkcheck", action="store_true") + parser.add_argument("--pdf", action="store_true") + parser.add_argument("--no-html", action="store_true") + parser.add_argument("--clean-html", action="store_true") + parser.add_argument("--serve", action="store_true") + parser.add_argument("out_dir", type=Path, default=Path("dist") / "docs", nargs="?") + options = parser.parse_args() + try: + main( + out_dir=options.out_dir, + linkcheck=options.linkcheck, + pdf=options.pdf, + html=not options.no_html, + clean_html=options.clean_html, + serve=options.serve, + ) + except subprocess.CalledProcessError as e: + sys.exit(e.returncode) diff --git a/scripts/format.py b/scripts/format.py index ba7629894..e25169845 100755 --- a/scripts/format.py +++ b/scripts/format.py @@ -35,7 +35,17 @@ def run_black(*args: str) -> None: dest=sys.stdout, ) as out_fd: subprocess.run( - args=["black", "--color", *args, "build-backend", "pex", "scripts", "testing", "tests"], + args=[ + "black", + "--color", + *args, + "build-backend", + "docs", + "pex", + "scripts", + "testing", + "tests", + ], stdout=out_fd, stderr=subprocess.STDOUT, check=True, @@ -44,7 +54,8 @@ def run_black(*args: str) -> None: def run_isort(*args: str) -> None: subprocess.run( - args=["isort", *args, "build-backend", "pex", "scripts", "testing", "tests"], check=True + args=["isort", *args, "build-backend", "docs", "pex", "scripts", "testing", "tests"], + check=True, ) diff --git a/scripts/lint.py b/scripts/lint.py index 41e01ec20..a17b0caa0 100644 --- a/scripts/lint.py +++ b/scripts/lint.py @@ -25,6 +25,7 @@ def run_autoflake(*args: str) -> None: ",".join(excludes), "--recursive", "build-backend", + "docs", "pex", "scripts", "testing", diff --git a/scripts/package.py b/scripts/package.py index b160f78bc..9944c5c71 100755 --- a/scripts/package.py +++ b/scripts/package.py @@ -1,21 +1,26 @@ #!/usr/bin/env python3 +import atexit import hashlib import io import os +import shutil import subprocess import sys +import tempfile from argparse import ArgumentDefaultsHelpFormatter, ArgumentParser from email.parser import Parser from enum import Enum, unique from http.server import HTTPServer, SimpleHTTPRequestHandler from pathlib import Path, PurePath -from typing import Optional, Tuple, cast +from typing import Dict, Iterator, Optional, Tuple, cast DIST_DIR = Path("dist") -def build_pex_pex(output_file: PurePath, verbosity: int = 0) -> None: +def build_pex_pex( + output_file: PurePath, verbosity: int = 0, env: Optional[Dict[str, str]] = None +) -> PurePath: # NB: We do not include the subprocess extra (which would be spelled: `.[subprocess]`) since we # would then produce a pex that would not be consumable by all python interpreters otherwise # meeting `python_requires`; ie: we'd need to then come up with a deploy environment / deploy @@ -44,7 +49,8 @@ def build_pex_pex(output_file: PurePath, verbosity: int = 0) -> None: "pex", pex_requirement, ] - subprocess.run(args, check=True) + subprocess.run(args=args, env=env, check=True) + return output_file def describe_rev() -> str: @@ -82,32 +88,54 @@ def build_arg(self) -> str: return f"--{self.value}" -def build_pex_dists(dist_fmt: Format, *additional_dist_fmts: Format, verbose: bool = False) -> None: +def build_pex_dists( + dist_fmt: Format, + *additional_dist_fmts: Format, + verbose: bool = False, + env: Optional[Dict[str, str]] = None +) -> Iterator[PurePath]: + tmp_dir = DIST_DIR / ".tmp" + tmp_dir.mkdir(parents=True, exist_ok=True) + out_dir = tempfile.mkdtemp(dir=tmp_dir) + atexit.register(shutil.rmtree, out_dir, ignore_errors=True) + output = None if verbose else subprocess.DEVNULL + subprocess.run( - [ + args=[ sys.executable, "-m", "build", "--outdir", - str(DIST_DIR), + out_dir, *[fmt.build_arg() for fmt in [dist_fmt, *additional_dist_fmts]], ], + env=env, stdout=output, stderr=output, check=True, ) + for dist in os.listdir(out_dir): + built = DIST_DIR / dist + shutil.move(os.path.join(out_dir, dist), built) + yield built + def main( *additional_dist_formats: Format, verbosity: int = 0, + embed_docs: bool = False, pex_output_file: Optional[Path] = DIST_DIR / "pex", serve: bool = False ) -> None: + env = os.environ.copy() + if embed_docs: + env.update(__PEX_BUILD_INCLUDE_DOCS__="1") + if pex_output_file: print(f"Building Pex PEX to `{pex_output_file}` ...") - build_pex_pex(pex_output_file, verbosity) + build_pex_pex(pex_output_file, verbosity, env=env) rev = describe_rev() sha256, size = describe_file(pex_output_file) @@ -120,16 +148,17 @@ def main( f"Building additional distribution formats to `{DIST_DIR}`: " f'{", ".join(f"{i + 1}.) {fmt}" for i, fmt in enumerate(additional_dist_formats))} ...' ) - build_pex_dists( - additional_dist_formats[0], *additional_dist_formats[1:], verbose=verbosity > 0 + built = list( + build_pex_dists( + additional_dist_formats[0], + *additional_dist_formats[1:], + verbose=verbosity > 0, + env=env + ) ) print("Built:") - for root, _, files in os.walk(DIST_DIR): - root_path = Path(root) - for f in files: - dist_path = root_path / f - if dist_path != pex_output_file: - print(f" {dist_path}") + for dist_path in built: + print(f" {dist_path}") if serve: server = HTTPServer(("", 0), SimpleHTTPRequestHandler) @@ -149,6 +178,12 @@ def main( parser.add_argument( "-v", dest="verbosity", action="count", default=0, help="Increase output verbosity level." ) + parser.add_argument( + "--embed-docs", + default=False, + action="store_true", + help="Embed offline docs in the built binary distributions.", + ) parser.add_argument( "--additional-format", dest="additional_formats", @@ -180,6 +215,7 @@ def main( main( *(args.additional_formats or ()), verbosity=args.verbosity, + embed_docs=args.embed_docs, pex_output_file=None if args.no_pex else args.pex_output_file, serve=args.serve ) diff --git a/scripts/typecheck.py b/scripts/typecheck.py index f3e2ad93c..da98a200b 100755 --- a/scripts/typecheck.py +++ b/scripts/typecheck.py @@ -41,6 +41,11 @@ def main() -> None: run_mypy( "2.7", files=sorted(find_files_to_check(include=["build-backend"])), subject="build-backend" ) + run_mypy( + "3.8", + files=sorted(find_files_to_check(include=["docs"])), + subject="sphinx_pex", + ) run_mypy("3.8", files=sorted(find_files_to_check(include=["scripts"])), subject="scripts") source_and_tests = sorted( diff --git a/tox.ini b/tox.ini index c28b741ac..b563116a8 100644 --- a/tox.ini +++ b/tox.ini @@ -74,8 +74,7 @@ setenv = # line buffering (which is what setting PYTHONUNBUFFERED nets you) so that tests can rely on # stderr lines being observable. py{py35,py36,py37,py38,py39,35,36,37,38}: PYTHONUNBUFFERED=1 -whitelist_externals = - open +allowlist_externals = bash git @@ -136,6 +135,8 @@ deps = types-setuptools types-toml==0.10.5 httpx==0.23.0 + sphinx + types-docutils commands = python scripts/typecheck.py @@ -168,13 +169,11 @@ commands = git diff --exit-code [testenv:docs] -changedir = docs +basepython = python3 deps = - sphinx - sphinx-rtd-theme + -r docs-requirements.txt commands = - sphinx-build -b html -d {envtmpdir}/doctrees . _build/html - open _build/html/index.html + python scripts/build_docs.py {posargs} [_package] basepython = python3

5vhKp<3UA1BsqbB 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 literal 0 HcmV?d00001 diff --git a/assets/pex.ico b/assets/pex.ico new file mode 100644 index 0000000000000000000000000000000000000000..bcd0c3ab993cda4166cc156d0bc79373f132df88 GIT binary patch literal 47040 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 literal 0 HcmV?d00001 diff --git a/assets/python.svg b/assets/python.svg new file mode 100644 index 000000000..3d35fb51b --- /dev/null +++ b/assets/python.svg @@ -0,0 +1,4 @@ + + + + diff --git a/build-backend/pex_build/__init__.py b/build-backend/pex_build/__init__.py index 87fb2ed9a..05794947f 100644 --- a/build-backend/pex_build/__init__.py +++ b/build-backend/pex_build/__init__.py @@ -1,2 +1,11 @@ # Copyright 2024 Pex project contributors. # Licensed under the Apache License, Version 2.0 (see LICENSE). + +import os + +# When running under MyPy, this will be set to True for us automatically; so we can use it as a typing module import +# guard to protect Python 2 imports of typing - which is not normally available in Python 2. +TYPE_CHECKING = False + + +INCLUDE_DOCS = os.environ.get("__PEX_BUILD_INCLUDE_DOCS__", "False").lower() in ("1", "true") diff --git a/build-backend/pex_build/hatchling/build.py b/build-backend/pex_build/hatchling/build.py index cf00f622e..25985bbe1 100644 --- a/build-backend/pex_build/hatchling/build.py +++ b/build-backend/pex_build/hatchling/build.py @@ -1,7 +1,29 @@ # Copyright 2024 Pex project contributors. # Licensed under the Apache License, Version 2.0 (see LICENSE). -from __future__ import absolute_import +from __future__ import absolute_import, print_function + +import hatchling.build +import pex_build # We re-export all hatchling's PEP-517 build backend hooks here for the build frontend to call. from hatchling.build import * # NOQA + +if pex_build.TYPE_CHECKING: + from typing import Any, Dict, List, Optional + + +def get_requires_for_build_wheel(config_settings=None): + # type: (Optional[Dict[str, Any]]) -> List[str] + + reqs = hatchling.build.get_requires_for_build_wheel( + config_settings=config_settings + ) # type: List[str] + if pex_build.INCLUDE_DOCS: + with open("docs-requirements.txt") as fp: + for raw_req in fp.readlines(): + req = raw_req.strip() + if not req or req.startswith("#"): + continue + reqs.append(req) + return reqs diff --git a/build-backend/pex_build/hatchling/build_hook.py b/build-backend/pex_build/hatchling/build_hook.py new file mode 100644 index 000000000..1b89faeda --- /dev/null +++ b/build-backend/pex_build/hatchling/build_hook.py @@ -0,0 +1,38 @@ +# Copyright 2024 Pex project contributors. +# Licensed under the Apache License, Version 2.0 (see LICENSE). + +from __future__ import absolute_import, print_function + +import os.path +import subprocess +import sys + +import pex_build +from hatchling.builders.hooks.plugin.interface import BuildHookInterface + +if pex_build.TYPE_CHECKING: + from typing import Any, Dict + + +class AdjustBuild(BuildHookInterface): + """Allows alteration of the build process.""" + + PLUGIN_NAME = "pex-adjust-build" + + def initialize( + self, + version, # type: str + build_data, # type: Dict[str, Any] + ): + # type: (...) -> None + if pex_build.INCLUDE_DOCS: + out_dir = os.path.join(self.root, "dist", "docs") + subprocess.check_call( + args=[ + sys.executable, + os.path.join(self.root, "scripts", "build_docs.py"), + "--clean-html", + out_dir, + ] + ) + build_data["force_include"][out_dir] = os.path.join("pex", "docs") diff --git a/build-backend/pex_build/hatchling/dynamic_requires_python.py b/build-backend/pex_build/hatchling/dynamic_requires_python.py deleted file mode 100644 index 666581ad2..000000000 --- a/build-backend/pex_build/hatchling/dynamic_requires_python.py +++ /dev/null @@ -1,27 +0,0 @@ -# Copyright 2024 Pex project contributors. -# Licensed under the Apache License, Version 2.0 (see LICENSE). - -from __future__ import absolute_import, print_function - -import os -import sys -from typing import Any, Dict - -from hatchling.metadata.plugin.interface import MetadataHookInterface - - -class DynamicRequiresPythonHook(MetadataHookInterface): - """Allows dynamically specifying requires-python metadata via _PEX_REQUIRES_PYTHON env var.""" - - PLUGIN_NAME = "pex-dynamic-requires-python" - - def update(self, metadata): - # type: (Dict[str, Any]) -> None - requires_python = os.environ.get("_PEX_REQUIRES_PYTHON") - if requires_python: - print( - "pex_build: Dynamically modifying pyproject.toml requires-python of {original} to " - "{dynamic}".format(original=metadata["requires-python"], dynamic=requires_python), - file=sys.stderr, - ) - metadata["requires-python"] = requires_python diff --git a/build-backend/pex_build/hatchling/hooks.py b/build-backend/pex_build/hatchling/hooks.py index 2a90079c4..ea8305c24 100644 --- a/build-backend/pex_build/hatchling/hooks.py +++ b/build-backend/pex_build/hatchling/hooks.py @@ -3,13 +3,22 @@ from __future__ import absolute_import -from typing import Type - +import pex_build from hatchling.plugin import hookimpl -from pex_build.hatchling.dynamic_requires_python import DynamicRequiresPythonHook +from pex_build.hatchling.build_hook import AdjustBuild +from pex_build.hatchling.metadata_hook import AdjustMetadata + +if pex_build.TYPE_CHECKING: + from typing import Type @hookimpl def hatch_register_metadata_hook(): - # type: () -> Type[DynamicRequiresPythonHook] - return DynamicRequiresPythonHook + # type: () -> Type[AdjustMetadata] + return AdjustMetadata + + +@hookimpl +def hatch_register_build_hook(): + # type: () -> Type[AdjustBuild] + return AdjustBuild diff --git a/build-backend/pex_build/hatchling/metadata_hook.py b/build-backend/pex_build/hatchling/metadata_hook.py new file mode 100644 index 000000000..196f9d4d4 --- /dev/null +++ b/build-backend/pex_build/hatchling/metadata_hook.py @@ -0,0 +1,61 @@ +# Copyright 2024 Pex project contributors. +# Licensed under the Apache License, Version 2.0 (see LICENSE). + +from __future__ import absolute_import, print_function + +import os +import sys + +import pex_build +from hatchling.metadata.plugin.interface import MetadataHookInterface + +if pex_build.TYPE_CHECKING: + from typing import Any, Dict + + +def expand_value( + value, # type: Any + **fmt # type: str +): + # type: (...) -> Any + if isinstance(value, str): + return value.format(**fmt) + if isinstance(value, list): + return [expand_value(val) for val in value] + if isinstance(value, dict): + return {key: expand_value(value, **fmt) for key, value in value.items()} + return value + + +class AdjustMetadata(MetadataHookInterface): + """Allows modifying project metadata. + + The following mutations are supported: + + Specifying alternate requires-python metadata via _PEX_REQUIRES_PYTHON env var. + + Expanding format string placeholder (`{name}`) with metadata values via the `expand` mapping of placeholder name + to metadata value. + """ + + PLUGIN_NAME = "pex-adjust-metadata" + + def update(self, metadata): + # type: (Dict[str, Any]) -> None + requires_python = os.environ.get("_PEX_REQUIRES_PYTHON") + if requires_python: + print( + "pex_build: Dynamically modifying pyproject.toml requires-python of {original} to " + "{dynamic}".format(original=metadata["requires-python"], dynamic=requires_python), + file=sys.stderr, + ) + metadata["requires-python"] = requires_python + + expand = self.config.get("expand") + if expand: + metadata.update( + ( + key, + expand_value(value, **{key: metadata[value] for key, value in expand.items()}), + ) + for key, value in metadata.items() + if key != "version" + ) diff --git a/docs-requirements.txt b/docs-requirements.txt new file mode 100644 index 000000000..6c95b3098 --- /dev/null +++ b/docs-requirements.txt @@ -0,0 +1,5 @@ +furo +httpx +myst-parser[linkify] +sphinx +sphinx-simplepdf \ No newline at end of file diff --git a/docs/_ext/sphinx_pex/__init__.py b/docs/_ext/sphinx_pex/__init__.py new file mode 100644 index 000000000..dd6b8b944 --- /dev/null +++ b/docs/_ext/sphinx_pex/__init__.py @@ -0,0 +1,71 @@ +# Copyright 2024 Pex project contributors. +# Licensed under the Apache License, Version 2.0 (see LICENSE). + +from __future__ import annotations + +from dataclasses import dataclass +from pathlib import Path, PurePath +from typing import Any, Dict, List, Optional + +from sphinx.application import Sphinx +from sphinx_pex.vars import Vars + +PROJECT_ROOT = Path(__file__).parent.parent.parent.parent +ICON_ASSETS = PROJECT_ROOT / "assets" +DOC_ROOT = PROJECT_ROOT / "docs" +STATIC_ASSETS = [DOC_ROOT / "_static", DOC_ROOT / "_static_dynamic"] + + +def html_static_path() -> List[str]: + return [ + static_asset_root.relative_to(DOC_ROOT).as_posix() for static_asset_root in STATIC_ASSETS + ] + + +@dataclass(frozen=True) +class SVGIcon: + @classmethod + def load(cls, name: str, icon_asset: PurePath, url: str, css_class: str = "") -> SVGIcon: + return cls( + name=name, url=url, html=(ICON_ASSETS / icon_asset).read_text(), css_class=css_class + ) + + @classmethod + def load_if_static_asset_exists( + cls, name: str, icon_asset: PurePath, static_asset: PurePath, css_class: str = "" + ) -> Optional[SVGIcon]: + for static_asset_root in STATIC_ASSETS: + static_asset_file = static_asset_root / static_asset + if static_asset_file.is_file(): + return cls.load( + name=name, + icon_asset=icon_asset, + # N.B.: No matter where in the `html_static_path` the static asset comes from, its destination in + # the generated doc site will be the `_static/` dir. + url=(PurePath("_static") / static_asset).as_posix(), + css_class=css_class, + ) + return None + + name: str + url: str + html: str + css_class: str = "" + + def as_furo_footer_icon(self) -> Dict[str, str]: + return { + "name": self.name, + "url": self.url, + "html": self.html, + "class": self.css_class, + } + + +def setup(app: Sphinx) -> Dict[str, Any]: + app.add_directive("vars", Vars) + + return { + "version": "0.1", + "parallel_read_safe": True, + "parallel_write_safe": True, + } diff --git a/docs/_ext/vars.py b/docs/_ext/sphinx_pex/vars.py similarity index 78% rename from docs/_ext/vars.py rename to docs/_ext/sphinx_pex/vars.py index c39a2c282..433bbd271 100644 --- a/docs/_ext/vars.py +++ b/docs/_ext/sphinx_pex/vars.py @@ -1,6 +1,8 @@ # Copyright 2020 Pex project contributors. # Licensed under the Apache License, Version 2.0 (see LICENSE). +from typing import List + from docutils import nodes, statemachine from docutils.parsers.rst import Directive from sphinx import addnodes @@ -10,7 +12,7 @@ class Vars(Directive): - def convert_rst_to_nodes(self, rst_source): + def convert_rst_to_nodes(self, rst_source: str) -> List[nodes.Node]: """Turn an RST string into a node that can be used in the document.""" node = nodes.Element() node.document = self.state.document @@ -19,8 +21,8 @@ def convert_rst_to_nodes(self, rst_source): ) return node.children - def run(self): - def make_nodes(var_name): + def run(self) -> List[nodes.Node]: + def make_nodes(var_name: str) -> List[nodes.Node]: var_obj = Variables.__dict__[var_name] if isinstance(var_obj, DefaultedProperty): desc_str = var_obj._func.__doc__ @@ -33,18 +35,10 @@ def make_nodes(var_name): sig.append(nodes.target("", "", ids=[var_name])) sig.append(addnodes.desc_signature(var_name, var_name)) - return [sig] + self.convert_rst_to_nodes(desc_str) + var_nodes = [sig] # type: List[nodes.Node] + var_nodes.extend(self.convert_rst_to_nodes(desc_str)) + return var_nodes return [ node for var in dir(Variables) if var.startswith("PEX_") for node in make_nodes(var) ] - - -def setup(app): - app.add_directive("vars", Vars) - - return { - "version": "0.1", - "parallel_read_safe": True, - "parallel_write_safe": True, - } diff --git a/docs/_static/pex-cover.png b/docs/_static/pex-cover.png new file mode 120000 index 000000000..22dcab8fb --- /dev/null +++ b/docs/_static/pex-cover.png @@ -0,0 +1 @@ +../../assets/pex-icon-512.png \ No newline at end of file diff --git a/docs/_static/pex-full-dark.png b/docs/_static/pex-full-dark.png new file mode 120000 index 000000000..b2d8376da --- /dev/null +++ b/docs/_static/pex-full-dark.png @@ -0,0 +1 @@ +../../assets/pex-full-dark.png \ No newline at end of file diff --git a/docs/_static/pex-full-light.png b/docs/_static/pex-full-light.png new file mode 120000 index 000000000..d0e6eca9f --- /dev/null +++ b/docs/_static/pex-full-light.png @@ -0,0 +1 @@ +../../assets/pex-full-light.png \ No newline at end of file diff --git a/docs/_static/pex.ico b/docs/_static/pex.ico new file mode 120000 index 000000000..069050e0c --- /dev/null +++ b/docs/_static/pex.ico @@ -0,0 +1 @@ +../../assets/pex.ico \ No newline at end of file diff --git a/docs/_templates/search.html b/docs/_templates/search.html new file mode 100644 index 000000000..629f7c8f8 --- /dev/null +++ b/docs/_templates/search.html @@ -0,0 +1,54 @@ +{% extends "page.html" %} + +{%- block htmltitle -%} +{{ _("Search") }} - {{ docstitle }} +{%- endblock htmltitle -%} + +{% block content %} +