From f94f339d6b412b03a44ef99bc1b573b2ede69664 Mon Sep 17 00:00:00 2001 From: Jean-Christophe Morin <38703886+JeanChristopheMorinPerso@users.noreply.github.com> Date: Wed, 13 Sep 2023 22:05:11 -0400 Subject: [PATCH] Convert wiki to Sphinx/ReadTheDocs (REP-004 part 1) (#1522) Migrate wiki to RTD + Sphinx (keeping the wiki around for now) Signed-off-by: Jean-Christophe Morin Signed-off-by: David Lai Co-authored-by: David Lai --- .github/workflows/sphinx.yaml | 53 - .gitignore | 4 + .readthedocs.yaml | 14 + docs/Makefile | 187 +-- docs/NOTES.md | 15 + docs/api/modules.rst | 8 - docs/api/rez._sys.rst | 22 - docs/api/rez.bind.rst | 62 - docs/api/rez.cli.rst | 134 -- docs/api/rez.rst | 256 ---- docs/api/rez.tests.rst | 78 -- docs/api/rezplugins.build_system.rst | 38 - docs/api/rezplugins.release_hook.rst | 22 - docs/api/rezplugins.release_vcs.rst | 38 - docs/api/rezplugins.rst | 21 - docs/api/rezplugins.shell.rst | 54 - docs/api/rezplugins.source_retriever.rst | 38 - docs/build.py | 173 --- docs/conf.py | 274 ---- docs/index.rst | 17 - docs/make.bat | 231 +--- docs/one-liners.rst | 71 -- docs/requirements.txt | 3 + docs/rez_sphinxext.py | 349 ++++++ docs/source/_static/css/custom.css | 11 + docs/source/_static/other_pkg_mgr.png | Bin 0 -> 13425 bytes docs/source/_static/pkg_path_anatomy.png | Bin 0 -> 44007 bytes docs/source/_static/rez-horizontal-black.svg | 1 + docs/source/_static/rez-horizontal-white.svg | 1 + docs/source/_static/rez_deps_simple_eg.png | Bin 0 -> 5589 bytes docs/source/_static/rez_env.png | Bin 0 -> 118567 bytes docs/source/_static/rez_pkg_mgr.png | Bin 0 -> 40820 bytes docs/source/_templates/autosummary/module.rst | 18 + docs/source/api.rst | 47 + docs/source/basic_concepts.rst | 435 +++++++ docs/source/building_packages.rst | 293 +++++ docs/source/commands_index.rst | 7 + docs/source/conf.py | 85 ++ docs/source/configuring_rez.rst | 142 +++ docs/source/context.rst | 111 ++ docs/source/context_bundles.rst | 86 ++ docs/source/environment.rst | 253 ++++ docs/source/ephemerals.rst | 182 +++ docs/source/index.rst | 77 ++ docs/source/installation.rst | 86 ++ docs/source/managing_packages.rst | 384 ++++++ docs/source/package_commands.rst | 782 ++++++++++++ docs/source/package_definition.rst | 1116 +++++++++++++++++ docs/source/pip.rst | 143 +++ docs/source/suites.rst | 180 +++ docs/source/variants.rst | 261 ++++ src/rez/bundle_context.py | 2 +- src/rez/cli/_main.py | 2 +- src/rez/cli/_util.py | 6 +- src/rez/cli/pkg-cache.py | 2 +- src/rez/command.py | 17 +- src/rez/package_cache.py | 33 +- src/rez/packages.py | 20 +- src/rez/rezconfig.py | 696 +++++----- src/rez/system.py | 43 +- src/rez/utils/data_utils.py | 7 + 61 files changed, 5562 insertions(+), 2129 deletions(-) delete mode 100644 .github/workflows/sphinx.yaml create mode 100644 .readthedocs.yaml create mode 100644 docs/NOTES.md delete mode 100644 docs/api/modules.rst delete mode 100644 docs/api/rez._sys.rst delete mode 100644 docs/api/rez.bind.rst delete mode 100644 docs/api/rez.cli.rst delete mode 100644 docs/api/rez.rst delete mode 100644 docs/api/rez.tests.rst delete mode 100644 docs/api/rezplugins.build_system.rst delete mode 100644 docs/api/rezplugins.release_hook.rst delete mode 100644 docs/api/rezplugins.release_vcs.rst delete mode 100644 docs/api/rezplugins.rst delete mode 100644 docs/api/rezplugins.shell.rst delete mode 100644 docs/api/rezplugins.source_retriever.rst delete mode 100644 docs/build.py delete mode 100644 docs/conf.py delete mode 100644 docs/index.rst delete mode 100644 docs/one-liners.rst create mode 100644 docs/requirements.txt create mode 100644 docs/rez_sphinxext.py create mode 100644 docs/source/_static/css/custom.css create mode 100644 docs/source/_static/other_pkg_mgr.png create mode 100644 docs/source/_static/pkg_path_anatomy.png create mode 100644 docs/source/_static/rez-horizontal-black.svg create mode 100644 docs/source/_static/rez-horizontal-white.svg create mode 100644 docs/source/_static/rez_deps_simple_eg.png create mode 100644 docs/source/_static/rez_env.png create mode 100644 docs/source/_static/rez_pkg_mgr.png create mode 100644 docs/source/_templates/autosummary/module.rst create mode 100644 docs/source/api.rst create mode 100644 docs/source/basic_concepts.rst create mode 100644 docs/source/building_packages.rst create mode 100644 docs/source/commands_index.rst create mode 100644 docs/source/conf.py create mode 100644 docs/source/configuring_rez.rst create mode 100644 docs/source/context.rst create mode 100644 docs/source/context_bundles.rst create mode 100644 docs/source/environment.rst create mode 100644 docs/source/ephemerals.rst create mode 100644 docs/source/index.rst create mode 100644 docs/source/installation.rst create mode 100644 docs/source/managing_packages.rst create mode 100644 docs/source/package_commands.rst create mode 100644 docs/source/package_definition.rst create mode 100644 docs/source/pip.rst create mode 100644 docs/source/suites.rst create mode 100644 docs/source/variants.rst diff --git a/.github/workflows/sphinx.yaml b/.github/workflows/sphinx.yaml deleted file mode 100644 index eec1775ad..000000000 --- a/.github/workflows/sphinx.yaml +++ /dev/null @@ -1,53 +0,0 @@ -name: sphinx -on: - release: - types: [released] - -jobs: - build: - name: Build Sphinx Documentation - runs-on: ubuntu-latest - - steps: - - name: Checkout - uses: actions/checkout@v3 - - - name: Set up Python - uses: actions/setup-python@v4 - with: - python-version: 3.7 - - - name: Sphinx Build - run: python docs/build.py --no-docker - - - uses: actions/upload-artifact@v3 - with: - name: html-docs - path: docs/_build - - publish: - name: Publish to GitHub pages - runs-on: ubuntu-latest - needs: build - - steps: - - uses: actions/download-artifact@v3 - with: - name: html-docs - path: . - - - name: Setup git repository - run: | - git init . - git config --global user.name "github.com/${{ github.actor }}" - git config --global user.email "${{ github.actor }}@${{ github.sha }}" - git remote add origin "https://${{ github.actor }}:${{ secrets.GITHUB_TOKEN }}@github.com/${{ github.repository }}.git" - git checkout -B gh-pages - - - name: Git commit, force push gh-pages - run: | - git add . - git commit \ - -m "Generated from GitHub "${{ github.workflow }}" Workflow" \ - && git push --force origin gh-pages \ - || echo "Nothing new to commit" diff --git a/.gitignore b/.gitignore index eb2862755..3995ffa13 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,7 @@ wiki/out LATEST_CHANGELOG.md __pycache__ .git-commit-template +.vscode/ +.venv/ +docs/source/api/ +docs/source/commands/ \ No newline at end of file diff --git a/.readthedocs.yaml b/.readthedocs.yaml new file mode 100644 index 000000000..8b4c8f8e5 --- /dev/null +++ b/.readthedocs.yaml @@ -0,0 +1,14 @@ +version: 2 + +build: + os: "ubuntu-22.04" + tools: + python: "3.11" + +sphinx: + builder: html + configuration: docs/source/conf.py + +python: + install: + - requirements: docs/requirements.txt diff --git a/docs/Makefile b/docs/Makefile index 87f9e2860..bc5dc404a 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -1,177 +1,28 @@ -# Makefile for Sphinx documentation +# Minimal makefile for Sphinx documentation # -# You can set these variables from the command line. -SPHINXOPTS = -SPHINXBUILD = sphinx-build -PAPER = +# You can set these variables from the command line, and also +# from the environment for the first two. +SPHINXOPTS ?= -n +SPHINXBUILD ?= sphinx-build +SOURCEDIR = source BUILDDIR = _build -# User-friendly check for sphinx-build -ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) -$(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) -endif - -# Internal variables. -PAPEROPT_a4 = -D latex_paper_size=a4 -PAPEROPT_letter = -D latex_paper_size=letter -ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . -# the i18n builder cannot share the environment and doctrees with the others -I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . - -.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext - +# Put it first so that "make" without argument is like "make help". help: - @echo "Please use \`make ' where is one of" - @echo " html to make standalone HTML files" - @echo " dirhtml to make HTML files named index.html in directories" - @echo " singlehtml to make a single large HTML file" - @echo " pickle to make pickle files" - @echo " json to make JSON files" - @echo " htmlhelp to make HTML files and a HTML help project" - @echo " qthelp to make HTML files and a qthelp project" - @echo " devhelp to make HTML files and a Devhelp project" - @echo " epub to make an epub" - @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" - @echo " latexpdf to make LaTeX files and run them through pdflatex" - @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" - @echo " text to make text files" - @echo " man to make manual pages" - @echo " texinfo to make Texinfo files" - @echo " info to make Texinfo files and run them through makeinfo" - @echo " gettext to make PO message catalogs" - @echo " changes to make an overview of all changed/added/deprecated items" - @echo " xml to make Docutils-native XML files" - @echo " pseudoxml to make pseudoxml-XML files for display purposes" - @echo " linkcheck to check all external links for integrity" - @echo " doctest to run all doctests embedded in the documentation (if enabled)" - -clean: - rm -rf $(BUILDDIR)/* - -html: - $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html - @echo - @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." - -dirhtml: - $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml - @echo - @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." - -singlehtml: - $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml - @echo - @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." - -pickle: - $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle - @echo - @echo "Build finished; now you can process the pickle files." - -json: - $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json - @echo - @echo "Build finished; now you can process the JSON files." - -htmlhelp: - $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp - @echo - @echo "Build finished; now you can run HTML Help Workshop with the" \ - ".hhp project file in $(BUILDDIR)/htmlhelp." - -qthelp: - $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp - @echo - @echo "Build finished; now you can run "qcollectiongenerator" with the" \ - ".qhcp project file in $(BUILDDIR)/qthelp, like this:" - @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/Rez.qhcp" - @echo "To view the help file:" - @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/Rez.qhc" + @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) -devhelp: - $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp - @echo - @echo "Build finished." - @echo "To view the help file:" - @echo "# mkdir -p $$HOME/.local/share/devhelp/Rez" - @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/Rez" - @echo "# devhelp" +.PHONY: help clean livehtml Makefile -epub: - $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub - @echo - @echo "Build finished. The epub file is in $(BUILDDIR)/epub." +# Catch-all target: route all unknown targets to Sphinx using the new +# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). +%: Makefile + $(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) -latex: - $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex - @echo - @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." - @echo "Run \`make' in that directory to run these through (pdf)latex" \ - "(use \`make latexpdf' here to do that automatically)." +livehtml: + sphinx-autobuild "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) -latexpdf: - $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex - @echo "Running LaTeX files through pdflatex..." - $(MAKE) -C $(BUILDDIR)/latex all-pdf - @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." - -latexpdfja: - $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex - @echo "Running LaTeX files through platex and dvipdfmx..." - $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja - @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." - -text: - $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text - @echo - @echo "Build finished. The text files are in $(BUILDDIR)/text." - -man: - $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man - @echo - @echo "Build finished. The manual pages are in $(BUILDDIR)/man." - -texinfo: - $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo - @echo - @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." - @echo "Run \`make' in that directory to run these through makeinfo" \ - "(use \`make info' here to do that automatically)." - -info: - $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo - @echo "Running Texinfo files through makeinfo..." - make -C $(BUILDDIR)/texinfo info - @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." - -gettext: - $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale - @echo - @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." - -changes: - $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes - @echo - @echo "The overview file is in $(BUILDDIR)/changes." - -linkcheck: - $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck - @echo - @echo "Link check complete; look for any errors in the above output " \ - "or in $(BUILDDIR)/linkcheck/output.txt." - -doctest: - $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest - @echo "Testing of doctests in the sources finished, look at the " \ - "results in $(BUILDDIR)/doctest/output.txt." - -xml: - $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml - @echo - @echo "Build finished. The XML files are in $(BUILDDIR)/xml." - -pseudoxml: - $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml - @echo - @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." +clean: + rm -rf _build + rm -rf source/api + rm -rf source/commands diff --git a/docs/NOTES.md b/docs/NOTES.md new file mode 100644 index 000000000..357efc0da --- /dev/null +++ b/docs/NOTES.md @@ -0,0 +1,15 @@ +============ +page section +============ + +Section 1 +========= + +Section 1.1 +----------- + +Section 1.1.1 ++++++++++++++ + +Section 1.1.1.1 +*************** diff --git a/docs/api/modules.rst b/docs/api/modules.rst deleted file mode 100644 index 332d86474..000000000 --- a/docs/api/modules.rst +++ /dev/null @@ -1,8 +0,0 @@ -API Documentation -================= - -.. toctree:: - :maxdepth: 4 - - rez - rezplugins diff --git a/docs/api/rez._sys.rst b/docs/api/rez._sys.rst deleted file mode 100644 index 10cd7dd14..000000000 --- a/docs/api/rez._sys.rst +++ /dev/null @@ -1,22 +0,0 @@ -rez._sys package -================ - -Submodules ----------- - -rez._sys._setup module ----------------------- - -.. automodule:: rez._sys._setup - :members: - :undoc-members: - :show-inheritance: - - -Module contents ---------------- - -.. automodule:: rez._sys - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/api/rez.bind.rst b/docs/api/rez.bind.rst deleted file mode 100644 index cf1aec9f6..000000000 --- a/docs/api/rez.bind.rst +++ /dev/null @@ -1,62 +0,0 @@ -rez.bind package -================ - -Submodules ----------- - -rez.bind.arch module --------------------- - -.. automodule:: rez.bind.arch - :members: - :undoc-members: - :show-inheritance: - -rez.bind.cmake module ---------------------- - -.. automodule:: rez.bind.cmake - :members: - :undoc-members: - :show-inheritance: - -rez.bind.hello_world module ---------------------------- - -.. automodule:: rez.bind.hello_world - :members: - :undoc-members: - :show-inheritance: - -rez.bind.os module ------------------- - -.. automodule:: rez.bind.os - :members: - :undoc-members: - :show-inheritance: - -rez.bind.platform module ------------------------- - -.. automodule:: rez.bind.platform - :members: - :undoc-members: - :show-inheritance: - -rez.bind.python module ----------------------- - -.. automodule:: rez.bind.python - :members: - :undoc-members: - :show-inheritance: - - -Module contents ---------------- - -.. automodule:: rez.bind - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/api/rez.cli.rst b/docs/api/rez.cli.rst deleted file mode 100644 index 486c8878c..000000000 --- a/docs/api/rez.cli.rst +++ /dev/null @@ -1,134 +0,0 @@ -rez.cli package -=============== - -Submodules ----------- - -rez.cli._bez module -------------------- - -.. automodule:: rez.cli._bez - :members: - :undoc-members: - :show-inheritance: - -rez.cli._main module --------------------- - -.. automodule:: rez.cli._main - :members: - :undoc-members: - :show-inheritance: - -rez.cli._util module --------------------- - -.. automodule:: rez.cli._util - :members: - :undoc-members: - :show-inheritance: - -rez.cli.bind module -------------------- - -.. automodule:: rez.cli.bind - :members: - :undoc-members: - :show-inheritance: - -rez.cli.bootstrap module ------------------------- - -.. automodule:: rez.cli.bootstrap - :members: - :undoc-members: - :show-inheritance: - -rez.cli.build module --------------------- - -.. automodule:: rez.cli.build - :members: - :undoc-members: - :show-inheritance: - -rez.cli.context module ----------------------- - -.. automodule:: rez.cli.context - :members: - :undoc-members: - :show-inheritance: - -rez.cli.env module ------------------- - -.. automodule:: rez.cli.env - :members: - :undoc-members: - :show-inheritance: - -rez.cli.forward module ----------------------- - -.. automodule:: rez.cli.forward - :members: - :undoc-members: - :show-inheritance: - -rez.cli.interpret module ------------------------- - -.. automodule:: rez.cli.interpret - :members: - :undoc-members: - :show-inheritance: - -rez.cli.release module ----------------------- - -.. automodule:: rez.cli.release - :members: - :undoc-members: - :show-inheritance: - -rez.cli.settings module ------------------------ - -.. automodule:: rez.cli.settings - :members: - :undoc-members: - :show-inheritance: - -rez.cli.suite module --------------------- - -.. automodule:: rez.cli.suite - :members: - :undoc-members: - :show-inheritance: - -rez.cli.test module -------------------- - -.. automodule:: rez.cli.test - :members: - :undoc-members: - :show-inheritance: - -rez.cli.tools module --------------------- - -.. automodule:: rez.cli.tools - :members: - :undoc-members: - :show-inheritance: - - -Module contents ---------------- - -.. automodule:: rez.cli - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/api/rez.rst b/docs/api/rez.rst deleted file mode 100644 index 533c30461..000000000 --- a/docs/api/rez.rst +++ /dev/null @@ -1,256 +0,0 @@ -Rez -=== - -Subpackages ------------ - -.. toctree:: - - rez._sys - rez.bind - rez.cli - rez.tests - -Submodules ----------- - -rez.bind_utils module ---------------------- - -.. automodule:: rez.bind_utils - :members: - :undoc-members: - :show-inheritance: - -rez.bootstrap module --------------------- - -.. automodule:: rez.bootstrap - :members: - :undoc-members: - :show-inheritance: - -rez.build_process module ------------------------- - -.. automodule:: rez.build_process - :members: - :undoc-members: - :show-inheritance: - -rez.build_system module ------------------------ - -.. automodule:: rez.build_system - :members: - :undoc-members: - :show-inheritance: - -rez.build_utils module ----------------------- - -.. automodule:: rez.build_utils - :members: - :undoc-members: - :show-inheritance: - -rez.dot module --------------- - -.. automodule:: rez.dot - :members: - :undoc-members: - :show-inheritance: - -rez.env module --------------- - -.. automodule:: rez.env - :members: - :undoc-members: - :show-inheritance: - -rez.exceptions module ---------------------- - -.. automodule:: rez.exceptions - :members: - :undoc-members: - :show-inheritance: - -rez.formulae_manager module ---------------------------- - -.. automodule:: rez.formulae_manager - :members: - :undoc-members: - :show-inheritance: - -rez.package_maker module ------------------------- - -.. automodule:: rez.package_maker - :members: - :undoc-members: - :show-inheritance: - -rez.package_maker_ module -------------------------- - -.. automodule:: rez.package_maker_ - :members: - :undoc-members: - :show-inheritance: - -rez.packages module -------------------- - -.. automodule:: rez.packages - :members: - :undoc-members: - :show-inheritance: - -rez.platform_ module --------------------- - -.. automodule:: rez.platform_ - :members: - :undoc-members: - :show-inheritance: - -rez.plugin_managers module --------------------------- - -.. automodule:: rez.plugin_managers - :members: - :undoc-members: - :show-inheritance: - -rez.py_dist module ------------------- - -.. automodule:: rez.py_dist - :members: - :undoc-members: - :show-inheritance: - -rez.release_hook module ------------------------ - -.. automodule:: rez.release_hook - :members: - :undoc-members: - :show-inheritance: - -rez.release_vcs module ----------------------- - -.. automodule:: rez.release_vcs - :members: - :undoc-members: - :show-inheritance: - -rez.resolved_context module ---------------------------- - -.. automodule:: rez.resolved_context - :members: - :undoc-members: - :show-inheritance: - -rez.resolver module -------------------- - -.. automodule:: rez.resolver - :members: - :undoc-members: - :show-inheritance: - -rez.resources module --------------------- - -.. automodule:: rez.resources - :members: - :undoc-members: - :show-inheritance: - -rez.rex module --------------- - -.. automodule:: rez.rex - :members: - :undoc-members: - :show-inheritance: - -rez.rex_bindings module ------------------------ - -.. automodule:: rez.rex_bindings - :members: - :undoc-members: - :show-inheritance: - -rez.settings module -------------------- - -.. automodule:: rez.settings - :members: - :undoc-members: - :show-inheritance: - -rez.shells module ------------------ - -.. automodule:: rez.shells - :members: - :undoc-members: - :show-inheritance: - -rez.sigint module ------------------ - -.. automodule:: rez.sigint - :members: - :undoc-members: - :show-inheritance: - -rez.solver module ------------------ - -.. automodule:: rez.solver - :members: - :undoc-members: - :show-inheritance: - -rez.source_retrieval module ---------------------------- - -.. automodule:: rez.source_retrieval - :members: - :undoc-members: - :show-inheritance: - -rez.system module ------------------ - -.. automodule:: rez.system - :members: - :undoc-members: - :show-inheritance: - -rez.util module ---------------- - -.. automodule:: rez.util - :members: - :undoc-members: - :show-inheritance: - - -Module contents ---------------- - -.. automodule:: rez - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/api/rez.tests.rst b/docs/api/rez.tests.rst deleted file mode 100644 index c191f9729..000000000 --- a/docs/api/rez.tests.rst +++ /dev/null @@ -1,78 +0,0 @@ -rez.tests package -================= - -Submodules ----------- - -rez.tests.test_build module ---------------------------- - -.. automodule:: rez.tests.test_build - :members: - :undoc-members: - :show-inheritance: - -rez.tests.test_commands module ------------------------------- - -.. automodule:: rez.tests.test_commands - :members: - :undoc-members: - :show-inheritance: - -rez.tests.test_context module ------------------------------ - -.. automodule:: rez.tests.test_context - :members: - :undoc-members: - :show-inheritance: - -rez.tests.test_formatter module -------------------------------- - -.. automodule:: rez.tests.test_formatter - :members: - :undoc-members: - :show-inheritance: - -rez.tests.test_rex module -------------------------- - -.. automodule:: rez.tests.test_rex - :members: - :undoc-members: - :show-inheritance: - -rez.tests.test_shells module ----------------------------- - -.. automodule:: rez.tests.test_shells - :members: - :undoc-members: - :show-inheritance: - -rez.tests.test_solver module ----------------------------- - -.. automodule:: rez.tests.test_solver - :members: - :undoc-members: - :show-inheritance: - -rez.tests.util module ---------------------- - -.. automodule:: rez.tests.util - :members: - :undoc-members: - :show-inheritance: - - -Module contents ---------------- - -.. automodule:: rez.tests - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/api/rezplugins.build_system.rst b/docs/api/rezplugins.build_system.rst deleted file mode 100644 index 995ab9ee4..000000000 --- a/docs/api/rezplugins.build_system.rst +++ /dev/null @@ -1,38 +0,0 @@ -rezplugins.build_system package -=============================== - -Submodules ----------- - -rezplugins.build_system.bez module ----------------------------------- - -.. automodule:: rezplugins.build_system.bez - :members: - :undoc-members: - :show-inheritance: - -rezplugins.build_system.cmake module ------------------------------------- - -.. automodule:: rezplugins.build_system.cmake - :members: - :undoc-members: - :show-inheritance: - -rezplugins.build_system.make module ------------------------------------ - -.. automodule:: rezplugins.build_system.make - :members: - :undoc-members: - :show-inheritance: - - -Module contents ---------------- - -.. automodule:: rezplugins.build_system - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/api/rezplugins.release_hook.rst b/docs/api/rezplugins.release_hook.rst deleted file mode 100644 index cfb6bae97..000000000 --- a/docs/api/rezplugins.release_hook.rst +++ /dev/null @@ -1,22 +0,0 @@ -rezplugins.release_hook package -=============================== - -Submodules ----------- - -rezplugins.release_hook.emailer module --------------------------------------- - -.. automodule:: rezplugins.release_hook.emailer - :members: - :undoc-members: - :show-inheritance: - - -Module contents ---------------- - -.. automodule:: rezplugins.release_hook - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/api/rezplugins.release_vcs.rst b/docs/api/rezplugins.release_vcs.rst deleted file mode 100644 index 387e0b5f2..000000000 --- a/docs/api/rezplugins.release_vcs.rst +++ /dev/null @@ -1,38 +0,0 @@ -rezplugins.release_vcs package -============================== - -Submodules ----------- - -rezplugins.release_vcs.git module ---------------------------------- - -.. automodule:: rezplugins.release_vcs.git - :members: - :undoc-members: - :show-inheritance: - -rezplugins.release_vcs.hg module --------------------------------- - -.. automodule:: rezplugins.release_vcs.hg - :members: - :undoc-members: - :show-inheritance: - -rezplugins.release_vcs.svn module ---------------------------------- - -.. automodule:: rezplugins.release_vcs.svn - :members: - :undoc-members: - :show-inheritance: - - -Module contents ---------------- - -.. automodule:: rezplugins.release_vcs - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/api/rezplugins.rst b/docs/api/rezplugins.rst deleted file mode 100644 index 9ed107155..000000000 --- a/docs/api/rezplugins.rst +++ /dev/null @@ -1,21 +0,0 @@ -Rez Plugins -=========== - -Subpackages ------------ - -.. toctree:: - - rezplugins.build_system - rezplugins.release_hook - rezplugins.release_vcs - rezplugins.shell - rezplugins.source_retriever - -Module contents ---------------- - -.. automodule:: rezplugins - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/api/rezplugins.shell.rst b/docs/api/rezplugins.shell.rst deleted file mode 100644 index afc5ecf4e..000000000 --- a/docs/api/rezplugins.shell.rst +++ /dev/null @@ -1,54 +0,0 @@ -rezplugins.shell package -======================== - -Submodules ----------- - -rezplugins.shell.bash module ----------------------------- - -.. automodule:: rezplugins.shell.bash - :members: - :undoc-members: - :show-inheritance: - -rezplugins.shell.csh module ---------------------------- - -.. automodule:: rezplugins.shell.csh - :members: - :undoc-members: - :show-inheritance: - -rezplugins.shell.sh module --------------------------- - -.. automodule:: rezplugins.shell.sh - :members: - :undoc-members: - :show-inheritance: - -rezplugins.shell.tcsh module ----------------------------- - -.. automodule:: rezplugins.shell.tcsh - :members: - :undoc-members: - :show-inheritance: - -rezplugins.shell.windows module -------------------------------- - -.. automodule:: rezplugins.shell.windows - :members: - :undoc-members: - :show-inheritance: - - -Module contents ---------------- - -.. automodule:: rezplugins.shell - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/api/rezplugins.source_retriever.rst b/docs/api/rezplugins.source_retriever.rst deleted file mode 100644 index f570aa200..000000000 --- a/docs/api/rezplugins.source_retriever.rst +++ /dev/null @@ -1,38 +0,0 @@ -rezplugins.source_retriever package -=================================== - -Submodules ----------- - -rezplugins.source_retriever.archive module ------------------------------------------- - -.. automodule:: rezplugins.source_retriever.archive - :members: - :undoc-members: - :show-inheritance: - -rezplugins.source_retriever.git module --------------------------------------- - -.. automodule:: rezplugins.source_retriever.git - :members: - :undoc-members: - :show-inheritance: - -rezplugins.source_retriever.hg module -------------------------------------- - -.. automodule:: rezplugins.source_retriever.hg - :members: - :undoc-members: - :show-inheritance: - - -Module contents ---------------- - -.. automodule:: rezplugins.source_retriever - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/build.py b/docs/build.py deleted file mode 100644 index 650384e23..000000000 --- a/docs/build.py +++ /dev/null @@ -1,173 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function -from __future__ import unicode_literals - - -import argparse -import errno -import os -import re -import subprocess -import tempfile - - -THIS_FILE = os.path.abspath(__file__) -THIS_DIR = os.path.dirname(THIS_FILE) -REZ_SOURCE_DIR = os.getenv("REZ_SOURCE_DIR", os.path.dirname(THIS_DIR)) -REQUIREMENTS = ['sphinx_rtd_theme', REZ_SOURCE_DIR] -DEST_DIR = os.path.join("docs", "_build") -PIP_PATH_REGEX = re.compile(r"'([^']+)' which is not on PATH.") - - -class CliParser(argparse.ArgumentParser): - """Parser flags, using global variables as defaults.""" - INIT_DEFAULTS = { - "prog": "build", - "description": "Build Sphinx Python API docs", - } - - def __init__(self, **kwargs): - """Setup default arguments and parser description/program name. - - If no parser description/program name are given, default ones will - be assigned. - - Args: - kwargs (dict[str]): - Same key word arguments taken by - ``argparse.ArgumentParser.__init__()`` - """ - for key, value in self.INIT_DEFAULTS.items(): - kwargs.setdefault(key, value) - super(CliParser, self).__init__(**kwargs) - - self.add_argument( - "--no-docker", - action="store_false", - dest="docker", - help="Don't run build processes inside Docker container.", - ) - self.add_argument( - "requirement", - nargs="*", - help="Additional packages to pip install.", - ) - - -def construct_docker_run_args(): - """Create subprocess arguments list for running this script inside docker. - - Returns: - list[str]: Arguments list for ``subprocess.call()``. - """ - docker_args = ["docker", "run", "--interactive", "--rm"] - - if os.sys.stdin.isatty() and os.sys.stdout.isatty(): - docker_args.append("--tty") - - if os.name == "posix": - user_group_ids = os.getuid(), os.getgid() - docker_args += ["--user", ":".join(map(str, user_group_ids))] - - docker_args += [ - "--workdir", REZ_SOURCE_DIR, - "--volume", ":".join([REZ_SOURCE_DIR, REZ_SOURCE_DIR]), - "python:{v.major}.{v.minor}".format(v=os.sys.version_info), - "python", THIS_FILE, "--no-docker" - ] - - return docker_args - - -def print_call(cmdline_args, *print_args, **print_kwargs): - """Print command line call for given arguments. - - - Args: - cmdline_args (list): Command line arguments to print for. - print_args (dict): Additional arguments for print function. - print_kwargs (dict): Keyword arguments for print function. - """ - width = os.getenv('COLUMNS', 80) - out_file = print_kwargs.setdefault('file', os.sys.stdout) - message = '{:=^{width}}{nl}{}{nl:=<{width}}'.format( - " Calling ", - subprocess.list2cmdline(cmdline_args), - nl=os.linesep, - width=width - ) - print(message, *print_args, **print_kwargs) - out_file.flush() - - -def path_with_pip_scripts(install_stderr, path_env=None): - """Create new PATH variable with missing pip scripts paths added to it. - - Args: - install_stderr (str): stderr output from pip install command. - path_env (str): Custom PATH env value to start off with. - - Returns: - str: New PATH variable value. - """ - if path_env is None: - path_env = os.getenv('PATH', '') - paths = path_env.split(os.pathsep) - - for match in PIP_PATH_REGEX.finditer(install_stderr): - script_path = match.group(1) - if script_path not in paths: - paths.append(script_path) - - return os.pathsep.join(paths) - - -def _cli(): - """Main routine for when called from command line.""" - args = CliParser().parse_args() - - if args.docker: - docker_args = construct_docker_run_args() + args.requirement - print_call(docker_args) - os.sys.exit(subprocess.call(docker_args)) - else: - docs_env = os.environ.copy() - - # Fake user's $HOME in container to fix permission issues - if os.name == "posix" and os.path.expanduser("~") == "/": - docs_env['HOME'] = tempfile.mkdtemp() - - # Run pip install for required docs building packages - pip_args = ['pip', 'install', '--user'] - pip_args += REQUIREMENTS + args.requirement - with tempfile.TemporaryFile() as stderr_file: - subprocess.check_call(pip_args, env=docs_env, stderr=stderr_file) - stderr_file.seek(0) - stderr = str(stderr_file.read()) - docs_env['PATH'] = path_with_pip_scripts(stderr) - - # Run sphinx-build docs, falling back to use sphinx-build.exe - sphinx_build = 'sphinx-build' - build_args = ['docs', DEST_DIR] - sphinx_build_args = [sphinx_build] + build_args - try: - print_call(sphinx_build_args) - os.sys.exit(subprocess.call(sphinx_build_args, env=docs_env)) - except OSError as error: - if error.errno == errno.ENOENT: - # Windows Py2.7 needs full .exe path, see GitHub workflows run: - # https://github.com/wwfxuk/rez/runs/380399547 - latest_path = docs_env['PATH'].split(os.pathsep)[-1] - sphinx_build = os.path.join(latest_path, sphinx_build + '.exe') - - sphinx_build_args = [sphinx_build] + build_args - print_call(sphinx_build_args) - os.sys.exit(subprocess.call(sphinx_build_args, env=docs_env)) - else: - raise - - -if __name__ == "__main__": - _cli() diff --git a/docs/conf.py b/docs/conf.py deleted file mode 100644 index 17a0bb1c4..000000000 --- a/docs/conf.py +++ /dev/null @@ -1,274 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Rez documentation build configuration file, created by -# sphinx-quickstart on Mon Jun 30 11:14:36 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. -# -# All configuration values have a default; values that are commented out -# serve to show the default. - -import sys -import os - -# If extensions (or modules to document with autodoc) are in another directory, -# add these directories to sys.path here. If the directory is relative to the -# documentation root, use os.path.abspath to make it absolute, like shown here. - -sys.path.insert(0, os.path.abspath('../src')) - -import rez - -# -- General configuration ------------------------------------------------ - -# If your documentation needs a minimal Sphinx version, state it here. -needs_sphinx = '1.3' - -# Add any Sphinx extension module names here, as strings. They can be -# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom -# ones. -extensions = [ - 'sphinx.ext.autodoc', - 'sphinx.ext.intersphinx', - 'sphinx.ext.todo', - 'sphinx.ext.coverage', - 'sphinx.ext.ifconfig', - 'sphinx.ext.viewcode', - 'sphinx.ext.napoleon', - 'sphinx.ext.githubpages', -] - -# 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'Rez' -copyright = u'2014, Allan Johns' - -# 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(rez.__version__.split('.')[:2]) -# The full version, including alpha/beta/rc tags. -release = rez.__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', 'Thumbs.db', '.DS_Store'] - -# 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 = None - -# 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. -html_theme = 'sphinx_rtd_theme' - -# 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 = 'Rezdoc' - - -# -- 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': '', -} - -# 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', 'Rez.tex', u'Rez Documentation', - u'Allan Johns', 'manual'), -] - -# 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', 'rez', u'Rez Documentation', - [u'Allan Johns'], 1) -] - -# If true, show URL addresses after external links. -#man_show_urls = False - - -# -- Options for Texinfo output ------------------------------------------- - -# 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', 'Rez', u'Rez Documentation', - u'Allan Johns', 'Rez', 'One line description of project.', - 'Miscellaneous'), -] - -# Documents to append as an appendix to all manuals. -#texinfo_appendices = [] - -# If false, no module index is generated. -#texinfo_domain_indices = True - -# How to display URL addresses: 'footnote', 'no', or 'inline'. -#texinfo_show_urls = 'footnote' - -# If true, do not generate a @detailmenu in the "Top" node's menu. -#texinfo_no_detailmenu = False - - -# Example configuration for intersphinx: refer to the Python standard library. -intersphinx_mapping = {'http://docs.python.org/': None} diff --git a/docs/index.rst b/docs/index.rst deleted file mode 100644 index 0fd843a3f..000000000 --- a/docs/index.rst +++ /dev/null @@ -1,17 +0,0 @@ -REZ API Reference -================= - -.. toctree:: - :maxdepth: 2 - :numbered: - - api/modules - one-liners - - -Indices and tables -================== - -* :ref:`genindex` -* :ref:`modindex` -* :ref:`search` diff --git a/docs/make.bat b/docs/make.bat index 21d8ea40c..826f02ab2 100644 --- a/docs/make.bat +++ b/docs/make.bat @@ -1,53 +1,16 @@ @ECHO OFF +pushd %~dp0 + REM Command file for Sphinx documentation if "%SPHINXBUILD%" == "" ( set SPHINXBUILD=sphinx-build ) -set BUILDDIR=_build -set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . -set I18NSPHINXOPTS=%SPHINXOPTS% . -if NOT "%PAPER%" == "" ( - set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% - set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% -) - -if "%1" == "" goto help - -if "%1" == "help" ( - :help - echo.Please use `make ^` where ^ is one of - echo. html to make standalone HTML files - echo. dirhtml to make HTML files named index.html in directories - echo. singlehtml to make a single large HTML file - echo. pickle to make pickle files - echo. json to make JSON files - echo. htmlhelp to make HTML files and a HTML help project - echo. qthelp to make HTML files and a qthelp project - echo. devhelp to make HTML files and a Devhelp project - echo. epub to make an epub - echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter - echo. text to make text files - echo. man to make manual pages - echo. texinfo to make Texinfo files - echo. gettext to make PO message catalogs - echo. changes to make an overview over all changed/added/deprecated items - echo. xml to make Docutils-native XML files - echo. pseudoxml to make pseudoxml-XML files for display purposes - echo. linkcheck to check all external links for integrity - echo. doctest to run all doctests embedded in the documentation if enabled - goto end -) - -if "%1" == "clean" ( - for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i - del /q /s %BUILDDIR%\* - goto end -) - +set SOURCEDIR=source +set BUILDDIR=build -%SPHINXBUILD% 2> nul +%SPHINXBUILD% >NUL 2>NUL if errorlevel 9009 ( echo. echo.The 'sphinx-build' command was not found. Make sure you have Sphinx @@ -56,187 +19,17 @@ if errorlevel 9009 ( echo.may add the Sphinx directory to PATH. echo. echo.If you don't have Sphinx installed, grab it from - echo.http://sphinx-doc.org/ + echo.https://www.sphinx-doc.org/ exit /b 1 ) -if "%1" == "html" ( - %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The HTML pages are in %BUILDDIR%/html. - goto end -) - -if "%1" == "dirhtml" ( - %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. - goto end -) - -if "%1" == "singlehtml" ( - %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. - goto end -) - -if "%1" == "pickle" ( - %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; now you can process the pickle files. - goto end -) - -if "%1" == "json" ( - %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; now you can process the JSON files. - goto end -) - -if "%1" == "htmlhelp" ( - %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; now you can run HTML Help Workshop with the ^ -.hhp project file in %BUILDDIR%/htmlhelp. - goto end -) - -if "%1" == "qthelp" ( - %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; now you can run "qcollectiongenerator" with the ^ -.qhcp project file in %BUILDDIR%/qthelp, like this: - echo.^> qcollectiongenerator %BUILDDIR%\qthelp\Rez.qhcp - echo.To view the help file: - echo.^> assistant -collectionFile %BUILDDIR%\qthelp\Rez.ghc - goto end -) - -if "%1" == "devhelp" ( - %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. - goto end -) - -if "%1" == "epub" ( - %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The epub file is in %BUILDDIR%/epub. - goto end -) - -if "%1" == "latex" ( - %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. - goto end -) - -if "%1" == "latexpdf" ( - %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex - cd %BUILDDIR%/latex - make all-pdf - cd %BUILDDIR%/.. - echo. - echo.Build finished; the PDF files are in %BUILDDIR%/latex. - goto end -) - -if "%1" == "latexpdfja" ( - %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex - cd %BUILDDIR%/latex - make all-pdf-ja - cd %BUILDDIR%/.. - echo. - echo.Build finished; the PDF files are in %BUILDDIR%/latex. - goto end -) - -if "%1" == "text" ( - %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The text files are in %BUILDDIR%/text. - goto end -) - -if "%1" == "man" ( - %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The manual pages are in %BUILDDIR%/man. - goto end -) - -if "%1" == "texinfo" ( - %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. - goto end -) - -if "%1" == "gettext" ( - %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The message catalogs are in %BUILDDIR%/locale. - goto end -) - -if "%1" == "changes" ( - %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes - if errorlevel 1 exit /b 1 - echo. - echo.The overview file is in %BUILDDIR%/changes. - goto end -) - -if "%1" == "linkcheck" ( - %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck - if errorlevel 1 exit /b 1 - echo. - echo.Link check complete; look for any errors in the above output ^ -or in %BUILDDIR%/linkcheck/output.txt. - goto end -) - -if "%1" == "doctest" ( - %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest - if errorlevel 1 exit /b 1 - echo. - echo.Testing of doctests in the sources finished, look at the ^ -results in %BUILDDIR%/doctest/output.txt. - goto end -) +if "%1" == "" goto help -if "%1" == "xml" ( - %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The XML files are in %BUILDDIR%/xml. - goto end -) +%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% -n +goto end -if "%1" == "pseudoxml" ( - %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml. - goto end -) +:help +%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% :end +popd diff --git a/docs/one-liners.rst b/docs/one-liners.rst deleted file mode 100644 index 8f4c8edc4..000000000 --- a/docs/one-liners.rst +++ /dev/null @@ -1,71 +0,0 @@ -One-Liners -========== - -A list of useful one-liners for rez-config and related tools - -Display info about the package foo: - -:: - - rez-info foo - -List the packages that foo depends on: - -:: - - rez-config --print-packages foo - -Jump into an environment containing foo-5(.x.x.x...): - -:: - - rez-env foo-5 - -Run a command inside a configured shell - -:: - - rez-run foo-5 bah-1.2 -- my-command - -Show the resolve dot-graph for a given shell: - -:: - - rez-run foo-5 bah-1.2 fee -- rez-context-image - -Display a dot-graph showing the first failed attempt of the given configuration PKGS: - -:: - - rez-config --max-fails=0 --dot-file=/tmp/dot.jpg PKGS ; firefox /tmp/dot.jpg - -Show a dot-graph of all the packages dependent on foo: - -:: - - rez-depends show-dot foo - -List every package in the system, and the description of each - -:: - - rez-config-list --desc - -Show the resolve dot-graph for a given shell, but just show that part of the graph that -contains packages dependent (directly or indirectly) on fee: - -:: - - rez-run foo-5 bah-1.2 fee -- rez-context-image --package=fee - -Run a command inside a toolchain wrapper: -:: - - rez-run mytoolchain -- sometool -- some-command - -Jump into a toolchain, and then into a wrapper's env: - -:: - - rez-run mytoolchain - sometool ---i diff --git a/docs/requirements.txt b/docs/requirements.txt new file mode 100644 index 000000000..470e82a19 --- /dev/null +++ b/docs/requirements.txt @@ -0,0 +1,3 @@ +sphinx +furo +sphinx-autobuild diff --git a/docs/rez_sphinxext.py b/docs/rez_sphinxext.py new file mode 100644 index 000000000..f5dce23ec --- /dev/null +++ b/docs/rez_sphinxext.py @@ -0,0 +1,349 @@ +import os +import re +import argparse + +import rez.cli._main +import rez.cli._util +import rez.rezconfig +import docutils.nodes +import sphinx.util.nodes +import sphinx.application +import sphinx.environment +import sphinx.util.logging +import sphinx.util.docutils +import docutils.statemachine + +_LOG = sphinx.util.logging.getLogger(f"ext.{__name__.split('.')[-1]}") + + +def convert_rez_config_to_rst() -> list[str]: + with open(rez.rezconfig.__file__) as fd: + txt = fd.read() + + lines = txt.split('\n') + + start = None + end = None + for i, line in enumerate(lines): + if "__DOC_START__" in line: + start = i + elif "__DOC_END__" in line: + end = i + + lines = lines[start:end + 1] + assign_regex = re.compile("^([a-z0-9_]+) =") + settings = {} + + section_header = re.compile(r"^\#{10,}") + + section_title = None + section_description = None + end_of_section = 0 + + # parse out settings sections, settings and their comment + for i, line in enumerate(lines): + + if section_header.match(line) and i != end_of_section: + section_title = lines[i + 1].split('#', 1)[-1].strip() + section_description = '' + description_linenumber = i + 2 + end_of_section = description_linenumber + + while not section_header.match(lines[description_linenumber]): + section_description += lines[description_linenumber].split('#', 1)[-1].strip() + '\n' + description_linenumber += 1 + end_of_section = description_linenumber + + m = assign_regex.match(line) + if not m: + continue + + start_defn = i + end_defn = i + while lines[end_defn].strip() and not lines[end_defn].startswith('#'): + end_defn += 1 + + value_lines = lines[start_defn:end_defn] + value_lines[0] = value_lines[0].split("=")[-1].strip() + + end_comment = i + while not lines[end_comment].startswith('#'): + end_comment -= 1 + + start_comment = end_comment + while lines[start_comment].startswith('#'): + start_comment -= 1 + start_comment += 1 + + comments = lines[start_comment:end_comment + 1] + comment_lines = [x[2:] for x in comments] # drop leading '# ' + + varname = m.groups()[0] + if section_title in settings: + settings[section_title]['settings'][varname] = (value_lines, comment_lines) + else: + settings[section_title] = { + 'desc': section_description, + 'settings': {varname: (value_lines, comment_lines)} + } + + # generate rst text + # rst = ['.. currentmodule:: config'] + rst = [] + + for section in settings: + rst.append('') + rst.append(section) + rst.append("-" * len(section)) + rst.append('') + + # This seems benign (storing each line individually), but it's actually extremelly + # important. The docutils ViewList class absolutely requires to have only one line per + # entry. If we don't do that, the docutils parser won't parse the sublines, + # and we'll get "garbage" (ie unformatted lines) in the output. + for line in settings[section]['desc'].strip().split('\n'): + rst.append(line) + rst.append('') + + for varname, (value_lines, comment_lines) in sorted(settings[section]['settings'].items()): + rst.append(".. py:data:: {0}".format(varname)) + if len(value_lines) == 1: + rst.append(" :value: {0}".format(value_lines[0])) + else: + rst.append('') + rst.append(' Default:') + rst.append('') + rst.append(' .. code-block:: python') + rst.append('') + for line in value_lines: + rst.append(f' {line}') + + rst.append('') + for line in comment_lines: + rst.append(f' {line}') + rst.append('') + + envvar = f'REZ_{varname.upper()}' + rst.append(f' .. envvar:: {envvar}') + rst.append('') + rst.append(f' The ``{envvar}`` environment variable can also be used to configure this.') + rst.append('') + + return rst + + +# Inspired by https://github.com/pypa/pip/blob/4a79e65cb6aac84505ad92d272a29f0c3c1aedce/docs/pip_sphinxext.py +# https://stackoverflow.com/a/44084890 +class RezConfigDirective(sphinx.util.docutils.SphinxDirective): + """ + Special rez-config directive. This is quite similar to "autodoc" in some ways. + """ + required_arguments = 0 + optional_arguments = 0 + + def run(self) -> list[docutils.nodes.Node]: + # Create the node. + node = docutils.nodes.section() + node.document = self.state.document + + rst = docutils.statemachine.ViewList() + + # Get the configuration settings as reStructuredText text. + configLines = convert_rez_config_to_rst() + + # Add rezconfig as a dependency to the current document. The document + # will be rebuilt if rezconfig changes. + self.env.note_dependency(rez.rezconfig.__file__) + self.env.note_dependency(__file__) + + path, lineNumber = self.get_source_info() + + # Add each line to the view list. + for index, line in enumerate(configLines): + # Note to future people that will look at this. + # "line" has to be a single line! It can't be a line like "this\nthat". + rst.append(line, path, lineNumber+index) + + # Finally, convert the rst into the appropriate docutils/sphinx nodes. + sphinx.util.nodes.nested_parse_with_titles(self.state, rst, node) + + # Return the generated nodes. + return node.children + + +class RezAutoArgparseDirective(sphinx.util.docutils.SphinxDirective): + """ + Special rez-autoargparse directive. This is quite similar to "autosummary" in some ways. + """ + required_arguments = 0 + optional_arguments = 0 + + def run(self) -> list[docutils.nodes.Node]: + # Create the node. + node = docutils.nodes.section() + node.document = self.state.document + + rst = docutils.statemachine.ViewList() + + # Add rezconfig as a dependency to the current document. The document + # will be rebuilt if rezconfig changes. + self.env.note_dependency(rez.cli._util.__file__) + self.env.note_dependency(__file__) + + path, lineNumber = self.get_source_info() + + toc = """.. toctree:: + :maxdepth: 1 + :hidden: + + commands/rez + +""" + listRst = "* :doc:`commands/rez`\n" + + for subcommand, config in rez.cli._util.subcommands.items(): + if config.get('hidden'): + continue + + toc += f" commands/rez-{subcommand}\n" + listRst += f"* :doc:`commands/rez-{subcommand}`\n" + + # Add each line to the view list. + for index, line in enumerate((toc + "\n" + listRst).split("\n")): + # Note to future people that will look at this. + # "line" has to be a single line! It can't be a line like "this\nthat". + rst.append(line, path, lineNumber+index) + + # Finally, convert the rst into the appropriate docutils/sphinx nodes. + sphinx.util.nodes.nested_parse_with_titles(self.state, rst, node) + + # Return the generated nodes. + return node.children + + +# Inspired by autosummary (https://github.com/sphinx-doc/sphinx/blob/fcc38997f1d9b728bb4ffc64fc362c7763a4ee25/sphinx/ext/autosummary/__init__.py#L782) +# and https://github.com/ashb/sphinx-argparse/blob/b2f42564fb03ede94e94c149a425e398764158ca/sphinxarg/parser.py#L49 +def write_cli_documents(app: sphinx.application.Sphinx) -> None: + """ + Write the CLI pages into the "commands" folder. + """ + _LOG.info("[rez-autoargparse] generating command line documents") + + _LOG.info("[rez-autoargparse] seting up the parser") + main_parser = rez.cli._main.setup_parser() + main_parser._setup_all_subparsers() + + parsers = [main_parser] + for action in main_parser._actions: + if isinstance(action, rez.cli._util.LazySubParsersAction): + parsers += action.choices.values() + + for parser in sorted(parsers, key=lambda x: x.prog): + full_cmd = parser.prog.replace(' ', '-') + + # Title + document = [f".. _{full_cmd}:"] + document.append("") + document.append(f"{'='*len(parser.prog)}") + document.append(f"{full_cmd}") + document.append(f"{'='*len(parser.prog)}") + document.append("") + + document.append(f".. program:: {full_cmd}") + document.append("") + document.append("Usage") + document.append("=====") + document.append("") + document.append(".. code-block:: text") + document.append("") + for line in parser.format_usage()[7:].split("\n"): + document.append(f" {line}") + document.append("") + + if parser.description == argparse.SUPPRESS: + continue + + document.append("description") + document.append("===========") + document.extend(parser.description.split("\n")) + + document.append("") + document.append("Options") + document.append("=======") + document.append("") + + for action in parser._action_groups[1]._group_actions: + if isinstance(action, argparse._HelpAction): + continue + + # Quote default values for string/None types + default = action.default + if action.default not in ['', None, True, False] and action.type in [None, str] and isinstance(action.default, str): + default = f'"{default}"' + + # fill in any formatters, like %(default)s + format_dict = dict(vars(action), prog=parser.prog, default=default) + format_dict['default'] = default + help_str = action.help or '' # Ensure we don't print None + try: + help_str = help_str % format_dict + except Exception: + pass + + if help_str == argparse.SUPPRESS: + continue + + # Avoid Sphinx warnings. + help_str = help_str.replace("*", "\\*") + # Replace everything that looks like an argument with an option directive. + help_str = re.sub(r"(?`", help_str) + help_str = help_str.replace("--", "\\--") + + # Options have the option_strings set, positional arguments don't + name = action.option_strings + if name == []: + if action.metavar is None: + name = [action.dest] + else: + name = [action.metavar] + + # Skip lines for subcommands + if name == [argparse.SUPPRESS]: + continue + + metavar = f"<{action.metavar}>" if action.metavar else "" + document.append(f".. option:: {', '.join(name)} {metavar.lower()}") + document.append("") + document.append(f" {help_str}") + if action.choices: + document.append("") + document.append(f" Choices: {', '.join(action.choices)}") + document.append("") + + document = "\n".join(document) + + dest = os.path.join(app.srcdir, "commands", f"{full_cmd}.rst") + os.makedirs(os.path.dirname(dest), exist_ok=True) + + if os.path.exists(dest): + with open(dest, "r") as fd: + if fd.read() == document: + # Documents are the same, skip writing to avoid + # invalidating Sphinx's cache. + continue + + with open(dest, "w") as fd: + fd.write(document) + + +def setup(app: sphinx.application.Sphinx) -> dict[str, bool | str]: + app.setup_extension('sphinx.ext.autodoc') + app.add_directive('rez-config', RezConfigDirective) + + app.connect('builder-inited', write_cli_documents) + app.add_directive('rez-autoargparse', RezAutoArgparseDirective) + + return { + 'parallel_read_safe': True, + 'parallel_write_safe': True, + } diff --git a/docs/source/_static/css/custom.css b/docs/source/_static/css/custom.css new file mode 100644 index 000000000..f43493b13 --- /dev/null +++ b/docs/source/_static/css/custom.css @@ -0,0 +1,11 @@ +/* Enable dark-mode, if requested. */ +body[data-theme=dark] .rez-diagram { + filter: invert(100%) hue-rotate(180deg); +} + +/* Enable dark mode, unless explicitly told to avoid. */ +@media (prefers-color-scheme: dark) { + body:not([data-theme="light"]) .rez-diagram { + filter: invert(100%) hue-rotate(180deg); + } +} \ No newline at end of file diff --git a/docs/source/_static/other_pkg_mgr.png b/docs/source/_static/other_pkg_mgr.png new file mode 100644 index 0000000000000000000000000000000000000000..f3891daafa53f99d688a0d6845d299c1617b0e1c GIT binary patch literal 13425 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 literal 0 HcmV?d00001 diff --git a/docs/source/_static/pkg_path_anatomy.png b/docs/source/_static/pkg_path_anatomy.png new file mode 100644 index 0000000000000000000000000000000000000000..d7b66049564c8c8c2944a3fb84b3e918598e1586 GIT binary patch literal 44007 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 qXNU^yo=*bqKZO6Mf7PZpj_Bh`m$@poj2-{ \ No newline at end of file diff --git a/docs/source/_static/rez-horizontal-white.svg b/docs/source/_static/rez-horizontal-white.svg new file mode 100644 index 000000000..a3c06f3de --- /dev/null +++ b/docs/source/_static/rez-horizontal-white.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/docs/source/_static/rez_deps_simple_eg.png b/docs/source/_static/rez_deps_simple_eg.png new file mode 100644 index 0000000000000000000000000000000000000000..59e2ba485af78b7432d8bf38273652d9572d10ed GIT binary patch literal 5589 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 literal 0 HcmV?d00001 diff --git a/docs/source/_static/rez_env.png b/docs/source/_static/rez_env.png new file mode 100644 index 0000000000000000000000000000000000000000..ef6e19c0d61974d86b7f6fba6d0a760c4c6cd23c GIT binary patch literal 118567 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 literal 0 HcmV?d00001 diff --git a/docs/source/_static/rez_pkg_mgr.png b/docs/source/_static/rez_pkg_mgr.png new file mode 100644 index 0000000000000000000000000000000000000000..87e1b162df4e1b68de4617aa2726aba5cfb58166 GIT binary patch literal 40820 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 oa@ZmyfIm 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

fsYKD^q|V=d`3jH!lO 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 s@2r2YrUOd?jOBc*{C_^)KGkN^Mx literal 0 HcmV?d00001 diff --git a/docs/source/_templates/autosummary/module.rst b/docs/source/_templates/autosummary/module.rst new file mode 100644 index 000000000..7792d1f71 --- /dev/null +++ b/docs/source/_templates/autosummary/module.rst @@ -0,0 +1,18 @@ +{{ fullname }} +{{ underline }} + +.. automodule:: {{ fullname }} + :members: + +{% block modules %} +{% if modules %} +.. rubric:: Modules + +.. autosummary:: + :toctree: + :recursive: +{% for item in modules %} + {{ item }} +{%- endfor %} +{% endif %} +{% endblock %} diff --git a/docs/source/api.rst b/docs/source/api.rst new file mode 100644 index 000000000..ad4c76fca --- /dev/null +++ b/docs/source/api.rst @@ -0,0 +1,47 @@ +========== +Python API +========== + +.. autosummary:: + :toctree: api + :recursive: + + rez.build_process + rez.build_system + rez.bundle_context + rez.command + rez.config + rez.developer_package + rez.exceptions + rez.package_cache + rez.package_copy + rez.package_filter + rez.package_help + rez.package_maker + rez.package_move + rez.package_order + rez.package_py_utils + rez.package_remove + rez.package_repository + rez.package_resources + rez.package_search + rez.package_serialise + rez.package_test + rez.packages + rez.plugin_managers + rez.release_hook + rez.release_vcs + rez.resolved_context + rez.resolver + rez.rex_bindings + rez.rex + rez.serialise + rez.shells + rez.solver + rez.status + rez.suite + rez.system + rez.util + rez.utils + rez.vendor.version + rez.wrapper diff --git a/docs/source/basic_concepts.rst b/docs/source/basic_concepts.rst new file mode 100644 index 000000000..34d2d01fd --- /dev/null +++ b/docs/source/basic_concepts.rst @@ -0,0 +1,435 @@ +============== +Basic concepts +============== + +Rez manages packages. You request a list of packages from rez, and it resolves this request, if +possible. If the resolution is not possible, the system supplies you with the relevant information +to determine why this is so. You typically want to resolve a list of packages because you want to +create an environment in which you can use them in combination, without conflicts occurring. A +conflict occurs when there is a request for two or more different versions of the same package. + +Rez lets you describe the environment you want in a natural way. For example, you can say: +"I want an environment with...": + +* the latest version of houdini +* maya-2009.1 +* the latest rv and the latest maya and houdini-11.something +* rv-3.something or greater +* the latest houdini which works with boost-1.37.0 +* PyQt-2.2 or greater, but less than PyQt-4.5.3 + +In many examples in this documentation we will use the +:ref:`rez-env` command line tool. This tool takes a list of package +requests and creates the resulting configured environment. It places you in a subshell. Simply +exit the shell to return to a non-configured environment. + +.. _versions-concept: + +Versions +======== + +Rez version numbers are alphanumeric (any combination of numbers, letters and +underscores). A version number is a set of *tokens*, separated by either dot or dash. For example, +here is a list of valid package version numbers: + +* ``1`` +* ``1.0.0`` +* ``3.2.build_13`` +* ``4.rc1`` +* ``10a-5`` + +Version number tokens follow a strict ordering schema and are case sensitive. Underscore is the +smallest character, followed by letters (a-z and A-Z), followed by numbers. The ordering rules are +like so: + +* Underscore before everything else; +* Letters alphabetical, and before numbers; +* Lowercase letters before uppercase; +* Zero-padded numbers before equivalent non-padded (or less padded) number (``01`` is smaller than ``1``); +* If a token contains a combination of numbers and letters, it is internally split into groups + containing only numbers or only letters, and the resulting list is compared using the same rules + as above. + +The following table shows some example version token comparisons: + +============= ============ +smaller token larger token +============= ============ +0 1 +a b +a A +a 3 +_5 2 +ham hamster +alpha beta +alpha bob +02 2 +002 02 +13 043 +3 3a +beta3 3beta +============= ============ + +Versions are compared by their token lists. The token delimiter (usually dot, but can also be dash) +is ignored for comparison purposes, thus the versions ``1.0.0`` and ``1-0.0`` are equivalent. If two +versions share the same token list prefix, the longer version is greater, thus ``1.0.0`` is a higher +version than ``1.0``. + +.. note:: + No special importance is given to specific characters or letters in Rez version numbers. + The terms ``alpha`` and ``beta`` for example have no special meaning. Similarly, the number of tokens in + a version number doesn't matter, you can have as many as you like. While you are encouraged to use + semantic versioning (see ``_), it is not enforced. + +.. _packages-concept: + +Packages +======== + +A *package* is a versioned piece of software, that may have dependencies on other packages. Packages +are self-contained. They have a single package definition file (typically ``package.py``), which +describes everything we need to know about the package in order to use it. Rez manages any kind of +package, whether it be a python package, compiled package, or simply build code or configuration +data. + +Here is an example package definition file (see the :doc:`package definition guide ` for further details +of each attribute): + +.. code-block:: python + + name = "foo" + + version = "1.0.0" + + description = "Something that does foo-like things." + + requires = [ + "python-2.6", + "utils-1.1+<2" + ] + + tools = [ + "fooify" + ] + + def commands(): + env.PYTHONPATH.append("{root}/python") + env.PATH.append("{root}/bin") + +The :attr:`requires` section defines the requirements of the package. The :func:`commands` section describes +what happens when this package is added to an environment. Here, the ``bin`` directory in the package +installation is appended to ``PATH``, and similarly the ``python`` subdirectory is appended to +``PYTHONPATH``. + +.. _package-repositories-concept: + +Package Repositories +==================== + +Packages are installed into package repositories. + +.. caution:: + The following is an implementation of the filesystem repository plugin. + +A package repository is a directory on disk, with +packages and their versions laid out in a known structure underneath. Going on with our (foo, bah, +eek) example, here is how the package repository might look: + +.. code-block:: text + + /packages/inhouse/foo/1.1 + /1.2 + /1.3 + /packages/inhouse/bah/2 + /3 + /4 + /packages/inhouse/eek/2.5 + /2.6 + /2.7 + + # more detailed example of foo-1.1 + /packages/inhouse/foo/1.1/package.py + /python/ + /bin/ + +Here we have a package repository under the directory ``/packages/inhouse``. The actual package content +(files, executables etc) is installed into each leaf-node version directory, as shown for ``foo-1.1``. +The package definition file, in this case ``package.py``, is always stored at the root of the package, +right under the version directory for that package. + +Rez only requires that the package's definition file is at the root of the package installation. The +layout of the rest of the package, for example the ``python`` and ``bin`` directories, is completely +up to the package's own build to determine. You should expect to see a package's ``commands`` section +match up with its installation though. For example, notice how the path for foo's python files and +binaries match what its package commands specified from earlier. ``{root}/python`` and ``{root}/bin`` +will expand to these paths respectively. + +.. _package-search-path-concept: + +Package Search Path +=================== + +Rez finds packages using a search path in much the same way that python finds python modules using +``PYTHONPATH``. You can find out what the search path is, using the rez command line tool :ref:`rez-config` +(which you can also use to find any other rez setting): + +.. code-block:: text + + ]$ rez-config packages_path + - /home/ajohns/packages + - /packages/inhouse + - /packages/vendor + +If the same package appears in two or more repositories on the search path, the earlier package is +used in preference. This happens at the version level. For example an earlier package ``foo-1.0.0`` +will hide a later package ``foo-1.0.0``, but not ``foo-1.2.0``. + +The example search path shown is a typical setting. There are some central repositories later in the +search path, where packages are released to so everyone can use them. But there is also a local +package path at the front of the search path. This is where packages go that are being locally +developed by a user. Having this at the start of the search-path allows developers to resolve +environments that pull in test packages in preference to released ones, so they can test a package +before releasing it for general use. + +You can change the packages search path in several ways. A common way is to set the :envvar:`REZ_PACKAGES_PATH` +environment variable. + +.. tip:: + See :doc:`configuring_rez` for more configuration options. + +.. _package-commands-concept: + +Package Commands +================ + +The :func:`commands` section of the package definition determines how the environment is configured in +order to use it. It is a python function, but note that if any imports are used, they must appear +within the body of this function. + +Consider this commands example: + +.. code-block:: python + + def commands(): + env.PYTHONPATH.append("{root}/python") + env.PATH.append("{root}/bin") + +This is a typical example, where a package adds its source path to ``PYTHONPATH``, and its tools to +``PATH``. See :doc:`here ` for details on what can be done within the :func:`commands` section, +as well as details on what order package commands are executed in. + +.. _package-requests-concept: + +Package Requests +================ + +A *package request* is a string with a special syntax which matches a number of possible package +versions. You use package requests in the requires section of a package definition file, and also +when creating your own configured environment directly using tools such as :ref:`rez-env`. + +For example, here is a request (using the :ref:`rez-env` tool) to create an environment containing +*python* version 2.6 or greater, and *my_py_utils* version 5.4 or greater, but less than 6: + +.. code-block:: text + + ]$ rez-env 'python-2.6+' 'my_py_utils-5.4+<6' + +Here are some example package requests: + +=============== =================================== ====================================== +Package request Description Example versions within request +=============== =================================== ====================================== +foo Any version of foo. foo-1, foo-0.4, foo-5.0, foo-2.0.alpha +foo-1 Any version of foo-1[.x.x...x]. foo-1, foo-1.0, foo-1.2.3 +foo-1+ foo-1 or greater. foo-1, foo-1.0, foo-1.2.3, foo-7.0.0 +foo-1.2+<2 foo-1.2 or greater, but less than 2 foo-1.2.0, foo-1.6.4, foo-1.99 +foo<2 Any version of foo less than 2 foo-1, foo-1.0.4 +foo==2.0.0 Only version 2.0.0 exactly foo-2.0.0 +foo-1.3\|5+ OR'd requests foo-1.3.0, foo-6.0.0 +=============== =================================== ====================================== + +.. _conflict-operator-concept: + +The Conflict Operator +--------------------- + +The ``!`` operator is called the *conflict* operator, and is used to define an incompatibility +between packages, or to specify that you do *not* want a package version present. For example, +consider the command: + +.. code-block:: text + + ]$ rez-env maya_utils '!maya-2015.6' + +This specifies that you require any version of ``maya_utils``, but that any version of ``maya`` within +2015.6 (and this includes 2015.6.1 and so on) is not acceptable. + +.. _weak-references-concept: + +Weak References +--------------- + +The ``~`` operator is called the *weak reference* operator. It forces a package version to be within +the specified range if present, but does not actually require the package. For example, consider +the command: + +.. code-block:: text + + ]$ rez-env foo '~nuke-9.rc2' + +This request may or may not pull in the ``nuke`` package, depending on the requirements of ``foo``. +However, if nuke *is* present, it must be within the version ``9.rc2``. + +Weak references are useful in certain cases. For example, applications such as *nuke* and *maya* +sometimes ship with their own version of *python*. Their rez packages don't have a requirement on +*python* (they have their own embedded version already). However often other python libraries are +used both inside and outside of these applications, and those packages *do* have a python +requirement. So, to make sure that they're using a compatible python version when used within the +app, the app may define a *weak package reference* to their relevant python version, like so: + +.. code-block:: python + + # in maya's package.py + requires = [ + "~python-2.7.3" + ] + +This example ensures that any package that uses python, will use the version compatible with maya +when maya is present in the environment. + +.. _implicit-packages-concept: + +Implicit Packages +================= + +The *implicit packages* are a list of package requests that are automatically added to every rez +request (for example, when you use :ref:`rez-env`). They are set by the configuration setting +:data:`implicit_packages`. The default setting looks like so: + +.. todo:: document implicit_packages and make it referenceable + +.. code-block:: python + + implicit_packages = [ + "~platform=={system.platform}", + "~arch=={system.arch}", + "~os=={system.os}", + ] + +Rez models the current system (the platform, architecture and operating systems) as packages +themselves. The default implicits are a set of *weak requirements* on each of ``platform``, ``arch`` and +``os``. This ensures that if any platform-dependent package is requested, the platform, architecture +and/or operating system it depends on, matches the current system. + +The list of implicits that were used in a request are printed by :ref:`rez-env` when you enter the newly +configured subshell, and are also printed by the :ref:`rez-context` tool. + +.. _dependency-resolving: + +Dependency Resolving +==================== + +Rez contains a solving algorithm that takes a *request* (a list of package requests) and produces +a *resolve* (a final list of packages that satisfy the request). The algorithm avoids version +conflicts (two or more different versions of the same package at once). + +When you submit a request to rez, it finds a solution for that request that aims to give you the +latest possible version of each package. If this is not possible, it will give you the next latest +version, and so on. + +Consider the following example (the arrows indicate dependencies): + +.. image:: _static/rez_deps_simple_eg.png + :align: center + :class: rez-diagram + +Here we have three packages, ``foo``, ``bah`` and ``eek``, where both foo and bah have dependencies on +eek. For example, package ``bah-4`` might have a package definition file that looks something like +this (some entries skipped for succinctness): + +.. code-block:: python + + name = "bah" + + version = "4" + + requires = [ + "eek-2.6" + ] + +A request for ``foo-1.3`` is going to result in the resolve (``foo-1.3``, ``eek-2.7``). A request for +``foo`` will give the same result. We are asking for "any version of foo", but rez will prefer the +latest. However, if we request (``foo``, ``bah``), we are not going to get the latest of both because they +depend on different versions of eek, and that would cause a version conflict. Instead, our resolve +is going to be (``foo-1.2``, ``bah-4``, ``eek-2.6``). Rez has given you the latest possible versions of +packages, that do not cause a conflict. + +Sometimes your request is impossible to fulfill. For example, the request (``foo-1.3``, ``bah-4``) is +not possible. In this case, the resolve will fail, and rez will inform you of the conflict. + +Resolving An Environment +======================== + +A user can create a resolved environment using the command line tool :ref:`rez-env` (also via the API - +practically everything in rez can be done in python). When you create the environment, the current +environment is not changed. You are placed into a sub-shell instead. Here is an example of using +rez-env, assuming that the package repository is from our earlier (foo, bah, eek) example: + +.. code-block:: text + + ]$ rez-env foo bah + + You are now in a rez-configured environment. + + resolved by ajohns@14jun01.methodstudios.com, on Wed Oct 22 12:44:00 2014, + using Rez v2.0.rc1.10 + + requested packages: + foo + bah + + resolved packages: + eek-2.6 /packages/inhouse/eek/2.6 + foo-1.2 /packages/inhouse/foo/1.2 + bah-4 /packages/inhouse/bah/4 + + > ]$ â–ˆ + +The output of rez-env shows the original request, along with the matching resolve. It's the resolve +that tells you what actual package versions are present in the newly resolved environment. Notice +the ``>`` character in the prompt. This is a visual cue telling you that you have been placed +into a rez-resolved environment. + +Putting It All Together +----------------------- + +Let's go through what happens when an environment is resolved, using a new (and slightly more +realistic) example. +Let us assume that the following packages are available: + +* ``maya-2014.sp2``; +* ``nuke-8.0v3``; +* 3 versions of a maya plugin ``mplugin``; +* 2 versions of a nuke plugin ``nplugin``; +* 3 versions of a common base library ``lib``. + +The following diagram shows what happens when the command ``rez-env mplugin-1.3.0`` is run: + +.. image:: _static/rez_env.png + :align: center + :class: rez-diagram + +The diagram shows the following operations occurring: + +* Rez takes the user's request, and runs it through the dependency solver. The solver reads packages + from the package repositories in order to complete the solve; +* This results in a list of resolved packages. These are the packages that are used in the + configured environment; +* The commands from each package are concatenated together; +* This master list of commands is then translated into the target shell language (in this example + that is ``bash``); +* A sub-shell is created and the translated command code is sourced within this environment, + creating the final configured environment. + +The order of package command execution depends on package dependencies, and the order that packages +were requested in. See :ref:`here ` for more details. diff --git a/docs/source/building_packages.rst b/docs/source/building_packages.rst new file mode 100644 index 000000000..c72c76d13 --- /dev/null +++ b/docs/source/building_packages.rst @@ -0,0 +1,293 @@ +================= +Building packages +================= + +Rez packages can be built and locally installed using the :ref:`rez-build` tool. This +tool performs the following actions: + +* Iterates over a package's :doc:`variants ` +* Constructs the build environment +* Runs the build system within this environment + +Each build occurs within a *build path* which is typically either a *build* +subdirectory, or a variant-specific subdirectory under *build*. For example, a +package with two python-based variants might look like this: + +.. code-block:: text + + +- package.py + +- CMakeLists.txt (or other build file) + +-build + +-python-2.6 # build dir for python-2.6 variant + +-python-2.7 # build dir for python-2.6 variant + +The current working directory is set to the *build path* during a build. + +The Build Environment +===================== + +The build environment is a rez resolved environment. Its requirement list is +constructed like so: + +* First, the package's :attr:`requires` list is used; +* Then, the package's :attr:`build_requires` is + appended. This is transitive, meaning that the :attr:`build_requires` of all other packages in the + environment are also used; +* Then, the package's :attr:`private_build_requires` + is appended (unlike :attr:`build_requires`, it is not transitive). +* Finally, if the package has variants, the current variant's requirements are + appended. + +A standard list of environment variables is also set. You can see the full list :ref:`here `. + +The build system is then invoked within this environment, for each variant. + +Build Time Dependencies +======================= + +Sometimes it is desirable for a package to depend on another package only for the purposes +of building its code, or perhaps generating documentation. Let's use documentation as an +example: a C++ project may need to builds its docs using doxygen, but once the docs are +generated, doxygen is no longer needed. + +This is achieved by listing build-time dependencies under a +:attr:`build_requires` or :attr:`private_build_requires` +section in the ``package.py``. The requirements in :attr:`private_build_requires` are only used +from the package being built. Requirements from :attr:`build_requires` however are transitive, build +requirements from all packages in the build environment are included. + +Some example :attr:`private_build_requires` use cases include: + +* Documentation generators such as ``doxygen`` or ``sphinx``; +* Build utilities. For example, you may have a package called ``pyqt_cmake_utils``, which + provides CMake macros for converting ``ui`` files to ``py``; +* Statically linked libraries (since the library is linked at build time, the package + is not needed at runtime). + +An example use case of :attr:`build_requires` is a header-only (hpp) C++ library. If your own +C++ package includes this library in its own headers, other packages will also need this +library at build time (since they may include your headers, which in turn include the +hpp headers). + +Package Communication +===================== + +Let's say I have two C++ packages, ``maya_utils`` and the well-known ``boost`` library. How +does ``maya_utils`` find ``boost``'s header files, or library files? + +The short answer is, that is entirely up to you. Rez is not actually a build system. +It supports various build systems (as the next section describes), and it configures the +build environment, but the details of the build itself are left open for the user. +Having said that, `CMake `_ has been supported by rez for some time, and rez comes with a +decent amount of utility code to manage CMake builds. + +When a rez environment is configured, each required package's +:func:`~commands` section configures the environment for the building +package to use. When a build is occurring, a special variable +:attr:`building` is set to ``True``. Your required packages should use this +variable to communicate build information to the package being built. + +For example, our ``boost`` package's commands might look like so: + +.. code-block:: python + + def commands(): + if building: + # there is a 'FindBoost.cmake' file in this dir.. + env.CMAKE_MODULE_PATH.append("{root}/cmake") + +.. warning:: + Note that :func:`commands` is never executed for the package actually being built. + If you want to run commands in that case, you can use :func:`pre_build_commands` instead. + +A (very simple) ``FindBoost.cmake`` file might look like this: + +.. code-block:: cmake + + set(Boost_INCLUDE_DIRS $ENV{REZ_BOOST_ROOT}/include) + set(Boost_LIBRARY_DIRS $ENV{REZ_BOOST_ROOT}/lib) + set(Boost_LIBRARIES boost-python) + +Then, our ``maya_utils`` package might have a ``CMakeLists.txt`` file (cmake's build script) +containing: + +.. code-block:: cmake + + find_package(Boost) + include_directories(${Boost_INCLUDE_DIRS}) + link_directories(${Boost_LIBRARY_DIRS}) + target_link_libraries(maya_utils ${Boost_LIBRARIES}) + +As it happens, the `find_package `_ +CMake macro searches the paths listed in the `CMAKE_MODULE_PATH `_ environment variable, +and looks for a file called ``FindXXX.cmake``, where ``XXX`` is the name of the package (in this +case, ``Boost``), which it then includes. + +.. hint:: + Modern CMake should be used instead of ``FindXXX.cmake`` files. See the + `cmake packages `_ + documentation for more information. + +The Build System +================ + +Rez supports multiple build systems, and new ones can be added as plugins. When a +build is invoked, the build system is detected automatically. For example, if a +``CMakeLists.txt`` file is found in the package's root directory, the ``cmake`` build +system is used. + +Argument Passing +---------------- + +There are two ways to pass arguments to the build system. + +First, some build system plugins add extra options to the :ref:`rez-build` command directly. +For example, if you are in a CMake-based package, and you run ``rez-build -h``, you will +see cmake-specific options listed, such as ``--build-target``. + +Second, you can pass arguments directly to the build system, either using the +:option:`rez-build --build-args` option or listing the build system arguments after ``--``. + +For example, here we explicitly define a variable in a cmake build: + +.. code-block:: console + + $ rez-build -- -DMYVAR=YES + +Custom Build Commands +--------------------- + +As well as detecting the build system from build files, a package can explicitly +specify its own build command, using the +:attr:`build_command` package attribute. If present, +this takes precedence over other detected build systems. + +For example, consider the following ``package.py`` snippet: + +.. code-block:: python + + name = "nuke_utils" + + version = "1.2.3" + + build_command = "bash {root}/build.sh {install}" + +When :ref:`rez-build` is run on this package, the given ``build.sh`` script will be executed +with ``bash``. The ``{root}`` string expands to the root path of the package (the same +directory containing ``package.py``. The ``{install}`` string expands to ``install`` if +an install is occurring, or the empty string otherwise. This is useful for passing the +install target directly to the command (for example, when using ``make``) rather than +relying on a build script checking the :envvar:`REZ_BUILD_INSTALL` environment variable. + +.. warning:: + The current working directory during a build is set + to the *build path*, **not** to the package root directory. For this reason, you + will typically use the ``{root}`` string to refer to a build script in the package's + root directory. + +.. _custom-build-commands-pass-arguments: + +Passing Arguments ++++++++++++++++++ + +You can add arguments for your build script to the :ref:`rez-build` command directly, by +providing a ``parse_build_args.py`` source file in the package root directory. Here is an example: + +.. code-block:: python + + # in parse_build_args.py + parser.add_argument("--foo", action="store_true", help="do some foo") + +Now if you run ``rez-build -h`` on this package, you will see the option listed: + +.. code-block:: console + + $ rez-build -h + usage: rez build [-h] [-c] [-i] [-p PATH] [--fail-graph] [-s] [--view-pre] + [--process {remote,local}] [--foo] + [--variants INDEX [INDEX ...]] [--ba ARGS] [--cba ARGS] [-v] + + Build a package from source. + + optional arguments: + ... + --foo do some foo + +The added arguments are stored into environment variables so that your build script +can access them. They are prefixed with ``__PARSE_ARG_``; in our example above, the +variable ``__PARSE_ARG_FOO`` will be set. Booleans will be set to 0/1, and lists are +space separated, with quotes where necessary. + +Make Example +++++++++++++ + +Following is a very simple C++ example, showing how to use a custom build command to +build and install via ``make``: + +.. code-block:: python + + # in package.py + build_command = "make -f {root}/Makefile {install}" + +.. code-block:: makefile + + # in Makefile + hai: ${REZ_BUILD_SOURCE_PATH}/lib/main.cpp + g++ -o hai ${REZ_BUILD_SOURCE_PATH}/lib/main.cpp + + .PHONY: install + install: hai + mkdir -p ${REZ_BUILD_INSTALL_PATH}/bin + cp $< ${REZ_BUILD_INSTALL_PATH}/bin/hai + +Local Package Installs +====================== + +After you've made some code changes, you presumably want to test them. You do this +by *locally installing* the package, then resolving an environment with :ref:`rez-env` +to test the package in. The cycle goes like this: + +* Make code changes; +* Run ``rez-build --install`` to install as a local package; +* Run ``rez-env mypackage`` in a separate shell. This will pick up your local package, + and your package requirements; +* Test the package. + +A local install builds and installs the package to the :data:`local package repository `, +which is typically the directory :file:`~/packages`. +This directory is listed at the start of the +:ref:`package search path `, so when you resolve an +environment to test with, the locally installed package will be picked up first. Your +package will typically be installed to :file:`~/packages/{name}/{version}`, for example +:file:`~/packages/maya_utils/1.0.5`. If you have variants, they will be installed into subdirectories +within this install path (see :ref:`variants-disk-structure` for more details). + +.. tip:: + You don't need to run :ref:`rez-env` after every install. If your + package's requirements haven't changed, you can keep using the existing test environment. + +You can make sure you've picked up your local package by checking the output of the +:ref:`rez-env` call: + +.. code-block:: console + + $ rez-env sequence + + You are now in a rez-configured environment. + + resolved by ajohns@turtle, on Thu Mar 09 11:41:06 2017, using Rez v2.7.0 + + requested packages: + sequence + ~platform==linux (implicit) + ~arch==x86_64 (implicit) + ~os==Ubuntu-16.04 (implicit) + + resolved packages: + arch-x86_64 /sw/packages/arch/x86_64 + os-Ubuntu-16.04 /sw/packages/os/Ubuntu-16.04 + platform-linux /sw/packages/platform/linux + python-2.7.12 /sw/packages/python/2.7.12 + sequence-2.1.2 /home/ajohns/packages/sequence/2.1.2 (local) + +Note here that the ``sequence`` package is a local install, denoted by the ``(local)`` label. diff --git a/docs/source/commands_index.rst b/docs/source/commands_index.rst new file mode 100644 index 000000000..b818689a6 --- /dev/null +++ b/docs/source/commands_index.rst @@ -0,0 +1,7 @@ +======== +Commands +======== + +Every rez command is documented in these pages: + +.. rez-autoargparse:: diff --git a/docs/source/conf.py b/docs/source/conf.py new file mode 100644 index 000000000..ff4a4623d --- /dev/null +++ b/docs/source/conf.py @@ -0,0 +1,85 @@ +# Configuration file for the Sphinx documentation builder. +# +# 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 + +# Add path to rez's source. +sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', 'src'))) + +# Add path to the root of the docs folder to get access to the rez_sphinxext extension. +sys.path.insert(0, os.path.dirname(os.path.dirname(__file__))) + +import rez.utils._version + +# -- Project information ----------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information + +project = 'rez' +copyright = 'Contributors to the rez project' +author = 'Contributors to the rez project' +version = rez.utils._version._rez_version +release = rez.utils._version._rez_version + +# -- General configuration --------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration + +extensions = [ + "sphinx.ext.autodoc", + "sphinx.ext.autosummary", + "sphinx.ext.extlinks", + "sphinx.ext.intersphinx", + "sphinx.ext.napoleon", + "sphinx.ext.todo", + # Rez custom extension + 'rez_sphinxext' +] + +templates_path = ['_templates'] + +# -- Options for HTML output ------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output + +html_theme = 'furo' +html_static_path = ['_static'] + +html_theme_options = { + 'light_logo': 'rez-horizontal-black.svg', + 'dark_logo': 'rez-horizontal-white.svg', + 'sidebar_hide_name': True, +} + +# These paths are either relative to html_static_path +# or fully qualified paths (eg. https://...) +html_css_files = [ + 'css/custom.css', +] + +# -- Options for intersphinx extension --------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/extensions/intersphinx.html#module-sphinx.ext.intersphinx + +intersphinx_mapping = { + 'python': ('https://docs.python.org/3', None), +} + +# -- Options for autodoc extension ------------------------------------------ +# https://www.sphinx-doc.org/en/master/usage/extensions/autodoc.html#module-sphinx.ext.autodoc + +# autoclass_content = 'both' +autodoc_class_signature = 'separated' +autodoc_member_order = 'bysource' + + +# -- Options for extlinks extension ----------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/extensions/extlinks.html + +extlinks = { + 'gh-rez': ('https://github.com/AcademySoftwareFoundation/rez/blob/master/%s', '%s'), +} + +# -- Options for todo extension --------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/extensions/todo.html + +todo_emit_warnings = True diff --git a/docs/source/configuring_rez.rst b/docs/source/configuring_rez.rst new file mode 100644 index 000000000..3555510ab --- /dev/null +++ b/docs/source/configuring_rez.rst @@ -0,0 +1,142 @@ +=============== +Configuring rez +=============== + +Rez has a good number of configurable settings. The default settings, and +documentation for every setting, can be found :gh-rez:`src/rez/rezconfig.py`. + +Settings are determined in the following way: + +- The setting is first read from the file ``rezconfig.py`` in the rez installation; +- The setting is then overridden if it is present in another settings file pointed at by the + :envvar:`REZ_CONFIG_FILE` environment variable. This can also be a path-like variable, to read from + multiple configuration files; +- The setting is further overriden if it is present in ``$HOME/.rezconfig``; +- The setting is overridden again if the environment variable :envvar:`REZ_XXX` is present, where ``XXX`` is + the uppercase version of the setting key. For example, :data:`.image_viewer` will be overriden by + :envvar:`REZ_IMAGE_VIEWER`. +- This is a special case applied only during a package build or release. In this case, if the + package definition file contains a "config" section, settings in this section will override all + others. See :ref:`configuring-rez-package-overrides`. + +It is fairly typical to provide your site-specific rez settings in a file that the environment +variable :envvar:`REZ_CONFIG_FILE` is then set to for all your users. + +.. tip:: + You do not need to provide a copy of all settings in this file. Just provide those + that are changed from the defaults. + +.. _configuring-rez-settings-merge-rules: + +Settings Merge Rules +==================== + +When multiple configuration sources are present, the settings are merged together - +one config file does not replace the previous one, it overrides it. By default, the +following rules apply: + +* Dicts are recursively merged together; +* Non-dicts override the previous value. + +However, it is also possible to append and/or prepend list-based settings by using the +:class:`ModifyList <.ModifyList>` class. For example, the +following config entry will append to the :data:`.release_hooks` setting value defined by the +previous configuration sources (you can also supply a ``prepend`` argument): + +.. code-block:: python + + release_hooks = ModifyList(append=["custom_release_notify"]) + +.. _configuring-rez-package-overrides: + +Package Overrides +================= + +.. todo:: Properly document the scope function as a function that takes a string, etc and make it referenceable. + +Packages themselves can override configuration settings. To show how this is useful, +consider the following example: + +.. code-block:: python + + # in package.py + with scope("config") as c: + c.release_packages_path = "/svr/packages/internal" + +Here a package is overriding the default release path - perhaps you're releasing +internally- and externally-developed packages to different locations, for example. + +These config overrides are only applicable during building and releasing of the package. +As such, even though any setting can be overridden, it's only useful to do so for +those that have any effect during the build/install process. These include: + +* Settings that determine where packages are found, such as :data:`.packages_path`, + :data:`.local_packages_path` and :data:`.release_packages_path`; +* Settings in the ``build_system``, ``release_hook`` and ``release_vcs`` plugin types; +* :data:`.package_definition_python_path`; +* :data:`.package_filter`. + +.. _configuring-rez-string-expansions: + +String Expansions +================= + +The following string expansions occur on all configuration settings: + +* Any environment variable reference, in the form ``${HOME}``; +* Any property of the ``system`` object, eg ``{system.platform}``. See :class:`rez.system.System` for more details. + +.. _configuring-rez-delay-load: + +Delay Load +========== + +It is possible to store a config setting in a separate file, which will be loaded +only when that setting is referenced. This can be useful if you have a large value +(such as a dict) that you don't want to pollute the main config with. YAML and +JSON formats are supported: + +.. code-block:: python + + # in rezconfig + default_relocatable_per_package = DelayLoad('/svr/configs/rez_relocs.yaml') + +See :Class:`.DelayLoad`. + +.. _configuring-rez-commandline-line: + +Commandline Tool +================ + +You can use the :ref:`rez-config` command line tool to see what the current configured settings are. +Called with no arguments, it prints all settings; if you specify an argument, it prints out just +that setting:: + + ]$ rez-config packages_path + - /home/sclaus/packages + - /home/sclaus/.rez/packages/int + - /home/sclaus/.rez/packages/ext + +Here is an example showing how to override settings using your own configuration file:: + + ]$ echo 'packages_path = ["~/packages", "/packages"]' > myrezconfig.py + ]$ export REZ_CONFIG_FILE=${PWD}/myrezconfig.py + ]$ rez-config packages_path + - /home/sclaus/packages + - /packages + +.. _configuring-rez-configuration-settings: + +Configuration Settings +====================== + +Following is an alphabetical list of rez settings. + +.. note:: + Note that this list has been generated automatically from the :gh-rez:`src/rez/rezconfig.py` + file in the rez source, so you can also refer to that file for the same information. + +.. This is a custom directive. See the rez_sphinxext.py file for more information. +.. TL;DR: It will take care of generating the documentation or all the settings defined +.. in rezconfig.py +.. rez-config:: diff --git a/docs/source/context.rst b/docs/source/context.rst new file mode 100644 index 000000000..a84a5b019 --- /dev/null +++ b/docs/source/context.rst @@ -0,0 +1,111 @@ +======= +Context +======= + +When you use :ref:`rez-env` to create a resolved environment, you are actually +creating something called a *context*. A context is a store of information +including: + +* The initial :ref:`package request ` list; +* The *resolve* (the list of variants that were chosen); +* A graph which shows the resolve visually. + +The context does not store copies of the packages it resolved to; rather, it +stores a kind of handle for each, which gives enough information to know where +to fetch the full package definition and contents from. + +Contexts themselves are quite small, and are stored in JSON format in a file +with the extension ``rxt``. When you use :ref:`rez-env`, it actually creates a temporary +context file on disk, which is removed when the shell is exited: + +.. code-block:: console + + $ rez-env foo bah + + You are now in a rez-configured environment. + + resolved by ajohns@14jun01.methodstudios.com, on Wed Oct 22 12:44:00 2014, + using Rez v2.0.rc1.10 + + requested packages: + foo + bah + + resolved packages: + eek-2.6 /packages/inhouse/eek/2.6 + foo-1.2 /packages/inhouse/foo/1.2 + bah-4 /packages/inhouse/bah/4 + + > $ echo $REZ_RXT_FILE + /tmp/rez_context_0tMS4U/context.rxt + +.. _context-baking-resolves: + +Baking Resolves +=============== + +You can use the :option:`rez-env --output` flag to write a resolved context directly +to file, rather than invoking a subshell: + +.. code-block:: console + + $ rez-env foo bah --output test.rxt + +Later, you can read the context back again, to reconstruct the same environment: + +.. code-block:: console + + $ rez-env --input test.rxt + + You are now in a rez-configured environment. + + resolved by ajohns@14jun01.methodstudios.com, on Wed Oct 22 12:44:00 2014, + using Rez v2.0.rc1.10 + + requested packages: + foo + bah + + resolved packages: + eek-2.6 /packages/inhouse/eek/2.6 + foo-1.2 /packages/inhouse/foo/1.2 + bah-4 /packages/inhouse/bah/4 + + > $ â–ˆ + +Contexts do not store a copy of the environment that is configured (that is, the +environment variables exported, for example). A context just stores the resolved +list of packages that need to be applied in order to configure the environment. +When you load a context via :option:`rez-env --input`, each of the packages' :attr:`commands` +sections are interpreted once more. + +You can think of package :attr:`commands` like fragments of a wrapper script which +configures an environment. By creating a context, you are creating a list of +script fragments which, when run in serial, produce the target environment. So, +if your package added a path to ``$PATH`` which included a reference to ``$USER`` +for example, this would work correctly even if Joe created the rxt file, and +Jill read it because the commands are reinterpreted when Jill loads the context. + +The rez-context Tool +==================== + +The :ref:`rez-context` tool inspects context files. When you're within a resolved +subshell, :ref:`rez-context` inspects the current context, unless one is specified +explicitly. For example, we can inspect the context created in the previous +example, without actually being within it: + +.. code-block:: console + + $ rez-context test.rxt + + resolved by ajohns@14jun01.methodstudios.com, on Wed Oct 22 12:44:00 2014, + using Rez v2.0.rc1.10 + + requested packages: + foo + bah + + resolved packages: + eek-2.6 /packages/inhouse/eek/2.6 + foo-1.2 /packages/inhouse/foo/1.2 + bah-4 /packages/inhouse/bah/4 diff --git a/docs/source/context_bundles.rst b/docs/source/context_bundles.rst new file mode 100644 index 000000000..d7846664e --- /dev/null +++ b/docs/source/context_bundles.rst @@ -0,0 +1,86 @@ +=============== +Context bundles +=============== + +A "context bundle" is a directory containing a :doc:`context ` (an rxt file), and a +package repository. All packages in the context are stored in the repository, +making the bundle relocatable and standalone. You can copy a bundle onto a +server for example, or into a container, and there are no external references +to shared package repositories. This is in contrast to a typical context, which +contains absolute references to one or more package repositories that are +typically on shared disk storage. + +To create a bundle via command line: + +.. code-block:: console + + $ rez-env foo -o foo.rxt + $ rez-bundle foo.rxt ./mybundle + + $ # example of running a command from the bundled context + $ rez-env -i ./mybundle/context.rxt -- foo-tool + +To create a bundle via API: + +.. code-block:: python + + from rez.bundle_context import bundle_context + from rez.resolved_context import ResolvedContext + + c = ResolvedContext(["python-3+", "foo-1.2+<2"]) + bundle_context(c, "./mybundle") + +Structure +========= + +A bundle directory looks like this: + +.. code-block:: text + + .../mybundle/ + ./context.rxt + ./packages/ + + +Package references in the rxt file are relative (unlike in a standard context, +where they're absolute), and this makes the bundle relocatable. + +Patching Libraries +================== + +Depending on how compiled libraries and executables within a rez package were +built, it's possible that the dynamic linker will attempt to resolve them to +libraries found outside of the bundle. For example, this is possible in linux +if an `elf `_ (``.so``) +contains an absolute search-path in its rpath/runpath header to a library in another package. + +Rez bundling performs a library patching step that applies various fixes to +solve this issue (use :option:`--no-lib-patch ` if you want to skip this step). This step +is platform-specific and is covered in the following sections. + +.. note:: + Note that in all cases, references to libraries outside of the bundle will remain intact, + if there is no equivalent path found within the bundle (for example, if the reference is + to a system library not provided by a rez package). + +Linux +----- + +On linux, `rpath/runpath `_ headers are altered if paths are found that map to a +subdirectory within another package in the bundle. To illustrate what happens, +consider the following example, where packages from :file:`/sw/packages` have been +bundled into the local directory :file:`./mybundle`: + +.. code-block:: console + + $ # a lib in an original non-bundled package + $ patchelf --print-rpath /sw/packages/foo/1.0.0/bin/foo + /sw/packages/bah/2.1.1/lib + $ + $ # the same lib in our bundle. We assume that package 'bah' is in the bundle + $ # also, since foo links to one of its libs + $ patchelf --print-rpath ./mybundle/packages/foo/1.0.0/bin/foo + $ORIGIN/../../../bah/2.1.1/lib + +Remapped rpaths make use of the special ``$ORIGIN`` variable, which refers to +the directory containing the current file. diff --git a/docs/source/environment.rst b/docs/source/environment.rst new file mode 100644 index 000000000..279db3db8 --- /dev/null +++ b/docs/source/environment.rst @@ -0,0 +1,253 @@ +===================== +Environment variables +===================== + +This chapter lists the environment variables that rez generates in certain +circumstances, as well as environment variables that you can set which affect +the operation of rez. + +.. _context-environment-variables: + +Context Environment Variables +============================= + +These are variables that rez generates within a resolved environment (a "context"). + +.. envvar:: REZ_RXT_FILE + + Filepath of the current context (an rxt file). + + .. seealso:: Documentation on :doc:`contexts `. + +.. envvar:: REZ_USED + + Path to rez installation that was used to resolve this environment. + +.. envvar:: REZ_USED_IMPLICIT_PACKAGES + + The list of implicit packages used in the resolve. + +.. envvar:: REZ_USED_PACKAGES_PATH + + The package search-path used for this resolve. + +.. envvar:: REZ_USED_RESOLVE + + The list of resolved packages, eg ``platform-linux utils-1.2.3``. + +.. envvar:: REZ_USED_EPH_RESOLVE + + The list of resolved ephemerals, eg ``.foo.cli-1 .debugging-0``. + +.. envvar:: REZ_USED_LOCAL_RESOLVE + + The list of resolved local packages, eg ``utils-1.2.3 maya_utils-1.3+``. Packages listed here will always be a subset of the packages in :envvar:`REZ_USED_RESOLVE`. + +.. envvar:: REZ_USED_REQUEST + + The environment request string, eg ``maya-2017 maya_utils-1.3+``. Does not include implicit packages. + +.. envvar:: REZ_USED_REQUESTED_TIMESTAMP + + The epoch time of this resolved environment, explicitly set by the user with (for example) the :option:`rez-env --time` flag; zero otherwise. + +.. envvar:: REZ_USED_TIMESTAMP + + The epoch time when this environment was resolved; OR, the value of :envvar:`REZ_USED_REQUESTED_TIMESTAMP`, if non-zero. + +.. envvar:: REZ_USED_VERSION + + The version of rez used to resolve this environment. + +.. envvar:: REZ_SHELL_INIT_TIMESTAMP + + The epoch time when the current shell was instantiated. + +.. envvar:: REZ_SHELL_INTERACTIVE + + Will be 1 if the shell is interactive, and 0 otherwise + (ie, when a command is specified, like ``rez-env foo -- mycommand``). + +.. envvar:: REZ_CONTEXT_FILE + + Filepath of the current context's shell code that is the result of all the + resolved packages :func:`commands`'s sections. + +Package environment variables +----------------------------- + +Specifically, per-package, the following variables are generated. Note that for a given +package name, ``(PKG)`` in the variables below is the uppercased package name, with any +dots replaced with underscore. + +.. envvar:: REZ_(PKG)_BASE + + The base directory of the package installation, eg ``/packages/utils/1.0.0``. + +.. envvar:: REZ_(PKG)_ROOT + + The root directory of the package installation (actually,the variant), eg ``/packages/utils/1.0.0/python-2.7``. + +.. envvar:: REZ_(PKG)_VERSION + + The version of the package. + +.. envvar:: REZ_(PKG)_MAJOR_VERSION + + The major version of the package, or an empty string. + +.. envvar:: REZ_(PKG)_MINOR_VERSION + + The minor version of the package, or an empty string. + +.. envvar:: REZ_(PKG)_PATCH_VERSION + + The patch version of the package, or an emopty string. + +Ephemeral packages environment variables +---------------------------------------- + +For every ephemeral package request, the following variables are generated. Note +that for a given ephemeral package name, ``(PKG)`` in the variables below is the +uppercased package name, with dots replaced by underscore, and **the leading dot +removed**: + +.. envvar:: REZ_EPH_(PKG)_REQUEST + + The resolved ephemeral package request. + +.. _build-environment-variables: + +Build Environment Variables +=========================== + +These are variables that rez generates within a build environment, in addition +to those listed :ref:`here `. + +.. glossary:: + +.. envvar:: REZ_BUILD_ENV + + Always present in a build, has value 1. + +.. envvar:: REZ_BUILD_INSTALL + + Has a value of 1 if an installation is taking place (either a :option:`rez-build -i` or :ref:`rez-release`), otherwise 0. + +.. envvar:: REZ_BUILD_INSTALL_PATH + + Installation path, if an install is taking place. + +.. envvar:: REZ_BUILD_PATH + + Path where build output goes. + +.. envvar:: REZ_BUILD_PROJECT_DESCRIPTION + + Equal to the *description* attribute of the package being built. + +.. envvar:: REZ_BUILD_PROJECT_FILE + + The filepath of the package being built (typically a ``package.py`` file). + +.. envvar:: REZ_BUILD_PROJECT_NAME + + Name of the package being built. + +.. envvar:: REZ_BUILD_PROJECT_VERSION + + Version of the package being built. + +.. envvar:: REZ_BUILD_REQUIRES + + Space-separated list of requirements for the build - comes from the current package's :attr:`requires`, + :attr:`build_requires` and :attr:`private_build_requires` attributes, including the current variant's requirements. + +.. envvar:: REZ_BUILD_REQUIRES_UNVERSIONED + + Equivalent but unversioned list to :envvar:`REZ_BUILD_REQUIRES`. + +.. envvar:: REZ_BUILD_SOURCE_PATH + + Path containing the package.py file. + +.. envvar:: REZ_BUILD_THREAD_COUNT + :noindex: + + Number of threads being used for the build. + + .. seealso:: The :data:`build_thread_count` setting. + +.. envvar:: REZ_BUILD_TYPE + + One of ``local`` or ``central``. Value is ``central`` if a release is occurring. + +.. envvar:: REZ_BUILD_VARIANT_INDEX + + Zero-based index of the variant currently being built. For non-varianted packages, this is 0. + +.. envvar:: REZ_BUILD_VARIANT_REQUIRES + + Space-separated list of runtime requirements of the current variant. This does not include + the common requirements as found in :envvar:`REZ_BUILD_REQUIRES`. For non-varianted builds, this is an empty string. + +.. envvar:: REZ_BUILD_VARIANT_SUBPATH + + Subdirectory containing the current variant. For non-varianted builds, this is an empty string. + +.. envvar:: __PARSE_ARG_XXX + + .. seealso:: :ref:`custom-build-commands-pass-arguments` + +.. _runtime-environment-variables: + +Runtime Environment Variables +============================= + +These are environment variables that the user can set, which affect the +operation of rez. + +.. envvar:: REZ_CONFIG_FILE + + Path to a rez configuration file. + +.. envvar:: REZ_XXX + + For any given rez config entry (see ``rezconfig.py``), + you can override the setting with an environment variable, for convenience. Here, + ``XXX`` is the uppercased equivalent of the setting name. For example, + a setting commonly overriden this way is :data:`packages_path`, whos equivalent + variable is :envvar:`REZ_PACKAGES_PATH`. + + .. hint:: + Each setting documented in :ref:`configuring-rez-configuration-settings` documents their environment variable. + +.. envvar:: REZ_XXX_JSON + + Same as :envvar:`REZ_XXX`, except that the format + is a JSON string. This means that some more complex settings can be overridden, + that aren't supported in the non-JSON case (:data:`package_filter` is an example). + +.. envvar:: REZ_DISABLE_HOME_CONFIG + + If 1/t/true, the default ``~/.rezconfig.py`` config file is skipped. + +.. envvar:: EDITOR + + On Linux and OSX systems, this will set the default editor to use + if and when rez requires one (an example is on release if the :data:`prompt_release_message` + config setting is true). + +.. envvar:: REZ_KEEP_TMPDIRS + + If set to a non-empty string, this prevents rez from + cleaning up any temporary directories. This is for debugging purposes. + +.. envvar:: REZ_SIGUSR1_ACTION + + If you set this to ``print_stack``, rez will prints its + current stacktrace to stdout if sent a USR1 signal. This is for debugging purposes. + +.. envvar:: REZ_ENV_PROMPT + + See the :data:`set_prompt` and :data:`prefix_prompt` settings. diff --git a/docs/source/ephemerals.rst b/docs/source/ephemerals.rst new file mode 100644 index 000000000..57c6c89f9 --- /dev/null +++ b/docs/source/ephemerals.rst @@ -0,0 +1,182 @@ +========== +Ephemerals +========== + +.. versionadded:: 2.71.0 + +Ephemeral packages (or simply 'ephemerals') are requests for packages that do not +exist. Ephemeral package names always begin with a dot (``.``). Like all package +requests, ephemerals can be requested as part of packages' requires or variants +lists, or directly by the user (via :ref:`rez-env` for eg). + +Example: + +.. code-block:: text + + ]$ rez-env .foo-1 + You are now in a rez-configured environment. + + resolved by ajohns@turtle, on Tue Dec 22 08:17:00 2020, using Rez v2.70.0 + + requested packages: + .foo-1 (ephemeral) + ~platform==linux (implicit) + ~arch==x86_64 (implicit) + ~os==Ubuntu-16.04 (implicit) + + resolved packages: + .foo-1 (ephemeral) + +Ephemerals will act like real packages during a resolve (ie, their request ranges +will intersect, and conflicts can occur) but they never actually correlate to a +real package, nor do they perform any configuration on the runtime (not directly +in any case). + +Example showing range intersection: + +.. code-block:: text + + ]$ rez-env .foo-1 '.foo-1.5+' + + You are now in a rez-configured environment. + + resolved by ajohns@turtle, on Tue Dec 22 08:21:04 2020, using Rez v2.70.0 + + requested packages: + .foo-1 (ephemeral) + .foo-1.5+ (ephemeral) + ~platform==linux (implicit) + ~arch==x86_64 (implicit) + ~os==Ubuntu-16.04 (implicit) + + resolved packages: + .foo-1.5+<1_ (ephemeral) + +Example of conflicting request: + +.. code-block:: text + + ]$ rez-env .foo-1 .foo-2 + The context failed to resolve: + The following package conflicts occurred: (.foo-1 <--!--> .foo-2) + +Environment Variables +===================== + +Ephemerals do not affect the runtime in the way that packages can (via their +:func:`commands` section), however some environment variables are set: + +* :envvar:`REZ_USED_EPH_RESOLVE` +* :envvar:`REZ_EPH_(PKG)_REQUEST` + +The following example illustrates: + +.. code-block:: text + + ]$ rez-env python .foo-1 .bah-2 + ... + ]$ echo $REZ_EPH_FOO_REQUEST + 1 + ]$ echo $REZ_USED_EPH_RESOLVE + .foo-1 .bah-2 + +Introspection +============= + +In order for a package to inspect the ephemerals that are present in a runtime, +there is an :attr:`ephemerals` object provided, similar +to the :attr:`resolve` object. You would typically use the +:func:`intersects` function to inspect it, like so: + +.. code-block:: python + + # in package.py + def commands() + if intersects(ephemerals.get_range('enable_tracking', '0'), '1'): + env.TRACKING_ENABLED = 1 + +In this example, the given package would set the ``TRACKING_ENABLED`` environment +variable if an ephemeral such as ``.enable_tracking-1`` (or ``.enable_tracking-1.2+`` +etc) is present in the resolve. Note that the leading ``.`` is implied and not +included when querying the :attr:`ephemerals` object. + +.. warning:: + Since :attr:`ephemerals` is a dict-like object, so it has + a ``get`` function which will return a full request string if key exists. Hence, + the default value should also be a full request string, not just a version range + string like ``0`` in :func:`ephemerals.get_range`. Or :func:`intersects` may not work as expect. + +Ephemeral Use Cases +=================== + +Why would you want to request packages that don't exist? There are two main use +cases. + +Passing Information to Packages +------------------------------- + +Ephemerals can be used as a kind of 'package option', or a way to pass information +to packages in a resolve. For example, consider the following package definition: + +.. code-block:: python + + name = 'bah' + + def commands(): + if intersects(ephemerals.get_range('bah.cli', '1'), '1'): + env.PATH.append('{root}/bin') + +This package will disable its command line tools if an ephemeral like ``.bah.cli-0`` +is present in the runtime. + +.. note:: + Ephemerals are standard package requests and so can + have any range, such as ``1.2.3``, ``2.5+`` and so on. However, they're often used + as boolean package options, as in the example above. In this case, it is + recommended to use the conventional ranges ``1`` and ``0`` to designate true and + false. + +Since ephemerals can be pretty much anything, you might also decide to use them +as a global package option. Here's another take on our example, but in this case +we introduce a ``.cli`` ephemeral that acts as a global whitelist: + +.. code-block:: python + + name = 'bah' + + def commands(): + if intersects(ephemerals.get_range('cli', ''), 'bah'): + env.PATH.append('{root}/bin') + +Here, all packages' cli will be enabled if ``.cli`` is not specified, but if it is +specified then it acts as a whitelist: + +.. code-block:: text + + # turn on cli for foo and bah only + ]$ rez-env foo-1 bah==2.3.1 eek-2.4 '.cli-foo|bah' + +Abstract Package Representation +------------------------------- + +Sometimes it makes sense for a package to require some form of abstract object or +capability, rather than an actual package. For example, perhaps your package (or +one of its variants) requires a GPU to be present on the host machine. To support +this, you might have something setup that includes a ``.gpu-1`` ephemeral in the +:ref:`implicits ` list on all GPU-enabled hosts. +Then, your package could look like this: + +.. code-block:: python + + name = 'pixxelator' + + variants = [ + ['.gpu-0'], # renders via CPU + ['.gpu-1'] # renders via GPU + ] + +.. warning:: + Be aware that on hosts that do **not** have a gpu + implicit, either variant could be selected. You would want to either guarantee + that every host has the gpu implicit set to 0 or 1, or that the user always + explicitly specifies ``.gpu-0`` or ``.gpu-1`` in their request. diff --git a/docs/source/index.rst b/docs/source/index.rst new file mode 100644 index 000000000..da4e133f5 --- /dev/null +++ b/docs/source/index.rst @@ -0,0 +1,77 @@ +.. rez documentation master file, created by + sphinx-quickstart on Sat Aug 5 19:30:28 2023. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + +Welcome to rez's documentation! +=============================== + +.. toctree:: + :maxdepth: 2 + :caption: General + :hidden: + + installation + basic_concepts + building_packages + context + variants + +.. toctree:: + :maxdepth: 2 + :caption: Advanced + :hidden: + + ephemerals + context_bundles + suites + managing_packages + pip + +.. toctree:: + :maxdepth: 2 + :caption: Package definition + :hidden: + + package_definition + package_commands + +.. toctree:: + :maxdepth: 2 + :caption: Reference + :hidden: + + configuring_rez + commands_index + environment + api + +Rez is a cross-platform package manager with a difference. Using Rez you can create +standalone environments configured for a given set of packages. However, unlike many +other package managers, packages are not installed into these standalone environments. +Instead, all package versions are installed into a central repository, and standalone +environments reference these existing packages. This means that configured environments +are lightweight, and very fast to create, often taking just a few seconds to configure +despite containing hundreds of packages. + +Traditional package manager: + +.. figure:: _static/other_pkg_mgr.png + :align: center + :alt: Typical package managers install packages into an environment + :class: rez-diagram + + Typical package managers install packages into an environment + +Rez: + +.. figure:: _static/rez_pkg_mgr.png + :align: center + :alt: Rez installs packages once, and configures environments dynamically + :class: rez-diagram + + Rez installs packages once, and configures environments dynamically + +Rez takes a list of package requests, and constructs the target environment, resolving +all the necessary package dependencies. Any type of software package is supported - +compiled, python, applications and libraries. diff --git a/docs/source/installation.rst b/docs/source/installation.rst new file mode 100644 index 000000000..87c085a2b --- /dev/null +++ b/docs/source/installation.rst @@ -0,0 +1,86 @@ +============ +Installation +============ + +Installation Script +=================== + +To install rez, download the source. Then from the root directory, run:: + + ]$ python ./install.py + +This installs rez to ``/opt/rez``. See ```install.py -h``` for how to install to a +different location. + +Once the installation is complete, a message tells you how to run it:: + + SUCCESS! To activate Rez, add the following path to $PATH: + /opt/rez/bin/rez + + You may also want to source the completion script (for bash): + source /opt/rez/completion/complete.sh + + +.. warning:: + Do **not** move the installation. Re-install to a new location if you want to change + the install path. If you want to install rez for multiple operating systems, + perform separate installs for each of those systems. + + +Installation via pip +==================== + +It is possible to install rez with pip, like so:: + + ]$ pip install rez + +However, this comes with a caveat. Rez command line tools **are not guaranteed +to work correctly** once inside a rez environment (ie after using the :ref:`rez-env` +command). The reasons are given in the next section. + +Pip installation is adequate however, if all you require is the rez API, or you +don't require its command line tools to be available within a resolved environment. + +.. note:: + that running pip-installed rez command line tools will print a warning like so: + + .. code-block:: text + + Pip-based rez installation detected. Please be aware that rez command line tools + are not guaranteed to function correctly in this case. See :ref:`why-not-pip-for-production` + for further details. + +.. _why-not-pip-for-production: + +Why Not Pip For Production? +=========================== + +Rez is not a normal python package. Although it can successfully be installed +using standard mechanisms such as pip, this comes with a number of caveats. +Specifically: + +* When within a rez environment (ie after using the :ref:`rez-env` command), the rez + command line tools are not guaranteed to function correctly; +* When within a rez environment, other packages' tools (that were also installed + with pip) remain visible, but are not guaranteed to work. + +When you enter a rez environment, the rez packages in the resolve configure +that environment as they see fit. For example, it is not uncommon for a python +package to append to :envvar:`PYTHONPATH`. Environment variables such as :envvar:`PYTHONPATH` +affect the behaviour of tools, including rez itself, and this can cause it to +crash or behave abnormally. + +When you use the ``install.py`` script to install rez, some extra steps are taken +to avoid this problem. Specifically: + +* Rez is installed into a virtualenv so that it operates standalone; +* The rez tools are shebanged with ``python -E``, in order to protect them from + environment variables that affect python's behaviour; +* The rez tools are stored in their own directory, so that other unrelated tools + are not visible. + +Due to the way standard wheel-based python installations work, it simply is not +possible to perform these extra steps without using a custom installation script. +Wheels do not give the opportunity to run post-installation code. Neither do +they provide functionality for specifying interpreter arguments to be added for +any given entry point. diff --git a/docs/source/managing_packages.rst b/docs/source/managing_packages.rst new file mode 100644 index 000000000..1e6e1b995 --- /dev/null +++ b/docs/source/managing_packages.rst @@ -0,0 +1,384 @@ +================= +Managing packages +================= + +Ignoring Packages +================= + +Packages can be ignored. When this happens, the package is still present in its +repository, but it will not be visible to the rez API nor to any newly resolved +runtimes. Any runtimes that are currently using an ignored package are unaffected, +since the package's payload has not been removed. + +To ignore a package via commandline: + +.. code-block:: console + + $ # you need to specify the repo, but you'll be shown a list if you don't + $ rez-pkg-ignore foo-1.2.3 + No action taken. Run again, and set PATH to one of: + filesystem@/home/ajohns/packages + + $ rez-pkg-ignore foo-1.2.3 filesystem@/home/ajohns/packages + Package is now ignored and will not be visible to resolves + +Via API: + +.. code-block:: python + + >>> from rez.package_repository import package_repository_manager + >>> + >>> repo_path = "filesystem@/home/ajohns/packages" + >>> repo = package_repository_manager.get_repository(repo_path) + >>> repo.ignore_package("foo", "1.2.3") + 1 # -1: pkg not found; 0: pkg already ignored; 1: pkg ignored + +Both of these options generate a :file:`.ignore{{version}}` file (e.g. +``.ignore3.1.2``) next to the package version directory. + +You can also do the reverse (ie unignore a package). Use the :option:`-u ` option of +:ref:`rez-pkg-ignore`, or the :meth:`~rez.package_repository.PackageRepository.unignore_package` method on the package repository +object. + +Copying Packages +================ + +Packages can be copied from one :ref:`package repository ` +to another, like so: + +Via commandline: + +.. code-block:: console + + $ rez-cp --dest-path /svr/packages2 my_pkg-1.2.3 + +Via API: + +.. code-block:: python + + >>> from rez.package_copy import copy_package + >>> from rez.packages import get_latest_package + >>> + >>> p = get_latest_package("python") + >>> p + Package(FileSystemPackageResource({'location': '/home/ajohns/packages', 'name': 'python', 'repository_type': 'filesystem', 'version': '3.7.4'})) + >>> + >>> r = copy_package(p, "./repo2") + >>> + >>> print(pprint.pformat(r)) + { + 'copied': [ + ( + Variant(FileSystemVariantResource({'location': '/home/ajohns/packages', 'name': 'python', 'repository_type': 'filesystem', 'index': 0, 'version': '3.7.4'})), + Variant(FileSystemVariantResource({'location': '/home/ajohns/repo2', 'name': 'python', 'repository_type': 'filesystem', 'index': 0, 'version': '3.7.4'})) + ) + ], + 'skipped': [] + } + +Copying packages is actually done one variant at a time, and you can copy some +variants of a package if you want, rather than the entire package. The API call's +return value shows what variants were copied. The 2-tuple in ``copied`` lists the +source (the variant that was copied from) and destination (the variant that was +created) respectively. + +.. danger:: + Do not simply copy package directories on disk. + You should always use :ref:`rez-cp` or use the API. Copying directly on disk is bypassing rez and + this can cause problems such as a stale resolve cache. Using :ref:`rez-cp` and the API give + you more control anyway. + +.. _enabling-package-copying: + +Enabling Package Copying +------------------------ + +Copying packages is enabled by default, however you're also able to specify which +packages are and are not *relocatable*, for much the same reasons as given +:ref:`here `. + +You can mark a package as non-relocatable by setting :attr:`relocatable` +to ``False`` in its package definition file. There are also config settings that affect relocatability +in the event that relocatable is not defined in a package's definition. For example, +see :data:`default_relocatable`, :data:`default_relocatable_per_package` +and :data:`default_relocatable_per_repository`. + +Attempting to copy a non-relocatable package will raise a :exc:`~rez.exceptions.PackageCopyError`. +However, note that there is a ``force`` option that will override this. Use at +your own risk. + +.. _moving-packages: + +Moving Packages +=============== + +Packages can be moved from one :ref:`package repository ` +to another. Be aware that moving a package does not actually delete the source +package however. Instead, the source package is hidden (ignored). It is up to +you to delete it at some later date. + +To move a package via commandline: + +.. code-block:: console + + $ rez-mv --dest-path /packages2 python-3.7.4 /packages + +Via API: + +.. code-block:: python + + >>> from rez.package_move import move_package + >>> from rez.packages import get_package_from_repository + >>> + >>> p = get_package_from_repository("python", "3.7.4", "/packages") + >>> p + Package(FileSystemPackageResource({'location': '/packages', 'name': 'python', 'repository_type': 'filesystem', 'version': '3.7.4'})) + >>> + >>> new_p = move_package(p, "/packages2") + >>> new_p + Package(FileSystemPackageResource({'location': '/packages2', 'name': 'python', 'repository_type': 'filesystem', 'version': '3.7.4'})) + >>> + >>> p = get_package_from_repository("python", "3.7.4", "/packages") + >>> p + None + +Be aware that a non-relocatable package is also not movable (see +:attr:`here `. Like package +copying, there is a ``force`` option to move it regardless. + +A typical reason you might want to move a package is to archive packages that are +no longer in use. In this scenario, you would move the package to some archival +package repository. In case an old runtime needs to be resurrected, you would add +this archival repository to the packages path before performing the resolve. + +.. note:: + You will probably want to use the :option:`--keep-timestamp ` option when doing this, + otherwise rez will think the package did not exist prior to its archival date. + +.. _removing-packages: + +Removing Packages +================= + +Packages can be removed. This is different from ignoring. The package and its +payload is deleted from storage, whereas ignoring just hides it. It is not +possible to un-remove a package. + +To remove a package via commandline: + +.. code-block:: console + + $ rez-rm --package python-3.7.4 /packages + +Via API: + +.. code-block:: python + + >>> from rez.package_remove import remove_package + >>> + >>> remove_package("python", "3.7.4", "/packages") + +During the removal process, package versions will first be ignored so that +partially-deleted versions are not visible. + +It can be useful to ignore packages that you don't want to use anymore, and +actually remove them at a later date. This gives you a safety buffer in case +current runtimes are using the package. They won't be affected if the package is +ignored, but could break if it is removed. + +To facilitate this workflow, :ref:`rez-rm` lets you remove all packages that have +been ignored for longer than N days (using the timestamp of the +:file:`.ignore{{version}}` file). Here we remove all packages that have been ignored +for 30 days or longer: + +.. code-block:: console + + $ rez-rm --ignored-since=30 -v + 14:47:09 INFO Searching filesystem@/home/ajohns/packages... + 14:47:09 INFO Removed python-3.7.4 from filesystem@/home/ajohns/packages + 1 packages were removed. + +Via API: + +.. code-block:: python + + >>> from rez.package_remove import remove_packages_ignored_since + >>> + >>> remove_packages_ignored_since(days=30) + 1 + +.. _package-caching: + +Package Caching +=============== + +Package caching is a feature that copies package payloads onto local disk in +order to speed up runtime environments. For example, if your released packages +reside on shared storage (which is common), then running say, a Python process, +will fetch all source from the shared storage across your network. The point of +the cache is to copy that content locally instead, and avoid the network cost. + +.. note:: + Please note: Package caching does **NOT** cache package + definitions. Only their payloads (ie, the package root directory). + +.. _enabling-package-caching: + +Enabling Package Caching +======================== + +Package caching is not enabled by default. To enable it, you need to configure +:data:`cache_packages_path` to specify a path to +store the cache in. + +You also have granular control over whether an individual package will or will +not be cached. To make a package cachable, you can set :attr:`cachable` +to False in its package definition file. Reasons you may *not* want to do this include +packages that are large, or that aren't relocatable because other compiled packages are +linked to them in a way that doesn't support library relocation. + +There are also config settings that affect cachability in the event that :attr:`cachable` +is not defined in a package's definition. For example, see +:data:`default_cachable`, :data:`default_cachable_per_package` +and :data:`default_cachable_per_repository`. + +Note that you can also disable package caching on the command line, using +:option:`rez-env --no-pkg-cache`. + +Verifying +--------- + +When you resolve an environment, you can see which variants have been cached by +noting the ``cached`` label in the right-hand column of the :ref:`rez-context` output, +as shown below: + +.. code-block:: console + + $ rez-env Flask + + You are now in a rez-configured environment. + + requested packages: + Flask + ~platform==linux (implicit) + ~arch==x86_64 (implicit) + ~os==Ubuntu-16.04 (implicit) + + resolved packages: + Flask-1.1.2 /home/ajohns/package_cache/Flask/1.1.2/d998/a (cached) + Jinja2-2.11.2 /home/ajohns/package_cache/Jinja2/2.11.2/6087/a (cached) + MarkupSafe-1.1.1 /svr/packages/MarkupSafe/1.1.1/d9e9d80193dcd9578844ec4c2c22c9366ef0b88a + Werkzeug-1.0.1 /home/ajohns/package_cache/Werkzeug/1.0.1/fe76/a (cached) + arch-x86_64 /home/ajohns/package_cache/arch/x86_64/6450/a (cached) + click-7.1.2 /home/ajohns/package_cache/click/7.1.2/0da2/a (cached) + itsdangerous-1.1.0 /home/ajohns/package_cache/itsdangerous/1.1.0/b23f/a (cached) + platform-linux /home/ajohns/package_cache/platform/linux/9d4d/a (cached) + python-3.7.4 /home/ajohns/package_cache/python/3.7.4/ce1c/a (cached) + +For reference, cached packages also have their original payload location stored to +an environment variable like so: + +.. code-block:: console + + $ echo $REZ_FLASK_ORIG_ROOT + /svr/packages/Flask/1.1.2/88a70aca30cb79a278872594adf043dc6c40af99 + +How it Works +------------ + +Package caching actually caches :doc:`variants`, not entire packages. When you perform +a resolve, or source an existing context, the variants required are copied to +local disk asynchronously (if they are cachable), in a separate process called +:ref:`rez-pkg-cache`. This means that a resolve will not necessarily use the cached +variants that it should, the first time around. Package caching is intended to have +a cumulative effect, so that more cached variants will be used over time. This is +a tradeoff to avoid blocking resolves while variant payloads are copied across +your network (and that can be a slow process). + +Note that a package cache is **not** a package repository. It is simply a store +of variant payloads, structured in such a way as to be able to store variants from +any package repository, into the one shared cache. + +Variants that are cached are assumed to be immutable. No check is done to see if +a variant's payload has changed, and needs to replace an existing cache entry. So +you should **not** enable caching on package repositories where packages may get +overwritten. It is for this reason that caching is disabled for local packages by +default (see :data:`package_cache_local`). + +Commandline Tool +---------------- + +Inspection +++++++++++ + +Use the :ref:`rez-pkg-cache` tool to view the state of the cache, and to perform +warming and deletion operations. Example output follows: + +.. code-block:: console + + $ rez-pkg-cache + Package cache at /home/ajohns/package_cache: + + status package variant uri cache path + ------ ------- ----------- ---------- + cached Flask-1.1.2 /svr/packages/Flask/1.1.2/package.py[0] /home/ajohns/package_cache/Flask/1.1.2/d998/a + cached Jinja2-2.11.2 /svr/packages/Jinja2/2.11.2/package.py[0] /home/ajohns/package_cache/Jinja2/2.11.2/6087/a + cached Werkzeug-1.0.1 /svr/packages/Werkzeug/1.0.1/package.py[0] /home/ajohns/package_cache/Werkzeug/1.0.1/fe76/a + cached arch-x86_64 /svr/packages/arch/x86_64/package.py[] /home/ajohns/package_cache/arch/x86_64/6450/a + cached click-7.1.2 /svr/packages/click/7.1.2/package.py[0] /home/ajohns/package_cache/click/7.1.2/0da2/a + cached itsdangerous-1.1.0 /svr/packages/itsdangerous/1.1.0/package.py[0] /home/ajohns/package_cache/itsdangerous/1.1.0/b23f/a + cached platform-linux /svr/packages/platform/linux/package.py[] /home/ajohns/package_cache/platform/linux/9d4d/a + copying python-3.7.4 /svr/packages/python/3.7.4/package.py[0] /home/ajohns/package_cache/python/3.7.4/ce1c/a + stalled MarkupSafe-1.1.1 /svr/packages/MarkupSafe/1.1.1/package.py[1] /home/ajohns/package_cache/MarkupSafe/1.1.1/724c/a + +Each variant is stored into a directory based on a partial hash of that variant's +unique identifier (its "handle"). The package cache is thread and multiprocess +proof, and uses a file lock to control access where necessary. + +Cached variants have one of the following statuses at any given time: + +* **copying**: The variant is in the process of being copied into the cache, and is not + yet available for use; +* **cached**: The variant has been cached and is ready for use; +* **stalled**: The variant was getting copied, but something went wrong and there is + now a partial copy present (but unused) in the cache. + +Logging ++++++++ + +Caching operations are stored into logfiles within the cache directory. To view: + +.. code-block:: console + + $ rez-pkg-cache --logs + rez-pkg-cache 2020-05-23 16:17:45,194 PID-29827 INFO Started daemon + rez-pkg-cache 2020-05-23 16:17:45,201 PID-29827 INFO Started caching of variant /home/ajohns/packages/Werkzeug/1.0.1/package.py[0]... + rez-pkg-cache 2020-05-23 16:17:45,404 PID-29827 INFO Cached variant to /home/ajohns/package_cache/Werkzeug/1.0.1/fe76/a in 0.202576 seconds + rez-pkg-cache 2020-05-23 16:17:45,404 PID-29827 INFO Started caching of variant /home/ajohns/packages/python/3.7.4/package.py[0]... + rez-pkg-cache 2020-05-23 16:17:46,006 PID-29827 INFO Cached variant to /home/ajohns/package_cache/python/3.7.4/ce1c/a in 0.602037 seconds + +Cleaning The Cache +++++++++++++++++++ + +Cleaning the cache refers to deleting variants that are stalled or no longer in use. +It isn't really possible to know whether a variant is in use, so there is a +configurable :data:`package_cache_max_variant_days` +setting, that will delete variants that have not been used (ie that have not appeared +in a created or sourced context) for more than N days. + +You can also manually remove variants from the cache using :option:`rez-pkg-cache -r`. +Note that when you do this, the variant is no longer available in the cache, +however it is still stored on disk. You must perform a clean (:option:`rez-pkg-cache --clean`) +to purge unused cache files from disk. + +You can use the :data:`package_cache_clean_limit` +setting to asynchronously perform some cleanup every time the cache is updated. If +you do not use this setting, it is recommended that you set up a cron or other form +of execution scheduler, to run :option:`rez-pkg-cache --clean` periodically. Otherwise, +your cache will grow indefinitely. + +Lastly, note that a stalled variant will not attempt to be re-cached until it is +removed by a clean operation. Using :data:`package_cache_clean_limit` will not clean +stalled variants either, as that could result in a problematic variant getting +cached, then stalled, then deleted, then cached again and so on. You must run +:option:`rez-pkg-cache --clean` to delete stalled variants. diff --git a/docs/source/package_commands.rst b/docs/source/package_commands.rst new file mode 100644 index 000000000..24b357d19 --- /dev/null +++ b/docs/source/package_commands.rst @@ -0,0 +1,782 @@ +================ +Package commands +================ + +Package definition files (``package.py``) usually define a :func:`.commands` section. This is a python +function that determines how the environment is configured in order to include the package. + +Consider the simple example: + +.. code-block:: python + + def commands(): + env.PYTHONPATH.append("{root}/python") + env.PATH.append("{root}/bin") + +This is a typical case, where a package adds its source path to ``PYTHONPATH``, and its tools to +``PATH``. The ``{root}`` string expands to the installation directory of the package. + +When a rez environment is configured, every package in the resolve list has its :func:`.commands` section +interpreted and converted into shell code (the language, bash or other, depends on the platform +and is extensible). The resulting shell code is sourced, and this configures the environment. +Within a configured environment, the variable :envvar:`REZ_CONTEXT_FILE` points at this shell code, and the +command :option:`rez-context --interpret` prints it. + +The python API that you use in the :func:`.commands` section is called ``rex`` (**R**\ez **EX**\ecution language). It +is an API for performing shell operations in a shell-agnostic way. Some common operations you would +perform with this API include setting environment variables, and appending/prepending path-like +environment variables. + +.. note:: + By default, environment variables that are not referenced by any package + are left unaltered. There will typically be many system variables that are left unchanged. + +.. warning:: + If you need to import any python modules to use in a :func:`.commands` + section, the import statements **must** be done inside that function. + +.. _package-commands-order-of-execution: + +Order Of Command Execution +========================== + +The order in which package commands are interpreted depends on two factors: the order in which +the packages were requested, and dependencies between packages. This order can be defined as: + +* If package ``A`` was requested before package ``B``, then ``A``'s commands are interpreted before ``B``'s; +* Unless package ``A`` requires (depends on) ``B``, in which case ``B`` will be interpreted before ``A``. + +Consider a package ``maya_anim_tool``. Let us say this is a maya plugin. Naturally it has a dependency +on ``maya``, therefore ``maya``'s commands will be interpreted first. This is because the maya plugin +may depend on certain environment variables that ``maya`` sets. For example, ``maya`` might initialize +the ``MAYA_PLUG_IN_PATH`` environment variable, and ``maya_anim_tool`` may then append to this +variable. + +For example, consider the request: + +.. code-block:: text + + ]$ rez-env maya_anim_tool-1.3+ PyYAML-3.10 maya-2015 + +Assuming that ``PyYAML`` depends on ``python``, and ``maya_anim_tool`` depends on ``maya``, then the +resulting :func:`.commands` execution order would be: + +* maya; +* maya_anim_tool; +* python; +* PyYAML. + +.. _variable-appending-and-prepending: + +Variable Appending And Prepending +================================= + +Path-like environment variables can be appended and prepended like so: + +.. code-block:: python + + env.PATH.append("{root}/bin") + +However, the first append/prepend operation on any given variable actually **overwrites** the +variable, rather than appending. Why does this happen? Consider ``PYTHONPATH``: if an initial +overwrite did not happen, then any modules visible on ``PYTHONPATH`` before the rez environment was +configured would still be there. This would mean you may not have a properly configured +environment. If your system ``PyQt`` were on ``PYTHONPATH`` for example, and you used :ref:`rez-env` to set +a different ``PyQt`` version, an attempt to import it within the configured environment would still, +incorrectly, import the system version. + +.. note:: + ``PATH`` is a special case. It is not simply overwritten, because if that + happened you would lose important system paths and thus utilities like ``ls`` and ``cd``. In this + case the system paths are appended back to ``PATH`` after all commands are interpreted. The system + paths are defined as the default value of ``PATH`` in a non-interactive shell. + +.. todo:: Add custom class for "construction"? + +.. admonition:: Noteasd + + Better control over environment variable initialization is + coming. Specifically, you will be able to specify various modes for variables. For example, one + mode will append the original (pre-rez) value back to the resulting value. + +.. _string-expansion: + +String Expansion +================ + +Object Expansion +---------------- + +Any of the objects available to you in a :func:`commands` section can be referred to in formatted strings +that are passed to rex functions such as :func:`setenv` and so on. For example, consider the code: + +.. code-block:: python + + appendenv("PATH", "{root}/bin") + +Here, ``{root}`` will expand out to the value of :attr:`root`, which is the installation path of the +package (:attr:`this.root` could also have been used). + +You don't *have* to use this feature. It is provided as a convenience. For example, the following +code is equivalent to the previous example, and is just as valid (but more verbose): + +.. code-block:: python + + import os.path + appendenv("PATH", os.path.join(root, "bin")) + +Object string expansion is also supported when setting an environment variable via the :attr:`env` object: + +.. code-block:: python + + env.FOO_LIC = "{this.root}/lic" + +Environment Variable Expansion +------------------------------ + +Environment variable expansion is also supported when passed to rex functions. Both syntax ``$FOO`` +and ``${FOO}`` are supported, regardless of the syntax supported by the target shell. + +Literal Strings +--------------- + +You can use the :func:`literal` function to inhibit object and environment variable string +expansion. For example, the following code will set the environment variable to the literal string: + +.. code-block:: python + + env.TEST = literal("this {root} will not expand") + +There is also an :func:`expandable` function, which matches the default behavior. You wouldn't typically +use this function. However, you can define a string containing literal and expandable parts by +chaining together :func:`literal` and :func:`expandable`: + +.. code-block:: python + + env.DESC = literal("the value of {root} is").expandable("{root}") + +.. _explicit-string-expansion: + +Explicit String Expansion +------------------------- + +Object string expansion usually occurs **only** when a string is passed to a rex function, or to +the :attr:`env` object. For example the simple statement ``var = "{root}/bin"`` would not expand ``{root}`` +into ``var``. However, you can use the :func:`expandvars` function to enable this behavior +explicitly: + +.. code-block:: python + + var = expandvars("{root}/bin") + +The :func:`expandvars` and :func:`expandable` functions are slightly different. :func:`expandable` will generate a +shell variable assignment that will expand out while :func:`expandvars` will expand the value immediately. + +This table illustrates the difference between :func:`literal`, :func:`expandable` and :func:`expandvars`: + +=================================== ======================= +Package command Equivalent bash command +=================================== ======================= +``env.FOO = literal("${USER}")`` ``export FOO='${USER}'`` +``env.FOO = expandable("${USER}")`` ``export FOO="${USER}"`` +``env.FOO = expandvars("${USER}")`` ``export FOO="jbloggs"`` +=================================== ======================= + +.. admonition:: Additional context + :class: admonition note + + In Bash, single quote strings (``'foo'``) will not be expanded. + +Filepaths +========= + +Rez expects POSIX-style filepath syntax in package commands, regardless of the shell or platform. +Thus, even if you're on Windows, you should do this: + +.. code-block:: python + + def commands(): + env.PATH.append("{root}/bin") # note the forward slash + +Where necessary, filepaths will be automatically normalized for you. That is, converted into +the syntax expected by the shell. In order for this to work correctly however, rez needs to know +what environment variables are actually paths. You determine this with the +:data:`pathed_env_vars` config setting. By default, any environment +variable ending in ``PATH`` will be treated as a filepath or list of filepaths, and any +set/append/prepend operation on it will cause those values to be path-normalized automatically. + +.. warning:: + Avoid using :data:`os.pathsep` or hardcoded lists of paths such as + ``{root}/foo:{root}/bah``. Doing so can cause your package to be incompatible with some shells or + platforms. Even the seemingly innocuous :data:`os.pathsep` is an issue, because there are some cases + (eg Git for Windows, aka git-bash) where the shell's path separator does not match the underlying + system's. + +Pre And Post Commands +===================== + +Occasionally, it's useful for a package to run commands either before or after all other packages, +regardless of the command execution order rules. This can be achieved by defining a :func:`pre_commands` +or :func:`post_commands` function. A package can have any, all or none of :func:`pre_commands`, :func:`commands` and +:func:`post_commands` defined, although it is very common for a package to define just :func:`commands`. + +The order of command execution is: + +* All package :func:`pre_commands` are executed, in standard execution order; +* Then, all package :func:`commands` are executed, in standard execution order; +* Then, all package :func:`post_commands` are executed, in standard execution order. + +.. _pre-build-commands: + +Pre Build Commands +================== + +If a package is being built, that package's commands are not run, simply because that package is +not present in its own build environment! However, sometimes there is a need to run commands +specifically for the package being built. For example, you may wish to set some environment +variables to pass information along to the build system. + +The :func:`pre_build_commands` function does just this. It is called prior to the build. Note that info +about the current build (such as the installation path) is available in a +:attr:`build` object (other commands functions do not have this object visible). + +.. _pre-test-commands: + +Pre Test Commands +================= + +Sometimes it's useful to perform some extra configuration in the environment that a package's test +will run in. You can define the :func:`pre_test_commands` function to do this. It will be invoked just +before the test is run. As well as the standard :attr:`this` object, a :attr:`test` object is also +provided to distinguish which test is about to run. + +A Largish Example +================= + +Here is an example of a package definition with a fairly lengthy :func:`commands` section: + +.. code-block:: python + + name = "foo" + + version = "1.0.0" + + requires = [ + "python-2.7", + "~maya-2015" + ] + + def commands(): + import os.path # imports MUST be inline to the function + + # add python module, executables + env.PYTHONPATH.append("{this.root}/python") + env.PATH.append("{this.root}/bin") + + # show include path if a build is occurring + if building: + env.FOO_INCLUDE_PATH = "{this.root}/include" + + # debug support to point at local config + if defined("DEBUG_FOO"): + conf_file = os.path.expanduser("~/.foo/config") + else: + conf_file = "{this.root}/config" + env.FOO_CONFIG_FILE = conf_file + + # if maya is in use then include the maya plugin part of this package + if "maya" in resolve: + env.MAYA_PLUG_IN_PATH.append("{this.root}/maya/plugins") + + if resolve.maya.version.minor == "sp3": + error("known issue with GL renderer in service pack 3, beware") + + # license file per major version + env.FOO_LIC = "/lic/foo_{this.version.major}.lic" + +Objects +======= + +Various objects and functions are available to use in the :func:`commands` function (as well as +:func:`pre_commands` and :func:`post_commands`). + +Following is a list of the objects and functions available. + +.. .. currentmodule:: pkgdefrex + +.. py:function:: alias() + + Create a command alias. + + .. code-block:: python + + alias("nukex", "Nuke -x") + + .. note:: + In ``bash``, aliases are implemented as bash functions. + +.. py:attribute:: base + :type: str + + See :attr:`this.base`. + +.. py:attribute:: build + + This is a dict like object. Each key can also be accessed as attributes. + + This object is only available in the :func:`pre_build_commands` + function. It has the following fields: + + .. code-block:: python + + if build.install: + info("An installation is taking place") + + if build['build_type'] == 'local': + pass + +.. py:attribute:: build.build_type + :type: typing.Literal['local', 'central'] + + One of ``local``, ``central``. The type is ``central`` if a package release is occurring, and ``local`` + otherwise. + +.. py:attribute:: build.install + :type: bool + + True if an installation is taking place, False otherwise. + +.. py:attribute:: build.build_path + :type: str + + Path to the build directory (not the installation path). This will typically reside somewhere + within the ``./build`` subdirectory of the package being built. + +.. py:attribute:: build.install_path + :type: str + + Installation directory. Note that this will be set, even if an installation is **not** taking place. + + .. warning:: + Do not check this variable to detect if an installation is occurring. Use :attr:`build.install` instead. + +.. py:attribute:: building + :type: bool + + This boolean variable is ``True`` if a build is occurring (typically done via the :ref:`rez-build` tool), + and ``False`` otherwise. + + However, the :func:`commands` block is only executed when the package is brought + into a resolved environment, so this is not used when the package itself is building. Typically a + package will use this variable to set environment variables that are only useful during when other + packages are being built. C++ header include paths are a good example. + + .. code-block:: python + + if building: + env.FOO_INCLUDE_PATH = "{root}/include" + +.. py:function:: command(arg: str) + + Run an arbitrary shell command. + + Example: + + .. code-block:: python + + command("rm -rf ~/.foo_plugin") + + .. note:: + Note that you cannot return a value from this function call, because + *the command has not yet run*. All of the packages in a resolve only have their commands executed + after all packages have been interpreted and converted to the target shell language. Therefore any + value returned from the command, or any side effect the command has, is not visible to any package. + + You should prefer to perform simple operations (such as file manipulations and so on) in python + where possible instead. Not only does that take effect immediately, but it's also more cross + platform. For example, instead of running the command above, we could have done this: + + .. code-block:: python + + def commands(): + import shutil + import os.path + path = os.path.expanduser("~/.foo_plugin") + if os.path.exists(path): + shutil.rmtree(path) + +.. py:function:: comment(arg: str) + + Creates a comment line in the converted shell script code. This is only visible if the user views + the current shell's code using the command :option:`rez-context --interpret` or looks at the file + referenced by the environment variable :envvar:`REZ_CONTEXT_FILE`. You would create a comment for debugging + purposes. + + .. code-block:: python + + if "nuke" in resolve: + comment("note: taking over 'nuke' binary!") + alias("nuke", "foo_nuke_replacer") + + +.. py:function:: defined(envvar: str) -> bool + + Use this boolean function to determine whether or not an environment variable is set. + + .. code-block:: python + + if defined("REZ_MAYA_VERSION"): + env.FOO_MAYA = 1 + +.. py:attribute:: env + :type: dict + + The ``env`` object represents the environment dict of the configured environment. Environment variables + can also be accessed as attributes. + + .. note:: + Note that this is different from the standard python :data:`os.environ` dict, which represents the current environment, + not the one being configured. If a prior package's :func:`commands` sets a variable via the ``env`` object, + it will be visible only via ``env``, not :data:`os.environ`. The :data:`os.environ` dict hasn't been updated because the target + configured environment does not yet exist! + + .. code-block:: python + + env.FOO_DEBUG = 1 + env["BAH_LICENSE"] = "/lic/bah.lic" + +.. py:function:: env.append(value: str) + + Appends a value to an environment variable. By default this will use the :data:`os.pathsep` delimiter + between list items, but this can be overridden using the config setting :data:`env_var_separators`. See + :ref:`variable-appending-and-prepending` for further information on the behavior of this function. + + .. code-block:: python + + env.PATH.append("{root}/bin") + +.. py:function:: env.prepend(value: str) + + Like :func:`env.append`, but prepends the environment variable instead. + + .. code-block:: python + + env.PYTHONPATH.prepend("{root}/python") + +.. py:attribute:: ephemerals + + A dict like object representing the list of ephemerals in the resolved environment. Each item is a + string (the full request, eg ``.foo.cli-1``), keyed by the ephemeral package name. Note + that you do **not** include the leading ``.`` when getting items from the ``ephemerals`` + object. + + Example: + + .. code-block:: python + + if "foo.cli" in ephemerals: + info("Foo cli option is being specified!") + +.. py:function:: ephemerals.get_range(name: str, range_: str) -> ~rez.vendor.version.version.VersionRange + + Use ``get_range`` to test with the :func:`intersects` function. + Here, we enable ``foo``'s commandline tools by default, unless explicitly disabled via + a request for ``.foo.cli-0``: + + .. code-block:: python + + if intersects(ephemerals.get_range("foo.cli", "1"), "1"): + info("Enabling foo cli tools") + env.PATH.append("{root}/bin") + +.. py:function:: error(message: str) + + Prints to standard error. + + .. note:: + This function just prints the error, it does not prevent the target + environment from being constructed (use the :func:`stop`) command for that). + + .. code-block:: python + + if "PyQt" in resolve: + error("The floob package has problems running in combo with PyQt") + +.. py:function:: expandable(arg: str) -> ~rez.rex.EscapedString + + See :ref:`explicit-string-expansion`. + +.. py:function:: expandvars(arg: str) + + See :ref:`explicit-string-expansion`. + +.. py:function:: getenv(envvar: str) + + Gets the value of an environment variable. + + .. code-block:: python + + if getenv("REZ_MAYA_VERSION") == "2016.sp1": + pass + + :raises RexUndefinedVariableError: if the environment variable is not set. + +.. py:attribute:: implicits + + A dict like object that is similar to the :attr:`request` object, but it contains only the package request as + defined by the :data:`implicit_packages` configuration setting. + + .. code-block:: python + + if "platform" in implicits: + pass + +.. py:function:: info(message: str) + + Prints to standard out. + + .. code-block:: python + + info("floob version is %s" % resolve.floob.version) + +.. py:function:: intersects(range1: str | ~rez.vendor.version.version.VersionRange | ~rez.rex_bindings.VariantBinding | ~rez.rex_bindings.VersionBinding, range2: str) -> bool + + A boolean function that returns True if the version or version range of the given + object, intersects with the given version range. Valid objects to query include: + + * A resolved package, eg ``resolve.maya``; + * A package request, eg ``request.foo``; + * A version of a resolved package, eg ``resolve.maya.version``; + * A resolved ephemeral, eg ``ephemerals.foo``; + * A version range object, eg ``ephemerals.get_range('foo.cli', '1')`` + + .. warning:: + Do **not** do this: + + .. code-block:: python + + if intersects(ephemerals.get("foo.cli", "0"), "1"): + ... + + .. todo:: document request.get_range + + If ``foo.cli`` is not present, this will unexpectedly compare the unversioned + package named ``0`` against the version range ``1``, which will succeed! Use + :func:`ephemerals.get_range` and ``request.get_range`` functions instead: + + .. code-block:: python + + if intersects(ephemerals.get_range("foo.cli", "0"), "1"): + ... + + Example: + + .. code-block:: python + + if intersects(resolve.maya, "2019+"): + info("Maya 2019 or greater is present") + +.. py:function:: literal(arg: str) -> ~rez.rex.EscapedString + + Inhibits expansion of object and environment variable references. + + .. code-block:: python + + env.FOO = literal("this {root} will not expand") + + You can also chain together ``literal`` and :func:`expandable` functions like so: + + .. code-block:: python + + env.FOO = literal("the value of {root} is").expandable("{root}") + +.. py:function:: optionvars(name: str, default: typing.Any | None = None) -> typing.Any + + A :meth:`dict.get` like function for package accessing arbitrary data from :data:`optionvars` in rez config. + +.. py:attribute:: request + :type: ~rez.rex_bindings.RequirementsBinding + + A dict like object representing the list of package requests. Each item is a request string keyed by the + package name. For example, consider the package request: + + .. code-block:: text + + ]$ rez-env maya-2015 maya_utils-1.2+<2 !corelib-1.4.4 + + This request would yield the following ``request`` object: + + .. code-block:: python + + { + "maya": "maya-2015", + "maya_utils": "maya_utils-1.2+<2", + "corelib": "!corelib-1.4.4" + } + + Use ``get_range`` to test with the :func:`intersects` function: + + if intersects(request.get_range("maya", "0"), "2019"): + info("maya 2019.* was asked for!") + + Example: + + .. code-block:: python + + if "maya" in request: + info("maya was asked for!") + + .. tip:: + If multiple requests are present that refer to the same package, the + request is combined ahead of time. In other words, if requests ``foo-4+`` and ``foo-<6`` were both + present, the single request ``foo-4+<6`` would be present in the ``request`` object. + +.. py:function:: resetenv(envvar: str, value: str, friends=None) -> None + + TODO: Document + +.. py:attribute:: resolve + + A dict like object representing the list of packages in the resolved environment. Each item is a + :ref:`Package ` object, keyed by the package name. + + Packages can be accessed using attributes (ie ``resolve.maya``). + + .. code-block:: python + + if "maya" in resolve: + info("Maya version is %s", resolve.maya.version) + # ..or resolve["maya"].version + +.. py:attribute:: root + :type: str + + See :attr:`this.root`. + +.. py:function:: setenv(envvar: str, value: str) + + This function sets an environment variable to the given value. It is equivalent to setting a + variable via the :attr:`env` object (eg, ``env.FOO = 'BAH'``). + + .. code-block:: python + + setenv("FOO_PLUGIN_PATH", "{root}/plugins") + +.. py:function:: source(path: str) -> None + + Source a shell script. Note that, similarly to :func:`commands`, this function cannot return a value, and + any side effects that the script sourcing has is not visible to any packages. For example, if the + ``init.sh`` script below contained ``export FOO=BAH``, a subsequent test for this variable on the + :attr:`env` object would yield nothing. + + .. code-block:: python + + source("{root}/scripts/init.sh") + +.. py:attribute:: stop(message: str) -> typing.NoReturn + + Raises an exception and stops a resolve from completing. You should use this when an unrecoverable + error is detected and it is not possible to configure a valid environment. + + .. code-block:: python + + stop("The value should be %s", expected_value) + +.. py:attribute:: system + :type: ~rez.system.System + + This object provided system information, such as current platform, arch and os. + + .. code-block:: python + + if system.platform == "windows": + ... + +.. py:attribute:: test + + Dict like object to access test related attributes. Only available in the :func:`pre_test_commands` function. + Keys can be accessed as object attributes. + +.. py:attribute:: test.name + :type: str + + Name of the test about to run. + + .. code-block:: python + + if test.name == "unit": + info("My unit test is about to run yay") + +.. py:attribute:: this + + The ``this`` object represents the current package. The following attributes are most commonly used + in a :func:`commands`) section (though you have access to all package attributes. See :ref:`here `): + + .. py:attribute:: this.base + :type: str + + Similar to :attr:`this.root`, but does not include the variant subpath, if there is one. Different + variants of the same package share the same :attr:`base` directory. See :doc:`here ` for more + information on package structure in relation to variants. + + .. py:attribute:: this.is_package + :type: bool + + .. todo:: Document + + TODO: Document + + .. py:attribute:: this.is_variant + :type: bool + + .. todo:: Document + + TODO: Document + + .. py:attribute:: this.name + :type: str + + The name of the package, eg ``houdini``. + + .. py:attribute:: this.root + :type: str + + The installation directory of the package. If the package contains variants, this path will include + the variant subpath. This is the directory that contains the installed package payload. See + :doc:`here ` for more information on package structure in relation to variants. + + .. py:attribute:: this.version + :type: ~rez.rex_bindings.VersionBinding + + The package version. It can be used as a string, however you can also access specific tokens in the + version (such as major version number and so on), as this code snippet demonstrates: + + .. code-block:: python + + env.FOO_MAJOR = this.version.major # or, this.version[0] + + The available token references are ``this.version.major``, ``this.version.minor`` and + ``this.version.patch``, but you can also use a standard list index to reference any version token. + +.. py:function:: undefined(envvar: str) -> bool + + Use this boolean function to determine whether or not an environment variable is set. This is the + opposite of :func:`defined`. + + .. code-block:: python + + if undefined("REZ_MAYA_VERSION"): + info("maya is not present") + +.. py:function:: unsetenv(envvar: str) -> None + + Unsets an environment variable. This function does nothing if the environment variable was not set. + + .. code-block:: python + + unsetenv("FOO_LIC_SERVER") + +.. py:attribute:: version + :type: ~rez.rex_bindings.VersionBinding + + See :attr:`this.version`. diff --git a/docs/source/package_definition.rst b/docs/source/package_definition.rst new file mode 100644 index 000000000..566ff4278 --- /dev/null +++ b/docs/source/package_definition.rst @@ -0,0 +1,1116 @@ +================== +Package definition +================== + +Packages are defined by a *package definition file*. This is typically a file named ``package.py`` +that is located in the root directory of each package install. For example, given package +repository location :file:`/packages/inhouse`, the package definition file for package "foo-1.0.0" would +be :file:`/packages/inhouse/foo/1.0.0/package.py`. + +Here is an example package definition file: + +.. code-block:: python + + name = 'sequence' + + version = '2.1.2' + + description = 'Sequence detection library.' + + authors = ['ajohns'] + + tools = [ + 'lsq', + 'cpq' + ] + + requires = [ + 'python-2.6+<3', + 'argparse' + ] + + def commands(): + env.PATH.append("{root}/bin") + env.PYTHONPATH.append("{root}/python") + + uuid = '6c43d533-92bb-4f8b-b812-7020bf54d3f1' + +Attributes +========== + +Every variable defined in the package definition file becomes an attribute on the built or +installed package. This includes attributes that are not in the +:ref:`standard-package-attributes`. You can add any custom attribute to a package. + +Some variables are not, however, added as package attributes. Consider the following package +definition snippet: + +.. code-block:: python + + import sys + + description = "This package was built on %s" % sys.platform + +Here we do not want ``sys`` to become a package attribute, because providing a python module as a +package attribute is nonsensical. + +Python variables that do **not** become package attributes include: + +* Python modules; +* Functions, not including :ref:`early ` and :ref:`late ` + binding functions (see next), and not including the :attr:`commands` and related functions; +* Any variable with a leading double underscore; +* Any variable that is a :ref:`build-package-attributes`. + +Function Attributes +------------------- + +Package attributes can be implemented as functions. The return value of the function becomes +the attribute value. There are two types of attribute functions: *early binding* functions, +and *late binding* functions - and these are decorated using ``@early`` and ``@late`` respectively. + +.. warning:: + The :func:`commands` functions are an exception to the rule. They are + late bound, but are not the same as a standard function attribute, and are **never** decorated + with the early or late decorators. + +.. _package-definition-early-binding-functions: + +Early Binding Functions ++++++++++++++++++++++++ + +Early binding functions use the ``@early`` decorator. They are evaluated at *build time*, hence the +'early' in 'early binding'. Any package attribute can be implemented as an early binding function. + +Here is an example of an :attr:`authors` attribute that is automatically set to the contributors of the +package's git project: + +.. code-block:: python + + @early() + def authors(): + import subprocess + p = subprocess.Popen("git shortlog -sn | cut -f2", + shell=True, stdout=subprocess.PIPE) + out, _ = p.communicate() + return out.strip().split('\n') + +.. note:: + You can assume that during evaluation of early binding functions, the + current working directory is the root directory containing your ``package.py``. + +An early bound function can also have access to other package attributes. To do this, use the +implicit :attr:`this` object: + +.. code-block:: python + + @early() + def description(): + # a not very useful description + return "%s version %s" % (this.name, this.version) + +.. warning:: + Do not reference other early bound or late bound attributes in + your early bound function. An error will be raised if you do. + +Early binding functions are a convenience. You can always use an arbitrary function instead, like so: + +.. code-block:: python + + def _description(): + return "%s version %s" % (this.name, this.version) + + description = _description() + +However, using early binding results in a package definition that is cleaner and more explicit. It +is clear that an attribute is intended to be evaluated at build time, and you avoid the need to +define an arbitrary function earlier in the python source. You can always use a combination of the +two as well. An early binding function can call an arbitrary function defined at the bottom of +your definition file. + +Available Objects +***************** + +Following is the list of objects that are available during early evaluation. + +.. todo:: Document these properly with py:attribute? + +* **building**: See :attr:`building`; +* **build_variant_index**: The index of the variant currently being built. This is only relevant if + :attr:`building` is True. +* **build_variant_requires**: The subset of package requirements specific to the variant + currently being built. This is a list of ``PackageRequest`` objects. This is only relevant if + :attr:`building` is True. +* **this**: The current package, as described previously. + +Be aware that early-bound functions are actually evaluated multiple times during a build: once +pre-build, and once per variant, during its build. This is necessary in order for early-bound +functions to change their return value based on variables like ``build_variant_index``. Note that the +*pre-build* evaluated value is the one set into the installed package, and in this case, ``building`` +is False. + +An example of where you'd need to be aware of this is if you wanted the :attr:`requires` field to include +a certain package at runtime only (ie, not present during the package build). In this case, :attr:`requires` +might look like so: + +.. code-block:: python + + @early() + def requires(): + if building: + return ["python-2"] + else: + return ["runtimeonly-1.2", "python-2"] + +.. warning:: + You **must** ensure that your early-bound function returns the value + you want to see in the installed package, when ``building`` is False. + +.. _package-definition-late-binding-functions: + +Late Binding Functions +++++++++++++++++++++++ + +Late binding functions stay as functions in the installed package definition, and are only evaluated +lazily, when the attribute is accessed for the first time (the return value is then cached). + +Not any attribute can be implemented as a late binding function. The allowed attributes are: + +* requires +* build_requires +* private_build_requires +* tools +* help +* any arbitrary attribute + +Here is an example of a late binding :attr:`tools` attribute: + +.. code-block:: python + + @late() + def tools(): + import os + + # get everything in bin dir + binpath = os.path.join(this.root, "bin") + result = os.listdir(binpath) + + # we don't want artists to see the admin tools + if os.getenv("_USER_ROLE") != "superuser": + result = set(result) - set(["delete-all", "mod-things"]) + + return list(result) + +.. warning:: + Late binding function attributes **must** perform any necessary imports + **within** the function, not at the top of the ``package.py`` file. + +Note that, if this function just returned the binaries found in the bin dir, it would have made +more sense to implement this as an :ref:`early binding ` function. +No code evaluation has to happen at runtime then, so it's cheaper. However, here a modification +is made based on the value of the ``_USER_ROLE`` environment variable, which isn't known at build time. + +If some information for an attribute could be calculated once at build time, you can reduce the +runtime cost by storing that part into an early binding arbitrary attribute. For example, we could +reimplement the above example like so: + +.. code-block:: python + + @late() + def tools(): + import os + result = this._tools + + # we don't want artists to see the admin tools + if os.getenv("_USER_ROLE") != "superuser": + result = set(result) - set(["delete-all", "mod-things"]) + + return list(result) + + @early() + def _tools(): + import os + return os.listdir("./bin") + +.. todo:: Make this.root and co terms or something else like data? + +Note how in the ``_tools`` function we're referring to a relative path. Remember that early binding +functions are evaluated at build time. The package hasn't actually been built or installed yet, +so attributes such as :attr:`this.root` don't exist. + +.. _in_context: + +The in_context Function +*********************** + +When late binding functions are evaluated, a boolean function ``in_context`` is present, which +returns ``True`` if the package is part of a resolved context, or ``False`` otherwise. For example, +if you just use the rez API to iterate over packages (as the :ref:`rez-search` tool does), these +packages do not belong to a context. However if you create a :class:`~rez.resolved_context.ResolvedContext` object (as +the :ref:`rez-env` tool does) and iterate over its resolved packages, these belong to a context. + +The in-context or not-in-context distinction is important, because often the package attribute +will need information from the context to give desired behavior. For example, consider the +late binding :attr:`tools` attribute below: + +.. code-block:: python + + @late() + def tools(): + result = ["edit"] + + if in_context() and "maya" in request: + result.append("maya-edit") + + return result + +Here the :attr:`request` object is being checked to see if the ``maya`` package was requested in the +current env; if it was, a maya-specific tool ``maya-edit`` is added to the tool list. + +.. warning:: + Always ensure your late binding function returns a sensible + value regardless of whether :ref:`in_context ` is ``True`` or ``False``. + Otherwise, simply trying to query the package attributes (using :ref:`rez-search` for example) + may cause errors. + +Available Objects +***************** + +Following is the list of objects that are available during late evaluation, if :ref:`in_context ` +is ``True``: + +* **context**: the :class:`~rez.resolved_context.ResolvedContext` instance this package belongs to; +* **system**: see :attr:`system`; +* **building**: see :attr:`building`; +* **request**: see :attr:`request`; +* **implicits**: see :attr:`implicits`. + +The following objects are available in **all** cases: + +* :attr:`this`: the current package/variant (see note below); +* **in_context**: the :ref:`in_context ` function itself. + +.. warning:: + The :attr:`this` object may be either a package or a variant, + depending on the situation. For example, if :ref:`in_context ` is ``True``, + then :attr:`this` is a variant, because variants are the objects present in a resolved context. On the other + hand, if a package is accessed via API (for example, by using the :ref:`rez-search` tool), + then :attr:`this` may be a package. The difference matters, because variants have some + attributes that packages don't, notably, ``root`` and ``index``. Use the properties + :attr:`this.is_package` and :attr:`this.is_variant` to distinguish the case if needed. + +Example - Late Bound build_requires +*********************************** + +Here is an example of a ``package.py`` with a late-bound :attr:`build_requires` field: + +.. code-block:: python + + name = "maya_thing" + + version = "1.0.0" + + variants = [ + ["maya-2017"], + ["maya-2018"] + ] + + @late() + def build_requires(): + if this.is_package: + return [] + elif this.index == 0: + return ["maya_2017_build_utils"] + else: + return ["maya_2018_build_utils"] + +.. todo:: Figure out why I can't link to this.is_package + +Note the check for :attr:`this.is_package`. This is necessary, otherwise the evaluation would +fail in some circumstances. Specifically, if someone ran the following command, the :attr:`this` +field would actually be a :class:`.Package` instance, which doesn't have an ``index`` method: + +.. code-block:: text + + ]$ rez-search maya_thing --type package --format '{build_requires}' + +In this case, :attr:`build_requires` is somewhat nonsensical (there is no common build requirement +for both variants here), but something needs to be returned nonetheless. + +.. _package-definition-sharing-code: + +Sharing Code Across Package Definition Files +============================================ + +It is possible to share common code across package definition function attributes, but the +mechanism that is used is different depending on whether a function is early binding or late +binding. This is to avoid installed packages being dependent on external code that may change +at any time; builds being dependent on external code is not problematic however. + +Sharing Code During A Build +--------------------------- + +Functions in a ``package.py`` file which are evaluated at build time include: + +* The :attr:`preprocess` function; +* Any package attribute implemented as a function using the :ref:`@early ` decorator. + +You expose common code to these functions by using the +:data:`package_definition_build_python_paths` config setting. + +Sharing Code Across Installed Packages +-------------------------------------- + +Functions that are evaluated in installed packages' definition files include: + +.. todo:: Group all commands in one section? + +* The various :doc:`commands ` functions; +* Any package attribute implemented as a function using the :ref:`@late ` decorator. + +You expose common code to these functions by using the ``@include`` decorator, which relies on the +:data:`package_definition_python_path` config setting. +The module source files are actually copied into each package's install payload, so the package +stays self-contained, and will not break or change behavior if the original modules' source +files are changed. The downside though, is that these modules are not imported, and they themselves +cannot import other modules managed in the same way. + +Here is an example of a package's :attr:`commands` using a shared module: + +.. code-block:: python + + # in package.py + @include("utils") + def commands(): + utils.set_common_env_vars(this, env) + +.. _requirements-expansion: + +Requirements Expansion +====================== + +Often a package may be compatible with a broader range of its dependencies at build time than it is +at runtime. For example, a C++ package may build against any version of ``boost-1``, but may +then need to link to the specific minor version that it was built against, say ``boost-1.55``. + +You can describe this in your package's :attr:`requires` attribute (or any of the related attributes, +such as :attr:`build_requires`) by using wildcards as shown here: + +.. code-block:: python + + requires = [ + "boost-1.*" + ] + +If you check the ``package.py`` of the built package, you will see that the boost reference in the +requires list will be expanded to the latest found within the given range (``boost-1.55`` for example). + +There is also a special wilcard available, ``**``. This expands to the full package version. For +example, the requirement ``boost-1.**`` might expand to ``boost-1.55.1``. + +You can also achieve requirements expansion by implementing :attr:`requires` as an early binding +function (and you may want to use some variation of this to generate :attr:`variants` for example), and +using the rez :func:`~rez.package_py_utils.expand_requires` function: + +.. code-block:: python + + @early() + def requires(): + from rez.package_py_utils import expand_requires + return expand_requires(["boost-1.*"]) + +.. _preprocess: + +.. _package-preprocessing: + +Package Preprocessing +===================== + +You can define a :func:`preprocess` function either globally or in a ``package.py``. This can be used to +validate a package, or even change some of its attributes, before it is built. To set a global +preprocessing function, see the :data:`package_preprocess_function` config setting. + +Consider the following preprocessing function, defined in a ``package.py``: + +.. code-block:: python + + def preprocess(this, data): + from rez.package_py_utils import InvalidPackageError + import re + + if not re.match("[a-z]+$", this.name): + raise InvalidPackageError("Invalid name, only lowercase letters allowed") + + if not this.authors: + from preprocess_utils import get_git_committers + data["authors"] = get_git_committers() + +This preprocessor checks the package name against a regex and sets the package authors list to its +git committers, if not already supplied in the ``package.py``. To update package attributes, you have +to update the given ``data`` dict, **not** the package instance (:attr:`this`). + +To halt a build because a package is not valid, you must raise an :exc:`~rez.exceptions.InvalidPackageError` as shown +above. + +.. hint:: + To see the preprocessed contents of a package.py, run the command + ``rez-build --view-pre`` in the source root directory. This will just print the preprocessed + package to standard out, then exit. + +Overriding Config Settings In Preprocessing +------------------------------------------- + +It is not uncommon to override config settings such as the release path in a package, like so: + +.. code-block:: python + + # in package.py + with scope("config") as c: + c.release_packages_path = "/software/packages/external" + +Let's say we have a scenario where we want to install third party packages to a specific install +path, and that we set the arbitrary attribute ``external`` to ``True`` for these packages. We could do +this with a global preprocessing function like this: + +.. code-block:: python + + def preprocess(this, data): + if not data.get("external"): + return + + try: + _ = data["config"]["release_packages_path"] + return # already explicitly specified by package + except KeyError: + pass + + data["config"] = data.get("config", {}) + data["config"]["release_packages_path"] = "/software/packages/external" + +The ``with scope(...)`` statement is just a fancy way of defining a dict, so you can do the same +thing in the preprocess function simply by updating the ``config`` dict within ``data``. + +See :ref:`configuring-rez-package-overrides` for more details on the ``scope`` function. + +Example Package +=============== + +Here is an example package definition, demonstrating several features. This is an example of a +python package which, instead of actually installing python, detects the existing system python +installation instead, and binds that into a rez package. + +.. code-block:: python + + name = "python" + + @early() + def version(): + return this.__version + "-detected" + + authors = [ + "Guido van Rossum" + ] + + description = \ + """ + The Python programming language. + """ + + @early() + def variants(): + from rez.package_py_utils import expand_requires + requires = ["platform-**", "arch-**", "os-**"] + return [expand_requires(*requires)] + + @early() + def tools(): + version_parts = this.__version.split('.') + + return [ + "2to3", + "pydoc", + "python", + "python%s" % (version_parts[0]), + "python%s.%s" % (version_parts[0], version_parts[1]) + ] + + uuid = "recipes.python" + + def commands(): + env.PATH.append("{this._bin_path}") + + if building: + env.CMAKE_MODULE_PATH.append("{root}/cmake") + + # --- internals + + def _exec_python(attr, src): + import subprocess + + p = subprocess.Popen( + ["python", "-c", src], + stdout=subprocess.PIPE, stderr=subprocess.PIPE) + out, err = p.communicate() + + if p.returncode: + from rez.exceptions import InvalidPackageError + raise InvalidPackageError( + "Error determining package attribute '%s':\n%s" % (attr, err)) + + return out.strip() + + @early() + def _bin_path(): + return this._exec_python( + "_bin_path", + "import sys, os.path; print(os.path.dirname(sys.executable))") + + def _version(): + return _exec_python( + "version", + "import sys; print(sys.version.split()[0])") + + __version = _version() + +Note the following: + +.. todo:: Document which attributes supports automatic wildcard expansion? + +* :attr:`variants` is implemented as an early bound attribute, and uses :ref:`requirements-expansion` to + dynamically define the variant requirements. Even though only the :attr:`requires` and related attributes + natively expand wildcards, you can still use the :func:`~rez.package_py_utils.expand_requires` function + yourself, as illustrated here. +* A ``_version`` function has been defined, and its return value stored into the ``__version`` variable. + This is done because two other early binding attributes. :attr:`version` and :attr:`tools` use this value, + and we avoid calling the function twice. Both ``_version`` and ``__version`` are later stripped from + the package, because one is a normal function, and the other has double leading underscores. +* An arbitrary attribute ``_bin_path`` has been defined, and implemented as an early bound attribute. + The :attr:`commands` function then uses this value. In this example, it was far better to take this + approach than the alternative of running the python subprocess in the :attr:`commands` function. Doing that + would have been very costly, since commands are executed every time a new environment is created + (and launching a subprocess is slow). Instead, here we take this cost at build time, and cache the + result into the package attribute. +* Common code was provided in the normal function ``_exec_python``, which will be stripped from the + installed package. + +.. _package-attributes: + +Package Attributes +================== + +.. _standard-package-attributes: + +Standard Package Attributes +--------------------------- + +Following is a list, in alphabetical order, of every standard attribute that a user can define in a +package definition file (you can also define your own arbitrary attributes). Each entry specifies +the data type, and includes a code snippet. + +.. .. currentmodule:: pkgdef + +.. py:attribute:: authors + :type: list[str] + + Package authors. Should be in order, starting with the major contributor. + + .. code-block:: python + + authors = ["jchrist", "sclaus"] + +.. py:attribute:: build_requires + :type: list[str] + + This is the same as :attr:`requires`, except that these dependencies are only included during a build + (typically invoked using the :ref:`rez-build` tool). + + .. code-block:: python + + build_requires = [ + "cmake-2.8", + "doxygen" + ] + +.. py:attribute:: cachable + :type: bool + + Determines whether a package can be cached when :ref:`package-caching` is enabled. + If not provided, this is determined from the global config setting :data:`default_cachable` and related ``default_cachable_*`` settings. + + .. code-block:: python + + cachable = True + +.. py:function:: commands() -> None + + This is a block of python code which tells rez how to update an environment so that this package + can be used. It is executed when the package is brought into a rez environment, either by explicit + request or by another package's requirements. There is a python API provided (see + :doc:`package_commands` for more details) that lets you do things such as: + + * set, unset, prepend and append environment variables; + * create aliases; + * source scripts; + * print messages. + + In this example, the ``foo`` package is appending a path to ``PYTHONPATH``, and appending a path to + ``PATH``. The special string ``{root}`` will expand out to the install location of the package (see :ref:`string-expansion`). + This is a fairly typical example. + + .. code-block:: python + + def commands(): + env.PYTHONPATH.append("{root}/python") + env.PATH.append("{root}/bin") + +.. py:attribute:: config + :type: dict[str, typing.Any] + + Packages are able to override rez configuration settings. This is useful in some cases. For example, + we may want a package to release to a different directory than the default (as this example shows). + See :ref:`here ` for more details. + + .. note:: + ``config`` should not be modified as is. You need to use the ``scope`` function to manipulate it. + + .. code-block:: python + + with scope("config"): + release_packages_path = "/software/packages/apps" + +.. py:attribute:: description + :type: str + + This is a general description of the package. It should not mention details about a particular + version of the package, just about the package in general. + + .. code-block:: python + + description = "Library for communicating with the dead." + +.. py:attribute:: has_plugins + :type: bool + + Indicates that the package is an application that may have plugins. These plugins are often made + available as rez packages also. Used in conjuction with the :ref:`rez-plugins` command. Also, see :attr:`plugin_for`. + + .. code-block:: python + + has_plugins = True + +.. py:attribute:: hashed_variants + :type: bool + + Instructs the package to install variants into a subdirectory based on a hash of the variant's + contents (its requirements in other words). This is useful for variants with a high number of + requirements, or with requirements that do not translate well to directories on the filesystem + (such as conflict requirements). + + .. code-block:: python + + hashed_variants = True + +.. py:attribute:: help + :type: str | list[list[str]] + + URL for package webpage, or, if a string containing spaces, a command to run. You can show the help + for a package using the :ref:`rez-help` command line tool. If this value is a list of list, then this + represents multiple help entries. + + .. code-block:: python + + help = "https://github.com/__GITHUB_REPO__/wiki" + + .. code-block:: + + help = [ + ['Documentation', 'https://example.com/docs'], + ['API docs', 'https://example.com/docs/api'] + ] + +.. py:attribute:: name + :type: str + + **Mandatory** + + This is the name of the package. Alphanumerics and underscores are allowed. Name is case sensitive. + + .. code-block:: python + + name = "maya_utils" + +.. py:attribute:: plugin_for + :type: str + + Provided if this package is a plugin of another package. For example, this might be a maya plugin. + This is useful when using the :ref:`rez-plugins` command. Also, see :attr:`has_plugins`. + + .. code-block:: python + + plugin_for = "maya" + +.. py:function:: post_commands() -> None + + Similar to :func:`pre_commands`, but runs in a final phase rather than the first. See that attribute for + further details. + + .. code-block:: python + + def post_commands(): + env.FOO_PLUGIN_PATH.append("@") + +.. py:function:: pre_commands() -> None + + This is the same as :func:`commands`, except that all packages' ``pre_commands`` are executed in a first + pass; then, all ``commands`` are run in a second; and lastly, ``post_commands`` are all run in a third + phase. It is sometimes useful to ensure that some of a package's commands are run before, or after + all others, and using pre/post_commands is a way of doing that. + + .. code-block:: python + + def pre_commands(): + import os.path + env.FOO_PLUGIN_PATH = os.path.join(this.root, "plugins") + +.. py:function:: pre_test_commands() + + This is similar to :func:`commands`, except that it is run prior to each test defined in + :attr:`tests`. See :ref:`pre-test-commands` for more details. + + .. code-block:: python + + def pre_test_commands(): + if test.name == "unit": + env.IS_UNIT_TEST = 1 + +.. py:attribute:: relocatable + :type: bool + + Determines whether a package can be copied to another package repository (using the :ref:`rez-cp` tool for + example). If not provided, this is determined from the global config setting :data:`default_relocatable` and + related ``default_relocatable_*`` settings. + + .. code-block:: python + + relocatable = True + +.. py:attribute:: requires + :type: list[str] + + This is a list of other packages that this package depends on. A rez package should list all the + packages it needs. Someone should be able to use your package without needing to know about how it + works internally and this includes needing to know its dependencies. + + Rez has a syntax for these package requests. For example, ``python-2.6`` is a package request which + covers the range of all python packages starting with 2.6, for example, ``python-2.6.0``, + ``python-2.6.4`` (it is not simply a prefix. ``python-2.65`` is not within the request). When you + request a package, you are asking rez for any version within this request, although rez will aim to + give you the latest possible version. + + .. hint:: For more details on request syntax, see :ref:`package-requests-concept`. + + .. code-block:: python + + requires = [ + "python-2", + "maya-2016", + "maya_utils-3.4+<4" + ] + +.. py:attribute:: tests + :type: dict[str, str | dict] + + This is a dict of tests that can be run on the package using the :ref:`rez-test` tool. + + If a test entry is a string or list of strings, this is interpreted as the command to run. Command + strings will expand any references to package attributes, such as ``{root}``. + + If you provide a nested dict, you can specify extra fields per test, as follows: + + * ``requires``: Extra package requirements to include in the test's runtime env. + * ``run_on``: When to run this test. Valid values are: + * ``default`` (the default): Run when :ref:`rez-test` is run with test name (ie ``rez-test ``). + * ``pre_install``: Run before an install (ie :option:`rez-build -i`), and abort the install on fail. + * ``pre_release``: Run before a release, and abort the release on fail. + * ``explicit``: Only run if specified when :ref:`rez-test` is run (ie ``rez-test ``). + * ``on_variants``: Which variants the test should be run on. Valid values are: + * ``True``: Run the test on all variants. + * ``False`` (the default): Run the test only on one variant (ie the variant you get by + default when the test env is resolved). This is useful for tests like linting, + where variants may be irrelevant. + * A dict: This is a variant selection mechanism. In the example below, the ``maya_CI`` test will + run only on those variants that directly require ``maya`` (or a package within this range, eg + ``maya-2019``). Note that ``requires`` is the only filter type currently available. + + .. code-block:: python + + tests = { + "unit": "python -m unittest discover -s {root}/python/tests", + "lint": { + "command": "pylint mymodule", + "requires": ["pylint"], + "run_on": ["default", "pre_release"] + }, + "maya_CI": { + "command": "python {root}/ci_tests/maya.py", + "on_variants": { + "type": "requires", + "value": ["maya"] + }, + "run_on": "explicit" + } + } + + As an example, if you want to run the ``maya_CI`` block defined in the example above (named ``maya_utils``), you can run: + + .. code-block:: text + + ]$ rez-test maya_utils lint + + .. note:: + Prior to running the tests, you will need to run :ref:`rez-build`. :ref:`rez-test` can only + run tests on already built packages. + +.. py:attribute:: tools + :type: list[str] + + This is a list of tools that the package provides. This entry is important later on when we talk + about :ref:`suite tools `. + + .. code-block:: python + + tools = [ + "houdini", + "hescape", + "hython" + ] + +.. py:attribute:: uuid + :type: str + + This string should uniquely identify this *package family*. In other words, all the versions of a + particular package, such as ``maya``. It is used to detect the case where two unrelated packages that + happen to have the same name are attempted to be released. If rez detects a uuid mismatch, it will + abort the release. + + You should set the uuid on a new package once, and not change it from then on. The format of the + string doesn't actually matter, but you'd typically use a true UUID, and you can generate one + like so: + + .. code-block:: text + + ]$ python -c 'import uuid; print(uuid.uuid4().hex)' + + Example: + + .. code-block:: python + + uuid = "489ad32867494baab7e5be3e462473c6" + +.. py:attribute:: variants + :type: list[list[str]] + + A package can contain *variants* - think of them as different flavors of the same package version, + but with differing dependencies. See the :doc:`variants` section for further details. + + .. code-block:: python + + variants = [ + ["maya-2015.3"], + ["maya-2016.1"], + ["maya-2016.7"] + ] + +.. py:attribute:: version + :type: str + + This is the version of the package. See :ref:`versions-concept` for further details on valid + package versions. + + .. code-block:: python + + version = "1.0.0" + +.. _build-package-attributes: + +Build Time Package Attributes +----------------------------- + +The following package attributes only appear in packages to be built; they are stripped from the +package once installed because they are only used at build time. + +.. py:attribute:: build_command + :type: str | list[str] | False + + Package build command. If present, this is used as the build command when :ref:`rez-build` is run, + rather than detecting the build system from present build scripts (such as ``CMakeLists.txt``). If + ``False``, this indicates that no build step is necessary (the package definition will still be + installed, and this is enough to define the package). + + The ``{root}`` string expands to the root directory of the package (where the ``package.py`` is + contained). Note that, like all builds, the working directory is set to the *build path*, which + is typically somewhere under a *build* subdirectory, and is where build outputs should go. + + The ``{install}`` string expands to ``install`` if an installation is occurring, or the empty string + otherwise. This is useful for passing the install target directly to the command (for example, when + using ``make``) rather than relying on a build script checking the :envvar:`REZ_BUILD_INSTALL` environment + variable. + + The full set of variables that can be referenced in the build command are: + + * ``root``: (see above); + * ``install``: (see above) + * ``build_path``: The build path (this will also be the current working directory); + * ``install_path``: Full path to install destination; + * ``name``: Name of the package getting built; + * ``variant_index``: Index of the current variant getting built, or an empty + string ('') if no variants are present. + * ``version``: Package version currently getting built. + + .. code-block:: python + + build_command = "bash {root}/build.sh {install}" + +.. py:attribute:: build_system + :type: str + + .. todo:: reference the real --build-system cli flag + + Specify the build system used to build this package. If not set, it is detected automatically when + a build occurs (or the user specifies if using :option:`rez-build --build-system` option). + + .. code-block:: python + + build_system = "cmake" + + +.. py:function:: pre_build_commands() -> None + + This is similar to :func:`commands`, except that it is run *prior to the current package being built*. + See :ref:`pre-build-commands` for more details. + + .. code-block:: python + + def pre_build_commands(): + env.FOO_BUILT_BY_REZ = 1 + +.. py:function:: preprocess(this, data: dict[str, typing.Any]) + + See :ref:`package-preprocessing`. + +.. py:attribute:: private_build_requires + :type: list[str] + + This is the same as :attr:`build_requires`, except that these dependencies are only included if this + package is being built. Contrast this with :attr:`build_requires`, whose dependencies are included if a + build is occurring regardless of whether this package specifically is being built, or whether + this package is a dependency of the package being built. + + .. code-block:: python + + private_build_requires = [ + "cmake-2.8", + "doxygen" + ] + +.. py:attribute:: requires_rez_version + :type: str + + This defines the minimum version of rez needed to build this package. New package features have + been added over time, so older rez versions cannot necessarily build newer packages. + + .. code-block:: python + + requires_rez_version = "2.10" + +.. _release-package-attributes: + +Release Time Package Attributes +------------------------------- + +The following package attributes are created for you by Rez when your package is released via the +:ref:`rez-release` tool. If you look at the released ``package.py`` file you will notice that some or all +of these attributes have been added. + +.. py:attribute:: changelog + :type: str + + Change log containing all commits since the last released package. If the previous release was from + a different branch, the changelog given will go back to the last common commit ancestor. The syntax + of this changelog depends on the version control system. The example here is from a *git*-based + package. + + .. code-block:: python + + changelog = \ + """ + commit 22abe31541ceebced8d4e209e3f6c44d8d0bea1c + Author: allan johns <> + Date: Sun May 15 15:39:10 2016 -0700 + + first commit + """ + +.. py:attribute:: previous_revision + :type: typing.Any + + Revision information of the previously released package, if any (see :attr:`revision` for code example - + the code for this attribute is the same). + +.. py:attribute:: previous_version + :type: str + + The version of the package previously released, if any. + + .. code-block:: python + + previous_version = "1.0.1" + +.. py:attribute:: release_message + :type: str + + .. todo:: Reference --message option directly + + .. todo:: How should we document and link plugin settings? Like TODO_ADD_THIS. + + The package release message. This is supplied either via the :option:`rez-release --message` + option, or was entered in a text editor on release if rez is configured to do this (see the config + setting ``TODO_ADD_THIS``). A package may not have a release message. + + .. code-block:: python + + release_message = "Fixed the flickering thingo" + +.. py:attribute:: revision + :type: typing.Any + + Information about the source control revision containing the source code that was released. The + data type is determined by the version control system plugin that was used. The example code shown + here is the revision dict from a *git*-based package. + + .. code-block:: python + + revision = \ + {'branch': 'master', + 'commit': '22abe31541ceebced8d4e209e3f6c44d8d0bea1c', + 'fetch_url': 'git@github.com:foo/dummy.git', + 'push_url': 'git@github.com:foo/dummy.git', + 'tracking_branch': 'origin/master'} + +.. py:attribute:: timestamp + :type: int + + Epoch time at which the package was released. + + .. code-block:: python + + timestamp = 1463350552 + +.. py:attribute:: vcs + :type: str + + Name of the version control system this package was released from. + + .. code-block:: python + + vcs = "git" diff --git a/docs/source/pip.rst b/docs/source/pip.rst new file mode 100644 index 000000000..6cde13a78 --- /dev/null +++ b/docs/source/pip.rst @@ -0,0 +1,143 @@ +=== +Pip +=== + +Rez is language agnostic. +But since python is so much used in the VFX industry (and outside), +it knows how to convert/translate it into a rez package. +To do so, it provides a :ref:`rez-pip` command. + +.. warning:: + It doesn't know how to translate its own packages into pip packages. + +.. hint:: + We are planning to introduce a new rez-pip implemented as a plugin that will + overcome most of the current rez-pip limitations and annoyances. + +Usage +===== + +.. code-block:: text + + usage: rez pip [-h] [--python-version VERSION] [--pip-version VERSION] [-i] + [-s] [-r] [-v] + PACKAGE + + Install a pip-compatible python package, and its dependencies, as rez + packages. + + positional arguments: + PACKAGE package to install or archive/url to install from + + optional arguments: + -h, --help show this help message and exit + --python-version VERSION + python version (rez package) to use, default is + latest. Note that the pip package(s) will be installed + with a dependency on python-MAJOR.MINOR. + --pip-version VERSION + pip version (rez package) to use, default is latest. + This option is deprecated and will be removed in the + future. + -i, --install install the package + -s, --search search for the package on PyPi + -r, --release install as released package; if not set, package is + installed locally only + -p PATH, --prefix PATH + install to a custom package repository path. + -v, --verbose verbose mode, repeat for more verbosity + + +The :ref:`rez-pip` command allows you to do two main things. + +1. Install or release a pip package as a rez package. +2. Search for a package on PyPI + +.. _which-pip-will-be-used: + +Which pip will be used? +======================= + +:ref:`rez-pip` uses a fallback mechanism to find which pip will be used to run the commands. +The logic is as follow: + +1. Search for pip in the rezified ``python`` package specified with :option:`--python-version `, or + the latest version if not specified; +2. If found, use it; +3. If not found, search for pip in the rezified ``pip`` package specified with :option:`--pip-version `, + or latest version if not specified. + + .. note:: + Note that this is deprecated and will be removed in the future + +4. If rezified ``pip`` is found, use it; +5. If not found, fall back to pip installed in rez own virtualenv. + +.. note:: + In all of these options, we also check if the version of pip is greater or equal + than 19.0. This is a hard requirement of :ref:`rez-pip`. + +Note that rez-pip output should tell you what it tries and which pip it will use. + +It is extremely important to know which pip is used and understand why it is used. Pip packages +define which python version they are compatible with. +When you install a pip package, pip checks which python version it is +currently running under to determine if a package can be downloaded and installed. +Not only this but it also checks which python implementation is used (CPython, PyPy, +IronPython, etc), the architecture python was built with, and other variables. So the thing you +really need to know first is which python you want to use and from there you should know +which pip is used. Knowing the pip version comes in second place. + +At some point, we supported the :option:`--pip-version ` argument, but considering what was just said +above, we decided to deprecate it (but not yet removed) just for backward compatibility reasons. +Pip is too much (read tightly) coupled to the python version/interpreter it is installed with +for us to support having pip as a rez package. We just can't guarantee that pip can be +install once in a central way and work with multiple different python version, and potentially +different implementations. + +.. _how-should-i-install-pip: + +How should I install pip? +========================= + +Following the :ref:`which-pip-will-be-used` section, we recommend to install +pip inside your python packages. For Python 2, this can be done when you compile it with the +``--with-ensurepip`` flag of the ``configure`` script. This will install a version older than 19.0 +though, so you will need to upgrade it. For Python 3, it is already installed by default. +Though your mileage may vary for the version installed, depending on which Python version you +installed. So check the pip version and update it if necessary. We also encourage you +to install ``wheel`` and possibly update ``setuptools``. ``pip``, ``setuptools`` and ``wheel`` +are perfectly fine when installed in the interpreter directly as they are pretty core +packages and all have no dependencies (and that's what ``virtualenv`` does by default too). + +.. tip:: + When installing something in an interpreter, make sure you really install in this interpreter. + That means using something like: + + .. code-block:: console + + $ /path/to/python -E -s -m pip install + + ``-E`` will render any ``PYTHON*`` environment variable to not be used and ``-s`` will + remove your :mod:`user site ` from the equation. + +Install/release +--------------- + +You have two options when you want to convert a pip package to a rez package. You can +install it, or release it. Install means that it will install in your +:data:`local_packages_path`, while +release means it will be installed in your :data:`release_packages_path`. +You can also specify a custom installation location using :option:`--prefix ` (or ``-p``). + +You can (and we recommend) use :option:`--python-version ` to choose for which python +version you want to install a given package. This will make ``rez-pip`` to resolve +the given version of the ``python`` rez package and use it to run the ``pip install``. +See :ref:`which-pip-will-be-used` for more details. +If the pip package is not pure (so contains ``.so`` for example), you will need to +call :ref:`rez-pip` for each python version you want to install the pip package for. + +.. warning:: + :option:`--pip-version ` is deprecated and will be removed in the future. + See :ref:`how-should-i-install-pip` on how we recommend + to install pip from now on. diff --git a/docs/source/suites.rst b/docs/source/suites.rst new file mode 100644 index 000000000..ea1e8cb54 --- /dev/null +++ b/docs/source/suites.rst @@ -0,0 +1,180 @@ +====== +Suites +====== + +Let us say that you wish to provide a number of different tools to your users, +even though these tools may require being run in different environments. For +example, you may want artists at your studio to be able to run ``maya`` and ``nuke`` +from the command line, without needing to know that they execute within different +environments. + +Let's say that in order to do this, you create two :doc:`contexts `: ``maya.rxt`` and +``nuke.rxt`` (see :ref:`here ` for more information). In +order to run maya, you would do this: + +.. code-block:: console + + $ rez-env --input maya.rxt -- maya + +You may then decide to wrap this command within a wrapper script which is also +called ``maya``, and that might look something like this: + +.. code-block:: bash + + #!/bin/bash + rez-env --input maya.rxt -- maya $* + +Now, if you put this somewhere on ``$PATH``, and do the same for ``nuke``, then +voila, your artists can run these applications from the command line, without +needing to know what's happening under the hood. + +This, in a nutshell, is what a *suite* does. A suite is simply a directory +containing a set of contexts, and wrapper scripts which run tools within those +contexts. + +The rez-suite Tool +================== + +Let's go through the same example, this time using the :ref:`rez-suite` tool. + +First, we create the suite. This creates a directory called ``mysuite`` in the +current working directory: + +.. code-block:: console + + $ rez-suite --create mysuite + +Now we need to add contexts to our suite. First we create the contexts: + +.. code-block:: console + + $ rez-env maya-2016.2 --output maya.rxt + $ rez-env nuke --output nuke.rxt + +Then, we add these contexts to the suite (note that the :option:`rez-suite --context` arg just +gives each context a label. You would typically have this match the context +filename as shown). + +.. code-block:: console + + $ rez-suite --add maya.rxt --context maya mysuite + $ rez-suite --add nuke.rxt --context nuke mysuite + +The suite is created! Now all we need to do is to activate it, and that's as +simple as adding its ``bin`` path to ``$PATH``: + +.. code-block:: console + + $ export PATH=$(pwd)/mysuite/bin:$PATH + +You should now see your tools coming from the suite: + +.. code-block:: console + + $ which maya + ./mysuite/bin/maya + + $ ls ./mysuite/bin + maya + nuke + +.. _suite-tools: + +Suite Tools +=========== + +The tools in a context which are exposed by the suite is determined by the +:attr:`tools` package attribute. For example, the +``maya`` package might have a :attr:`tools` definition like so: + +.. code-block:: python + + # in maya package.py + tools = [ + "maya", + "mayapy", + "fcheck" + ] + +All these tools would be made available in the suite (although you can explicitly +hide tools by using :option:`rez-suite --hide` argument). + +.. warning:: + Only packages listed in the context *requests*, + that are not weak or conflict requests, have their tools exposed. Packages + pulled in as dependencies do not. If you need to control the version of a package + not in the request, without adding its command line tools, just add it as a weak + reference to the request list. + +Tool Aliasing +------------- + +Tools can be aliased to different names, either explicitly (on a per-tool basis), +or by applying a common prefix or suffix to all tools in a context. + +Prefixing/suffixing is particularly useful when you want to expose the same +package's tools, but in two or more contexts. For example, you may want to run a +stable version of maya, but also a newer beta version. These would run in +different contexts, and the beta context might prefix all tools with ``_beta``, +hence making available tools such as ``maya_beta``. + +For example, here we create a context with a newer version of maya, add it to +the suite, then add a suffix to all its tools: + +.. code-block:: console + + $ rez-env maya-2017 --output maya2017.rxt + $ rez-suite --add maya2017.rxt --context maya2017 mysuite + $ rez-suite --suffix _beta --context maya2017 mysuite + +Control Arguments +----------------- + +When using suite tools, any arguments passed to the wrappers are passed through +to the underlying tool, as expected. However, there is an exception to the case. +Rez provides a set of *control* arguments, which are prefixed with ``+``/``++`` +rather than the typical ``-``/``--``. These are suite-aware arguments that pass +directly to rez. You can get a listing of them using ``+h``/``++help``, like so: + +.. code-block:: console + + $ maya ++help + usage: maya [+h] [+a] [+i] [+p [PKG [PKG ...]]] [++versions] + [++command COMMAND [ARG ...]] [++stdin] [++strict] [++nl] + [++peek] [++verbose] [++quiet] [++no-rez-args] + + optional arguments: + +h, ++help show this help message and exit + +a, ++about print information about the tool + +i, ++interactive launch an interactive shell within the tool's + configured environment + +p [PKG [PKG ...]], ++patch [PKG [PKG ...]] + run the tool in a patched environment + ++versions list versions of package providing this tool + ++command COMMAND [ARG ...] + read commands from string, rather than executing the + tool + ++stdin read commands from standard input, rather than + executing the tool + ++strict strict patching. Ignored if ++patch is not present + ++nl, ++no-local don't load local packages when patching + ++peek diff against the tool's context and a re-resolved copy + - this shows how 'stale' the context is + ++verbose verbose mode, repeat for more verbosity + ++quiet hide welcome message when entering interactive mode + ++no-rez-args pass all args to the tool, even if they start with '+' + +For example, to see information about the suite wrapper: + +.. code-block:: console + + $ maya ++about + Tool: maya + Path: ./mysuite/bin/maya + Suite: ./mysuite + Context: ./mysuite/contexts/maya2016.rxt ('maya2016') + +.. tip:: + If the target tool also uses ``+`` for some of its + own arguments, you can change the prefix character that rez uses for its + control arguments. See the :option:`rez-suite --prefix-char` option. diff --git a/docs/source/variants.rst b/docs/source/variants.rst new file mode 100644 index 000000000..9b2bc9e45 --- /dev/null +++ b/docs/source/variants.rst @@ -0,0 +1,261 @@ +======== +Variants +======== + +Packages in rez can contain different *variants*. Think of these as different +flavors of the same package version. Each variant has one or more package +dependencies that differ to the other variants in the same package. + +Use of variants is best illustrated with an example. Consider a maya plugin, +``my_maya_plugin``. Let us assume that there are two active versions of maya +currently in use at your studio: ``2016.sp2`` and ``2017``. If your plugin is compiled, +you may need to build it separately for each maya version, even though the source +is no different. + +You would use *variants* to create a version of your plugin that will work with +both maya versions. Consider the following package definition for our plugin: + +.. code-block:: python + + name = "my_maya_plugin" + + version = "1.0.0" + + requires = [ + "openexr-2.2" + ] + + variants = [ + ["maya-2016.sp2"], + ["maya-2017"] + ] + +When you build and install this package, two separate builds will occur: one +using ``maya-2016.sp2``, and the other ``maya-2017``. When an environment is resolved +that includes ``my_maya_plugin``, the correct variant will be selected depending on +the version of maya present. Only one variant of a package is ever used in a +given configured environment. + +Each variant entry is a list of dependencies, no different to the packages listed +in the :attr:`requires` field. These dependencies are appended to the ``requires`` list +for each variant. Thus the first variant requires ``openexr-2.2`` and ``maya-2016.sp1``, +and the second variant requires ``openexr-2.2`` and ``maya-2017``. + +.. _variants-disk-structure: + +Disk Structure +============== + +Package variants are stored within the package, under subdirectories that match +the variant requirements. For example, continuing on with our ``my_maya_plugin`` +package, the installation of that package would look like so: + +.. code-block:: text + + /rez/packages/my_maya_plugin/1.0.0/maya-2016.sp2/ + /maya-2017/ + +The anatomy of a package with variants is illustrated in the following diagram: + +.. image:: _static/pkg_path_anatomy.png + :align: center + :class: rez-diagram + +The ``root`` of a package is the root directory of its current variant (the one +the current environment is configured to use); the ``base`` of a package is the +directory containing its variants. In a package that does not have variants, +``base`` and ``root`` are the same. + +Hashed Variants +=============== + +There are two problems with the variant subpath as illustrated above: + +* The variant install path can become long if there are many requirements; +* If some variant requirements contain characters such as ``!`` and ``<``, they + can cause escaping problems that affect build systems; and, depending on the + platform, may not be a valid filesystem path. + +You can avoid these issues by using :attr:`hashed_variants`. This sets the variant +sub-path to a hash of its requirements, rather than the requirements themselves. +The resulting sub-directory is somewhat unwieldy (example: +``83e0c415db1b602f9d59cee028da6ac785e9bacc``). However, another feature, +:data:`variant shortlinks `, deals with this. A shortlink is a symlink to each variant, +created in a separate subdirectory (default ``_v``). + +Here is an example hashed variant path: + +.. code-block:: text + + /rez/packages/my_maya_plugin/1.0.0/83e0c415db1b602f9d59cee028da6ac785e9bacc + +Here is the matching *shortlink*, which is what will be used in a resolved +environment: + +.. code-block:: text + + /rez/packages/my_maya_plugin/1.0.0/_v/a + +Hashed variants must be enabled explicitly for a package. To do this, simply set +this in your package definition: + +.. code-block:: python + + hashed_variants = True + +Platform As Variant +=================== + +It is not uncommon to see the platform, architecture and/or operating system +packages in package variants (recall that rez represents these as packages). For +example, you might see variants like this: + +.. code-block:: python + + # in package.py + variants = [ + ["platform-linux", "arch-x86_64", "os-Ubuntu-12.04"], + ["platform-linux", "arch-x86_64", "os-Ubuntu-16.04"] + ] + +This indicates that the package has been built for multiple platforms. The correct +variant will be selected for you, because you probably have the relevant +:ref:`implicit packages ` set to limit packages to +the current platform. + +Single Variants +=============== + +You may often see packages with just one variant. There are two reasons for this: + +* *Future proofing*: Let's say you have a compiled package that links against python. + It may currently support ``python-2.7``, however it's conceivable that support for + newer python versions may be added later. **It is not possible to add new variants + to a package that does not have any without changing/bumnig the version**. So by + adding the ``python-2.7`` variant now, you can add variants later without needing + to move to a newer version. +* *Installation path*: People often expect to see platform, architecture and/or + operating system information in the installation path of a piece of software (and + may also expect the same of python version, or other core packages). By putting + these dependencies into a variant, we ensure that they appear in the installation + path of the package. + +Variant Selection +================= + +As mentioned, rez will automatically select the correct variant of a package +depending on the environment being resolved. For example, consider: + +.. code-block:: text + + ]$ rez-env my_maya_plugin maya-2017 -- echo '$REZ_MY_MAYA_PLUGIN_ROOT' + /rez/packages/my_maya_plugin/1.0.0/maya-2017 + ]$ rez-env my_maya_plugin maya-2016 -- echo '$REZ_MY_MAYA_PLUGIN_ROOT' + /rez/packages/my_maya_plugin/1.0.0/maya-2016.sp2 + +You can see how the correct variant (ie the one that does not conflict with other +packages in the request) has been selected. But what if both variants are valid +for the given request? Consider: + +.. code-block:: text + + ]$ rez-env my_maya_plugin -- echo '$REZ_MY_MAYA_PLUGIN_ROOT' + /rez/packages/my_maya_plugin/1.0.0/maya-2017 + +Here ``maya`` was not in the request. Either variant of ``my_maya_plugin`` would have +satisfied the request, since we have not specified which version of maya we actually +want. + +By default, rez will prefer the variant with the higher-versioned packages, which +is why the ``maya-2017`` variant was selected in this example. If there are +multiple packages in the variant, priority is given to those that were in the request +list, if any; following that, priority is given to packages listed earlier in the +variant. For example, consider: + +.. code-block:: python + + name = "foo" + + variants = [ + ["python-2.6", "maya-2017"], + ["python-2.7", "maya-2016"] + ] + +If I run ``rez-env foo``, which variant will I get? The answer is not clear. In this +case it will be the second variant, since ``python`` is given priority (it is the +first listed package in the variant), and the second variant has the higher version +of python. However, if I ran ``rez-env foo maya``, I would get the ``first`` variant +because priority is now given to ``maya``, because it's listed in my request, and the +first variant has the higher version of maya. + +The rez setting :data:`variant_select_mode` affects this selection behavior. +The default mode just described is ``version_priority``, but there is another mode called +``intersection_priority``. In this mode, variants are preferred that have *the most +number of packages present in the request*; version priority is secondary. + +Mutual Exclusivity +------------------ + +In all the examples we've seen so far, a package's variants have been mutually +exclusive. For example, you cannot have both ``python-2.6`` and ``python-2.7`` in the +same environment, so when we request ``foo python-2.6`` we can be sure of which +variant we will get. + +Variants, however, do not need to be mutually exclusive. In fact, you may use +variants in order to provide support for different DCCs for your package. Consider +a package with the following variants: + +.. code-block:: python + + name = "geocache" + + variants = [ + ["maya-2016"], + ["houdini-14"] + ] + +Which variant will I get if I run ``rez-env geocache``? Behavior in this case is +undefined. Rez gives no guarantees as to which variant will be selected. We cannot +meaningfully compare version numbers across packages, so maya will not have preference +simply because 2016 > 14. However, ``version_priority`` mode does give priority to +packages listed in the request. So if we ran ``rez-env geocache maya``, we will get +the first variant... probably. + +Probably? ++++++++++ + +The operative word here is *preference*. Because the variants are not mutually +exclusive, we can't make guarantees. A resolve is still deterministic. You aren't +going to get differing results when requesting the same environment, but predicting +which variant you'll get can be tricky. + +Consider the following request: + +.. code-block:: text + + ]$ rez-env geocache maya animtools-1.4 + +We would expect to get the ``maya-2016`` variant of ``geocache``. However, what if +``animtools`` requires ``maya-2017``? This makes the first ``geocache`` variant impossible +to select, since a conflict would occur, and so the ``houdini`` variant of ``geocache`` +will be selected. Quite possibly not what you expected. + +.. todo:: Document this? +.. > [[media/icons/under_construction.png]] I plan on adding a new package request +.. > syntax, that is able to explicitly select package variants. This will avoid the +.. > ambiguity in cases like the one described here. + +Why Use Variants? +================= + +Variants are a powerful mechanism in rez. They avoid the need to maintain separate +branches of a package in order to support varying dependencies. You may have had +problems in the past where a common library depends on, say, boost, and is used in +various DCCs (maya, nuke etc), and depended on by many other packages in your +pipeline. When a DCC moves to a new version of boost (or python, or OIIO, etc) +you now have to branch this library, which potentially affects many other packages. +The problem gets worse if you have multiple dependencies with varying versions. + +Variants solve that problem. You simply add another boost variant to your library, +and other dependent packages are not affected. Rez will correctly select the +package variant that does not conflict with the resolved environment. diff --git a/src/rez/bundle_context.py b/src/rez/bundle_context.py index c60c827fb..f300c6514 100644 --- a/src/rez/bundle_context.py +++ b/src/rez/bundle_context.py @@ -21,7 +21,7 @@ def bundle_context(context, dest_dir, force=False, skip_non_relocatable=False, This creates a copy of a context with its variants retargeted to a local package repository containing only the variants the context uses. The - generated file structure looks like so: + generated file structure looks like so:: /dest_dir/ /context.rxt diff --git a/src/rez/cli/_main.py b/src/rez/cli/_main.py index dccf6261b..a27de793a 100644 --- a/src/rez/cli/_main.py +++ b/src/rez/cli/_main.py @@ -89,7 +89,7 @@ def setup_parser(): LazyArgumentParser: Argument parser for rez command. """ py = sys.version_info - parser = LazyArgumentParser("rez") + parser = LazyArgumentParser("rez", description="rez CLI") parser.add_argument("-i", "--info", action=InfoAction, help="print information about rez and exit") diff --git a/src/rez/cli/_util.py b/src/rez/cli/_util.py index 45910258e..99646bb6e 100644 --- a/src/rez/cli/_util.py +++ b/src/rez/cli/_util.py @@ -176,12 +176,16 @@ def __init__(self, *args, **kwargs): def format_help(self): """Sets up all sub-parsers when help is requested.""" + self._setup_all_subparsers() + return super(LazyArgumentParser, self).format_help() + + def _setup_all_subparsers(self): + """Sets up all sub-parsers on demand.""" if self._subparsers: for action in self._subparsers._actions: if isinstance(action, LazySubParsersAction): for parser_name, parser in action._name_parser_map.items(): action._setup_subparser(parser_name, parser) - return super(LazyArgumentParser, self).format_help() _handled_int = False diff --git a/src/rez/cli/pkg-cache.py b/src/rez/cli/pkg-cache.py index b8cfe4d6c..26a62e871 100644 --- a/src/rez/cli/pkg-cache.py +++ b/src/rez/cli/pkg-cache.py @@ -50,7 +50,7 @@ def setup_parser(parser, completions=False): parser.add_argument( "-f", "--force", action="store_true", help="Force a package add, even if package is not cachable. Only " - "applicable with --add" + "applicable with --add-variants" ) parser.add_argument( "DIR", nargs='?', diff --git a/src/rez/command.py b/src/rez/command.py index 9b58d4400..3cc0696a0 100644 --- a/src/rez/command.py +++ b/src/rez/command.py @@ -11,15 +11,18 @@ class Command(object): To register plugin and expose subcommand, the plugin module.. * MUST have a module-level docstring (used as the command help) - * MUST provide a `setup_parser()` function - * MUST provide a `command()` function - * MUST provide a `register_plugin()` function - * SHOULD have a module-level attribute `command_behavior` + * MUST provide a ``setup_parser()`` function + * MUST provide a ``command()`` function + * MUST provide a ``register_plugin()`` function + * SHOULD have a module-level attribute ``command_behavior`` - For example, a plugin named 'foo' and this is the `foo.py`: + For example, a plugin named 'foo' and this is the ``foo.py``: - '''The docstring for command help, this is required. - ''' + .. code-block:: python + + \""" + The docstring for command help, this is required. + \""" from rez.command import Command command_behavior = { diff --git a/src/rez/package_cache.py b/src/rez/package_cache.py index d9faecd05..663e61330 100644 --- a/src/rez/package_cache.py +++ b/src/rez/package_cache.py @@ -41,14 +41,13 @@ class PackageCache(object): * A rez-env is performed; * The context is resolved; - * For each variant in the context, we check to see if it's present in the - current package cache; + * For each variant in the context, we check to see if it's present in the current package cache; * If it is, the variant's root is remapped to this location. - A package cache is _not_ a package repository. It just stores copies of + A package cache is **not** a package repository. It just stores copies of variant payloads - no package definitions are stored. - Payloads are stored into the following structure: + Payloads are stored into the following structure:: //foo/1.0.0/af8d/a/ /a.json @@ -62,13 +61,13 @@ class PackageCache(object): matching variant. """ - VARIANT_NOT_FOUND = 0 # Variant was not found - VARIANT_FOUND = 1 # Variant was found - VARIANT_CREATED = 2 # Variant was created - VARIANT_COPYING = 3 # Variant payload is still being copied to this cache - VARIANT_COPY_STALLED = 4 # Variant payload copy has stalled - VARIANT_PENDING = 5 # Variant is pending caching - VARIANT_REMOVED = 6 # Variant was deleted + VARIANT_NOT_FOUND = 0 #: Variant was not found + VARIANT_FOUND = 1 #: Variant was found + VARIANT_CREATED = 2 #: Variant was created + VARIANT_COPYING = 3 #: Variant payload is still being copied to this cache + VARIANT_COPY_STALLED = 4 #: Variant payload copy has stalled + VARIANT_PENDING = 5 #: Variant is pending caching + VARIANT_REMOVED = 6 #: Variant was deleted _FILELOCK_TIMEOUT = 10 _COPYING_TIME_INC = 0.2 @@ -145,18 +144,14 @@ def add_variant(self, variant, force=False): config.package_cache_same_device' is False. Args: - variant (`Variant`): The variant to copy into this cache + variant (Variant): The variant to copy into this cache force (bool): Copy the variant regardless. Use at your own risk (there is no guarantee the resulting variant payload will be functional). Returns: 2-tuple: - str: Path to cached payload - - int: One of: - - VARIANT_FOUND - - VARIANT_CREATED - - VARIANT_COPYING - - VARIANT_COPY_STALLED + - int: One of VARIANT_FOUND, VARIANT_CREATED, VARIANT_COPYING, VARIANT_COPY_STALLED """ from rez.utils.base26 import get_next_base26 from rez.utils.filesystem import safe_makedirs @@ -598,8 +593,8 @@ def clean(self, time_limit=None): This should be run periodically via 'rez-pkg-cache --clean'. This removes: - - Variants that have not been used in more than - 'config.package_cache_max_variant_days' days; + + - Variants that have not been used in more than 'config.package_cache_max_variant_days' days; - Variants that have stalled; - Variants that are already pending deletion (remove_variant() was used). diff --git a/src/rez/packages.py b/src/rez/packages.py index b4c0c914d..2cac8f24a 100644 --- a/src/rez/packages.py +++ b/src/rez/packages.py @@ -190,16 +190,18 @@ def _eval_late_binding(self, sourcecode): class Package(PackageBaseResourceWrapper): """A package. - Note: + Warning: Do not instantiate this class directly, instead use the function - `iter_packages` or `PackageFamily.iter_packages`. + :func:`iter_packages` or :meth:`PackageFamily.iter_packages`. """ keys = schema_keys(package_schema) - # This is to allow for a simple check like 'this.is_package' in late-bound - # funcs, where 'this' may be a package or variant. - # + #: Allows for a simple check like ``this.is_package`` in late-bound + #: funcs, where ``this`` may be a package or variant. is_package = True + + #: Allows for a simple check like ``this.is_variant`` in late-bound + #: funcs, where ``this`` may be a package or variant. is_variant = False def __init__(self, resource, context=None): @@ -326,15 +328,17 @@ def get_variant(self, index=None): class Variant(PackageBaseResourceWrapper): """A package variant. - Note: + Warning: Do not instantiate this class directly, instead use the function - `Package.iter_variants`. + :meth:`Package.iter_variants`. """ keys = schema_keys(variant_schema) keys.update(["index", "root", "subpath"]) - # See comment in `Package` + #: See :attr:`Package.is_package`. is_package = False + + #: See :attr:`Package.is_variant`. is_variant = True def __init__(self, resource, context=None, parent=None): diff --git a/src/rez/rezconfig.py b/src/rez/rezconfig.py index 93e5f7a66..9b11f7fcc 100644 --- a/src/rez/rezconfig.py +++ b/src/rez/rezconfig.py @@ -48,13 +48,12 @@ import os +### Do not move or delete this comment (__DOC_START__) ############################################################################### # Paths ############################################################################### -### Do not move or delete this comment (__DOC_START__) - # The package search path. Rez uses this to find packages. A package with the # same name and version in an earlier path takes precedence. packages_path = [ @@ -63,64 +62,66 @@ "~/.rez/packages/ext", # external (3rd party) pkgs, such as houdini, boost ] -# The path that Rez will locally install packages to when rez-build is used +# The path that Rez will locally install packages to when :ref:`rez-build` is used local_packages_path = "~/packages" -# The path that Rez will deploy packages to when rez-release is used. For +# The path that Rez will deploy packages to when :ref:`rez-release` is used. For # production use, you will probably want to change this to a site-wide location. release_packages_path = "~/.rez/packages/int" # Where temporary files go. Defaults to appropriate path depending on your -# system - for example, *nix distributions will probably set this to "/tmp". It -# is highly recommended that this be set to local storage, such as /tmp. +# system. For example, \*nix distributions will probably set this to :file:`/tmp`. It +# is highly recommended that this be set to local storage, such as :file:`/tmp`. tmpdir = None # Where temporary files for contexts go. Defaults to appropriate path depending -# on your system - for example, *nix distributions will probably set this to "/tmp". -# This is separate to 'tmpdir' because you sometimes might want to set this to an -# NFS location - for example, perhaps rez is used during a render and you'd like +# on your system. For example, \*nix distributions will probably set this to :file:`/tmp`. +# This is separate to :data:`tmpdir` because you sometimes might want to set this to an +# NFS location. For example, perhaps rez is used during a render and you'd like # to store these tempfiles in the farm queuer's designated tempdir so they're # cleaned up when the render completes. context_tmpdir = None -# These are extra python paths that are added to sys.path **only during a build**. +# These are extra python paths that are added to :data:`sys.path` **only during a build**. # This means that any of the functions in the following list can import modules # from these paths: -# * The *preprocess* function; -# * Any function decorated with @early - these get evaluated at build time. +# +# * The :func:`preprocess` function; +# * Any function decorated with :ref:`@early `. These get evaluated at build time. # # You can use this to provide common code to your package definition files during # a build. To provide common code for packages to use at resolve time instead (for -# example, in a *commands* function) see the following -# *package_definition_python_path* setting. -# +# example, in a :func:`commands` function) see the following +# :data:`package_definition_python_path` setting. package_definition_build_python_paths = [] # This is the directory from which installed packages can import modules. This # is a way for packages to use shared code. # -# This is NOT a standard path added to sys.path. Packages that use modules from +# This is NOT a standard path added to :data:`sys.path`. Packages that use modules from # within this directory need to explicitly name them. Furthermore, modules that -# a package uses are copied into that package's install - this ensures that the +# a package uses are copied into that package's install. This ensures that the # package remains standalone and that changes to the shared code will not break # or alter existing package installs. # # Consider the setting: # -# package_definition_python_path = "/src/rezutils" +# .. code-block:: python +# +# package_definition_python_path = "/src/rezutils" # -# Consider also the following package *commands* function: +# Consider also the following package :func:`commands` function: # -# @include("utils") -# def commands(): -# utils.do_some_common_thing(this) +# .. code-block:: python # -# This package will import the code from */src/rezutils/utils.py* (or more -# specifically, its copy of this sourcefile) and will bind it to the name *utils*. +# @include("utils") +# def commands(): +# utils.do_some_common_thing(this) # -# For further information, see -# [here](Package-Definition-Guide#sharing-code-across-package-definition-files). +# This package will import the code from :file:`/src/rezutils/utils.py` (or more +# specifically, its copy of this sourcefile) and will bind it to the name ``utils``. # +# For further information, see :ref:`package-definition-sharing-code`. package_definition_python_path = None @@ -131,7 +132,7 @@ # Search path for rez plugins. plugin_path = [] -# Search path for bind modules. The *rez-bind* tool uses these modules to create +# Search path for bind modules. The :ref:`rez-bind` tool uses these modules to create # rez packages that reference existing software already installed on the system. bind_module_path = [] @@ -161,8 +162,8 @@ resource_caching_maxsize = -1 # Uris of running memcached server(s) to use as a file and resolve cache. For -# example, the uri "127.0.0.1:11211" points to memcached running on localhost on -# its default port. Must be either null, or a list of strings. +# example, the URI ``127.0.0.1:11211`` points to memcached running on localhost on +# its default port. Must be either None, or a list of strings. memcached_uri = [] # Bytecount beyond which memcached entries are compressed, for cached package @@ -187,37 +188,41 @@ ############################################################################### # Whether a package is relocatable or not, if it does not explicitly state with -# the 'relocatable' attribute in its package definition file. +# the :attr:`relocatable` attribute in its package definition file. default_relocatable = True -# Set relocatable on a per-package basis. This is here for migration purposes - -# it's better for packages themselves to set their relocatable attribute. -# Overrides 'default_relocatable' if a package matches. +# Set relocatable on a per-package basis. This is here for migration purposes. +# It's better for packages themselves to set their relocatable attribute. +# Overrides :data:`default_relocatable` if a package matches. # # Example: # -# default_relocatable_per_package = { -# "nuke": False, -# "maya": True -# } +# .. code-block:: python +# +# default_relocatable_per_package = { +# "nuke": False, +# "maya": True +# } default_relocatable_per_package = None # Set relocatable on a per-package-repository basis. Overrides -# 'default_relocatable_per_package' and 'default_relocatable' for any repos +# :data:`default_relocatable_per_package` and :data:`default_relocatable` for any repos # listed here. # # Example: # -# default_relocatable_per_repostitory = { -# '/svr/packages': False -# } +# .. code-block:: python +# +# default_relocatable_per_repostitory = { +# '/svr/packages': False +# } default_relocatable_per_repository = None ############################################################################### # Package Caching # -# Note: "package caching" refers to copying variant payloads to a path on local +# Package caching refers to copying variant payloads to a path on local # disk, and using those payloads instead. It is a way to avoid fetching files # over shared storage, and is unrelated to memcached-based caching of resolves # and package definitions as seen in the "Caching" config section. @@ -225,30 +230,34 @@ ############################################################################### # Whether a package is cachable or not, if it does not explicitly state with -# the 'cachable' attribute in its package definition file. If None, defaults -# to packages' relocatability (ie cachable will == relocatable). +# the :attr:`cachable` attribute in its package definition file. If None, defaults +# to packages' relocatability (ie cachable == relocatable). default_cachable = False -# Set cachable on a per-package basis. This is here for migration purposes - -# it's better for packages themselves to set their cachable attribute. Overrides -# 'default_cachable' if a package matches. +# Set cachable on a per-package basis. This is here for migration purposes. +# It's better for packages themselves to set their cachable attribute. Overrides +# :data:`default_cachable` if a package matches. # # Example: # -# default_cachable_per_package = { -# "nuke": False, -# "maya": True -# } +# .. code-block:: python +# +# default_cachable_per_package = { +# "nuke": False, +# "maya": True +# } default_cachable_per_package = None # Set cachable on a per-package-repository basis. Overrides -# 'default_cachable_per_package' and 'default_cachable' for any repos listed here. +# :data:`default_cachable_per_package` and :data:`default_cachable` for any repos listed here. # # Example: # -# default_cachable_per_repostitory = { -# '/svr/packages': False -# } +# .. code-block:: python +# +# default_cachable_per_repostitory = { +# '/svr/packages': False +# } default_cachable_per_repository = None # The path where rez locally caches variants. If this is None, then package @@ -262,7 +271,7 @@ # If True, creating or sourcing a context will cause variants to be cached. write_package_cache = True -# Delete variants that haven't been used in N days (see `rez-pkg-cache --clean`). +# Delete variants that haven't been used in N days (see :option:`rez-pkg-cache --clean`). # To disable, set to zero. package_cache_max_variant_days = 30 @@ -280,11 +289,11 @@ # If > 0, spend up to this many seconds cleaning the cache every time the cache # is updated. This is a way to keep the cache size under control without having -# to periodically run 'rez-pkg-cache --clean'. Set to -1 to disable. +# to periodically run :option:`rez-pkg-cache --clean`. Set to -1 to disable. package_cache_clean_limit = 0.5 # Number of days of package cache logs to keep. -# Logs are written to {pkg-cache-root}/.sys/log/*.log +# Logs are written to :file:`{pkg-cache-root}/.sys/log/{filename}.log` package_cache_log_days = 7 @@ -293,7 +302,7 @@ ############################################################################### # Packages that are implicitly added to all package resolves, unless the -# --no-implicit flag is used. +# :option:`rez-env --no-implicit` flag is used. implicit_packages = [ "~platform=={system.platform}", "~arch=={system.arch}", @@ -302,21 +311,25 @@ # Override platform values from Platform.os and arch. # This is useful as Platform.os might show different -# values depending on the availability of lsb-release on the system. -# The map supports regular expression e.g. to keep versions. -# Please note that following examples are not necessarily recommendations. -# -# platform_map = { -# "os": { -# r"Scientific Linux-(.*)": r"Scientific-\1", # Scientific Linux-x.x -> Scientific-x.x -# r"Ubuntu-14.\d": r"Ubuntu-14", # Any Ubuntu-14.x -> Ubuntu-14 -# r'CentOS Linux-(\d+)\.(\d+)(\.(\d+))?': r'CentOS-\1.\2', ' # Centos Linux-X.Y.Z -> CentOS-X.Y -# }, -# "arch": { -# "x86_64": "64bit", # Maps both x86_64 and amd64 -> 64bit -# "amd64": "64bit", -# }, -# } +# values depending on the availability of ``lsb-release`` on the system. +# The map supports regular expression, e.g. to keep versions. +# +# .. note:: +# The following examples are not necessarily recommendations. +# +# .. code-block:: python +# +# platform_map = { +# "os": { +# r"Scientific Linux-(.*)": r"Scientific-\1", # Scientific Linux-x.x -> Scientific-x.x +# r"Ubuntu-14.\d": r"Ubuntu-14", # Any Ubuntu-14.x -> Ubuntu-14 +# r'CentOS Linux-(\d+)\.(\d+)(\.(\d+))?': r'CentOS-\1.\2', ' # Centos Linux-X.Y.Z -> CentOS-X.Y +# }, +# "arch": { +# "x86_64": "64bit", # Maps both x86_64 and amd64 -> 64bit +# "amd64": "64bit", +# }, +# } platform_map = {} # If true, then when a resolve graph is generated during a failed solve, packages @@ -327,146 +340,159 @@ # Variant select mode. This determines which variants in a package are preferred # during a solve. Valid options are: -# - version_priority: Prefer variants that contain higher versions of packages +# +# - **version_priority**: Prefer variants that contain higher versions of packages # present in the request; -# - intersection_priority: Prefer variants that contain the most number of +# - **intersection_priority**: Prefer variants that contain the most number of # packages that are present in the request. # # As an example, suppose you have a package foo which has two variants: # -# variants = [ -# ["bar-3.0", "baz-2.1"], -# ["bar-2.8", "burgle-1.0"] -# ] -# -# if you do: -# -# rez-env foo bar -# -# ...then, in either variant_select_mode, it will prefer the first variant, -# ["bar-3.0", "baz-2.1"], because it has a higher version of the first variant -# requirement (bar). However, if we instead do: +# .. code-block:: python # -# rez-env foo bar burgle +# variants = [ +# ["bar-3.0", "baz-2.1"], +# ["bar-2.8", "burgle-1.0"] +# ] # -# ...we get different behavior. version_priority mode will still return -# ["bar-3.0", "baz-2.1"], because the first requirement's version is higher. +# if you do ``rez-env foo bar`` then, in either variant_select_mode, +# it will prefer the first variant, ``["bar-3.0", "baz-2.1"]``, because it has a +# higher version of the first variant requirement (bar). However, if we instead do +# ``rez-env foo bar burgle`` we get different behavior. version_priority mode will +# still return ``["bar-3.0", "baz-2.1"]``, because the first requirement's version is higher. # -# However, intersection_priority mode will pick the second variant, -# ["bar-2.8", "burgle-1.0"], because it contains more packages that were in the +# However, ``intersection_priority`` mode will pick the second variant, +# ``["bar-2.8", "burgle-1.0"]``, because it contains more packages that were in the # original request (burgle). variant_select_mode = "version_priority" -# Package filter. One or more filters can be listed, each with a list of +# One or more filters can be listed, each with a list of # exclusion and inclusion rules. These filters are applied to each package # during a resolve, and if any filter excludes a package, that package is not # included in the resolve. Here is a simple example: # -# package_filter = { -# 'excludes': 'glob(*.beta)', -# 'includes': 'glob(foo-*)', -# } +# .. code-block:: python +# +# package_filter = { +# 'excludes': 'glob(*.beta)', +# 'includes': 'glob(foo-*)', +# } # # This is an example of a single filter with one exclusion rule and one inclusion -# rule. The filter will ignore all packages with versions ending in '.beta', -# except for package 'foo' (which it will accept all versions of). A filter will +# rule. The filter will ignore all packages with versions ending in ``.beta``, +# except for package ``foo`` (which it will accept all versions of). A filter will # only exclude a package if that package matches at least one exclusion rule, # and does not match any inclusion rule. # # Here is another example, which excludes all beta and dev packages, and all -# packages except 'foo' that are released after a certain date. Note that in +# packages except ``foo`` that are released after a certain date. Note that in # order to use multiple filters, you need to supply a list of dicts, rather # than just a dict: # -# package_filter = [ -# { -# 'excludes': ['glob(*.beta)', 'glob(*.dev)'] -# }, -# { -# 'excludes': ['after(1429830188)'], -# 'includes': ['foo'], # same as range(foo), same as glob(foo-*) -# } -# ] -# -# This example shows why multiple filters are supported - with only one filter, +# .. code-block:: python +# +# package_filter = [ +# { +# 'excludes': ['glob(*.beta)', 'glob(*.dev)'] +# }, +# { +# 'excludes': ['after(1429830188)'], +# 'includes': ['foo'], # same as range(foo), same as glob(foo-*) +# } +# ] +# +# This example shows why multiple filters are supported. With only one filter, # it would not be possible to exclude all beta and dev packages (including foo), # but also exclude all packages after a certain date, except for foo. # # Following are examples of all the possible rules: # -# example | description -# --------------------|---------------------------------------------------- -# glob(*.beta) | Matches packages matching the glob pattern. -# regex(.*-\\.beta) | Matches packages matching re-style regex. -# range(foo-5+) | Matches packages within the given requirement. -# before(1429830188) | Matches packages released before the given date. -# after(1429830188) | Matches packages released after the given date. -# *.beta | Same as glob(*.beta) -# foo-5+ | Same as range(foo-5+) +# ======================== ==================================================== +# Example Description +# ======================== ==================================================== +# ``glob(*.beta)`` Matches packages matching the glob pattern. +# ``regex(.*-\\.beta)`` Matches packages matching re-style regex. +# ``range(foo-5+)`` Matches packages within the given requirement. +# ``before(1429830188)`` Matches packages released before the given date. +# ``after(1429830188)`` Matches packages released after the given date. +# ``*.beta`` Same as glob(\*.beta). +# ``foo-5+`` Same as range(foo-5+). +# ======================== ==================================================== package_filter = None -# Package order. One or more "orderers" can be listed. +# One or more "orderers" can be listed. # This will affect the order of version resolution. # This can be used to ensure that specific version have priority over others. # Higher versions can still be accessed if required. # # A common use case is to ease migration from python-2 to python-3: # -# package_orderers = [ -# { -# "type": "per_family", -# "orderers": [ -# { -# "packages": ["python"], -# "type": "version_split", -# "first_version": "2.7.16" -# } -# ] -# } -# ] +# .. code-block:: python +# +# package_orderers = [ +# { +# "type": "per_family", +# "orderers": [ +# { +# "packages": ["python"], +# "type": "version_split", +# "first_version": "2.7.16" +# } +# ] +# } +# ] # # This will ensure that for the "python" package, versions equals or lower than "2.7.16" will have priority. # Considering the following versions: "2.7.4", "2.7.16", "3.7.4": # -# example | result -# --------------------|---------------------------------------------------- -# rez-env python | python-2.7.16 -# rez-env python-3 | python-3.7.4 +# ==================== ============= +# Example Result +# ==================== ============= +# rez-env python python-2.7.16 +# rez-env python-3 python-3.7.4 +# ==================== ============= # # Here's another example, using another orderer: "soft_timestamp". # This orderer will prefer packages released before a provided timestamp. # The following example will prefer package released before 2019-09-09. # -# package_orderers = [ -# { -# "type": "soft_timestamp", -# "timestamp": 1568001600, # 2019-09-09 -# "rank": 3 -# } -# ] +# .. code-block:: python +# +# package_orderers = [ +# { +# "type": "soft_timestamp", +# "timestamp": 1568001600, # 2019-09-09 +# "rank": 3 +# } +# ] # # A timestamp can be generated with python: # -# $ python -c "import datetime, time; print(int(time.mktime(datetime.date(2019, 9, 9).timetuple())))" -# 1568001600 +# .. code-block:: text +# +# $ python -c "import datetime, time; print(int(time.mktime(datetime.date(2019, 9, 9).timetuple())))" +# 1568001600 # # The rank can be used to allow some versions released after the timestamp to still be considered. # When using semantic versionnng, a value of 3 is the most common. # This will let version with a different patch number to be accepted. # # Considering a package "foo" with the following versions: +# # - "1.0.0" was released at 2019-09-07 # - "2.0.0" was released at 2019-09-08 # - "2.0.1" was released at 2019-09-10 # - "2.1.0" was released at 2019-09-11 # - "3.0.0" was released at 2019-09-12 # -# example | timestamp | rank | result -# --------------------|------------|------|-------------------------------- -# rez-env foo | 2019-09-09 | 0 | foo-2.0.0 -# rez-env foo | 2019-09-09 | 3 | foo-2.0.1 -# rez-env foo | 2019-09-09 | 2 | foo-2.1.0 -# rez-env foo | 2019-09-09 | 1 | foo-3.0.0 +# =========== ========== ==== ========= +# Example Timestamp Rank Result +# =========== ========== ==== ========= +# rez-env foo 2019-09-09 0 foo-2.0.0 +# rez-env foo 2019-09-09 3 foo-2.0.1 +# rez-env foo 2019-09-09 2 foo-2.1.0 +# rez-env foo 2019-09-09 1 foo-3.0.0 +# =========== ========== ==== ========= package_orderers = None # If True, unversioned packages are allowed. Solve times are slightly better if @@ -484,40 +510,44 @@ # python modules from the parent environment would be (incorrectly) accessible # within the Rez environment. # -# "Parent variables" override this behaviour - they are appended/prepended to, -# rather than being overwritten. If you set "all_parent_variables" to true, then -# all variables are considered parent variables, and the value of "parent_variables" -# is ignored. Be aware that if you make variables such as PATH, PYTHONPATH or +# "Parent variables" override this behaviour. They are appended/prepended to, +# rather than being overwritten. If you set :data:`all_parent_variables` to :data:`True`, then +# all variables are considered parent variables, and the value of :data:`parent_variables` +# is ignored. Be aware that if you make variables such as ``PATH``, :envvar:`PYTHONPATH` or # app plugin paths parent variables, you are exposing yourself to potentially # incorrect behaviour within a resolved environment. parent_variables = [] + +# See :data:`parent_variables`. all_parent_variables = False # When two or more packages in a resolve attempt to set the same environment # variable, Rez's default behaviour is to flag this as a conflict and abort the # resolve. You can overcome this in a package's commands section by using the -# Rex command "resetenv" instead of "setenv". However, you can also turn off this -# behaviour globally - for certain variables, by adding them to "resetting_variables", -# and for all variables, by setting "all_resetting_variables" to true. +# Rex command :func:`resetenv` instead of :func:`setenv`. However, you can also turn off this +# behaviour globally for some varibles by adding them to :data:`resetting_variables`, +# and for all variables, by setting :data:`all_resetting_variables` to true. resetting_variables = [] + +# See :data:`resetting_variables`. all_resetting_variables = False # The default shell type to use when creating resolved environments (eg when using -# rez-env, or calling ResolvedContext.execute_shell). If empty or null, the +# :ref:`rez-env`, or calling :meth:`.ResolvedContext.execute_shell`). If empty or None, the # current shell is used (for eg, "bash"). default_shell = "" # The command to use to launch a new Rez environment in a separate terminal (this -# is enabled using rez-env's "detached" option). If None, it is detected. +# is enabled using the :option:`rez-env --detached` option). If None, it is detected. terminal_emulator_command = None -# subprocess.Popen arguments to use in order to execute a shell in a new process -# group (see ResolvedContext.execute_shell, 'start_new_session'). Dict of -# (Popen argument, value). +# :class:`subprocess.Popen` arguments to use in order to execute a shell in a new process +# group (see :meth:`.ResolvedContext.execute_shell` and its ``start_new_session`` argument). +# Dict of (Popen argument, value). new_session_popen_args = None # This setting can be used to override the separator used for environment -# variables that represent a list of items. By default, the value of os.pathsep +# variables that represent a list of items. By default, the value of :data:`os.pathsep` # will be used, unless the environment variable is list here, in which case the # configured separator will be used. env_var_separators = { @@ -527,93 +557,101 @@ # This setting identifies path-like environment variables. This is required # because some shells need to apply path normalization. For example, the command -# `env.PATH.append("{root}/bin")` will be normalized to (eg) `C:\...\bin` in a -# `cmd` shell on Windows. Note that wildcards are supported. If this setting is +# ``env.PATH.append("{root}/bin")`` will be normalized to (eg) ``C:\...\bin`` in a +# ``cmd`` shell on Windows. Note that wildcards are supported. If this setting is # not correctly configured, then your shell may not work correctly. pathed_env_vars = [ "*PATH" ] -# Defines what suites on $PATH stay visible when a new rez environment is resolved. +# Defines what suites on ``$PATH`` stay visible when a new rez environment is resolved. # Possible values are: +# # - "never": Don"t attempt to keep any suites visible in a new env # - "always": Keep suites visible in any new env # - "parent": Keep only the parent suite of a tool visible # - "parent_priority": Keep all suites visible and the parent takes precedence suite_visibility = "always" -# Defines how Rez's command line tools are added back to PATH within a resolved +# Defines how Rez's command line tools are added back to ``$PATH`` within a resolved # environment. Valid values are: +# # - "append": Rez tools are appended to PATH (default); # - "prepend": Rez tools are prepended to PATH; -# - "never": Rez tools are not added back to PATH - rez will not be available +# - "never": Rez tools are not added back to PATH. Rez will not be available # within resolved shells. rez_tools_visibility = "append" # Defines when package commands are sourced during the startup sequence of an # interactive shell. If True, package commands are sourced before startup -# scripts (such as .bashrc). If False, package commands are sourced after. +# scripts (such as ``.bashrc``). If False, package commands are sourced after. package_commands_sourced_first = True -# Defines paths to initially set $PATH to, if a resolve appends/prepends $PATH. +# Defines paths to initially set ``$PATH`` to, if a resolve appends/prepends ``$PATH``. # If this is an empty list, then this initial value is determined automatically -# depending on the shell (for example, *nix shells create a temp clean shell and -# get $PATH from there; Windows inspects its registry). +# depending on the shell (for example, \*nix shells create a temp clean shell and +# get ``$PATH`` from there; Windows inspects its registry). standard_system_paths = [] # If you define this function, by default it will be called as the # *preprocess function* on every package that does not provide its own, # as part of the build process. This behavior can be changed by using the -# [package_preprocess_mode](#package_preprocess_mode) setting so that +# :data:`package_preprocess_mode` setting so that # it gets executed even if a package define its own preprocess function. # -# The setting can be a function defined in your rezconfig.py, or a string. +# The setting can be a function defined in your ``rezconfig.py``, or a string. # # Example of a function to define the setting: # -# # in your rezconfig.py -# def package_preprocess_function(this, data): -# # some custom code... +# .. code-block:: python +# +# # in your rezconfig.py +# def package_preprocess_function(this, data): +# # some custom code... # # In the case where the function is a string, it must be made available by setting -# the value of [package_definition_build_python_paths](#package_definition_build_python_paths) -# appropriately. +# the value of :data:`package_definition_build_python_paths` appropriately. # # For example, consider the settings: # -# package_definition_build_python_paths = ["/src/rezutils"] -# package_preprocess_function = "build.validate" +# .. code-block:: python +# +# package_definition_build_python_paths = ["/src/rezutils"] +# package_preprocess_function = "build.validate" # -# This would use the 'validate' function in the sourcefile /src/rezutils/build.py +# This would use the ``validate`` function in the sourcefile :file:`/src/rezutils/build.py` # to preprocess every package definition file that does not define its own # preprocess function. # # If the preprocess function raises an exception, an error message is printed, # and the preprocessing is not applied to the package. However, if the -# *InvalidPackageError* exception is raised, the build is aborted. +# :exc:`~rez.exceptions.InvalidPackageError` exception is raised, the build is aborted. # # You would typically use this to perform common validation or modification of # packages. For example, your common preprocess function might check that the # package name matches a regex. Here's what that might look like: # -# # in /src/rezutils/build.py -# import re -# from rez.exceptions import InvalidPackageError +# .. code-block:: python +# +# # in /src/rezutils/build.py +# import re +# from rez.exceptions import InvalidPackageError # -# def validate(package, data): -# regex = re.compile("(a-zA-Z_)+$") -# if not regex.match(package.name): -# raise InvalidPackageError("Invalid package name.") +# def validate(package, data): +# regex = re.compile("(a-zA-Z_)+$") +# if not regex.match(package.name): +# raise InvalidPackageError("Invalid package name.") # package_preprocess_function = None -# Defines in which order the [package_preprocess_function](#package_preprocess_function) -# and the `preprocess` function inside a `package.py` are executed. +# Defines in which order the :data:`package_preprocess_function` +# and the :attr:`preprocess` function inside a ``package.py`` are executed. # # Note that "global preprocess" means the preprocess defined by -# [package_preprocess_function](#package_preprocess_function). +# :data:`package_preprocess_function`. # # Possible values are: +# # - "before": Package's preprocess function is executed before the global preprocess; # - "after": Package's preprocess function is executed after the global preprocess; # - "override": Package's preprocess function completely overrides the global preprocess. @@ -624,39 +662,40 @@ # Context Tracking ############################################################################### -# Send data to AMQP whenever a context is created or sourced. +# Send data to AMQP server whenever a context is created or sourced. # The payload is like so: # -# { -# "action": "created", -# "host": "some_fqdn", -# "user": "${USER}", -# "context": { -# ... -# } -# } -# -# Action is one of ("created", "sourced"). Routing key is set to -# '{exchange_routing_key}.{action|upper}', eg 'REZ.CONTEXT.SOURCED'. "Created" +# .. code-block:: python +# +# { +# "action": "created", +# "host": "some_fqdn", +# "user": "${USER}", +# "context": { +# ... +# } +# } +# +# Action is one of (``created``, ``sourced``). Routing key is set to +# ``{exchange_routing_key}.{action|upper}``, eg ``REZ.CONTEXT.SOURCED``. ``created`` # is when a new context is constructed, which will either cause a resolve to -# occur, or fetches a resolve from the cache. "Sourced" is when an existing +# occur, or fetches a resolve from the cache. ``sourced`` is when an existing # context is recreated (eg loading an rxt file). # -# The "context" field contains the context itself (the same as what is present -# in an rxt file), filtered by the fields listed in 'context_tracking_context_fields'. +# The ``context`` field contains the context itself (the same as what is present +# in an rxt file), filtered by the fields listed in :data:`context_tracking_context_fields`. # -# Tracking is enabled if 'context_tracking_host' is non-empty. Set to "stdout" +# Tracking is enabled if :data:`context_tracking_host` is non-empty. Set to ``stdout`` # to just print the message to standard out instead, for testing purposes. -# Otherwise, '{host}[:{port}]' is expected. +# Otherwise, ``{host}[:{port}]`` is expected. # -# If any items are present in 'context_tracking_extra_fields', they are added +# If any items are present in :data:`context_tracking_extra_fields`, they are added # to the payload. If any extra field contains references to unknown env-vars, or # is set to an empty string (possibly due to var expansion), it is removed from # the message payload. -# context_tracking_host = '' -# See [context_tracking_host](#context_tracking_host) +# See :data:`context_tracking_host` context_tracking_amqp = { "userid": '', "password": '', @@ -666,7 +705,7 @@ "message_delivery_mode": 1 } -# See [context_tracking_host](#context_tracking_host) +# See :data:`context_tracking_host` context_tracking_context_fields = [ "status", "timestamp", @@ -678,7 +717,7 @@ "resolved_packages" ] -# See [context_tracking_host](#context_tracking_host) +# See :data:`context_tracking_host` context_tracking_extra_fields = {} @@ -687,8 +726,8 @@ ############################################################################### # If true, print warnings associated with shell startup sequence, when using -# tools such as rez-env. For example, if the target shell type is "sh", and -# the "rcfile" param is used, you would get a warning, because the sh shell +# tools such as :ref:`rez-env`. For example, if the target shell type is ``sh``, and +# the ``rcfile`` param is used, you would get a warning, because the sh shell # does not support rcfile. warn_shell_startup = False @@ -698,7 +737,7 @@ # Turn on all warnings warn_all = False -# Turn off all warnings. This overrides warn_all. +# Turn off all warnings. This overrides :data:`warn_all`. warn_none = False # Print info whenever a file is loaded from disk, or saved to disk. @@ -708,7 +747,7 @@ debug_plugins = False # Print debugging info such as VCS commands during package release. Note that -# rez-pip installations are controlled with this setting also. +# :ref:`rez-pip` installations are controlled with this setting also. debug_package_release = False # Print debugging info in binding modules. Binding modules should print using @@ -726,16 +765,16 @@ # Debug memcache usage. As well as printing debugging info to stdout, it also # sends human-readable strings as memcached keys (that you can read by running -# "memcached -vv" as the server) +# ``memcached -vv`` as the server) debug_memcache = False -# Print debugging info when AMPQ is used in context tracking +# Print debugging info when an AMPQ server is used in context tracking debug_context_tracking = False # Turn on all debugging messages debug_all = False -# Turn off all debugging messages. This overrides debug_all. +# Turn off all debugging messages. This overrides :data:`debug_all`. debug_none = False # When an error is encountered in rex code, rez catches the error and processes @@ -758,19 +797,19 @@ # build files are written). build_directory = "build" -# The number of threads a build system should use, eg the make '-j' option. -# If the string values "logical_cores" or "physical_cores", it is set to the +# The number of threads a build system should use, eg the make ``-j`` option. +# If the string values ``logical_cores`` or ``physical_cores`` are used, it is set to the # detected number of logical / physical cores on the host system. # (Logical cores are the number of cores reported to the OS, physical are the -# number of actual hardware processor cores. They may differ if, ie, the CPUs -# support hyperthreading, in which case logical_cores == 2 * physical_cores). -# This setting is exposed as the environment variable $REZ_BUILD_THREAD_COUNT +# number of actual hardware processor cores. They may differ if, ie, the CPUs +# support hyperthreading, in which case ``logical_cores == 2 * physical_cores``). +# This setting is exposed as the environment variable :envvar:`REZ_BUILD_THREAD_COUNT` # during builds. build_thread_count = "physical_cores" -# The release hooks to run when a release occurs. Release hooks are plugins - if +# The release hooks to run when a release occurs. Release hooks are plugins. If # a plugin listed here is not present, a warning message is printed. Note that a -# release hook plugin being loaded does not mean it will run - it needs to be +# release hook plugin being loaded does not mean it will run. It needs to be # listed here as well. Several built-in release hooks are available, see # rezplugins/release_hook. release_hooks = [] @@ -784,19 +823,16 @@ # existing package (such as releasing a variant into an existing package, or # copying a package) will, if possible, temporarily make a package writable # during these processes. The mode will be set back to original afterwards. -# make_package_temporarily_writable = True # The subdirectory where hashed variant symlinks (known as variant shortlinks) # are created. This is only relevant for packages whose 'hashed_variants' is # set to True. To disable variant shortlinks, set this to None. -# variant_shortlinks_dirname = "_v" # Whether or not to use variant shortlinks when resolving variant root paths. # You might want to disable this for testing purposes, but typically you would # leave this True. -# use_variant_shortlinks = True @@ -804,8 +840,8 @@ # Suites ############################################################################### -# The prefix character used to pass rez-specific commandline arguments to alias -# scripts in a suite. This must be a character other than "-", so that it doesn"t +# The prefix character used to pass rez-specific command line arguments to alias +# scripts in a suite. This must be a character other than ``-``, so that it doesn"t # clash with the wrapped tools" own commandline arguments. suite_alias_prefix_char = "+" @@ -814,43 +850,43 @@ # Appearance ############################################################################### -# Suppress all extraneous output - warnings, debug messages, progress indicators -# and so on. Overrides all warn_xxx and debug_xxx settings. +# Suppress all extraneous output (warnings, debug messages, progress indicators +# and so on). Overrides all ``warn_xxx`` and ``debug_xxx settings``. quiet = False # Show progress bars where applicable show_progress = True # The editor used to get user input in some cases. -# On osx, set this to "open -a " if you want to use a specific app. +# On macOS, set this to ``open -a `` if you want to use a specific app. editor = None -# The program used to view images by tools such as "rez-context -g" -# On osx, set this to "open -a " if you want to use a specific app. +# The program used to view images by tools such as :option:`rez-context -g` +# On macOS, set this to ``open -a `` if you want to use a specific app. image_viewer = None -# The browser used to view documentation; the rez-help tool uses this -# On osx, set this to "open -a " if you want to use a specific app. +# The browser used to view documentation. The :ref:`rez-help` tool uses this +# On macOS, set this to ``open -a `` if you want to use a specific app. browser = None -# The viewer used to view file diffs. On osx, set this to "open -a " +# The viewer used to view file diffs. On macOS, set this to ``open -a `` # if you want to use a specific app. difftool = None # The default image format that dot-graphs are rendered to. dot_image_format = "png" -# If true, tools such as rez-env will update the prompt when moving into a new +# If true, tools such as :ref:`rez-env` will update the prompt when moving into a new # resolved shell. Prompt nerds might do fancy things with their prompt that Rez -# can't deal with (but it can deal with a lot - colors etc - so try it first). -# By setting this to false, Rez will not change the prompt. Instead, you will -# probably want to set it yourself in your startup script (.bashrc etc). You will -# probably want to use the environment variable $REZ_ENV_PROMPT, which contains +# can't deal with (but it can deal with a lot (colors etc) so try it first). +# By setting this to False, Rez will not change the prompt. Instead, you will +# probably want to set it yourself in your startup script (``.bashrc`` etc). You will +# probably want to use the environment variable :envvar:`REZ_ENV_PROMPT`, which contains # the set of characters that are normally prefixed/suffixed to the prompt, ie -# '>', '>>' etc. +# ``>``, ``>>`` etc. set_prompt = True -# If true, prefixes the prompt, suffixes if false. Ignored if 'set_prompt' is +# If true, prefixes the prompt, suffixes if false. Ignored if :data:`set_prompt` is # false. prefix_prompt = True @@ -860,60 +896,63 @@ ############################################################################### # If not zero, truncates all package changelog entries to this maximum length. -# You should set this value - changelogs can theoretically be very large, and +# You should set this value. Changelogs can theoretically be very large, and # this adversely impacts package load times. max_package_changelog_chars = 65536 # If not zero, truncates all package changelogs to only show the last N commits max_package_changelog_revisions = 0 -# Default option on how to create scripts with util.create_executable_script. +# Default option on how to create scripts with :func:`~rez.utils.execution.create_executable_script`. # In order to support both windows and other OS it is recommended to set this -# to 'both'. +# to ``both``. # # Possible modes: +# # - single: # Creates the requested script only. # - py: -# Create .py script that will allow launching scripts on windows, -# if the shell adds .py to PATHEXT. Make sure to use PEP-397 py.exe -# as default application for .py files. +# Create ``.py`` script that will allow launching scripts on windows, +# if the shell adds .py to ``PATHEXT``. Make sure to use :pep:`397` ``py.exe`` +# as default application for ``.py`` files. # - platform_specific: # Will create py script on windows and requested on other platforms # - both: -# Creates the requested file and a .py script so that scripts can be +# Creates the requested file and a ``.py`` script so that scripts can be # launched without extension from windows and other systems. create_executable_script_mode = "single" -# Configurable pip extra arguments passed to the rez-pip install command. -# Since the rez-pip install command already includes some pre-configured -# arguments (target, use-pep517) this setting can potentially override the +# Configurable pip extra arguments passed to the :ref:`rez-pip` install command. +# Since the :ref:`rez-pip` install command already includes some pre-configured +# arguments (``--target``, ``--use-pep517``) this setting can potentially override the # default configuration in a way which can cause package installation issues. # It is recommended to refrain from overriding the default arguments and only # use this setting for additional arguments that might be needed. # https://pip.pypa.io/en/stable/reference/pip_install/#options pip_extra_args = [] -# Substitutions for re.sub when unknown parent paths are encountered in the -# pip package distribution record: *.dist-info/RECORD +# Substitutions for :func:`re.sub` when unknown parent paths are encountered in the +# pip package distribution record: :file:`{name}.dist-info/RECORD` # # Rez reads the distribution record to figure out where pip installed files # to, then copies them to their final sub-path in the rez package. Ie, python -# source files are hard coded to be moved under the "python" sub-folder inside -# the rez package, which then gets added to PYTHONPATH upon rez-env. +# source files are hard coded to be moved under the ``python`` sub-folder inside +# the rez package, which then gets added to :envvar:`PYTHONPATH` upon :ref:`rez-env`. # # When it can't find the file listed in the record AND the path starts -# with a reference to the parent directory "..", the following remaps are +# with a reference to the parent directory ``..``, the following remaps are # used to: +# # 1. Match a path listed in the record to perform the filepath remapping; -# 2. re.sub expression from step 1 to make the relative path of where pip +# 2. :func:`re.sub` expression from step 1 to make the relative path of where pip # actually installed the file to; -# 3. re.sub expression from step 1 to make the destination filepath, relative +# 3. :func:`re.sub` expression from step 1 to make the destination filepath, relative # to the rez variant root. # # Use these tokens to avoid regular expression and OS-specific path issues: -# - "{pardir}" or "{p}" for parent directory: os.pardir, i.e. ".." on Linux/Mac -# - "{sep}" or "{s}" for folder separators: os.sep, i.e. "/" on Linux/Mac +# +# - "{pardir}" or "{p}" for parent directory: :data:`os.pardir`, i.e. ``..`` on Linux/Mac +# - "{sep}" or "{s}" for folder separators: :data:`os.sep`, i.e. ``/`` on Linux/Mac pip_install_remaps = [ # Typical bin copy behaviour # Path in record | pip installed to | copy to rez destination @@ -935,49 +974,58 @@ }, ] -# Optional variables. A dict type config for storing arbitrary data that can be -# accessed by the [optionvars](Package-Commands#optionvars) function in packages -# *commands*. +# A dict type config for storing arbitrary data that can be +# accessed by the :func:`optionvars` function in packages +# :func:`commands`. # # This is like user preferences for packages, which may not easy to define in -# package's definition file directly due to the differences between machines/ -# users/pipeline-roles. +# package's definition file directly due to the differences between machines/users/pipeline-roles. # # Example: # -# # in your rezconfig.py -# optionvars = { -# "userRoles": ["artist"] -# } +# .. code-block:: python +# +# # in your rezconfig.py +# optionvars = { +# "userRoles": ["artist"] +# } # # And to access: # -# # in package.py -# def commands(): -# roles = optionvars("userRoles", default=None) or [] -# if "artist" in roles: -# env.SOMETHING_FRIENDLY = "Yes" +# .. code-block:: python +# +# # in package.py +# def commands(): +# roles = optionvars("userRoles", default=None) or [] +# if "artist" in roles: +# env.SOMETHING_FRIENDLY = "Yes" # # Note that you can refer to values in nested dicts using dot notation: # -# def commands(): -# if optionvars("nuke.lighting_tools_enabled"): -# ... +# .. code-block:: python # +# def commands(): +# if optionvars("nuke.lighting_tools_enabled"): +# ... optionvars = None ############################################################################### # Rez-1 Compatibility +# +# These settings are for rez v1 compatibility purposes. +# ############################################################################### -# If this is true, rxt files are written in yaml format. If false, they are -# written in json, which is a LOT faster. You would only set to true for +# If this is True, rxt files are written in YAML format. If False, they are +# written in JSON, which is a LOT faster. You would only set to true for # backwards compatibility reasons. Note that rez will detect either format on # rxt file load. rxt_as_yaml = False # Warn or disallow when a package is found to contain old rez-1-style commands. warn_old_commands = True + +# See :data:`warn_old_commands`. error_old_commands = False # Print old commands and their converted rex equivalent. Note that this can @@ -990,40 +1038,48 @@ # instead of "commands". Unlike "commands", "commands2" only allows new rex- # style commands. Once you have fully deprecated Rez-1, you should stop using # "commands2". -# TODO DEPRECATE +# TODO: DEPRECATE warn_commands2 = False + +# See :data:`warn_commands2`. error_commands2 = False # If True, Rez will continue to generate the given environment variables in # resolved environments, even though their use has been deprecated in Rez-2. # The variables in question, and their Rez-2 equivalent (if any) are: # -# REZ-1 | REZ-2 -# -------------------|----------------- -# REZ_REQUEST | REZ_USED_REQUEST -# REZ_RESOLVE | REZ_USED_RESOLVE -# REZ_VERSION | REZ_USED_VERSION -# REZ_PATH | REZ_USED -# REZ_RESOLVE_MODE | not set -# REZ_RAW_REQUEST | not set -# REZ_IN_REZ_RELEASE | not set +# ================== ========================== +# REZ-1 REZ-2 +# ================== ========================== +# REZ_REQUEST :envvar:`REZ_USED_REQUEST` +# REZ_RESOLVE :envvar:`REZ_USED_RESOLVE` +# REZ_VERSION :envvar:`REZ_USED_VERSION` +# REZ_PATH :envvar:`REZ_USED` +# REZ_RESOLVE_MODE not set +# REZ_RAW_REQUEST not set +# REZ_IN_REZ_RELEASE not set +# ================== ========================== rez_1_environment_variables = True # If True, Rez will continue to generate the given CMake variables at build and -# release time, even though their use has been deprecated in Rez-2. The +# release time, even though their use has been deprecated in Rez-2. The # variables in question, and their Rez-2 equivalent (if any) are: # -# REZ-1 | REZ-2 -# --------|--------------- -# CENTRAL | REZ_BUILD_TYPE +# ======= ======================== +# REZ-1 REZ-2 +# ======= ======================== +# CENTRAL :envvar:`REZ_BUILD_TYPE` +# ======= ======================== rez_1_cmake_variables = True # If True, override all compatibility-related settings so that Rez-1 support is # deprecated. This means that: +# # * All warn/error settings in this section of the config will be set to # warn=False, error=True; -# * rez_1_environment_variables will be set to False. -# * rez_1_cmake_variables will be set to False. +# * :data:`rez_1_environment_variables` will be set to False. +# * :data:`rez_1_cmake_variables` will be set to False. +# # You should aim to do this - it will mean your packages are more strictly # validated, and you can more easily use future versions of Rez. disable_rez_1_compatibility = False @@ -1034,43 +1090,45 @@ ############################################################################### # Where Rez's own documentation is hosted -documentation_url = "https://github.com/AcademySoftwareFoundation/rez/wiki" +documentation_url = "https://rez.readthedocs.io" ############################################################################### # Colorization -############################################################################### - +# # The following settings provide styling information for output to the console, # and is based on the capabilities of the Colorama module # (https://pypi.python.org/pypi/colorama). # -# *_fore and *_back colors are based on the colors supported by this module and -# the console. One or more styles can be applied using the *_styles +# \*_fore and \*_back colors are based on the colors supported by this module and +# the console. One or more styles can be applied using the \*_styles # configuration. These settings will also affect the logger used by rez. # # At the time of writing, valid values are: # fore/back: black, red, green, yellow, blue, magenta, cyan, white # style: dim, normal, bright +# +############################################################################### # Enables/disables colorization globally. # -# > [[media/icons/warning.png]] Note: Turned off for Windows currently as there seems -# > to be a problem with the Colorama module. +# .. warning:: +# Turned off for Windows currently as there seems to be a problem with the colorama module. # -# May also set to the string "force", which will make rez output color styling +# May also set to the string ``force``, which will make rez output color styling # information, even if the the output streams are not ttys. Useful if you are -# piping the output of rez, but will eventually be printing to a tty later. +# piping the output of rez, but will eventually be printing to a ``tty`` later. # When force is used, will generally be set through an environment variable, eg: # -# echo $(REZ_COLOR_ENABLED=force python -c "from rez.utils.colorize import Printer, local; Printer()('foo', local)") - +# .. code-block:: text +# +# echo $(REZ_COLOR_ENABLED=force python -c "from rez.utils.colorize import Printer, local; Printer()('foo', local)") +# # TODO: Colorama is documented to work on Windows and trivial test case # proves this to be the case, but it doesn't work in Rez (with cmd.exe). -# If the initialise is called in sec/rez/__init__.py then it does work, +# If the initialise is called in src/rez/__init__.py then it does work, # however this is not always desirable. # As it does with with some Windows shells it can be enabled in rezconfig - color_enabled = (os.name == "posix") ### Do not move or delete this comment (__DOC_END__) diff --git a/src/rez/system.py b/src/rez/system.py index 3cea0625f..4a9fed1e3 100644 --- a/src/rez/system.py +++ b/src/rez/system.py @@ -24,21 +24,18 @@ def rez_version(self): @cached_property def platform(self): """Get the current platform. - @returns The current platform. Examples: - linux - windows - osx + Returns: + The current platform (windows, linux, osx, etc). """ return platform_.name @cached_property def arch(self): """Get the current architecture. - @returns The current architecture. Examples: - x86_64 - i386 + Returns: + The current architecture (x86_64, i386, etc). """ r = platform_.arch return self._make_safe_version_string(r) @@ -46,20 +43,18 @@ def arch(self): @cached_property def os(self): """Get the current operating system. - @returns The current operating system. Examples: - Ubuntu-12.04 - CentOS-5.4 - windows-6.1.7600.sp1 - osx-10.6.2 + Returns: + The current operating system (Ubuntu-22.04, CentOS-7.8, windows-6.1.7600.sp1, etc). """ r = platform_.os return self._make_safe_version_string(r) @cached_property def variant(self): - """Returns a list of the form ["platform-X", "arch-X", "os-X"] suitable - for use as a variant in a system-dependent package.""" + """Returns a list of the form ``["platform-X", "arch-X", "os-X"]`` suitable + for use as a variant in a system-dependent package. + """ return ["platform-%s" % self.platform, "arch-%s" % self.arch, "os-%s" % self.os] @@ -68,10 +63,9 @@ def variant(self): @cached_property def shell(self): """Get the current shell. - @returns The current shell this process is running in. Examples: - bash - tcsh + Returns: + The current shell this process is running in (bash, tcsh, pwsh, etc). """ from rez.shells import get_shell_types shells = set(get_shell_types()) @@ -172,7 +166,7 @@ def home(self): @cached_property def fqdn(self): """ - @returns Fully qualified domain name, eg 'somesvr.somestudio.com' + Returns the fully qualified domain name (FQDN) of the current machine, eg ``somesvr.somestudio.com``. """ import socket return socket.getfqdn() @@ -180,7 +174,7 @@ def fqdn(self): @cached_property def hostname(self): """ - @returns The machine hostname, eg 'somesvr' + Returns the machine hostname, eg ``somesvr``. """ import socket return socket.gethostname() @@ -188,7 +182,7 @@ def hostname(self): @cached_property def domain(self): """ - @returns The domain, eg 'somestudio.com' + Returns the domain, eg ``somestudio.com``. """ try: return self.fqdn.split('.', 1)[1] @@ -249,14 +243,11 @@ def is_production_rez_install(self): @property def selftest_is_running(self): - """Return True if tests are running via rez-selftest tool.""" + """Return True if tests are running via ``rez-selftest`` tool.""" return os.getenv("__REZ_SELFTEST_RUNNING") == "1" def get_summary_string(self): """Get a string summarising the state of Rez as a whole. - - Returns: - String. """ from rez.plugin_managers import plugin_manager @@ -274,8 +265,8 @@ def clear_caches(self, hard=False): Args: hard (bool): Perform a 'hard' cache clear. This just means that the - memcached cache is also cleared. Generally this is not needed - - this option is for debugging purposes. + memcached cache is also cleared. Generally this is not needed. + This option is for debugging purposes. """ from rez.package_repository import package_repository_manager from rez.utils.memcached import memcached_client diff --git a/src/rez/utils/data_utils.py b/src/rez/utils/data_utils.py index 333418b5f..c3a8a4ea1 100644 --- a/src/rez/utils/data_utils.py +++ b/src/rez/utils/data_utils.py @@ -7,6 +7,7 @@ """ import os.path import json +import functools from rez.vendor.schema.schema import Schema, Optional from threading import Lock @@ -240,6 +241,8 @@ class cached_property(object): """ def __init__(self, func, name=None): self.func = func + # Make sure that Sphinx autodoc can follow and get the docstring from our wrapped function. + functools.update_wrapper(self, func) self.name = name or func.__name__ def __get__(self, instance, owner=None): @@ -254,6 +257,10 @@ def __get__(self, instance, owner=None): % (self.name, instance)) return result + # This is to silence Sphinx that complains that cached_property is not a callable. + def __call__(self): + raise RuntimeError("@cached_property should not be called.") + @classmethod def uncache(cls, instance, name): if hasattr(instance, name):