diff --git a/.azure/docs-linux.yml b/.azure/docs-linux.yml index 6e4e30491652..ea768ca9e9b9 100644 --- a/.azure/docs-linux.yml +++ b/.azure/docs-linux.yml @@ -19,16 +19,14 @@ jobs: versionSpec: '${{ parameters.pythonVersion }}' displayName: 'Use Python ${{ parameters.pythonVersion }}' - - bash: | - set -e - python -m pip install --upgrade pip setuptools wheel - python -m pip install -U "tox<4.4.0" - sudo apt-get update - sudo apt-get install -y graphviz pandoc + - bash: tools/install_ubuntu_docs_dependencies.sh displayName: 'Install dependencies' - bash: | - tox -edocs + set -e + tox -e docs + # Clean up Sphinx detritus. + rm -rf docs/_build/html/{.doctrees,.buildinfo} displayName: 'Run Docs build' - task: ArchiveFiles@2 diff --git a/.azure/tutorials-linux.yml b/.azure/tutorials-linux.yml index 197ca186615a..de84d3fa8f48 100644 --- a/.azure/tutorials-linux.yml +++ b/.azure/tutorials-linux.yml @@ -16,14 +16,10 @@ jobs: versionSpec: '${{ parameters.pythonVersion }}' displayName: 'Use Python ${{ parameters.pythonVersion }}' - - bash: | - set -e - python -m pip install --upgrade pip setuptools wheel - python -m pip install -U "tox<4.4.0" - sudo apt-get update - sudo apt-get install -y graphviz pandoc + - bash: tools/install_ubuntu_docs_dependencies.sh displayName: 'Install dependencies' + # Sync with '.github/workflows/docs_deploy.yml' - bash: tools/prepare_tutorials.bash algorithms circuits circuits_advanced operators displayName: 'Download current tutorials' diff --git a/.github/workflows/docs_deploy.yml b/.github/workflows/docs_deploy.yml new file mode 100644 index 000000000000..1f32c9b94948 --- /dev/null +++ b/.github/workflows/docs_deploy.yml @@ -0,0 +1,266 @@ +name: Documentation +on: + push: + branches: + - main + tags: + # Only match non-prerelease tags. + - '[0-9]+.[0-9]+.[0-9]' + workflow_dispatch: + inputs: + deploy_prefix: + description: "Deployment prefix (leave blank for the root): https://qiskit.org/documentation/." + required: false + type: string + do_deployment: + description: "Push to qiskit.org?" + required: false + type: boolean + do_translatables: + description: "Push translatable strings?" + required: false + type: boolean + +jobs: + build: + if: github.repository_owner == "Qiskit" + name: Build + runs-on: ubuntu-latest + + outputs: + latest_tag: ${{ steps.latest_tag.outputs.latest_tag }} + + steps: + - uses: actions/checkout@v3 + with: + # We need to fetch the whole history so 'reno' can do its job and we can inspect tags. + fetch-depth: 0 + + - name: Determine latest full release tag + id: latest_tag + run: | + set -e + latest_tag=$(git tag --list --sort=-version:refname | sed -n '/^[0-9]\+\.[0-9]\+\.[0-9]\+$/p' | head -n 1) + echo "Latest release tag: '$latest_tag'" + echo "latest_tag=$latest_tag" >> "$GITHUB_OUTPUT" + + - uses: actions/setup-python@v4 + name: Install Python + with: + # Sync with 'documentationPythonVersion' in 'azure-pipelines.yml'. + python-version: '3.9' + + - name: Install dependencies + run: tools/install_ubuntu_docs_dependencies.sh + + # Sync with '.azure/tutorials-linux.yml'. + - name: Download current tutorials + run: tools/prepare_tutorials.bash algorithms circuits circuits_advanced operators + shell: bash + + # This is just to have tox create the environment, so we can use it to execute the tutorials. + # We want to re-use it later for the build, hence 'tox run --notest' instead of 'tox devenv'. + - name: Prepare Python environment + run: tox run -e docs --notest + + # The reason to use the custom script rather than letting 'nbsphinx' do its thing normally + # within the Sphinx build is so that the execution process is the same as in the test CI. + - name: Execute tutorials in place + run: .tox/docs/bin/python tools/execute_tutorials.py docs/tutorials + env: + QISKIT_CELL_TIMEOUT: "300" + + - name: Build documentation + # We can skip re-installing the package, since we just did it a couple of steps ago. + run: tox run -e docs --skip-pkg-install + env: + QISKIT_ENABLE_ANALYTICS: "true" + # We've already built them. + QISKIT_DOCS_BUILD_TUTORIALS: "never" + + - name: Build translatable strings + run: tox -e gettext + env: + # We've already built them. + QISKIT_DOCS_BUILD_TUTORIALS: "never" + + - name: Store built documentation artifact + uses: actions/upload-artifact@v3 + with: + name: qiskit-docs + path: | + ./docs/_build/html/* + !**/.doctrees + !**/.buildinfo + if-no-files-found: error + + - name: Store translatable strings artifact + uses: actions/upload-artifact@v3 + with: + name: qiskit-translatables + path: ./docs/locale/en/* + if-no-files-found: error + + deploy: + if: github.event_name != 'workflow_dispatch' || inputs.do_deployment + name: Deploy to qiskit.org + needs: [build] + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + with: + path: qiskit + + - uses: actions/download-artifact@v3 + with: + name: qiskit-docs + path: deploy + + - id: choose + name: Choose deployment location(s) + run: | + set -e + declare -a prefixes + case ${{ github.event_name }} in + push) + case ${{ github.ref_type }} in + branch) + if [[ "$GITHUB_REF_NAME" != "main" ]]; then + echo "Push to unhandled branch '$GITHUB_REF_NAME'" >&2 + exit 1 + fi + + prefixes+=( "dev" ) + ;; + tag) + tag=$GITHUB_REF_NAME + echo "Full tag: ${tag}" + IFS=. read -ra version <<< "$tag" + minor_version="${version[0]}.${version[1]}" + echo "Minor version: ${minor_version}" + prefixes+=( "stable/${minor_version}" ) + if [[ "$tag" == "$LATEST_TAG" ]]; then + # Deploy to the root as well. + prefixes+=( "" ) + fi + ;; + *) + echo "Unhandled reference type '${{ github.ref_type }}'" >&2 + exit 1 + ;; + esac + ;; + workflow_dispatch) + prefixes+=( "$WORKFLOW_DISPATCH_PREFIX" ) + ;; + *) + echo "Unhandled GitHub event ${{ github.event_name }}" >&2 + exit 1 + ;; + esac + # Join the array of prefixes into a colon-delimited list for + # serialisation. This includes a trailing colon, so we can detect + # the presence of the empty string, even if it's the only prefix. + if [[ "${#prefixes[@]}" -gt 0 ]]; then + joined_prefixes=$(printf "%s:" "${prefixes[@]}") + echo "Chosen deployment prefixes: '$joined_prefixes'" + echo "joined_prefixes=$joined_prefixes" >> "$GITHUB_OUTPUT" + else + echo "Nothing to deploy to." + fi + env: + LATEST_TAG: ${{ needs.build.outputs.latest_tag }} + GITHUB_REF_NAME: ${{ github.ref_name }} + WORKFLOW_DISPATCH_PREFIX: ${{ inputs.deploy_prefix }} + + - name: Install rclone + run: | + set -e + curl https://downloads.rclone.org/rclone-current-linux-amd64.deb -o rclone.deb + sudo apt-get install -y ./rclone.deb + + - name: Deploy to qiskit.org + if: ${{ steps.choose.outputs.joined_prefixes != '' }} + run: | + set -e + RCLONE_CONFIG=$(rclone config file | tail -1) + openssl aes-256-cbc -K "$RCLONE_KEY" -iv "$RCLONE_IV" -in qiskit/tools/rclone.conf.enc -out "$RCLONE_CONFIG" -d + IFS=: read -ra prefixes <<< "$JOINED_PREFIXES" + for prefix in "${prefixes[@]}"; do + # The 'documentation' bit of the prefix is hard-coded in this step + # rather than being chosen during the prefix-choosing portion + # because we don't want to allow the 'workflow_dispatch' event + # trigger to accidentally allow a deployment to a dodgy prefix that + # wipes out _everything_ on qiskit.org. + location=documentation/$prefix + echo "Deploying to 'qiskit.org/$location'" + rclone sync --progress --exclude-from qiskit/tools/docs_exclude.txt deploy "IBMCOS:qiskit-org-web-resources/$location" + done + env: + JOINED_PREFIXES: ${{ steps.choose.outputs.joined_prefixes }} + RCLONE_KEY: ${{ secrets.ENCRYPTED_RCLONE_KEY}} + RCLONE_IV: ${{ secrets.ENCRYPTED_RCLONE_IV }} + + deploy_translatables: + if: (github.event_name == 'workflow_dispatch' && inputs.do_translatables) || (github.event_name == 'push' && github.ref_type == 'tag' && github.ref_name == needs.build.outputs.latest_tag) + name: Push translatable strings + needs: [build] + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + with: + path: 'qiskit' + + - uses: actions/download-artifact@v3 + with: + name: qiskit-translatables + path: 'deploy' + + - name: Decrypt SSH secret key + id: ssh_key + run: | + set -e + ssh_key=$(openssl enc -aes-256-cbc -d -in qiskit/tools/github_poBranch_update_key.enc -K $SSH_UPDATE_KEY -iv $SSH_UPDATE_IV) + echo "::add-mask::${ssh_key}" + echo "ssh_key=${ssh_key}" >> "$GITHUB_OUTPUT" + env: + SSH_UPDATE_KEY: ${{ secrets.ENCRYPTED_DEPLOY_PO_BRANCH_KEY }} + SSH_UPDATE_IV: ${{ secrets.ENCRYPTED_DEPLOY_PO_BRANCH_IV }} + + - uses: actions/checkout@v3 + with: + repository: 'qiskit-community/qiskit-translations' + path: 'qiskit-translations' + ssh-key: '${{ steps.ssh_key.outputs.ssh_key }}' + + - name: Remove ignored documents + run: rm -r LC_MESSAGES/{apidocs,stubs} + working-directory: 'deploy' + + - name: Push changes to translations repository + run: | + set -e + shopt -s failglob + # Bring the new `.po` target files into the repository. + git rm -r --ignore-unmatch docs/locale/en + mv "${{ github.workspace }}/deploy" docs/locale/en + # Update the ways to recreate the build. + cp "${{ github.workspace }}/qiskit/"{setup.py,requirements-*.txt,constraints.txt} . + git add . + + cat > COMMIT_MSG << EOF + Automated documentation update to add .po files from ${{ github.repository }} + + skip ci + + Commit: ${{ github.sha }} + GitHub Actions run: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + EOF + + git config user.name "Qiskit Autodeploy" + git config user.email "qiskit@qiskit.org" + git commit -F COMMIT_MSG + git push origin + working-directory: 'qiskit-translations' diff --git a/.gitignore b/.gitignore index 8b2cb16279ee..c2a5039d1ca4 100644 --- a/.gitignore +++ b/.gitignore @@ -76,8 +76,6 @@ instance/ # Scrapy stuff: .scrapy -# Sphinx documentation -docs/_build/ # PyBuilder target/ @@ -119,10 +117,6 @@ tutorial/rst/_build/* test/python/test_qasm_python_simulator.pdf -doc/_build/* - -doc/**/_autodoc - qiskit/bin/* test/python/test_save.json @@ -143,8 +137,11 @@ src/qasm-simulator-cpp/test/qubit_vector_tests qiskit/transpiler/passes/**/cython/**/*.cpp qiskit/quantum_info/states/cython/*.cpp -docs/stubs/* -executed_tutorials/ +# Sphinx documentation +/docs/_build +/docs/stubs +/docs/locale +/executed_tutorials # Notebook testing images test/visual/mpl/circuit/circuit_results/*.png diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 2f04ae2e2ef8..95f3fbc7bd77 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -60,6 +60,7 @@ parameters: type: string default: "3.8" + # Sync with 'python-version' in '.github/workflows/docs_deploy.yml'. - name: "documentationPythonVersion" displayName: "Version of Python to use to build Sphinx documentation" type: string diff --git a/docs/conf.py b/docs/conf.py index 851fd3aa424d..efe21c6d1d6a 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -28,6 +28,9 @@ # The full version, including alpha/beta/rc tags release = "0.45.0" +# The language for content autogenerated by Sphinx or the default for gettext content translation. +language = "en" + # For 'qiskit_sphinx_theme' tells it we're based at 'https://qiskit.org/'. # Should not include the subdirectory for the stable version. docs_url_prefix = "documentation" @@ -57,8 +60,20 @@ # Available keys are 'figure', 'table', 'code-block' and 'section'. '%s' is the number. numfig_format = {"table": "Table %s"} -# The language for content autogenerated by Sphinx or the default for gettext content translation. -language = "en" +# Translations configuration. +translations_list = [ + ("en", "English"), + ("bn_BN", "Bengali"), + ("fr_FR", "French"), + ("de_DE", "German"), + ("ja_JP", "Japanese"), + ("ko_KR", "Korean"), + ("pt_UN", "Portuguese"), + ("es_UN", "Spanish"), + ("ta_IN", "Tamil"), +] +locale_dirs = ["locale/"] +gettext_compact = False # Relative to source directory, affects general discovery, and html_static_path and html_extra_path. exclude_patterns = ["_build", "**.ipynb_checkpoints"] @@ -112,7 +127,7 @@ html_favicon = "images/favicon.ico" html_last_updated_fmt = "%Y/%m/%d" html_context = { - "analytics_enabled": os.getenv("QISKIT_ENABLE_ANALYTICS", False) + "analytics_enabled": bool(os.getenv("QISKIT_ENABLE_ANALYTICS", "")) } # enable segment analytics for qiskit.org/documentation html_static_path = ["_static"] diff --git a/tools/docs_exclude.txt b/tools/docs_exclude.txt new file mode 100644 index 000000000000..5d58decc7e22 --- /dev/null +++ b/tools/docs_exclude.txt @@ -0,0 +1,3 @@ +/stable/** +/locale/** +/dev/** diff --git a/tools/github_poBranch_update_key.enc b/tools/github_poBranch_update_key.enc new file mode 100644 index 000000000000..dcdfb55197c4 Binary files /dev/null and b/tools/github_poBranch_update_key.enc differ diff --git a/tools/install_ubuntu_docs_dependencies.sh b/tools/install_ubuntu_docs_dependencies.sh new file mode 100755 index 000000000000..f071b88d3f61 --- /dev/null +++ b/tools/install_ubuntu_docs_dependencies.sh @@ -0,0 +1,11 @@ +#!/bin/sh +# +# Prepare an Ubuntu CI machine for running 'tox -e docs'. Assumes that Python is available. + +set -e + +python -m pip install --upgrade pip setuptools wheel +python -m pip install --upgrade "tox<4.4.0" + +sudo apt-get update +sudo apt-get install -y graphviz pandoc diff --git a/tools/rclone.conf.enc b/tools/rclone.conf.enc new file mode 100644 index 000000000000..985bd728abc0 Binary files /dev/null and b/tools/rclone.conf.enc differ diff --git a/tox.ini b/tox.ini index d62dcc9fa6cc..e7daf7756d51 100644 --- a/tox.ini +++ b/tox.ini @@ -73,11 +73,12 @@ setenv = {[testenv]setenv} QISKIT_SUPPRESS_PACKAGING_WARNINGS=Y RUST_DEBUG=1 # Faster to compile. -passenv = {[testenv]passenv}, QISKIT_DOCS_BUILD_TUTORIALS +passenv = {[testenv]passenv}, QISKIT_DOCS_BUILD_TUTORIALS, QISKIT_CELL_TIMEOUT deps = setuptools_rust # This is work around for the bug of tox 3 (see #8606 for more details.) -r{toxinidir}/requirements-dev.txt -r{toxinidir}/requirements-optional.txt + -r{toxinidir}/requirements-tutorials.txt # Some optionals depend on Terra. We want to make sure pip satisfies that requirement from a local # installation, not from PyPI. But Tox normally doesn't install the local installation until # after `deps` is installed. So, instead, we tell pip to do the local installation at the same @@ -92,13 +93,18 @@ deps = allowlist_externals = rm commands = - rm -rf {toxinidir}/docs/stubs/ {toxinidir}/docs/_build + rm -rf {toxinidir}/docs/stubs/ {toxinidir}/docs/_build {toxinidir}/docs/locale [testenv:tutorials] -basepython = python3 -deps = - {[testenv:docs]deps} - -r{toxinidir}/requirements-tutorials.txt -passenv = {[testenv]passenv}, QISKIT_CELL_TIMEOUT +base = docs commands = python tools/execute_tutorials.py {toxinidir}/docs/tutorials --out={toxinidir}/executed_tutorials {posargs} + +[testenv:gettext] +base = docs +deps = + {[testenv:docs]deps} + sphinx-intl +commands = + sphinx-build -b gettext docs docs/_build/gettext {posargs} + sphinx-intl -c docs/conf.py update -p docs/_build/gettext -l en -d docs/locale