Skip to content

Commit

Permalink
Support specifying the wheel platform
Browse files Browse the repository at this point in the history
Calling `python -m build --wheel -C--build-option=-Pwindows` now creates
dist/warp_lang-0.9.0-py3-none-win_amd64.whl, even if warp/bin/ contains
libraries other than .dll.

Likewise -C--build-option=-Plinux and -C--build-option=-Pmacos package
just the .so and .dylib libraries, respectively. This enables creating
three platform-specific wheels from a single artifact archive containing
binary libraries for all three platforms.

Executing just `python -m build --wheel` is still supported and will
select a single platform, based on the libraries it detects. This is
useful for creating a wheel from an artifact archive containing
libraries for a single platform.
  • Loading branch information
c0d1f1ed committed Jun 7, 2023
1 parent 4ba34d9 commit 5dd61aa
Show file tree
Hide file tree
Showing 3 changed files with 114 additions and 22 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,5 @@ build/lib/
.venv/
exts/omni.warp/config/extension.gen.toml
/external/llvm-project
/build
/dist
11 changes: 8 additions & 3 deletions PACKAGING.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,9 +71,14 @@ Release Steps:

2) Run `cd warp`

2) Run `python -m build`

3) Run `python -m twine upload dist/*`
3) Run
```bash
python -m build --wheel -C--build-option=-Pwindows &&
python -m build --wheel -C--build-option=-Plinux &&
python -m build --wheel -C--build-option=-Pmacos
```

4) Run `python -m twine upload dist/*`

* user: `__token__`
* pass: `(your token string from pypi)`
Expand Down
123 changes: 104 additions & 19 deletions setup.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,99 @@
import setuptools
import os
import shutil
import argparse

from typing import NamedTuple
from wheel.bdist_wheel import bdist_wheel

# Parse --build-option arguments meant for the bdist_wheel command. We have to parse these
# ourselves because when bdist_wheel runs it's too late to select a subset of libraries for package_data.
parser = argparse.ArgumentParser(exit_on_error=False)
parser.add_argument("command")
parser.add_argument("--platform", "-P", type=str, default="", help="Wheel platform: windows|linux|macos")
args = parser.parse_known_args()[0]


class Platform(NamedTuple):
name: str
fancy_name: str
extension: str
tag: str


def get_warp_platform():
platforms = [
Platform("windows", "Windows", ".dll", "win_amd64"),
Platform("linux", "Linux", ".so", "manylinux2014_x86_64"),
Platform("macos", "macOS", ".dylib", "macosx_10_13_x86_64"),
]


def detect_warp_platforms():
detected_platforms = set()
for filename in os.listdir("warp/bin"):
if os.path.splitext(filename)[1] == ".dll":
return "win_amd64"
if os.path.splitext(filename)[1] == ".so":
return "manylinux2014_x86_64"
if os.path.splitext(filename)[1] == ".dylib":
return "macosx_10_13_x86_64"
for p in platforms:
if os.path.splitext(filename)[1] == p.extension:
detected_platforms.add(p)

if len(detected_platforms) == 0:
raise Exception("No libraries found in warp/bin. Please run build_lib.py first.")

return detected_platforms


wheel_platform = None

if args.command == "bdist_wheel":
detected_platforms = detect_warp_platforms()

if args.platform != "":
for p in platforms:
if args.platform == p.name or args.platform == p.fancy_name:
wheel_platform = p
print(f"Platform argument specified for building {p.fancy_name} wheel")
break

if wheel_platform is None:
print(f"Platform argument '{args.platform}' not recognized")
elif wheel_platform not in detected_platforms:
print(f"No libraries found for {wheel_platform.fancy_name}")
print(f"Falling back to auto-detection")
wheel_platform = None

if wheel_platform is None:
if len(detected_platforms) > 1:
print("Libraries for multiple platforms were detected. Picking the first one.")
print("Run `python -m build --wheel -C--build-option=-P[windows|linux|macos]` to select a specific one.")
wheel_platform = next(iter(detected_platforms))

raise Exception("No libraries found in warp/bin")
print("Creating Warp wheel for " + wheel_platform.fancy_name)


# Binary wheel distribution builds assume that the platform you're building on will be the platform
# of the package. This class overrides the platform tag.
# https://packaging.python.org/en/latest/specifications/platform-compatibility-tags
class WarpBDistWheel(bdist_wheel):
# Even though we parse the platform argument ourselves, we need to declare it here as well so
# setuptools.Command can validate the command line options.
user_options = bdist_wheel.user_options + [
("platform=", "P", "Wheel platform: windows|linux|macos"),
]

def initialize_options(self):
super().initialize_options()
self.platform = ""

def get_tag(self):
# The wheel's complete tag format is {python tag}-{abi tag}-{platform tag}.
return "py3", "none", wheel_platform.tag

def run(self):
super().run()

# Clean up so we can re-invoke `py -m build --wheel -C--build-option=--platform=...`
# See https://github.com/pypa/setuptools/issues/1871 for details.
shutil.rmtree("./build", ignore_errors=True)
shutil.rmtree("./warp_lang.egg-info", ignore_errors=True)


# Distributions are identified as non-pure (i.e. containing non-Python code, or binaries) if the
Expand All @@ -23,17 +104,19 @@ def has_ext_modules(self):
return True


# Binary wheel distribution builds assume that the platform you're building on will be the platform
# of the package. This factory function provides a class which overrides the platform tag.
# https://packaging.python.org/en/latest/specifications/platform-compatibility-tags
def bdist_factory(platform_tag: str):
class warp_bdist(bdist_wheel):
def get_tag(self, *args, **kws):
# The tag format is {python tag}-{abi tag}-{platform tag}.
return "py3", "none", platform_tag
def get_warp_libraries(extension):
libraries = []
for filename in os.listdir("warp/bin"):
if os.path.splitext(filename)[1] == extension:
libraries.append("bin/" + filename)

return libraries

return warp_bdist

if wheel_platform is not None:
warp_binary_libraries = get_warp_libraries(wheel_platform.extension)
else:
warp_binary_libraries = [] # Not needed during egg_info command

setuptools.setup(
name="warp-lang",
Expand All @@ -57,8 +140,8 @@ def get_tag(self, *args, **kws):
"native/clang/*.cpp",
"native/nanovdb/*.h",
"tests/assets/*",
"bin/*",
]
+ warp_binary_libraries,
},
classifiers=[
"Programming Language :: Python :: 3.7",
Expand All @@ -70,7 +153,9 @@ def get_tag(self, *args, **kws):
"Operating System :: OS Independent",
],
distclass=BinaryDistribution,
cmdclass={"bdist_wheel": bdist_factory(get_warp_platform())},
cmdclass={
"bdist_wheel": WarpBDistWheel,
},
install_requires=["numpy"],
python_requires=">=3.7",
)

0 comments on commit 5dd61aa

Please sign in to comment.