Skip to content

Commit b29b0db

Browse files
scbeddmccoyp
andauthored
"Find Wheel" now handles platform-specific artifacts during selection (#30196)
* selection of wheel now honors the invoking executable's pip compatibility tags to select the correct wheel from a list * add a unit test testing the selection algo --------- Co-authored-by: McCoy Patiño <[email protected]>
1 parent 70f627f commit b29b0db

File tree

7 files changed

+247
-130
lines changed

7 files changed

+247
-130
lines changed

eng/tox/create_package_and_install.py

Lines changed: 11 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,10 @@
1818
import shutil
1919
from pkg_resources import parse_version
2020

21-
from tox_helper_tasks import find_whl, find_sdist, get_pip_list_output
21+
from tox_helper_tasks import get_pip_list_output
2222
from ci_tools.parsing import ParsedSetup, parse_require
2323
from ci_tools.build import create_package
24-
from ci_tools.functions import get_package_from_repo
24+
from ci_tools.functions import get_package_from_repo, find_whl, find_sdist, discover_prebuilt_package
2525

2626
logging.getLogger().setLevel(logging.INFO)
2727

@@ -46,6 +46,15 @@ def discover_packages(setuppy_path, args):
4646
packages = []
4747
if os.getenv("PREBUILT_WHEEL_DIR") is not None and not args.force_create:
4848
packages = discover_prebuilt_package(os.getenv("PREBUILT_WHEEL_DIR"), setuppy_path, args.package_type)
49+
pkg = ParsedSetup.from_path(setuppy_path)
50+
51+
if not packages:
52+
logging.error(
53+
"Package is missing in prebuilt directory {0} for package {1} and version {2}".format(
54+
os.getenv("PREBUILT_WHEEL_DIR"), pkg.name, pkg.version
55+
)
56+
)
57+
exit(1)
4958
else:
5059
packages = build_and_discover_package(
5160
setuppy_path,
@@ -56,25 +65,6 @@ def discover_packages(setuppy_path, args):
5665
return packages
5766

5867

59-
def discover_prebuilt_package(dist_directory, setuppy_path, package_type):
60-
packages = []
61-
pkg = ParsedSetup.from_path(setuppy_path)
62-
if package_type == "wheel":
63-
prebuilt_package = find_whl(dist_directory, pkg.name, pkg.version)
64-
else:
65-
prebuilt_package = find_sdist(dist_directory, pkg.name, pkg.version)
66-
67-
if prebuilt_package is None:
68-
logging.error(
69-
"Package is missing in prebuilt directory {0} for package {1} and version {2}".format(
70-
dist_directory, pkg.name, pkg.version
71-
)
72-
)
73-
exit(1)
74-
packages.append(prebuilt_package)
75-
return packages
76-
77-
7868
def in_ci():
7969
return os.getenv("TF_BUILD", False)
8070

eng/tox/run_apistubgen.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
import os
1010
import logging
1111

12-
from tox_helper_tasks import find_whl
12+
from ci_tools.functions import find_whl
1313
from ci_tools.parsing import ParsedSetup
1414

1515
logging.getLogger().setLevel(logging.INFO)

eng/tox/tox_helper_tasks.py

Lines changed: 0 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -73,76 +73,3 @@ def move_and_rename(source_location):
7373

7474
os.rename(source_location, new_location)
7575
return new_location
76-
77-
78-
def find_sdist(dist_dir, pkg_name, pkg_version):
79-
# This function will find a sdist for given package name
80-
if not os.path.exists(dist_dir):
81-
logging.error("dist_dir is incorrect")
82-
return
83-
84-
if pkg_name is None:
85-
logging.error("Package name cannot be empty to find sdist")
86-
return
87-
88-
pkg_name_format = "{0}-{1}.zip".format(pkg_name, pkg_version)
89-
packages = []
90-
for root, dirnames, filenames in os.walk(dist_dir):
91-
for filename in fnmatch.filter(filenames, pkg_name_format):
92-
packages.append(os.path.join(root, filename))
93-
94-
packages = [os.path.relpath(w, dist_dir) for w in packages]
95-
96-
if not packages:
97-
logging.error("No sdist is found in directory %s with package name format %s", dist_dir, pkg_name_format)
98-
return
99-
return packages[0]
100-
101-
102-
def find_whl(whl_dir, pkg_name, pkg_version):
103-
# This function will find a whl for given package name
104-
if not os.path.exists(whl_dir):
105-
logging.error("whl_dir is incorrect")
106-
return
107-
108-
if pkg_name is None:
109-
logging.error("Package name cannot be empty to find whl")
110-
return
111-
112-
pkg_name_format = "{0}-{1}*.whl".format(pkg_name.replace("-", "_"), pkg_version)
113-
whls = []
114-
for root, dirnames, filenames in os.walk(whl_dir):
115-
for filename in fnmatch.filter(filenames, pkg_name_format):
116-
whls.append(os.path.join(root, filename))
117-
118-
whls = [os.path.relpath(w, whl_dir) for w in whls]
119-
120-
if not whls:
121-
logging.error("No whl is found in directory %s with package name format %s", whl_dir, pkg_name_format)
122-
logging.info("List of whls in directory: %s", glob.glob(os.path.join(whl_dir, "*.whl")))
123-
return
124-
125-
if len(whls) > 1:
126-
# So we have whls specific to py version or platform since we are here
127-
py_version = "py{0}".format(sys.version_info.major)
128-
# filter whl for py version and check if we found just one whl
129-
whls = [w for w in whls if py_version in w]
130-
131-
# if whl is platform independent then there should only be one whl in filtered list
132-
if len(whls) > 1:
133-
# if we have reached here, that means we have whl specific to platform as well.
134-
# for now we are failing the test if platform specific wheels are found. Todo: enhance to find platform specific whl
135-
logging.error(
136-
"More than one whl is found in wheel directory for package {}. Platform specific whl discovery is not supported now".format(
137-
pkg_name
138-
)
139-
)
140-
sys.exit(1)
141-
142-
# Additional filtering based on arch type willbe required in future if that need arises.
143-
# for now assumption is that no arch specific whl is generated
144-
if len(whls) == 1:
145-
logging.info("Found whl {}".format(whls[0]))
146-
return whls[0]
147-
else:
148-
return None

scripts/devops_tasks/test_regression.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ def run(self):
125125

126126
self.whl_path = os.path.join(
127127
self.context.whl_directory,
128-
find_whl(pkg_name, self.context.pkg_version, self.context.whl_directory),
128+
find_whl(self.context.whl_directory, pkg_name, self.context.pkg_version),
129129
)
130130
if find_packages_missing_on_pypi(self.whl_path):
131131
logging.error("Required packages are not available on PyPI. Skipping regression test")

tools/azure-sdk-tools/ci_tools/functions.py

Lines changed: 124 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@
5959
"Omit_management": omit_mgmt,
6060
}
6161

62+
6263
def apply_compatibility_filter(package_set: List[str]) -> List[str]:
6364
"""
6465
This function takes in a set of paths to python packages. It returns the set filtered by compatibility with the currently running python executable.
@@ -393,39 +394,6 @@ def build_and_install_dev_reqs(file: str, pkg_root: str) -> None:
393394
shutil.rmtree(os.path.join(pkg_root, ".tmp_whl_dir"))
394395

395396

396-
def find_whl(package_name: str, version: str, whl_directory: str) -> str:
397-
"""Helper function to find where the built whl resides.
398-
399-
:param str package_name: the name of the package, e.g. azure-core
400-
:param str version: the version used to build the whl
401-
:param str whl_directory: the absolute path to the temp directory where the whls are built
402-
:return: The absolute path to the whl built
403-
"""
404-
if not os.path.exists(whl_directory):
405-
logging.error("Whl directory is incorrect")
406-
exit(1)
407-
408-
parsed_version = parse(version)
409-
410-
logging.info("Searching whl for package {0}-{1}".format(package_name, parsed_version.base_version))
411-
whl_name_format = "{0}-{1}*.whl".format(package_name.replace("-", "_"), parsed_version.base_version)
412-
whls = []
413-
for root, dirnames, filenames in os.walk(whl_directory):
414-
for filename in fnmatch.filter(filenames, whl_name_format):
415-
whls.append(os.path.join(root, filename))
416-
417-
whls = [os.path.relpath(w, whl_directory) for w in whls]
418-
419-
if not whls:
420-
logging.error(
421-
"whl is not found in whl directory {0} for package {1}-{2}".format(
422-
whl_directory, package_name, parsed_version.base_version
423-
)
424-
)
425-
exit(1)
426-
427-
return whls[0]
428-
429397
def build_whl_for_req(req: str, package_path: str) -> str:
430398
"""Builds a whl from the dev_requirements file.
431399
@@ -434,6 +402,7 @@ def build_whl_for_req(req: str, package_path: str) -> str:
434402
:return: The absolute path to the whl built or the requirement if a third-party package
435403
"""
436404
from ci_tools.build import create_package
405+
437406
if ".." in req:
438407
# Create temp path if it doesn't exist
439408
temp_dir = os.path.join(package_path, ".tmp_whl_dir")
@@ -446,9 +415,130 @@ def build_whl_for_req(req: str, package_path: str) -> str:
446415
logging.info("Building wheel for package {}".format(parsed.name))
447416
create_package(req_pkg_path, temp_dir, enable_sdist=False)
448417

449-
whl_path = os.path.join(temp_dir, find_whl(parsed.name, parsed.version, temp_dir))
418+
whl_path = os.path.join(temp_dir, find_whl(temp_dir, parsed.name, parsed.version))
450419
logging.info("Wheel for package {0} is {1}".format(parsed.name, whl_path))
451420
logging.info("Replacing dev requirement. Old requirement:{0}, New requirement:{1}".format(req, whl_path))
452421
return whl_path
453422
else:
454423
return req
424+
425+
426+
def find_sdist(dist_dir: str, pkg_name: str, pkg_version: str) -> str:
427+
"""This function attempts to look within a directory (and all subdirs therein) and find a source distribution for the targeted package and version."""
428+
# This function will find a sdist for given package name
429+
if not os.path.exists(dist_dir):
430+
logging.error("dist_dir is incorrect")
431+
return
432+
433+
if pkg_name is None:
434+
logging.error("Package name cannot be empty to find sdist")
435+
return
436+
437+
pkg_name_format = f"{pkg_name}-{pkg_version}.zip"
438+
pkg_name_format_alt = "${0}-{1}.tar.gz"
439+
440+
packages = []
441+
for root, dirnames, filenames in os.walk(dist_dir):
442+
for filename in fnmatch.filter(filenames, pkg_name_format):
443+
packages.append(os.path.join(root, filename))
444+
445+
packages = [os.path.relpath(w, dist_dir) for w in packages]
446+
447+
if not packages:
448+
logging.error("No sdist is found in directory %s with package name format %s", dist_dir, pkg_name_format)
449+
return
450+
return packages[0]
451+
452+
453+
def get_interpreter_compatible_tags() -> List[str]:
454+
"""
455+
This function invokes pip from the invoking interpreter and discovers which tags the interpreter is compatible with.
456+
"""
457+
458+
commands = [sys.executable, "-m", "pip", "debug", "--verbose"]
459+
460+
output = subprocess.run(
461+
commands,
462+
check=True,
463+
capture_output=True,
464+
).stdout.decode(encoding="utf-8")
465+
466+
tag_strings = output.split(os.linesep)
467+
468+
for index, value in enumerate(tag_strings):
469+
if "Compatible tags" in value:
470+
break
471+
472+
tags = tag_strings[index + 1 :]
473+
474+
return [tag.strip() for tag in tags if tag]
475+
476+
477+
def check_whl_against_tags(whl_name: str, tags: List[str]) -> bool:
478+
for tag in tags:
479+
if tag in whl_name:
480+
return True
481+
return False
482+
483+
484+
def find_whl(whl_dir: str, pkg_name: str, pkg_version: str) -> str:
485+
"""This function attempts to look within a directory (and all subdirs therein) and find a wheel that matches our targeted name and version AND
486+
whose compilation is compatible with the invoking interpreter."""
487+
if not os.path.exists(whl_dir):
488+
logging.error("whl_dir is incorrect")
489+
return
490+
491+
if pkg_name is None:
492+
logging.error("Package name cannot be empty to find whl")
493+
return
494+
495+
pkg_name_format = f"{pkg_name.replace('-', '_')}-{pkg_version}*.whl"
496+
whls = []
497+
498+
# todo: replace with glob, we aren't using py2 anymore!
499+
for root, dirnames, filenames in os.walk(whl_dir):
500+
for filename in fnmatch.filter(filenames, pkg_name_format):
501+
whls.append(os.path.join(root, filename))
502+
503+
whls = [os.path.relpath(w, whl_dir) for w in whls]
504+
505+
if not whls:
506+
logging.error("No whl is found in directory %s with package name format %s", whl_dir, pkg_name_format)
507+
logging.info("List of whls in directory: %s", glob.glob(os.path.join(whl_dir, "*.whl")))
508+
return
509+
510+
compatible_tags = get_interpreter_compatible_tags()
511+
512+
logging.debug("Dumping visible tags and whls")
513+
logging.debug(compatible_tags)
514+
logging.debug(whls)
515+
516+
if whls:
517+
# grab the first whl that matches a tag from our compatible_tags list
518+
for whl in whls:
519+
if check_whl_against_tags(whl, compatible_tags):
520+
logging.info(f"Found whl {whl}")
521+
return whl
522+
523+
# if whl is platform independent then there should only be one whl in filtered list
524+
if len(whls) > 1:
525+
# if we have reached here, that means we have whl specific to platform as well.
526+
# for now we are failing the test if platform specific wheels are found. Todo: enhance to find platform specific whl
527+
logging.error(f"We were unable to locate a compatible wheel for {pkg_name}")
528+
sys.exit(1)
529+
530+
return None
531+
532+
533+
def discover_prebuilt_package(dist_directory: str, setup_path: str, package_type: str) -> List[str]:
534+
"""Discovers a prebuild wheel or sdist for a given setup path."""
535+
packages = []
536+
pkg = ParsedSetup.from_path(setup_path)
537+
if package_type == "wheel":
538+
prebuilt_package = find_whl(dist_directory, pkg.name, pkg.version)
539+
else:
540+
prebuilt_package = find_sdist(dist_directory, pkg.name, pkg.version)
541+
542+
if prebuilt_package is not None:
543+
packages.append(prebuilt_package)
544+
return packages

tools/azure-sdk-tools/tests/integration/test_package_discovery.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,3 +109,4 @@ def test_discovery_honors_override():
109109
"azure-core-tracing-opentelemetry",
110110
"azure-mgmt-core",
111111
]
112+

0 commit comments

Comments
 (0)