diff --git a/python/private/pypi/BUILD.bazel b/python/private/pypi/BUILD.bazel index f541cbe98b..06ca3a8e34 100644 --- a/python/private/pypi/BUILD.bazel +++ b/python/private/pypi/BUILD.bazel @@ -236,13 +236,9 @@ bzl_library( name = "pep508_deps_bzl", srcs = ["pep508_deps.bzl"], deps = [ - ":pep508_env_bzl", ":pep508_evaluate_bzl", - ":pep508_platform_bzl", ":pep508_requirement_bzl", - "//python/private:full_version_bzl", "//python/private:normalize_name_bzl", - "@pythons_hub//:versions_bzl", ], ) diff --git a/python/private/pypi/config.bzl.tmpl.bzlmod b/python/private/pypi/config.bzl.tmpl.bzlmod index deb53631d1..c3ada70d27 100644 --- a/python/private/pypi/config.bzl.tmpl.bzlmod +++ b/python/private/pypi/config.bzl.tmpl.bzlmod @@ -6,4 +6,4 @@ with your usecase. This may change in between rules_python versions without any @generated by rules_python pip.parse bzlmod extension. """ -target_platforms = %%TARGET_PLATFORMS%% +whl_map = %%WHL_MAP%% diff --git a/python/private/pypi/extension.bzl b/python/private/pypi/extension.bzl index 647407f16f..67b4925340 100644 --- a/python/private/pypi/extension.bzl +++ b/python/private/pypi/extension.bzl @@ -17,6 +17,7 @@ load("@bazel_features//:features.bzl", "bazel_features") load("@pythons_hub//:interpreters.bzl", "INTERPRETER_LABELS") load("@pythons_hub//:versions.bzl", "MINOR_MAPPING") +load("@rules_python_internal//:rules_python_config.bzl", rp_config = "config") load("//python/private:auth.bzl", "AUTH_ATTRS") load("//python/private:full_version.bzl", "full_version") load("//python/private:normalize_name.bzl", "normalize_name") @@ -72,7 +73,8 @@ def _create_whl_repos( available_interpreters = INTERPRETER_LABELS, minor_mapping = MINOR_MAPPING, evaluate_markers = evaluate_markers_py, - get_index_urls = None): + get_index_urls = None, + enable_pipstar = False): """create all of the whl repositories Args: @@ -87,6 +89,7 @@ def _create_whl_repos( minor_mapping: {type}`dict[str, str]` The dictionary needed to resolve the full python version used to parse package METADATA files. evaluate_markers: the function used to evaluate the markers. + enable_pipstar: enable the pipstar feature. Returns a {type}`struct` with the following attributes: whl_map: {type}`dict[str, list[struct]]` the output is keyed by the @@ -216,7 +219,6 @@ def _create_whl_repos( enable_implicit_namespace_pkgs = pip_attr.enable_implicit_namespace_pkgs, environment = pip_attr.environment, envsubst = pip_attr.envsubst, - experimental_target_platforms = pip_attr.experimental_target_platforms, group_deps = group_deps, group_name = group_name, pip_data_exclude = pip_attr.pip_data_exclude, @@ -227,6 +229,9 @@ def _create_whl_repos( for p, args in whl_overrides.get(whl_name, {}).items() }, ) + if not enable_pipstar: + maybe_args["experimental_target_platforms"] = pip_attr.experimental_target_platforms + whl_library_args.update({k: v for k, v in maybe_args.items() if v}) maybe_args_with_default = dict( # The following values have defaults next to them @@ -249,6 +254,7 @@ def _create_whl_repos( auth_patterns = pip_attr.auth_patterns, python_version = major_minor, multiple_requirements_for_whl = len(requirements) > 1., + enable_pipstar = enable_pipstar, ).items(): repo_name = "{}_{}".format(pip_name, repo_name) if repo_name in whl_libraries: @@ -277,7 +283,7 @@ def _create_whl_repos( }, ) -def _whl_repos(*, requirement, whl_library_args, download_only, netrc, auth_patterns, multiple_requirements_for_whl = False, python_version): +def _whl_repos(*, requirement, whl_library_args, download_only, netrc, auth_patterns, multiple_requirements_for_whl = False, python_version, enable_pipstar = False): ret = {} dists = requirement.whls @@ -305,13 +311,14 @@ def _whl_repos(*, requirement, whl_library_args, download_only, netrc, auth_patt args["urls"] = [distribution.url] args["sha256"] = distribution.sha256 args["filename"] = distribution.filename - args["experimental_target_platforms"] = [ - # Get rid of the version fot the target platforms because we are - # passing the interpreter any way. Ideally we should search of ways - # how to pass the target platforms through the hub repo. - p.partition("_")[2] - for p in requirement.target_platforms - ] + if not enable_pipstar: + args["experimental_target_platforms"] = [ + # Get rid of the version fot the target platforms because we are + # passing the interpreter any way. Ideally we should search of ways + # how to pass the target platforms through the hub repo. + p.partition("_")[2] + for p in requirement.target_platforms + ] # Pure python wheels or sdists may need to have a platform here target_platforms = None @@ -357,7 +364,11 @@ def _whl_repos(*, requirement, whl_library_args, download_only, netrc, auth_patt return ret -def parse_modules(module_ctx, _fail = fail, simpleapi_download = simpleapi_download, **kwargs): +def parse_modules( + module_ctx, + _fail = fail, + simpleapi_download = simpleapi_download, + **kwargs): """Implementation of parsing the tag classes for the extension and return a struct for registering repositories. Args: @@ -639,7 +650,7 @@ def _pip_impl(module_ctx): module_ctx: module contents """ - mods = parse_modules(module_ctx) + mods = parse_modules(module_ctx, enable_pipstar = rp_config.enable_pipstar) # Build all of the wheel modifications if the tag class is called. _whl_mods_impl(mods.whl_mods) diff --git a/python/private/pypi/generate_whl_library_build_bazel.bzl b/python/private/pypi/generate_whl_library_build_bazel.bzl index 31c9d4da60..3764e720c0 100644 --- a/python/private/pypi/generate_whl_library_build_bazel.bzl +++ b/python/private/pypi/generate_whl_library_build_bazel.bzl @@ -26,10 +26,11 @@ _RENDER = { "entry_points": render.dict, "extras": render.list, "group_deps": render.list, + "include": str, "requires_dist": render.list, "srcs_exclude": render.list, "tags": render.list, - "target_platforms": lambda x: render.list(x) if x else "target_platforms", + "target_platforms": render.list, } # NOTE @aignas 2024-10-25: We have to keep this so that files in @@ -62,28 +63,44 @@ def generate_whl_library_build_bazel( A complete BUILD file as a string """ - fn = "whl_library_targets" + loads = [] if kwargs.get("tags"): + fn = "whl_library_targets" + # legacy path unsupported_args = [ "requires", "metadata_name", "metadata_version", + "include", ] else: - fn = "{}_from_requires".format(fn) + fn = "whl_library_targets_from_requires" unsupported_args = [ "dependencies", "dependencies_by_platform", + "target_platforms", + "default_python_version", ] + dep_template = kwargs.get("dep_template") + loads.append( + """load("{}", "{}")""".format( + dep_template.format( + name = "", + target = "config.bzl", + ), + "whl_map", + ), + ) + kwargs["include"] = "whl_map" for arg in unsupported_args: if kwargs.get(arg): fail("BUG, unsupported arg: '{}'".format(arg)) - loads = [ + loads.extend([ """load("@rules_python//python/private/pypi:whl_library_targets.bzl", "{}")""".format(fn), - ] + ]) additional_content = [] if annotation: diff --git a/python/private/pypi/hub_repository.bzl b/python/private/pypi/hub_repository.bzl index d2cbf88c24..0a1e772d05 100644 --- a/python/private/pypi/hub_repository.bzl +++ b/python/private/pypi/hub_repository.bzl @@ -49,7 +49,7 @@ def _impl(rctx): "config.bzl", rctx.attr._config_template, substitutions = { - "%%TARGET_PLATFORMS%%": render.list(rctx.attr.target_platforms), + "%%WHL_MAP%%": render.dict(rctx.attr.whl_map, value_repr = lambda x: "None"), }, ) rctx.template("requirements.bzl", rctx.attr._requirements_bzl_template, substitutions = { diff --git a/python/private/pypi/pep508_deps.bzl b/python/private/pypi/pep508_deps.bzl index bcc4845cf1..e73f747bed 100644 --- a/python/private/pypi/pep508_deps.bzl +++ b/python/private/pypi/pep508_deps.bzl @@ -15,23 +15,17 @@ """This module is for implementing PEP508 compliant METADATA deps parsing. """ -load("@pythons_hub//:versions.bzl", "DEFAULT_PYTHON_VERSION", "MINOR_MAPPING") -load("//python/private:full_version.bzl", "full_version") load("//python/private:normalize_name.bzl", "normalize_name") -load(":pep508_env.bzl", "env") load(":pep508_evaluate.bzl", "evaluate") -load(":pep508_platform.bzl", "platform", "platform_from_str") load(":pep508_requirement.bzl", "requirement") def deps( name, *, requires_dist, - platforms = [], extras = [], excludes = [], - default_python_version = None, - minor_mapping = MINOR_MAPPING): + include = []): """Parse the RequiresDist from wheel METADATA Args: @@ -39,12 +33,9 @@ def deps( requires_dist: {type}`list[str]` the list of RequiresDist lines from the METADATA file. excludes: {type}`list[str]` what packages should we exclude. + include: {type}`list[str]` what packages should we exclude. If it is not + specified, then we will include all deps from `requires_dist`. extras: {type}`list[str]` the requested extras to generate targets for. - platforms: {type}`list[str]` the list of target platform strings. - default_python_version: {type}`str` the host python version. - minor_mapping: {type}`type[str, str]` the minor mapping to use when - resolving to the full python version as DEFAULT_PYTHON_VERSION can by - of format `3.x`. Returns: A struct with attributes: @@ -60,39 +51,20 @@ def deps( deps_select = {} name = normalize_name(name) want_extras = _resolve_extras(name, reqs, extras) + include = [normalize_name(n) for n in include] # drop self edges excludes = [name] + [normalize_name(x) for x in excludes] - default_python_version = default_python_version or DEFAULT_PYTHON_VERSION - if default_python_version: - # if it is not bzlmod, then DEFAULT_PYTHON_VERSION may be unset - default_python_version = full_version( - version = default_python_version, - minor_mapping = minor_mapping, - ) - platforms = [ - platform_from_str(p, python_version = default_python_version) - for p in platforms - ] - - abis = sorted({p.abi: True for p in platforms if p.abi}) - if default_python_version and len(abis) > 1: - _, _, tail = default_python_version.partition(".") - default_abi = "cp3" + tail - elif len(abis) > 1: - fail( - "all python versions need to be specified explicitly, got: {}".format(platforms), - ) - else: - default_abi = None - reqs_by_name = {} for req in reqs: if req.name_ in excludes: continue + if include and req.name_ not in include: + continue + reqs_by_name.setdefault(req.name, []).append(req) for name, reqs in reqs_by_name.items(): @@ -102,55 +74,25 @@ def deps( normalize_name(name), reqs, extras = want_extras, - platforms = platforms, - default_abi = default_abi, ) return struct( deps = sorted(deps), deps_select = { - _platform_str(p): sorted(deps) - for p, deps in deps_select.items() + d: markers + for d, markers in sorted(deps_select.items()) }, ) -def _platform_str(self): - if self.abi == None: - return "{}_{}".format(self.os, self.arch) - - return "{}_{}_{}".format( - self.abi, - self.os or "anyos", - self.arch or "anyarch", - ) - -def _add(deps, deps_select, dep, platform): +def _add(deps, deps_select, dep, markers = None): dep = normalize_name(dep) - if platform == None: + if not markers: deps[dep] = True - - # If the dep is in the platform-specific list, remove it from the select. - pop_keys = [] - for p, _deps in deps_select.items(): - if dep not in _deps: - continue - - _deps.pop(dep) - if not _deps: - pop_keys.append(p) - - for p in pop_keys: - deps_select.pop(p) - return - - if dep in deps: - # If the dep is already in the main dependency list, no need to add it in the - # platform-specific dependency list. - return - - # Add the platform-specific branch - deps_select.setdefault(platform, {})[dep] = True + elif len(markers) == 1: + deps_select[dep] = markers[0] + else: + deps_select[dep] = "({})".format(") or (".join(sorted(markers))) def _resolve_extras(self_name, reqs, extras): """Resolve extras which are due to depending on self[some_other_extra]. @@ -207,37 +149,24 @@ def _resolve_extras(self_name, reqs, extras): # Poor mans set return sorted({x: None for x in extras}) -def _add_reqs(deps, deps_select, dep, reqs, *, extras, platforms, default_abi = None): +def _add_reqs(deps, deps_select, dep, reqs, *, extras): for req in reqs: if not req.marker: - _add(deps, deps_select, dep, None) + _add(deps, deps_select, dep) return - platforms_to_add = {} - for plat in platforms: - if plat in platforms_to_add: - # marker evaluation is more expensive than this check - continue - - added = False - for extra in extras: - if added: + markers = {} + for req in reqs: + for x in extras: + m = evaluate(req.marker, env = {"extra": x}, strict = False) + if m == False: + continue + elif m == True: + _add(deps, deps_select, dep) break + else: + markers[m] = None + continue - for req in reqs: - if evaluate(req.marker, env = env(target_platform = plat, extra = extra)): - platforms_to_add[plat] = True - added = True - break - - if len(platforms_to_add) == len(platforms): - # the dep is in all target platforms, let's just add it to the regular - # list - _add(deps, deps_select, dep, None) - return - - for plat in platforms_to_add: - if default_abi: - _add(deps, deps_select, dep, plat) - if plat.abi == default_abi or not default_abi: - _add(deps, deps_select, dep, platform(os = plat.os, arch = plat.arch)) + if markers: + _add(deps, deps_select, dep, sorted(markers)) diff --git a/python/private/pypi/pep508_evaluate.bzl b/python/private/pypi/pep508_evaluate.bzl index 61a5b19999..d4492a75bb 100644 --- a/python/private/pypi/pep508_evaluate.bzl +++ b/python/private/pypi/pep508_evaluate.bzl @@ -122,7 +122,9 @@ def evaluate(marker, *, env, strict = True, **kwargs): **kwargs: Extra kwargs to be passed to the expression evaluator. Returns: - The {type}`bool` If the marker is compatible with the given env. + The {type}`bool | str` If the marker is compatible with the given env. If strict is + `False`, then the output type is `str` which will represent the remaining + expression that has not been evaluated. """ tokens = tokenize(marker) diff --git a/python/private/pypi/whl_installer/wheel_installer.py b/python/private/pypi/whl_installer/wheel_installer.py index 2db03e039d..600d45f940 100644 --- a/python/private/pypi/whl_installer/wheel_installer.py +++ b/python/private/pypi/whl_installer/wheel_installer.py @@ -126,7 +126,6 @@ def _extract_wheel( _setup_namespace_pkg_compatibility(installation_dir) metadata = { - "python_version": f"{sys.version_info[0]}.{sys.version_info[1]}.{sys.version_info[2]}", "entry_points": [ { "name": name, diff --git a/python/private/pypi/whl_library.bzl b/python/private/pypi/whl_library.bzl index 160bb5b799..ed6b7bc2d1 100644 --- a/python/private/pypi/whl_library.bzl +++ b/python/private/pypi/whl_library.bzl @@ -22,7 +22,6 @@ load("//python/private:repo_utils.bzl", "REPO_DEBUG_ENV_VAR", "repo_utils") load(":attrs.bzl", "ATTRS", "use_isolated") load(":deps.bzl", "all_repo_names", "record_files") load(":generate_whl_library_build_bazel.bzl", "generate_whl_library_build_bazel") -load(":parse_requirements.bzl", "host_platform") load(":parse_whl_name.bzl", "parse_whl_name") load(":patch_whl.bzl", "patch_whl") load(":pypi_repo_utils.bzl", "pypi_repo_utils") @@ -362,7 +361,6 @@ def _whl_library_impl(rctx): metadata = json.decode(rctx.read("metadata.json")) rctx.delete("metadata.json") - python_version = metadata["python_version"] # NOTE @aignas 2024-06-22: this has to live on until we stop supporting # passing `twine` as a `:pkg` library via the `WORKSPACE` builds. @@ -400,9 +398,7 @@ def _whl_library_impl(rctx): entry_points = entry_points, metadata_name = metadata.name, metadata_version = metadata.version, - default_python_version = python_version, requires_dist = metadata.requires_dist, - target_platforms = rctx.attr.experimental_target_platforms or [host_platform(rctx)], # TODO @aignas 2025-04-14: load through the hub: annotation = None if not rctx.attr.annotation else struct(**json.decode(rctx.read(rctx.attr.annotation))), data_exclude = rctx.attr.pip_data_exclude, diff --git a/python/private/pypi/whl_library_targets.bzl b/python/private/pypi/whl_library_targets.bzl index 21e4a54a3a..e0c03a1505 100644 --- a/python/private/pypi/whl_library_targets.bzl +++ b/python/private/pypi/whl_library_targets.bzl @@ -19,6 +19,7 @@ load("//python:py_binary.bzl", "py_binary") load("//python:py_library.bzl", "py_library") load("//python/private:glob_excludes.bzl", "glob_excludes") load("//python/private:normalize_name.bzl", "normalize_name") +load(":env_marker_setting.bzl", "env_marker_setting") load( ":labels.bzl", "DATA_LABEL", @@ -29,9 +30,7 @@ load( "WHEEL_FILE_IMPL_LABEL", "WHEEL_FILE_PUBLIC_LABEL", ) -load(":parse_whl_name.bzl", "parse_whl_name") load(":pep508_deps.bzl", "deps") -load(":whl_target_platforms.bzl", "whl_target_platforms") def whl_library_targets_from_requires( *, @@ -40,8 +39,7 @@ def whl_library_targets_from_requires( metadata_version = "", requires_dist = [], extras = [], - target_platforms = [], - default_python_version = None, + include = [], group_deps = [], **kwargs): """The macro to create whl targets from the METADATA. @@ -57,25 +55,21 @@ def whl_library_targets_from_requires( requires_dist: {type}`list[str]` The list of `Requires-Dist` values from the whl `METADATA`. extras: {type}`list[str]` The list of requested extras. This essentially includes extra transitive dependencies in the final targets depending on the wheel `METADATA`. - target_platforms: {type}`list[str]` The list of target platforms to create - dependency closures for. - default_python_version: {type}`str` The python version to assume when parsing - the `METADATA`. This is only used when the `target_platforms` do not - include the version information. + include: {type}`list[str]` The list of packages to include. **kwargs: Extra args passed to the {obj}`whl_library_targets` """ package_deps = _parse_requires_dist( - name = name, - default_python_version = default_python_version, + name = metadata_name, requires_dist = requires_dist, excludes = group_deps, extras = extras, - target_platforms = target_platforms, + include = include, ) + whl_library_targets( name = name, dependencies = package_deps.deps, - dependencies_by_platform = package_deps.deps_select, + dependencies_with_markers = package_deps.deps_select, tags = [ "pypi_name={}".format(metadata_name), "pypi_version={}".format(metadata_version), @@ -86,31 +80,16 @@ def whl_library_targets_from_requires( def _parse_requires_dist( *, name, - default_python_version, requires_dist, excludes, - extras, - target_platforms): - parsed_whl = parse_whl_name(name) - - # NOTE @aignas 2023-12-04: if the wheel is a platform specific wheel, we - # only include deps for that target platform - if parsed_whl.platform_tag != "any": - target_platforms = [ - p.target_platform - for p in whl_target_platforms( - platform_tag = parsed_whl.platform_tag, - abi_tag = parsed_whl.abi_tag.strip("tm"), - ) - ] - + include, + extras): return deps( - name = normalize_name(parsed_whl.distribution), + name = normalize_name(name), requires_dist = requires_dist, - platforms = target_platforms, excludes = excludes, + include = include, extras = extras, - default_python_version = default_python_version, ) def whl_library_targets( @@ -126,6 +105,7 @@ def whl_library_targets( }, dependencies = [], dependencies_by_platform = {}, + dependencies_with_markers = {}, group_deps = [], group_name = "", data = [], @@ -137,6 +117,7 @@ def whl_library_targets( copy_file = copy_file, py_binary = py_binary, py_library = py_library, + env_marker_setting = env_marker_setting, )): """Create all of the whl_library targets. @@ -149,6 +130,8 @@ def whl_library_targets( dependencies: {type}`list[str]` A list of dependencies. dependencies_by_platform: {type}`dict[str, list[str]]` A list of dependencies by platform key. + dependencies_with_markers: {type}`dict[str, str]` A marker to evaluate + in order for the dep to be included. filegroups: {type}`dict[str, list[str]]` A dictionary of the target names and the glob matches. group_name: {type}`str` name of the dependency group (if any) which @@ -207,10 +190,16 @@ def whl_library_targets( data.append(dest) _config_settings( - dependencies_by_platform.keys(), + dependencies_by_platform = dependencies_by_platform.keys(), + dependencies_with_markers = dependencies_with_markers, native = native, + rules = rules, visibility = ["//visibility:private"], ) + deps_conditional = { + d: "is_include_{}_true".format(d) + for d in dependencies_with_markers + } # TODO @aignas 2024-10-25: remove the entry_point generation once # `py_console_script_binary` is the only way to use entry points. @@ -290,6 +279,7 @@ def whl_library_targets( data = _deps( deps = dependencies, deps_by_platform = dependencies_by_platform, + deps_conditional = deps_conditional, tmpl = dep_template.format(name = "{}", target = WHEEL_FILE_PUBLIC_LABEL), # NOTE @aignas 2024-10-28: Actually, `select` is not part of # `native`, but in order to support bazel 6.4 in unit tests, I @@ -342,6 +332,7 @@ def whl_library_targets( deps = _deps( deps = dependencies, deps_by_platform = dependencies_by_platform, + deps_conditional = deps_conditional, tmpl = dep_template.format(name = "{}", target = PY_LIBRARY_PUBLIC_LABEL), select = getattr(native, "select", select), ), @@ -350,7 +341,7 @@ def whl_library_targets( experimental_venvs_site_packages = Label("@rules_python//python/config_settings:venvs_site_packages"), ) -def _config_settings(dependencies_by_platform, native = native, **kwargs): +def _config_settings(dependencies_by_platform, dependencies_with_markers, rules, native = native, **kwargs): """Generate config settings for the targets. Args: @@ -362,9 +353,19 @@ def _config_settings(dependencies_by_platform, native = native, **kwargs): * `@//python/config_settings:is_python_3.{minor_version}` * `{os}_{cpu}` * `cp3{minor_version}_{os}_{cpu}` + dependencies_with_markers: {type}`dict[str, str]` The markers to evaluate by + each dep. + rules: used for testing native: {type}`native` The native struct for overriding in tests. **kwargs: Extra kwargs to pass to the rule. """ + for dep, expression in dependencies_with_markers.items(): + rules.env_marker_setting( + name = "include_{}".format(dep), + expression = expression, + **kwargs + ) + for p in dependencies_by_platform: if p.startswith("@") or p.endswith("default"): continue @@ -404,9 +405,15 @@ def _plat_label(plat): else: return ":is_" + plat.replace("cp3", "python_3.") -def _deps(deps, deps_by_platform, tmpl, select = select): +def _deps(deps, deps_by_platform, deps_conditional, tmpl, select = select): deps = [tmpl.format(d) for d in sorted(deps)] + for dep, setting in deps_conditional.items(): + deps = deps + select({ + ":{}".format(setting): [tmpl.format(dep)], + "//conditions:default": [], + }) + if not deps_by_platform: return deps diff --git a/tests/pypi/extension/extension_tests.bzl b/tests/pypi/extension/extension_tests.bzl index 1cd6869c84..1ce61cd911 100644 --- a/tests/pypi/extension/extension_tests.bzl +++ b/tests/pypi/extension/extension_tests.bzl @@ -62,7 +62,12 @@ def _mod(*, name, parse = [], override = [], whl_mods = [], is_root = True): def _parse_modules(env, **kwargs): return env.expect.that_struct( - parse_modules(**kwargs), + parse_modules( + # TODO @aignas 2025-05-11: start integration testing the branch which + # includes this. + enable_pipstar = 0, + **kwargs + ), attrs = dict( exposed_packages = subjects.dict, hub_group_map = subjects.dict, diff --git a/tests/pypi/generate_whl_library_build_bazel/generate_whl_library_build_bazel_tests.bzl b/tests/pypi/generate_whl_library_build_bazel/generate_whl_library_build_bazel_tests.bzl index 83be7395d4..225b296ebf 100644 --- a/tests/pypi/generate_whl_library_build_bazel/generate_whl_library_build_bazel_tests.bzl +++ b/tests/pypi/generate_whl_library_build_bazel/generate_whl_library_build_bazel_tests.bzl @@ -19,8 +19,73 @@ load("//python/private/pypi:generate_whl_library_build_bazel.bzl", "generate_whl _tests = [] +def _test_all_legacy(env): + want = """\ +load("@rules_python//python/private/pypi:whl_library_targets.bzl", "whl_library_targets") + +package(default_visibility = ["//visibility:public"]) + +whl_library_targets( + copy_executables = { + "exec_src": "exec_dest", + }, + copy_files = { + "file_src": "file_dest", + }, + data = ["extra_target"], + data_exclude = [ + "exclude_via_attr", + "data_exclude_all", + ], + dep_template = "@pypi//{name}:{target}", + dependencies = ["foo"], + dependencies_by_platform = { + "baz": ["bar"], + }, + entry_points = { + "foo": "bar.py", + }, + group_deps = [ + "foo", + "fox", + "qux", + ], + group_name = "qux", + name = "foo.whl", + srcs_exclude = ["srcs_exclude_all"], + tags = ["tag1"], +) + +# SOMETHING SPECIAL AT THE END +""" + actual = generate_whl_library_build_bazel( + dep_template = "@pypi//{name}:{target}", + name = "foo.whl", + dependencies = ["foo"], + dependencies_by_platform = {"baz": ["bar"]}, + entry_points = { + "foo": "bar.py", + }, + data_exclude = ["exclude_via_attr"], + annotation = struct( + copy_files = {"file_src": "file_dest"}, + copy_executables = {"exec_src": "exec_dest"}, + data = ["extra_target"], + data_exclude_glob = ["data_exclude_all"], + srcs_exclude_glob = ["srcs_exclude_all"], + additive_build_content = """# SOMETHING SPECIAL AT THE END""", + ), + group_name = "qux", + group_deps = ["foo", "fox", "qux"], + tags = ["tag1"], + ) + env.expect.that_str(actual.replace("@@", "@")).equals(want) + +_tests.append(_test_all_legacy) + def _test_all(env): want = """\ +load("@pypi//:config.bzl", "whl_map") load("@rules_python//python/private/pypi:whl_library_targets.bzl", "whl_library_targets_from_requires") package(default_visibility = ["//visibility:public"]) @@ -47,6 +112,7 @@ whl_library_targets_from_requires( "qux", ], group_name = "qux", + include = whl_map, name = "foo.whl", requires_dist = [ "foo", @@ -54,7 +120,6 @@ whl_library_targets_from_requires( "qux", ], srcs_exclude = ["srcs_exclude_all"], - target_platforms = ["foo"], ) # SOMETHING SPECIAL AT THE END @@ -76,7 +141,6 @@ whl_library_targets_from_requires( additive_build_content = """# SOMETHING SPECIAL AT THE END""", ), group_name = "qux", - target_platforms = ["foo"], group_deps = ["foo", "fox", "qux"], ) env.expect.that_str(actual.replace("@@", "@")).equals(want) @@ -85,6 +149,7 @@ _tests.append(_test_all) def _test_all_with_loads(env): want = """\ +load("@pypi//:config.bzl", "whl_map") load("@rules_python//python/private/pypi:whl_library_targets.bzl", "whl_library_targets_from_requires") package(default_visibility = ["//visibility:public"]) @@ -111,6 +176,7 @@ whl_library_targets_from_requires( "qux", ], group_name = "qux", + include = whl_map, name = "foo.whl", requires_dist = [ "foo", diff --git a/tests/pypi/pep508/deps_tests.bzl b/tests/pypi/pep508/deps_tests.bzl index 118cd50092..aaa3b2f7dd 100644 --- a/tests/pypi/pep508/deps_tests.bzl +++ b/tests/pypi/pep508/deps_tests.bzl @@ -29,101 +29,41 @@ def test_simple_deps(env): _tests.append(test_simple_deps) def test_can_add_os_specific_deps(env): - for target in [ - struct( - platforms = [ - "linux_x86_64", - "osx_x86_64", - "osx_aarch64", - "windows_x86_64", - ], - python_version = "3.3.1", - ), - struct( - platforms = [ - "cp33_linux_x86_64", - "cp33_osx_x86_64", - "cp33_osx_aarch64", - "cp33_windows_x86_64", - ], - python_version = "", - ), - struct( - platforms = [ - "cp33.1_linux_x86_64", - "cp33.1_osx_x86_64", - "cp33.1_osx_aarch64", - "cp33.1_windows_x86_64", - ], - python_version = "", - ), - ]: - got = deps( - "foo", - requires_dist = [ - "bar", - "an_osx_dep; sys_platform=='darwin'", - "posix_dep; os_name=='posix'", - "win_dep; os_name=='nt'", - ], - platforms = target.platforms, - default_python_version = target.python_version, - ) - - env.expect.that_collection(got.deps).contains_exactly(["bar"]) - env.expect.that_dict(got.deps_select).contains_exactly({ - "linux_x86_64": ["posix_dep"], - "osx_aarch64": ["an_osx_dep", "posix_dep"], - "osx_x86_64": ["an_osx_dep", "posix_dep"], - "windows_x86_64": ["win_dep"], - }) - -_tests.append(test_can_add_os_specific_deps) - -def test_deps_are_added_to_more_specialized_platforms(env): got = deps( "foo", requires_dist = [ - "m1_dep; sys_platform=='darwin' and platform_machine=='arm64'", - "mac_dep; sys_platform=='darwin'", - ], - platforms = [ - "osx_x86_64", - "osx_aarch64", + "bar", + "an_osx_dep; sys_platform=='darwin'", + "posix_dep; os_name=='posix'", + "win_dep; os_name=='nt'", ], - default_python_version = "3.8.4", ) - env.expect.that_collection(got.deps).contains_exactly(["mac_dep"]) + env.expect.that_collection(got.deps).contains_exactly(["bar"]) env.expect.that_dict(got.deps_select).contains_exactly({ - "osx_aarch64": ["m1_dep"], + "an_osx_dep": "sys_platform == \"darwin\"", + "posix_dep": "os_name == \"posix\"", + "win_dep": "os_name == \"nt\"", }) -_tests.append(test_deps_are_added_to_more_specialized_platforms) +_tests.append(test_can_add_os_specific_deps) -def test_non_platform_markers_are_added_to_common_deps(env): +def test_deps_are_added_to_more_specialized_platforms(env): got = deps( "foo", requires_dist = [ - "bar", - "baz; implementation_name=='cpython'", "m1_dep; sys_platform=='darwin' and platform_machine=='arm64'", + "mac_dep; sys_platform=='darwin'", ], - platforms = [ - "linux_x86_64", - "osx_x86_64", - "osx_aarch64", - "windows_x86_64", - ], - default_python_version = "3.8.4", ) - env.expect.that_collection(got.deps).contains_exactly(["bar", "baz"]) + env.expect.that_collection(got.deps).contains_exactly([]) env.expect.that_dict(got.deps_select).contains_exactly({ - "osx_aarch64": ["m1_dep"], + "m1_dep": "sys_platform == \"darwin\" and platform_machine == \"arm64\"", + "mac_dep": "sys_platform == \"darwin\"", }) -_tests.append(test_non_platform_markers_are_added_to_common_deps) +_tests.append(test_deps_are_added_to_more_specialized_platforms) def test_self_is_ignored(env): got = deps( @@ -167,180 +107,59 @@ def _test_can_get_deps_based_on_specific_python_version(env): "posix_dep; os_name=='posix' and python_version >= '3.8'", ] - py38 = deps( - "foo", - requires_dist = requires_dist, - platforms = ["cp38_linux_x86_64"], - ) - py373 = deps( - "foo", - requires_dist = requires_dist, - platforms = ["cp37.3_linux_x86_64"], - ) - py37 = deps( + got = deps( "foo", requires_dist = requires_dist, - platforms = ["cp37_linux_x86_64"], ) # since there is a single target platform, the deps_select will be empty - env.expect.that_collection(py37.deps).contains_exactly(["bar", "baz"]) - env.expect.that_dict(py37.deps_select).contains_exactly({}) - env.expect.that_collection(py38.deps).contains_exactly(["bar", "posix_dep"]) - env.expect.that_dict(py38.deps_select).contains_exactly({}) - env.expect.that_collection(py373.deps).contains_exactly(["bar"]) - env.expect.that_dict(py373.deps_select).contains_exactly({}) - -_tests.append(_test_can_get_deps_based_on_specific_python_version) - -def _test_no_version_select_when_single_version(env): - got = deps( - "foo", - requires_dist = [ - "bar", - "baz; python_version >= '3.8'", - "posix_dep; os_name=='posix'", - "posix_dep_with_version; os_name=='posix' and python_version >= '3.8'", - "arch_dep; platform_machine=='x86_64' and python_version >= '3.8'", - ], - platforms = [ - "cp38_linux_x86_64", - "cp38_windows_x86_64", - ], - default_python_version = "", - ) - - env.expect.that_collection(got.deps).contains_exactly(["bar", "baz", "arch_dep"]) + env.expect.that_collection(got.deps).contains_exactly(["bar"]) env.expect.that_dict(got.deps_select).contains_exactly({ - "linux_x86_64": ["posix_dep", "posix_dep_with_version"], + "baz": "python_full_version < \"3.7.3\"", + "posix_dep": "os_name == \"posix\" and python_version >= \"3.8\"", }) -_tests.append(_test_no_version_select_when_single_version) +_tests.append(_test_can_get_deps_based_on_specific_python_version) -def _test_can_get_version_select(env): +def _test_include_only_particular_deps(env): requires_dist = [ "bar", - "baz; python_version < '3.8'", - "baz_new; python_version >= '3.8'", - "posix_dep; os_name=='posix'", - "posix_dep_with_version; os_name=='posix' and python_version >= '3.8'", - "arch_dep; platform_machine=='x86_64' and python_version < '3.8'", + "baz; python_full_version < '3.7.3'", + "posix_dep; os_name=='posix' and python_version >= '3.8'", ] got = deps( "foo", requires_dist = requires_dist, - platforms = [ - "cp3{}_{}_x86_64".format(minor, os) - for minor in ["7.4", "8.8", "9.8"] - for os in ["linux", "windows"] - ], - default_python_version = "3.7", - minor_mapping = { - "3.7": "3.7.4", - }, + include = ["bar", "posix_dep"], ) + # since there is a single target platform, the deps_select will be empty env.expect.that_collection(got.deps).contains_exactly(["bar"]) env.expect.that_dict(got.deps_select).contains_exactly({ - "cp37.4_linux_x86_64": ["arch_dep", "baz", "posix_dep"], - "cp37.4_windows_x86_64": ["arch_dep", "baz"], - "cp38.8_linux_x86_64": ["baz_new", "posix_dep", "posix_dep_with_version"], - "cp38.8_windows_x86_64": ["baz_new"], - "cp39.8_linux_x86_64": ["baz_new", "posix_dep", "posix_dep_with_version"], - "cp39.8_windows_x86_64": ["baz_new"], - "linux_x86_64": ["arch_dep", "baz", "posix_dep"], - "windows_x86_64": ["arch_dep", "baz"], + "posix_dep": "os_name == \"posix\" and python_version >= \"3.8\"", }) -_tests.append(_test_can_get_version_select) +_tests.append(_test_include_only_particular_deps) -def _test_deps_spanning_all_target_py_versions_are_added_to_common(env): +def test_all_markers_are_added(env): requires_dist = [ "bar", "baz (<2,>=1.11) ; python_version < '3.8'", "baz (<2,>=1.14) ; python_version >= '3.8'", ] - default_python_version = "3.8.4" got = deps( "foo", requires_dist = requires_dist, - platforms = [ - "cp3{}_linux_x86_64".format(minor) - for minor in [7, 8, 9] - ], - default_python_version = default_python_version, - ) - - env.expect.that_collection(got.deps).contains_exactly(["bar", "baz"]) - env.expect.that_dict(got.deps_select).contains_exactly({}) - -_tests.append(_test_deps_spanning_all_target_py_versions_are_added_to_common) - -def _test_deps_are_not_duplicated(env): - default_python_version = "3.7.4" - - # See an example in - # https://files.pythonhosted.org/packages/76/9e/db1c2d56c04b97981c06663384f45f28950a73d9acf840c4006d60d0a1ff/opencv_python-4.9.0.80-cp37-abi3-win32.whl.metadata - requires_dist = [ - "bar >=0.1.0 ; python_version < '3.7'", - "bar >=0.2.0 ; python_version >= '3.7'", - "bar >=0.4.0 ; python_version >= '3.6' and platform_system == 'Linux' and platform_machine == 'aarch64'", - "bar >=0.4.0 ; python_version >= '3.9'", - "bar >=0.5.0 ; python_version <= '3.9' and platform_system == 'Darwin' and platform_machine == 'arm64'", - "bar >=0.5.0 ; python_version >= '3.10' and platform_system == 'Darwin'", - "bar >=0.5.0 ; python_version >= '3.10'", - "bar >=0.6.0 ; python_version >= '3.11'", - ] - - got = deps( - "foo", - requires_dist = requires_dist, - platforms = [ - "cp3{}_{}_{}".format(minor, os, arch) - for minor in [7, 10] - for os in ["linux", "osx", "windows"] - for arch in ["x86_64", "aarch64"] - ], - default_python_version = default_python_version, ) env.expect.that_collection(got.deps).contains_exactly(["bar"]) - env.expect.that_dict(got.deps_select).contains_exactly({}) - -_tests.append(_test_deps_are_not_duplicated) - -def _test_deps_are_not_duplicated_when_encountering_platform_dep_first(env): - # Note, that we are sorting the incoming `requires_dist` and we need to ensure that we are not getting any - # issues even if the platform-specific line comes first. - requires_dist = [ - "bar >=0.4.0 ; python_version >= '3.6' and platform_system == 'Linux' and platform_machine == 'aarch64'", - "bar >=0.5.0 ; python_version >= '3.9'", - ] - - got = deps( - "foo", - requires_dist = requires_dist, - platforms = [ - "cp37.1_linux_aarch64", - "cp37.1_linux_x86_64", - "cp310_linux_aarch64", - "cp310_linux_x86_64", - ], - default_python_version = "3.7.1", - minor_mapping = {}, - ) - - env.expect.that_collection(got.deps).contains_exactly([]) env.expect.that_dict(got.deps_select).contains_exactly({ - "cp310_linux_aarch64": ["bar"], - "cp310_linux_x86_64": ["bar"], - "cp37.1_linux_aarch64": ["bar"], - "linux_aarch64": ["bar"], + "baz": "(python_version < \"3.8\") or (python_version >= \"3.8\")", }) -_tests.append(_test_deps_are_not_duplicated_when_encountering_platform_dep_first) +_tests.append(test_all_markers_are_added) def deps_test_suite(name): # buildifier: disable=function-docstring test_suite( diff --git a/tests/pypi/whl_installer/wheel_installer_test.py b/tests/pypi/whl_installer/wheel_installer_test.py index e838047925..ef5a2483ab 100644 --- a/tests/pypi/whl_installer/wheel_installer_test.py +++ b/tests/pypi/whl_installer/wheel_installer_test.py @@ -72,7 +72,7 @@ def test_wheel_exists(self) -> None: extras={}, enable_implicit_namespace_pkgs=False, platforms=[], - enable_pipstar = False, + enable_pipstar=False, ) want_files = [ @@ -97,7 +97,6 @@ def test_wheel_exists(self) -> None: deps_by_platform={}, entry_points=[], name="example-minimal-package", - python_version="3.11.11", version="0.0.1", ) self.assertEqual(want, metadata_file_content) diff --git a/tests/pypi/whl_library_targets/whl_library_targets_tests.bzl b/tests/pypi/whl_library_targets/whl_library_targets_tests.bzl index 432cdbfa1b..f0e5f57ac0 100644 --- a/tests/pypi/whl_library_targets/whl_library_targets_tests.bzl +++ b/tests/pypi/whl_library_targets/whl_library_targets_tests.bzl @@ -180,18 +180,20 @@ _tests.append(_test_entrypoints) def _test_whl_and_library_deps_from_requires(env): filegroup_calls = [] py_library_calls = [] + env_marker_setting_calls = [] whl_library_targets_from_requires( name = "foo-0-py3-none-any.whl", metadata_name = "Foo", metadata_version = "0", - dep_template = "@pypi_{name}//:{target}", + dep_template = "@pypi//{name}:{target}", requires_dist = [ "foo", # this self-edge will be ignored - "bar-baz", + "bar", + "bar-baz; python_version < \"8.2\"", + "booo", # this is effectively excluded due to the list below ], - target_platforms = ["cp38_linux_x86_64"], - default_python_version = "3.8.1", + include = ["foo", "bar", "bar_baz"], data_exclude = [], # Overrides for testing filegroups = {}, @@ -203,6 +205,7 @@ def _test_whl_and_library_deps_from_requires(env): ), rules = struct( py_library = lambda **kwargs: py_library_calls.append(kwargs), + env_marker_setting = lambda **kwargs: env_marker_setting_calls.append(kwargs), ), ) @@ -210,7 +213,10 @@ def _test_whl_and_library_deps_from_requires(env): { "name": "whl", "srcs": ["foo-0-py3-none-any.whl"], - "data": ["@pypi_bar_baz//:whl"], + "data": ["@pypi//bar:whl"] + _select({ + ":is_include_bar_baz_true": ["@pypi//bar_baz:whl"], + "//conditions:default": [], + }), "visibility": ["//visibility:public"], }, ]) # buildifier: @unsorted-dict-items @@ -233,12 +239,22 @@ def _test_whl_and_library_deps_from_requires(env): ] + glob_excludes.version_dependent_exclusions(), ), "imports": ["site-packages"], - "deps": ["@pypi_bar_baz//:pkg"], + "deps": ["@pypi//bar:pkg"] + _select({ + ":is_include_bar_baz_true": ["@pypi//bar_baz:pkg"], + "//conditions:default": [], + }), "tags": ["pypi_name=Foo", "pypi_version=0"], "visibility": ["//visibility:public"], "experimental_venvs_site_packages": Label("//python/config_settings:venvs_site_packages"), }, ]) # buildifier: @unsorted-dict-items + env.expect.that_collection(env_marker_setting_calls).contains_exactly([ + { + "name": "include_bar_baz", + "expression": "python_version < \"8.2\"", + "visibility": ["//visibility:private"], + }, + ]) # buildifier: @unsorted-dict-items _tests.append(_test_whl_and_library_deps_from_requires)