diff --git a/.github/workflows/build_nwx_buildenv_image.yaml b/.github/workflows/build_nwx_buildenv_image.yaml index aa2b793c..b77f00d8 100644 --- a/.github/workflows/build_nwx_buildenv_image.yaml +++ b/.github/workflows/build_nwx_buildenv_image.yaml @@ -21,7 +21,8 @@ jobs: build_image: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - name: Checkout Source + uses: actions/checkout@v4 - name: Login to GitHub Container Registry uses: docker/login-action@v2 with: diff --git a/.github/workflows/check_formatting.yaml b/.github/workflows/check_formatting.yaml new file mode 100644 index 00000000..96b498ac --- /dev/null +++ b/.github/workflows/check_formatting.yaml @@ -0,0 +1,88 @@ +# Copyright 2025 NWChemEx-Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +name: Check Formatting +# Performs checks for proper license headers and formatting. + +on: + workflow_call: + inputs: + license_config: + description: "The license configuration file for SkyWalking Eyes" + type: string + required: false + default: ".licenserc.yaml" + cpp_source_dirs: + description: "Space seperated list of dirs to apply clang-format to" + type: string + required: false + default: "include src tests" + format_python: + description: "Whether or not to format python files" + type: boolean + required: false + default: true + +jobs: + # Check licensing. + check_licensing: + if: inputs.license_config != '' + runs-on: ubuntu-latest + steps: + - name: Checkout Source + uses: actions/checkout@v4 + - name: Check for License Config + if: hashFiles(inputs.license_config) == '' + uses: actions/github-script@v3 + with: + script: | + core.setFailed("License Config file doesn't exist: ${{inputs.license_config}}") + - name: Check License + uses: apache/skywalking-eyes@v0.4.0 + with: + config: ${{ inputs.license_config }} + mode: check + + # Check C++ formatting. + check_cpp_format: + if: inputs.cpp_source_dirs != '' + runs-on: ubuntu-latest + steps: + - name: Checkout Source + uses: actions/checkout@v4 + - name: Check C++ Formatting + uses: DoozyX/clang-format-lint-action@v0.18 + with: + source: ${{ inputs.cpp_source_dirs }} + extensions: "hpp,cpp,ipp,h,c" + clangFormatVersion: 12 + inplace: False + + # Check Python formatting. + check_python_format: + if: inputs.format_python == true + runs-on: ubuntu-latest + steps: + - name: Checkout Source + uses: actions/checkout@v4 + - uses: actions/setup-python@v2 + with: + python-version: "3.x" + - name: Install yapf + run: pip install yapf + shell: bash + - name: Check Python Formatting + run: yapf -r -q . + shell: bash diff --git a/.github/workflows/common_pull_request.yaml b/.github/workflows/common_pull_request.yaml deleted file mode 100644 index 1290b5ad..00000000 --- a/.github/workflows/common_pull_request.yaml +++ /dev/null @@ -1,236 +0,0 @@ -# Copyright 2023 NWChemEx-Project -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -name: Common Pull Request Workflow -# Collects common Pull Request related jobs for the NWX stack, specifically -# 1. Apply licensing -# 2. Apply formatting -# 2. Build and test C++ libraries -# 3. Test building the documentation -# Each step can be skipped by setting the corresponding input variable as -# an empty string ''. - -on: - workflow_call: - inputs: - config_file: - description: "The license configuration file for SkyWalking Eyes" - type: string - required: false - default: '.licenserc.yaml' - source_dir: - description: "Space seperated list of dirs to apply clang-format to" - type: string - required: false - default: 'include src tests' - compilers: - description: "String of a JSON list of compilers to test" - type: string - required: false - default: '' - repo_toolchain: - description: "Path (from repo root) to a CMake toolchain for repo specific settings" - type: string - required: false - default: '' - doc_target: - description: "The name of the documentation target. Set to 'Sphinx' to skip doxygen" - type: string - required: false - default: '' - sphinx_fail_on_warning: - description: "Whether or not the Sphinx docs should fail due to warnings" - type: boolean - required: false - default: true - build_fail_on_warning: - description: "Whether warnings in the library build should be failures" - type: boolean - required: false - default: true - format_python: - description: "Whether or not to format python files" - type: boolean - required: false - default: true - secrets: - CMAIZE_GITHUB_TOKEN: - description: "Token passed to CMaize" - required: true - CONTAINER_REPO_TOKEN: - description: "Token to access Github Image Registry" - required: true - -jobs: - # Apply licensing and formatting, then push changes. - # If changes are made, the workflow should end and be re-triggered by the - # new commit to ensure that the changes don't break anything. - license_and_format: - runs-on: ubuntu-latest - outputs: - made_changes: ${{ steps.commit-changes.outputs.pushed }} - steps: - - uses: actions/checkout@v4 - with: - # Need non-default token so changes trigger additional CI - token: ${{ secrets.CMAIZE_GITHUB_TOKEN }} - repository: ${{ github.event.pull_request.head.repo.full_name }} - ref: ${{ github.event.pull_request.head.ref }} - - uses: actions/setup-python@v2 - if: inputs.format_python == true - with: - python-version: '3.x' - - name: Check License Config - if: | - inputs.config_file != '' && - hashFiles(inputs.config_file) == '' - uses: actions/github-script@v3 - with: - script: | - core.setFailed("License Config file doesn't exist: ${{inputs.config_file}}") - - name: Add license - if: inputs.config_file != '' - uses: apache/skywalking-eyes@v0.4.0 - with: - config: ${{ inputs.config_file }} - mode: fix - - name: Apply formatting - if: inputs.source_dir != '' - uses: DoozyX/clang-format-lint-action@v0.18 - with: - source: ${{ inputs.source_dir }} - extensions: 'hpp,cpp,ipp,h,c' - clangFormatVersion: 12 - inplace: True - - name: Apply python formatting - if: inputs.format_python == true - run: | - pip install yapf - find . -name '*.py' -print0 | xargs -0 yapf -i - shell: bash - - name: Commit any changes - id: commit-changes - uses: EndBug/add-and-commit@v9 - with: - author_name: github-actions[bot] - author_email: github-actions[bot]@users.noreply.github.com - message: 'Committing clang-format changes' - - # Build and test C++ libraries. - # Runs in a container built on the NWX Build Environment image. - test_library: - needs: license_and_format - # Only run if no new changes were made above and if compilers isn't empty - if: | - needs.license_and_format.outputs.made_changes == 'false' && - inputs.compilers != '' && toJson(fromJson(inputs.compilers)) != '[]' - runs-on: ubuntu-latest - container: - image: ghcr.io/nwchemex/nwx_buildenv:latest - credentials: - username: ${{ github.actor }} - password: ${{ secrets.CONTAINER_REPO_TOKEN }} - continue-on-error: true - strategy: - matrix: - compiler: ${{ fromJSON(inputs.compilers) }} - steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - name: Set ownership - run: | - # Fix for git not liking owner of the checkout dir - chown -R $(id -u):$(id -g) $PWD - - name: Make warnings into errors - if: inputs.build_fail_on_warning == true - run: | - toolchain=/toolchains/nwx_${{ matrix.compiler }}.cmake - echo 'include(/toolchains/error_flag.cmake)' >> $toolchain - - name: Append Repo Settings to CMake Toolchain - if: inputs.repo_toolchain != '' - run: | - toolchain=/toolchains/nwx_${{ matrix.compiler }}.cmake - echo 'include('${PWD}'/${{ inputs.repo_toolchain }})' >> $toolchain - shell: bash - - name: Python Requirements - run: | - if [ -f requirements.txt ]; then - pip install -r requirements.txt - fi - shell: bash - - name: Build and Test - env: - CMAIZE_GITHUB_TOKEN: ${{secrets.CMAIZE_GITHUB_TOKEN}} - run: | - toolchain=/toolchains/nwx_${{ matrix.compiler }}.cmake - echo 'set(CMAIZE_GITHUB_TOKEN '${CMAIZE_GITHUB_TOKEN}')' >> $toolchain - - cmake -Bbuild -H. -GNinja \ - -DCMAKE_INSTALL_PREFIX=./install \ - -DCMAKE_TOOLCHAIN_FILE="${toolchain}" - - cmake --build build --parallel - - cd build - OMPI_ALLOW_RUN_AS_ROOT=1 OMPI_ALLOW_RUN_AS_ROOT_CONFIRM=1 ctest -VV - shell: bash - - # Test building the documentation. - test_build_docs: - needs: license_and_format - # Only run if no new changes were made above - if: | - needs.license_and_format.outputs.made_changes == 'false' && - inputs.doc_target != '' - runs-on: ubuntu-latest - container: - image: ghcr.io/nwchemex/nwx_buildenv:latest - credentials: - username: ${{ github.actor }} - password: ${{ secrets.CONTAINER_REPO_TOKEN }} - steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - name: Set ownership - run: | - # Fix for git not liking owner of the checkout dir - chown -R $(id -u):$(id -g) $PWD - - name: Doxygen Docs - if: ${{ inputs.doc_target != 'Sphinx' }} - run: | - cmake -Bbuild -H. -GNinja -DBUILD_DOCS=ON -DONLY_BUILD_DOCS=ON - cmake --build build --target ${{ inputs.doc_target }} --parallel - - # Migrate the Doxygen documentation to the docs source - mkdir docs/build - mkdir docs/build/html - mv build/html "docs/build/html/${{ inputs.doc_target }}" - shell: bash - - name: Set Fail on Warnings - if: inputs.sphinx_fail_on_warning == true - run: | - echo "SPHINX_OPTS=-W --keep-going -n" >> $GITHUB_ENV - shell: bash - - name: Sphinx Docs - run: | - cd docs - if [ -f requirements.txt ]; then - pip install -r requirements.txt - fi - make html SPHINXOPTS="${SPHINX_OPTS}" - shell: bash - diff --git a/.github/workflows/common_merge.yaml b/.github/workflows/deploy_nwx_docs.yaml similarity index 64% rename from .github/workflows/common_merge.yaml rename to .github/workflows/deploy_nwx_docs.yaml index 49d20195..8492ca02 100644 --- a/.github/workflows/common_merge.yaml +++ b/.github/workflows/deploy_nwx_docs.yaml @@ -1,4 +1,4 @@ -# Copyright 2023 NWChemEx-Project +# Copyright 2025 NWChemEx-Project # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -13,70 +13,34 @@ # limitations under the License. # -name: Common Merge Workflow -# Collects common Merge related jobs for the NWX stack, specifically -# 1. Bumping version tags -# 2. Deploying updated documentation +name: Deploy Documentation on: workflow_call: inputs: - bump_tag: - description: "Whether or not to bump the tag of the commit" - type: boolean - required: false - default: true doc_target: description: "The name of the documentation target. Set to 'Sphinx' to skip doxygen" type: string - required: false - default: 'Sphinx' + required: true generate_module_docs: description: "Whether or not to generate PluginPlay Module documentation" type: boolean default: false - secrets: - CMAIZE_GITHUB_TOKEN: - description: "Token passed to CMaize" - required: false - TAG_TOKEN: - description: "Token used to bump the version tag" - required: true - CONTAINER_REPO_TOKEN: - description: "Token to access Github Image Registry" - required: true jobs: - # Bump the version tag - increment_tag: - if: inputs.bump_tag == true - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: haya14busa/action-bumpr@v1 - with: - default_bump_level: patch - github_token: ${{ secrets.TAG_TOKEN }} - tag_as_user: .github[bot] - tag_as_email: .github[bot]@github.com - - # Deploy updated documentation deploy_docs: - if: inputs.doc_target != '' runs-on: ubuntu-latest container: image: ghcr.io/nwchemex/nwx_buildenv:latest - credentials: - username: ${{ github.actor }} - password: ${{ secrets.CONTAINER_REPO_TOKEN }} steps: - - uses: actions/checkout@v4 + - name: Fail if Target is Blank + if: inputs.doc_target == '' + uses: actions/github-script@v3 with: - fetch-depth: 0 - - name: Set ownership - run: | - # Fix for git not liking owner of the checkout dir - chown -R $(id -u):$(id -g) $PWD + script: | + core.setFailed("doc_target must not be blank.") + - name: Checkout Source + uses: actions/checkout@v4 # These next two steps will configure CMake if required - name: Configure Only Docs if: | @@ -87,11 +51,8 @@ jobs: shell: bash - name: Configure Library if: inputs.generate_module_docs == true - env: - CMAIZE_GITHUB_TOKEN: ${{secrets.CMAIZE_GITHUB_TOKEN}} run: | toolchain=/toolchains/nwx_gcc-11.cmake - echo 'set(CMAIZE_GITHUB_TOKEN '${CMAIZE_GITHUB_TOKEN}')' >> $toolchain cmake -Bbuild -H. -GNinja -DBUILD_DOCS=ON \ -DCMAKE_INSTALL_PREFIX=./install \ diff --git a/.github/workflows/merge.yaml b/.github/workflows/merge.yaml index 0a046f5f..4beb3cbd 100644 --- a/.github/workflows/merge.yaml +++ b/.github/workflows/merge.yaml @@ -21,9 +21,12 @@ on: - master jobs: - Common-Merge: - uses: ./.github/workflows/common_merge.yaml + tag-commit: + uses: ./.github/workflows/tag.yaml + secrets: inherit + + deploy_nwx_docs: + uses: ./.github/workflows/deploy_nwx_docs.yaml with: - doc_target: 'Sphinx' - generate_module_docs: false + doc_target: "Sphinx" secrets: inherit diff --git a/.github/workflows/pull_request.yaml b/.github/workflows/pull_request.yaml index f198e294..22a057f0 100644 --- a/.github/workflows/pull_request.yaml +++ b/.github/workflows/pull_request.yaml @@ -21,11 +21,13 @@ on: - master jobs: - Common-Pull-Request: - uses: ./.github/workflows/common_pull_request.yaml + check_formatting: + uses: ./.github/workflows/check_formatting.yaml with: - config_file: '' - source_dir: '' - compilers: '' - doc_target: 'Sphinx' - secrets: inherit + license_config: "" # Skip license check here + cpp_source_dirs: "" # Skip clang-format check + + test_nwx_docs: + uses: ./.github/workflows/test_nwx_docs.yaml + with: + doc_target: "Sphinx" diff --git a/.github/workflows/tag.yaml b/.github/workflows/tag.yaml new file mode 100644 index 00000000..afb33520 --- /dev/null +++ b/.github/workflows/tag.yaml @@ -0,0 +1,37 @@ +# Copyright 2025 NWChemEx-Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +name: Update Commit Tag + +on: + workflow_call: + secrets: + TAG_TOKEN: + description: "Token used to bump the version tag" + required: true + +jobs: + increment_tag: + if: inputs.bump_tag == true + runs-on: ubuntu-latest + steps: + - name: Checkout Source + uses: actions/checkout@v4 + - uses: haya14busa/action-bumpr@v1 + with: + default_bump_level: patch + github_token: ${{ secrets.TAG_TOKEN }} + tag_as_user: .github[bot] + tag_as_email: .github[bot]@github.com diff --git a/.github/workflows/test_nwx_docs.yaml b/.github/workflows/test_nwx_docs.yaml new file mode 100644 index 00000000..d2812dee --- /dev/null +++ b/.github/workflows/test_nwx_docs.yaml @@ -0,0 +1,76 @@ +# Copyright 2025 NWChemEx-Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +name: Test NWX Library Docs +# Test building documentation + +on: + workflow_call: + inputs: + doc_target: + description: "The name of the documentation target. Set to 'Sphinx' to skip doxygen" + type: string + required: true + sphinx_fail_on_warning: + description: "Whether or not the Sphinx docs should fail due to warnings" + type: boolean + required: false + default: true + + + +jobs: + test_build_docs: + runs-on: ubuntu-latest + container: + image: ghcr.io/nwchemex/nwx_buildenv:latest + steps: + - name: Fail if Target is Blank + if: inputs.doc_target == '' + uses: actions/github-script@v3 + with: + script: | + core.setFailed("doc_target must not be blank.") + - name: Checkout Source + uses: actions/checkout@v4 + - name: Set ownership + run: | + # Fix for git not liking owner of the checkout dir + chown -R $(id -u):$(id -g) $PWD + - name: Doxygen Docs + if: ${{ inputs.doc_target != 'Sphinx' }} + run: | + cmake -Bbuild -H. -GNinja -DBUILD_DOCS=ON -DONLY_BUILD_DOCS=ON + cmake --build build --target ${{ inputs.doc_target }} --parallel + + # Migrate the Doxygen documentation to the docs source + mkdir docs/build + mkdir docs/build/html + mv build/html "docs/build/html/${{ inputs.doc_target }}" + shell: bash + - name: Set Fail on Warnings + if: inputs.sphinx_fail_on_warning == true + run: | + echo "SPHINX_OPTS=-W --keep-going -n" >> $GITHUB_ENV + shell: bash + - name: Sphinx Docs + run: | + cd docs + if [ -f requirements.txt ]; then + pip install -r requirements.txt + fi + make html SPHINXOPTS="${SPHINX_OPTS}" + shell: bash + diff --git a/.github/workflows/test_nwx_library.yaml b/.github/workflows/test_nwx_library.yaml new file mode 100644 index 00000000..5caedbf3 --- /dev/null +++ b/.github/workflows/test_nwx_library.yaml @@ -0,0 +1,106 @@ +# Copyright 2025 NWChemEx-Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +name: Test NWX Library +# Build and test an NWX library + +on: + workflow_call: + inputs: + compilers: + description: "String of a JSON list of compilers to test" + type: string + required: false + default: '["gcc-11", "clang-14"]' + repo_toolchain: + description: "Path (from repo root) to a CMake toolchain for repo specific settings" + type: string + required: false + default: "" + build_fail_on_warning: + description: "Whether warnings in the library build should be failures" + type: boolean + required: false + default: true + +jobs: + test_library: + runs-on: ubuntu-latest + container: + image: ghcr.io/nwchemex/nwx_buildenv:latest + strategy: + fail-fast: false + matrix: + compiler: ${{ fromJSON(inputs.compilers) }} + steps: + - name: Checkout Source + uses: actions/checkout@v4 + - name: Set Ownership + run: | + # Fix for git not liking owner of the checkout dir + chown -R $(id -u):$(id -g) $PWD + - name: Check for Compiler Toolchain + run: | + toolchain=/toolchains/nwx_${{ matrix.compiler }}.cmake + echo "Checking for compiler toolchain: $toolchain" + if test -f "$toolchain"; then + echo "Toolchain for compiler found." + exit 0 + else + echo "Toolchain for compiler not found." + exit 1 + fi + - name: Check for Repo Toolchain + if: inputs.repo_toolchain != '' + run: | + toolchain=${PWD}/${{ inputs.repo_toolchain }} + echo "Checking for repo toolchain: ${{ inputs.repo_toolchain }}" + if test -f "$toolchain"; then + echo "Repo toolchain found." + exit 0 + else + echo "Repo toolchain not found." + exit 1 + fi + - name: Append Repo Settings to CMake Toolchain + if: inputs.repo_toolchain != '' + run: | + toolchain=/toolchains/nwx_${{ matrix.compiler }}.cmake + echo 'include('${PWD}'/${{ inputs.repo_toolchain }})' >> $toolchain + shell: bash + - name: Make Warnings Into Errors + if: inputs.build_fail_on_warning == true + run: | + toolchain=/toolchains/nwx_${{ matrix.compiler }}.cmake + echo 'include(/toolchains/error_flag.cmake)' >> $toolchain + - name: Install Python Requirements + run: | + if [ -f requirements.txt ]; then + pip install -r requirements.txt + fi + shell: bash + - name: Build and Test + run: | + toolchain=/toolchains/nwx_${{ matrix.compiler }}.cmake + + cmake -Bbuild -H. -GNinja \ + -DCMAKE_INSTALL_PREFIX=./install \ + -DCMAKE_TOOLCHAIN_FILE="${toolchain}" + + cmake --build build --parallel + + cd build + OMPI_ALLOW_RUN_AS_ROOT=1 OMPI_ALLOW_RUN_AS_ROOT_CONFIRM=1 ctest -VV + shell: bash diff --git a/docs/source/continuous_deployment/architecture.png b/docs/source/continuous_deployment/architecture.png deleted file mode 100644 index cb14ad62..00000000 Binary files a/docs/source/continuous_deployment/architecture.png and /dev/null differ diff --git a/docs/source/continuous_deployment/ci_maintainer_notes.rst b/docs/source/continuous_deployment/ci_maintainer_notes.rst index f60eb01c..3bebc9a7 100644 --- a/docs/source/continuous_deployment/ci_maintainer_notes.rst +++ b/docs/source/continuous_deployment/ci_maintainer_notes.rst @@ -21,18 +21,18 @@ Here are a few useful tutorials on workflows in GitHub: Currently the following repositories in ``NWChemEx`` follow the methods outlined here: -1. ``ChemCache`` -2. ``Chemist`` -3. ``.github`` (This is our configuration repository) -4. ``Integrals`` -5. ``Mokup`` -6. ``NWChemEx`` -7. ``ParallelZone`` -8. ``PluginPlay`` -9. ``SimDE`` -10. ``SCF`` -11. ``TensorWrapper`` -12. ``Utilities`` +#. ``.github`` (This is our configuration repository) +#. ``ChemCache`` +#. ``Chemist`` +#. ``Integrals`` +#. ``Mokup`` +#. ``NWChemEx`` +#. ``ParallelZone`` +#. ``PluginPlay`` +#. ``SimDE`` +#. ``SCF`` +#. ``TensorWrapper`` +#. ``Utilities`` Common and Repo-Specific Workflows ================================== @@ -40,13 +40,17 @@ Common and Repo-Specific Workflows The ``.github/workflow`` directory in the ``NWChemEx/.github`` repo houses both the set of workflows specific to the ``.github`` repo and the set of reusable workflows that should be used throughout the NWX stack. Generally, -these reusable workflows should be named as ``common_{trigger event}.yaml``, -where ``{trigger event}`` can be ``pull_request``, ``merge``, or other `workflow -triggering events `__. -These common workflows aggregrate the reoccurring jobs required by the NWX repos -when a given event occurs, e.g applying formatting and testing changes to files -in a pull request. The common workflows for pull requests and merging changes -are discussed more specifically below. +these reusable workflows should be named in a way that describes what kind of +effect they should achieve, e.g. ``tag.yaml`` or ``check_formatting.yaml``. +These common workflows represent the reoccurring GitHub Actions operations +required by the NWX repos, e.g tagging a newly merged commit or checking +formatting. The common workflows currently available include: + +* ``check_formatting.yaml`` - performs common formatting and licensing checks +* ``deploy_nwx_docs.yaml`` - deploys the docs for an NWX library to GitHub pages +* ``tag.yaml`` - updates the version tag for a commit, used after merging into ``master`` +* ``test_nwx_docs.yaml`` - ensures the documentation of an NWX library builds properly +* ``test_nwx_library.yaml`` - ensures an NWX library builds and passes its tests Aside from the common workflows, there are the repo-specific workflows with the naming convention ``{trigger event}.yaml``. Where needed, a version of these @@ -55,56 +59,60 @@ workflows can be added in each repo. Here's an example of a repo-specific .. code-block:: yaml + name: Pull Request Workflow + on: pull_request: branches: - master - + jobs: - Common-Pull-Request: - uses: NWChemEx/.github/.github/workflows/common_pull_request.yaml@master + check_formatting: + uses: NWChemEx/.github/.github/workflows/check_formatting.yaml@master + with: + license_config: ".github/.licenserc.yaml" + + test_nwx_docs: + uses: NWChemEx/.github/.github/workflows/test_nwx_docs.yaml@master + with: + doc_target: "tensorwrapper_cxx_api" + + test_library: + uses: NWChemEx/.github/.github/workflows/test_nwx_library.yaml@master with: - config_file: '' - source_dir: '' - compilers: '' - doc_target: 'Sphinx' - secrets: - CMAIZE_GITHUB_TOKEN: ${{ secrets.CMAIZE_GITHUB_TOKEN }} - Unique-Job: + compilers: '["gcc-11", "clang-14"]' + + unique_job: # Other steps unique to the individual repos can be added as Jobs -Generally, these workflows are expected to call to the corresponding reusable -workflow to handle the reoccurring task and then locally implement any unique -automations. +And here is an example of the repo-specific ``merge.yaml``: + +.. code-block:: yaml + + name: Merge Workflow -CI coverage -=========== - -Common Pull Request Workflow ----------------------------- - -The common workflow for pull requests first handles the addition of any license -headers to files and runs clang formatting. NWChemEx uses `Apache License (2.0) -`__. We want all our source code -(except for some configuration, test and input files) to have the proper license -header when deployed. The licensing setting are configured by the -``.licenserc.yaml`` file in each repo's ``.github`` directory. For details on -this files construction, see `here `__ - -If there are changes resulting from these first steps, they will be pushed to -the branch and the workflow will end. The new commit resulting from this step -should then re-trigger the origin workflow, ensuring that the new changes are -included in the following steps. If changes were not made by the license and -formatting step, the workflow continues on to attempt to build the current -library and its documentation. If the library builds properly, it's test suite -is also run. - -Common Merge Workflow ---------------------- - -The common workflow for merging a successful pull request applies the correct -version tag to the resulting commit and deployes the repo's documentation to -GitHub Pages. + on: + push: + branches: + - master + + jobs: + tag-commit: + uses: NWChemEx/.github/.github/workflows/tag.yaml@master + secrets: inherit + + deploy_nwx_docs: + uses: NWChemEx/.github/.github/workflows/deploy_nwx_docs.yaml@master + with: + doc_target: "XYZ_cxx_api" + secrets: inherit + + unique_job: + # Other steps unique to the individual repos can be added as Jobs + +Generally, these workflows are expected to call to the necessary reusable +workflows to handle the reoccurring tasks and then locally implement any unique +automations. NWX Build Environment Image =========================== diff --git a/docs/source/continuous_deployment/documentation.rst b/docs/source/continuous_deployment/documentation.rst index d0362554..42a3837f 100644 --- a/docs/source/continuous_deployment/documentation.rst +++ b/docs/source/continuous_deployment/documentation.rst @@ -22,9 +22,11 @@ functions. The C++ API documentation should be written using Doxygen's native markdown (markup?) language. The Doxygen documentation is built using CMake's Doxygen module. Using CMake's Doxygen module is fairly cookie-cutter so NWChemEx provides the ``nwx_cxx_api_docs.cmake`` CMake module to avoid code duplication. -The master version of ``nwx_cxx_api_docs.cmake`` lives in this repo and is -synchronized with all other repos. Downstream repos need only include the -following two lines in their top-level ``CMakeLists.txt`` file: +The master version of ``nwx_cxx_api_docs.cmake`` lives in the +``MWChemEx/NWXCMake`` repo and is downloaded along with the other scripts in +that repo by the CMake module ``get_nwx_cmake.cmake`` found in most repositories +in this organization. Downstream repos need only include the following two lines +in their top-level ``CMakeLists.txt`` file: .. code-block:: cmake diff --git a/scripts/make_tutorials.py b/scripts/make_tutorials.py deleted file mode 100644 index 741d93ac..00000000 --- a/scripts/make_tutorials.py +++ /dev/null @@ -1,283 +0,0 @@ -import os - - -def strip_newline(block): - """ - Strips any blank lines from the beginning and end of the block. - :param block: The block to parse. - :return: The block w/o the proceeding/trailing blank lines - """ - start = 0 - end = len(block) - for line in block: - if line.strip(): - break - start += 1 - - # Early termination for empty block - if start == end: - return [] - - for line in reversed(block[start:]): - if line.strip(): - break - end -= 1 - - return block[start:end] - - -def write_code(block, lang): - """ - Given a code block from the parsed file this function will turn it into the - corresponding reST code. This function will automatically remove any - proceeding or trailing blank lines from the parsed code block. - - :param block: The code block to print out. - :param lang: The language of the code snippet - :return: A string suitable for printing in a reST file - """ - a_tab = " " * 4 - - parsed_block = strip_newline(block) - - if len(parsed_block) == 0: - return "" - - # Assemble the actual code block - output = ".. code:: {}\n\n".format(lang) - - for line in parsed_block: - output += a_tab + line - output += '\n' - - return output - - -def write_comment(block): - """ - This function takes the raw tutorial comment block and turns it into a - string suitable for printing in a .rst file. This function does no - processing of the block aside from removing proceeding and trailing - new lines - - :param block: The tutorial block comment to print out. - :return: The comment-blcok reST-itized. - """ - - output = "" - for line in strip_newline(block): - output += line - output += '\n' - return output - - -def parse_file(cc, filename): - """ - This function actually parses the test. It does so using some pretty simple - logic. Consequentially, you can't deviate too much from the expected format. - Specifically we assume: - - - The start of the tutorial block (i.e., "TUTORIAL") is the - first non-whitespace token on the line. - - The start of a skipping directive (i.e., - "TUTORIAL_START_SKIP") is the first non-whitespace token on - the line. - - The end of a skipping directive (i.e., - "TUTORIAL_STOP_SKIP") is the first non-whitespace token on - the line. - C++ is the first non-whitespace token on the line. - - all comment blocks are continuous (no blank lines in the middle) - - :param cc: The character denoting the comment - :param filename: The full path to the file - :return: A list of tutorial comments and code blocks - """ - - # These are parsing strings we'll need to look for - tutor_start = cc + "TUTORIAL" - tutor_skip_start = cc + "TUTORIAL_START_SKIP" - tutor_skip_stop = cc + "TUTORIAL_STOP_SKIP" - - comments = [] # Will be the comments we find - code = [] # Will be the code snippets we fine - in_comment = False # True signals we are in a tutorial comment block - skipping = False - which_first = None - - with open(filename, 'r') as input_file: - for line in input_file: - no_ws = line.lstrip() # The line w/o proceeding whitespace - - # Dispatch conditions - is_skip_start = tutor_skip_start == no_ws[:len(tutor_skip_start)] - is_skip_stop = tutor_skip_stop == no_ws[:len(tutor_skip_stop)] - is_tutor_start = tutor_start == no_ws[:len(tutor_start)] - is_comment = cc == line.lstrip()[:len(cc)] - - # Actually do the dispatching - if skipping: # Currently skipping - if is_skip_stop: # and we were told to stop - skipping = False - elif is_skip_stop: # Not skipping, but told to stop - raise Exception("TUTORIAL_STOP_SKIP w/o TUTORIAL_START_SKIP") - elif is_skip_start: # Told to start skipping - skipping = True - elif is_tutor_start: # Told to start tutorial comment - if which_first is None: - which_first = True - if in_comment: - raise Exception("Can't nest TUTORIAL sections") - comments.append([]) # Start a new comment block - in_comment = True - elif is_comment and in_comment: # Part of tutorial comment - comments[-1].append(no_ws[len(cc):]) # omit comment character - elif in_comment: # 1st line outside a comment block - in_comment = False - code.append([]) - code[-1].append(line) - else: # n-th line outside a comment block - if which_first is None: - which_first = False - if len(code) == 0: # If code came first, there's no array yet - code.append([]) - code[-1].append(line) - - return comments, code, which_first - - -def write_tutorial(name, lang, comments, code, first): - """ - Given the parsed comments and code blocks this function will write out the - full reST page for the tutorial. It assumes that the code blocks and - comment blocks are interspersed, i.e. the original file went - - code, comment, code, ... - - or - - comment, code, comment, ... - - Consequentially the number of code and comment blocks must be equal or can - differ by at most one. - - :param name: The name of the tutorial. Will be used for the title. - :param comments: A list of the comment blocks. - :param code: A list of the code blocks. - :param frist: True if a comment was first and false if a code block was - :param lang: The language for the reST code snippets - :return: - """ - - first_list = comments if first else code - second_list = code if first else comments - n_first = len(first_list) - n_second = len(second_list) - - # The first list needs to be either the same size as the second or one more - if n_first - n_second > 1: - raise Exception("abs(#code blocks - #comment blocks) > 1") - - # Write the title of the tutorial - output = name + '\n' - output += '=' * len(name) + "\n\n" - - for i in range( - n_second): # Loop over second because it is possibly shorter - if first: - output += write_comment(comments[i]) - output += write_code(code[i], lang) - else: - output += write_code(code[i], lang) - output += write_comment(comments[i]) - - if n_first == n_second + 1: - if first: - output += write_comment(comments[-1]) - else: - output += write_code(code[-1], lang) - - return output - - -def write_index(file_names): - output = "List of Tutorials\n"\ - "=================\n\n"\ - ".. toctree::\n"\ - " :maxdepth: 2\n"\ - " :caption: Contents:\n\n" - for file_i in file_names: - output += " " + file_i + "\n" - return output - - -def sort_file_names(file_names): - return - - -def make_tutorials(input_dir, output_dir): - """ - Given a full path to a directory, this function will parse each source file - it finds. This process is repeated recursively for each subdirectory. For - each tutorial file, a .rst file with the same name will be created which - contains the contents of the source file converted to reST. The resulting - tutorial will be named X, where X is the file name (without extension) - converted to title case with underscores replace by spaces. - - :param input_dir: The full path to the directory containing the tutorials. - :param output_dir: The full path to the directory where the tutorials should - go - :return: nothing - """ - - # Define the comment characters and name for each language - comment_chars = {".py": '#', ".hpp": "//", ".cpp": "//", ".cmake": '#'} - lang = {".py": "python", ".hpp": "c++", ".cpp": "c++", ".cmake": "cmake"} - - # Make output directory if it does not exist - if not os.path.exists(output_dir): - os.mkdir(output_dir) - - file_names = [] # Will be list of reST files we make - - for file_name in os.listdir(input_dir): - input_file = os.path.join(input_dir, file_name) - file_name_base, extension = os.path.splitext(file_name) - - if os.path.isdir(input_file): - make_tutorials(input_file, os.path.join(output_dir, file_name)) - file_names.append(os.path.join(file_name, "index")) - continue - - if extension not in lang: # Not a file we're supposed to parse - continue - - # Assemble the name of input file, output file, and tutorial - - output_file = os.path.join(output_dir, file_name_base + ".rst") - tutorial_name = file_name_base.replace('_', ' ').title() - - file_names.append(file_name_base) - - rv = write_tutorial(tutorial_name, lang[extension], - *parse_file(comment_chars[extension], input_file)) - with open(output_file, 'w') as output_file: - output_file.write(rv) - - #with open(os.path.join(output_dir, "index.rst"), 'w') as index_file: - # index_file.write(write_index(file_names)) - - -# def main(): -# docs_dir = os.path.dirname(os.path.realpath(__file__)) -# root_dir = os.path.dirname(docs_dir) -# examples_dir = os.path.join(root_dir, "tests", "examples") -# tutorial_dir = os.path.join(docs_dir, "source", "tutorials") -# -# if not os.path.exists(tutorial_dir): -# os.mkdir(tutorial_dir) -# -# write_tutorials(examples_dir, tutorial_dir) -# subprocess.call(["make", "html"]) -# -# if __name__ == "__main__" : -# main()