blob: 395ef0e82cf75e6606f4e155613f0035700900b9 [file] [log] [blame]
# Copyright 2022 The Bazel 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.
"""This file contains macros to be called during WORKSPACE evaluation.
For historic reasons, pip_repositories() is defined in //python:pip.bzl.
"""
load("//python/private:toolchains_repo.bzl", "resolved_interpreter_os_alias", "toolchains_repo")
load(
":versions.bzl",
"DEFAULT_RELEASE_BASE_URL",
"MINOR_MAPPING",
"PLATFORMS",
"TOOL_VERSIONS",
"get_release_url",
)
def py_repositories():
# buildifier: disable=print
print("py_repositories is a no-op and is deprecated. You can remove this from your WORKSPACE file")
########
# Remaining content of the file is only used to support toolchains.
########
def _python_repository_impl(rctx):
if rctx.attr.distutils and rctx.attr.distutils_content:
fail("Only one of (distutils, distutils_content) should be set.")
platform = rctx.attr.platform
python_version = rctx.attr.python_version
python_short_version = python_version.rpartition(".")[0]
release_filename = rctx.attr.release_filename
url = rctx.attr.url
if release_filename.endswith(".zst"):
rctx.download(
url = url,
sha256 = rctx.attr.sha256,
output = release_filename,
)
unzstd = rctx.which("unzstd")
if not unzstd:
url = rctx.attr.zstd_url.format(version = rctx.attr.zstd_version)
rctx.download_and_extract(
url = url,
sha256 = rctx.attr.zstd_sha256,
)
working_directory = "zstd-{version}".format(version = rctx.attr.zstd_version)
rctx.execute(
["make", "--jobs=4"],
timeout = 600,
quiet = True,
working_directory = working_directory,
)
zstd = "{working_directory}/zstd".format(working_directory = working_directory)
unzstd = "./unzstd"
rctx.symlink(zstd, unzstd)
exec_result = rctx.execute([
"tar",
"--extract",
"--strip-components=2",
"--use-compress-program={unzstd}".format(unzstd = unzstd),
"--file={}".format(release_filename),
])
if exec_result.return_code:
fail(exec_result.stderr)
else:
rctx.download_and_extract(
url = url,
sha256 = rctx.attr.sha256,
stripPrefix = rctx.attr.strip_prefix,
)
# Write distutils.cfg to the Python installation.
if "windows" in rctx.os.name:
distutils_path = "Lib/distutils/distutils.cfg"
else:
distutils_path = "lib/python{}/distutils/distutils.cfg".format(python_short_version)
if rctx.attr.distutils:
rctx.file(distutils_path, rctx.read(rctx.attr.distutils))
elif rctx.attr.distutils_content:
rctx.file(distutils_path, rctx.attr.distutils_content)
# Make the Python installation read-only.
if "windows" not in rctx.os.name:
exec_result = rctx.execute(["chmod", "-R", "ugo-w", "lib"])
if exec_result.return_code:
fail(exec_result.stderr)
python_bin = "python.exe" if ("windows" in platform) else "bin/python3"
build_content = """\
# Generated by python/repositories.bzl
load("@bazel_tools//tools/python:toolchain.bzl", "py_runtime_pair")
package(default_visibility = ["//visibility:public"])
filegroup(
name = "files",
srcs = glob(
include = [
"*.exe",
"bin/**",
"DLLs/**",
"extensions/**",
"include/**",
"lib/**",
"libs/**",
"Scripts/**",
"share/**",
],
exclude = [
"**/* *", # Bazel does not support spaces in file names.
],
),
)
filegroup(
name = "includes",
srcs = glob(["include/**/*.h"]),
)
cc_library(
name = "python_headers",
hdrs = [":includes"],
includes = [
"include",
"include/python{python_version}",
"include/python{python_version}m",
],
)
exports_files(["{python_path}"])
py_runtime(
name = "py3_runtime",
files = [":files"],
interpreter = "{python_path}",
python_version = "PY3",
)
py_runtime_pair(
name = "python_runtimes",
py2_runtime = None,
py3_runtime = ":py3_runtime",
)
""".format(
python_path = python_bin,
python_version = python_short_version,
)
rctx.file("BUILD.bazel", build_content)
return {
"distutils": rctx.attr.distutils,
"distutils_content": rctx.attr.distutils_content,
"name": rctx.attr.name,
"platform": platform,
"python_version": python_version,
"release_filename": release_filename,
"sha256": rctx.attr.sha256,
"strip_prefix": rctx.attr.strip_prefix,
"url": url,
}
python_repository = repository_rule(
_python_repository_impl,
doc = "Fetches the external tools needed for the Python toolchain.",
attrs = {
"distutils": attr.label(
allow_single_file = True,
doc = "A distutils.cfg file to be included in the Python installation. " +
"Either distutils or distutils_content can be specified, but not both.",
mandatory = False,
),
"distutils_content": attr.string(
doc = "A distutils.cfg file content to be included in the Python installation. " +
"Either distutils or distutils_content can be specified, but not both.",
mandatory = False,
),
"platform": attr.string(
doc = "The platform name for the Python interpreter tarball.",
mandatory = True,
values = PLATFORMS.keys(),
),
"python_version": attr.string(
doc = "The Python version.",
mandatory = True,
),
"release_filename": attr.string(
doc = "The filename of the interpreter to be downloaded",
mandatory = True,
),
"sha256": attr.string(
doc = "The SHA256 integrity hash for the Python interpreter tarball.",
mandatory = True,
),
"strip_prefix": attr.string(
doc = "A directory prefix to strip from the extracted files.",
mandatory = True,
),
"url": attr.string(
doc = "The URL of the interpreter to download",
mandatory = True,
),
"zstd_sha256": attr.string(
default = "7c42d56fac126929a6a85dbc73ff1db2411d04f104fae9bdea51305663a83fd0",
),
"zstd_url": attr.string(
default = "https://github.com/facebook/zstd/releases/download/v{version}/zstd-{version}.tar.gz",
),
"zstd_version": attr.string(
default = "1.5.2",
),
},
)
# Wrapper macro around everything above, this is the primary API.
def python_register_toolchains(
name,
python_version,
distutils = None,
distutils_content = None,
register_toolchains = True,
tool_versions = TOOL_VERSIONS,
**kwargs):
"""Convenience macro for users which does typical setup.
- Create a repository for each built-in platform like "python_linux_amd64" -
this repository is lazily fetched when Python is needed for that platform.
- Create a repository exposing toolchains for each platform like
"python_platforms".
- Register a toolchain pointing at each platform.
Users can avoid this macro and do these steps themselves, if they want more
control.
Args:
name: base name for all created repos, like "python38".
python_version: the Python version.
distutils: see the distutils attribute in the python_repository repository rule.
distutils_content: see the distutils_content attribute in the python_repository repository rule.
register_toolchains: Whether or not to register the downloaded toolchains.
tool_versions: a dict containing a mapping of version with SHASUM and platform info. If not supplied, the defaults
in python/versions.bzl will be used
**kwargs: passed to each python_repositories call.
"""
base_url = kwargs.pop("base_url", DEFAULT_RELEASE_BASE_URL)
if python_version in MINOR_MAPPING:
python_version = MINOR_MAPPING[python_version]
for platform in PLATFORMS.keys():
sha256 = tool_versions[python_version]["sha256"].get(platform, None)
if not sha256:
continue
(release_filename, url, strip_prefix) = get_release_url(platform, python_version, base_url, tool_versions)
python_repository(
name = "{name}_{platform}".format(
name = name,
platform = platform,
),
sha256 = sha256,
platform = platform,
python_version = python_version,
release_filename = release_filename,
url = url,
distutils = distutils,
distutils_content = distutils_content,
strip_prefix = strip_prefix,
**kwargs
)
if register_toolchains:
native.register_toolchains("@{name}_toolchains//:{platform}_toolchain".format(
name = name,
platform = platform,
))
resolved_interpreter_os_alias(
name = name,
user_repository_name = name,
)
toolchains_repo(
name = "{name}_toolchains".format(name = name),
user_repository_name = name,
)