From 2a3ad5003e831377612dff3f43b6d15f4ad783be Mon Sep 17 00:00:00 2001 From: Google AI Edge Date: Wed, 28 Aug 2024 11:48:16 -0700 Subject: [PATCH] Update TF and XNNPACK bazel dependencies. PiperOrigin-RevId: 668544706 --- .bazelrc | 2 + WORKSPACE | 14 +- bazel/org_tensorflow_system_python.diff | 528 +++++++++++++++++++++++- 3 files changed, 521 insertions(+), 23 deletions(-) diff --git a/.bazelrc b/.bazelrc index affff962..65c2d498 100644 --- a/.bazelrc +++ b/.bazelrc @@ -6,6 +6,8 @@ build --jobs 128 build --enable_platform_specific_config build --define xnnpack_use_latest_ops=true +build --define=xnn_enable_avx512amx=false +build --define=xnn_enable_avx512fp16=false # Linux build:linux --cxxopt=-std=c++17 diff --git a/WORKSPACE b/WORKSPACE index 6578c547..93cc52db 100644 --- a/WORKSPACE +++ b/WORKSPACE @@ -61,13 +61,13 @@ http_archive( ], ) -# XNNPACK on 2024-05-03. +# XNNPACK on 2024-07-16 http_archive( name = "XNNPACK", # `curl -L | shasum -a 256` - sha256 = "0a38628999b2e8cc84c41b82a1282dcd90b8da3cf24a67e7f9ee148d8c066a94", - strip_prefix = "XNNPACK-76a9c653c2fe71613996edc1e218936add79ef55", - url = "https://github.com/google/XNNPACK/archive/76a9c653c2fe71613996edc1e218936add79ef55.zip", + sha256 = "0e5d5c16686beff813e3946b26ca412f28acaf611228d20728ffb6479264fe19", + strip_prefix = "XNNPACK-9ddeb74f9f6866174d61888947e4aa9ffe963b1b", + url = "https://github.com/google/XNNPACK/archive/9ddeb74f9f6866174d61888947e4aa9ffe963b1b.zip", ) # Needed by TensorFlow @@ -82,10 +82,10 @@ http_archive( ) # TensorFlow repo should always go after the other external dependencies. -# TF on 2024-05-02. -_TENSORFLOW_GIT_COMMIT = "26d4ea90364daa14bbb2bc5c2aa68f5b70c4641f" +# TF on 2024-07-18. +_TENSORFLOW_GIT_COMMIT = "117a62ac439ed87eb26f67208be60e01c21960de" # curl -L https://github.com/tensorflow/tensorflow/archive/.tar.gz | shasum -a 256 -_TENSORFLOW_SHA256 = "92d4f6bb040496711cd0faf3cec59e2bedc6e3ab215ceb92d7ce0a2be558c786" +_TENSORFLOW_SHA256 = "2a1e56f9f83f99e2b9d01a184bc6f409209b36c98fb94b6d5db3f0ab20ec33f2" http_archive( name = "org_tensorflow", urls = [ diff --git a/bazel/org_tensorflow_system_python.diff b/bazel/org_tensorflow_system_python.diff index 06fb9d27..b3eb23ce 100644 --- a/bazel/org_tensorflow_system_python.diff +++ b/bazel/org_tensorflow_system_python.diff @@ -1,17 +1,113 @@ +diff --git a/tensorflow/lite/c/BUILD b/tensorflow/lite/c/BUILD +index 19cdd37ed4f..b4253b49a61 100644 +--- a/tensorflow/lite/c/BUILD ++++ b/tensorflow/lite/c/BUILD +@@ -119,10 +119,7 @@ cc_library_with_tflite_with_c_headers_test( + tflite_deps = [ + ":c_api", + ], +- deps = [ +- "//tensorflow/lite/core/c:c_api_experimental", +- "//tensorflow/lite/core/c:c_api_opaque", +- ], ++ deps = ["//tensorflow/lite/core/c:c_api_experimental"], + ) + + # Same as ":c_api_experimental", but without linking in the default CreateOpResolver implementation. +@@ -143,10 +140,7 @@ cc_library_with_tflite_with_c_headers_test( + tflite_deps = [ + ":c_api_without_op_resolver", + ], +- deps = [ +- "//tensorflow/lite/core/c:c_api_experimental_without_op_resolver", +- "//tensorflow/lite/core/c:c_api_opaque_without_op_resolver", +- ], ++ deps = ["//tensorflow/lite/core/c:c_api_experimental_without_op_resolver"], + ) + + # Same as ":c_api_experimental", but without linking in the default CreateOpResolver implementation, +@@ -183,6 +177,7 @@ cc_library_with_tflite_with_c_headers_test( + copts = tflite_copts() + tflite_copts_warnings(), + generate_opaque_delegate_target = True, + tflite_deps = [":c_api"], ++ linkstatic = 1, + deps = ["//tensorflow/lite/core/c:c_api_opaque"], + ) + +@@ -209,7 +204,7 @@ cc_library_with_tflite_with_c_headers_test( + tflite_deps = [ + ":c_api_without_op_resolver_without_alwayslink", + ], +- deps = ["//tensorflow/lite/core/c:c_api_opaque_without_op_resolver_without_alwayslink"], ++ deps = ["//tensorflow/lite/core/c:c_api_experimental_without_op_resolver_without_alwayslink"], + ) + + cc_library_with_tflite_with_c_headers_test( +@@ -457,7 +452,6 @@ cc_test( + ":c_api_experimental", + "//tensorflow/lite/core/c:c_api", + "//tensorflow/lite/core/c:c_api_experimental", +- "//tensorflow/lite/core/c:c_api_opaque", + "//tensorflow/lite/core/c:c_api_types", + "//tensorflow/lite/core/c:common", + ], +diff --git a/tensorflow/lite/core/c/BUILD b/tensorflow/lite/core/c/BUILD +index 018b6e1004c..cf355612386 100644 +--- a/tensorflow/lite/core/c/BUILD ++++ b/tensorflow/lite/core/c/BUILD +@@ -378,7 +378,6 @@ tflite_cc_library_with_c_headers_test( + deps = [ + ":c_api", + ":c_api_experimental_without_op_resolver", +- ":c_api_opaque", + ":c_api_types", + ":common", + ":operator", +@@ -399,6 +398,7 @@ tflite_cc_library_with_c_headers_test( + name = "c_api_experimental_without_op_resolver", + srcs = [ + "c_api_experimental.cc", ++ "c_api_opaque.cc", + ], + hdrs = [ + "c_api_experimental.h", +@@ -413,7 +413,6 @@ tflite_cc_library_with_c_headers_test( + copts = tflite_copts(), + tags = ["allow_undefined_symbols"], # For tflite::CreateOpResolver(). + deps = [ +- ":c_api_opaque_without_op_resolver", + ":c_api_types", + ":c_api_without_op_resolver", + ":common", +@@ -451,6 +450,7 @@ tflite_cc_library_with_c_headers_test( + name = "c_api_experimental_without_op_resolver_without_alwayslink", + srcs = [ + "c_api_experimental.cc", ++ "c_api_opaque.cc", + ], + hdrs = [ + "c_api_experimental.h", +@@ -467,7 +467,6 @@ tflite_cc_library_with_c_headers_test( + copts = tflite_copts(), + tags = ["allow_undefined_symbols"], # For tflite::CreateOpResolver(). + deps = [ +- ":c_api_opaque_without_op_resolver_without_alwayslink", + ":c_api_types", + ":c_api_without_op_resolver_without_alwayslink", + ":common", diff --git a/tensorflow/tools/toolchains/cpus/aarch64/aarch64_compiler_configure.bzl b/tensorflow/tools/toolchains/cpus/aarch64/aarch64_compiler_configure.bzl index 00cd6983ca3..d9c5ef16f9b 100644 --- a/tensorflow/tools/toolchains/cpus/aarch64/aarch64_compiler_configure.bzl +++ b/tensorflow/tools/toolchains/cpus/aarch64/aarch64_compiler_configure.bzl @@ -1,7 +1,7 @@ """Configurations of AARCH64 builds used with Docker container.""" - + load("//tensorflow/tools/toolchains:cpus/aarch64/aarch64.bzl", "remote_aarch64_configure") -load("//third_party/py:python_configure.bzl", "remote_python_configure") +load("//third_party/py/non_hermetic:python_configure.bzl", "remote_python_configure") load("//third_party/remote_config:remote_platform_configure.bzl", "remote_platform_configure") - + def ml2014_tf_aarch64_configs(name_container_map, env): - diff --git a/tensorflow/tools/toolchains/remote_config/rbe_config.bzl b/tensorflow/tools/toolchains/remote_config/rbe_config.bzl index ae776c2a2fd..108e79edbd7 100644 --- a/tensorflow/tools/toolchains/remote_config/rbe_config.bzl @@ -24,12 +120,12 @@ index ae776c2a2fd..108e79edbd7 100644 +load("//third_party/py/non_hermetic:python_configure.bzl", "local_python_configure", "remote_python_configure") load("//third_party/remote_config:remote_platform_configure.bzl", "remote_platform_configure") load("//third_party/tensorrt:tensorrt_configure.bzl", "remote_tensorrt_configure") - + diff --git a/tensorflow/workspace2.bzl b/tensorflow/workspace2.bzl -index 056df85ffdb..7422baf8c59 100644 +index 380a1a93585..3f3e12a0617 100644 --- a/tensorflow/workspace2.bzl +++ b/tensorflow/workspace2.bzl -@@ -37,7 +37,7 @@ load("//third_party/nasm:workspace.bzl", nasm = "repo") +@@ -44,7 +44,7 @@ load("//third_party/nasm:workspace.bzl", nasm = "repo") load("//third_party/nccl:nccl_configure.bzl", "nccl_configure") load("//third_party/opencl_headers:workspace.bzl", opencl_headers = "repo") load("//third_party/pasta:workspace.bzl", pasta = "repo") @@ -38,16 +134,416 @@ index 056df85ffdb..7422baf8c59 100644 load("//third_party/py/ml_dtypes:workspace.bzl", ml_dtypes = "repo") load("//third_party/pybind11_abseil:workspace.bzl", pybind11_abseil = "repo") load("//third_party/pybind11_bazel:workspace.bzl", pybind11_bazel = "repo") +diff --git a/third_party/py/non_hermetic/BUILD b/third_party/py/non_hermetic/BUILD +new file mode 100644 +index 00000000000..d14731cafcd +--- /dev/null ++++ b/third_party/py/non_hermetic/BUILD +@@ -0,0 +1,2 @@ ++# Empty BUILD file ++ +diff --git a/third_party/py/non_hermetic/BUILD.tpl b/third_party/py/non_hermetic/BUILD.tpl +new file mode 100644 +index 00000000000..0d2d06da29a +--- /dev/null ++++ b/third_party/py/non_hermetic/BUILD.tpl +@@ -0,0 +1,80 @@ ++licenses(["restricted"]) ++ ++package(default_visibility = ["//visibility:public"]) ++ ++# Point both runtimes to the same python binary to ensure we always ++# use the python binary specified by ./configure.py script. ++load("@bazel_tools//tools/python:toolchain.bzl", "py_runtime_pair") ++ ++py_runtime( ++ name = "py2_runtime", ++ interpreter_path = "%{PYTHON_BIN_PATH}", ++ python_version = "PY2", ++) ++ ++py_runtime( ++ name = "py3_runtime", ++ interpreter_path = "%{PYTHON_BIN_PATH}", ++ python_version = "PY3", ++) ++ ++py_runtime_pair( ++ name = "py_runtime_pair", ++ py2_runtime = ":py2_runtime", ++ py3_runtime = ":py3_runtime", ++) ++ ++toolchain( ++ name = "py_toolchain", ++ toolchain = ":py_runtime_pair", ++ toolchain_type = "@bazel_tools//tools/python:toolchain_type", ++ target_compatible_with = [%{PLATFORM_CONSTRAINT}], ++ exec_compatible_with = [%{PLATFORM_CONSTRAINT}], ++) ++ ++# To build Python C/C++ extension on Windows, we need to link to python import library pythonXY.lib ++# See https://docs.python.org/3/extending/windows.html ++cc_import( ++ name = "python_lib", ++ interface_library = select({ ++ ":windows": ":python_import_lib", ++ # A placeholder for Unix platforms which makes --no_build happy. ++ "//conditions:default": "not-existing.lib", ++ }), ++ system_provided = 1, ++) ++ ++cc_library( ++ name = "python_headers", ++ hdrs = [":python_include"], ++ deps = select({ ++ ":windows": [":python_lib"], ++ "//conditions:default": [], ++ }), ++ includes = ["python_include"], ++) ++ ++# This alias is exists for the use of targets in the @llvm-project dependency, ++# which expect a python_headers target called @python_runtime//:headers. We use ++# a repo_mapping to alias python_runtime to this package, and an alias to create ++# the correct target. ++alias( ++ name = "headers", ++ actual = ":python_headers", ++) ++ ++cc_library( ++ name = "numpy_headers", ++ hdrs = [":numpy_include"], ++ includes = ["numpy_include"], ++) ++ ++config_setting( ++ name = "windows", ++ values = {"cpu": "x64_windows"}, ++ visibility = ["//visibility:public"], ++) ++ ++%{PYTHON_INCLUDE_GENRULE} ++%{NUMPY_INCLUDE_GENRULE} ++%{PYTHON_IMPORT_LIB_GENRULE} diff --git a/third_party/py/non_hermetic/python_configure.bzl b/third_party/py/non_hermetic/python_configure.bzl -index 89732c3e33d..4ac1c8f5c04 100644 ---- a/third_party/py/non_hermetic/python_configure.bzl +new file mode 100644 +index 00000000000..e6eeb02641a +--- /dev/null +++ b/third_party/py/non_hermetic/python_configure.bzl -@@ -203,7 +203,7 @@ def _create_local_python_repository(repository_ctx): - # Resolve all labels before doing any real work. Resolving causes the - # function to be restarted with all previous state being lost. This - # can easily lead to a O(n^2) runtime in the number of labels. -- build_tpl = repository_ctx.path(Label("//third_party/py:BUILD.tpl")) +@@ -0,0 +1,313 @@ ++"""Repository rule for Python autoconfiguration. ++ ++`python_configure` depends on the following environment variables: ++ ++ * `PYTHON_BIN_PATH`: location of python binary. ++ * `PYTHON_LIB_PATH`: Location of python libraries. ++""" ++ ++load( ++ "//third_party/remote_config:common.bzl", ++ "BAZEL_SH", ++ "PYTHON_BIN_PATH", ++ "PYTHON_LIB_PATH", ++ "TF_PYTHON_CONFIG_REPO", ++ "auto_config_fail", ++ "config_repo_label", ++ "execute", ++ "get_bash_bin", ++ "get_host_environ", ++ "get_python_bin", ++ "is_windows", ++ "raw_exec", ++ "read_dir", ++) ++ ++def _genrule(src_dir, genrule_name, command, outs): ++ """Returns a string with a genrule. ++ ++ Genrule executes the given command and produces the given outputs. ++ """ ++ return ( ++ "genrule(\n" + ++ ' name = "' + ++ genrule_name + '",\n' + ++ " outs = [\n" + ++ outs + ++ "\n ],\n" + ++ ' cmd = """\n' + ++ command + ++ '\n """,\n' + ++ ")\n" ++ ) ++ ++def _norm_path(path): ++ """Returns a path with '/' and remove the trailing slash.""" ++ path = path.replace("\\", "/") ++ if path[-1] == "/": ++ path = path[:-1] ++ return path ++ ++def _symlink_genrule_for_dir( ++ repository_ctx, ++ src_dir, ++ dest_dir, ++ genrule_name, ++ src_files = [], ++ dest_files = []): ++ """Returns a genrule to symlink(or copy if on Windows) a set of files. ++ ++ If src_dir is passed, files will be read from the given directory; otherwise ++ we assume files are in src_files and dest_files ++ """ ++ if src_dir != None: ++ src_dir = _norm_path(src_dir) ++ dest_dir = _norm_path(dest_dir) ++ files = "\n".join(read_dir(repository_ctx, src_dir)) ++ ++ # Create a list with the src_dir stripped to use for outputs. ++ dest_files = files.replace(src_dir, "").splitlines() ++ src_files = files.splitlines() ++ command = [] ++ outs = [] ++ for i in range(len(dest_files)): ++ if dest_files[i] != "": ++ # If we have only one file to link we do not want to use the dest_dir, as ++ # $(@D) will include the full path to the file. ++ dest = "$(@D)/" + dest_dir + dest_files[i] if len(dest_files) != 1 else "$(@D)/" + dest_files[i] ++ ++ # Copy the headers to create a sandboxable setup. ++ cmd = "cp -f" ++ command.append(cmd + ' "%s" "%s"' % (src_files[i], dest)) ++ outs.append(' "' + dest_dir + dest_files[i] + '",') ++ genrule = _genrule( ++ src_dir, ++ genrule_name, ++ " && ".join(command), ++ "\n".join(outs), ++ ) ++ return genrule ++ ++def _get_python_lib(repository_ctx, python_bin): ++ """Gets the python lib path.""" ++ python_lib = get_host_environ(repository_ctx, PYTHON_LIB_PATH) ++ if python_lib != None: ++ return python_lib ++ ++ # The interesting program to execute. ++ print_lib = [ ++ "from __future__ import print_function", ++ "import site", ++ "import os", ++ "python_paths = []", ++ "if os.getenv('PYTHONPATH') is not None:", ++ " python_paths = os.getenv('PYTHONPATH').split(':')", ++ "try:", ++ " library_paths = site.getsitepackages()", ++ "except AttributeError:", ++ " from distutils.sysconfig import get_python_lib", ++ " library_paths = [get_python_lib()]", ++ "all_paths = set(python_paths + library_paths)", ++ "paths = []", ++ "for path in all_paths:", ++ " if os.path.isdir(path):", ++ " paths.append(path)", ++ "if len(paths) >=1:", ++ " print(paths[0])", ++ ] ++ ++ # The below script writes the above program to a file ++ # and executes it. This is to work around the limitation ++ # of not being able to upload files as part of execute. ++ cmd = "from os import linesep;" ++ cmd += "f = open('script.py', 'w');" ++ for line in print_lib: ++ cmd += "f.write(\"%s\" + linesep);" % line ++ cmd += "f.close();" ++ cmd += "from subprocess import call;" ++ cmd += "call([\"%s\", \"script.py\"]);" % python_bin ++ ++ result = execute(repository_ctx, [python_bin, "-c", cmd]) ++ return result.stdout.strip() ++ ++def _check_python_lib(repository_ctx, python_lib): ++ """Checks the python lib path.""" ++ cmd = 'test -d "%s" -a -x "%s"' % (python_lib, python_lib) ++ result = raw_exec(repository_ctx, [get_bash_bin(repository_ctx), "-c", cmd]) ++ if result.return_code == 1: ++ auto_config_fail("Invalid python library path: %s" % python_lib) ++ ++def _check_python_bin(repository_ctx, python_bin): ++ """Checks the python bin path.""" ++ cmd = '[[ -x "%s" ]] && [[ ! -d "%s" ]]' % (python_bin, python_bin) ++ result = raw_exec(repository_ctx, [get_bash_bin(repository_ctx), "-c", cmd]) ++ if result.return_code == 1: ++ auto_config_fail("--define %s='%s' is not executable. Is it the python binary?" % ( ++ PYTHON_BIN_PATH, ++ python_bin, ++ )) ++ ++def _get_python_include(repository_ctx, python_bin): ++ """Gets the python include path.""" ++ result = execute( ++ repository_ctx, ++ [ ++ python_bin, ++ "-Wignore", ++ "-c", ++ "import sysconfig; " + ++ "print(sysconfig.get_path('include'))", ++ ], ++ error_msg = "Problem getting python include path.", ++ error_details = ("Is the Python binary path set up right? " + ++ "(See ./configure or " + PYTHON_BIN_PATH + ".) " + ++ "Is distutils installed?"), ++ ) ++ return result.stdout.splitlines()[0] ++ ++def _get_python_import_lib_name(repository_ctx, python_bin): ++ """Get Python import library name (pythonXY.lib) on Windows.""" ++ result = execute( ++ repository_ctx, ++ [ ++ python_bin, ++ "-c", ++ "import sys;" + ++ 'print("python" + str(sys.version_info[0]) + ' + ++ ' str(sys.version_info[1]) + ".lib")', ++ ], ++ error_msg = "Problem getting python import library.", ++ error_details = ("Is the Python binary path set up right? " + ++ "(See ./configure or " + PYTHON_BIN_PATH + ".) "), ++ ) ++ return result.stdout.splitlines()[0] ++ ++def _get_numpy_include(repository_ctx, python_bin): ++ """Gets the numpy include path.""" ++ return execute( ++ repository_ctx, ++ [ ++ python_bin, ++ "-c", ++ "from __future__ import print_function;" + ++ "import numpy;" + ++ " print(numpy.get_include());", ++ ], ++ error_msg = "Problem getting numpy include path.", ++ error_details = "Is numpy installed?", ++ ).stdout.splitlines()[0] ++ ++def _create_local_python_repository(repository_ctx): ++ """Creates the repository containing files set up to build with Python.""" ++ ++ # Resolve all labels before doing any real work. Resolving causes the ++ # function to be restarted with all previous state being lost. This ++ # can easily lead to a O(n^2) runtime in the number of labels. + build_tpl = repository_ctx.path(Label("//third_party/py/non_hermetic:BUILD.tpl")) - - python_bin = get_python_bin(repository_ctx) - _check_python_bin(repository_ctx, python_bin) ++ ++ python_bin = get_python_bin(repository_ctx) ++ _check_python_bin(repository_ctx, python_bin) ++ python_lib = _get_python_lib(repository_ctx, python_bin) ++ _check_python_lib(repository_ctx, python_lib) ++ python_include = _get_python_include(repository_ctx, python_bin) ++ numpy_include = _get_numpy_include(repository_ctx, python_bin) + "/numpy" ++ python_include_rule = _symlink_genrule_for_dir( ++ repository_ctx, ++ python_include, ++ "python_include", ++ "python_include", ++ ) ++ python_import_lib_genrule = "" ++ ++ # To build Python C/C++ extension on Windows, we need to link to python import library pythonXY.lib ++ # See https://docs.python.org/3/extending/windows.html ++ if is_windows(repository_ctx): ++ python_bin = python_bin.replace("\\", "/") ++ python_include = _norm_path(python_include) ++ python_import_lib_name = _get_python_import_lib_name(repository_ctx, python_bin) ++ python_import_lib_src = python_include.rsplit("/", 1)[0] + "/libs/" + python_import_lib_name ++ python_import_lib_genrule = _symlink_genrule_for_dir( ++ repository_ctx, ++ None, ++ "", ++ "python_import_lib", ++ [python_import_lib_src], ++ [python_import_lib_name], ++ ) ++ numpy_include_rule = _symlink_genrule_for_dir( ++ repository_ctx, ++ numpy_include, ++ "numpy_include/numpy", ++ "numpy_include", ++ ) ++ ++ platform_constraint = "" ++ if repository_ctx.attr.platform_constraint: ++ platform_constraint = "\"%s\"" % repository_ctx.attr.platform_constraint ++ repository_ctx.template("BUILD", build_tpl, { ++ "%{PYTHON_BIN_PATH}": python_bin, ++ "%{PYTHON_INCLUDE_GENRULE}": python_include_rule, ++ "%{PYTHON_IMPORT_LIB_GENRULE}": python_import_lib_genrule, ++ "%{NUMPY_INCLUDE_GENRULE}": numpy_include_rule, ++ "%{PLATFORM_CONSTRAINT}": platform_constraint, ++ }) ++ ++def _create_remote_python_repository(repository_ctx, remote_config_repo): ++ """Creates pointers to a remotely configured repo set up to build with Python. ++ """ ++ repository_ctx.template("BUILD", config_repo_label(remote_config_repo, ":BUILD"), {}) ++ ++def _python_autoconf_impl(repository_ctx): ++ """Implementation of the python_autoconf repository rule.""" ++ if get_host_environ(repository_ctx, TF_PYTHON_CONFIG_REPO) != None: ++ _create_remote_python_repository( ++ repository_ctx, ++ get_host_environ(repository_ctx, TF_PYTHON_CONFIG_REPO), ++ ) ++ else: ++ _create_local_python_repository(repository_ctx) ++ ++_ENVIRONS = [ ++ BAZEL_SH, ++ PYTHON_BIN_PATH, ++ PYTHON_LIB_PATH, ++] ++ ++local_python_configure = repository_rule( ++ implementation = _create_local_python_repository, ++ environ = _ENVIRONS, ++ attrs = { ++ "environ": attr.string_dict(), ++ "platform_constraint": attr.string(), ++ }, ++) ++ ++remote_python_configure = repository_rule( ++ implementation = _create_local_python_repository, ++ environ = _ENVIRONS, ++ remotable = True, ++ attrs = { ++ "environ": attr.string_dict(), ++ "platform_constraint": attr.string(), ++ }, ++) ++ ++python_configure = repository_rule( ++ implementation = _python_autoconf_impl, ++ environ = _ENVIRONS + [TF_PYTHON_CONFIG_REPO], ++ attrs = { ++ "platform_constraint": attr.string(), ++ }, ++) ++"""Detects and configures the local Python. ++ ++Add the following to your WORKSPACE FILE: ++ ++```python ++python_configure(name = "local_config_python") ++``` ++ ++Args: ++ name: A unique name for this workspace rule. ++""" ++ \ No newline at end of file