From 74261a51d2364f9a99da17cfa9b796b12830fc01 Mon Sep 17 00:00:00 2001 From: Sebastian Pietras Date: Sat, 15 Aug 2020 22:42:33 +0200 Subject: [PATCH 1/2] Changed rules to work on Windows and exposed interpreter --- pyenv/defs.bzl | 170 ++++++++++++++++++++++++++++++++++--------------- 1 file changed, 118 insertions(+), 52 deletions(-) diff --git a/pyenv/defs.bzl b/pyenv/defs.bzl index e9e382f..aa21859 100644 --- a/pyenv/defs.bzl +++ b/pyenv/defs.bzl @@ -1,21 +1,51 @@ load("@bazel_tools//tools/build_defs/repo:utils.bzl", "maybe") +DEFAULT_REPOSITORY_NAME = "pyenv" +PY2_DEFAULT_DIR_NAME = "py2" +PY3_DEFAULT_DIR_NAME = "py3" +INTERPRETER_DEFAULT_NAME = "python" + +CONFIGURATIONS = { + "windows": { + "pyenv_dir": "bin", + "executable": "pyenv.bat", + "versions_dir": "versions", + "options": ["-q"], + "interpreter_dir": "", + "interpreter": "python.exe" + }, + "unix": { + "pyenv_dir": "bin", + "executable": "pyenv", + "versions_dir": "versions", + "options": [], + "interpreter_dir": "bin", + "interpreter": "python" + } +} + BUILD_FILE_CONTENT = """# This file was automatically generated by @dpu_rules_pyenv//pyenv:defs.bzl package(default_visibility = ["//visibility:public"]) load("@bazel_tools//tools/python:toolchain.bzl", "py_runtime_pair") +# export interpreter so it can accessed as a regular file +exports_files([ + "{py2_dir_link}/{interpreter}", + "{py3_dir_link}/{interpreter}" +]) + py_runtime( name = "python_{py2}_runtime", files = glob(["versions/{py2}/**/*"], exclude_directories = 0), - interpreter = "versions/{py2}/bin/python", + interpreter = "versions/{py2}/{interpreter_path}", python_version = "PY2" ) py_runtime( name = "python_{py3}_runtime", files = glob(["versions/{py3}/**/*"], exclude_directories = 0), - interpreter = "versions/{py3}/bin/python", + interpreter = "versions/{py3}/{interpreter_path}", python_version = "PY3" ) @@ -32,27 +62,30 @@ toolchain( ) """ +def _is_windows(repository_ctx): + """Returns true when os is recognized as windows + Args: + repository_ctx: The repository rule context + """ + os_family = repository_ctx.os.name.lower() + return os_family.find("windows") != -1 + +def _get_config(repository_ctx): + return CONFIGURATIONS["windows"] if _is_windows(repository_ctx) else CONFIGURATIONS["unix"] + def _download_pyenv(repository_ctx): """Used to download a pyenv version. Args: repository_ctx: The repository rule context """ - pyenv_version = repository_ctx.attr._pyenv_version - pyenv_repositories = repository_ctx.attr._pyenv_repositories - os_family = "unix" if repository_ctx.os.name.lower().find("windows") == -1 else "windows" - version_os = "%s-%s" % (pyenv_version, os_family) - - if version_os in pyenv_repositories: - url, strip_prefix, sha256 = pyenv_repositories[version_os] - else: - fail("Unknown pyenv version %s" % pyenv_version) + pyenv_repo = repository_ctx.attr.pyenv_win_repo if _is_windows(repository_ctx) else repository_ctx.attr.pyenv_repo repository_ctx.download_and_extract( - url = [url], + url = pyenv_repo["url"], output = "./", - stripPrefix = strip_prefix, - sha256 = sha256, + stripPrefix = pyenv_repo["strip_prefix"], + sha256 = pyenv_repo["sha256"], ) def _setup_pyenv(repository_ctx): @@ -70,73 +103,106 @@ def _setup_pyenv(repository_ctx): if not pyenv_root: fail("Unable to find PYENV_ROOT") - repository_ctx.symlink(pyenv_path, "bin/pyenv") - repository_ctx.symlink("{pyenv_root}/versions".format(pyenv_root = pyenv_root), "versions") + pyenv_dir = _get_config(repository_ctx)["pyenv_dir"] + executable = _get_config(repository_ctx)["executable"] + versions_dir = _get_config(repository_ctx)["versions_dir"] + + repository_ctx.symlink(pyenv_path, + "{}/{}".format(pyenv_dir, executable)) + repository_ctx.symlink("{}/{}".format(pyenv_root, versions_dir), + versions_dir) def _install_python(repository_ctx, version): if not version.startswith("2") and not version.startswith("3"): fail("pyenv_install currently only supports cpython major versions 2 and 3") repository_ctx.report_progress("Installing Python %s" % version) - pyenv_root = repository_ctx.path("./bin/pyenv").realpath.dirname.dirname - res = repository_ctx.execute(["bin/pyenv", "install", "-s", version], environment = {"PYENV_ROOT": str(pyenv_root)}) + + pyenv_dir = _get_config(repository_ctx)["pyenv_dir"] + executable = _get_config(repository_ctx)["executable"] + options = _get_config(repository_ctx)["options"] + + pyenv_path = "./{}/{}".format(pyenv_dir, executable) + pyenv_root = repository_ctx.path(pyenv_path).realpath.dirname.dirname + # install in current directory, so intepreter can be accessed by "versions/..." + res = repository_ctx.execute(["bin/{}".format(executable), "install"] + options + [version], + environment = {"PYENV_ROOT": str(pyenv_root)}) if res.return_code: fail("pyenv failed to install version %s" % version + res.stdout + res.stderr) -def _setup_build_file(repository_ctx, py2, py3): +def _setup_links(repository_ctx, version, dir_name): + interpreter_dir = _get_config(repository_ctx)["interpreter_dir"] + versions_dir = _get_config(repository_ctx)["versions_dir"] + bindir = "{}/{}".format(versions_dir, version) + ("/{}".format(interpreter_dir) if interpreter_dir else "") + + # create symlink to bin directory for easier access by users + repository_ctx.symlink(bindir, dir_name) + + # on windows interpreter is 'python.exe', so create a symlink to it and name it 'python', so it can be accessed the same way on windows and unix + interpreter = _get_config(repository_ctx)["interpreter"] + if interpreter != INTERPRETER_DEFAULT_NAME: + repository_ctx.symlink("{}/{}".format(bindir, interpreter), "{}/{}".format(bindir, INTERPRETER_DEFAULT_NAME)) + +def _setup_build_files(repository_ctx, py2, py3, py2_dir, py3_dir): + interpreter_dir = _get_config(repository_ctx)["interpreter_dir"] + interpreter_path = "{}/{}".format(interpreter_dir, INTERPRETER_DEFAULT_NAME) if interpreter_dir else INTERPRETER_DEFAULT_NAME repository_ctx.file( - "BUILD.bazel", - content = BUILD_FILE_CONTENT.format(py2 = py2, py3 = py3), + "BUILD", + content = BUILD_FILE_CONTENT.format(py2 = py2, + py3 = py3, + py2_dir_link = py2_dir, + py3_dir_link = py3_dir, + interpreter = INTERPRETER_DEFAULT_NAME, + interpreter_path = interpreter_path) ) def _pyenv_install_impl(repository_ctx): - py2 = repository_ctx.attr.py2_version - py3 = repository_ctx.attr.py3_version + py2 = repository_ctx.attr.py2 + py3 = repository_ctx.attr.py3 + py2_dir = repository_ctx.attr.py2_dir + py3_dir = repository_ctx.attr.py3_dir _setup_pyenv(repository_ctx) _install_python(repository_ctx, py2) _install_python(repository_ctx, py3) - _setup_build_file(repository_ctx, py2, py3) + _setup_links(repository_ctx, py2, py2_dir) + _setup_links(repository_ctx, py3, py3_dir) + _setup_build_files(repository_ctx, py2, py3, py2_dir, py3_dir) _pyenv_install = repository_rule( _pyenv_install_impl, attrs = { - "py2_version": attr.string(mandatory = True), - "py3_version": attr.string(mandatory = True), - "hermetic": attr.bool(mandatory = True), - # NOTE: Users may care about these private attributes at some point - "_pyenv_version": attr.string( - default = "1.2", - ), - "_pyenv_repositories": attr.string_list_dict( - default = { - "1.2-unix": ( - "https://github.com/pyenv/pyenv/archive/v1.2.18.tar.gz", - "pyenv-1.2.18", - "cc147f020178bb2f1ce0a8b9acb0bdf73979d967ce7d7415e22746e84e0eec7a", - ), - "1.2-windows": ( - "https://github.com/pyenv-win/pyenv-win/archive/v1.2.4.tar.gz", - "pyenv-win-1.2.4", - "0f3d3851eb692335c443a54700ffd99f0066f51c3b94ab0a867174c83f749554", - ), - }, - ), - }, + "py2": attr.string(mandatory = True, doc = "exact version of python2"), + "py3": attr.string(mandatory = True, doc = "exact version of python3"), + "py2_dir": attr.string(mandatory = True, doc = "directory for python2"), + "py3_dir": attr.string(mandatory = True, doc = "directory for python3"), + "hermetic": attr.bool(default = True, doc = "True if pyenv should be downloaded, False if local pyenv should be used"), + "pyenv_repo": attr.string_dict(default = { + "url": "https://github.com/pyenv/pyenv/archive/v1.2.18.tar.gz", + "strip_prefix": "pyenv-1.2.18", + "sha256": "cc147f020178bb2f1ce0a8b9acb0bdf73979d967ce7d7415e22746e84e0eec7a" + }, doc = "unix pyenv repository"), + "pyenv_win_repo": attr.string_dict(default = { + "url": "https://github.com/pyenv-win/pyenv-win/archive/v2.64.3.tar.gz", + "strip_prefix": "pyenv-win-2.64.3/pyenv-win", + "sha256": "9894fed264fb29e4aaff29728bea3c6f1c2d0106128fa8bbf17949f722d510d9" + }, doc = "windows pyenv repository") + } ) -def pyenv_install(py2, py3, hermetic = True): +def pyenv_install(py2, py3, py2_dir = PY2_DEFAULT_DIR_NAME, py3_dir = PY3_DEFAULT_DIR_NAME, name = DEFAULT_REPOSITORY_NAME, **kwargs): """ Macro to install and register a py_runtime_pair. """ maybe( _pyenv_install, - name = "pyenv", - py2_version = py2, - py3_version = py3, - hermetic = hermetic, + name = name, + py2 = py2, + py3 = py3, + py2_dir = py2_dir, + py3_dir = py3_dir ) - native.register_toolchains("@pyenv//:python_toolchain") + native.register_toolchains("@{}//:python_toolchain".format(name)) From e740e225875a7e9ea06ebf2f0711253af137d72a Mon Sep 17 00:00:00 2001 From: Sebastian Pietras Date: Sat, 15 Aug 2020 22:50:22 +0200 Subject: [PATCH 2/2] Updated README --- README.md | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 2788873..7f8c001 100644 --- a/README.md +++ b/README.md @@ -55,11 +55,24 @@ pyenv_install( ) ``` +## Windows + +If you want to use ```rules_pyenv``` on Windows you should [enable symlinks](https://docs.bazel.build/versions/master/windows.html#enable-symlink-support) + +## Using the interpreter in repository rules + +The interpreters are exposed as files, so you can refer to them in a repository rule. + +You can refer to the ```python2``` interpreter as ```@pyenv//:py2/python``` and to the ```python3``` interpreter as ```@pyenv//:py3/python```. + +## Build problems + +```pyenv``` is building Python from sources, so you need all things necessary to build Python. If you have any problems regarding this, you should see [this page](https://github.com/pyenv/pyenv/wiki/common-build-problems) first. + ## Caveats - This thing needs tests - This thing needs build automation -- This thing hasn't been tried on Windows (yet); hence tests and build automation - You will probably find bugs. No, you're not crazy, it's just not working right; open an issue (PRs are welcome too) - Since the native Bazel rules only support CPython your use of `pyenv` is restricted to CPython versions 2 and 3. If you want additional support for other Python impl