diff --git a/.github/workflows/Preview-Url-Comment.yml b/.github/workflows/Preview-Url-Comment.yml new file mode 100644 index 00000000000000..4f0c629071224b --- /dev/null +++ b/.github/workflows/Preview-Url-Comment.yml @@ -0,0 +1,58 @@ +name: Comment Preview URLs + +on: + workflow_run: + workflows: ["Doc-Preview"] + types: + - completed + +jobs: + comment: + name: Post Preview URLs Comment + runs-on: ubuntu-latest + if: > + github.event.workflow_run.event == 'pull_request' && + github.event.workflow_run.conclusion == 'success' + permissions: + pull-requests: write + + steps: + - name: Download artifacts + id: download + uses: actions/download-artifact@v4 + continue-on-error: true + with: + name: doc-preview-comment + github-token: ${{ secrets.GITHUB_TOKEN }} + run-id: ${{ github.event.workflow_run.id }} + + - name: Read artifacts + id: artifacts-data + if: steps.download.outcome == 'success' + run: | + PR_NUMBER=$(cat pr_number.txt) + echo "pr_number=$PR_NUMBER" >> $GITHUB_OUTPUT + COMMENT_BODY=$(cat comment_body.txt) + { + echo 'comment_body<> $GITHUB_OUTPUT + + - name: Find existing comment + id: fc + if: steps.download.outcome == 'success' + uses: peter-evans/find-comment@v4 + with: + issue-number: ${{ steps.artifacts-data.outputs.pr_number }} + comment-author: 'github-actions[bot]' + body-includes: '本次 PR 文档预览链接' + + - name: Create or update comment + if: steps.download.outcome == 'success' + uses: peter-evans/create-or-update-comment@v4 + with: + comment-id: ${{ steps.fc.outputs.comment-id }} + issue-number: ${{ steps.artifacts-data.outputs.pr_number }} + body: ${{ steps.artifacts-data.outputs.comment_body }} + edit-mode: replace diff --git a/.github/workflows/_Doc-Preview.yml b/.github/workflows/_Doc-Preview.yml index 04c3d77179c488..57e24fbc8d4413 100644 --- a/.github/workflows/_Doc-Preview.yml +++ b/.github/workflows/_Doc-Preview.yml @@ -86,25 +86,70 @@ jobs: -w /paddle --network host ${docker_image} - name: Doc build + id: doc_build run: | + CHANGES_FLAG_FILE="${{ github.workspace }}/has_changes.txt" + rm -f ${CHANGES_FLAG_FILE} + docker exec -t ${{ env.container_name }} /bin/bash -c ' + FLAG_FILE_IN_CONTAINER="/paddle/has_changes.txt" + rm -rf * .[^.]* echo "Downloading build.tar.gz from cfs" cp ${CFS_DIR}/build_bos/${AGILE_PULL_ID}/${AGILE_REVISION}/build.tar.gz . echo "Extracting build.tar.gz" git config --global --add safe.directory ${work_dir} tar --use-compress-program="pzstd -1" -xpf build.tar.gz --strip-components=1 - api_doc_spec_diff=$(python tools/diff_api.py paddle/fluid/API_DEV.spec.doc paddle/fluid/API_PR.spec.doc) - if [ "$api_doc_spec_diff" == "" ]; then + api_doc_spec_diff=$(python tools/diff_api.py paddle/fluid/API_DEV.spec.doc paddle/fluid/API_PR.spec.doc || true) + if [ -z "$api_doc_spec_diff" ]; then echo "API documents no change." + echo "false" > ${FLAG_FILE_IN_CONTAINER} exit 0 fi + # Save diff to a file for the next step + echo "$api_doc_spec_diff" > /tmp/api_doc_diff.txt curl -sS -o /tmp/entrypoint.sh https://paddle-dev-tools-open.bj.bcebos.com/fluiddoc-preview/entrypoint-paddle-docs-review.sh cd / source ${{ github.workspace }}/../../../proxy bash "/tmp/entrypoint.sh" + echo "true" > ${FLAG_FILE_IN_CONTAINER} ' + if [ -f "${CHANGES_FLAG_FILE}" ]; then + has_changes_value=$(cat ${CHANGES_FLAG_FILE}) + echo "Detected API change status: ${has_changes_value}" + echo "has_changes=${has_changes_value}" >> $GITHUB_OUTPUT + else + echo "Error: The change flag file was not created by the container. Defaulting to no changes." + echo "has_changes=false" >> $GITHUB_OUTPUT + fi + - name: Generate Comment Body + if: steps.doc_build.outputs.has_changes == 'true' + id: generate_comment + run: | + comment_body=$(docker exec -t ${{ env.container_name }} python /paddle/tools/generate_doc_comment.py /tmp/api_doc_diff.txt ${{ env.PR_ID }}) + echo "comment_body<> $GITHUB_OUTPUT + echo "$comment_body" >> $GITHUB_OUTPUT + echo "EOF" >> $GITHUB_OUTPUT + + - name: Save comment artifacts + if: steps.doc_build.outputs.has_changes == 'true' && steps.generate_comment.outputs.comment_body != '' + run: | + echo "${{ steps.generate_comment.outputs.comment_body }}" > comment_body.txt + echo "${{ env.PR_ID }}" > pr_number.txt + # [TODO] remove it after test. + echo "${{ steps.generate_comment.outputs.comment_body }}" + echo "${{ env.PR_ID }}" + + - name: Upload comment artifacts + if: steps.doc_build.outputs.has_changes == 'true' && steps.generate_comment.outputs.comment_body != '' + uses: actions/upload-artifact@v4 + with: + name: doc-preview-comment + path: | + comment_body.txt + pr_number.txt + retention-days: 1 - name: Terminate and delete the container if: always() diff --git a/python/paddle/tensor/creation.py b/python/paddle/tensor/creation.py index 8cc89b0985e7ff..31e716fdf561a6 100644 --- a/python/paddle/tensor/creation.py +++ b/python/paddle/tensor/creation.py @@ -115,7 +115,7 @@ def create_global_var( This function creates a new tensor variable with value in the global block(block 0). Args: - shape (list[int]|tuple[int]): Shape of the variable + shape (list[int]|tuple[int]): Shape of the variable 567567 value (float): The value of the variable. The new created variable will be filled with it. dtype (str): Data type of the variable diff --git a/python/paddle/tensor/logic.py b/python/paddle/tensor/logic.py index 3c3bfbaf55d2f0..ec4d77de6b306d 100755 --- a/python/paddle/tensor/logic.py +++ b/python/paddle/tensor/logic.py @@ -181,7 +181,7 @@ def logical_not_(x: Tensor, name: str | None = None) -> Tensor: def is_empty(x: Tensor, name: str | None = None) -> Tensor: """ - Test whether a Tensor is empty. + Test whether a Tensor is empty 哇塞. Args: x (Tensor): The Tensor to be tested. diff --git a/python/paddle/tensor/math.py b/python/paddle/tensor/math.py index cb82ba59aa6600..1e40fb5497f82f 100644 --- a/python/paddle/tensor/math.py +++ b/python/paddle/tensor/math.py @@ -504,7 +504,7 @@ def pow( out = x^{y} Note: - ``paddle.pow`` supports broadcasting. If you want know more about broadcasting, please refer to `Introduction to Tensor`_ . + ``paddle.pow`` supports broadcasting balabalaba. If you want know more about broadcasting, please refer to `Introduction to Tensor`_ . .. _Introduction to Tensor: ../../guides/beginner/tensor_en.html#chapter5-broadcasting-of-tensor @@ -698,7 +698,7 @@ def add( Out=X+Y - $X$ the tensor of any dimension. + $X$ the tensor of any dimension 1433223. $Y$ the tensor whose dimensions must be less than or equal to the dimensions of $X$. This operator is used in the following cases: diff --git a/tools/generate_doc_comment.py b/tools/generate_doc_comment.py new file mode 100644 index 00000000000000..e107c0dcb7fbfe --- /dev/null +++ b/tools/generate_doc_comment.py @@ -0,0 +1,111 @@ +# Copyright (c) 2025 PaddlePaddle Authors. All Rights Reserved. +# +# 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. + +import importlib +import inspect +import re +import sys + +import paddle # noqa: F401 + + +def resolve_string_to_obj(path: str): + """ + Recursively resolves a string path to a Python object. + Handles modules, functions, classes, and methods. + """ + if not path or '.' not in path: + try: + return importlib.import_module(path) + except ImportError: + return None + + try: + # First, try to import the whole path as a module + return importlib.import_module(path) + except ImportError: + # If that fails, it must be an object within a module. + # Split off the last part and try to resolve the parent. + parent_path, child_name = path.rsplit('.', 1) + parent_obj = resolve_string_to_obj(parent_path) + if parent_obj is not None: + try: + return getattr(parent_obj, child_name) + except AttributeError: + return None + else: + return None + + +def generate_comment_body(doc_diff, pr_id): + if not doc_diff: + return "" + + output_lines = [] + base_url = f"http://preview-paddle-pr-{pr_id}.paddle-docs-preview.paddlepaddle.org.cn/documentation/docs/en/api" + + # Extract API names like 'paddle.autograd.backward' from lines like: + # - paddle.autograd.backward (ArgSpec(...), ('document', ...)) + # + paddle.autograd.backward (ArgSpec(...), ('document', ...)) + apis = sorted( + set( + re.findall(r"^[+-]\s*([a-zA-Z0-9_.]+)\s*\(", doc_diff, re.MULTILINE) + ) + ) + + for api in apis: + api_obj = resolve_string_to_obj(api) + + api_path = api.replace('.', '/') + url = f"{base_url}/{api_path}_en.html" + + if "." in api: + parent_path, child_name = api.rsplit('.', 1) + parent_obj = resolve_string_to_obj(parent_path) + if inspect.isclass(parent_obj) and inspect.isfunction(api_obj): + parent_api_path = parent_path.replace('.', '/') + url = f"{base_url}/{parent_api_path}_en.html#{child_name}" + + output_lines.append(f"- [{api}]({url})") + + if not output_lines: + return "本次 PR 未检测到 API 文档变更。" + + comment_body = """
+📚 因为涉及修改 api docstring,生成本次 PR 文档预览链接 (点击展开) + +以下是本次 PR 中变更文档的预览链接: + +{} + +
""".format("\n".join(output_lines)) + + return comment_body + + +if __name__ == "__main__": + if len(sys.argv) < 3: + print( + "Usage: python generate_doc_comment.py " + ) + sys.exit(1) + + doc_diff_path = sys.argv[1] + pr_id = sys.argv[2] + + with open(doc_diff_path, 'r') as f: + doc_diff_content = f.read() + + comment = generate_comment_body(doc_diff_content, pr_id) + print(comment)