-
Notifications
You must be signed in to change notification settings - Fork 5
zowex(py): Fix python bindings build process #968
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
base: main
Are you sure you want to change the base?
Changes from all commits
5ecc0e0
1549e39
fc03a68
2fa1c1d
28d1f32
5b50006
d718d04
f4c45df
2082a35
8234207
4846b03
11d4f2d
5216f6c
183363a
90bfe40
a2e6e0f
c11846b
6e1224e
c505514
ee462d1
6141592
4a05a98
f0629ce
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,76 @@ | ||
| # Zowe Remote SSH Python Bindings Distributions | ||
|
|
||
| This directory contains utility scripts to bundle and distribute the Python bindings for the Zowe Remote SSH (ZRS) project on z/OS. | ||
|
|
||
| We provide two distinct packaging scripts, tailored for different deployment needs. Both bundles are completely system agnostic on z/OS and do **NOT** require SWIG to be installed on the target machine. | ||
|
|
||
| ## Distribution Types | ||
|
|
||
| ### 1. Precompiled Binary Distribution (`zbind_bin_dist.tar.gz`) | ||
| *Designed for end-users who just want to use the Python bindings immediately without compiling anything.* | ||
|
|
||
| - **Compiled on:** The build mainframe (e.g. `lpar.1`). | ||
| - **Dependencies needed on target:** Only Python (e.g. Python 3.11, 3.12, etc.). **No compiler, SWIG, C++ sources, or headers are required.** | ||
| - **How to Build:** | ||
| ```bash | ||
| python package_precompiled.py | ||
| ``` | ||
| This creates `zbind_bin_dist.tar.gz` containing precompiled shared libraries (`_*.so`), wrapper modules (`*.py`), and package metadata. | ||
|
|
||
| - **How to Extract & Use on Target Machine (e.g. `lpar.2`):** | ||
| 1. Transfer `zbind_bin_dist.tar.gz` to the target machine (binary mode). | ||
| 2. Unpack the tarball safely (disabling C-runtime autoconversion): | ||
| ```bash | ||
| chtag -b zbind_bin_dist.tar.gz | ||
| python3 -c "import gzip; f_in = gzip.open('zbind_bin_dist.tar.gz', 'rb'); f_out = open('zbind_bin_dist.tar', 'wb'); f_out.writelines(f_in); f_in.close(); f_out.close()" | ||
| chtag -b zbind_bin_dist.tar | ||
| tar -xf zbind_bin_dist.tar && rm zbind_bin_dist.tar | ||
| ``` | ||
| 3. Untag the files inside the extracted folder: | ||
| ```bash | ||
| chtag -r zbind_bin_dist/* | ||
| ``` | ||
| 4. Import instantly in your scripts using `sys.path`: | ||
| ```python | ||
| import sys | ||
| sys.path.insert(0, "/path/to/zbind_bin_dist") | ||
|
|
||
| from zusf_py import list_uss_dir | ||
| print(list_uss_dir("/tmp")) | ||
| ``` | ||
|
|
||
| ### 2. Self-Contained Source Distribution (`zbind_src_dist.tar.gz`) | ||
| *Designed for environments where you need to build and install the bindings locally from source (e.g. target machines with different or multiple Python versions), but without SWIG.* | ||
|
|
||
| - **Dependencies needed on target:** Python and `ibm-clang`. **SWIG is NOT required.** | ||
| - **How to Build:** | ||
| ```bash | ||
| python package_zbind.py | ||
| ``` | ||
| This creates `zbind_src_dist.tar.gz` containing all required C++ sources, headers, precompiled Metal C object files, and a flat-layout optimized `setup.py`. | ||
|
|
||
| - **How to Extract & Install on Target Machine:** | ||
| 1. Transfer `zbind_src_dist.tar.gz` to the target machine (binary mode). | ||
| 2. Unpack the tarball safely: | ||
| ```bash | ||
| chtag -b zbind_src_dist.tar.gz | ||
| python3 -c "import gzip; f_in = gzip.open('zbind_src_dist.tar.gz', 'rb'); f_out = open('zbind_src_dist.tar', 'wb'); f_out.writelines(f_in); f_in.close(); f_out.close()" | ||
| chtag -b zbind_src_dist.tar | ||
| tar -xf zbind_src_dist.tar && rm zbind_src_dist.tar | ||
| ``` | ||
| 3. Untag the C++ files inside the extracted directory so that autoconversion works: | ||
| ```bash | ||
| cd zbind_src_dist | ||
| chtag -r *.cpp *.hpp *.h *.cxx chdsect/*.h | ||
| ``` | ||
| 4. Build and install inside your target Python environment: | ||
| ```bash | ||
| export CC=ibm-clang64 | ||
| export CXX=ibm-clang++64 | ||
|
|
||
| # Option A: Build in-place | ||
| python setup.py build_ext --inplace | ||
|
|
||
| # Option B: Install via pip | ||
| pip install . | ||
| ``` |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,141 @@ | ||
| #!/usr/bin/env python3 | ||
|
|
||
| import os | ||
| import shutil | ||
| import subprocess | ||
|
|
||
| # Define paths relative to this script's directory | ||
| BINDINGS_DIR = os.path.dirname(os.path.abspath(__file__)) | ||
| PRECOMPILED_DIR = os.path.join(BINDINGS_DIR, "zbind_bin_dist") | ||
| TARBALL_PATH = os.path.join(BINDINGS_DIR, "zbind_bin_dist.tar.gz") | ||
|
|
||
| print("==================================================") | ||
|
Check failure on line 12 in native/python/bindings/package_precompiled.py
|
||
| print("Building precompiled Python bindings wheel-like bundle...") | ||
| print(f"Bindings directory: {BINDINGS_DIR}") | ||
| print(f"Target precompiled directory: {PRECOMPILED_DIR}") | ||
| print("==================================================") | ||
|
|
||
| # 1. Build the extensions in-place to get the compiled .so files | ||
| print("\n[Step 1] Building C++ extensions in-place...") | ||
| env = os.environ.copy() | ||
| env["CC"] = "ibm-clang64" | ||
| env["CXX"] = "ibm-clang++64" | ||
|
|
||
| # Clean any existing build artifacts first to ensure a fresh compile | ||
| if os.path.exists(os.path.join(BINDINGS_DIR, "build")): | ||
| shutil.rmtree(os.path.join(BINDINGS_DIR, "build")) | ||
|
|
||
| # Run the build command | ||
| subprocess.run(["python", "setup.py", "build_ext", "--inplace"], cwd=BINDINGS_DIR, env=env, check=True) | ||
|
|
||
| # 2. Clean previous precompiled directory | ||
| if os.path.exists(PRECOMPILED_DIR): | ||
| shutil.rmtree(PRECOMPILED_DIR) | ||
| os.makedirs(PRECOMPILED_DIR, exist_ok=True) | ||
|
|
||
| # 3. Copy the compiled .so files and Python wrapper modules | ||
| print("\n[Step 2] Packaging compiled binaries and Python files...") | ||
| py_files = ["zusf_py.py", "zds_py.py", "zjb_py.py"] | ||
|
|
||
| for filename in os.listdir(BINDINGS_DIR): | ||
| # Find compiled .so libraries | ||
| if filename.startswith("_") and filename.endswith(".so"): | ||
| src = os.path.join(BINDINGS_DIR, filename) | ||
| shutil.copy2(src, PRECOMPILED_DIR) | ||
| print(f"Packaged precompiled library: {filename}") | ||
|
|
||
| for filename in py_files: | ||
| src = os.path.join(BINDINGS_DIR, filename) | ||
| if os.path.exists(src): | ||
| shutil.copy2(src, PRECOMPILED_DIR) | ||
| print(f"Packaged Python wrapper: {filename}") | ||
| else: | ||
| print(f"Error: Python wrapper not found: {filename}") | ||
|
zFernand0 marked this conversation as resolved.
|
||
| sys.exit(1) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
|
|
||
| # 4. Create an __init__.py to allow importing as a standard python package | ||
| init_content = """# Zowe Remote SSH Python Bindings (Precompiled) | ||
|
|
||
| from .zusf_py import * | ||
| from .zds_py import * | ||
| from .zjb_py import * | ||
| """ | ||
| with open(os.path.join(PRECOMPILED_DIR, "__init__.py"), "w") as f: | ||
| f.write(init_content) | ||
| print("Created __init__.py inside precompiled package.") | ||
|
|
||
| # 5. Create a helpful README.md inside the bundle | ||
| readme_content = """# Zowe Remote SSH Python Bindings (Precompiled) | ||
|
|
||
| This bundle contains precompiled z/OS C++ shared libraries and Python wrapper modules. | ||
| Your users **do not need a compiler, C++ headers, or SWIG** to use this! They only need Python. | ||
|
|
||
| ## How to use this bundle: | ||
|
|
||
| ### Option A: Insert directory to sys.path (Recommended for simple scripts) | ||
| Unpack this bundle and add the path of the `zbind_bin_dist` directory to `sys.path` in your Python script: | ||
|
|
||
| ```python | ||
| import sys | ||
| import os | ||
|
|
||
| # Append the directory containing the files to sys.path | ||
| sys.path.insert(0, "/path/to/zbind_bin_dist") | ||
|
|
||
| # Now import the modules directly! | ||
| from zusf_py import list_uss_dir | ||
| from zjb_py import list_jobs_by_owner | ||
| from zds_py import list_data_sets | ||
| ``` | ||
|
|
||
| ### Option B: Use as a Python Package | ||
| Alternatively, you can place the `zbind_bin_dist` folder directly inside your application's directory and import it: | ||
|
|
||
| ```python | ||
| import zbind_bin_dist | ||
|
|
||
| # Access modules as sub-modules | ||
| print(zbind_bin_dist.zusf_py.list_uss_dir("/tmp")) | ||
| ``` | ||
| """ | ||
| with open(os.path.join(PRECOMPILED_DIR, "README.md"), "w") as f: | ||
| f.write(readme_content) | ||
| print("Created README.md inside precompiled package.") | ||
|
|
||
| # 6. Apply precise file tagging for z/OS compatibility | ||
| print("\n[Step 3] Applying precise file tagging for z/OS...") | ||
| # Tag Python files as ASCII | ||
| subprocess.run("chtag -t -c ISO8859-1 zbind_bin_dist/*.py zbind_bin_dist/*.md", shell=True, check=True, cwd=BINDINGS_DIR) | ||
| # Tag .so binary files as binary | ||
| subprocess.run("chtag -b zbind_bin_dist/*.so", shell=True, check=True, cwd=BINDINGS_DIR) | ||
|
|
||
| # 7. Package everything in a tarball using native tar and python gzip | ||
| print(f"\n[Step 4] Creating tarball {TARBALL_PATH}...") | ||
| if os.path.exists(TARBALL_PATH): | ||
| os.remove(TARBALL_PATH) | ||
|
|
||
| uncompressed_tar = TARBALL_PATH[:-3] | ||
| if os.path.exists(uncompressed_tar): | ||
| os.remove(uncompressed_tar) | ||
|
|
||
| # Create raw tar archive | ||
| subprocess.run(f"tar -cf {uncompressed_tar} -C {BINDINGS_DIR} zbind_bin_dist", shell=True, check=True, cwd=BINDINGS_DIR) | ||
|
|
||
| # Tag tar archive as binary to disable C runtime autoconversion during read/write | ||
| subprocess.run(f"chtag -b {uncompressed_tar}", shell=True, check=True, cwd=BINDINGS_DIR) | ||
|
|
||
| # Compress using Python's built-in gzip module | ||
| import gzip | ||
| with open(uncompressed_tar, 'rb') as f_in: | ||
| with gzip.open(TARBALL_PATH, 'wb') as f_out: | ||
| f_out.writelines(f_in) | ||
|
|
||
| os.remove(uncompressed_tar) | ||
|
|
||
| # Explicitly tag the tarball as binary | ||
| subprocess.run(f"chtag -b {TARBALL_PATH}", shell=True, check=True, cwd=BINDINGS_DIR) | ||
|
|
||
| print("\n==================================================") | ||
| print("Successfully packaged precompiled Python bindings!") | ||
| print(f"Precompiled Bundle: {TARBALL_PATH}") | ||
| print("==================================================") | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If SWIG is absent and no precompiled bundle has been posted, the workflow emits
::warning::and skips the Python test step rather than failing. Do we plan to resolve this in a separate PR? I noticed the TODO around fixing automated tests so I wasn't sure if we wanted to address that here.