From 9a6e699ad9006b2203d3e599aefe89eecfe223f9 Mon Sep 17 00:00:00 2001 From: mhucka Date: Tue, 9 Dec 2025 05:37:43 +0000 Subject: [PATCH 1/5] Add new scripts for building and cleaning wheels These encapsulate instructions from Michael Broughton about how to build new versions of TensorFlow Quantum. --- release/README.md | 112 ++++++++++++++++++++++++ release/build_distribution.sh | 159 ++++++++++++++++++++++++++++++++++ release/clean_distribution.sh | 100 +++++++++++++++++++++ 3 files changed, 371 insertions(+) create mode 100644 release/README.md create mode 100755 release/build_distribution.sh create mode 100755 release/clean_distribution.sh diff --git a/release/README.md b/release/README.md new file mode 100644 index 000000000..20e082000 --- /dev/null +++ b/release/README.md @@ -0,0 +1,112 @@ +# Tools for building releases of TensorFlow Quantum + +This directory contains configurations and scripts that the TensorFlow Quantum +maintainers use to create Python packages for software releases. The process of +making a TFQ release is complex, and has not been fully automated. The scripts +in this directory help automate some steps and are a way of capturing the +process more precisely, but there are still manual steps involved. + +## Background: how TensorFlow Quantum is linked with TensorFlow + +TFQ is implemented as a Python library that integrates static C++ objects. Those +C++ objects are linked with TensorFlow static objects when both TFQ and +TensorFlow are installed on your system. Unlike a pure Python library, the +result is platform-dependent: the Python code itself remains portable, but the +underlying C++ objects need to be compiled specifically for each target +environment (operating system and CPU architecture). + +TensorFlow does not provide ABI stability guarantees between versions of +TensorFlow. In order to avoid the need for users to compile the TFQ source code +themselves when they want to install TFQ, each release of TFQ must be pinned to +a specific version of TensorFlow. As a consequence, TFQ releases will not work +with any other version of TensorFlow than the one they are pinned to. + +Python wheels for TFQ are produced by compiling them locally with a toolchain +that matches that used by the version of TensorFlow being targeted by a given +version of TFQ. A number of elements affect whether the whole process succeeds +and the resulting wheel is portable to environments other than the specific +computer TFQ is built on, including: + +* The version of Python and the local Python environment +* The version of TensorFlow +* The TensorFlow build container used +* The Crosstool configuration used +* Whether CUDA is being used, and its version +* The dependency requirements implied by Cirq, NumPy, Protobuf, and + other Python packages + +## Procedure + +Building a TensorFlow Quantum release for Linux involves some additional steps +beyond just building TFQ and producing an initial Python wheel. The procedure +uses `auditwheel` to "repair" the resulting wheel; this improves the +compatibility of the wheel so that it can run on a wider range of Linux +distributions, even if those distributions have different versions of system +libraries. + +The steps are: + +1. Git clone the TensorFlow Quantum repo to a directory on your computer. + +1. `cd` into the local clone directory. + +1. Create a Python virtual environment. + +1. Run `pip install -r requirements.txt` + +1. Verify the major.minor version of Python you are using. The rest of these + instructions use 3.11 as an example. + +1. Run `./release/build_distribution.sh -p 3.11` + +1. If the above succeeds, it will leave the wheel in `/tmp/tensorflow_quantum/` + on your system. Take note of the name of the wheel file that + `build_distribution.sh` prints when it finishes. + +1. Run `./release/clean_distribution.sh /tmp/tensorflow_quantum/WHEEL_FILE`, + where `WHEEL_FILE` is the file noted in the previous step. If this works, it + will create a new wheel file in `../wheelhouse`. If an error occurs, it will + hopefully report the problem. If the error is a platform tag mismatch, run + `./release/clean_distribution.sh -s /tmp/tensorflow_quantum/WHEEL_FILE`; + this will run auditwheel's `show` command on the wheel file to indicate what + version of `manylinux` this wheel can be made to run on if you use + `auditwheel` to repair it. With that information, you may be able to edit + the `build_distribution.sh` script to experiment with different values for + the Crosstool and/or the Docker images used. + +1. Test the wheel. Go to a remotely hosted Colab (or any other Linux platform + that is distinctly difference from yours) upload this new generated wheel + file to local storage in the Colab, and test if it works. (E.g., try to run + `https://www.tensorflow.org/quantum/tutorials/hello_many_worlds` with the + new TensorFlow and TFQ wheels to verify things are working smoothly). + +1. Repeat the `build_distribution.sh` and `clean_distribution.sh` steps for + different versions of Python. + +To make TFQ wheels that will work on other systems, it's essential that the +platform tags of the TFQ wheel must match the tags of the current TensorFlow +version you are targeting. (Visit `https://pypi.org/project/tensorflow//#files` to determine what the tags are). + +## Testing the scripts + +There are some basic unit tests in the fils `build_distribution_test.py` and +`clean_distribution_test.py`. They can be run using as follows: + +```shell +# Move to the top-level directory of the repo before running the tests. +cd .. +bazel test //release:build_distribution_test //release:clean_distribution_test +``` + +## More information + +"TensorFlow SIG Build" is a community group dedicated to the TensorFlow build +process. This repository is a showcase of resources, guides, tools, and builds +contributed by the community, for the community. The following resources may be +useful when trying to figure out how to make this all work. + +* The "TF SIG Build Dockerfiles" document: + https://github.com/tensorflow/build/tree/ff4320fee2cf48568ebd2f476d7714438bfa0bee/tf_sig_build_dockerfiles#readme + +* Other info in the SIG Build repo: https://github.com/tensorflow/build diff --git a/release/build_distribution.sh b/release/build_distribution.sh new file mode 100755 index 000000000..0bbfb98fe --- /dev/null +++ b/release/build_distribution.sh @@ -0,0 +1,159 @@ +#!/usr/bin/env bash +# Copyright 2025 Google LLC +# +# 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 +# +# https://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. +# ============================================================================= + +# Summary: build a wheel for TFQ using a TensorFlow SIG Build container. +# Run this script with the option "-h" to get a usage summary. +# +# To ensure binary compatibility with TensorFlow, TFQ distributions are built +# using TensorFlow's SIG Build containers and Crosstool C++ toolchain. This +# script encapsulates the process. The basic steps this script performs are: +# +# 1. Write to a file a small shell script that does the following: +# +# a) pip install TFQ's requirements.txt file +# b) run TFQ's configure.sh script +# c) run Bazel to build build_pip_package +# d) run the resulting build_pip_package +# e) copy the wheel created by build_pip_package to ./wheels +# +# 2. Start Docker with image tensorflow/build:${tf_version}-python${py_version} +# and run the script written in step 1. +# +# 3. Do some basic tests on the wheel using standard Python utilities. +# +# 4. Exit. + +set -eu + +# Find the top of the local TFQ git tree. Do it early in case this fails. +thisdir=$(CDPATH="" cd -- "$(dirname -- "$0")" && pwd -P) +repo_dir=$(git -C "${thisdir}" rev-parse --show-toplevel 2>/dev/null || \ + echo "${thisdir}/..") + +# Default values for variables that can be changed via command line flags. +tf_version="2.16" +py_version=$(python3 --version | cut -d' ' -f2 | cut -d. -f1,2) +cuda_version="12" +cleanup="true" + +usage="Usage: ${0} [OPTIONS] +Build a distribution wheel for TensorFlow Quantum. + +Configuration options: + -c X.Y Use CUDA version X.Y (default: ${cuda_version}) + -p X.Y Use Python version X.Y (default: ${py_version}) + -t X.Y Use TensorFlow version X.Y (default: ${tf_version}) + +General options: + -e Don't run bazel clean at the end (default: do) + -n Dry run: print commands but don't execute them + -h Show this help message and exit" + +dry_run="false" +while getopts "c:ehnp:t:" opt; do + case "${opt}" in + c) cuda_version="${OPTARG}" ;; + e) cleanup="false" ;; + h) echo "${usage}"; exit 0 ;; + n) dry_run="true" ;; + p) py_version="${OPTARG}" ;; + t) tf_version="${OPTARG}" ;; + *) echo "${usage}" >&2; exit 1 ;; + esac +done +shift $((OPTIND -1)) + +# See https://hub.docker.com/r/tensorflow/build/tags for available containers. +docker_image="tensorflow/build:${tf_version}-python${py_version}" + +# This should match what TensorFlow's .bazelrc file uses. +crosstool="@sigbuild-r${tf_version}-clang_config_cuda//crosstool:toolchain" + +# Note: configure.sh is run inside the container, and it creates a .bazelrc +# file that adds other cxxopt flags. They don't need to be repeated here. +BUILD_OPTIONS="--cxxopt=-O3 --cxxopt=-msse2 --cxxopt=-msse3 --cxxopt=-msse4" + +# Create a script to be run by the shell inside the Docker container. +build_script=$(mktemp /tmp/tfq_build.XXXXXX) +trap 'rm -f "${build_script}" || true' EXIT + +# The printf'ed section dividers are to make it easier to search the output. +cat <<'EOF' > "${build_script}" +#!/usr/bin/env bash +set -o errexit +cd /tfq +PREFIX='[DOCKER] ' +exec > >(sed "s/^/${PREFIX} /") +exec 2> >(sed "s/^/${PREFIX} /" >&2) +printf "Build configuration inside Docker container:\n" +printf " Docker image: ${docker_image}\n" +printf " Crosstool: ${crosstool}\n" +printf " TF version: ${tf_version}\n" +printf " Python version: ${py_version}\n" +printf " CUDA version: ${cuda_version}\n" +printf " vCPUs available: $(nproc)\n" +printf "\n:::::::: Configuring Python environment ::::::::\n\n" +python3 -m pip install --upgrade pip --root-user-action ignore +pip install -r requirements.txt --root-user-action ignore +printf "\n:::::::: Configuring TensorFlow Quantum build ::::::::\n\n" +printf "Y\n" | ./configure.sh +printf "\n:::::::: Starting Bazel build ::::::::\n\n" +bazel build ${build_flags} release:build_pip_package +printf "\n:::::::: Creating Python wheel ::::::::\n\n" +bazel-bin/release/build_pip_package /build_output/ +if [ "${cleanup}" == "true" ]; then + printf "\n:::::::: Cleaning up ::::::::\n\n" + bazel clean --async +fi +EOF + +chmod +x "${build_script}" + +# Use 'set --' to build the command in the positional parameters ($1, $2, ...) +set -- docker run -it --rm --network host \ + -w /tfq \ + -v "${repo_dir}":/tfq \ + -v /tmp/tensorflow_quantum:/build_output \ + -v "${build_script}:/tmp/build_script.sh" \ + -e HOST_PERMS="$(id -u):$(id -g)" \ + -e build_flags="--crosstool_top=${crosstool} ${BUILD_OPTIONS}" \ + -e cuda_version="${cuda_version}" \ + -e py_version="${py_version}" \ + -e tf_version="${tf_version}" \ + -e docker_image="${docker_image}" \ + -e crosstool="${crosstool}" \ + -e cleanup="${cleanup}" \ + "${docker_image}" \ + /tmp/build_script.sh + +if [ "${dry_run}" == "true" ]; then + # Loop through the positional parameters and simply print them. + printf "(Dry run) " + printf '%s ' "$@" + echo + echo '(Dry run) check-wheel-contents /tmp/tensorflow_quantum/*.whl' +else + echo "Spinning up a Docker container with ${docker_image} …" + "$@" + + # Do some basic checks on the wheel file. + echo "Doing basic sanity checks on the wheel …" + pip install -qq check-wheel-contents + check-wheel-contents /tmp/tensorflow_quantum/*.whl + + echo "Done. Look for wheel in /tmp/tensorflow_quantum/." + ls -l /tmp/tensorflow_quantum/ +fi diff --git a/release/clean_distribution.sh b/release/clean_distribution.sh new file mode 100755 index 000000000..680ea6bee --- /dev/null +++ b/release/clean_distribution.sh @@ -0,0 +1,100 @@ +#!/usr/bin/env bash +# Copyright 2025 Google LLC +# +# 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 +# +# https://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. +# ============================================================================= + +# Summary: bundle external shared libraries into the final TFQ wheel. +# Run this script with the option "-h" to get a usage summary. +# +# This uses auditwheel to "repair" the wheel: copy external shared libraries +# into the wheel itself and modify the RPATH entries such that these libraries +# will be picked up at runtime. This accomplishes a similar result as if the +# libraries had been statically linked. + +set -eu + +# Find the top of the local TFQ git tree. Do it early in case this fails. +thisdir=$(CDPATH="" cd -- "$(dirname -- "$0")" && pwd -P) +repo_dir=$(git -C "${thisdir}" rev-parse --show-toplevel 2>/dev/null || \ + echo "${thisdir}/..") + +# Default values for variables that can be changed via command line flags. +docker_image="quay.io/pypa/manylinux_2_34_x86_64" +platform="manylinux_2_17_x86_64" +py_version=$(python3 --version | cut -d' ' -f2 | cut -d. -f1,2) +action="repair" +verbose="" + +usage="Usage: ${0} [OPTIONS] /path/to/wheel.whl +Run auditwheel on the given wheel file. Available options: + +Configuration options: + -m IMG Use manylinux Docker image IMG (default: ${docker_image}) + -p X.Y Use Python version X.Y (default: ${py_version}) + -t TAG Pass --plat TAG to auditwheel (default: ${platform}) + +General options: + -h Show this help message and exit + -n Dry run: print commands but don't execute them + -s Run 'auditwheel show', not repair (default: run 'auditwheel repair') + -v Produce verbose output" + +dry_run="false" +while getopts "hm:np:st:v" opt; do + case "${opt}" in + h) echo "${usage}"; exit 0 ;; + m) docker_image="${OPTARG}" ;; + n) dry_run="true" ;; + p) py_version="${OPTARG}" ;; + s) action="show" ;; + t) platform="${OPTARG}" ;; + v) verbose="--verbose" ;; + *) echo "${usage}" >&2; exit 1 ;; + esac +done +shift $((OPTIND -1)) +if [ ! $# -ge 1 ]; then + echo "ERROR: insufficient arguments." + echo "${usage}" >&2 + exit 1 +fi + +wheel_path="$(realpath "${1}")" +wheel_name="$(basename "${1}")" + +args="" +if [ "${action}" = "repair" ]; then + args="${verbose} --exclude libtensorflow_framework.so.2 --plat ${platform}" +fi + +# Use 'set --' to build the command in the positional parameters ($1, $2, ...) +set -- docker run -it --rm --network host \ + -w /tfq \ + -v "${repo_dir}":/tfq \ + -v "${wheel_path}":"/tmp/${wheel_name}" \ + "${docker_image}" \ + bash -c "auditwheel ${action} ${args} -w /tfq/wheelhouse /tmp/${wheel_name}" + +if [ "${dry_run}" = "true" ]; then + # Loop through the positional parameters and simply print them. + printf "(Dry run) " + printf '%s ' "$@" + echo +else + echo "Running 'auditwheel ${action}' in Docker with image ${docker_image}" + "$@" + if [ "${action}" = "repair" ]; then + echo "Done. New wheel file written to ${repo_dir}/wheelhouse" + fi +fi From 03121e745c8af9ab77aff336262b20d828d22dcc Mon Sep 17 00:00:00 2001 From: mhucka Date: Tue, 9 Dec 2025 05:37:59 +0000 Subject: [PATCH 2/5] Add unit tests for build_distribution & clean_distribution --- release/BUILD | 23 +++++++ release/build_distribution_test.py | 103 +++++++++++++++++++++++++++ release/clean_distribution_test.py | 107 +++++++++++++++++++++++++++++ 3 files changed, 233 insertions(+) create mode 100644 release/build_distribution_test.py create mode 100644 release/clean_distribution_test.py diff --git a/release/BUILD b/release/BUILD index f23d8528f..9aa967bc1 100644 --- a/release/BUILD +++ b/release/BUILD @@ -82,3 +82,26 @@ sh_binary( "//tensorflow_quantum/python/optimizers:spsa_minimizer", ], ) + +exports_files([ + "build_distribution.sh", + "clean_distribution.sh", +]) + +py_test( + name = "build_distribution_test", + srcs = ["build_distribution_test.py"], + data = [ + "build_distribution.sh", + ], + python_version = "PY3", +) + +py_test( + name = "clean_distribution_test", + srcs = ["clean_distribution_test.py"], + data = [ + "clean_distribution.sh", + ], + python_version = "PY3", +) diff --git a/release/build_distribution_test.py b/release/build_distribution_test.py new file mode 100644 index 000000000..f9a003c23 --- /dev/null +++ b/release/build_distribution_test.py @@ -0,0 +1,103 @@ +# Copyright 2025 Google LLC +# +# 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 +# +# https://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. +# ============================================================================ +"""Tests for build_distribution.sh script.""" + +import os +import subprocess +import unittest + + +class BuildDistributionTest(unittest.TestCase): + """Tests for build_distribution.sh script.""" + + def setUp(self): + # Find the repo root. Default to the current directory if that fails. + try: + self.repo_root = subprocess.check_output( + ["git", "rev-parse", "--show-toplevel"], + universal_newlines=True).strip() + except subprocess.CalledProcessError: + self.repo_root = os.getcwd() + + self.script = os.path.join(self.repo_root, "release", + "build_distribution.sh") + + def test_dry_run(self): + """Test build_distribution.sh script in dry-run mode.""" + cmd = [self.script, "-n", "-c", "11.2", "-p", "3.9", "-t", "2.10"] + result = subprocess.run(cmd, + capture_output=True, + text=True, + check=False) + self.assertEqual(result.returncode, 0, + f"Script failed with stderr: {result.stderr}") + output = result.stdout + + self.assertIn("(Dry run) docker run", output) + # Check that arguments propagated to the docker image tag and env vars + self.assertIn("tensorflow/build:2.10-python3.9", output) + self.assertIn("cuda_version=11.2", output) + self.assertIn("py_version=3.9", output) + self.assertIn("tf_version=2.10", output) + + self.assertIn( + "(Dry run) check-wheel-contents /tmp/tensorflow_quantum/*.whl", + output) + + def test_defaults(self): + """Test build_distribution.sh script defaults.""" + cmd = [self.script, "-n"] + result = subprocess.run(cmd, + capture_output=True, + text=True, + check=False) + self.assertEqual(result.returncode, 0, + f"Script failed with stderr: {result.stderr}") + output = result.stdout + # Check default cuda version (12) and default cleanup (true) + self.assertIn("cuda_version=12", output) + self.assertIn("cleanup=true", output) + + def test_help(self): + """Test build_distribution.sh script help flag.""" + cmd = [self.script, "-h"] + result = subprocess.run(cmd, + capture_output=True, + text=True, + check=False) + self.assertEqual(result.returncode, 0, + f"Script failed with stderr: {result.stderr}") + self.assertIn("Usage:", result.stdout) + self.assertIn("Build a distribution wheel for TensorFlow Quantum.", + result.stdout) + + def test_invalid_option(self): + """Test build_distribution.sh script with invalid option.""" + cmd = [self.script, "-z"] + result = subprocess.run(cmd, + capture_output=True, + text=True, + check=False) + self.assertNotEqual(result.returncode, 0) + self.assertIn("Usage:", result.stdout + result.stderr) + stderr = result.stderr.lower() + self.assertTrue( + "illegal option" in stderr or "invalid option" in stderr, + "Expected 'illegal option' or 'invalid option' in stderr, " + f"got: {stderr}") + + +if __name__ == "__main__": + unittest.main() diff --git a/release/clean_distribution_test.py b/release/clean_distribution_test.py new file mode 100644 index 000000000..ff96a2c83 --- /dev/null +++ b/release/clean_distribution_test.py @@ -0,0 +1,107 @@ +# Copyright 2025 Google LLC +# +# 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 +# +# https://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. +# ============================================================================ +"""Tests for clean_distribution.sh script.""" + +import os +import subprocess +import unittest + + +class CleanDistributionTest(unittest.TestCase): + """Tests for clean_distribution.sh script.""" + + def setUp(self): + # Find the repo root + try: + self.repo_root = subprocess.check_output( + ["git", "rev-parse", "--show-toplevel"], + universal_newlines=True).strip() + except subprocess.CalledProcessError: + self.repo_root = os.getcwd() + + self.script = os.path.join(self.repo_root, "release", + "clean_distribution.sh") + + def test_dry_run(self): + """Test clean_distribution.sh script in dry-run mode.""" + wheel_path = "fake.whl" + cmd = [self.script, "-n", "-p", "3.10", wheel_path] + result = subprocess.run(cmd, + capture_output=True, + text=True, + check=False) + self.assertEqual(result.returncode, 0, + f"Script failed with stderr: {result.stderr}") + output = result.stdout + + self.assertIn("(Dry run) docker run", output) + self.assertIn("auditwheel repair", output) + self.assertIn(f"/tmp/{wheel_path}", output) + + def test_show_action(self): + """Test clean_distribution.sh script with -s (show) action.""" + wheel_path = "fake.whl" + cmd = [self.script, "-n", "-s", wheel_path] + result = subprocess.run(cmd, + capture_output=True, + text=True, + check=False) + self.assertEqual(result.returncode, 0, + f"Script failed with stderr: {result.stderr}") + output = result.stdout + + self.assertIn("auditwheel show", output) + + def test_missing_arg(self): + """Test clean_distribution.sh script fails without wheel argument.""" + cmd = [self.script, "-n"] + result = subprocess.run(cmd, + capture_output=True, + text=True, + check=False) + self.assertNotEqual(result.returncode, 0) + self.assertIn("ERROR: insufficient arguments", + result.stdout + result.stderr) + + def test_help(self): + """Test clean_distribution.sh script help flag.""" + cmd = [self.script, "-h"] + result = subprocess.run(cmd, + capture_output=True, + text=True, + check=False) + self.assertEqual(result.returncode, 0, + f"Script failed with stderr: {result.stderr}") + self.assertIn("Usage:", result.stdout) + self.assertIn("Run auditwheel on the given wheel file.", result.stdout) + + def test_invalid_option(self): + """Test clean_distribution.sh script with invalid option.""" + cmd = [self.script, "-z"] + result = subprocess.run(cmd, + capture_output=True, + text=True, + check=False) + self.assertNotEqual(result.returncode, 0) + self.assertIn("Usage:", result.stdout + result.stderr) + stderr = result.stderr.lower() + self.assertTrue( + "illegal option" in stderr or "invalid option" in stderr, + "Expected 'illegal option' or 'invalid option' in stderr, " + f"got: {stderr}") + + +if __name__ == "__main__": + unittest.main() From 7ad112da497671e68ab969a756731a25b543115e Mon Sep 17 00:00:00 2001 From: mhucka Date: Tue, 9 Dec 2025 05:40:10 +0000 Subject: [PATCH 3/5] Remove `set -x` & a print stmt that leads to confusion The final print statement about the directory is misleading when `build_pip_package.sh` is being run inside a Docker container. On balance, I think it's better to remove it. Hopefully the user running the script will remember that they supplied the destination parameter as an argument on the command line to begin with. --- release/build_pip_package.sh | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/release/build_pip_package.sh b/release/build_pip_package.sh index 8bed5b909..9cdfcf9f4 100755 --- a/release/build_pip_package.sh +++ b/release/build_pip_package.sh @@ -1,20 +1,20 @@ #!/bin/bash # Copyright 2020 The TensorFlow Quantum 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. # ============================================================================== + set -e -set -x EXPORT_DIR="bazel-bin/release/build_pip_package.runfiles/__main__" @@ -53,7 +53,7 @@ function main() { cp dist/*.whl "${DEST}" popd rm -rf ${TMPDIR} - echo $(date) : "=== Output wheel file is in: ${DEST}" + echo "$(date) : === Done." } main "$@" From 247faacbb9c34b78f77334d736dab2c6ba346e85 Mon Sep 17 00:00:00 2001 From: mhucka Date: Tue, 9 Dec 2025 05:43:39 +0000 Subject: [PATCH 4/5] Make sure build_pip_package.sh has the tools it needs --- release/build_pip_package.sh | 3 +++ 1 file changed, 3 insertions(+) diff --git a/release/build_pip_package.sh b/release/build_pip_package.sh index 9cdfcf9f4..603a060f5 100755 --- a/release/build_pip_package.sh +++ b/release/build_pip_package.sh @@ -16,6 +16,9 @@ set -e +# Ensure packaging tools are present in THIS interpreter. +pip install -qq setuptools wheel build --root-user-action ignore + EXPORT_DIR="bazel-bin/release/build_pip_package.runfiles/__main__" function main() { From 1377c8402b6c10994657a610c4cd8cddfdae0f2f Mon Sep 17 00:00:00 2001 From: mhucka Date: Tue, 9 Dec 2025 22:14:11 +0000 Subject: [PATCH 5/5] Use /bin/bash Per Google guidance, executable scripts should use `/bin/bash`. --- release/build_distribution.sh | 4 ++-- release/clean_distribution.sh | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/release/build_distribution.sh b/release/build_distribution.sh index 0bbfb98fe..3e1da6fba 100755 --- a/release/build_distribution.sh +++ b/release/build_distribution.sh @@ -1,4 +1,4 @@ -#!/usr/bin/env bash +#!/bin/bash # Copyright 2025 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -92,7 +92,7 @@ trap 'rm -f "${build_script}" || true' EXIT # The printf'ed section dividers are to make it easier to search the output. cat <<'EOF' > "${build_script}" -#!/usr/bin/env bash +#!/bin/bash set -o errexit cd /tfq PREFIX='[DOCKER] ' diff --git a/release/clean_distribution.sh b/release/clean_distribution.sh index 680ea6bee..212da0de4 100755 --- a/release/clean_distribution.sh +++ b/release/clean_distribution.sh @@ -1,4 +1,4 @@ -#!/usr/bin/env bash +#!/bin/bash # Copyright 2025 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License");