Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
5ecc0e0
chore: clear MD warning + generate SWIG wrappers
zFernand0 Apr 23, 2026
1549e39
Merge branch 'main' of https://github.com/zowe/zowe-native-proto into…
zFernand0 May 4, 2026
fc03a68
wip: get further into the swig build process
zFernand0 May 4, 2026
2fa1c1d
chore: update doc/apis.md
zFernand0 May 7, 2026
28d1f32
Merge branch 'main' of https://github.com/zowe/zowe-native-proto into…
zFernand0 May 7, 2026
5b50006
Merge branch 'main' of https://github.com/zowe/zowe-native-proto into…
zFernand0 May 7, 2026
d718d04
Merge branch 'main' of https://github.com/zowe/zowe-native-proto into…
zFernand0 Jun 15, 2026
f4c45df
review: fix python bindings for jobs, and aother followup needed from…
zFernand0 Jun 15, 2026
2082a35
done: Finalize python bindings packing process
zFernand0 Jun 15, 2026
8234207
review: address PR comments :yum:
zFernand0 Jun 29, 2026
4846b03
Merge branch 'main' into zf/fix-py
zFernand0 Jun 29, 2026
11d4f2d
build: Create prerelease versions of the precopiled python bindings
zFernand0 Jun 29, 2026
5216f6c
review: address gh-sec comments :yum:
zFernand0 Jun 29, 2026
183363a
ci: test with z:test:python on Marist :yum:
zFernand0 Jun 29, 2026
90bfe40
chore: swap python-related scripts to have them grouped
zFernand0 Jun 29, 2026
a2e6e0f
wip: don't suppress tty
zFernand0 Jun 29, 2026
c11846b
wip: escape EOF?
zFernand0 Jun 29, 2026
6e1224e
Merge branch 'main' into zf/fix-py
zFernand0 Jun 30, 2026
c505514
chore: update zos-build's setup-credentials step
zFernand0 Jun 29, 2026
ee462d1
chore: reduce code duplication :yum:
zFernand0 Jun 30, 2026
6141592
chore: let py-bi tests to fail CI
zFernand0 Jul 13, 2026
4a05a98
Merge branch 'main' of https://github.com/zowe/zowe-native-proto into…
zFernand0 Jul 13, 2026
f0629ce
chore: fix python system test
zFernand0 Jul 13, 2026
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
2 changes: 1 addition & 1 deletion .github/workflows/zos-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ jobs:

- name: Set up Credentials
run: |
cat << EOF > config.yaml
cat << 'EOF' > config.yaml
activeProfile: default

profiles:
Expand Down
39 changes: 36 additions & 3 deletions .github/workflows/zos-py-build.yml

Copy link
Copy Markdown
Member

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.

Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ on:
required: true

permissions:
contents: read
pull-requests: write

jobs:
Expand All @@ -40,7 +41,7 @@ jobs:

- name: Set up Credentials
run: |
cat << EOF > config.yaml
cat << 'EOF' > config.yaml
activeProfile: default

profiles:
Expand All @@ -60,11 +61,43 @@ jobs:
- name: Upload Python Bindings
run: npm run z:upload

# SWIG is required to build the bindings from source. It is not always
# available on the remote z/OS system, so check first and branch on it.
- name: Check for SWIG on z/OS
id: swig
run: |
if npm run -s z:has:swig; then
echo "present=true" >> "$GITHUB_OUTPUT"
else
echo "present=false" >> "$GITHUB_OUTPUT"
fi

# Path A: SWIG present -> build the bindings from source as usual.
- name: Build Python Bindings
run: npm run z:build:python
if: steps.swig.outputs.present == 'true'
run: npm run z:python:build

# Path B: SWIG absent -> fetch the latest precompiled bundle posted to this
# PR (any author) and apply it so the tests can run without building.
- name: Fetch precompiled Python bindings
if: steps.swig.outputs.present != 'true'
env:
GH_TOKEN: ${{ github.token }}
run: npm run z:python:fetch -- ${{ github.event.pull_request.number }}

- name: Apply precompiled Python bindings
if: steps.swig.outputs.present != 'true' && hashFiles('dist/zbind_bin_dist.tar.gz') != ''
run: npm run z:python:apply

- name: Warn if no bindings available
if: steps.swig.outputs.present != 'true' && hashFiles('dist/zbind_bin_dist.tar.gz') == ''
run: echo "::warning::SWIG not found on z/OS and no 'Precompiled Python bindings' comment found on this PR — skipping Python tests."

# Run tests when we have bindings: either freshly built, or applied from a
# downloaded precompiled bundle.
- name: Test Python Bindings
run: npm run z:test:python
if: steps.swig.outputs.present == 'true' || hashFiles('dist/zbind_bin_dist.tar.gz') != ''
run: npm run z:python:test

- name: Clean up on z/OS
if: always()
Expand Down
172 changes: 86 additions & 86 deletions doc/apis.md

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions native/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ Check [Keep a Changelog](http://keepachangelog.com/) for recommendations on how
## Recent Changes

- `c`: Added support to return information for current linklist. [#1061](https://github.com/zowe/zowex/pull/1061)
- `python`: Added `package_precompiled.py` tool to package precompiled binary assets (equivalent to Python wheels) into `zbind_bin_dist.tar.gz` for instant compiler-free installation.
- `python`: Added `package_zbind.py` tool to package a clean, self-contained source-based distribution bundle (`zbind_src_dist.tar.gz`) containing all necessary headers, sources, and objects to build the bindings on any z/OS host without SWIG.
- `python`: Fixed compilation of Python bindings on z/OS by passing required Language Environment feature macros `_EXT` and `_OPEN_SYS_FILE_EXT` to the compiler in `setup.py`.

## `0.6.1`

Expand Down
2 changes: 1 addition & 1 deletion native/c/makefile
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ MTL_HEADERS=$(MTL_HEADERS_BASE) -I./chdsect

C_FLAGS=$(C_FLAGS_BASE) -I./chdsect
CPP_FLAGS=$(CPP_FLAGS_BASE) -I./chdsect
SWIG_FLAGS=$(SWIG_FLAGS_BASE) -I./chdsect -MMD -MP
SWIG_FLAGS=$(SWIG_FLAGS_BASE) -I./chdsect

# make -DBuildType=DEBUG or -DBuildType=RELEASE
.IF $(BuildType) == DEBUG
Expand Down
16 changes: 2 additions & 14 deletions native/c/zjb.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ struct ZJobDD
*/
int zjb_list_by_owner(ZJB *zjb, const std::string &owner_name, std::vector<ZJob> &jobs);

#ifndef SWIG
/**
* @brief Return a list of jobs from an input or default owner
*
Expand All @@ -65,6 +66,7 @@ int zjb_list_by_owner(ZJB *zjb, const std::string &owner_name, std::vector<ZJob>
* @return int 0 for success; non zero otherwise
*/
int zjb_list_by_owner(ZJB *zjb, const std::string &owner_name, const std::string &prefix_name, std::vector<ZJob> &jobs);
#endif

/**
* @brief Return a list of jobs from an input or default owner
Expand All @@ -79,20 +81,6 @@ int zjb_list_by_owner(ZJB *zjb, const std::string &owner_name, const std::string
*/
int zjb_list_by_owner(ZJB *zjb, const std::string &owner_name, const std::string &prefix_name, const std::string &status_name, std::vector<ZJob> &jobs);

#ifndef SWIG
/**
* @brief Return a list of jobs from an input or default owner
*
* @param zjb job returned attributes and error information
* @param owner_name owner name of the job to query, defaults to current user if == "", may use wild cards, i.e.
* "IBMUS*"
* @param prefix_name job prefix, defaults to "*" if == "", may use wild cards, i.e. "IBMUS*"
* @param jobs populated list returned containing job information array
* @return int 0 for success; non zero otherwise
*/
int zjb_list_by_owner(ZJB *zjb, const std::string &owner_name, const std::string &prefix_name, std::vector<ZJob> &jobs);
#endif

// Exclude status implementation for SWIG
#ifdef SWIG
extern "C"
Expand Down
1 change: 1 addition & 0 deletions native/c/zutm.c
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
* Copyright Contributors to the Zowe Project.
*
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
Expand Down
2 changes: 2 additions & 0 deletions native/python/bindings/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ all: swig-extenders build
# Environment variables for z/OS compatibility
_SWIG_ENV= \
export _BPXK_AUTOCVT=ON; \
export CC=ibm-clang64; \
export CXX=ibm-clang++64; \
export _CEE_RUNOPTS="FILETAG(AUTOCVT,AUTOTAG) POSIX(ON)" ; \
export _CC_CCMODE=1; \
export _CXX_CCMODE=1; \
Expand Down
76 changes: 76 additions & 0 deletions native/python/bindings/README.md
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 .
```
141 changes: 141 additions & 0 deletions native/python/bindings/package_precompiled.py
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

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Define a constant instead of duplicating this literal "==================================================" 3 times.

See more on https://sonarcloud.io/project/issues?id=zowe_zowe-native-proto&issues=AZ7JG3k4XrXjbuBKDK70&open=AZ7JG3k4XrXjbuBKDK70&pullRequest=968
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}")
Comment thread
zFernand0 marked this conversation as resolved.
sys.exit(1)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

sys is never imported, this may surface a NameError. Consider adding import sys to the top of this file


# 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("==================================================")
Loading
Loading