From 5f7983048f56e6d8b16d4b6f48d5f939ac38cc60 Mon Sep 17 00:00:00 2001 From: Trevor Gamblin Date: Thu, 13 Aug 2026 13:34:18 -0400 Subject: [PATCH] workflows: build-grpcio: add We need an updated version of our old patch for the install_all_python_modules.sh script, since the project now uses pyproject.toml for pip instead of setup.py. The sunny day skip patch is preserved as well, and applies cleanly (no changes since v1.76.0). We add a new patch 'grpcio_tests: anchor GatherProto's proto paths to GRPC_STEM' to deal with a path regression in the install_all_python_modules.sh script used by the build. grpcio tries to install maturin, which is a binary wheel, so set PIP_EXTRA_INDEX_URL inside CIBW_ENVIRONMENT and enforce binary pulls only in CIBW_TEST_ENVIRONMENT. Upstream commit d8698ff717 changed the project to pyproject.toml, but there are pathing issues when running the tests against built grpcio with cibuildwheel, so patch the project to fix it. Ref: #45 Signed-off-by: Trevor Gamblin --- .github/workflows/build-grpcio.yml | 142 ++++++++++++ ...cio-in-install_all_python_modules.sh.patch | 37 ++++ ...hor-GatherProto-s-proto-paths-to-GRP.patch | 45 ++++ .../1.78.0/0002-Skip-test_sunny_day.patch | 67 ++++++ ...-command-cwd-relative-path-regressio.patch | 205 ++++++++++++++++++ 5 files changed, 496 insertions(+) create mode 100644 .github/workflows/build-grpcio.yml create mode 100644 patches/grpcio/1.78.0/0001-don-t-build-grpcio-in-install_all_python_modules.sh.patch create mode 100644 patches/grpcio/1.78.0/0001-grpcio_tests-anchor-GatherProto-s-proto-paths-to-GRP.patch create mode 100644 patches/grpcio/1.78.0/0002-Skip-test_sunny_day.patch create mode 100644 patches/grpcio/1.78.0/0004-fix-per-package-command-cwd-relative-path-regressio.patch diff --git a/.github/workflows/build-grpcio.yml b/.github/workflows/build-grpcio.yml new file mode 100644 index 00000000..97ccbe2c --- /dev/null +++ b/.github/workflows/build-grpcio.yml @@ -0,0 +1,142 @@ +--- +name: Build grpcio wheels (riscv64) + +on: + workflow_dispatch: + inputs: + version: + description: 'grpcio version to build (git tag without leading v, e.g. 1.78.0)' + required: true + default: '1.78.0' + pull_request: + paths: + - '.github/workflows/build-grpcio.yml' + - 'patches/grpcio/**' + +concurrency: + group: ${{ github.workflow }}-${{ inputs.version || '1.78.0' }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +permissions: + contents: read + +env: + GRPCIO_VERSION: ${{ inputs.version || '1.78.0' }} + UV_EXTRA_INDEX_URL: https://pypi.riseproject.dev/simple/ + UV_INDEX_STRATEGY: unsafe-best-match + UV_ONLY_BINARY: ':all:' + MANYLINUX_RISCV64_IMAGE: quay.io/pypa/manylinux_2_39_riscv64 + +jobs: + build_wheels: + name: Build grpcio ${{ inputs.version || '1.78.0' }} ${{ matrix.python }}-manylinux_riscv64 + runs-on: ubuntu-24.04-riscv + strategy: + fail-fast: false + matrix: + # grpcio doesn't advertise free-threaded support yet + # (src/python/grpcio/python_version.py lists only 3.10-3.14), so + # 3.14t is dropped from the usual default matrix. + # See also: https://github.com/grpc/grpc/issues/38762 + python: ["cp312", "cp313", "cp314"] + + env: + CCACHE_DIR: ${{ github.workspace }}/.ccache + CCACHE_BASEDIR: "/project" + CCACHE_COMPILERCHECK: "content" + + steps: + - name: Checkout grpc v${{ env.GRPCIO_VERSION }} + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + repository: grpc/grpc + ref: v${{ env.GRPCIO_VERSION }} + submodules: true + fetch-depth: 1 # grpc's submodules (boringssl, abseil, re2, ...) are large; skip history + persist-credentials: false + + - name: Checkout python-wheels + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + path: python-wheels + fetch-depth: 1 + persist-credentials: false + + # install_all_python_modules.sh (run below via CIBW_TEST_COMMAND) would + # otherwise reinstall grpcio from source before testing, rather than + # testing the wheel this job just built. + - name: Patch grpc source + run: git apply python-wheels/patches/grpcio/${{ env.GRPCIO_VERSION }}/00*.patch + + # The C++ core is identical across cp312/cp313/cp314; share ccache across + # builds assuming that the package version is the same. + - name: Restore compilation cache + uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 + with: + path: ${{ env.CCACHE_DIR }} + key: ccache-wheels-grpcio-v${{ env.GRPCIO_VERSION }}-manylinux_riscv64-${{ matrix.python }} + restore-keys: | + ccache-wheels-grpcio-v${{ env.GRPCIO_VERSION }}-manylinux_riscv64- + + - name: Build wheels + uses: pypa/cibuildwheel@294735312765b09d24a2fbec22660ce817587d55 # v4.1.0 + env: + CIBW_BUILD: ${{ matrix.python }}-manylinux_riscv64 + CIBW_MANYLINUX_RISCV64_IMAGE: ${{ env.MANYLINUX_RISCV64_IMAGE }} + CIBW_BEFORE_ALL: | + set -eux + CCACHE_VERSION=4.13.6 + curl -fsSL https://github.com/ccache/ccache/releases/download/v${CCACHE_VERSION}/ccache-${CCACHE_VERSION}-linux-$(uname -m)-musl-static.tar.gz | \ + tar -xvzf - --strip-components 1 -C /usr/local/bin ccache-${CCACHE_VERSION}-linux-$(uname -m)-musl-static/ccache + ccache --version + ccache --zero-stats + CIBW_ENVIRONMENT: PIP_EXTRA_INDEX_URL=https://pypi.riseproject.dev/simple/ + # only-binary is test-scoped only: grpcio_tests pulls cryptography + # transitively (via oauth2client/google-auth), and an unbounded + # `pip install .` picks the newest PyPI version even when our + # registry has an older riscv64 wheel, forcing a maturin/Rust + # source build. Build phase still needs sdist compiles (cython + # has no riscv64 wheel anywhere), so this can't go in CIBW_ENVIRONMENT. + CIBW_TEST_ENVIRONMENT: "PIP_ONLY_BINARY=:all:" + CIBW_ENVIRONMENT_PASS_LINUX: >- + CCACHE_BASEDIR + CCACHE_COMPILERCHECK + CIBW_CONTAINER_ENGINE: "docker; create_args: --volume ${{ env.CCACHE_DIR }}:/root/.ccache" + CIBW_BEFORE_BUILD: pip install -r requirements.txt + CIBW_REPAIR_WHEEL_COMMAND: auditwheel repair --strip -w {dest_dir} {wheel} + CIBW_TEST_REQUIRES: setuptools + CIBW_TEST_COMMAND: >- + cd {project}/tools/distrib && + PIP_NO_BUILD_ISOLATION=0 ./install_all_python_modules.sh && + cd {project}/src/python/grpcio_tests && python setup.py test_lite + + - name: Save compilation cache + if: always() && github.ref == 'refs/heads/main' + uses: actions/cache/save@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 + with: + path: ${{ env.CCACHE_DIR }} + key: ccache-wheels-grpcio-v${{ env.GRPCIO_VERSION }}-manylinux_riscv64-${{ matrix.python }} + + - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: grpcio-${{ env.GRPCIO_VERSION }}-${{ matrix.python }}-manylinux_riscv64 + path: ./wheelhouse/*.whl + if-no-files-found: error + + publish: + name: Publish grpcio ${{ inputs.version || '1.78.0' }} to GitLab + needs: [build_wheels] + runs-on: ubuntu-latest + permissions: + contents: write + pull-requests: write + + steps: + - name: Publish wheels and open docs PR + uses: riseproject-dev/python-wheels/actions/publish-wheels@main + with: + artifact-pattern: grpcio-${{ env.GRPCIO_VERSION }}-*-manylinux_riscv64 + gitlab-username: ${{ vars.GITLAB_DEPLOY_USER }} + gitlab-token: ${{ secrets.GITLAB_DEPLOY_TOKEN }} + gitlab-project-id: ${{ vars.GITLAB_PROJECT_ID }} + gh-token: ${{ secrets.GITHUB_TOKEN }} diff --git a/patches/grpcio/1.78.0/0001-don-t-build-grpcio-in-install_all_python_modules.sh.patch b/patches/grpcio/1.78.0/0001-don-t-build-grpcio-in-install_all_python_modules.sh.patch new file mode 100644 index 00000000..8c176e37 --- /dev/null +++ b/patches/grpcio/1.78.0/0001-don-t-build-grpcio-in-install_all_python_modules.sh.patch @@ -0,0 +1,37 @@ +From 67901366eea10b98c29dc57a72c796ec786a210e Mon Sep 17 00:00:00 2001 +From: Trevor Gamblin +Date: Thu, 13 Aug 2026 13:31:27 -0400 +Subject: [PATCH] don't build grpcio in install_all_python_modules.sh + +We run this script in CIBW_TEST_COMMAND to install grpcio's other Python +packages and their test dependencies, but we don't want it to reinstall +grpcio itself from source - we want the test environment to use the +wheel built (and about to be distributed) in this same cibuildwheel +invocation. + +This is a rework of the older patches from Mark to handle upstream's +change to using pyproject.toml instead of setup.py. + +Upstream-Status: Inappropriate [upstream tests in a different way] + +Signed-off-by: Trevor Gamblin +--- + tools/distrib/install_all_python_modules.sh | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/tools/distrib/install_all_python_modules.sh b/tools/distrib/install_all_python_modules.sh +index 5652580c76..acab4f9d6f 100755 +--- a/tools/distrib/install_all_python_modules.sh ++++ b/tools/distrib/install_all_python_modules.sh +@@ -30,7 +30,7 @@ function maybe_run_command () { + } + + python3 -m pip install --upgrade "cython==3.1.1"; +-python3 -m pip install .; ++#python3 -m pip install .; + + # Build and install grpcio_tools + pushd tools/distrib/python/grpcio_tools; +-- +2.55.0 + diff --git a/patches/grpcio/1.78.0/0001-grpcio_tests-anchor-GatherProto-s-proto-paths-to-GRP.patch b/patches/grpcio/1.78.0/0001-grpcio_tests-anchor-GatherProto-s-proto-paths-to-GRP.patch new file mode 100644 index 00000000..6a3ea5f2 --- /dev/null +++ b/patches/grpcio/1.78.0/0001-grpcio_tests-anchor-GatherProto-s-proto-paths-to-GRP.patch @@ -0,0 +1,45 @@ +From 052eae2d33743cf67847521309c0201dc0bee9aa Mon Sep 17 00:00:00 2001 +From: Trevor Gamblin +Date: Wed, 19 Aug 2026 12:59:15 -0400 +Subject: [PATCH] grpcio_tests: anchor GatherProto's proto paths to GRPC_STEM + +commands.py's GRPC_PROTO_STEM/PROTO_STEM/PYTHON_PROTO_TOP_LEVEL were made +cwd-relative by the pyproject.toml migration (#40833), which is fine for +the new PEP517 build path (BuildPackageProtos), but breaks the legacy +"preprocess" command (GatherProto) used by +tools/distrib/install_all_python_modules.sh. That script pushd's into +src/python/grpcio_tests before invoking `setup.py preprocess`, so the +relative path "src/proto" no longer resolves: + + running preprocess + error: [Errno 2] No such file or directory: 'src/proto' + +Upstream-Status: To upstream + +AI-Generated: Uses Claude Sonnet 5 + +Signed-off-by: Trevor Gamblin +--- + src/python/grpcio_tests/commands.py | 6 +++--- + 1 file changed, 3 insertions(+), 3 deletions(-) + +diff --git a/src/python/grpcio_tests/commands.py b/src/python/grpcio_tests/commands.py +index 997bd2a906..d3bbd0f733 100644 +--- a/src/python/grpcio_tests/commands.py ++++ b/src/python/grpcio_tests/commands.py +@@ -32,9 +32,9 @@ from setuptools.command import test + PYTHON_STEM = os.path.dirname(os.path.abspath(__file__)) + GRPC_STEM = os.path.abspath(PYTHON_STEM + "../../../../") + PYTHON_REL_PATH = os.path.relpath(PYTHON_STEM, start=GRPC_STEM) +-GRPC_PROTO_STEM = os.path.join("src", "proto") +-PROTO_STEM = os.path.join(PYTHON_REL_PATH, "src", "proto") +-PYTHON_PROTO_TOP_LEVEL = os.path.join(PYTHON_REL_PATH, "src") ++GRPC_PROTO_STEM = os.path.join(GRPC_STEM, "src", "proto") ++PROTO_STEM = os.path.join(GRPC_STEM, PYTHON_REL_PATH, "src", "proto") ++PYTHON_PROTO_TOP_LEVEL = os.path.join(GRPC_STEM, PYTHON_REL_PATH, "src") + + + class CommandError(object): +-- +2.55.0 + diff --git a/patches/grpcio/1.78.0/0002-Skip-test_sunny_day.patch b/patches/grpcio/1.78.0/0002-Skip-test_sunny_day.patch new file mode 100644 index 00000000..798113c8 --- /dev/null +++ b/patches/grpcio/1.78.0/0002-Skip-test_sunny_day.patch @@ -0,0 +1,67 @@ +From 3b1b82e17e21b4b6b59c6240fb233b2f2f0175ea Mon Sep 17 00:00:00 2001 +From: Mark Ryan +Date: Mon, 3 Nov 2025 09:27:22 +0000 +Subject: [PATCH 2/2] Skip test_sunny_day + +The test + +tests.unit._dynamic_stubs_test.DynamicStubTest.test_sunny_day + +seems to be hanging on the riscv64 builders. + +Use the nuclear option and strip the test out entirely to ensure no +strange import interactions happen. + +Upstream-Status: To upstream [ Further investigation needed ] + +Signed-off-by: Mark Ryan +Signed-off-by: Trevor Gamblin +--- + src/python/grpcio_tests/tests/tests.json | 1 - + .../tests/unit/_dynamic_stubs_test.py | 18 ------------------ + 2 files changed, 19 deletions(-) + +diff --git a/src/python/grpcio_tests/tests/tests.json b/src/python/grpcio_tests/tests/tests.json +index 86b359e2d2..8ab1ead2c2 100644 +--- a/src/python/grpcio_tests/tests/tests.json ++++ b/src/python/grpcio_tests/tests/tests.json +@@ -60,7 +60,6 @@ + "tests.unit._cython.cygrpc_test.SecureServerSecureClient", + "tests.unit._cython.cygrpc_test.TypeSmokeTest", + "tests.unit._dns_resolver_test.DNSResolverTest", +- "tests.unit._dynamic_stubs_test.DynamicStubTest", + "tests.unit._empty_message_test.EmptyMessageTest", + "tests.unit._error_message_encoding_test.ErrorMessageEncodingTest", + "tests.unit._exit_test.ExitTest", +diff --git a/src/python/grpcio_tests/tests/unit/_dynamic_stubs_test.py b/src/python/grpcio_tests/tests/unit/_dynamic_stubs_test.py +index 98bdb19a2f..108db35ea3 100644 +--- a/src/python/grpcio_tests/tests/unit/_dynamic_stubs_test.py ++++ b/src/python/grpcio_tests/tests/unit/_dynamic_stubs_test.py +@@ -136,24 +136,6 @@ def _test_grpc_tools_unimportable(): + _assert_unimplemented("grpcio-tools") + + +-# NOTE(rbellevi): multiprocessing.Process fails to pickle function objects +-# when they do not come from the "__main__" module, so this test passes +-# if run directly on Windows or MacOS, but not if started by the test runner. +-@unittest.skipIf( +- os.name == "nt" or "darwin" in sys.platform, +- "Windows and MacOS multiprocessing unsupported", +-) +-class DynamicStubTest(unittest.TestCase): +- def test_sunny_day(self): +- _run_in_subprocess(_test_sunny_day) +- +- def test_well_known_types(self): +- _run_in_subprocess(_test_well_known_types) +- +- def test_grpc_tools_unimportable(self): +- _run_in_subprocess(_test_grpc_tools_unimportable) +- +- + if __name__ == "__main__": + logging.basicConfig() + unittest.main(verbosity=2) +-- +2.34.1 + diff --git a/patches/grpcio/1.78.0/0004-fix-per-package-command-cwd-relative-path-regressio.patch b/patches/grpcio/1.78.0/0004-fix-per-package-command-cwd-relative-path-regressio.patch new file mode 100644 index 00000000..00dbee34 --- /dev/null +++ b/patches/grpcio/1.78.0/0004-fix-per-package-command-cwd-relative-path-regressio.patch @@ -0,0 +1,205 @@ +From: RISE python-wheels +Subject: [PATCH] fix per-package command cwd-relative path regression from #40833 + +Same regression as the grpcio_tests/commands.py GatherProto fix +(0003-fix-GatherProto-cwd-relative-path-regression.patch): the +pyproject.toml migration (#40833) made these per-package "preprocess"/ +"build_package_protos" setuptools Command classes cwd-relative instead +of anchored to GRPC_ROOT_ABS_PATH/ROOT_DIR (both already computed +absolute in each file). install_all_python_modules.sh pushd's into each +package's own directory before running these commands, so the relative +paths ("src/proto/...", "./LICENSE", ROOT_REL_DIR) don't resolve there - +symptoms range from a silently-skipped proto copy to +grpc_tools.command.build_package_protos() finding zero .proto files and +silently emitting no *_pb2.py, e.g.: + + ImportError: cannot import name 'health_pb2' from 'grpc_health.v1' + +Fixes the same way as 0003: revert each file's constants/run() bodies to +use the already-computed absolute ROOT_DIR/GRPC_ROOT_ABS_PATH instead of +the relative ROOT_REL_DIR, matching pre-#40833 behavior. Affects every +per-package command module install_all_python_modules.sh's PACKAGES loop +touches that generates protos or copies LICENSE: grpcio_channelz, +grpcio_health_checking, grpcio_reflection, grpcio_status, grpcio_testing. +(grpcio_tests/commands.py is 0003; grpcio/commands.py and grpcio_csds/ +grpcio_admin/grpcio_csm_observability are unaffected - the former is +invoked from repo root, the latter three carry no such module.) + +Upstream-Status: To upstream + +Signed-off-by: Trevor Gamblin +--- +diff --git a/src/python/grpcio_channelz/channelz_commands.py b/src/python/grpcio_channelz/channelz_commands.py +index 298262df71..2d3fe65433 100644 +--- a/src/python/grpcio_channelz/channelz_commands.py ++++ b/src/python/grpcio_channelz/channelz_commands.py +@@ -20,9 +20,10 @@ import setuptools + + ROOT_DIR = os.path.abspath(os.path.dirname(os.path.abspath(__file__))) + GRPC_ROOT_ABS_PATH = os.path.join(ROOT_DIR, "../../..") +-ROOT_REL_DIR = os.path.relpath(ROOT_DIR, start=GRPC_ROOT_ABS_PATH) +-CHANNELZ_PROTO = "src/proto/grpc/channelz/channelz.proto" +-LICENSE = "./LICENSE" ++CHANNELZ_PROTO = os.path.join( ++ GRPC_ROOT_ABS_PATH, "src/proto/grpc/channelz/channelz.proto" ++) ++LICENSE = os.path.join(GRPC_ROOT_ABS_PATH, "LICENSE") + + + class Preprocess(setuptools.Command): +@@ -43,10 +44,10 @@ class Preprocess(setuptools.Command): + if os.path.isfile(CHANNELZ_PROTO): + shutil.copyfile( + CHANNELZ_PROTO, +- os.path.join(ROOT_REL_DIR, "grpc_channelz/v1/channelz.proto"), ++ os.path.join(ROOT_DIR, "grpc_channelz/v1/channelz.proto"), + ) + if os.path.isfile(LICENSE): +- shutil.copyfile(LICENSE, os.path.join(ROOT_REL_DIR, "LICENSE")) ++ shutil.copyfile(LICENSE, os.path.join(ROOT_DIR, "LICENSE")) + + + class BuildPackageProtos(setuptools.Command): +@@ -65,4 +66,4 @@ class BuildPackageProtos(setuptools.Command): + from grpc_tools import command + + # find and build all protos in the current package +- command.build_package_protos(ROOT_REL_DIR) ++ command.build_package_protos(ROOT_DIR) +diff --git a/src/python/grpcio_health_checking/health_commands.py b/src/python/grpcio_health_checking/health_commands.py +index 82f2ac96c5..706e467b17 100644 +--- a/src/python/grpcio_health_checking/health_commands.py ++++ b/src/python/grpcio_health_checking/health_commands.py +@@ -20,9 +20,10 @@ import setuptools + + ROOT_DIR = os.path.abspath(os.path.dirname(os.path.abspath(__file__))) + GRPC_ROOT_ABS_PATH = os.path.join(ROOT_DIR, "../../..") +-ROOT_REL_DIR = os.path.relpath(ROOT_DIR, start=GRPC_ROOT_ABS_PATH) +-HEALTH_PROTO = "src/proto/grpc/health/v1/health.proto" +-LICENSE = "./LICENSE" ++HEALTH_PROTO = os.path.join( ++ GRPC_ROOT_ABS_PATH, "src/proto/grpc/health/v1/health.proto" ++) ++LICENSE = os.path.join(GRPC_ROOT_ABS_PATH, "LICENSE") + + + class Preprocess(setuptools.Command): +@@ -43,10 +44,10 @@ class Preprocess(setuptools.Command): + if os.path.isfile(HEALTH_PROTO): + shutil.copyfile( + HEALTH_PROTO, +- os.path.join(ROOT_REL_DIR, "grpc_health/v1/health.proto"), ++ os.path.join(ROOT_DIR, "grpc_health/v1/health.proto"), + ) + if os.path.isfile(LICENSE): +- shutil.copyfile(LICENSE, os.path.join(ROOT_REL_DIR, "LICENSE")) ++ shutil.copyfile(LICENSE, os.path.join(ROOT_DIR, "LICENSE")) + + + class BuildPackageProtos(setuptools.Command): +@@ -65,4 +66,4 @@ class BuildPackageProtos(setuptools.Command): + from grpc_tools import command + + # find and build all protos in the current package +- command.build_package_protos(ROOT_REL_DIR) ++ command.build_package_protos(ROOT_DIR) +diff --git a/src/python/grpcio_reflection/reflection_commands.py b/src/python/grpcio_reflection/reflection_commands.py +index 8b48a60312..6b571d22cc 100644 +--- a/src/python/grpcio_reflection/reflection_commands.py ++++ b/src/python/grpcio_reflection/reflection_commands.py +@@ -20,9 +20,10 @@ import setuptools + + ROOT_DIR = os.path.abspath(os.path.dirname(os.path.abspath(__file__))) + GRPC_ROOT_ABS_PATH = os.path.join(ROOT_DIR, "../../..") +-ROOT_REL_DIR = os.path.relpath(ROOT_DIR, start=GRPC_ROOT_ABS_PATH) +-REFLECTION_PROTO = "src/proto/grpc/reflection/v1alpha/reflection.proto" +-LICENSE = "./LICENSE" ++REFLECTION_PROTO = os.path.join( ++ GRPC_ROOT_ABS_PATH, "src/proto/grpc/reflection/v1alpha/reflection.proto" ++) ++LICENSE = os.path.join(GRPC_ROOT_ABS_PATH, "LICENSE") + + + class Preprocess(setuptools.Command): +@@ -44,11 +45,11 @@ class Preprocess(setuptools.Command): + shutil.copyfile( + REFLECTION_PROTO, + os.path.join( +- ROOT_REL_DIR, "grpc_reflection/v1alpha/reflection.proto" ++ ROOT_DIR, "grpc_reflection/v1alpha/reflection.proto" + ), + ) + if os.path.isfile(LICENSE): +- shutil.copyfile(LICENSE, os.path.join(ROOT_REL_DIR, "LICENSE")) ++ shutil.copyfile(LICENSE, os.path.join(ROOT_DIR, "LICENSE")) + + + class BuildPackageProtos(setuptools.Command): +@@ -67,4 +68,4 @@ class BuildPackageProtos(setuptools.Command): + from grpc_tools import command + + # find and build all protos in the current package +- command.build_package_protos(ROOT_REL_DIR) ++ command.build_package_protos(ROOT_DIR) +diff --git a/src/python/grpcio_status/status_commands.py b/src/python/grpcio_status/status_commands.py +index dec7c78c2f..575f235101 100644 +--- a/src/python/grpcio_status/status_commands.py ++++ b/src/python/grpcio_status/status_commands.py +@@ -20,10 +20,11 @@ import setuptools + + ROOT_DIR = os.path.abspath(os.path.dirname(os.path.abspath(__file__))) + GRPC_ROOT_ABS_PATH = os.path.join(ROOT_DIR, "../../..") +-ROOT_REL_DIR = os.path.relpath(ROOT_DIR, start=GRPC_ROOT_ABS_PATH) +-STATUS_PROTO = "third_party/googleapis/google/rpc/status.proto" ++STATUS_PROTO = os.path.join( ++ GRPC_ROOT_ABS_PATH, "third_party/googleapis/google/rpc/status.proto" ++) + PACKAGE_STATUS_PROTO_DIR = "grpc_status/google/rpc" +-LICENSE = "./LICENSE" ++LICENSE = os.path.join(GRPC_ROOT_ABS_PATH, "LICENSE") + + + class Preprocess(setuptools.Command): +@@ -39,16 +40,16 @@ class Preprocess(setuptools.Command): + pass + + def run(self): +- package_status_proto_rel_path = os.path.join( +- ROOT_REL_DIR, PACKAGE_STATUS_PROTO_DIR ++ package_status_proto_path = os.path.join( ++ ROOT_DIR, PACKAGE_STATUS_PROTO_DIR + ) + + if os.path.isfile(STATUS_PROTO): +- if not os.path.isdir(package_status_proto_rel_path): +- os.makedirs(package_status_proto_rel_path) ++ if not os.path.isdir(package_status_proto_path): ++ os.makedirs(package_status_proto_path) + shutil.copyfile( + STATUS_PROTO, +- os.path.join(package_status_proto_rel_path, "status.proto"), ++ os.path.join(package_status_proto_path, "status.proto"), + ) + if os.path.isfile(LICENSE): +- shutil.copyfile(LICENSE, os.path.join(ROOT_REL_DIR, "LICENSE")) ++ shutil.copyfile(LICENSE, os.path.join(ROOT_DIR, "LICENSE")) +diff --git a/src/python/grpcio_testing/testing_commands.py b/src/python/grpcio_testing/testing_commands.py +index cf8594bf8e..293183741b 100644 +--- a/src/python/grpcio_testing/testing_commands.py ++++ b/src/python/grpcio_testing/testing_commands.py +@@ -20,8 +20,7 @@ import setuptools + + ROOT_DIR = os.path.abspath(os.path.dirname(os.path.abspath(__file__))) + GRPC_ROOT_ABS_PATH = os.path.join(ROOT_DIR, "../../..") +-ROOT_REL_DIR = os.path.relpath(ROOT_DIR, start=GRPC_ROOT_ABS_PATH) +-LICENSE = "./LICENSE" ++LICENSE = os.path.join(GRPC_ROOT_ABS_PATH, "LICENSE") + + + class Preprocess(setuptools.Command): +@@ -38,4 +37,4 @@ class Preprocess(setuptools.Command): + + def run(self): + if os.path.isfile(LICENSE): +- shutil.copyfile(LICENSE, os.path.join(ROOT_REL_DIR, "LICENSE")) ++ shutil.copyfile(LICENSE, os.path.join(ROOT_DIR, "LICENSE"))