From dd0d2a3dfd03ebe1ab5156870159bae8ceee0953 Mon Sep 17 00:00:00 2001 From: Zhe Tao Date: Wed, 11 May 2022 14:36:40 +0000 Subject: [PATCH 1/8] Fail if the specified version is missing (#14). --- bazel_python.bzl | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/bazel_python.bzl b/bazel_python.bzl index ccde609..4a604be 100644 --- a/bazel_python.bzl +++ b/bazel_python.bzl @@ -78,17 +78,21 @@ def _bazel_python_venv_impl(ctx): only_warn = ctx.var.get("BAZEL_PYTHON_ONLY_WARN", "false").lower() == "true" if "BAZEL_PYTHON_DIR" not in ctx.var: if only_warn: - print("A bazel-python installation was not found. Falling back to the system python. For reproducibility, please run setup_python.sh for " + python_version) + print("Bazel build flag 'BAZEL_PYTHON_DIR' was not defined. Falling back to the system python. For reproducibility, please run setup_python.sh for " + python_version) use_system = True else: - fail("You must run setup_python.sh for " + python_version) + fail("Bazel build flag 'BAZEL_PYTHON_DIR' was not defined. You must run setup_python.sh for " + python_version) if use_system: python_dir = "" else: python_parent_dir = ctx.var.get("BAZEL_PYTHON_DIR") - python_dir = python_parent_dir + "/" + python_version + python_dir = ctx.path(python_parent_dir).dirname.get_child(python_version) + if not python_dir.exists: + fail("A bazel-python installation for {python_version} was not found in '{python_parent_dir}/{python_version}'. You must run setup_python.sh for {python_version}".format( + python_parent_dir=python_parent_dir + python_version=python_version + )) - # TODO: Fail if python_dir does not exist. venv_dir = ctx.actions.declare_directory("bazel_python_venv_installed") inputs = [] if use_system: From 684f1dbed53df3d2414619eae7fe510bd1aaf8cb Mon Sep 17 00:00:00 2001 From: Zhe Tao Date: Wed, 11 May 2022 15:01:30 +0000 Subject: [PATCH 2/8] Fix a typo (#14). --- bazel_python.bzl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bazel_python.bzl b/bazel_python.bzl index 4a604be..86050b0 100644 --- a/bazel_python.bzl +++ b/bazel_python.bzl @@ -89,7 +89,7 @@ def _bazel_python_venv_impl(ctx): python_dir = ctx.path(python_parent_dir).dirname.get_child(python_version) if not python_dir.exists: fail("A bazel-python installation for {python_version} was not found in '{python_parent_dir}/{python_version}'. You must run setup_python.sh for {python_version}".format( - python_parent_dir=python_parent_dir + python_parent_dir=python_parent_dir, python_version=python_version )) From d40300adf116692915419c6eeb6540e671fa776c Mon Sep 17 00:00:00 2001 From: Zhe Tao Date: Fri, 17 Feb 2023 01:30:48 +0000 Subject: [PATCH 3/8] Separate local and hermetic python rules. --- BUILD | 1 + bazel_python.bzl | 187 +++++++++++++++++++++++++-------- external/python3.9.BUILD | 220 +++++++++++++++++++++++++++++++++++++++ pywrapper.sh | 10 +- setup_python.sh | 6 +- 5 files changed, 373 insertions(+), 51 deletions(-) create mode 100644 external/python3.9.BUILD diff --git a/BUILD b/BUILD index 3ae07ae..f22fee6 100644 --- a/BUILD +++ b/BUILD @@ -2,6 +2,7 @@ exports_files([ "._dummy_.py", "pywrapper.sh", "coverage_report.sh", + "external/python3.9.BUILD" ]) sh_library( diff --git a/bazel_python.bzl b/bazel_python.bzl index 86050b0..818b554 100644 --- a/bazel_python.bzl +++ b/bazel_python.bzl @@ -1,17 +1,121 @@ load("@bazel_tools//tools/python:toolchain.bzl", "py_runtime_pair") +load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive", "http_file") -def bazel_python(venv_name = "bazel_python_venv"): +""" Using a local pre-installed Python. """ + +_LOCAL_PYTHON_REPO_BUILD=""" +cc_library( + name = "python", + srcs = glob(["lib/*.a"]), + hdrs = glob([ + "include/**/*.h", + "include/**/**/*.h", + ]), + includes = ["include/python3.9"], + visibility = ["//visibility:public"], +) + +exports_files(glob(["bin/**"])) +""" + +def _setup_local_python_repository_impl(repository_ctx): + BAZEL_PYTHON_DIR = repository_ctx.attr.dir \ + or repository_ctx.os.environ.get("BAZEL_PYTHON_DIR", None) + if BAZEL_PYTHON_DIR == None: + fail("Environment variable `BAZEL_PYTHON_DIR` is missing.") + + path = "{}/{}".format(BAZEL_PYTHON_DIR, repository_ctx.attr.python_version) + repository_ctx.symlink(path, "") + repository_ctx.file("BUILD", + content = _LOCAL_PYTHON_REPO_BUILD, + executable = False, + legacy_utf8 = True + ) + return BAZEL_PYTHON_DIR + +setup_local_python_repository = repository_rule( + implementation = _setup_local_python_repository_impl, + attrs = { + "python_version": attr.string(), + "dir": attr.string() + }, + local = True, +) + +def bazel_local_python( + python_version = "3.9.7", + name = "python", + venv_name = "bazel_python_venv" +): + """Workspace rule setting up bazel_python for a repository from a + local Python installation. + + Arguments + ========= + @python_version should be a Python version number in `major.minor.patch`. + @name is the repository name. + @venv_name should match the 'name' argument given to the + bazel_python_interpreter call in the BUILD file. + """ + native.register_toolchains("//:" + venv_name + "_toolchain") + setup_local_python_repository( + name=name, + python_version=python_version + ) + + +""" Build a hermetic Python. """ + +_known_python_archives_sha256 = { + 3: { + 9: { + 7: "a838d3f9360d157040142b715db34f0218e535333696a5569dc6f854604eb9d1" + } + } +} + +def bazel_hermetic_python( + python_version = "3.9.7", + name = "python", + venv_name = "bazel_python_venv" +): """Workspace rule setting up bazel_python for a repository. Arguments ========= + @python_version should be a Python version number in `major.minor.patch`. + @name is the repository name. @venv_name should match the 'name' argument given to the bazel_python_interpreter call in the BUILD file. """ native.register_toolchains("//:" + venv_name + "_toolchain") + print(""" + ========================================================================================= + Warning: build a hermetic Python is experimental. + + Know issues: + 1. "Value for scheme.headers does not match" due to the `bazel-out/host/bin` prefix. + 2. "[33/43] test_pprint -- test_pickle failed (6 errors)" during PGO. + 3. (Optional) `libsqlite3-dev` is required for Jupyter Notebook. + + TODOs: + 1. Build with tcmalloc. + ========================================================================================= + """) + py_major, py_minor, py_patch = [int(num) for num in python_version.split(".")] + http_archive( + name = name, + build_file = "@bazel_python//:external/python{major}.{minor}.BUILD".format( + major=py_major, minor=py_minor), + sha256 = _known_python_archives_sha256[py_major][py_minor][py_patch], + strip_prefix = "Python-{}".format(python_version), + urls = ["https://www.python.org/ftp/python/{0}/Python-{0}.tgz".format( + python_version)], + ) + + def bazel_python_interpreter( - python_version, name = "bazel_python_venv", requirements_file = None, **kwargs): @@ -19,7 +123,7 @@ def bazel_python_interpreter( Arguments ========= - @python_version should be the Python version string to use (e.g. 3.7.4 is + @python_version (deprecated) should be the Python version string to use (e.g. 3.7.4 is the standard for DARG projects). You must run the setup_python.sh script with this version number. @name is your preferred Bazel name for referencing this. The default should @@ -30,7 +134,6 @@ def bazel_python_interpreter( """ bazel_python_venv( name = name, - python_version = python_version, requirements_file = requirements_file, **kwargs ) @@ -73,70 +176,64 @@ def _bazel_python_venv_impl(ctx): Also installs requirements specified by @ctx.attr.requirements_file. """ - python_version = ctx.attr.python_version - use_system = False - only_warn = ctx.var.get("BAZEL_PYTHON_ONLY_WARN", "false").lower() == "true" - if "BAZEL_PYTHON_DIR" not in ctx.var: - if only_warn: - print("Bazel build flag 'BAZEL_PYTHON_DIR' was not defined. Falling back to the system python. For reproducibility, please run setup_python.sh for " + python_version) - use_system = True - else: - fail("Bazel build flag 'BAZEL_PYTHON_DIR' was not defined. You must run setup_python.sh for " + python_version) - if use_system: - python_dir = "" - else: - python_parent_dir = ctx.var.get("BAZEL_PYTHON_DIR") - python_dir = ctx.path(python_parent_dir).dirname.get_child(python_version) - if not python_dir.exists: - fail("A bazel-python installation for {python_version} was not found in '{python_parent_dir}/{python_version}'. You must run setup_python.sh for {python_version}".format( - python_parent_dir=python_parent_dir, - python_version=python_version - )) - + tool_inputs, tool_input_mfs = ctx.resolve_tools(tools = [ctx.attr.python]) venv_dir = ctx.actions.declare_directory("bazel_python_venv_installed") inputs = [] - if use_system: - command = "" - else: - command = """ - export PATH={py_dir}/bin:$PATH - export PATH={py_dir}/include:$PATH - export PATH={py_dir}/lib:$PATH - export PATH={py_dir}/share:$PATH - export PYTHON_PATH={py_dir}:{py_dir}/bin:{py_dir}/include:{py_dir}/lib:{py_dir}/share - """ - command += """ - python3 -m venv {out_dir} || exit 1 - source {out_dir}/bin/activate || exit 1 + + """ Setup venv. """ + command = """ + $(readlink -f {py_executable}) -m venv {venv_dir} || exit 1 + source {venv_dir}/bin/activate || exit 1 + export PATH=$PWD/{venv_dir}/bin:$PWD/{venv_dir}/include:$PWD/{venv_dir}/lib:$PWD/{venv_dir}/share:$PATH + export PYTHON_PATH=$PWD/{venv_dir}:$PWD/{venv_dir}/bin:$PWD/{venv_dir}/include:$PWD/{venv_dir}/lib:$PWD/{venv_dir}/share """ + + """ Install requirements.txt. """ if ctx.attr.requirements_file: - command += "pip3 install -r " + ctx.file.requirements_file.path + " || exit 1" + command += "python3 -m pip install -r " + ctx.file.requirements_file.path + " || exit 1" inputs.append(ctx.file.requirements_file) - for src in ctx.attr.run_after_pip_srcs: + + """ Include resources. """ + for src in ctx.attr.data: inputs.extend(src.files.to_list()) + + """ Append post-pip scripts. """ command += ctx.attr.run_after_pip + command += """ - REPLACEME=$PWD/'{out_dir}' + REPLACEME=$PWD/'{venv_dir}' REPLACEWITH='$PWD/bazel_python_venv_installed' # This prevents sed from trying to modify the directory. We may want to # do a more targeted sed in the future. - rm -rf {out_dir}/bin/__pycache__ || exit 1 - sed -i'' -e s:$REPLACEME:$REPLACEWITH:g {out_dir}/bin/* || exit 1 + rm -rf {venv_dir}/bin/__pycache__ || exit 1 + sed -i'' -e s:$REPLACEME:$REPLACEWITH:g {venv_dir}/bin/* || exit 1 """ + command = command.format( + py_executable = ctx.executable.python.path, + venv_dir = venv_dir.path + ) + # print(command) ctx.actions.run_shell( - command = command.format(py_dir = python_dir, out_dir = venv_dir.path), inputs = inputs, outputs = [venv_dir], + tools = tool_inputs, + command = command ) + return [DefaultInfo(files = depset([venv_dir]))] bazel_python_venv = rule( implementation = _bazel_python_venv_impl, attrs = { - "python_version": attr.string(), "requirements_file": attr.label(allow_single_file = True), "run_after_pip": attr.string(), - "run_after_pip_srcs": attr.label_list(allow_files = True), + "data": attr.label_list(allow_files = True), + "python": attr.label( + executable = True, + allow_single_file = True, + cfg = 'exec', + default = Label("@python//:bin/python3"), + ), }, ) diff --git a/external/python3.9.BUILD b/external/python3.9.BUILD new file mode 100644 index 0000000..19c3154 --- /dev/null +++ b/external/python3.9.BUILD @@ -0,0 +1,220 @@ +genrule( + name = "build_python", + srcs = glob(["**/**"]) + [ + "@local_config_cc//:toolchain", + ], + outs = [ + "bin/2to3", + "bin/2to3-3.9", + "bin/idle3", + "bin/idle3.9", + "bin/pip3", + "bin/pip3.9", + "bin/pydoc3", + "bin/pydoc3.9", + "bin/python3", + "bin/python3-config", + "bin/python3.9", + "bin/python3.9-config", + # + "lib/libpython3.9.a", + "lib/pkgconfig", + "lib/python3.9", + # + "include/python3.9/abstract.h", + "include/python3.9/asdl.h", + "include/python3.9/ast.h", + "include/python3.9/bitset.h", + "include/python3.9/bltinmodule.h", + "include/python3.9/boolobject.h", + "include/python3.9/bytearrayobject.h", + "include/python3.9/bytesobject.h", + "include/python3.9/cellobject.h", + "include/python3.9/ceval.h", + "include/python3.9/classobject.h", + "include/python3.9/code.h", + "include/python3.9/codecs.h", + "include/python3.9/compile.h", + "include/python3.9/complexobject.h", + "include/python3.9/context.h", + "include/python3.9/datetime.h", + "include/python3.9/descrobject.h", + "include/python3.9/dictobject.h", + "include/python3.9/dynamic_annotations.h", + "include/python3.9/enumobject.h", + "include/python3.9/errcode.h", + "include/python3.9/eval.h", + "include/python3.9/exports.h", + "include/python3.9/fileobject.h", + "include/python3.9/fileutils.h", + "include/python3.9/floatobject.h", + "include/python3.9/frameobject.h", + "include/python3.9/funcobject.h", + "include/python3.9/genericaliasobject.h", + "include/python3.9/genobject.h", + "include/python3.9/graminit.h", + "include/python3.9/grammar.h", + "include/python3.9/import.h", + "include/python3.9/interpreteridobject.h", + "include/python3.9/intrcheck.h", + "include/python3.9/iterobject.h", + "include/python3.9/listobject.h", + "include/python3.9/longintrepr.h", + "include/python3.9/longobject.h", + "include/python3.9/marshal.h", + "include/python3.9/memoryobject.h", + "include/python3.9/methodobject.h", + "include/python3.9/modsupport.h", + "include/python3.9/moduleobject.h", + "include/python3.9/namespaceobject.h", + "include/python3.9/node.h", + "include/python3.9/object.h", + "include/python3.9/objimpl.h", + "include/python3.9/odictobject.h", + "include/python3.9/opcode.h", + "include/python3.9/osdefs.h", + "include/python3.9/osmodule.h", + "include/python3.9/parsetok.h", + "include/python3.9/patchlevel.h", + "include/python3.9/picklebufobject.h", + "include/python3.9/py_curses.h", + "include/python3.9/pyarena.h", + "include/python3.9/pycapsule.h", + "include/python3.9/pyconfig.h", + "include/python3.9/pyctype.h", + "include/python3.9/pydebug.h", + "include/python3.9/pydtrace.h", + "include/python3.9/pyerrors.h", + "include/python3.9/pyexpat.h", + "include/python3.9/pyfpe.h", + "include/python3.9/pyframe.h", + "include/python3.9/pyhash.h", + "include/python3.9/pylifecycle.h", + "include/python3.9/pymacconfig.h", + "include/python3.9/pymacro.h", + "include/python3.9/pymath.h", + "include/python3.9/pymem.h", + "include/python3.9/pyport.h", + "include/python3.9/pystate.h", + "include/python3.9/pystrcmp.h", + "include/python3.9/pystrhex.h", + "include/python3.9/pystrtod.h", + "include/python3.9/Python-ast.h", + "include/python3.9/Python.h", + "include/python3.9/pythonrun.h", + "include/python3.9/pythread.h", + "include/python3.9/pytime.h", + "include/python3.9/rangeobject.h", + "include/python3.9/setobject.h", + "include/python3.9/sliceobject.h", + "include/python3.9/structmember.h", + "include/python3.9/structseq.h", + "include/python3.9/symtable.h", + "include/python3.9/sysmodule.h", + "include/python3.9/token.h", + "include/python3.9/traceback.h", + "include/python3.9/tracemalloc.h", + "include/python3.9/tupleobject.h", + "include/python3.9/typeslots.h", + "include/python3.9/ucnhash.h", + "include/python3.9/unicodeobject.h", + "include/python3.9/warnings.h", + "include/python3.9/weakrefobject.h", + "include/python3.9/cpython/abstract.h", + "include/python3.9/cpython/bytearrayobject.h", + "include/python3.9/cpython/bytesobject.h", + "include/python3.9/cpython/ceval.h", + "include/python3.9/cpython/code.h", + "include/python3.9/cpython/dictobject.h", + "include/python3.9/cpython/fileobject.h", + "include/python3.9/cpython/fileutils.h", + "include/python3.9/cpython/frameobject.h", + "include/python3.9/cpython/import.h", + "include/python3.9/cpython/initconfig.h", + "include/python3.9/cpython/interpreteridobject.h", + "include/python3.9/cpython/listobject.h", + "include/python3.9/cpython/methodobject.h", + "include/python3.9/cpython/object.h", + "include/python3.9/cpython/objimpl.h", + "include/python3.9/cpython/pyerrors.h", + "include/python3.9/cpython/pylifecycle.h", + "include/python3.9/cpython/pymem.h", + "include/python3.9/cpython/pystate.h", + "include/python3.9/cpython/sysmodule.h", + "include/python3.9/cpython/traceback.h", + "include/python3.9/cpython/tupleobject.h", + "include/python3.9/cpython/unicodeobject.h", + "include/python3.9/internal/pegen_interface.h", + "include/python3.9/internal/pycore_abstract.h", + "include/python3.9/internal/pycore_accu.h", + "include/python3.9/internal/pycore_atomic.h", + "include/python3.9/internal/pycore_bytes_methods.h", + "include/python3.9/internal/pycore_byteswap.h", + "include/python3.9/internal/pycore_call.h", + "include/python3.9/internal/pycore_ceval.h", + "include/python3.9/internal/pycore_code.h", + "include/python3.9/internal/pycore_condvar.h", + "include/python3.9/internal/pycore_context.h", + "include/python3.9/internal/pycore_dtoa.h", + "include/python3.9/internal/pycore_fileutils.h", + "include/python3.9/internal/pycore_gc.h", + "include/python3.9/internal/pycore_getopt.h", + "include/python3.9/internal/pycore_gil.h", + "include/python3.9/internal/pycore_hamt.h", + "include/python3.9/internal/pycore_hashtable.h", + "include/python3.9/internal/pycore_import.h", + "include/python3.9/internal/pycore_initconfig.h", + "include/python3.9/internal/pycore_interp.h", + "include/python3.9/internal/pycore_object.h", + "include/python3.9/internal/pycore_pathconfig.h", + "include/python3.9/internal/pycore_pyerrors.h", + "include/python3.9/internal/pycore_pyhash.h", + "include/python3.9/internal/pycore_pylifecycle.h", + "include/python3.9/internal/pycore_pymem.h", + "include/python3.9/internal/pycore_pystate.h", + "include/python3.9/internal/pycore_runtime.h", + "include/python3.9/internal/pycore_sysmodule.h", + "include/python3.9/internal/pycore_traceback.h", + "include/python3.9/internal/pycore_tupleobject.h", + "include/python3.9/internal/pycore_warnings.h", + ], + cmd = """ + OUT_DIR=$$PWD/$(@D) + rm -rf $$OUT_DIR + mkdir $$OUT_DIR + # + cd external/python + INSTALL_DIR=$$PWD/out + # + ./configure \ + --prefix=$$OUT_DIR \ + --exec_prefix=$$OUT_DIR \ + --enable-loadable-sqlite-extensions \ + --enable-optimizations + make && make install + # + if ! $$OUT_DIR/bin/python3 -c "import ssl" + then + echo "ERROR: Python was built *WITHOUT* the SSL module. This will break PyPI downloads. Please re-run this script after installing libssl-dev (see the README)." + exit 1 + fi + if ! $$OUT_DIR/bin/python3 -c "import zlib" + then + echo "ERROR: Python was built *WITHOUT* the zlib module. If needed, please re-run this script after installing zlib1g-dev (see the README)." + exit 1 + fi + """, + visibility = ["//visibility:public"], +) + +cc_library( + name = "python", + srcs = glob(["lib/*.a"]) + [":build_python"], + hdrs = glob([ + "include/**/*.h", + "include/**/**/*.h", + ]), + includes = ["include/python3.9"], + linkstatic = 1, + visibility = ["//visibility:public"], +) diff --git a/pywrapper.sh b/pywrapper.sh index 647a487..d66bf0a 100755 --- a/pywrapper.sh +++ b/pywrapper.sh @@ -6,8 +6,12 @@ # bazel_out/.../[mainworkspace]. This searches for the first matching path then # exits, so it should be reasonably fast in most cases. # https://unix.stackexchange.com/questions/68414/only-find-first-few-matched-files-using-find -venv_path=$((find -L . -path "*/bazel_python_venv_installed/bin/activate" & ) | head -n 1) +venv_activate=$((find -L . -path "*/bazel_python_venv_installed/bin/activate" & ) | head -n 1) +venv_path=$(dirname $(dirname $venv_activate)) # If venv_path was not found it will be empty and the below will throw an # error, alerting Bazel something went wrong. -source $venv_path || exit 1 -python $@ +source $venv_activate || exit 1 +export PATH=$venv_path/bin:$venv_path/include:$venv_path/lib:$venv_path/share:$PATH +export PYTHON_PATH=$venv_path:$venv_path/bin:$venv_path/include:$venv_path/lib:$venv_path/share + +$venv_path/bin/python3 $@ diff --git a/setup_python.sh b/setup_python.sh index e05c139..d97ea72 100755 --- a/setup_python.sh +++ b/setup_python.sh @@ -25,10 +25,10 @@ then tar -xzf Python-$version.tgz cd Python-$version - ./configure --prefix=$install_dir $@ + ./configure --prefix=$install_dir --enable-optimizations $@ - make -j - make install + make -j64 + make install -j64 cd $install_dir rm -rf Python-$version rm -rf Python-$version.tgz From 61dd97154ff3fdb6225a6df80fe5cee41f1cf6df Mon Sep 17 00:00:00 2001 From: Zhe Tao Date: Fri, 17 Feb 2023 01:49:04 +0000 Subject: [PATCH 4/8] Checked in my setup_python.sh by mistake. --- setup_python.sh | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/setup_python.sh b/setup_python.sh index d97ea72..5c5ddb9 100755 --- a/setup_python.sh +++ b/setup_python.sh @@ -25,10 +25,10 @@ then tar -xzf Python-$version.tgz cd Python-$version - ./configure --prefix=$install_dir --enable-optimizations $@ + ./configure --prefix=$install_dir $@ - make -j64 - make install -j64 + make -j + make install cd $install_dir rm -rf Python-$version rm -rf Python-$version.tgz @@ -49,4 +49,4 @@ then fi else echo "Aborting." -fi +fi \ No newline at end of file From 0c4fdb6e89ec96c34bec006fd2beff27572485b2 Mon Sep 17 00:00:00 2001 From: Zhe Tao Date: Tue, 21 Feb 2023 08:04:26 +0000 Subject: [PATCH 5/8] Enable -fPIC when building Python. --- external/python3.9.BUILD | 6 ++++-- setup_python.sh | 4 ++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/external/python3.9.BUILD b/external/python3.9.BUILD index 19c3154..974ce6c 100644 --- a/external/python3.9.BUILD +++ b/external/python3.9.BUILD @@ -186,11 +186,13 @@ genrule( cd external/python INSTALL_DIR=$$PWD/out # - ./configure \ + CFLAGS=-fPIC ./configure \ --prefix=$$OUT_DIR \ --exec_prefix=$$OUT_DIR \ --enable-loadable-sqlite-extensions \ - --enable-optimizations + --enable-optimizations \ + --enable-shared=no + make && make install # if ! $$OUT_DIR/bin/python3 -c "import ssl" diff --git a/setup_python.sh b/setup_python.sh index 5c5ddb9..b350012 100755 --- a/setup_python.sh +++ b/setup_python.sh @@ -25,9 +25,9 @@ then tar -xzf Python-$version.tgz cd Python-$version - ./configure --prefix=$install_dir $@ + CFLAGS=-fPIC ./configure --prefix=$install_dir $@ - make -j + make make install cd $install_dir rm -rf Python-$version From df784b68586e2cf13ab513c3765bc6d8d7e4d355 Mon Sep 17 00:00:00 2001 From: Zhe Tao Date: Tue, 21 Feb 2023 08:05:34 +0000 Subject: [PATCH 6/8] Workaround for pywrapper.sh --- pywrapper.sh | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/pywrapper.sh b/pywrapper.sh index d66bf0a..2ec3398 100755 --- a/pywrapper.sh +++ b/pywrapper.sh @@ -6,12 +6,17 @@ # bazel_out/.../[mainworkspace]. This searches for the first matching path then # exits, so it should be reasonably fast in most cases. # https://unix.stackexchange.com/questions/68414/only-find-first-few-matched-files-using-find -venv_activate=$((find -L . -path "*/bazel_python_venv_installed/bin/activate" & ) | head -n 1) +venv_activate=$((find -L $PWD -path "*/bazel_python_venv_installed/bin/activate" & ) | head -n 1) venv_path=$(dirname $(dirname $venv_activate)) # If venv_path was not found it will be empty and the below will throw an # error, alerting Bazel something went wrong. source $venv_activate || exit 1 -export PATH=$venv_path/bin:$venv_path/include:$venv_path/lib:$venv_path/share:$PATH -export PYTHON_PATH=$venv_path:$venv_path/bin:$venv_path/include:$venv_path/lib:$venv_path/share + +# Not sure should we expose $PATH or not. +export PATH=$venv_path/bin:$PATH #:$venv_path/include:$venv_path/lib:$venv_path/share:$PATH +export LD_LIBRARY_PATH=$venv_path/lib #:${LD_LIBRARY_PATH} + +# export PYTHONPATH=$venv_path:$venv_path/bin:$venv_path/include:$venv_path/lib:$venv_path/share +export PYTHONPATH=$(/usr/bin/dirname $1):$PYTHONPATH $venv_path/bin/python3 $@ From 75b57c6ca79fda67321569e0a6d559732ab251bc Mon Sep 17 00:00:00 2001 From: Zhe Tao Date: Tue, 21 Feb 2023 08:06:12 +0000 Subject: [PATCH 7/8] Improve bazel_python. --- BUILD | 3 +- bazel_python.bzl | 100 ++++++++++++++++++++++++++++++++++++++++++----- 2 files changed, 91 insertions(+), 12 deletions(-) diff --git a/BUILD b/BUILD index f22fee6..db34e8d 100644 --- a/BUILD +++ b/BUILD @@ -2,8 +2,7 @@ exports_files([ "._dummy_.py", "pywrapper.sh", "coverage_report.sh", - "external/python3.9.BUILD" -]) +] + glob(["external/*.BUILD"])) sh_library( name = "pywrapper", diff --git a/bazel_python.bzl b/bazel_python.bzl index 818b554..1a634c8 100644 --- a/bazel_python.bzl +++ b/bazel_python.bzl @@ -94,10 +94,9 @@ def bazel_hermetic_python( ========================================================================================= Warning: build a hermetic Python is experimental. - Know issues: - 1. "Value for scheme.headers does not match" due to the `bazel-out/host/bin` prefix. - 2. "[33/43] test_pprint -- test_pickle failed (6 errors)" during PGO. - 3. (Optional) `libsqlite3-dev` is required for Jupyter Notebook. + Known issues: + 1. "[33/43] test_pprint -- test_pickle failed (6 errors)" during PGO. + 2. (Optional) `libsqlite3-dev` is required for Jupyter Notebook. TODOs: 1. Build with tcmalloc. @@ -114,7 +113,6 @@ def bazel_hermetic_python( python_version)], ) - def bazel_python_interpreter( name = "bazel_python_venv", requirements_file = None, @@ -171,6 +169,29 @@ def bazel_python_interpreter( toolchain_type = "@bazel_tools//tools/python:toolchain_type", ) +# def _bazel_python_install_external(ctx, srcs): +# outs = [] +# for src in srcs: +# for file in src.files.to_list(): +# out = ctx.actions.declare_file("{dir}/{name}".format(dir=bazel_python_external_bin, name=file.basename)) +# outs.append(out) +# print(out.path) +# ctx.actions.symlink(output=out, target_file=file) + +# return outs + +def _resolve_external_index(ctx, index_file_path, srcs): + + files = [f for src in srcs for f in src.files.to_list()] + + index = ctx.actions.declare_file(index_file_path) + ctx.actions.write( + output=index, + content="\n".join([f.path for f in files]) + ) + + return index, files + def _bazel_python_venv_impl(ctx): """A Bazel rule to set up a Python virtual environment. @@ -178,19 +199,71 @@ def _bazel_python_venv_impl(ctx): """ tool_inputs, tool_input_mfs = ctx.resolve_tools(tools = [ctx.attr.python]) venv_dir = ctx.actions.declare_directory("bazel_python_venv_installed") + inputs = [] + inputs_setup = [ f for s in ctx.attr.setups for f in s.files.to_list() ] + index_setup = ctx.actions.declare_file("_external_setup_to_install") + ctx.actions.write( + output=index_setup, + content="\n".join([ s.label.workspace_root for s in ctx.attr.setups ]) + ) + + inputs.append(index_setup) + inputs.extend(inputs_setup) + + index_bin, inputs_bin = _resolve_external_index(ctx, "_external_bin_to_install", ctx.attr.bins) + + inputs.append(index_bin) + inputs.extend(inputs_bin) + + index_lib, inputs_lib = _resolve_external_index(ctx, "_external_lib_to_install", ctx.attr.libs) + + inputs.append(index_lib) + inputs.extend(inputs_lib) + """ Setup venv. """ command = """ + set -e + $(readlink -f {py_executable}) -m venv {venv_dir} || exit 1 source {venv_dir}/bin/activate || exit 1 + export venv_path={venv_dir} + export bazel_bin_path={bazel_bin_dir} export PATH=$PWD/{venv_dir}/bin:$PWD/{venv_dir}/include:$PWD/{venv_dir}/lib:$PWD/{venv_dir}/share:$PATH - export PYTHON_PATH=$PWD/{venv_dir}:$PWD/{venv_dir}/bin:$PWD/{venv_dir}/include:$PWD/{venv_dir}/lib:$PWD/{venv_dir}/share + export LD_LIBRARY_PATH=$PWD/{venv_dir}/lib:$LD_LIBRARY_PATH + export PYTHONPATH=$PWD/{venv_dir}:$PWD/{venv_dir}/bin:$PWD/{venv_dir}/include:$PWD/{venv_dir}/lib:$PWD/{venv_dir}/share + + python3 -m pip install --upgrade pip + + for _external_bin_src in $(cat {index_bin}) + do + echo $_external_bin_src + ln -sfn $(readlink $_external_bin_src) $PWD/{venv_dir}/bin + done + rm {index_bin} + + for _external_lib_src in $(cat {index_lib}) + do + echo $_external_lib_src + ln -sfn $(readlink $_external_lib_src) $PWD/{venv_dir}/lib + done + rm {index_lib} + + for _external_setup_prefix in $(cat {index_setup}) + do + _external_setup_file=$(find . -path "*/$_external_setup_prefix/*/setup.py" | head -n 1) + pushd $(dirname $_external_setup_file) + python3 ./setup.py install || exit 1 + popd + done + rm {index_setup} + """ """ Install requirements.txt. """ if ctx.attr.requirements_file: - command += "python3 -m pip install -r " + ctx.file.requirements_file.path + " || exit 1" + command += "pip3 install -r " + ctx.file.requirements_file.path + " || exit 1" inputs.append(ctx.file.requirements_file) """ Include resources. """ @@ -210,9 +283,13 @@ def _bazel_python_venv_impl(ctx): """ command = command.format( py_executable = ctx.executable.python.path, - venv_dir = venv_dir.path + venv_dir = venv_dir.path, + bazel_bin_dir = ctx.bin_dir.path, + index_bin = index_bin.path, + index_lib = index_lib.path, + index_setup = index_setup.path, ) - # print(command) + ctx.actions.run_shell( inputs = inputs, outputs = [venv_dir], @@ -226,8 +303,11 @@ bazel_python_venv = rule( implementation = _bazel_python_venv_impl, attrs = { "requirements_file": attr.label(allow_single_file = True), - "run_after_pip": attr.string(), + "bins": attr.label_list(allow_files = True), + "libs": attr.label_list(allow_files = True), + "setups": attr.label_list(allow_files = True), "data": attr.label_list(allow_files = True), + "run_after_pip": attr.string(), "python": attr.label( executable = True, allow_single_file = True, From 420588c2005f34d14cc9b35ff69b12799606d13f Mon Sep 17 00:00:00 2001 From: Zhe Tao Date: Tue, 21 Feb 2023 08:06:25 +0000 Subject: [PATCH 8/8] Adding pybind11 support (WIP.) --- bazel_pybind11.bzl | 138 ++++++++++++++++++++++++++++++++++++++++ external/pybind11.BUILD | 62 ++++++++++++++++++ 2 files changed, 200 insertions(+) create mode 100644 bazel_pybind11.bzl create mode 100644 external/pybind11.BUILD diff --git a/bazel_pybind11.bzl b/bazel_pybind11.bzl new file mode 100644 index 0000000..17c8e39 --- /dev/null +++ b/bazel_pybind11.bzl @@ -0,0 +1,138 @@ + +def bazel_pybind11( + name = "pybind11", + build_file = "@pybind11_bazel//:pybind11.BUILD", + sha256 = "c9375b7453bef1ba0106849c83881e6b6882d892c9fae5b2572a2192100ffb8a", + strip_prefix = "pybind11-a54eab92d265337996b8e4b4149d9176c2d428a6", + urls = ["https://github.com/pybind/pybind11/archive/a54eab92d265337996b8e4b4149d9176c2d428a6.tar.gz"], +): + native.http_archive( + name = name, + build_file = build_file, + sha256 = sha256, + strip_prefix = strip_prefix, + urls = urls, + ) + +# https://github.com/pybind/pybind11_bazel/blob/master/build_defs.bzl +# Copyright (c) 2019 The Pybind Development Team. All rights reserved. +# +# All rights reserved. Use of this source code is governed by a +# BSD-style license that can be found in the LICENSE file. + +"""Build rules for pybind11.""" + +def register_extension_info(**kwargs): + pass + +PYBIND_COPTS = select({ + "@pybind11//:msvc_compiler": [], + "//conditions:default": [ + "-fexceptions", + ], +}) + +PYBIND_FEATURES = [ + "-use_header_modules", # Required for pybind11. + "-parse_headers", +] + +PYBIND_DEPS = [ + "@pybind11", + # "@local_config_python//:python_headers", + # "@python//:python", + "@python", +] + +# Builds a Python extension module using pybind11. +# This can be directly used in python with the import statement. +# This adds rules for a .so binary file. +def pybind_extension( + name, + copts = [], + features = [], + linkopts = [], + tags = [], + deps = [], + **kwargs): + # Mark common dependencies as required for build_cleaner. + tags = tags + ["req_dep=%s" % dep for dep in PYBIND_DEPS] + + native.cc_binary( + name = name + ".so", + copts = copts + PYBIND_COPTS + select({ + "@pybind11//:msvc_compiler": [], + "//conditions:default": [ + "-fvisibility=hidden", + ], + }), + features = features + PYBIND_FEATURES, + linkopts = linkopts + select({ + "@pybind11//:msvc_compiler": [], + "@pybind11//:osx": [], + "//conditions:default": ["-Wl,-Bsymbolic"], + }), + linkshared = 1, + tags = tags, + deps = deps + PYBIND_DEPS, + **kwargs + ) + +# Builds a pybind11 compatible library. This can be linked to a pybind_extension. +def pybind_library( + name, + copts = [], + features = [], + tags = [], + deps = [], + **kwargs): + # Mark common dependencies as required for build_cleaner. + tags = tags + ["req_dep=%s" % dep for dep in PYBIND_DEPS] + + native.cc_library( + name = name, + copts = copts + PYBIND_COPTS, + features = features + PYBIND_FEATURES, + tags = tags, + deps = deps + PYBIND_DEPS, + **kwargs + ) + +# # Builds a C++ test for a pybind_library. +# def pybind_library_test( +# name, +# copts = [], +# features = [], +# tags = [], +# deps = [], +# **kwargs): +# # Mark common dependencies as required for build_cleaner. +# tags = tags + ["req_dep=%s" % dep for dep in PYBIND_DEPS] + +# native.cc_test( +# name = name, +# copts = copts + PYBIND_COPTS, +# features = features + PYBIND_FEATURES, +# tags = tags, +# deps = deps + PYBIND_DEPS + [ +# "//util/python:python_impl", +# "//util/python:test_main", +# ], +# **kwargs +# ) + +# Register extension with build_cleaner. +register_extension_info( + extension = pybind_extension, + label_regex_for_dep = "{extension_name}", +) + +register_extension_info( + extension = pybind_library, + label_regex_for_dep = "{extension_name}", +) + +# register_extension_info( +# extension = pybind_library_test, +# label_regex_for_dep = "{extension_name}", +# ) diff --git a/external/pybind11.BUILD b/external/pybind11.BUILD new file mode 100644 index 0000000..65fe8a2 --- /dev/null +++ b/external/pybind11.BUILD @@ -0,0 +1,62 @@ +# pybind11 - Seamless operability between C++11 and Python. +load("@rules_cc//cc:defs.bzl", "cc_library") + +package(default_visibility = ["//visibility:public"]) + +licenses(["notice"]) + +exports_files(["LICENSE"]) + +OPTIONS = select({ + ":msvc_compiler": [], + "//conditions:default": [ + "-fexceptions", + # Useless warnings + "-Xclang-only=-Wno-undefined-inline", + "-Xclang-only=-Wno-pragma-once-outside-header", + "-Xgcc-only=-Wno-error", # no way to just disable the pragma-once warning in gcc + ], +}) + +INCLUDES = [ + "include/pybind11/**/*.h", +] + +EXCLUDES = [ + # Deprecated file that just emits a warning + "include/pybind11/common.h", +] + +cc_library( + name = "pybind11", + hdrs = glob( + INCLUDES, + exclude = EXCLUDES, + ), + copts = OPTIONS, + includes = ["include"], + # deps = ["@local_config_python//:python_headers"], + deps = ["@python"], +) + +cc_library( + name = "pybind11_embed", + hdrs = glob( + INCLUDES, + exclude = EXCLUDES, + ), + copts = OPTIONS, + includes = ["include"], + # deps = ["@local_config_python//:python_embed"], + deps = ["@python"], +) + +config_setting( + name = "msvc_compiler", + flag_values = {"@bazel_tools//tools/cpp:compiler": "msvc-cl"}, +) + +config_setting( + name = "osx", + constraint_values = ["@platforms//os:osx"], +)