Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Finish out of tree build system (except xbuildenv deploy) #2823

Merged
merged 43 commits into from
Jul 6, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
f3ab3d2
Respect symbol visibility in export calculation
hoodmane Jul 1, 2022
480d7cc
Add a test
hoodmane Jul 1, 2022
f4eebdf
Work on out of tree builds
hoodmane Jun 29, 2022
f1c10bd
Set up download + install
hoodmane Jun 30, 2022
1566861
Try to get pywasmbuild entrypoint working
hoodmane Jun 30, 2022
0055269
Chmod pywasmcross.py
hoodmane Jun 30, 2022
5c65533
Try to fix entry point
hoodmane Jun 30, 2022
2cb47b3
Fix pywasmcross when accessed from entrypoint
hoodmane Jun 30, 2022
e4c30a3
More work to get pywasmcross to work from entrypoint
hoodmane Jun 30, 2022
0f68b04
Cleanup pywasmcross prelude a bit
hoodmane Jun 30, 2022
4996fe5
use exports=requested
hoodmane Jul 1, 2022
1d59947
Rearrange cli a bit
hoodmane Jul 1, 2022
66e37ce
Fix calculate_object_exports
hoodmane Jul 1, 2022
02cf02c
Merge branch 'symbol-visibility' into out-of-tree
hoodmane Jul 1, 2022
109c89d
Set exports default to requested
hoodmane Jul 1, 2022
e7ffd7e
cleanup
hoodmane Jul 1, 2022
9811c7d
Fix again
hoodmane Jul 1, 2022
d93690c
Merge branch 'symbol-visibility' into out-of-tree
hoodmane Jul 1, 2022
9229d5b
Add version argument to download_xbuild_env
hoodmane Jul 1, 2022
3371a0d
Fix pywasmcross so that it works when pyodide_build is installed out …
hoodmane Jul 1, 2022
9f1a6b7
Tune up
hoodmane Jul 1, 2022
1c1bd37
Rename a few things
hoodmane Jul 1, 2022
abd658a
Merge branch 'out-of-tree-pywasmcross' into out-of-tree
hoodmane Jul 1, 2022
d4e7f88
More rearrange
hoodmane Jul 1, 2022
8ae3acb
Update logic again
hoodmane Jul 1, 2022
5f4042c
Rework yet again
hoodmane Jul 1, 2022
49d5d5f
Reduce diff
hoodmane Jul 1, 2022
4b4da1b
Don't print exported symbols
hoodmane Jul 1, 2022
8fed837
Merge branch 'symbol-visibility' into out-of-tree
hoodmane Jul 1, 2022
6e163a2
Fall back to nm for bitcode
hoodmane Jul 2, 2022
cb8a022
Merge branch 'symbol-visibility' into out-of-tree
hoodmane Jul 2, 2022
9f32585
Update test
hoodmane Jul 2, 2022
d5328b9
Merge branch 'symbol-visibility' into out-of-tree
hoodmane Jul 2, 2022
7a0c0b4
Merge branch 'main' into out-of-tree
hoodmane Jul 2, 2022
1fa76e9
Merge branch 'main' into symbol-visibility
hoodmane Jul 2, 2022
c261ad5
Merge branch 'main' into symbol-visibility
hoodmane Jul 2, 2022
84b37ce
Revert debug changes
hoodmane Jul 2, 2022
8d8ccd2
Merge branch 'symbol-visibility' into out-of-tree
hoodmane Jul 2, 2022
9f1084b
Remove replace_libs argument
hoodmane Jul 2, 2022
6e4566b
Add unisolated.txt to xbuildenv
hoodmane Jul 3, 2022
b5f6b87
New build environment version
hoodmane Jul 3, 2022
fa0f5ea
Merge branch 'main' into out-of-tree
hoodmane Jul 4, 2022
1043040
Revert one change
hoodmane Jul 4, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 20 additions & 12 deletions pyodide-build/pyodide_build/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -229,11 +229,14 @@ def init_environment() -> None:
os.environ["PYODIDE_ROOT"] = str(search_pyodide_root(os.getcwd()))

os.environ.update(get_make_environment_vars())
hostsitepackages = get_hostsitepackages()
pythonpath = [
hostsitepackages,
]
os.environ["PYTHONPATH"] = ":".join(pythonpath)
try:
hostsitepackages = get_hostsitepackages()
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This call will fail if we haven't installed the xbuildenv yet.

pythonpath = [
hostsitepackages,
]
os.environ["PYTHONPATH"] = ":".join(pythonpath)
except KeyError:
pass
os.environ["BASH_ENV"] = ""
get_unisolated_packages()

Expand All @@ -251,13 +254,18 @@ def get_unisolated_packages() -> list[str]:
if "UNISOLATED_PACKAGES" in os.environ:
return json.loads(os.environ["UNISOLATED_PACKAGES"])
PYODIDE_ROOT = get_pyodide_root()
unisolated_packages = []
for pkg in (PYODIDE_ROOT / "packages").glob("**/meta.yaml"):
config = parse_package_config(pkg, check=False)
if config.get("build", {}).get("cross-build-env", False):
unisolated_packages.append(config["package"]["name"])
# TODO: remove setuptools_rust from this when they release the next version.
unisolated_packages.append("setuptools_rust")
unisolated_file = PYODIDE_ROOT / "unisolated.txt"
if unisolated_file.exists():
# in xbuild env, read from file
unisolated_packages = unisolated_file.read_text().splitlines()
else:
unisolated_packages = []
for pkg in (PYODIDE_ROOT / "packages").glob("**/meta.yaml"):
config = parse_package_config(pkg, check=False)
if config.get("build", {}).get("cross-build-env", False):
unisolated_packages.append(config["package"]["name"])
# TODO: remove setuptools_rust from this when they release the next version.
unisolated_packages.append("setuptools_rust")
os.environ["UNISOLATED_PACKAGES"] = json.dumps(unisolated_packages)
return unisolated_packages

Expand Down
7 changes: 5 additions & 2 deletions pyodide-build/pyodide_build/create_xbuildenv.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import subprocess
from pathlib import Path

from .common import get_make_flag, get_pyodide_root
from .common import get_make_flag, get_pyodide_root, get_unisolated_packages
from .io import parse_package_config


Expand Down Expand Up @@ -44,7 +44,7 @@ def copy_wasm_libs(xbuildenv_path: Path) -> None:
wasm_lib_dir = get_relative_path(pyodide_root, "WASM_LIBRARY_DIR")
sysconfig_dir = get_relative_path(pyodide_root, "SYSCONFIGDATA_DIR")
xbuildenv_root = xbuildenv_path / "pyodide-root"
xbuildenv_path.mkdir()
xbuildenv_path.mkdir(exist_ok=True)
to_copy: list[Path] = [
pythoninclude,
sysconfig_dir,
Expand Down Expand Up @@ -89,3 +89,6 @@ def main(args: argparse.Namespace) -> None:
stdout=subprocess.PIPE,
)
(xbuildenv_path / "requirements.txt").write_bytes(res.stdout)
(xbuildenv_path / "pyodide-root/unisolated.txt").write_text(
"\n".join(get_unisolated_packages())
)
27 changes: 25 additions & 2 deletions pyodide-build/pyodide_build/install_xbuildenv.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,27 @@ def make_parser(parser: argparse.ArgumentParser) -> argparse.ArgumentParser:
"on numpy or scipy.\n"
"Note: this is a private endpoint that should not be used outside of the Pyodide Makefile."
)
parser.add_argument("--download", action="store_true", help="Download xbuild env")
parser.add_argument("xbuild_env", type=str, nargs=1)
return parser


def main(args: argparse.Namespace) -> None:
xbuildenv_path = Path(args.xbuild_env[0])
def download_xbuild_env(version: str, xbuildenv_path: Path) -> None:
from shutil import rmtree, unpack_archive
from tempfile import NamedTemporaryFile
from urllib.request import urlretrieve

rmtree(xbuildenv_path, ignore_errors=True)
with NamedTemporaryFile(suffix=".tar") as f:
urlretrieve(
f"http://pyodide-cache.s3-website-us-east-1.amazonaws.com/xbuildenv/{version}.tar",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this your personal S3 bucket?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Roman set it up. We could use it for other things too.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, as long as it remains a bit of dev oriented cache and we make no quality of service promises, I think it should be OK. Eventually, this could probably be also done with github releases though that's harder for nightly deployments.

Also if you can make it a .tar.gz to reduce the bandwidth that would be better.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also is there anything platform/OS dependent in this tar? For instance, I see a,

xbuildenv/site-packages-extras/numpy/core/lib/libnpymath.a

is this intentional?

Should we add the linux-x86_64 suffix to indicate that it's linux specific? And also maybe name it something more explicit than "2.tar" otherwise it's not ideal when downloaded manually.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not linux specific, it's an Emscripten static library.

2.tar

I was thinking about naming them by version e.g., 0.21.0.tar.gz. But probably github releases is a good idea for the release versions.

f.name,
)
unpack_archive(f.name, xbuildenv_path)


def install_xbuild_env(xbuildenv_path: Path) -> None:
xbuildenv_path = xbuildenv_path / "xbuildenv"
pyodide_root = get_pyodide_root()
xbuildenv_root = xbuildenv_path / "pyodide-root"
host_site_packages = xbuildenv_root / Path(
Expand All @@ -42,3 +57,11 @@ def main(args: argparse.Namespace) -> None:
shutil.copytree(
xbuildenv_path / "site-packages-extras", host_site_packages, dirs_exist_ok=True
)


def main(args: argparse.Namespace) -> None:
xbuildenv_path = Path(args.xbuild_env[0])
version = "2"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is your future plan of versioning build envs?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One version per Pyodide release. After we merge this I am planning to add a CI job to deploy the cross build environment as part of deploy-release. It would also be nice to periodically deploy a "nightly" build at some point. Maybe once a month or once every other week or something.

if args.download:
download_xbuild_env(version, xbuildenv_path)
install_xbuild_env(xbuildenv_path)
Empty file.
36 changes: 36 additions & 0 deletions pyodide-build/pyodide_build/out_of_tree/__main__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import argparse
import os
from pathlib import Path

from . import build


def ensure_env_installed(env: Path) -> None:
if env.exists():
return
from ..install_xbuildenv import download_xbuild_env, install_xbuild_env

version = "2"
download_xbuild_env(version, env)
install_xbuild_env(env)


def main():
main_parser = argparse.ArgumentParser(prog="pywasmbuild")
main_parser.description = "Tools for creating Python extension modules for the wasm32-unknown-emscripten platform"
subparsers = main_parser.add_subparsers(help="action")
for module in [build]:
modname = module.__name__.rpartition(".")[-1]
parser = module.make_parser(subparsers.add_parser(modname))
parser.set_defaults(func=module.main)

env = Path(".pyodide-xbuildenv")
os.environ["PYODIDE_ROOT"] = str(env / "xbuildenv/pyodide-root")
ensure_env_installed(env)

args = main_parser.parse_args()
if hasattr(args, "func"):
# run the selected action
args.func(args)
else:
main_parser.print_help()
47 changes: 47 additions & 0 deletions pyodide-build/pyodide_build/out_of_tree/build.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import argparse
import os

from .. import common, pypabuild, pywasmcross


def run(exports, args):
cflags = common.get_make_flag("SIDE_MODULE_CFLAGS")
cflags += f" {os.environ.get('CFLAGS', '')}"
cxxflags = common.get_make_flag("SIDE_MODULE_CXXFLAGS")
cxxflags += f" {os.environ.get('CXXFLAGS', '')}"
ldflags = common.get_make_flag("SIDE_MODULE_LDFLAGS")
ldflags += f" {os.environ.get('LDFLAGS', '')}"
build_env_ctx = pywasmcross.get_build_env(
env=os.environ.copy(),
pkgname="",
cflags=cflags,
cxxflags=cxxflags,
ldflags=ldflags,
target_install_dir="",
exports=exports,
)

with build_env_ctx as env:
pypabuild.build(env, " ".join(args))


def main(parser_args: argparse.Namespace) -> None:
run(parser_args.exports, parser_args.backend_args)


def make_parser(parser: argparse.ArgumentParser) -> argparse.ArgumentParser:
parser.description = "Use pypa/build to build a Python package."
parser.add_argument(
"--exports",
choices=["pyinit", "requested", "whole_archive"],
default="requested",
help="Which symbols should be exported when linking .so files?",
)
parser.add_argument(
"backend_args",
metavar="args",
type=str,
nargs=argparse.REMAINDER,
help="Arguments to pass on to the backend",
)
return parser
1 change: 1 addition & 0 deletions pyodide-build/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
setuptools.setup(
entry_points={
"console_scripts": [
"pywasm = pyodide_build.out_of_tree.__main__:main",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a bit confusing with https://github.com/mohanson/pywasm

So I guess it would be a bit difficult to use subcommands here. Sill maybe something like pyodide-build-wasm even if it's a bit more verbose? pywasmbuild would also be better. Ideally, it would be to have pyodide in the name but maybe it will be too complex of a name if we include it.

Copy link
Member

@rth rth Jul 6, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Although if you are calling it as pywasm build (I missed the subcommand part), build in the name would indeed be a bit redundant.

Maybe we should just call this command pyodide and make this the new CLI API? So it would be pyodide build when called in CI of other projects. Or pyodide cross-build or any similar sub-command.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The main distinction in my mind is between in-tree and out-of-tree commands. E.g., mkpkg should be a public in-tree command, whereas it makes no sense out of tree. This is an out of tree command. People are likely to only need one CLI as appropriate to whether they are doing an in tree or out of tree build. So I think it makes sense to keep these as separate CLIs.

"_pywasmcross = pyodide_build.pywasmcross:compiler_main",
]
}
Expand Down